Skip to content

main → 2887 → 2883 → 2884 → 2885 → 2886 → 2888 - #2889

Merged
cameronraysmith merged 13 commits into
mainfrom
fm/linearize-2883-2888
Sep 1, 2026
Merged

main → 2887 → 2883 → 2884 → 2885 → 2886 → 2888#2889
cameronraysmith merged 13 commits into
mainfrom
fm/linearize-2883-2888

Conversation

@cameronraysmith

Copy link
Copy Markdown
Owner

This is an integration gate for six pull requests that have been arranged into a single linear chain on top of main. Its head commit is the tip of that chain, so it carries exactly the same thirteen commits as the six member pull requests and introduces nothing of its own.

The chain, from the bottom up:

  1. magnetite: confine the builder account's key to the nix protocol #2887 — magnetite: confine the builder account's key to the nix protocol
  2. feat(system): add stibnite as an aarch64-darwin build target #2883 — feat(system): add stibnite as an aarch64-darwin build target
  3. fix(deps): pin conventional-changelog preset to the v9 line #2884 — fix(deps): pin conventional-changelog preset to the v9 line
  4. atomic: update to 0.9.17 #2885 — atomic: update to 0.9.17
  5. feat(omp): declare direnv preflight settings for agent bash commands #2886 — feat(omp): declare direnv preflight settings for agent bash commands
  6. Fix store-copied package updaters and update linear-cli to 2.5.0 #2888 — Fix store-copied package updaters and update linear-cli to 2.5.0

Each member pull request now targets the branch immediately below it, so each one shows only its own layer of the diff. The ordering places #2887 first so that the documentation added in #2883 describes a key restriction that is already in effect at that point in history.

Merging this pull request by fast-forward advances main to the tip of the chain. Because every member's head commit then becomes reachable from main, all six close as merged, and no merge commit is created.

The chain was produced by replaying each branch onto the one below it. The content of every commit is unchanged from its original form: a commit-by-commit content comparison against the pre-arrangement state pairs all thirteen commits with no unmatched commit in either direction. Every commit in the chain carries a valid signature, which also restores signatures on the four commits in #2888 that an earlier history edit had dropped.

No file content was modified, so verification rests on the checks already configured for each member pull request; those run against the individual layers and again against this branch as the fully integrated result.

The nix-remote-build key that stibnite holds authorized an unrestricted
shell on magnetite's builder account, which is a nix trusted user on a
host whose sshd accepts connections from the public internet. Wrap that
key with restrict and a forced nix-daemon --stdio command, so it can
start only the protocol its single consumer speaks: the ssh-ng
buildMachines entry in modules/system/magnetite-builder.nix, which
authenticates with the matching private half at
config.clan.core.vars.generators.nix-remote-build.files.key.path.

The forced command interpolates config.nix.package rather than a bare
program name so it resolves to the nix deployed on magnetite, and the
account keeps its default bash shell because sshd runs the forced
command through it.

Drop crs58's personal keys from the same account. They authorize the
wheel account fleet-wide through modules/system/admins.nix, where wheel
also has passwordless sudo, so sudo -u builder still reaches the account
for debugging and no access is lost.

The builder entry in nix.settings.trusted-users is deliberately
unchanged; removing it depends on per-machine store signing, which is
tracked separately.

Verified by evaluation, which renders the key list as a single entry
carrying restrict, the store path of the deployed nix, and no personal
keys. Ran the treefmt, structure-nixos-configurations and
deployment-safety checks. No deployment was performed, so the live
remote build path is untested.
…ines

stibnite is the fleet's only aarch64-darwin machine. magnetite is
x86_64-linux and cannot build darwin derivations, and no nix.buildMachines
entry named stibnite existed, so darwin derivations had no build target.

modules/system/stibnite-access.nix exports three aspects. The nixos aspect
services.stibnite-builder computes a nix.buildMachines entry, which
magnetite splices into its own nix.buildMachines; the module never sets
that option itself. The same aspect computes a read-only storeUri and
materializes it at /etc/nix/stibnite-store-uri, for a caller that wants
nix build --store to run the build inside stibnite's store rather than
copying the output closure back. The darwin aspect
services.stibnite-build-host creates the nixbuild account on stibnite and
authorizes the two keys.

