Null agentTokenFile with forgeTokenFile under the Forge opt-out #11

Merged
vnprc merged 1 commit from agent/agent-token-opt-out into master 2026-07-31 00:57:44 +01:00
Member

This completes the Forge opt-out for dev machines: forgeAccess = false now nulls the shared agent PR token file as well as the per-machine Forge HTTPS token, so a machine that pushes no code is never assigned an encrypted file it has no key to open. One flag now means one thing. No machine in this template opts out, so nothing changes for the current fleet — the produced identities are proven value-identical. This is the data half of the fix and must merge after the framework half, allod/archetypes#18, which makes the builder tolerate the null and enforce that the two files stay paired.

Closes allod/archetypes#17

What changed

devIdentities binds the flag once (forgeAccess = vm.forgeAccess or true) and derives both forgeTokenFile and agentTokenFile from it, so the two files cannot drift apart within the template. The comment now explains why the agent token follows the flag: the shared ciphertext's recipients are the hypervisor plus the machines that push, so a non-pushing machine cannot decrypt it and must not be handed it. Deployments that fork this template pick the change up when they port the opt-out.

Landing order

This PR merges second, after allod/archetypes#18. Under the previous builder a null agentTokenFile hits the token module's file = null as a bare type error for any opted-out machine; the archetypes PR makes the module conditional and adds the eval-head assert that keeps the two files paired. Merged in that order there is no broken intermediate state for any consumer. The enforcement deliberately lives on the archetypes side, in the builder — the last point that sees a machine's final credential values and the one piece of framework code every fork shares — which is also what holds this mapping honest: composing the old unconditional mapping with an opted-out machine trips that assert with a named error, as demonstrated below.

Risk

R1 residual. The opt-out branch is dead data until a deployment declares forgeAccess = false — the current fleet's identities are value-identical before and after — and rollback is a straight revert. The one live concern is a fork porting this half without the archetypes half; the wrong order fails at evaluation either way (the old builder's type error, or the new builder's named assert), never silently.

Validation

  • nix flake check passes in this repo.
  • lib.devIdentities on this branch is value-identical to master for every identity the template produces, compared as JSON with store-path prefixes normalized: allod-dev keeps both credential files.
  • The new else-branch is exercised end to end through the real mapping: a scratch copy of this branch with allod-dev flipped to forgeAccess = false, composed with the archetypes branch, evaluates that machine cleanly with config.age.secrets = {}. The same flip under master's mapping — the half-done state the issue describes, agent token still set while the HTTPS token is null — fails the archetypes branch's pairing assert with 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.
  • Inertness from allod/deploy, evaluated one configuration at a time with --override-input pinned to explicit revisions (base = both masters, since deploy's committed lock trails master): nexus, privacy-1, and the installer keep byte-identical toplevel.drvPath with both branches substituted. allod-dev's drvPath shifts — a finding, and a benign one: dev machines embed the whole secrets source store path through their age.secrets.*.file references, so any commit to this repo shifts their derivation. Attribution was isolated three ways: archetypes branch alone leaves allod-dev's drvPath identical to base; archetypes master with this branch produces the same shifted drvPath as both branches together; and a comment-only control commit to this repo shifts it again. The machine's generated age.secrets under both branches is identical to the pre-change baseline once the source store prefix is normalized — same file names, paths, owners, groups, and modes.
This completes the Forge opt-out for dev machines: `forgeAccess = false` now nulls the shared agent PR token file as well as the per-machine Forge HTTPS token, so a machine that pushes no code is never assigned an encrypted file it has no key to open. One flag now means one thing. No machine in this template opts out, so nothing changes for the current fleet — the produced identities are proven value-identical. This is the data half of the fix and must merge after the framework half, allod/archetypes#18, which makes the builder tolerate the null and enforce that the two files stay paired. Closes allod/archetypes#17 ## What changed `devIdentities` binds the flag once (`forgeAccess = vm.forgeAccess or true`) and derives both `forgeTokenFile` and `agentTokenFile` from it, so the two files cannot drift apart within the template. The comment now explains why the agent token follows the flag: the shared ciphertext's recipients are the hypervisor plus the machines that push, so a non-pushing machine cannot decrypt it and must not be handed it. Deployments that fork this template pick the change up when they port the opt-out. ## Landing order This PR merges second, after allod/archetypes#18. Under the previous builder a null `agentTokenFile` hits the token module's `file = null` as a bare type error for any opted-out machine; the archetypes PR makes the module conditional and adds the eval-head assert that keeps the two files paired. Merged in that order there is no broken intermediate state for any consumer. The enforcement deliberately lives on the archetypes side, in the builder — the last point that sees a machine's final credential values and the one piece of framework code every fork shares — which is also what holds this mapping honest: composing the old unconditional mapping with an opted-out machine trips that assert with a named error, as demonstrated below. ## Risk R1 residual. The opt-out branch is dead data until a deployment declares `forgeAccess = false` — the current fleet's identities are value-identical before and after — and rollback is a straight revert. The one live concern is a fork porting this half without the archetypes half; the wrong order fails at evaluation either way (the old builder's type error, or the new builder's named assert), never silently. ## Validation - `nix flake check` passes in this repo. - `lib.devIdentities` on this branch is value-identical to master for every identity the template produces, compared as JSON with store-path prefixes normalized: allod-dev keeps both credential files. - The new else-branch is exercised end to end through the real mapping: a scratch copy of this branch with allod-dev flipped to `forgeAccess = false`, composed with the archetypes branch, evaluates that machine cleanly with `config.age.secrets = {}`. The same flip under master's mapping — the half-done state the issue describes, agent token still set while the HTTPS token is null — fails the archetypes branch's pairing assert with `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`. - Inertness from `allod/deploy`, evaluated one configuration at a time with `--override-input` pinned to explicit revisions (base = both masters, since deploy's committed lock trails master): nexus, privacy-1, and the installer keep byte-identical `toplevel.drvPath` with both branches substituted. allod-dev's drvPath shifts — a finding, and a benign one: dev machines embed the whole secrets source store path through their `age.secrets.*.file` references, so any commit to this repo shifts their derivation. Attribution was isolated three ways: archetypes branch alone leaves allod-dev's drvPath identical to base; archetypes master with this branch produces the same shifted drvPath as both branches together; and a comment-only control commit to this repo shifts it again. The machine's generated `age.secrets` under both branches is identical to the pre-change baseline once the source store prefix is normalized — same file names, paths, owners, groups, and modes.
forgeAccess = false now nulls both dev-identity credential files, so a
machine that pushes no code is never handed the shared agent-token
ciphertext, whose recipient set does not include it. The archetypes dev
builder skips the token module when the file is null and asserts the
two files stay paired, so one flag means one thing.

Refs allod/archetypes#17
vnprc approved these changes 2026-07-31 00:57:39 +01:00
vnprc merged commit 8381b4f857 into master 2026-07-31 00:57:44 +01:00
vnprc deleted branch agent/agent-token-opt-out 2026-07-31 00:57:44 +01:00
Sign in to join this conversation.
No description provided.