Skip to content

fix(generated-output): publish artifacts transactionally - #796

Merged
cssbruno merged 1 commit into
mainfrom
agent/transactional-generated-output
Jul 7, 2026
Merged

fix(generated-output): publish artifacts transactionally#796
cssbruno merged 1 commit into
mainfrom
agent/transactional-generated-output

Conversation

@cssbruno

@cssbruno cssbruno commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What changed

  • Publish static build output from a fully planned file set instead of writing artifacts as each stage completes.
  • Replace buildgen and appgen file writes through same-directory temporary files followed by atomic rename.
  • Plan embedded generated-app output, generated Go source, lifecycle files, audit tests, and inline/addon Go block files before publishing.
  • Add failure-path coverage that planned artifacts stay unpublished when later planning fails.
  • Document the generated-output publication contract.

Why

Disk builds and generated app emission could leave mixed old/new output when later manifest, report, security, formatting, or app-generation stages failed. This narrows the publication boundary so those failures happen before committed files are touched.

Refs #669.

Verification

  • go test ./internal/buildgen ./internal/appgen ./internal/compiler
  • go build ./cmd/gowdk
  • go test ./internal/gowdkcmd was also attempted before branching cleanly; it failed on an existing missing fixture, .github/workflows/release-smoke.yml.

@cssbruno
cssbruno marked this pull request as ready for review July 7, 2026 20:12
@cssbruno
cssbruno merged commit 5217095 into main Jul 7, 2026
32 of 34 checks passed
@cssbruno
cssbruno deleted the agent/transactional-generated-output branch July 7, 2026 20:14

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 66202143ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +192 to +195
for _, file := range files {
wrote, err := writeFileIfChangedStatus(file.path, file.contents)
if err != nil {
return Result{}, reporter.fail("write", err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stage temps before renaming files

When any write after the first fails during publication (for example ENOSPC, a permissions change, or an unwritable report/security-manifest path), this loop has already renamed earlier files into place and then returns an error. That still leaves mixed old/new output for write-time failures despite the new planned-publish contract; stage all temporary files first and only commit renames after every file is ready, or otherwise roll back/limit the guarantee to planning failures.

Useful? React with 👍 / 👎.

Comment on lines +80 to +83
- Disk static output and generated app source are planned before publication.
Final file replacements use same-directory temporary files followed by atomic
rename, so validation, formatting, manifest/report generation, and security
manifest generation fail before touching committed files.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope the publication guarantee to non-incremental builds

This new rule says disk static output is planned before publication, but gowdk dev still uses BuildIncrementalFromValidatedProgram (internal/gowdkcmd/dev_loop.go:131), whose incremental path writes CSS/runtime/page files as it goes before later cleanup and manifest/report steps. In incremental rebuilds, a late error can still partially update the served output, so the documented contract is broader than the implementation; either apply the planned-publish path to incremental builds or explicitly scope this guarantee to full static builds.

Useful? React with 👍 / 👎.

finalizeCSSArtifact(&artifact)
result.CSSArtifacts = append(result.CSSArtifacts, artifact.CSSArtifact)
files = append(files, plannedPublishedFile{path: artifact.Path, contents: artifact.contents, countWrite: true})
reporter.debug("write", "css_written", "CSS artifact written", BuildEvent{Path: eventPath(outputDir, artifact.Path)})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Emit write events only after publication

When a later planning step fails after this point, such as an invalid SEO addon BaseURL, the build now returns before publishing any planned files, but the returned debug BuildError.Report already says css_written/page_written. That makes failed transactional builds report artifacts as written even though the transaction correctly aborted; defer these events until the publish loop succeeds or rename them to planned events.

Useful? React with 👍 / 👎.

Comment on lines +352 to +353
if err := os.Rename(tempName, filePath); err != nil {
return false, err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid promising atomic replace on non-Unix

For Windows and other non-Unix builds, this helper does not satisfy the new atomic-publication contract: I checked go doc os.Rename, which says Rename is not an atomic operation on non-Unix platforms even within one directory. Since GOWDK is portable and the docs now promise atomic final replacements, use a platform-specific atomic replace helper or qualify the guarantee for non-Unix users.

Useful? React with 👍 / 👎.

Comment thread internal/appgen/files.go
if err != nil {
return err
}
planned = append(planned, plannedFile{path: targetPath, contents: payload})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid buffering every embedded asset

For generated app builds with many or large embeddable assets, this now retains the contents of every copied output file in planned until all planning finishes, whereas the previous path only held one file at a time. Large static outputs can therefore spike memory or fail app generation before publication; keep the plan as paths/temp files or stream each file into a staged temp instead of accumulating all payloads in memory.

Useful? React with 👍 / 👎.

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