forge: build the dev VM forge from the Go program #40

Merged
vnprc merged 2 commits from agent/forge-go-cutover into master 2026-08-24 23:01:51 +01:00
Member

The next rebuild of a dev VM installs forge as the Go program from allod/tools instead of the Bash script; nothing else on the VM changes, and no VM changes before a human rebuilds.

The pinned Go binary now includes forge pr snapshot, including the repository-identity and AGit ref checks used by the PR explanation workflow. Its package tests passed during the build, and I compared help, two error paths, four live read commands, and a live pull-request snapshot byte for byte against the Bash tool.

The Bash source remains in the same input and runnable from a checkout. If the cutover is wrong, revert this PR and rebuild; that restores both the Bash wrapper and the pre-cutover allod-tools pin at 93eb4baee4d74855eb451a415006d1ca26f3a6ad.

Risk

R3, per the migration plan. This swaps the deployed forge for every dev VM at the next human rebuild, and forge is the tool agents use for every Forge operation, so silent contract drift would land everywhere at once.

The diff against current master touches only the forge package and advances allod-tools by one commit. The other six tools in this module are unchanged. The Bash forge still ships in the pinned input because deleting it belongs after both cutovers, not in this PR.

Two rollback constraints remain. Moving only the input back while leaving the Go wrapper would still build at the recorded pre-cutover revision because that revision contains cmd/forge, but it would deploy a Go binary without pr snapshot; revert this PR so the implementation and wrapper move together. Later, once the nexus input carries allod/nexus#25, reverting this PR alone would leave the host wrapper and the old tools pin out of sync; from that point, roll the nexus input back together with this PR.

The wrapper's runtime pins change with the implementation. The Bash wrapper pinned jq and curl; the Go API client runs in-process and needs neither. The Go binary still runs git to infer the repository and current branch, so this wrapper pins git explicitly instead of relying on the ambient PATH.

Validation

Current master was merged into the cutover branch, resolving the lock conflict without reverting any of master's other input advances. The allod-tools input moves from 93eb4baee4d74855eb451a415006d1ca26f3a6ad to 5649cec426b5d36d740f9286481b5496ab01d4e3, the merged allod/tools#156 revision. That one-commit span adds the Go snapshot implementation and tests; the Bash source is unchanged.

I evaluated the package generated for the real allod-dev configuration, not a standalone approximation. Filtering nixosConfigurations.allod-dev.config.home-manager.users.allod.home.packages for pname == "forge" yielded /nix/store/bd2g9k60i2205c8d9pra17c1513ppjqr-forge-0.1.0.drv; building that derivation produced /nix/store/bvwm2lm8z9ik91lgcyqwknfjidif1l58-forge-0.1.0.

The derivation uses buildGoModule with Go 1.25.10, vendorHash = null, subPackages = cmd/forge, and doCheck = 1, so the cmd/forge suite—including the 19 ported snapshot scenarios—ran during the package build. subPackages scopes that build's check phase, so the separate internal/ package tests remain the responsibility of allod/tools' own checks.

The result is a Go package, not a shell application: bin/forge is a 394-byte wrapper that prepends the pinned Git 2.51.2 and executes bin/.forge-wrapped; the wrapped file starts with the ELF magic bytes and is about 7.1 MB.

Byte-for-byte comparisons against the Bash tool at current allod/tools master all matched, including stderr and exit status:

  • --help
  • pr view with its missing argument (exit 1)
  • issue create with its missing title (exit 1)
  • live read-only pr list, issue list, issue view 98, and auth status
  • live read-only pr snapshot 156, including schema version 1 and its repository/ref projection

The Git wrapper check also passed with an empty environment. The wrapped package inferred allod/tools and reached the deliberately dead API endpoint, reporting the parity label curl exit 7; invoking .forge-wrapped directly could not find Git and reported that no repository was specified. This proves the deployed wrapper supplies Git and does not imply that the Go client executes curl.

The exact source store path used by the derivation still contains an executable Bash forge, preserving the checkout fallback during deployment.

Not covered here: the rebuild itself, which is a human step. The host-side cutover remains allod/nexus#25. This repo pins nexus separately, so merging either PR alone changes no running host; the host changes only after that pin advances and a human rebuilds.

