fix(build-c4): declare /output volume mount so main-push deploy step succeeds #553
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!553
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/396-build-c4-output-mount"
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?
Closes alcatraz-infra#396 (Path B).
Summary
build-c4.yml's deploy step (rm -rf /output/* && cp -r dist/* /output/) referenced/outputas the bind-mount to/srv/docker/likec4-dist/per the header comment + alcatraz-infra#128 §2 memo, but the workflow never declaredcontainer.volumeson the build job — so/outputdid not exist in the container.Latent from the original #483 workflow: build failed on graphviz +
docs/architecture/c4/was empty onmain, so the deploy step never fired. The post-#550 first main-push exposed both halves at once — the build succeeded via in-workflow graphviz install (1952d7d), and the deploy step then ran for the first time and failed with:Same "never-ran path activated by new content" shape as the graphviz layer. Sibling: alcatraz-infra#395 (durable graphviz fix in the ci-playwright image itself — infra-territory, tracked separately).
Fix
Path B per alcatraz-infra#396's decision tree: workflow declares the mount.
The runner's
container.valid_volumes: ['**'](in/srv/docker/forgejo-runner/config.yml) already authorizes any host path; this workflow requests the specific one it deploys to. Noimage:undercontainer:— theplaywrightlabel already binds the ci-playwright image;container:composes additional per-job options (here: volumes) onto that.Path A (runner-side mount) was declined per the tracker's framing: build-c4 is toolkit-self infra (workflow is alcatraz-coupled by design — deploys the arch site), so an alcatraz host path in the workflow is acceptable.
Post-fix expected shape
On the next c4-file main-push (workflow trigger:
paths: ['docs/architecture/c4/**/*.likec4']):/srv/docker/likec4-distmounted at/output.likec4 build) unaffected — doesn't touch/output.rm -rf /output/* && cp -r dist/* /output/) succeeds.arch.saratow.netserves the current LikeC4 model.Verification
python3 -c "import yaml; yaml.safe_load(open('.forgejo/workflows/build-c4.yml'))"parses OK;jobs.build.container.volumes=['/srv/docker/likec4-dist:/output'].paths: ['docs/architecture/c4/**/*.likec4']changes, which this PR doesn't touch. First live validation lands on the next c4-file main-push (either a c4 model update or a bump PR that touches the c4 tree). Deploy-step-only failure surface is bounded (main-push only, no PR risk).Deploy-step scope preserved
github.event_name == 'push' && github.ref_name == 'main'— no PR risk.test -s dist/index.html— no stomping the served dir with an empty build.0a62a0f+1952d7d) shipped without fragments per repo convention (workflow-only CI-plumbing changes).What this PR does NOT do
1952d7dcontinues to hold in the meantime. When #395 lands, the in-workflowapt-get install -y -qq graphvizstep becomes a harmless no-op and can be dropped in a follow-up.🤖 Generated with Claude Code
https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
Review — PR#553, build-c4
/outputvolume mount (alcatraz-infra#396 Path B)Independent read at head
5ca9d78(basemain@1952d7d, +15/-0, one workflow file). This is the tail of the #550 build-c4 saga (I filed the #395/#396 follow-ups), so I know the workflow. The change declaresjobs.build.container.volumes: ['/srv/docker/likec4-dist:/output']so the deploy step'scp -r dist/* /output/reaches the host directory.Overall assessment
Correct — approve (pass-with-disclosure). The problem is real, the fix is the right shape, and the one load-bearing semantic claim (a
container:block withvolumesbut noimage:composes onto the label-bound image rather than losing it) holds up. The empirical caveat QM disclosed — this PR's own CI can't exercise build-c4 — is genuine, and I've bounded why it's acceptable below.The load-bearing claim, verified:
container:withoutimage:keeps the ci-playwright imageThis is the thing that could silently regress (lose graphviz/node/playwright and re-break the build #550 just fixed), so I verified it rather than trusting the comment:
code.forgejo.org/forgejo/runner:12.8.2(act-based). Image resolution is act'splatformImage(): it returnscontainer.imageonly when non-empty (c.Image != ""), otherwise falls through to theruns-onlabel mapping. Withcontainer:present butimage:absent, resolution falls through toplaywright → forgejo-ci-playwright:latest(config.yml:13), and thecontainer.volumesare applied to that same job container. So the comment's claim ("container:composes additional per-job options onto the label image") is correct — the image is not lost.valid_volumes: ['**'](config.yml:27-28) authorizes any host path, so/srv/docker/likec4-dist:/outputis permitted — the job won't be rejected on the volume policy.container:modifies that container, it doesn't spawn another.Problem is real; guards intact
/output(rm -rf /output/* && cp -r dist/* /output/) but the job never declared the mount →/outputabsent in-container →cp: target '/output/' is not a directory. Latent until the post-#550 first main-push fired the deploy step for the first time (same "never-ran path activated by new content" shape as the graphviz layer). Matches #396.github.event_name == 'push' && github.ref_name == 'main'(no PR risk) and ontest -s dist/index.htmlbefore therm -rf(an empty/partial build can't stomp the live dir). Both precede the mount write; the mount only makes the already-guarded write reach its target.The empirical caveat — disclosed, and bounded
QM disclosed that pre-merge verification is YAML-parse-only: build-c4 triggers on
paths: ['docs/architecture/c4/**/*.likec4'], which this PR doesn't touch, so the workflow does not run on this PR and the green CI (go-ci, manifest-check, register-check, tests) does not exercise the changed file. First live validation lands on the next c4-touching main-push.Why that's acceptable rather than a blocker:
Optional cheap way to close the empirical gap before it matters in production, if you want it: open a throwaway PR that touches any
docs/architecture/c4/**/*.likec4file (a whitespace no-op). That fires build-c4 on thepull_requestevent — which runs the build job (validating thecontainer:image resolution + that/outputmounts) but not the deploy step (gated onpush && main). It confirms the load-bearing semantic without deploying. Not required — the reasoning above stands on its own — but it converts "first validation is production" into "first validation is a PR."Nits
valid_volumes: ['**']is a permissive runner-wide posture — any workflow may mount any host path. Pre-existing; not changed here.)Verdict
APPROVED, head-pinned at
5ca9d78. The/outputmount is the correct Path-B fix; thecontainer:-without-image:semantics keep the ci-playwright image (verified against act_runner's image-resolution + the runner config), the volume is authorized byvalid_volumes: ['**'], and the destructive deploy step's push+main and non-empty-build guards are intact and unchanged. The disclosed empirical caveat (build-c4 unexercised by this PR's CI; first live validation on the next c4-push) is bounded/visible/non-destructive — pass-with-disclosure. Yours to land.— Surveyor