allod secret rotate: read the credential-store URL grammar as data #216

Merged
vnprc merged 1 commit from agent/credential-url-predicate into master 2026-09-19 22:12:21 +01:00
Member

allod secret rotate stops carrying its own copy of the credential-store URL rule. The regular expression and the blank-line class that decide whether a credential can feed a local auth refresh are now read from the secrets flake's lib.credentialStoreUrl export at rotation time, next to the lib.credentialEncodings read the command already makes, and the hand-written Go copy with its comment claiming parity with allod/archetypes and allod/nexus is deleted. What changes for a running machine: a rotation against a secrets checkout whose flake lacks that export is refused by name, pointing at allod/secrets#29, before anything is rotated, and an export whose grammar is empty or does not compile is refused rather than matching everything. What stays identical: which templates are accepted, because the exported expression is equivalent to the deleted literal and the shared 26-vector table pins both sides, and every other step of rotation. What proves it is the predicate test, which reads the table checked in under testdata as a byte copy of the secrets file, a table test of the export decoding, the flake's go-checks, and a dry run of the built binary against the secrets PR worktree and against a checkout at current master. If this is wrong the symptom is rotate refusing a group with a message naming lib.credentialStoreUrl, and the fix is a straight revert. A deployment's private fork of allod/secrets must merge the template change from allod/secrets#29 before this build of allod rotates against it.

Refs allod/secrets#29

What changed

  • cmd/allod/secret_rotate.go: credentialStoreURLTemplate and its parity comment are gone; credentialStoreURLGrammar holds the two compiled expressions; nixEvalCredentialStoreURL reads lib.credentialStoreUrl through the existing nixEvalJSON path and hands the result to decodeCredentialStoreURLGrammar, a pure function that maps nix's does not provide attribute wording to the named refusal, refuses an empty line or blank_line, and compiles both; isCredentialStoreURLSource takes the grammar and, to agree with the table, also refuses a credential carrying format (the registry decoder already refuses such a registry earlier, so this is not a new refusal on the rotation path); validateGroupMetadata passes the grammar through.
  • cmd/allod/testdata/credential-store-url.json: byte copy of the secrets file (grammar plus vectors). Until the archetypes credential-store-url-parity check lands, cmp against the secrets checkout is the witness that it has not drifted.
  • cmd/allod/secret_value_test.go: TestIsCredentialStoreURLSource runs the table; TestDecodeCredentialStoreURLGrammar covers the missing-export text, an unrelated error passed through, an empty line, a non-compiling line, and the real file. The hand-written accept and refuse lists are gone. secret_test.go and secret_rotate_test.go wire the new eval seam into the existing fixture the way secretEvalEncodings is wired.

Risk

R3. This is a cross-repo interface: rotation now depends on an export of the secrets flake, and a deployment's private fork must carry it before this build rotates against it, which is auth and secrets sequencing. What holds it there rather than higher: the accepted set is unchanged and pinned by the table on both sides, every new failure mode fails closed before rotation starts with a named message, and rollback is a straight revert.

Validation

Run by the overseer in the worktree at commit 5b5b5c2, with CGO_ENABLED=0 under nix shell nixpkgs#go:

  • cmp cmd/allod/testdata/credential-store-url.json <secrets PR worktree>/credential-store-url.json — silent.
  • gofmt -l ./cmd ./internal — empty; go vet ./... — clean.
  • go test ./..., go test -tags secret ./..., go test -tags site,secret ./... — all ok.
  • Built with -tags secret,site, allod secret rotate forgejo-https-token-allod-dev <secrets PR worktree> --dry-run reads lib.credentialStoreUrl and proceeds to the group metadata guard, which refuses the public template's group for declaring no service (pre-existing, unrelated). The same against a branch checkout at current allod/secrets master — could not evaluate lib.credentialStoreUrl in <checkout>: the secrets checkout does not export lib.credentialStoreUrl and predates allod/secrets#29.

Run by the worker, output read by the overseer:

  • nix build path:.#checks.x86_64-linux.go-checks — built.
  • Sabotage: widening the copy's blank_line to [[:space:]] fails the test naming the carriage-return, vertical-tab and form-feed vectors; reverted, cmp silent again.

Cold cross-family review: codex gpt-5.6-sol, reasoning effort high, on the commit before the fix round (e3254c3). One major, fixed: a missing or null line compiled to an empty expression that matched everything; empty fields are now refused by name. Minor, fixed: the missing-export mapping was untested; it is now a pure function with a table test using nix's exact wording. Minor, body corrected: the format refusal is unreachable on the production path because the decoder refuses first; the exported expression is equivalent to, not identical with, the deleted literal; risk raised from R2 to R3 for the cross-repo sequencing. Findings after the fix round are settled by the runs above, not by another reading.