The build key is authorized on nixbuild with restrict and a forced
nix-daemon --stdio command, which is the program an ssh-ng caller would
have invoked, so the key can speak the protocol and nothing else. nixbuild
is also placed in nix.settings.trusted-users, which is store-root
equivalent on stibnite. That grant is required rather than incidental: an
untrusted account cannot receive the unsigned store paths a caller
evaluated itself, because the daemon rejects them as lacking a signature
by a trusted key. magnetite's own builder account carries the same grant.
The session key is authorized on crs58 without a forced command and is the
broader of the two credentials, since crs58 is in the admin group and
stibnite's trusted-users includes @admin.

Activation adds nixbuild to the com.apple.access_ssh service ACL. sshd
gates on that ACL, which on stibnite nests only the admin group, so a
non-admin build account is refused before its key is consulted and the
failure reads as Permission denied (publickey) with a correct key
installed.

The builder entry advertises aarch64-darwin alone, because nix config show
extra-platforms on stibnite reports that system and no other. maxJobs is 4
against 18 logical cores and 64 GiB on a machine that is also in
interactive use and commits 12 cores and 48 GiB to the rosetta VM.
…etite

The stibnite-nix-build generator produces the keypair magnetite uses to
reach stibnite's nix daemon. The private half is committed encrypted and
is decryptable by magnetite and by the users recorded alongside it; the
public half is committed in clear, and stibnite's configuration reads it
at evaluation time to authorize the nixbuild account. No plaintext private
key material is committed.
…magnetite

The stibnite-agent-session generator produces the keypair magnetite uses
for interactive ssh to stibnite as crs58. It is a separate generator from
stibnite-nix-build so that either key can be revoked or rotated without
disturbing the other. The private half is committed encrypted and the
public half in clear, as with the build key.
…hines

The wiring spans two machines, and neither machine's own evaluation can
catch a mismatch between them. Swapping the two keys, dropping the forced
command from the build key's authorized-keys entry, or letting magnetite's
buildMachines entry and its /etc/nix/stibnite-store-uri name different
accounts all evaluate and activate cleanly while being wrong.

stibnite-access-wiring evaluates both configurations together and asserts
the pairing: that magnetite's builder entry and store URI name the same
account and ssh alias, that stibnite authorizes the build key with restrict
and the forced nix-daemon --stdio command, and that the build account is a
trusted user. The check is defined for both x86_64-linux and
aarch64-darwin so either machine can run it.
A remote builder and a remote store are routinely treated as one thing.
They differ in where the output closure ends up, which is what decides
between them, so the distinction is written down alongside the wiring
rather than left to be rediscovered.

A remote builder copies input closures out to stibnite, builds there, and
copies the output closure back, so the result exists in the caller's
store. Under nix build --store ssh-ng:// the remote store is the store:
evaluation stays with the caller, the derivation and its source inputs are
written to stibnite, and the output stays there. The page also records the
authority the two keys actually carry, that CI cannot reach this builder
because nixbot.toml sets attribute to checks.x86_64-linux, and the
operator steps that activation still requires on each machine.
The docs release path has failed at generateNotes on every main push
with a release to make since the preset was bumped to ^10.4.0.

Preset major 10 requires conventional-changelog-writer major 9 or
newer, but semantic-release@25.0.9 resolves
@semantic-release/release-notes-generator@14.1.0, which pins writer
^8.0.0 and therefore 8.2.0. The failure is not a version check but a
planted handlebars template: @conventional-changelog/template@1.4.0
supplies a mainTemplate whose body is the error sentence itself, a
writer-8 renderer compiles it and throws it as a Missing helper
error, and writer 9 ignores the option entirely.

Upgrading forward is unavailable. Every semantic-release channel,
including 26.0.0-beta.1, depends on release-notes-generator ^14.1.0,
and only 15.0.0-beta.2 accepts writer 9, which no semantic-release
release references. A top-level override forcing writer 9 also fails,
because the package manager nests writer 8 beneath
release-notes-generator to satisfy its own range and the nested copy
is the one that renders. Pinning to a lower v10 does not help either,
since presets 10.0.0 through 10.3.0 all resolve
@conventional-changelog/template to 1.4.0 today.

9.3.1 is the version upstream itself tests against writer 8, and
unlike 10.4.0 it does not raise the node engine requirement to 22.
Both manifests change together because the root declares
workspaces: ["packages/*"] and bun.lock keys packages by name, so one
version resolves for the whole workspace.

