Repository navigation
feat(runtime): unpin the studio platform, retire the Rosetta machinery, and fix a native-build regression - #21
Open
duplo-darren wants to merge 4 commits into
Conversation
Every studio release now publishes amd64 and arm64, so pinning the platform is no longer correct: the
pin at docker-compose.yml:61 was the only one in the stack, and on Apple Silicon it forced QEMU
emulation when a native arm64 layer was already available in the manifest. Verified against the
currently pinned tag, which is an OCI index carrying both arches.
Four changes that have to land together, because any subset is broken:
1. docker-compose.yml — `platform: ${STUDIO_PLATFORM:-}`, the idiom BUILDER_PLATFORM already uses
two lines down. No pin, not linux/arm64: a hard arm64 value would break amd64 Linux.
2. .env.example — the live pin becomes a commented-out key. Unset is now the correct value.
3. scripts/_runtime.sh — runtime_rosetta_check treated an unset STUDIO_PLATFORM as amd64 to match
the old compose default. Left alone, dropping the pin would make it hard-exit with a false
positive on every Apple Silicon podman user. Unset now returns 0.
4. run.sh — a one-time migration removes the stale pin from existing .env files. The adoption
mechanism cannot do this itself: it skips keys the example ships blank, so blanking the key would
leave every existing user on amd64 forever.
The migration is deliberately conservative. It removes the line only when .env still holds
linux/amd64 AND .env.defaults agrees that was the last-applied default — i.e. the user never touched
it. Anything else is left exactly as found, and runtime_rosetta_check explains the situation instead
of overriding a deliberate choice. It takes its paths as arguments so the policy is testable without a
real .env, and it is silent unless it changes something.
Tests: 52 (was 45). Both migration guards were mutation-tested — loosening either the value check or
the lock check makes a specific test fail, so they are not passing by accident.
Reported in review of duplocloud#18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With the studio platform unpinned, the advice this subsystem generated stops being the advice we want to give. Once an arm64 layer resolves natively, the correct remediation is "remove the stale pin", not a walk through containers.conf — so ~150 lines that were correct are now actively misleading, pointing Apple Silicon users at Rosetta they no longer need. Deleted: _runtime_rosetta_conf_state, _runtime_machine_vmtype, _runtime_machine_conf_provider, _runtime_machine_provider, and the six-branch remediation with its libkrun/applehv provider diagnosis. scripts/_runtime.sh drops from 651 lines to 489. runtime_rosetta_check keeps its purpose and loses its bulk. It fires only on an EXPLICIT amd64 STUDIO_PLATFORM, and says two things: remove the pin (the stale-.env case, which is nearly all of them), and — because unsetting cannot help someone whose registry genuinely carries amd64 only — that such a tag does still need Rosetta, via Docker Desktop or an applehv machine. That second branch is why the check survives at all rather than going with the machinery. Retained: _runtime_host_arch and runtime_rosetta_active, the two impure seams that let the policy be tested without a VM. runtime_machine_check is the undersized-machine preflight and is unrelated to any of this; it is untouched. Tests: 41 (was 52). The 16 conf-state, provider and remediation-message cases go with the code they covered; 5 new ones pin the retained check's two branches and assert the deleted helpers are actually gone rather than merely unreferenced. The pipefail/`grep -q` warning is preserved on the new helper — it lived in a block this commit deletes, and the bug class it documents outlives the tests. Note: this deletes the function fixed in the [machine]-trailing-comment commit on duplocloud#18. That fix still matters — it ships first and protects anyone who hits it in the interim. Reported in review of duplocloud#18. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… platform
The docs described a world where the studio image was amd64-only and Rosetta was a setup step. Both are
now wrong, and wrong in the expensive direction: they send Apple Silicon users to configure Rosetta they
do not need, for emulation that no longer happens.
troubleshooting.md The "hangs on Waiting for studio" entry goes from 206 lines to 71, and from 45
Rosetta/libkrun mentions to 4. The symptom, the misleading "Login failed" and the
QEMU/.NET explanation are all kept — they are still exactly what you see. What
goes is the containers.conf walkthrough, the applehv measurement table, the
libkrun provider deep-dive and the rosetta-activation.service forensics. The fix
is now "remove the pin", with the single-arch-registry case noted after it.
prerequisites.md Apple Silicon setup goes from three requirements to one: size the machine. Says
plainly that Rosetta is not required.
configuration.md STUDIO_PLATFORM's default is *(unset)*, not linux/amd64.
faq.md Apple Silicon runs natively rather than emulated.
upgrading.md STUDIO_PLATFORM dropped from the tracked-keys list, with a note that it stays
tracked in code but is skipped while the example ships no value.
run.sh The preflight's comment described the old trigger; it now says the check fires
only on an explicit amd64 pin.
Four tests keep the docs honest rather than trusting this sweep: no libkrun/applehv walkthrough survives
anywhere under docs/, nothing still claims the image is amd64-only, configuration.md documents
unset-means-native, and nothing tells the user to pin linux/arm64 — which is now the thing that resolves
by itself. Tests: 45.
Reported in review of duplocloud#18.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RUNTIME=podman in .env breaks every containerized extension build, regardless of extension — reported with a full root-cause trace and a live rebuild confirming both the failure and the fix. build-extension.sh re-invokes itself inside the builder container to do the actual dotnet/npm work. docker-compose.yml's builder service sets DUPLO_BUILD_NATIVE=1 in its environment for exactly this re-entrant call — nesting a container runtime inside the build container isn't supported, and builder_mode() already has a documented branch for it. But builder_dispatch() called runtime_resolve unconditionally, before ever checking DUPLO_BUILD_NATIVE. Since the whole repo (.env included) is bind-mounted into the container at /work, the inner invocation's _envv lookup read RUNTIME=podman straight off the mounted file, runtime_requested() returned non-empty, and the fallback treated that as an explicit pin and fataled — before builder_mode()'s want_native=1 short-circuit ever got evaluated. RUNTIME=docker, the default, usually masks this: docker is typically also on the host's PATH, so the same resolve call that should be skipped happens to succeed by accident. Same latent bug either way. Fix: guard the runtime_resolve call with the already-computed want_native flag, so a forced-native build — which the container always is — skips runtime resolution entirely instead of racing it. No behavior change on the host-side (outer) dispatch, where want_native is unset by default and the existing resolve-or-die logic still runs. Verified both ways: reproduced the exact reported error in isolation against the unpatched code (same message, same exit 1), confirmed the fix resolves it, and confirmed the host-side path — a developer who names a runtime they genuinely don't have — still fatals correctly rather than silently going native. Also verified end-to-end: rebuilding extensions/soc2-posture-extension with RUNTIME=podman set succeeds after the fix, fails identically before it. Tests: 5, new file (none existed for _builder.sh before). Confirmed RED against the unpatched code — exactly the forced-native/unresolvable-RUNTIME case fails, nothing else — then GREEN with the fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Author
|
Also includes a fourth commit, |
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.
Follow-up to #18 — this is blocker 4 of that review, split out as it suggested: "land the runtime abstraction first and stack this on top … but it has to go in before we ship." #18 is now merged, so this goes up on its own against
dev: 3 commits, 10 files.What this changes
docker-compose.ymlpinned the studio image tolinux/amd64. That was correct when the image was amd64-only. It no longer is, and the pin is now actively harmful: on Apple Silicon it forces QEMU emulation when a native arm64 layer is already in the manifest — and QEMU cannot run the studio's .NET runtime, which is the failure the Rosetta preflight exists to diagnose. The pin was manufacturing the problem the machinery reports.The premise, verified
Not forward-looking. The studio tag this kit pins today already carries both arches:
(
podman manifest inspectis identical.) So every Apple Silicon user on this tag has been emulating an image they could have run natively.Four coupled parts — one commit, deliberately
Any subset is broken, which is why they land together:
platform: ${STUDIO_PLATFORM:-}BUILDER_PLATFORMalready uses two lines down. Notlinux/arm64— a hard arm64 value breaks amd64 Linux..env.examplepin → commentedlinux/amd64forever: the adoption pass skips keys the example ships blank (run.sh,[ -z "$ex" ] && continue)._runtime.shunset →return 0.envmigrationThe migration
runtime_migrate_studio_platformremovesSTUDIO_PLATFORMfrom a user's.env, and only when it is provably untouched:A hand-pinned value is never overridden. When the migration declines,
runtime_rosetta_checkexplains the situation instead — so nobody is silently stuck. It is idempotent, silent unless it changes something, and takes its paths as arguments so the policy is testable without a real.env. Verified against a real 193-line.env: one line removed, remainder byte-identical, second run silent.What is deleted
_runtime_rosetta_conf_state,_runtime_machine_vmtype,_runtime_machine_conf_provider,_runtime_machine_provider, and the six-branch remediation with its libkrun/applehv diagnosis.scripts/_runtime.shgoes 671 → 540 lines.troubleshooting.md's Apple Silicon entry goes 206 → 71 lines, and its Rosetta/libkrun mentions 45 → 4.Kept:
_runtime_host_archandruntime_rosetta_active— the two impure seams that let the policy be tested without a VM.runtime_rosetta_checksurvives at ~25 lines, firing only on an explicit amd64 pin, and says two things: remove the stale pin (nearly every case), and — because unsetting cannot help someone whose registry genuinely carries one arch — that such a tag does still need Rosetta, via Docker Desktop or an applehv machine. That second branch is why the check survives rather than going with the machinery.Untouched:
runtime_machine_check, the undersized-VM preflight. Unrelated to any of this.Blast radius
.envis written — the only user-data change here. Guarded as above; the pre-migration value is recoverable from.env.defaults.STUDIO_PLATFORMsemantics invert: unset now means native, where it used to mean amd64. Anyone with tooling that reads it should know.faq.md,configuration.mdandupgrading.mdcarried the same stale "amd64-only" claim. Four tests fail if any of it returns..env.example:STUDIO_PLATFORMstays inDEFAULT_KEYSbut ships no value, so it is skipped while blank. A future release can re-pin it with no code change;upgrading.mdrecords this.Testing
65 runtime tests, 135 across the suite, all passing on top of current
dev. The 16 conf-state, provider and remediation-message cases go with the code they covered; new ones pin the retained check's two branches, the compose default, the.env.examplestate, and six migration cases.Both migration guards are mutation-tested — loosening the value check or the lock check each makes a specific test fail, so neither passes by accident.
Verified on real hardware: a libkrun podman machine, which the old code hard-failed with a recreate-the-VM instruction, now passes silently because the arm64 image needs no translation.
Note for reviewers
This deletes the function fixed by "match a
[machine]header that carries a trailing comment" in #18. That fix is not wasted — #18 shipped first and covers anyone who hits it in the interim — but reviewers of both PRs will notice the add-then-delete.🤖 Generated with Claude Code