Store named Pi credential tokens with per-token ciphertexts #14
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/secrets!14
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/named-tokens-registry"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A Pi credential can now hold several named tokens instead of exactly one, and each token gets its own encrypted file.
Nothing changes for any running machine today. The public credential registry is empty, so this generates no secrets, no recipients, and no per-VM settings — same as before.
What stays identical: every non-Pi secret, every recipient list, every other flake output, and the empty public registry itself.
What changes is the contract that a private deployment fills in. A credential record is now exactly providers, targets, tokens, and defaultToken; the old rotationStrategy field is gone and a record still carrying it is rejected. Each token stores its bearer at
secrets/pi-credentials/<credential>/<token>.age, all tokens of one credential are encrypted to the same machines, and each VM is told only the token names and file paths — never a bearer or an endpoint.What proves it: both repository checks build green, and each rejection rule has a deliberately broken fixture that the check must reject.
If it is wrong, revert this commit straight. Nothing downstream pins the new shape until the deploy slice lands, and no live credential is touched either way.
Risk
Residual risk is R3 High, matching the plan's slice table entry for the secrets contract. The scrutiny is on the exact schema, the per-token recipient sets and paths, and the guarantee that no secret data reaches a projection. Those three are pinned by exact-equality assertions against a synthetic two-credential fixture plus per-rule sabotage, but a synthetic fixture cannot attest that a private registry migrated cleanly or that a real ciphertext decrypts on its target: this change alters the ciphertext layout that a private fork will have to move files into, and the correctness of that move is owned by the Nexus slice and the private rollout, not by anything provable here. The public blast radius is bounded to zero by the empty registry.
Validation
From the change worktree:
Both derivations built successfully (exit 0).
Additionally run from the same worktree:
All three checks (
external-ssh-trust-targets,pi-credential-registry,credential-inventory) evaluated and built (exit 0).The
pi-credential-registrycheck was proved non-vacuous two ways before the final build. Corrupting one expected recipient key in the fixture expectation made the build fail witherror: pi-credential-registry: recipient derivation drifted; pointing therejectshelper at the valid fixture instead of a sabotage made it fail witherror: pi-credential-registry: retired rotationStrategy field was accepted. Both edits were reverted before committing.Each schema sabotage was also confirmed to fail for the intended reason by reading the contract's non-throwing
diagnosticsoutput: a leftoverrotationStrategyand a removedtokensfield reportentries have missing or unknown fields; empty, duplicated, and invalid-name token lists reporttokens must be non-empty unique token-name lists; an unlisted and a non-string default reportdefaultToken must be null or one listed token; and hiding a single token's ciphertext reportsmissing ciphertexts: secrets/pi-credentials/shared/secondary.age, which pins per-token path derivation rather than the credential root alone. Token-name length was checked at the boundary: 63 characters is accepted and 64 is rejected.The empty public outputs were verified directly:
pi-credentials.jsonis{}, andlib.piCredentialRecipients,lib.piCredentialCiphertextPaths, and the derived credential inventory are all empty, withsecrets.nixstill listing exactly its six pre-existing paths.Refs allod/strategy#36
A Pi credential record is now exactly providers, targets, tokens, and defaultToken. The vestigial rotationStrategy field is gone, so a record still carrying it fails as an unknown field. Every named token derives its own ciphertext at secrets/pi-credentials/<credential>/<token>.age, all tokens of one credential share that credential's Nexus-plus-target active/staged recipient set, and the derived inventory emits one agenix consumer per ciphertext. The per-VM projection carries names and paths only: credentials.<credential> = { providers; tokens.<token>.file; defaultToken; }. The public registry stays empty and still generates nothing. Model: claude-opus-5Review pass (required post-open review): read-only diff review by gpt-5.6-terra at reasoning xhigh against the plan contract in allod/strategy dev-plans/pi-named-tokens.md. Scope: full master...HEAD diff, all in-repo consumers of the changed lib shapes (including the piCredentialCiphertextPaths credential->token->path change), sabotage non-vacuity, and every PR-body claim. Verdict: no findings. The reviewer sandbox could not reach the Nix daemon, so the managing agent independently re-ran the two acceptance checks from the PR worktree: nix build .#checks.x86_64-linux.credential-inventory .#checks.x86_64-linux.pi-credential-registry — both green.