refactor: deepen seams from the 2026-06-09 architecture review#20
Merged
Conversation
Land the 2026-06-09 review (hm-x9r, hm-i9k, hm-y36, hm-x7z, hm-cpc, hm-dwr, hm-a16): - Context owns project-config resolution (ctx.project_config(), memoized) — fixes five sites that ignored --project (generate, codegen x2, export, check, TUI health) - OutputResolver: one decrypt-once scan behind open(); tri-state env_map makes exec's label path a one-liner; generate/codegen stop decrypting the store twice - exec accepts multiple refs (union; conflicting values hard-error) - ADR-0001: keep last-wins collapse in file generators; codegen gains generate's duplicate-key warning - StoreOps owns the mutation chain (commit/push/completions refresh) for both the CLI dispatcher and TUI cores — TUI delete/rekey no longer leave the store dirty; join no longer prints into ratatui - KeyRegistry: exhaustive-match rows derive dispatch, live help, and palette shortcuts; envs config key renamed outputs (serde alias) - PathFolding/ResultSort/StoreHealth graduate out of the search view as terminal-free modules Also fixes: completions cache missed same-second deletions (mtime fingerprint now nanos + entry count); Ctrl+Space chords could not roundtrip the config format; viewer rekey targeted the ambient store instead of the secret's own. Gates: 691 lib + 155 integration tests, clippy clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI gates on `cargo fmt -- --check`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR applies the 2026-06-09 architecture review outcomes by deepening core seams in the Rust CLI and TUI: Context-owned/memoized project-config resolution, a unified OutputResolver pipeline for outputs→env/value materialization, centralized StoreOps mutation chaining, and a KeyRegistry-driven keybinding/help/palette system. It also extracts search-view state and store-health logic into reusable modules, and adds integration tests to lock in the new behaviors.
Changes:
- Centralizes project config resolution in
Context::project_config()(lazy, memoized acrossContextclones) and updates commands/views to use it. - Introduces
OutputResolverfor a single decrypt-once scan and consistent outputs label / selector / alias resolution acrossexec,generate, andcodegen(including newexecmulti-ref union behavior). - Adds
StoreOpsmutation cores + KeyRegistry, and graduatesPathFolding/ResultSort/StoreHealthinto dedicated modules with updated TUI help/palette integration.
Reviewed changes
Copilot reviewed 45 out of 45 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/integration/cli_test.rs | Adds integration coverage for --project resolution, exec multi-ref semantics, and codegen duplicate-key warnings. |
| rust/src/tui/widgets/store_health.rs | New reusable StoreHealth widget + project-store resolution via ctx.project_config(). |
| rust/src/tui/widgets/mod.rs | Exposes the new store_health widget module. |
| rust/src/tui/views/secret_viewer.rs | Routes rekey/delete/set mutations through store_ops and scopes mutations to the viewed secret’s store; live-keymap help entries. |
| rust/src/tui/views/search.rs | Extracts folding/sorting/health into modules; makes more actions keymap-driven; uses store_ops for join/rekey. |
| rust/src/tui/views/remote_add.rs | Updates test contexts to include project_config_cell. |
| rust/src/tui/views/recipient_list.rs | Switches TUI recipient mutations to store_ops; updates tests for new context field. |
| rust/src/tui/views/recipient_add.rs | Switches recipient add to store_ops; updates tests for new context field. |
| rust/src/tui/views/outputs.rs | Ensures cloned contexts carry project_config_cell; updates tests. |
| rust/src/tui/views/new_secret.rs | Switches secret creation to store_ops::set_secret; live-keymap help entries. |
| rust/src/tui/views/help.rs | Makes HelpView accept owned dynamic rows (enables live keymap-derived help). |
| rust/src/tui/views/command_palette.rs | Derives palette shortcut/action linkage via KeyRegistry rows. |
| rust/src/tui/model/result_sort.rs | New pure sorting module extracted from Search view, with unit tests. |
| rust/src/tui/model/path_folding.rs | New pure folding/grouping module extracted from Search view, with unit tests. |
| rust/src/tui/model/mod.rs | Introduces tui::model module namespace for pure state modules. |
| rust/src/tui/mod.rs | Wires in tui::model; updates contexts in init flow/tests for new config memo cell. |
| rust/src/tui/keymap.rs | Adds KeyRegistry (rows/scopes/palette links), new search actions, envs→outputs rename w/ serde alias, and space roundtripping. |
| rust/src/tui/harness.rs | Updates TUI harness test contexts to include project_config_cell. |
| rust/src/tui/app.rs | Builds help overlays from live keymap-derived rows; ensures cloned contexts carry memo cell. |
| rust/src/config/mod.rs | Makes raw project-config loaders pub(crate) and documents Context ownership of selection/memoization. |
| rust/src/completions_cache.rs | Improves cache invalidation fingerprinting (nanosecond mtime + entry count hashing). |
| rust/src/cli/sync.rs | Ensures derived contexts carry project_config_cell. |
| rust/src/cli/store_ops.rs | New StoreOps mutation seam: shared finalize chain and silent mutation cores with tests. |
| rust/src/cli/set.rs | Exposes encrypt_and_write to sibling modules (used by StoreOps). |
| rust/src/cli/search.rs | Adds Default for SearchResult to support new pure-model test helpers. |
| rust/src/cli/schema.rs | Updates test contexts to include project_config_cell. |
| rust/src/cli/resolver.rs | Removes now-redundant candidate-building helper from SecretResolver. |
| rust/src/cli/recipient.rs | Removes TUI-facing wrappers; exposes silent cores to store_ops; updates tests accordingly. |
| rust/src/cli/output_resolver.rs | Adds OutputResolver (open/env_map/decode) with invariants and extensive unit tests. |
| rust/src/cli/mod.rs | Adds Context::project_config() w/ shared memo cell; moves mutation finalization to store_ops; seeds memo during construction. |
| rust/src/cli/join.rs | Splits join into silent core + presentation wrapper; returns structured outcomes. |
| rust/src/cli/init.rs | Ensures derived contexts carry project_config_cell. |
| rust/src/cli/import.rs | Updates test contexts to include project_config_cell. |
| rust/src/cli/get.rs | Extracts channel-free expiry_message helper used by OutputResolver. |
| rust/src/cli/generate.rs | Uses OutputResolver for outputs resolution and value decode; keeps last-wins + warning behavior. |
| rust/src/cli/export.rs | Switches project config loading to ctx.project_config(). |
| rust/src/cli/exec.rs | Adds multi-ref union semantics, requires --, integrates OutputResolver tri-state label resolution. |
| rust/src/cli/doctor.rs | Updates test contexts to include project_config_cell. |
| rust/src/cli/completions.rs | Updates zsh completion patching for new exec positional refs shape. |
| rust/src/cli/codegen.rs | Uses OutputResolver for outputs resolution + decode; adds duplicate-key warning parity with generate. |
| rust/src/cli/check.rs | Uses ctx.project_config() for store discovery. |
| README.md | Updates exec docs for multi-ref usage and keymap action list changes. |
| docs/ARCHITECTURE.md | Documents exec multi-ref union/conflict semantics. |
| docs/adr/0001-keep-last-wins-collapse-in-file-generators.md | Adds ADR formalizing last-wins behavior with warning parity. |
| CONTEXT.md | Records architecture terms and the deepened seams introduced by this PR. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The old candidate builder used load_identities().unwrap_or_default(), so codegen --lang worked on machines with no age identity at all; open() propagated the error instead. Restore the tolerant scan (no identities -> no tags -> tag selectors match nothing, mirroring ls --tag) and the tolerant store listing. Needing a value still fails hard at env_map/decode time. Caught by CI only: local runs masked the regression via the developer's ~/.config/sops/age fallback keys. New regression test removes the on-disk key explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- HelpView::draw borrows row descriptions instead of cloning per frame - resolve_selector_env doc: ExecEmptyMatch fires only on zero path candidates; post-filter emptiness is the caller's policy Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeQL jobs failed with 'Requires authentication' while uploading status reports (runner-side token outage, same window as local gh 401s); the run is marked non-retryable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- store_ops::set_secret: a qualified ref writes to its own store; the mutation chain (commit/push/completions refresh) now targets that effective store instead of leaving it dirty and refreshing the ambient cache - store_health: the project-store check no longer inherits the ACTIVE store's recipients_path override (false NotRecipient); it resolves store-internal config first, then the project config override, mirroring the dispatcher Both from Copilot review on PR #20; the second is pre-existing behavior surfaced by the widget extraction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment on lines
50
to
+54
| let secrets_dir = store.join(".himitsu").join("secrets"); | ||
| let current_mtime = max_mtime_recursive(&secrets_dir); | ||
|
|
||
| // Check whether the tree has changed since the last cache build. | ||
| let stored_mtime: i64 = conn | ||
| // Check whether the tree has changed since the last cache build. The | ||
| // stored value is the bit-cast fingerprint; a missing row means "never |
Comment on lines
+48
to
+50
| /// Shared memo cell for the lazily-loaded project config. `Default` yields | ||
| /// an empty cell; `Context` clones share one cell, so the config loads (and | ||
| /// the legacy `envs:` migration warning fires) at most once per process. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Implements all seven beads from the 2026-06-09 architecture review (design decisions recorded in
CONTEXT.md; each bead's close note carries the full spec):hm-x9r— Context owns project-config resolution.ctx.project_config()is lazy + memoized (clones share the memo; the dispatcher seeds it in project mode). Fixes the latent--projectbug class at five sites that still cwd-walked:generate,codegen×2,export,check, and the TUI health sidebar. The raw loaders are nowpub(crate)internals;migrate's multi-root scan is the one sanctioned direct caller.hm-i9k— OutputResolver (rust/src/cli/output_resolver.rs). One module owns projectoutputs:map → candidates-with-decrypted-tags → selector/alias resolution → decoded values.open()is the single decrypt-once scan (zero I/O when no outputs are defined); tri-stateenv_map()replaces exec's ~105-line hand-rolled pipeline;decode()serves generate/codegen, which stop decrypting the store twice. Deletesresolver_candidates_with_tagsandSecretResolver::resolve_candidates.hm-y36— exec accepts multiple refs.himitsu exec tag:pci prod/db -- cmdinjects the union; every ref must match; the same env var resolving to different values across refs is a hard error. The--separator is now required (all docs/tests already used it). The zsh completion generator is updated for the new positional shape — the existing test caught that the rename would have silently broken fuzzy completion.hm-x7z— ADR-0001 (docs/adr/). Keep last-wins duplicate-key collapse in the file generators (aliases resolve after selectors, so last-wins = "the alias pins the binding");codegengainsgenerate's warning instead of silently clobbering;execstays strict.hm-cpc— StoreOps (rust/src/cli/store_ops.rs). The mutation chain (append-only commit on success and failure, push, completions-cache refresh) now lives in one module with two callers: the CLI dispatcher (one finalize per command, so batches stay one commit) and silent TUI mutation cores. Fixes real drift: TUI delete/rekey left the store dirty (no commit at all), joinprintln!'d into ratatui, and TUI sets skipped env-key validation, effective-store routing, and the cache refresh.hm-dwr— KeyRegistry. One exhaustive-match row perKeyAction(a missing row is a compile error) drives dispatch, help screens (rendered from the live keymap, so rebinds show up), and palette shortcuts. The three hardcoded search chords (Ctrl+Space / Ctrl+T / Ctrl+O) became rebindable actions. Theenvskeymap field is renamedoutputswith a serde alias for existing configs.hm-a16— search-view graduation.PathFoldingandResultSortare now pure, terminal-free modules (rust/src/tui/model/);StoreHealthis a drawable widget (rust/src/tui/widgets/store_health.rs). Behavior-identical extraction; all pre-existing view tests pass through the moved code unchanged.Bonus fixes (found by the new tests)
Ctrl+Spacechords serialized as a literal space and could never roundtrip the whitespace-separated chord config format — now renders asspace.Reviewer notes
--(undocumented form) now errors with a clap usage message;codegenwarns on duplicate keys (was silent); local undecryptable entries in generate/codegen now produce SecretResolver's rich "no matching key" diagnostic instead of the raw age error; TUI mutations now commit/push/refresh like their CLI equivalents.CONTEXT.mdand the bead close).demo/demo-vhs.gifwas modified by something outside this session and is intentionally not included in this PR.🤖 Generated with Claude Code