The renovate rule is required rather than hygiene:
.github/workflows/regenerate-lock-files.yaml re-runs the generator on
Renovate pull requests and amends the branch, so without the hold the
next Renovate run restores ^10.4.0 and re-materializes it into
bun.nix automatically. That workflow is gated to renovate[bot], so
bun.nix is regenerated here by hand.

Verification. The repository's own check set does not cover this
defect: the release path runs only as a post-build effect on main,
and the package-vanixiets-docs* checks never invoke generateNotes. A
green check set is therefore not evidence that this is fixed, so the
primary evidence is a paired reproduction. generateNotes from
release-notes-generator@14.1.0 was invoked with the repository's real
pluginConfig against the repository's own bun-resolved tree over the
real 9 commits since @vanixiets/docs-v0.6.0, holding writer at 8.2.0
and changing only the preset: 10.4.0 fails with the CI error
verbatim, and 9.3.1 renders the 0.6.1 notes correctly.

Checks run, selected as the narrowest set that would fail if this
change were wrong:

  nix build --no-link --print-out-paths \
    .#checks.aarch64-darwin.{treefmt,package-vanixiets-docs-deps,\
    package-vanixiets-docs,package-vanixiets-docs-test-unit}

All four pass. package-vanixiets-docs-deps is the derivation that
materializes node_modules from bun.nix, so a stale or inconsistent
lockfile fails there, and its log shows
bun-pkg-conventional-changelog-conventionalcommits-9.3.1.drv;
package-vanixiets-docs builds atop it; treefmt covers the changed nix
and json; -test-unit confirms the retuned tree still supports the
test runner. Deliberately left out: -test-e2e and -test-linkcheck,
which exercise docs site content this diff does not touch, and the
full check set, whose blast radius far exceeds a dependency pin.
Regenerate the npm source, dependency hashes, and distribution repair patch with the package-owned updater.

Verification: nix build .#atomic; ./result/bin/atomic --version; nix eval --raw .#atomic.version; nix eval --raw .#apps.<system>.update-atomic.program
omp folds a clone's direnv environment into the bash commands it runs,
which is what lets a devshell-provided tool resolve without wrapping the
call in `nix develop` or `direnv exec`. The mechanism is on by default;
what this sets is the budget it runs under.

`bash.direnvLoadTimeoutMs` goes from the upstream 30000 to 180000. The
first export in a clone has to realise the flake devshell, measured at
7.4 seconds in this repository against an already-populated nix store and
running to minutes when the closure has to be built, while later exports
served from the nix-direnv cache return in under 10 milliseconds. The
higher ceiling cannot stall a short command, because the effective budget
is the smaller of this value and the command's own deadline. Overrunning
it runs the command without the direnv environment rather than failing,
so a tight value loses devshell tools silently.

`bash.direnv` is declared at the value it already defaults to, so that a
change to that default appears as a diff here rather than as devshell
tools disappearing from every bash command.

A clone whose .envrc has not been through `direnv allow` still runs
without the devshell. That gate belongs to direnv's own configuration and
is untouched here.
@mergify

mergify Bot commented Sep 1, 2026

Copy link
Copy Markdown

⚠️ The sha of the head commit of this PR conflicts with #2888. Mergify cannot evaluate rules on this PR. Once #2888 is merged or closed, Mergify will resume processing this PR. ⚠️

@cameronraysmith cameronraysmith changed the title Integrate the stibnite build-access, dependency and updater changes as one linear chain main → 2887 → 2883 → 2884 → 2885 → 2886 → 2888 Sep 1, 2026
@cameronraysmith cameronraysmith changed the title main → 2887 → 2883 → 2884 → 2885 → 2886 → 2888 main → #2887 → #2883 → 2884 → 2885 → 2886 → 2888 Sep 1, 2026
@cameronraysmith cameronraysmith changed the title main → #2887 → #2883 → 2884 → 2885 → 2886 → 2888 main → 2887 → 2883 → 2884 → 2885 → 2886 → 2888 Sep 1, 2026
@cameronraysmith
cameronraysmith merged commit bd9a7ea into main Sep 1, 2026
11 checks passed
@cameronraysmith
cameronraysmith deleted the fm/linearize-2883-2888 branch September 1, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant