Skip to content

build: content-gate every committed generated artifact, not just the bundle - #634

Closed
mparrett wants to merge 3 commits into
mainfrom
build/content-gate-generated
Closed

build: content-gate every committed generated artifact, not just the bundle#634
mparrett wants to merge 3 commits into
mainfrom
build/content-gate-generated

Conversation

@mparrett

@mparrett mparrett commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

make check-generated verified one of five committed generated artifacts. It regenerates through lgbgen, which emits only core_compiled.lgb and the gitignored lowered tree, so op_generated.go, ir_bridge_generated.go, zz_primitives_generated.go and core/ir/data/generated.lg were never compared against their sources.

One had drifted. cmd/lginterop/prims_emit.go emits an ungrouped import block, and its comment assumed "the final file is run through gofmt, which canonicalizes the import block". gofmt preserves existing grouping rather than introducing it; only goimports inserts the stdlib separator, and goimports is a linter here, not part of generation. The committed copy was hand-grouped, so every make generate reproduced a file the linter would rewrite — anyone regenerating and committing would have broken lint.

Change

  • Emit the blank line in the generator, making regeneration idempotent.
  • GENERATED-TRACKED lists the five committed artifacts. The gate stashes them, regenerates through make generate, and cmps each. Using the full pipeline rather than lgbgen alone is what widens the coverage.
  • check-generated-manifest is no longer a prerequisite of check-generated.

On the prerequisite

This does not change whether a stale digest fails CI. It still does, in the build job (which runs check-generated-manifest directly) and in TestGeneratedArtifactsAreFresh. Neither is touched here.

What changes is that a stale digest no longer aborts check-generated before the content gate can run. The digest is a proxy; the cmp is the check that binds sources to artifacts. The proxy should not suppress the answer.

pkg/rt/generated.sums is stashed and restored rather than compared, because lgbgen's writeBundle refreshes it on every regeneration — without that, a check-* target would mutate a tracked file.

Verification

  • Passes clean; all five report lockstep and the tree is unmodified afterwards.
  • Catches injected drift in op_generated.go and zz_primitives_generated.go, neither of which the previous gate could see.
  • A failing generator now prints the underlying compiler error. It previously printed only make[1]: *** [generate] Error 1.
  • All five artifacts are byte-deterministic across consecutive make generate runs.
  • Cost is flat: regeneration goes 55.4s to 56.6s, whole gate 67s, against a job with no timeout set.

Note on the recipe

It runs under plain /bin/sh with no -e. The makeplus branch that sets SHELL := bash and -e -o pipefail is skipped whenever go is already on PATH, which is every CI run. mktemp and cp failures are checked explicitly and a trap removes the temp dir on interrupt.

@mparrett

Copy link
Copy Markdown
Collaborator Author

Follow-up filed as #635: untrack the digest once this lands. This PR is the prerequisite — it moves the four other committed generated artifacts onto a direct content check, so the digest stops being the only thing guarding them.

@mparrett

mparrett commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Reworked after self-review. Two regressions fixed: the sub-make's >/dev/null was swallowing generator diagnostics, and the gate was rewriting pkg/rt/generated.sums as a side effect of make generate.

Also corrected the framing. I had claimed dropping the check-generated-manifest prerequisite would stop rebased PRs going red. That was wrong — a stale digest still fails the build job (which runs the target directly) and TestGeneratedArtifactsAreFresh. This PR only changes whether the proxy aborts the content gate. Removing the rebase friction is #635's job, not this one.

@mparrett

Copy link
Copy Markdown
Collaborator Author

Filed #637 for the follow-up: GENERATED-TRACKED is hand-maintained and nothing checks it against scripts/generate.lg, so a sixth committed output would silently go uncovered. Kept out of this PR to avoid another review round.

mparrett and others added 3 commits July 26, 2026 21:59
…bundle

check-generated verified one of five committed generated artifacts. It
regenerated via lgbgen, which emits only core_compiled.lgb and the (gitignored)
lowered tree, so op_generated.go, ir_bridge_generated.go,
zz_primitives_generated.go and core/ir/data/generated.lg were never compared
against their sources. zz_primitives_generated.go had in fact drifted: the
emitter writes an ungrouped import block, and its comment assumed "gofmt
canonicalizes the import block" — gofmt preserves grouping rather than
introducing it, so only goimports (a linter here, not part of generation) adds
the stdlib separator. The committed copy was hand-grouped and every `make
generate` silently reintroduced a file the linter would rewrite. Emit the blank
line so regeneration is idempotent.

Regenerating through `make generate` widens the gate to all five, with one
pipeline (scripts/generate.lg) as the single source of truth for how artifacts
are produced.

Drop check-generated-manifest as a prerequisite. The digest attests to a moment
in history — "a bundle was generated when the sources hashed to X" — which a
rebase invalidates by construction, since it rewrites history while leaving both
artifacts and digest untouched. That made rebased PRs fail CI with nothing wrong
in the tree, and it aborted the target before the content gate (the check that
actually binds sources to artifacts) could run. The digest stays as
scripts/pre-commit's millisecond-scale local probe, where its cost profile is
the point.