Refs allod/tools#155
Refs allod/tools#98

The next rebuild of a dev VM installs `forge` as the Go program from allod/tools instead of the Bash script; nothing else on the VM changes, and no VM changes before a human rebuilds. The pinned Go binary now includes `forge pr snapshot`, including the repository-identity and AGit ref checks used by the PR explanation workflow. Its package tests passed during the build, and I compared help, two error paths, four live read commands, and a live pull-request snapshot byte for byte against the Bash tool. The Bash source remains in the same input and runnable from a checkout. If the cutover is wrong, revert this PR and rebuild; that restores both the Bash wrapper and the pre-cutover allod-tools pin at `93eb4baee4d74855eb451a415006d1ca26f3a6ad`. ## Risk R3, per the migration plan. This swaps the deployed `forge` for every dev VM at the next human rebuild, and `forge` is the tool agents use for every Forge operation, so silent contract drift would land everywhere at once. The diff against current master touches only the `forge` package and advances `allod-tools` by one commit. The other six tools in this module are unchanged. The Bash `forge` still ships in the pinned input because deleting it belongs after both cutovers, not in this PR. Two rollback constraints remain. Moving only the input back while leaving the Go wrapper would still build at the recorded pre-cutover revision because that revision contains `cmd/forge`, but it would deploy a Go binary without `pr snapshot`; revert this PR so the implementation and wrapper move together. Later, once the `nexus` input carries allod/nexus#25, reverting this PR alone would leave the host wrapper and the old tools pin out of sync; from that point, roll the `nexus` input back together with this PR. The wrapper's runtime pins change with the implementation. The Bash wrapper pinned `jq` and `curl`; the Go API client runs in-process and needs neither. The Go binary still runs `git` to infer the repository and current branch, so this wrapper pins `git` explicitly instead of relying on the ambient PATH. ## Validation Current master was merged into the cutover branch, resolving the lock conflict without reverting any of master's other input advances. The `allod-tools` input moves from `93eb4baee4d74855eb451a415006d1ca26f3a6ad` to `5649cec426b5d36d740f9286481b5496ab01d4e3`, the merged allod/tools#156 revision. That one-commit span adds the Go snapshot implementation and tests; the Bash source is unchanged. I evaluated the package generated for the real `allod-dev` configuration, not a standalone approximation. Filtering `nixosConfigurations.allod-dev.config.home-manager.users.allod.home.packages` for `pname == "forge"` yielded `/nix/store/bd2g9k60i2205c8d9pra17c1513ppjqr-forge-0.1.0.drv`; building that derivation produced `/nix/store/bvwm2lm8z9ik91lgcyqwknfjidif1l58-forge-0.1.0`. The derivation uses `buildGoModule` with Go 1.25.10, `vendorHash = null`, `subPackages = cmd/forge`, and `doCheck = 1`, so the `cmd/forge` suite—including the 19 ported snapshot scenarios—ran during the package build. `subPackages` scopes that build's check phase, so the separate `internal/` package tests remain the responsibility of allod/tools' own checks. The result is a Go package, not a shell application: `bin/forge` is a 394-byte wrapper that prepends the pinned Git 2.51.2 and executes `bin/.forge-wrapped`; the wrapped file starts with the ELF magic bytes and is about 7.1 MB. Byte-for-byte comparisons against the Bash tool at current allod/tools master all matched, including stderr and exit status: - `--help` - `pr view` with its missing argument (exit 1) - `issue create` with its missing title (exit 1) - live read-only `pr list`, `issue list`, `issue view 98`, and `auth status` - live read-only `pr snapshot 156`, including schema version 1 and its repository/ref projection The Git wrapper check also passed with an empty environment. The wrapped package inferred `allod/tools` and reached the deliberately dead API endpoint, reporting the parity label `curl exit 7`; invoking `.forge-wrapped` directly could not find Git and reported that no repository was specified. This proves the deployed wrapper supplies Git and does not imply that the Go client executes curl. The exact source store path used by the derivation still contains an executable Bash `forge`, preserving the checkout fallback during deployment. Not covered here: the rebuild itself, which is a human step. The host-side cutover remains allod/nexus#25. This repo pins `nexus` separately, so merging either PR alone changes no running host; the host changes only after that pin advances and a human rebuilds. Refs allod/tools#155 Refs allod/tools#98
cmd/forge replaces the bash script: the API client runs in-process, so the
wrapper no longer needs jq or curl, and keeps only git for repository
inference and the current branch. The bash source still ships in the
allod-tools input and stays runnable from a checkout.
Author
Member