`allod secret rotate` stops carrying its own copy of the credential-store URL rule. The regular expression and the blank-line class that decide whether a credential can feed a local auth refresh are now read from the secrets flake's `lib.credentialStoreUrl` export at rotation time, next to the `lib.credentialEncodings` read the command already makes, and the hand-written Go copy with its comment claiming parity with allod/archetypes and allod/nexus is deleted. What changes for a running machine: a rotation against a secrets checkout whose flake lacks that export is refused by name, pointing at allod/secrets#29, before anything is rotated, and an export whose grammar is empty or does not compile is refused rather than matching everything. What stays identical: which templates are accepted, because the exported expression is equivalent to the deleted literal and the shared 26-vector table pins both sides, and every other step of rotation. What proves it is the predicate test, which reads the table checked in under `testdata` as a byte copy of the secrets file, a table test of the export decoding, the flake's `go-checks`, and a dry run of the built binary against the secrets PR worktree and against a checkout at current master. If this is wrong the symptom is rotate refusing a group with a message naming `lib.credentialStoreUrl`, and the fix is a straight revert. A deployment's private fork of allod/secrets must merge the template change from allod/secrets#29 before this build of `allod` rotates against it. Refs allod/secrets#29 ## What changed - `cmd/allod/secret_rotate.go`: `credentialStoreURLTemplate` and its parity comment are gone; `credentialStoreURLGrammar` holds the two compiled expressions; `nixEvalCredentialStoreURL` reads `lib.credentialStoreUrl` through the existing `nixEvalJSON` path and hands the result to `decodeCredentialStoreURLGrammar`, a pure function that maps nix's `does not provide attribute` wording to the named refusal, refuses an empty `line` or `blank_line`, and compiles both; `isCredentialStoreURLSource` takes the grammar and, to agree with the table, also refuses a credential carrying `format` (the registry decoder already refuses such a registry earlier, so this is not a new refusal on the rotation path); `validateGroupMetadata` passes the grammar through. - `cmd/allod/testdata/credential-store-url.json`: byte copy of the secrets file (grammar plus vectors). Until the archetypes `credential-store-url-parity` check lands, `cmp` against the secrets checkout is the witness that it has not drifted. - `cmd/allod/secret_value_test.go`: `TestIsCredentialStoreURLSource` runs the table; `TestDecodeCredentialStoreURLGrammar` covers the missing-export text, an unrelated error passed through, an empty `line`, a non-compiling `line`, and the real file. The hand-written accept and refuse lists are gone. `secret_test.go` and `secret_rotate_test.go` wire the new eval seam into the existing fixture the way `secretEvalEncodings` is wired. ## Risk R3. This is a cross-repo interface: rotation now depends on an export of the secrets flake, and a deployment's private fork must carry it before this build rotates against it, which is auth and secrets sequencing. What holds it there rather than higher: the accepted set is unchanged and pinned by the table on both sides, every new failure mode fails closed before rotation starts with a named message, and rollback is a straight revert. ## Validation Run by the overseer in the worktree at commit 5b5b5c2, with `CGO_ENABLED=0` under `nix shell nixpkgs#go`: - `cmp cmd/allod/testdata/credential-store-url.json <secrets PR worktree>/credential-store-url.json` — silent. - `gofmt -l ./cmd ./internal` — empty; `go vet ./...` — clean. - `go test ./...`, `go test -tags secret ./...`, `go test -tags site,secret ./...` — all ok. - Built with `-tags secret,site`, `allod secret rotate forgejo-https-token-allod-dev <secrets PR worktree> --dry-run` reads `lib.credentialStoreUrl` and proceeds to the group metadata guard, which refuses the public template's group for declaring no `service` (pre-existing, unrelated). The same against a branch checkout at current allod/secrets master — `could not evaluate lib.credentialStoreUrl in <checkout>: the secrets checkout does not export lib.credentialStoreUrl and predates allod/secrets#29`. Run by the worker, output read by the overseer: - `nix build path:.#checks.x86_64-linux.go-checks` — built. - Sabotage: widening the copy's `blank_line` to `[[:space:]]` fails the test naming the carriage-return, vertical-tab and form-feed vectors; reverted, `cmp` silent again. Cold cross-family review: codex `gpt-5.6-sol`, reasoning effort `high`, on the commit before the fix round (e3254c3). One major, fixed: a missing or null `line` compiled to an empty expression that matched everything; empty fields are now refused by name. Minor, fixed: the missing-export mapping was untested; it is now a pure function with a table test using nix's exact wording. Minor, body corrected: the `format` refusal is unreachable on the production path because the decoder refuses first; the exported expression is equivalent to, not identical with, the deleted literal; risk raised from R2 to R3 for the cross-repo sequencing. Findings after the fix round are settled by the runs above, not by another reading.
isCredentialStoreURLSource used to carry a hand-written copy of the URL
regex and its own blank-line rule, kept in step with allod/archetypes and
allod/nexus only by a comment. It now takes a credentialStoreURLGrammar
compiled from the secrets flake's lib.credentialStoreUrl export, loaded
through the same nixEvalJSON seam pattern as lib.credentialEncodings, and
dies naming allod/secrets#29 when a checkout does not export it yet.

nixEvalCredentialStoreURL is split into the eval call and a pure decode
step, decodeCredentialStoreURLGrammar, so the missing-export mapping, an
unrelated eval failure, and the grammar decode can each be driven by a
table test without invoking nix. That decode step now also refuses an
empty line or blank_line before compiling it: a missing or null field
decodes to the empty string, and an empty regexp compiles and matches
everything, which would have turned a checkout missing the export's
fields into a predicate that accepts any template.

The shared vector table that backs lib.credentialStoreUrl is copied
byte-for-byte into cmd/allod/testdata/credential-store-url.json, and
TestIsCredentialStoreURLSource now reads every vector from that file and
decodes its credential into registryCredential instead of driving a
hand-written accept/refuse list, so a divergence from the other two
implementations fails a test here rather than surviving in a comment.

Refs allod/secrets#29
vnprc force-pushed agent/credential-url-predicate from 5b5b5c2ace to 08b381892b 2026-09-19 22:04:31 +01:00 Compare
vnprc merged commit 08b381892b into master 2026-09-19 22:12:21 +01:00
vnprc deleted branch agent/credential-url-predicate 2026-09-19 22:12:21 +01:00
Sign in to join this conversation.
No description provided.