Verified: passes clean; catches injected drift in zz_primitives_generated.go,
which the old gate could not see; and passes with a deliberately corrupted
digest, which the old gate rejected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three fixes from review of the previous commit.

The sub-make's `>/dev/null` swallowed every regeneration diagnostic.
generate.lg's `run!` rebinds *err* to *out* precisely so child errors cannot
hide, and redirecting stdout re-hid them: a broken generator produced
`make[1]: *** [generate] Error 1` as the whole CI log. Output now goes to a
file that is cat'd on failure. The old gate escaped this because lgbgen
writes to stderr.

`make generate` rewrites pkg/rt/generated.sums via lgbgen's writeBundle, so a
check-* target was mutating a tracked file. It is now stashed and restored,
and GENERATED-DIGEST documents why it is excluded from the comparison rather
than leaving the omission unexplained.

Added a trap so the temp dir is removed on interrupt, and made mktemp and cp
failures abort instead of continuing with an empty $stash. The recipe runs
under plain /bin/sh with no -e: the makeplus branch that sets `SHELL := bash`
and `-e -o pipefail` is skipped whenever go is already on PATH, which is every
CI run.

Also corrects the comment on check-generated-manifest. Dropping it as a
prerequisite does not stop a stale digest failing CI — the build job runs it
directly and TestGeneratedArtifactsAreFresh asserts the same thing in the test
lanes. What it changes is that a stale digest no longer aborts the content
gate before it can report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mparrett
mparrett force-pushed the build/content-gate-generated branch from da344ed to 8249138 Compare July 27, 2026 05:03
@mparrett
mparrett marked this pull request as draft July 27, 2026 05:25
@mparrett

Copy link
Copy Markdown
Collaborator Author

Converting to draft; can re-open after current in-flight backlog lands.

@mparrett

mparrett commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

Coordination status

nnunley’s active stack is #639#640#641, with #627 folded into #639. The generated-output gate overlap begins in #640 and is extended by #641’s dependency-manifest work.

I proposed two possible integration orders in the #641 discussion, but a direction has not selected one yet. I’m putting this PR back into draft and holding the integration decision until we confirm the branch owning gate reconciliation, so this PR does not force churn into the active stack.

@mparrett
mparrett marked this pull request as draft July 28, 2026 02:50

@nooga nooga left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — this branch has gone from "waiting on #639/#640/#641 to settle" to structurally obsolete now that #639 and #640 have merged.

  • cmd/lginterop/prims_emit.go, the file this PR patches for the import-grouping bug, no longer exists on main — it was moved/refactored to internal/primgen/prims_emit.go under a new cmd/lgprimgen tool by #639/#640.
  • The specific bug this PR fixes (missing blank line between stdlib/internal imports) has already been independently fixed at the new location.
  • The underlying goal — generalizing check-generated past just the bundle — is still valid and still missing on main. In fact main's check-generated has grown a third copy-pasted stanza (pkg/rt/corefns/zz_primitives_generated.go, from #640) on top of the original bundle-only gate, which is exactly the anti-pattern this PR was meant to replace.

Given how much of the target code has moved, I think a rebase would end up rewriting most of the diff anyway — recommend closing this and opening a fresh PR against current main that folds in the corefns registrar and the renamed lgprimgen invocations. The value proposition here is still real, just the diff isn't.

Two smaller things worth carrying into the redo, either way:

  • Makefile's check-generated recipe doesn't restore originally-committed bytes on a detected STALE failure — leaves a dirty multi-file tree with no printed guidance to git checkout it.
  • docs/contribution-policy.md:89-90 still describes the gate as bundle/lowered-tree only.

@mparrett

mparrett commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #683. @nooga's read was right: cmd/lginterop/prims_emit.go moved to internal/primgen/ in #639/#640, the import-grouping bug it patched has already been fixed there, and a rebase would have rewritten most of the diff.

#683 carries the thesis against current main — one GENERATED-TRACKED list replacing the now-three copy-pasted stanzas, regenerating through the full make generate so the three artifacts only generate.lg produces stop being invisible. Both carry-overs are in it: the gate restores the working tree on a failure instead of leaving it dirty, and docs/contribution-policy.md is updated.

Widening it turned up a second defect worth naming here. make generate did not regenerate the corefns registrar from #640 — it was reachable only through the Makefile's core_compiled.lgb prerequisite, an mtime rule that stops firing once the committed registrar is newer than its sources. So check-generated could report the registrar stale while the fix it printed, "run make generate", could not repair it. That had to be fixed in #683 regardless, because a gate regenerating through make generate would otherwise have passed corefns drift silently.

@mparrett mparrett closed this Aug 5, 2026
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.

2 participants