allod secret: print refresh-local-auth as the next step and gate on it being installed #210

Merged
vnprc merged 2 commits from agent/refresh-local-auth-migrate into master 2026-09-12 23:57:49 +01:00
Member

Nothing changes on any machine until the deployment moves its allod-tools pin. On the host, after that: allod secret rotate prints refresh-local-auth --group <alias> as the operator's next step for a group with local_auth_refresh entries, naming the script nexus now ships under that name (allod/nexus PR 55, allod/nexus#55), instead of the retired rotate-token refresh-local-auth; and allod secret migrate no longer refuses a credential that feeds a local auth refresh, provided it is a credential-store-url container and refresh-local-auth is on PATH, so a host still running the old nexus gets a refusal rather than a migrated entry the old refresh would then reject. Everything else about rotate, migrate, create, declare, and rekey is identical. Proof is the tagged and untagged test suites with three new migrate tests, plus two cold reviews by models that did not write it. Advance this pin no earlier than the nexus pin that carries the new script, ideally in the same lock bump. If it is wrong, revert and move the pin back; nothing here writes a file until an operator runs a command.

What changes

  • cmd/allod/secret_rotate.go: the printed deploy step for a group with local_auth_refresh entries is refresh-local-auth --group <alias> in both the live and dry-run wording, and a group with such entries is refused before the value is read, on a dry run too, unless refresh-local-auth resolves on PATH, so a host whose nexus pin predates allod/nexus#52 cannot land a rotation it cannot finish; comments updated.
  • cmd/allod/secret_migrate.go: the blanket refusal of a local_auth_refresh source is replaced by two checks, both before any decrypt: the source must be a credential-store-url container (an rclone stanza can never render a netrc line), and refresh-local-auth must resolve on PATH, with a refusal naming that the host's nexus pin predates allod/nexus#52 otherwise. The format check for the two containers moves ahead of the local-auth check so the more general refusal is reported first.
  • Tests: the obsolete refusal case is removed; three added: a local-auth source migrates with a fake refresh-local-auth on PATH (golden registry bytes, ciphertext untouched, and the migrated group passes validateGroupMetadata), is refused with nothing decrypted when it is absent from a PATH built without it, and an rclone-stanza source is refused naming its format.
  • docs/allod-secret.md: the printed step, the migrate refusal list, and the sentence that said rotate-token keeps rotating legacy entries (it no longer exists; a plain legacy entry becomes new-shape by a registry edit and rotate refuses it until then).

Risk

R2: a printed string and a refusal replaced by two narrower refusals, all before the plaintext is opened; main paths tested; rollback is a revert. The one behavioural widening, migrating a local-auth source, produces the exact shape nexus's refresh-local-auth and the archetypes check both accept, which the new golden test asserts through validateGroupMetadata.

Validation

Run by the overseer in the worktree on the committed tree (16b53ed, two commits):

command last line exit
nix develop -c gofmt -l . no output 0
nix develop -c go vet ./... no output 0
nix develop -c go test ./... ok forge.anarch.diy/allod/tools/internal/gitremote 0.125s 0
nix develop -c go test -tags site ./... ok forge.anarch.diy/allod/tools/internal/gitremote 0.127s 0
nix develop -c go test -tags secret ./... ok forge.anarch.diy/allod/tools/internal/gitremote 0.119s 0
nix develop -c go test -tags site,secret ./... ok forge.anarch.diy/allod/tools/internal/gitremote 0.120s 0
git diff --check no output 0

Cold reviews, each reading a .git-free snapshot and the diff against master, both from the GPT family because the change was authored by a Claude model: gpt-5.5 at effort high through codex on the first commit found one medium (rotate printed refresh-local-auth as the next step without checking it exists, so a live run could land a rotation the operator could not finish) and one low (a refusal string still said rotate-token still rotates a plain legacy entry); both were verified against the source and fixed in the second commit with tests. gpt-5.6-terra at thinking high through pi read the final tree and reported no findings, confirming the gate runs before the value is read and on a dry run and that its test fails with the gate removed. A first gpt-5.6-terra pass on the first commit also reported no findings.

Not run: a live migrate or rotate on the host.

Refs allod/nexus#52
Refs allod/tools#197

Nothing changes on any machine until the deployment moves its `allod-tools` pin. On the host, after that: `allod secret rotate` prints `refresh-local-auth --group <alias>` as the operator's next step for a group with `local_auth_refresh` entries, naming the script nexus now ships under that name (allod/nexus PR 55, https://forge.anarch.diy/allod/nexus/pulls/55), instead of the retired `rotate-token refresh-local-auth`; and `allod secret migrate` no longer refuses a credential that feeds a local auth refresh, provided it is a `credential-store-url` container and `refresh-local-auth` is on PATH, so a host still running the old nexus gets a refusal rather than a migrated entry the old refresh would then reject. Everything else about `rotate`, `migrate`, `create`, `declare`, and `rekey` is identical. Proof is the tagged and untagged test suites with three new migrate tests, plus two cold reviews by models that did not write it. Advance this pin no earlier than the nexus pin that carries the new script, ideally in the same lock bump. If it is wrong, revert and move the pin back; nothing here writes a file until an operator runs a command. ## What changes - `cmd/allod/secret_rotate.go`: the printed deploy step for a group with `local_auth_refresh` entries is `refresh-local-auth --group <alias>` in both the live and dry-run wording, and a group with such entries is refused before the value is read, on a dry run too, unless `refresh-local-auth` resolves on PATH, so a host whose nexus pin predates allod/nexus#52 cannot land a rotation it cannot finish; comments updated. - `cmd/allod/secret_migrate.go`: the blanket refusal of a `local_auth_refresh` source is replaced by two checks, both before any decrypt: the source must be a `credential-store-url` container (an rclone stanza can never render a netrc line), and `refresh-local-auth` must resolve on PATH, with a refusal naming that the host's nexus pin predates allod/nexus#52 otherwise. The format check for the two containers moves ahead of the local-auth check so the more general refusal is reported first. - Tests: the obsolete refusal case is removed; three added: a local-auth source migrates with a fake `refresh-local-auth` on PATH (golden registry bytes, ciphertext untouched, and the migrated group passes `validateGroupMetadata`), is refused with nothing decrypted when it is absent from a PATH built without it, and an rclone-stanza source is refused naming its format. - `docs/allod-secret.md`: the printed step, the migrate refusal list, and the sentence that said `rotate-token` keeps rotating legacy entries (it no longer exists; a plain legacy entry becomes new-shape by a registry edit and `rotate` refuses it until then). ## Risk R2: a printed string and a refusal replaced by two narrower refusals, all before the plaintext is opened; main paths tested; rollback is a revert. The one behavioural widening, migrating a local-auth source, produces the exact shape nexus's `refresh-local-auth` and the archetypes check both accept, which the new golden test asserts through `validateGroupMetadata`. ## Validation Run by the overseer in the worktree on the committed tree (`16b53ed`, two commits): | command | last line | exit | |---|---|---| | `nix develop -c gofmt -l .` | no output | 0 | | `nix develop -c go vet ./...` | no output | 0 | | `nix develop -c go test ./...` | `ok forge.anarch.diy/allod/tools/internal/gitremote 0.125s` | 0 | | `nix develop -c go test -tags site ./...` | `ok forge.anarch.diy/allod/tools/internal/gitremote 0.127s` | 0 | | `nix develop -c go test -tags secret ./...` | `ok forge.anarch.diy/allod/tools/internal/gitremote 0.119s` | 0 | | `nix develop -c go test -tags site,secret ./...` | `ok forge.anarch.diy/allod/tools/internal/gitremote 0.120s` | 0 | | `git diff --check` | no output | 0 | Cold reviews, each reading a `.git`-free snapshot and the diff against master, both from the GPT family because the change was authored by a Claude model: `gpt-5.5` at effort high through codex on the first commit found one medium (`rotate` printed `refresh-local-auth` as the next step without checking it exists, so a live run could land a rotation the operator could not finish) and one low (a refusal string still said `rotate-token` still rotates a plain legacy entry); both were verified against the source and fixed in the second commit with tests. `gpt-5.6-terra` at thinking high through pi read the final tree and reported no findings, confirming the gate runs before the value is read and on a dry run and that its test fails with the gate removed. A first `gpt-5.6-terra` pass on the first commit also reported no findings. Not run: a live `migrate` or `rotate` on the host. Refs allod/nexus#52 Refs allod/tools#197
allod/nexus#52 retires rotate-token and turns its refresh-local-auth
subcommand into a host script of its own name. rotate's dry-run and
live printed deploy step now says 'refresh-local-auth --group <alias>'
instead of 'rotate-token refresh-local-auth --group <alias>', so an
operator on a rebuilt host runs the command that will actually exist.

migrate no longer refuses outright a credential that feeds a
local_auth_refresh entry (that refusal named a nexus PR that has now
landed). Instead, before decrypting anything, it requires the
credential to be a credential-store-url container - an rclone stanza
can never render the netrc line the refresh consumes - and it requires
refresh-local-auth to resolve on PATH, refusing with an explanation
when a host's nexus pin predates allod/nexus#52 and would otherwise be
left with a migrated entry the old rotate-token refresh cannot handle.

docs/allod-secret.md is updated to match: the printed step's new
spelling, the local-auth migration rule, and the coexistence section's
description of a plain legacy entry, which rotate now refuses outright
rather than leaving to a rotate-token fallback that no longer exists.

Refs allod/nexus#52
rotate printed the refresh-local-auth deploy step for a group with
local_auth_refresh entries without ever checking the script exists. On
a host whose nexus pin predates allod/nexus#52, a live run would land
the rotation and only then leave the operator unable to run the
required next step. Add the same PATH gate migrate already has: before
the value is read, and on --dry-run too, such a group requires
refresh-local-auth to resolve on PATH, refusing with an explanation
naming the host's nexus pin otherwise.

Also reword legacyRefusalReason's plain-legacy-value message: it named
'rotate-token' as still rotating such an entry, which is no longer
true now that rotate-token is retired.

Refs allod/nexus#52
vnprc approved these changes 2026-09-12 23:57:43 +01:00
vnprc merged commit 16b53edd9c into master 2026-09-12 23:57:49 +01:00
vnprc deleted branch agent/refresh-local-auth-migrate 2026-09-12 23:57:49 +01:00
Sign in to join this conversation.
No description provided.