Read-only review pass on this PR, findings and what changed as a result. The code diff is unchanged; every fix below was to the PR body, which was wrong or overstated in three places.

Corrected: the rollback instruction was incomplete. "Revert this commit" is right today, but this flake also builds the host from its nexus input, so once that input carries the matching wrapper (allod/nexus#25), the host module in this same flake asks for cmd/forge as well, and reverting this commit alone would move allod-tools back to a rev without it and leave the host configuration unbuildable. The body now says to roll the nexus input back together with this commit from that point on.

Corrected: the cross-repo coupling was stated as merge order. It is not — this repo pins nexus separately, and that pin is untouched here, so allod/nexus#25 changes nothing on the host until someone bumps it. Either PR is safe to merge alone; the combination that cannot build is bumping this flake's nexus input past that wrapper while allod-tools sits at the old rev.

Corrected: two claims narrowed. doCheck runs the cmd/forge tests only, because subPackages scopes the check phase, so the internal/ tests do not run in this build. And the validation output forge: GET ... failed: curl exit 7 is a label the Go program prints from curlExitCode in cmd/forge/api.go, mapping Go transport errors onto curl's exit-code numbering; it is not a curl invocation, which matters because dropping pkgs.curl rests on that.

Checked and clean: the only child process anywhere in the Go program is git, from internal/gitremote — no LookPath, no shell-out, no editor or pager, and the API client uses net/http and encoding/json in-process, so dropping jq and curl is safe. Nothing in this repo or in allod/tools depends on forge being a shell script; callers use command -v forge and plain invocations. checks/pi-integration.nix filters home.packages by name, but for yolo and pi-coding-agent, so the derivation name changing from forge to forge-0.1.0 does not touch it. The bash source is present in the newly pinned input, verified in the store path this build read.

Two non-blocking observations for the owner, neither addressed here. src = allod-tools is the whole repository, so any commit to allod/tools — a README edit included — changes the source hash and rebuilds the Go binary and reruns its test suite on every dev VM; lib.fileset on the source or doCheck = false would decouple that if rebuild time becomes annoying. And the packaging expression now exists in three places, of which allod/tools' own dev flake is the odd one out: it has no git wrap, so nix build there yields an unwrapped binary that differs from what is deployed.

Read-only review pass on this PR, findings and what changed as a result. The code diff is unchanged; every fix below was to the PR body, which was wrong or overstated in three places. Corrected: the rollback instruction was incomplete. "Revert this commit" is right today, but this flake also builds the host from its `nexus` input, so once that input carries the matching wrapper (allod/nexus#25), the host module in this same flake asks for `cmd/forge` as well, and reverting this commit alone would move `allod-tools` back to a rev without it and leave the host configuration unbuildable. The body now says to roll the `nexus` input back together with this commit from that point on. Corrected: the cross-repo coupling was stated as merge order. It is not — this repo pins `nexus` separately, and that pin is untouched here, so allod/nexus#25 changes nothing on the host until someone bumps it. Either PR is safe to merge alone; the combination that cannot build is bumping this flake's `nexus` input past that wrapper while `allod-tools` sits at the old rev. Corrected: two claims narrowed. `doCheck` runs the `cmd/forge` tests only, because `subPackages` scopes the check phase, so the `internal/` tests do not run in this build. And the validation output `forge: GET ... failed: curl exit 7` is a label the Go program prints from `curlExitCode` in `cmd/forge/api.go`, mapping Go transport errors onto curl's exit-code numbering; it is not a curl invocation, which matters because dropping `pkgs.curl` rests on that. Checked and clean: the only child process anywhere in the Go program is `git`, from `internal/gitremote` — no `LookPath`, no shell-out, no editor or pager, and the API client uses `net/http` and `encoding/json` in-process, so dropping `jq` and `curl` is safe. Nothing in this repo or in allod/tools depends on `forge` being a shell script; callers use `command -v forge` and plain invocations. `checks/pi-integration.nix` filters `home.packages` by name, but for `yolo` and `pi-coding-agent`, so the derivation name changing from `forge` to `forge-0.1.0` does not touch it. The bash source is present in the newly pinned input, verified in the store path this build read. Two non-blocking observations for the owner, neither addressed here. `src = allod-tools` is the whole repository, so any commit to allod/tools — a README edit included — changes the source hash and rebuilds the Go binary and reruns its test suite on every dev VM; `lib.fileset` on the source or `doCheck = false` would decouple that if rebuild time becomes annoying. And the packaging expression now exists in three places, of which allod/tools' own dev flake is the odd one out: it has no git wrap, so `nix build` there yields an unwrapped binary that differs from what is deployed.
Author
Member

Cutover dependency update: agent archetype launchers now require forge pr snapshot, whose Go port is in allod/tools#156. Please do not merge/rebuild this cutover at its current allod-tools pin.

Once #156 merges, this branch needs current master merged into it, the allod-tools lock refreshed to the merged rev, and the package plus snapshot acceptance checks rerun before the human VM rebuild.

Refs allod/tools#155

Cutover dependency update: agent archetype launchers now require `forge pr snapshot`, whose Go port is in allod/tools#156. Please do not merge/rebuild this cutover at its current `allod-tools` pin. Once #156 merges, this branch needs current `master` merged into it, the `allod-tools` lock refreshed to the merged rev, and the package plus snapshot acceptance checks rerun before the human VM rebuild. Refs allod/tools#155
Author
Member

The owner has approved proceeding with the post-tools-merge refresh now. I am updating this branch against current master, advancing the allod-tools pin to the revision containing allod/tools#156, and rerunning the package and snapshot acceptance checks.

Refs allod/tools#155

The owner has approved proceeding with the post-tools-merge refresh now. I am updating this branch against current `master`, advancing the `allod-tools` pin to the revision containing allod/tools#156, and rerunning the package and snapshot acceptance checks. Refs allod/tools#155
Advance allod-tools to the revision containing the Go snapshot command.

Refs allod/tools#155.
Author
Member

Refresh complete at 66f2342.

  • current master is now an ancestor of the PR head and Forgejo reports the PR mergeable
  • the lock conflict was resolved while preserving every other input from current master
  • allod-tools now moves only from 93eb4ba to merged snapshot rev 5649cec
  • the generated allod-dev package built as /nix/store/bvwm2lm8z9ik91lgcyqwknfjidif1l58-forge-0.1.0
  • its cmd/forge tests ran during the build, including the 19 snapshot scenarios
  • help, two error cases, four live read commands, and live pr snapshot 156 matched the Bash tool byte for byte
  • the wrapper supplies pinned Git; the wrapped payload is an ELF binary; the pinned source still contains executable Bash forge for rollback

The PR body now records the current pin range, output paths, validation, and rollback constraints. The remaining gate is merge followed by the human dev-VM rebuild and deployed smoke test.

Refs allod/tools#155

Refresh complete at `66f2342`. - current `master` is now an ancestor of the PR head and Forgejo reports the PR mergeable - the lock conflict was resolved while preserving every other input from current master - `allod-tools` now moves only from `93eb4ba` to merged snapshot rev `5649cec` - the generated `allod-dev` package built as `/nix/store/bvwm2lm8z9ik91lgcyqwknfjidif1l58-forge-0.1.0` - its `cmd/forge` tests ran during the build, including the 19 snapshot scenarios - help, two error cases, four live read commands, and live `pr snapshot 156` matched the Bash tool byte for byte - the wrapper supplies pinned Git; the wrapped payload is an ELF binary; the pinned source still contains executable Bash `forge` for rollback The PR body now records the current pin range, output paths, validation, and rollback constraints. The remaining gate is merge followed by the human dev-VM rebuild and deployed smoke test. Refs allod/tools#155
vnprc approved these changes 2026-08-24 23:01:45 +01:00
vnprc merged commit 66f2342337 into master 2026-08-24 23:01:51 +01:00
vnprc deleted branch agent/forge-go-cutover 2026-08-24 23:01:51 +01:00
Sign in to join this conversation.
No description provided.