feat(studio): productionalize the frontend-studio deploy package - #74633
Draft
stephenliang wants to merge 10 commits into
Draft
feat(studio): productionalize the frontend-studio deploy package#74633stephenliang wants to merge 10 commits into
stephenliang wants to merge 10 commits into
Conversation
…rom the served dir Rails rendered /frontend-studio HTML from the local dist manifest while the assets were served from the S3 package. When a machine downloaded a package built elsewhere, the HTML referenced asset hashes that did not exist on that machine, and every page load 404ed on its own script tags. The package now carries .vite/ so the manifest travels with the assets it describes. The tar allowlists that one dotdir rather than including all dotfiles, and decompression clears dotfiles too so a stale manifest cannot survive an unpack. Outside development, Rails reads the manifest through the dashboard/public/frontend-studio symlink, which is the same directory the assets are served from. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s absent Our production and levelbuilder frontends have no node, so they cannot run turbo to compute the studio package key. Today they skip the package altogether, which is why /frontend-studio cannot ship to production. A build environment now writes a pointer object next to the package: the key is the git hash of the committed frontend/ tree, and the body is the turbo hash of the package that tree needs. A machine without node reads the pointer and downloads that package. The pointer is public-read, so the existing no-credentials download path works unchanged. The pointer is written on every update where the environment builds packages, not only after a build. A frontend/ change outside studio's turbo inputs rolls the git hash but not the turbo hash, and that new git hash still needs a pointer to the unchanged package. A missing pointer raises and names the remedy, mirroring the apps hotfix procedure: build the commit on a build environment first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The controller refused to render in production, which made the production rollout a code change rather than a switch. It also answered the SPA shell for every path under /frontend-studio, including asset URLs, so a missing asset returned 200 HTML that the CDN then cached under a .js address. The route is now gated on the DCDO flag frontend_studio_enabled, which defaults off in production and on everywhere else. That leaves production inert until we turn it on and gives us a kill switch afterwards. Paths under assets/ never fall through to the shell, because asset URLs only ever come from the Vite manifest. The rake update task no longer skips production. Levelbuilder is still skipped; it ships after production is stable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unpacking a new package wiped the directory first. A browser that had already loaded the previous HTML then lost the assets that HTML names, so any page open across a deploy broke until it was reloaded. The studio package now unpacks over what is already there. Asset filenames carry a content hash, so two packages coexist; commit_hash and the Vite manifest are overwritten, and the newest of those is the one we want. Each unpack records the tarball's file list under .generations/, and files that no longer appear in the newest two lists are deleted, so the directory stays bounded. A directory that predates this scheme is recorded as the previous generation, so the first deploy under it does not delete what is being served. Retention is a studio-only override. The apps package still replaces its directory wholesale, because its filenames are not content-addressed. Tests cover package layout, dotfile handling, pointer resolution, and retention across three deploys including the bootstrap case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eter Rails read '/frontend-studio/assets/app-abc123.js' as a request for format js. Cross-origin script protection then turned our 404 for a missing asset into a 500. Declaring the route format: false puts the whole path, extension included, into :path and leaves the format as html. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The assets/ prefix check missed the files Vite copies from public/ to the package root: favicon.ico, favicon.svg and mockServiceWorker.js. Those are not hashed and not listed in the manifest, and the app links to the favicons on every page load, so a miss on one returned the HTML shell with a 200 and let the CDN cache a page under a .svg address. That is the case the check exists to prevent. A path with an extension always asks for a file from the package, so 404 on any of them. No client route contains a dot: channel ids are urlsafe base64 with '=' stripped, course and unit names carry no dots, and lesson and level positions are integers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit_hash sorts before .vite/ in the tarball, so an unpack that died in its last few hundred kilobytes left the new marker next to the previous package's manifest. The next run read the marker, logged "Package is current", skipped the unpack, and served the previous build while reporting success. Unpacking over the old package instead of replacing it is what makes that state serve rather than fail. Exclude the marker from the unpack and write it once the package is whole. A run that dies part way now leaves the old marker, so the next run unpacks again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reading through download_object(...).path dropped the only reference to the tempfile before the read. The finalizer deletes the file when the object is collected, so a garbage collection inside that window raised ENOENT and failed the deploy for no reason. Reproduced with a forced GC. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
package:studio:update skips levelbuilder before it builds a packager, so the levelbuilder half of studio_pointer_mode? never ran. Leaving it in suggests levelbuilder resolves a pointer, which it does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The architecture doc still said Studio returns 404 in production because it is pre-production, and that production serves the Vite build output as static files. Deployed environments now unpack a prebuilt tarball and gate the app on a DCDO flag. studio AGENTS.md asks for this doc to track the Rails integration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
/frontend-studio deploys an S3 package to staging and test, but the HTML and the assets came from different places: Rails rendered asset tags from the local dist manifest while the files were served from the downloaded package. A machine that downloaded a package built elsewhere served HTML naming asset hashes it did not have, and every page load 404ed on its own script tags.
It also cannot ship to production at all. The package key is a Turborepo task hash, computing it needs node, and our production and levelbuilder frontends have no node. The rake task therefore skipped production outright, and the controller refused to render there, which made the rollout a code change rather than a switch.
What changes
frontend_studio_enabled, off in production by default. The branch is inert there until we flip it, and the flag is the kill switch afterwards.Levelbuilder stays skipped. It ships as a later stack member once production is stable.
Review shape
This is an all-in-one branch for design review. It will be split into a PR stack before landing.
Follow-ups, not in this PR
Verification
./tools/hooks/pre-commitclean over all ten changed files; new lib tests green (test_s3_packaging.rb4 tests,test_turbo_s3_packaging.rb7 tests, the latter building and unpacking real tarballs across three deploys to assert retention);frontend_studio_controller_test.rbgreen underspring testunit; route recognition for/frontend-studio/assets/app-abc123.jsconfirmed by hand viarails runner. Not run: any deploy against real S3.🤖 Generated with Claude Code