allod command to save an encrypted credential into a secrets repo #182

Closed
opened 2026-09-10 16:16:11 +01:00 by vnprc-agent · 4 comments
Contributor

Add an allod command that saves a credential into a secrets repo, so the registry entry, the recipient list and the age ciphertext land together instead of being assembled by hand from a page of prose. The manual ritual works — it was just used to land a real credential end to end — but every safeguard in it is a sentence the operator has to have read first, and three of its failure modes exit 0.

Primary goals:

  • One command for the whole landing — name the credential and its recipient machines, and the command writes the credentials.nix entry, the secrets.nix recipient line, and the .age ciphertext, leaving one commit to review instead of three hand-edited files.
  • Recipients from the file that owns them — encrypt through agenix against secrets.nix, never against a recipient list assembled by hand, so the declared set and the encrypted set cannot drift apart.
  • Refuse the silent no-ops — an empty capture aborts instead of encrypting an empty string; a recipient-only change rekeys instead of reporting success and changing nothing.
  • Keep the plaintext out of argv, history, and persistent disk — the secret arrives on stdin, and agenix's cleartext staging is pointed at a tmpfs.
  • Work from anywhere — the command finds the secrets checkout itself rather than depending on the caller's working directory.
  • Exists only where the identity key is — the namespace compiles behind a build tag, the way site does, and only the host toolchain sets it. On a dev machine allod secret is an unknown word, not a command that refuses. Agents cannot add secrets by construction, and no prose has to say so.
  • Registers rotation in the same landing — the create path writes the rotation registry entry (format, targets, deployed path, verify probe) so rotate exists for the credential from the first commit, and the registry stops being Forgejo-shaped: the last credential registered there carries a fake service: forgejo and ui_token_name because nothing else fits.
  • Pending is a state, not a red check — a credential an agent's PR declares before the host has produced its ciphertext is rotation_state = "pending"; the inventory check tolerates a missing file in that state and refuses one in active; the command flips it when it writes the file.

Current state and specifics

The ritual is documented in memory's save-encrypted-credential skill. What the command has to encode, each reproduced:

Failure What the operator sees
Run outside the secrets checkout error: path '<cwd>/secrets.nix' does not exist — agenix reads secrets.nix from the working directory
A leftover file from a failed attempt No identity found to decrypt <file> — agenix -e decrypts an existing file first, so the next attempt is silently a rotation
A recipient-only change <file> wasn't changed, skipping re-encryption., exit 0, and the added machine still cannot decrypt. Only agenix -r rekeys
An empty capture Nothing. A well-formed ciphertext of an empty string that decrypts cleanly and fails at the point of use

The registry gates both directions, so partial work cannot merge and the command must write all three pieces or none: a registered agenix consumer with no file fails at secrets/flake.nix:577 (ERROR: missing <path> for <name>), and a secrets.nix entry with no consumer record fails at secrets/flake.nix:569 (credential-inventory: secrets missing consumer records: <path>).

A guard shape worth carrying over, because the obvious ones are wrong: [ -n "$K" ] || { …; return; } does not stop anything outside a function, and exit 1 in the same position kills an interactive shell. An if block is the form that neither falls through nor takes the terminal with it.

The boundary

This is an operator command, run at the host terminal where the age identity lives. It is not a skill an agent follows: an agent never holds a plaintext credential, and a rule that depends on the agent remembering that is not a boundary. The site namespace already has the shape (cmd/allod/site.go, //go:build site, switched on by the host home module's tags), so this follows it, and the wrapper change in the host repository that adds the tag is part of landing it.

The agent's half is what it can legitimately do: name the credential, its kind and its recipient machines in an issue or a PR body, and hand over the command. Whether it may also land the two non-secret files ahead of the ciphertext is the one design question to settle before building. The default worth stating: the command accepts a credentials.nix entry and a secrets.nix line that already exist and match what it was asked for, and writes only the missing ciphertext, so a branch an agent prepared is a resume state rather than a conflict; entries that exist and disagree with the request are refused with both shown, never reconciled silently.

Recipients come from secrets.nix and nowhere else. There is no flag that names a recipient set, for the same reason allod site deploy has no flag that names a target: a command that can be pointed at the wrong recipients by an argument will be, and the ciphertext then decrypts on a machine the registry does not list.

The whole landing

Two credentials landed this month by hand, and each touched the same set. The command owns the first four; the rest is ordinary reviewed work an agent does before the host step:

Piece Where Who
ciphertext, encrypted against secrets.nix from stdin, staged on tmpfs secrets/<name>.age the command, at the host
recipient line secrets.nix the command, or the agent ahead of it
inventory entry with kind, owner, consumer record and rotation_state credentials.nix the command, or the agent ahead of it
rotation registration: format, targets with deployed path, verify probe the rotation registry the command, or the agent ahead of it
identity export the consumer reads, as a Nix path to the file, never a string the secrets flake the agent
a documentation section: alias, recipients, deployed path, consumer, rotate, generate, verify the deployment's secrets docs the agent
consumer wiring: the placement module, the lock bumps, the rebuild, the verify command framework and deployment repos the agent, then the operator's rebuild

Pi credentials are not this: they have a registry of their own with derived recipients and pi-provider owns their lifecycle. Neither are host and forge keys, which the three key tools stage, activate and retire.

The flow

The agent builds everything around the secret as one PR: registry entries in pending state, the identity export, the docs section, and the consumer module where the framework does not already have one. Nothing in that PR is secret and all of it is reviewable on its own, green, because pending is a legal state.

The human runs one command at the host terminal, on the agent's branch: it reads the plaintext, encrypts it against secrets.nix, flips the state to active, runs the repository's checks, commits, and pushes the branch. Not master; merging stays a separate human act. If the entries on the branch do not match what the command was asked to encrypt, it refuses with both shown. A rotation is the same gate on the same command, and the rekey path likewise.

Scope

In scope: the create path, the rotate path, and the recipient-change path that has to rekey rather than edit; the build tag and the one-line change in the host repository's home module that sets it; the rotation registration and a registry shape that is not Forgejo-named; and the pending rotation state, whose check lives in the secrets template (allod/secrets), so that part lands there first. Landing this retires the save-encrypted-credential skill in allod/memory rather than updating it, as the comment below says; its paste-safe relay shape moves to the relay conventions, because that was never about secrets. The rotate path likely subsumes allod/nexus#42, which teaches rotate-token one more value shape; decide that against this design rather than building both. Out of scope and tracked with the credential itself: creating the value at its source, revoking the value it replaces, rotation policy, and the consumer-side wiring — the module that mounts the secret, and the lock bump and rebuild that carry it to a machine.

Background, not a dependency: allod/strategy#13 explores keeping long-lived secrets out of agent environments altogether. This command is the host-side half that any such design still needs.

Add an `allod` command that saves a credential into a secrets repo, so the registry entry, the recipient list and the age ciphertext land together instead of being assembled by hand from a page of prose. The manual ritual works — it was just used to land a real credential end to end — but every safeguard in it is a sentence the operator has to have read first, and three of its failure modes exit 0. Primary goals: - **One command for the whole landing** — name the credential and its recipient machines, and the command writes the `credentials.nix` entry, the `secrets.nix` recipient line, and the `.age` ciphertext, leaving one commit to review instead of three hand-edited files. - **Recipients from the file that owns them** — encrypt through agenix against `secrets.nix`, never against a recipient list assembled by hand, so the declared set and the encrypted set cannot drift apart. - **Refuse the silent no-ops** — an empty capture aborts instead of encrypting an empty string; a recipient-only change rekeys instead of reporting success and changing nothing. - **Keep the plaintext out of argv, history, and persistent disk** — the secret arrives on stdin, and agenix's cleartext staging is pointed at a tmpfs. - **Work from anywhere** — the command finds the secrets checkout itself rather than depending on the caller's working directory. - **Exists only where the identity key is** — the namespace compiles behind a build tag, the way `site` does, and only the host toolchain sets it. On a dev machine `allod secret` is an unknown word, not a command that refuses. Agents cannot add secrets by construction, and no prose has to say so. - **Registers rotation in the same landing** — the create path writes the rotation registry entry (format, targets, deployed path, verify probe) so `rotate` exists for the credential from the first commit, and the registry stops being Forgejo-shaped: the last credential registered there carries a fake `service: forgejo` and `ui_token_name` because nothing else fits. - **Pending is a state, not a red check** — a credential an agent's PR declares before the host has produced its ciphertext is `rotation_state = "pending"`; the inventory check tolerates a missing file in that state and refuses one in `active`; the command flips it when it writes the file. ### Current state and specifics The ritual is documented in `memory`'s `save-encrypted-credential` skill. What the command has to encode, each reproduced: | Failure | What the operator sees | |---|---| | Run outside the secrets checkout | `error: path '<cwd>/secrets.nix' does not exist` — agenix reads `secrets.nix` from the working directory | | A leftover file from a failed attempt | `No identity found to decrypt <file>` — `agenix -e` decrypts an existing file first, so the next attempt is silently a rotation | | A recipient-only change | `<file> wasn't changed, skipping re-encryption.`, exit 0, and the added machine still cannot decrypt. Only `agenix -r` rekeys | | An empty capture | Nothing. A well-formed ciphertext of an empty string that decrypts cleanly and fails at the point of use | The registry gates both directions, so partial work cannot merge and the command must write all three pieces or none: a registered agenix consumer with no file fails at `secrets/flake.nix:577` (`ERROR: missing <path> for <name>`), and a `secrets.nix` entry with no consumer record fails at `secrets/flake.nix:569` (`credential-inventory: secrets missing consumer records: <path>`). A guard shape worth carrying over, because the obvious ones are wrong: `[ -n "$K" ] || { …; return; }` does not stop anything outside a function, and `exit 1` in the same position kills an interactive shell. An `if` block is the form that neither falls through nor takes the terminal with it. ### The boundary This is an operator command, run at the host terminal where the age identity lives. It is not a skill an agent follows: an agent never holds a plaintext credential, and a rule that depends on the agent remembering that is not a boundary. The `site` namespace already has the shape (`cmd/allod/site.go`, `//go:build site`, switched on by the host home module's `tags`), so this follows it, and the wrapper change in the host repository that adds the tag is part of landing it. The agent's half is what it can legitimately do: name the credential, its kind and its recipient machines in an issue or a PR body, and hand over the command. Whether it may also land the two non-secret files ahead of the ciphertext is the one design question to settle before building. The default worth stating: the command accepts a `credentials.nix` entry and a `secrets.nix` line that already exist and match what it was asked for, and writes only the missing ciphertext, so a branch an agent prepared is a resume state rather than a conflict; entries that exist and disagree with the request are refused with both shown, never reconciled silently. Recipients come from `secrets.nix` and nowhere else. There is no flag that names a recipient set, for the same reason `allod site deploy` has no flag that names a target: a command that can be pointed at the wrong recipients by an argument will be, and the ciphertext then decrypts on a machine the registry does not list. ### The whole landing Two credentials landed this month by hand, and each touched the same set. The command owns the first four; the rest is ordinary reviewed work an agent does before the host step: | Piece | Where | Who | | --- | --- | --- | | ciphertext, encrypted against `secrets.nix` from stdin, staged on tmpfs | `secrets/<name>.age` | the command, at the host | | recipient line | `secrets.nix` | the command, or the agent ahead of it | | inventory entry with `kind`, `owner`, consumer record and `rotation_state` | `credentials.nix` | the command, or the agent ahead of it | | rotation registration: format, targets with deployed path, verify probe | the rotation registry | the command, or the agent ahead of it | | identity export the consumer reads, as a Nix path to the file, never a string | the secrets flake | the agent | | a documentation section: alias, recipients, deployed path, consumer, rotate, generate, verify | the deployment's secrets docs | the agent | | consumer wiring: the placement module, the lock bumps, the rebuild, the verify command | framework and deployment repos | the agent, then the operator's rebuild | Pi credentials are not this: they have a registry of their own with derived recipients and `pi-provider` owns their lifecycle. Neither are host and forge keys, which the three key tools stage, activate and retire. ### The flow The agent builds everything around the secret as one PR: registry entries in `pending` state, the identity export, the docs section, and the consumer module where the framework does not already have one. Nothing in that PR is secret and all of it is reviewable on its own, green, because `pending` is a legal state. The human runs one command at the host terminal, on the agent's branch: it reads the plaintext, encrypts it against `secrets.nix`, flips the state to `active`, runs the repository's checks, commits, and pushes the branch. Not master; merging stays a separate human act. If the entries on the branch do not match what the command was asked to encrypt, it refuses with both shown. A rotation is the same gate on the same command, and the rekey path likewise. ### Scope In scope: the create path, the rotate path, and the recipient-change path that has to rekey rather than edit; the build tag and the one-line change in the host repository's home module that sets it; the rotation registration and a registry shape that is not Forgejo-named; and the `pending` rotation state, whose check lives in the secrets template (`allod/secrets`), so that part lands there first. Landing this retires the `save-encrypted-credential` skill in `allod/memory` rather than updating it, as the comment below says; its paste-safe relay shape moves to the relay conventions, because that was never about secrets. The rotate path likely subsumes allod/nexus#42, which teaches `rotate-token` one more value shape; decide that against this design rather than building both. Out of scope and tracked with the credential itself: creating the value at its source, revoking the value it replaces, rotation policy, and the consumer-side wiring — the module that mounts the secret, and the lock bump and rebuild that carry it to a machine. Background, not a dependency: allod/strategy#13 explores keeping long-lived secrets out of agent environments altogether. This command is the host-side half that any such design still needs.
Author
Contributor

Landing this retires the save-encrypted-credential skill rather than updating it, and that belongs in Scope.

The skill exists only because the manual path has four ways to fail while exiting 0. A command that refuses all four leaves it nothing to teach. It is also the weaker instrument for the same reason this issue already gives: a skill is advisory, obeyed only by an agent that read it and chose to comply, while a namespace that does not compile outside the host is a boundary with nothing to obey.

One part of it does not retire, because it was never about secrets: the paste-safe relay shape — no backslash line continuations, never inline a ~110-character recipient token, and run an interactive read as its own command because inside a pasted block the shell feeds it the next pasted line as the secret. That applies to any command handed to a human through a TUI that mangles copy, and this command makes it moot only for this one task by collapsing the handover to a single short line. It should move to the relay conventions rather than vanish with the skill.

Until this lands, the skill is the only written procedure and stays as it is. The retirement is part of closing this, not separate cleanup.

Landing this retires the `save-encrypted-credential` skill rather than updating it, and that belongs in Scope. The skill exists only because the manual path has four ways to fail while exiting 0. A command that refuses all four leaves it nothing to teach. It is also the weaker instrument for the same reason this issue already gives: a skill is advisory, obeyed only by an agent that read it and chose to comply, while a namespace that does not compile outside the host is a boundary with nothing to obey. One part of it does not retire, because it was never about secrets: the paste-safe relay shape — no backslash line continuations, never inline a ~110-character recipient token, and run an interactive `read` as its own command because inside a pasted block the shell feeds it the next pasted line as the secret. That applies to any command handed to a human through a TUI that mangles copy, and this command makes it moot only for this one task by collapsing the handover to a single short line. It should move to the relay conventions rather than vanish with the skill. Until this lands, the skill is the only written procedure and stays as it is. The retirement is part of closing this, not separate cleanup.
Member

Design settled with the owner before building. The command verifies and encrypts; it does not author. The rotate path is deferred; create and rekey land now. The rotation registry generalizes in place. Encryption is age fed over a pipe, so no plaintext is ever written to any filesystem.

Decisions

  1. allod secret create <name> verifies, then encrypts. It takes a credential name and the plaintext on stdin, nothing else. It refuses unless the branch already carries, for that name, a credentials.nix entry in rotation_state = "pending" with exactly one agenix consumer in this repo, a secrets.nix line for that consumer's path whose recipients include the host identity, and a rotation registry entry naming that credential and path, and no ciphertext exists yet. Every refusal shows what it found. There are no flags for kind, owner, recipients or format: the reviewed PR is the only authoring path, so there is one shape to get right and the recipient set is a diff that passed the inventory check before any ciphertext exists. A human landing a credential without an agent hand-edits the same three files. An untagged, agent-side allod secret declare that writes the three entries in canonical shape is a follow-up, not part of this issue.
  2. create and rekey land now; rotate is deferred. rotate-token in nexus already rotates every registered format, carries host and user forward, and refreshes local auth, and allod/nexus#42 is closed and landed. Duplicating that in Go doubles the surface for no new capability. allod secret rekey <name> covers the recipient-change path: decrypt with the host identity, re-encrypt to the current secrets.nix recipients, always rewriting so the agenix no-op cannot recur. Moving rotation into allod secret is a follow-up once the registry below has settled.
  3. The rotation registry generalizes in place. forgejo-token-groups.json and the lib.forgejoTokenGroups export keep their names; renaming is cross-repo and its own issue. service becomes an enum, forgejo or none; account, ui_token_name and the revocation copy are required and printed only for forgejo. archetypes reads only credentials[] and local_auth_refresh and is unaffected. rotate-token's validator and printed copy change accordingly.
  4. Encryption is age with recipients evaluated from secrets.nix. The recipient list is nix eval of that file's entry for the path, the same source rotate-token and pi-provider use, and no flag can name a recipient. The plaintext goes from stdin to the age process over a pipe and the ciphertext is captured and written whole, so nothing unencrypted touches any filesystem, tmpfs included, and there is no partial file to leave behind. Rekey pipes age -d into memory and back into age -e the same way.
  5. Checkout and branch. The command resolves the secrets checkout through the repository registry the way rotate-token does, and takes an optional [<checkout>] for a worktree. It uses whatever branch is checked out there and refuses the default branch and a dirty tree. The human checks out the agent's branch with ordinary git first. After writing, it runs the repository's flake checks, restores both files if they fail, and otherwise commits and pushes the branch. Merging stays a separate human act.
  6. Plaintext input. A pipe or file is taken verbatim, trailing newline included, so multi-line formats round-trip. A terminal gets one hidden-echo line without its newline. Empty or whitespace-only input is refused either way.
  7. Build tag. The namespace compiles behind its own tag, secret, and nexus sets tags = [ "site" "secret" ]. The two tags name two capabilities, publishing sites and holding the age identity, that happen to coincide on one machine today.
  8. pending state. The inventory check accepts rotation_state = "pending", requires the ciphertext absent in that state and present in every other, and otherwise treats a pending entry as it treats an active one for recipient coverage. That change lands first, in allod/secrets.

Landing order

  • allod/secrets: the pending state in the inventory check and README. Refs this issue.
  • allod/tools: the secret namespace behind its tag, the hidden-prompt code moved out of the site-only file so both tags can use it, tagged and untagged tests, checks for both tag directions, docs. Refs this issue.
  • allod/nexus: the tag in the host home module, the service enum in rotate-token's validator and copy with a test, docs. Closes this issue.
  • allod/memory: age.md points the landing at the command. The private save-encrypted-credential skill retires by hand, per the comment above; its paste-safe relay shape already lives in agent-behavior.md.

Residual risk

R3 for the tools PR: it writes to the authoritative secrets repository, pushes, and handles the age identity, though only on a non-default branch with a clean tree, with every file-system effect captured in memory first, and with the flake checks gating the commit. R2 for secrets and nexus. Rollback is a revert in each repo; a landed ciphertext is a commit on a branch that was never merged.

Design settled with the owner before building. The command verifies and encrypts; it does not author. The rotate path is deferred; `create` and `rekey` land now. The rotation registry generalizes in place. Encryption is `age` fed over a pipe, so no plaintext is ever written to any filesystem. ### Decisions 1. **`allod secret create <name>` verifies, then encrypts.** It takes a credential name and the plaintext on stdin, nothing else. It refuses unless the branch already carries, for that name, a `credentials.nix` entry in `rotation_state = "pending"` with exactly one `agenix` consumer in this repo, a `secrets.nix` line for that consumer's path whose recipients include the host identity, and a rotation registry entry naming that credential and path, and no ciphertext exists yet. Every refusal shows what it found. There are no flags for kind, owner, recipients or format: the reviewed PR is the only authoring path, so there is one shape to get right and the recipient set is a diff that passed the inventory check before any ciphertext exists. A human landing a credential without an agent hand-edits the same three files. An untagged, agent-side `allod secret declare` that writes the three entries in canonical shape is a follow-up, not part of this issue. 2. **`create` and `rekey` land now; `rotate` is deferred.** `rotate-token` in nexus already rotates every registered format, carries host and user forward, and refreshes local auth, and allod/nexus#42 is closed and landed. Duplicating that in Go doubles the surface for no new capability. `allod secret rekey <name>` covers the recipient-change path: decrypt with the host identity, re-encrypt to the current `secrets.nix` recipients, always rewriting so the agenix no-op cannot recur. Moving rotation into `allod secret` is a follow-up once the registry below has settled. 3. **The rotation registry generalizes in place.** `forgejo-token-groups.json` and the `lib.forgejoTokenGroups` export keep their names; renaming is cross-repo and its own issue. `service` becomes an enum, `forgejo` or `none`; `account`, `ui_token_name` and the revocation copy are required and printed only for `forgejo`. archetypes reads only `credentials[]` and `local_auth_refresh` and is unaffected. `rotate-token`'s validator and printed copy change accordingly. 4. **Encryption is `age` with recipients evaluated from `secrets.nix`.** The recipient list is `nix eval` of that file's entry for the path, the same source rotate-token and pi-provider use, and no flag can name a recipient. The plaintext goes from stdin to the `age` process over a pipe and the ciphertext is captured and written whole, so nothing unencrypted touches any filesystem, tmpfs included, and there is no partial file to leave behind. Rekey pipes `age -d` into memory and back into `age -e` the same way. 5. **Checkout and branch.** The command resolves the secrets checkout through the repository registry the way rotate-token does, and takes an optional `[<checkout>]` for a worktree. It uses whatever branch is checked out there and refuses the default branch and a dirty tree. The human checks out the agent's branch with ordinary git first. After writing, it runs the repository's flake checks, restores both files if they fail, and otherwise commits and pushes the branch. Merging stays a separate human act. 6. **Plaintext input.** A pipe or file is taken verbatim, trailing newline included, so multi-line formats round-trip. A terminal gets one hidden-echo line without its newline. Empty or whitespace-only input is refused either way. 7. **Build tag.** The namespace compiles behind its own tag, `secret`, and nexus sets `tags = [ "site" "secret" ]`. The two tags name two capabilities, publishing sites and holding the age identity, that happen to coincide on one machine today. 8. **`pending` state.** The inventory check accepts `rotation_state = "pending"`, requires the ciphertext absent in that state and present in every other, and otherwise treats a pending entry as it treats an active one for recipient coverage. That change lands first, in `allod/secrets`. ### Landing order - allod/secrets: the `pending` state in the inventory check and README. Refs this issue. - allod/tools: the `secret` namespace behind its tag, the hidden-prompt code moved out of the site-only file so both tags can use it, tagged and untagged tests, checks for both tag directions, docs. Refs this issue. - allod/nexus: the tag in the host home module, the `service` enum in rotate-token's validator and copy with a test, docs. Closes this issue. - allod/memory: `age.md` points the landing at the command. The private `save-encrypted-credential` skill retires by hand, per the comment above; its paste-safe relay shape already lives in `agent-behavior.md`. ### Residual risk R3 for the tools PR: it writes to the authoritative secrets repository, pushes, and handles the age identity, though only on a non-default branch with a clean tree, with every file-system effect captured in memory first, and with the flake checks gating the commit. R2 for secrets and nexus. Rollback is a revert in each repo; a landed ciphertext is a commit on a branch that was never merged.
Member

Implementation is up for review in three PRs plus a memory note, in merge order: allod/secrets#19 (the pending inventory state), allod/tools#195 (the allod secret namespace behind the secret tag), allod/nexus#50 (the tag on the host build and the service: none registry groups; closes this issue), then allod/memory's "age.md: a credential lands in two halves". After tools merges, the allod-tools pin in nexus and deploy has to move before the host binary carries the command.

Follow-ups filed from the design comment: allod/tools#196 for an agent-side allod secret declare that writes the three entries in canonical shape, and allod/tools#197 for moving value rotation out of rotate-token into allod secret rotate, where the registry rename is also decided.

Not done here and left to the operator: retiring the private save-encrypted-credential skill by hand once the nexus PR lands, and a full live landing, since no agent machine holds an age identity. The command's real nix eval and age paths were exercised against a clone of the secrets template up to the point where the identity is needed.

Implementation is up for review in three PRs plus a memory note, in merge order: allod/secrets#19 (the `pending` inventory state), allod/tools#195 (the `allod secret` namespace behind the `secret` tag), allod/nexus#50 (the tag on the host build and the `service: none` registry groups; closes this issue), then allod/memory's "age.md: a credential lands in two halves". After tools merges, the `allod-tools` pin in nexus and deploy has to move before the host binary carries the command. Follow-ups filed from the design comment: allod/tools#196 for an agent-side `allod secret declare` that writes the three entries in canonical shape, and allod/tools#197 for moving value rotation out of `rotate-token` into `allod secret rotate`, where the registry rename is also decided. Not done here and left to the operator: retiring the private `save-encrypted-credential` skill by hand once the nexus PR lands, and a full live landing, since no agent machine holds an age identity. The command's real `nix eval` and `age` paths were exercised against a clone of the secrets template up to the point where the identity is needed.
vnprc closed this issue 2026-09-11 02:01:55 +01:00
Member

Cross-family review pass run after the merges, as delegate-implementation asks; it had been skipped during implementation. Runner: pi driving gpt-5.6-sol and gpt-5.6-terra at thinking high, because codex refused both with a reconnect error. Two reviewers on the tools diff, one each on secrets and nexus, every finding verified against the source before acting.

Results: allod/tools#195 had seven real findings (a guessed master default branch that could push a landing to main, no rollback on a failed add or commit, a silent restore failure, two flip-parsing gaps, a path with a space, and the untagged build broken on the BSD family), fixed in the tools PR "allod secret: answer the cross-family review". allod/nexus#50 had three (the --forgejo-token selector could resolve a none group with placeholder fields, and two Forgejo-only messages for none groups), fixed in allod/nexus#51. allod/secrets#19 had one real finding, that nothing exercises the pending arm in the template, filed as allod/secrets#20; its other finding, that an untracked ciphertext is invisible to ${self}, is the ordinary flake-source rule.

Cross-family review pass run after the merges, as `delegate-implementation` asks; it had been skipped during implementation. Runner: pi driving gpt-5.6-sol and gpt-5.6-terra at thinking high, because codex refused both with a reconnect error. Two reviewers on the tools diff, one each on secrets and nexus, every finding verified against the source before acting. Results: allod/tools#195 had seven real findings (a guessed `master` default branch that could push a landing to `main`, no rollback on a failed add or commit, a silent restore failure, two flip-parsing gaps, a path with a space, and the untagged build broken on the BSD family), fixed in the tools PR "allod secret: answer the cross-family review". allod/nexus#50 had three (the `--forgejo-token` selector could resolve a `none` group with placeholder fields, and two Forgejo-only messages for `none` groups), fixed in allod/nexus#51. allod/secrets#19 had one real finding, that nothing exercises the `pending` arm in the template, filed as allod/secrets#20; its other finding, that an untracked ciphertext is invisible to `${self}`, is the ordinary flake-source rule.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/tools#182
No description provided.