Skip the agent token module when a dev machine has no token file #18
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/archetypes!18
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/agent-token-opt-out"
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 dev machine that opts out of Forge access is currently still handed the deployment's shared agent PR token — an encrypted file it has no key for, which breaks the machine the first time it activates, during provisioning. This PR makes the dev builder skip that token when the machine has no token file, the same way it already skips the per-machine Forge HTTPS credential, and adds a guard so a machine that has one of the two credentials without the other refuses to evaluate with an error naming the machine and both fields. No existing machine changes at all — every current machine's build derivation is proven byte-identical. This is the framework half of the fix; it must merge before allod/secrets#11, which completes the other half.
Refs allod/archetypes#17
What changed
mkDevVmincludesmodules/agent-forgejo-token.nixonly whentokenFile != null, mirroring the adjacentlib.optionalAttrs (httpsTokenFile != null)handling of the Forge HTTPS credential.devForgeCredentialMismatch, states the invariant the identity template maintains: the agent token file and the Forge HTTPS credential file are two facets of the singleforgeAccessidentity fact, so they are both set or both null.mkDevVmasserts it.dev-forge-opt-outcheck pins the contract with fixtures that reuse allod-dev's real identity and definition, varying only the two token files: the opted-out shape composes with emptyage.secretsand no agenix activation entries at all; the with-access shape keeps exactly today's two secrets, with file, path, owner, group, and mode captured literally from master's generated values before this change; each half-null shape fails evaluation, and the guard predicate is exercised directly so each sabotage fixture is pinned to its own reason.Where the assertion lives, and why
The pairing assert sits at the head of
mkDevVm, beforenixosSystem— not in the identity template and not only in a check. Three reasons. First, the builder is the last composition point where the final values meet:tokenFileandhttpsTokenFiledefault from the identity but can be overridden per machine through the profiles repo'sprofileData/profileSettings, so only the builder sees what a machine actually composes with, and a template-level assert would miss an override that nulls one of them for some unrelated reason. Second, the template is forkable data — an assertion there protects only the copy that carries it, while the builder is framework code every deployment shares, which is the deepest layer that cannot be talked out of the rule. Third, an assert at the builder's head makes the broken machine itself refuse to evaluate for every consumer — a deploy flake evaluatingnixosConfigurations, not just someone who runsnix flake check— which is what "fail at evaluation, not at first boot" has to mean.The head position is also load-bearing for the fixtures: a bare weak-head force of the builder call can only be failed by this assert, because the failure it replaces — the token module's
file = nulltype error — only surfaced deep inside module evaluation. The check's half-null fixtures therefore pass only when the eval-head assert itself fires, not when evaluation dies for some other reason.Landing order
This PR merges first. If the secrets change landed first, an opted-out machine would carry a null
agentTokenFilewhile this builder still included the token module unconditionally — an unhelpfulfile = nulltype error for anyone composing in between. In the correct order there is no intermediate breakage: no machine in the public template opts out, and a fork that already ported the opt-out gets a loud, named eval error instead of a machine that fails to decrypt during provisioning.Out of scope, per the issue: the
forgeAccessmechanism itself, and how the shared agent token's recipient set is chosen.Risk
R2 residual. The territory is inherently R3 — cross-repo sequencing plus generated activation behavior around credentials — but the validation below proves every current machine's derivation unchanged, proves both directions of the new eval error including the exact half-done state the issue describes, and lands sabotage-backed fixtures that keep all of it pinned; rollback is a straight revert. The residual worth human eyes: a private fork whose dev identities legitimately violate the both-or-neither invariant would start failing eval on pull with the new named error. The public template derives both files from one flag so no such shape should exist, but the framework cannot see private forks.
Validation
nix flake checkpasses on this branch: 9 checks including the newdev-forge-opt-out, peak RSS 2.9 GiB.config.age.secrets == {}and zeroagenix*activation entries; end to end, a scratch secrets tree flipping allod-dev toforgeAccess = falsethrough the real template mapping (with the secrets-side branch) evaluatesconfig.age.secretsto{}under--override-input secrets.nixosConfigurations.allod-dev.config.age.secretson this branch is byte-identical to the baseline captured from master before the change — same files, paths, owners, groups, modes — and the committed check pins that projection literally rather than deriving it from the identity the generator reads.dev machine 'allod-dev': agent token file and Forge HTTPS credential must be both set or both null (identity forgeAccess opts out of both together); got tokenFile set, httpsTokenFile null, while master silently composes that machine with a singleage.secretsentry pointing at the undecryptable ciphertext.credential-profilesstill binds every declared token target to a generatedage.secretsentry: it passes here, and against a scratch secrets whose machine opted out while the token registry still declares its HTTPS target it fails loudly (Forgejo token registry targets do not match generated age.secrets file/path: allod-dev.git:forgejo-https-token-allod-dev:allod-dev:/root/.git-credentials), so the registry binding remains the backstop for a half-ported opt-out.allod/deploy, evaluated one configuration at a time with--override-inputpinned to explicit revisions (base = both masters, since deploy's committed lock trails master): for all four configurations (allod-dev, nexus, privacy-1, installer),config.system.build.toplevel.drvPathwith this branch substituted is identical to base. With the secrets branch also substituted, allod-dev's drvPath shifts while the other three stay identical; that shift belongs entirely to the secrets input — archetypes master with the secrets branch produces the same shifted drvPath, and a comment-only control commit to secrets shifts it again — because dev machines embed the secrets source store path throughage.secrets.*.file. Details and the store-prefix-normalized identity proof are in allod/secrets#11.