Skip to content

feat: add SOPS secret add/delete with darwin-rebuild verification and commit - #655

Open
Scott McMaster (scottmcmaster) wants to merge 2 commits into
08-11-scott-agenix-secrets-rofrom
08-11-scott-add-del-sops-secrets
Open

feat: add SOPS secret add/delete with darwin-rebuild verification and commit#655
Scott McMaster (scottmcmaster) wants to merge 2 commits into
08-11-scott-agenix-secrets-rofrom
08-11-scott-add-del-sops-secrets

Conversation

@scottmcmaster

@scottmcmaster Scott McMaster (scottmcmaster) commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Hooks up the original storybook mocks end-to-end for SOP secret add and delete operations. (Age will be forthcoming.)

Includes some fileops and nix editor refactorings and enhancements for reusable/recurring patterns.

Screenshots:

Screenshot 2026-08-11 at 3 08 33 PM Screenshot 2026-08-11 at 2 58 26 PM image image Screenshot 2026-08-11 at 3 00 39 PM image

Test Plan

New unit tests as appropriate, plus manual testing e2e.

  • No test plan needed

Docs

  • Docs updated (companion PR in darkmatter/nixmac-web: #___)
  • No docs update needed

Scott McMaster (scottmcmaster) commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@scottmcmaster Scott McMaster (scottmcmaster) changed the title scott-add-del-sops-secrets feat: add SOPS secret add/delete with darwin-rebuild verification and commit Aug 11, 2026
@scottmcmaster
Scott McMaster (scottmcmaster) marked this pull request as ready for review August 11, 2026 07:12
@darkmatter

darkmatter Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🎨 Storybook preview

Open Storybook preview

Updated for 6593ba3


⚠️ Detected UI changes (5)

These stories' HTML snapshots changed. I've added screenshots + links to the changed stories below. Review them carefully then accept the changes to regenerate baselines and include them in this PR:

Flows/Evolve › Playground

Flows/Evolve › Playground

Flows/Evolve › 1. Begin (idle)

Flows/Evolve › 1. Begin (idle)

Flows/Evolve › 2. Evolving (progress)

Flows/Evolve › 2. Evolving (progress)

Flows/Evolve › Evolving With Error Event

Flows/Evolve › Evolving With Error Event

Flows/Evolve › 3. Review (changes generated)

Flows/Evolve › 3. Review (changes generated)


Accept UI changes

  • Click here to accept these changes

Alternatively, you can run bun run test:update-snapshots locally to re-generate the baselines and then push the changes to this PR.

What does this do?

The screenshots above show UI changes detected by the Storybook
snapshot tests run on this PR. Each image is the rendered output of
a Storybook story from the code in this PR branch; the snapshot
test compared it against the committed baseline in
__snapshots__/ and flagged the difference.

Checking the box tells the darkmatter[bot] to regenerate the
baselines from this PR's current code and commit them directly to
this branch. The new baselines become the source of truth for
future runs — only accept after confirming the visual changes are
intentional.

Comparison baseline: the committed __snapshots__/ files on this
PR branch (carried forward from develop). Accept updates them in
place on this branch.

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
Warnings
⚠️

No Linear issue ID found in this PR's title, description, or branch name (expected something like ENG-123). Add one so this work is traceable in Linear, or add #no-linear to the PR description to acknowledge it's intentionally untracked.

Messages
📖 No docs update needed — acknowledged.

📋 PR Overview

Lines changed 1458 (+1227 / -231)
Files 0 added, 14 modified, 0 deleted
Draft / WIP no
Has Test Plan yes
Linear issue no
No Test Plan Needed no
New UI components no
New Storybook stories no
New Rust modules no
New TS source files no
New tests no
package.json touched no
Cargo.toml touched no
Infra / CI touched no

🔬 Coverage

Report Lines Statements Functions Branches
apps/native/coverage/coverage-summary.json 35.5% 35.1% 30.6% 29.4%

Generated by 🚫 dangerJS against 6593ba3

@prelint

prelint Bot commented Aug 11, 2026

Copy link
Copy Markdown

Ship with changes SOPS secret add and delete wired to real backend

Product decisions in this change

Disagree 1. The delete button is active for agenix secrets, but the backend rejects the deletion only after the user reads and confirms the dialog.

The user opens the detail view for an agenix secret. They see an active Delete button. They click it, read "This removes the encrypted value and Nix declaration, verifies the configuration, and commits the change", and click "Delete & commit". Only then does the backend return "Deleting agenix secrets is not yet implemented". The add form avoids this by showing a static sops-nix badge with copy that says agenix support is coming. The delete surface must match that pattern before shipping.

Option What users see What it costs Effort to change later
Current approach Error after confirming a destructive-looking dialog User works through a commit-framed interaction before learning it fails Trivial
Disable button for agenix Greyed Delete with a tooltip explaining agenix is not yet supported No destructive path until feature ships Trivial
Agenix notice in dialog Dialog opens but confirm button stays disabled with an explanation banner User sees the flow without reaching a post-confirmation error Trivial

Any of the two alternatives resolves the issue. Disabling the button is the lower-effort fix and matches the pattern the add form already uses.

Agree with concerns 2. Delete bypasses the review sheet and uses a confirmation dialog, with no diff preview and no display of the commit message before the operation runs.

The add flow shows the user a diff, the two affected file names, and the commit message in a review sheet before they confirm. Delete skips all of this. For a remove operation, the asymmetry is defensible: the user is removing something they already know. The concern is that the dialog is pure prose, it names no files, and it gives no indication that a darwin-rebuild check will run before the commit. A user who uses add and then uses delete will notice the difference in transparency. Adding the two file names to the dialog description closes most of the gap.

Agree with concerns 3. Delete provides no phase-level feedback during the build verification step, showing only a spinner in the button while the operation runs.

The add flow has a dedicated building phase. It shows "Encrypting with SOPS · running darwin-rebuild check" while the operation runs, so the user knows the system is doing real work. Delete shows only "Deleting..." in the button. A darwin-rebuild check can take 30 to 60 seconds. The user has no signal about which phase is active or whether the operation is progressing. This gap is noticeable on a slow machine. Adding a visible phase label would align delete with the add experience.

Agree with concerns 4. The clean repository requirement fires when the user clicks "Delete & commit" or "Encrypt & commit", not when the form or detail view opens.

A user with uncommitted changes opens the detail view, reads the secret, clicks Delete, reads the confirmation dialog, and clicks confirm. The backend then returns the clean-repo error. The inline error keeps the user in the dialog without losing context, which is better than a navigation failure. But the user invested effort in the flow before learning about the prerequisite. A status check at view open would let users act before they engage with either flow. This is a UX cost rather than a data risk.

Agree with concerns 5. The delete confirmation dialog describes the operation in prose without naming the specific files that will change.

The dialog reads "This removes the encrypted value and Nix declaration, verifies the configuration, and commits the change." It does not name secrets/secrets.yaml or the declaration module path. The add review sheet names both files. A user who has used add and then uses delete will notice the asymmetry. Adding the two file paths to the dialog description costs minimal design work and gives users the same level of transparency for both operations.

Agree 6. The add secret button is now visible and the full add flow is wired to the real backend rather than a local simulation.

The add flow encrypts the value in memory, sends it to the Rust backend over stdin, runs darwin-rebuild check, and commits only the two managed files. The button was correctly hidden until this PR completed the backend wiring. Showing it now is the right timing. The flow handles failure by restoring both files and returning the user to the review sheet with an inline error.

Agree with concerns 7. A secret name is normalized to a lowercase slug before storage; the original name the user typed is not preserved anywhere in the configuration.

A slug is required for SOPS YAML keys and Nix identifiers. The form shows the slug preview as the user types, so the transformation is visible before submission. The concern is lost intent. A name like "GitHub Token (Personal, read-only)" becomes "github-token-personal-read-only" with no record of the annotation. A Nix comment above the declaration could preserve the original name at near-zero cost.

Agree with concerns 8. All SOPS secrets share one encrypted file at a fixed path, with no mechanism for users who have a different layout to use the add or delete feature.

The fixed path matches the shipped template. New users who follow the template work correctly without any configuration. The concern is existing users with a custom layout. Those users cannot use add or delete, and the secrets view gives them no warning when it opens. The feature is useful for the majority of template-based installs, so this is acceptable as a short-term known limitation.

Agree 9. The plaintext secret value is held in React component state during the review phase, between form submit and "Encrypt & commit".

The user typed the value into a form field, so it is already in component state. Holding it during the review phase is a natural continuation of that state. The alternative would be to discard and re-request the value, which is worse UX. The value never leaves the browser process during this phase, and the backend receives it over the IPC channel only when the user confirms.

Agree with concerns 10. SOPS selects encryption recipients via `--filename-override secrets/secrets.yaml`. Whether this reliably matches the intended creation rule when a repository has multiple rules is assumed but not validated at add time.

The --filename-override flag is the correct SOPS mechanism for stdin encryption against a .sops.yaml path rule. For the standard single-rule template, this works correctly. The concern is a user with multiple path-based creation rules in .sops.yaml. If SOPS matches the wrong rule, encryption succeeds but the intended identities cannot decrypt the secret. The add result does not report which recipients were selected, so the user has no way to verify the outcome.

Since the last review

  • Still open: Delete button active for all backends including agenix; user sees 'not yet implemented' error only after confirming the destructive dialog (secret-detail-view.tsx: button disabled only by capability === 'unavailable'; backend delete_secret returns Err("Deleting agenix secrets is not yet implemented") for SecretBackend::Agenix)
  • Addressed in code: Recipients for a new SOPS secret are informational only; user cannot change them, but the form showed interactive checkboxes (add-secret-view.tsx: toggleRecipient, recipientIds state, and Checkbox elements removed; read-only recipient list added with note citing .sops.yaml creation rules)
  • Still open: Clean repo check fires at confirmation time, not when the form or detail view opens (secrets_management.rs: add_sops_secret and delete_sops_secret both call crate::git::status() inside the operation function; no early check added to view lifecycle)
  • Still open: Delete dialog does not name the specific files that will change, unlike the add review sheet (secret-detail-view.tsx AlertDialogDescription: 'This removes the encrypted value and Nix declaration, verifies the configuration, and commits the change' — no file names included)
  • Still open: All SOPS secrets share one file at a fixed path with no support for custom layouts (secrets_management.rs: MANAGED_SOPS_FILE = "secrets/secrets.yaml" still hardcoded; no override mechanism added)
  • Still open: Declaration module expected at fixed path with fallback, but no support for multi-file declarations (secrets_management.rs: STANDARD_SOPS_MODULE = "modules/darwin/sops-secrets.nix" hardcoded; fallback logic unchanged)
  • Still open: Secret name normalized to slug; original typed name not stored anywhere (secrets_management.rs: validate_new_secret enforces slug rules; declare_sops_secret adds no comment above the declaration to preserve the original name)
  • Addressed in code: Error from a failed add operation should appear inline in the review sheet, not require navigation away (apply-sheet.tsx: error?: string | null prop added; secrets-management.tsx sets applyError in the catch block of runApply and passes it to ApplySheet)
  • Still open: What share of active users manage secrets with agenix and will see the delete error after confirmation (No PR conversation or comment addresses agenix user share)
  • Still open: No committed timeline for agenix add and delete support, despite the UI saying 'Agenix support will be added later' (add-secret-view.tsx copy unchanged: 'Agenix support will be added later'; no PR conversation commits to a timeline)
  • Still open: Clean-repo check should run at form open rather than at confirmation time (No early status check was added to the add form or the secret detail view lifecycle)
  • Still open: Whether SOPS reliably selects the correct creation rule via --filename-override when .sops.yaml has multiple rules (secrets_management.rs encrypt_sops_yaml: --filename-override MANAGED_SOPS_FILE is used; no test or runtime validation of which rule matched was added)
  • Still open: Git history restore mentioned only once in prose in the delete dialog; not discoverable for users unfamiliar with git (secret-detail-view.tsx AlertDialogDescription: 'You can restore it later from Git history' — single prose mention, no link to History view)

Open questions

  • What share of active nixmac users manage secrets with agenix? If the share is more than a small minority, the active delete button on agenix secrets affects real sessions, not just an edge case.

  • Does the team have a committed timeline for agenix add and delete support? The UI copy says 'Agenix support will be added later' without a date. Users who see this copy may wait for a feature that is not actively scheduled.

  • Should the clean repository check run when the add form opens or when the secret detail view opens? Running it there lets users fix the prerequisite before filling the form or reading the confirmation dialog.

  • When a repository's .sops.yaml contains multiple creation rules for different path prefixes, does SOPS reliably select the correct rule for secrets/secrets.yaml via --filename-override? A test against a multi-rule .sops.yaml would answer this before the feature reaches users with non-standard configurations.

  • Is the single sentence about Git history restore in the delete dialog discoverable enough for users who are not comfortable with git? A link or button that navigates to the History view would give those users a clearer path to recovery.

Recommendation

Ship with changes
The add flow is fully wired and ships in good shape. The recipient UX improvement (concern 2) and inline error wiring (concern 8) are real progress. One issue prevents shipping without a change: the delete button remains active for agenix secrets, so a user who confirms the dialog receives a "not yet implemented" error after a destructive-looking interaction. The fix is trivial — disable the delete button when the secret backend is agenix, or show an unsupported-backend notice before the confirm action is reachable — and unblocks merge.

@prelint prelint Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

The Delete button is rendered unconditionally for all secrets regardless of secret.decryptionCapability, but delete_sops_secret on the backend calls decrypt_sops_file internally — it must decryp...

apps/native/src/components/widget/secrets/secret-detail-view.tsx:248

1 finding(s) posted as inline comments.

Comment thread apps/native/src/components/widget/secrets/secret-detail-view.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR wires the previously mocked SOPS "add secret" and "delete secret" flows to real Rust backend implementations, replacing frontend simulations. On the backend it encrypts/decrypts the shared secrets/secrets.yaml via sops, declares/removes the secret in the nix-darwin module through the semantic Nix AST editor, verifies with darwin-rebuild dry-run, and commits only the two managed files. It also extracts reusable path helpers and a multi-file commit helper, and refactors ensure_secret to share them.

Changes:

  • New secrets::secrets_management::add_secret/delete_secret (SOPS only) with clean-repo precondition, dry-build verification, post-edit vault re-verification, and best-effort rollback; exposed via new secrets.addSecret/secrets.deleteSecret oRPC procedures and shared AddSecretResult/DeleteSecretResult types.
  • Reusable helpers: git::commit_files (selective multi-file commit), file_ops path helpers (repo_relative_path, repo_relative_path_string, relative_path_between), and nix_file_editor::remove_attrpath; ensure_secret refactored onto the shared relative-path helper.
  • Frontend: un-hides the Add-secret button, backend-owns the add flow with error surfacing in the apply sheet, adds a delete confirmation dialog in the detail view, and simplifies the add form to SOPS-only.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
apps/native/src-tauri/src/secrets/secrets_management.rs Core SOPS add/delete: encrypt/decrypt over stdin, YAML edit, module declaration edit, build verify, commit, rollback, plus tests.
apps/native/src-tauri/src/orpc/secrets.rs New addSecret/deleteSecret procedures and shared Git-state refresh after mutation.
apps/native/src-tauri/src/shared_types/secrets_management.rs Adds AddSecretResult/DeleteSecretResult specta types.
apps/native/src-tauri/src/git/exec.rs / git/mod.rs Generalizes commit_file into commit_files for selective multi-file commits; exports it; adds a test.
apps/native/src-tauri/src/evolve/file_ops.rs Adds repo-relative and lexical relative-path helpers with tests.
apps/native/src-tauri/src/evolve/nix_file_editor.rs Adds remove_attrpath for structural attrpath removal with tests.
apps/native/src-tauri/src/evolve/ensure_secret.rs / evolve/mod.rs Reuses shared path helper; exports GitignoreChecker.
apps/native/src/ipc/orpc-bindings.ts Generated TS bindings for the new inputs/results and procedures.
apps/native/src/components/widget/secrets/*.tsx Wires add flow, delete confirmation dialog, apply-sheet error display; SOPS-only add form.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/native/src-tauri/src/secrets/secrets_management.rs Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants