feat(software-factory): environment images built when first needed, verdicts run by image ID - #852
Merged
Merged
Conversation
…needed Spec item 4 of the framework-gaps design: a host-local image registry keyed by the recipe digest, builds at intake's fit step and at dispatch with single flight, a global limit, a timeout and refcounted cancel, target.json without images (base pinned by digest), work-order image binding, and (PR 2) builder and verifier running the bound image by id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight Important issues and the minors from the independent review: loadTaskRecipe for the CLI and the review's pin diff; --iidfile and recipe-key-scoped tags with an id tag; the verifier runs the bound image by id in PR 1 with policy from the binding; dispatch honours the route's cancel and re-checks the approved digest after the image step; a per-run lane registry; build labels checked by the builder in PR 2; a bounded wait journalled for the CLI; FACTORY_SKIP_BASE_PULL kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ads without an image Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… recipe at a pin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pe built Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ancelled by its last waiter Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and rebuilt when gone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion of a recipe Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… at the pin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y under its state Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sk without one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esumes it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and bound Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he fit step Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…thing is spent The row waits in received while the image builds, so the build costs its budget nothing. A cancel or the route's own signal abandons the wait, and the approved digest is checked again after it. A cancel that lands during the image step now refuses before any thread is made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y id Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e is refused on the record Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…build Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… throw, or its deadline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tag moves no verdict Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s image at the fit step The runtime's registry lives in the lane's own state dir, so intake builds the cli-flags image (cache-served) and journals image_prepare_started, the budget pause, image_prepared and image_bound before the oracle, as Task 13 intends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… it ends Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… moved binding is refused
Reconciliation wrote `image_prepare_aborted {reason: restart}` for a dispatch still
building its image in this process (a supervisor's /reconcile while it waited),
because `isTracked` knows only builder runs. The context now says whether a dispatch
of the id is live (`isPreparingImage`), and reconciliation writes the restart marker
only for one that is not, whenever the journal shows the dispatch preparing
(`dispatchPreparing`), not only when the last line is a build's start: a crash after
a failed or aborted build, before the refusal, no longer leaves a follower waiting.
At dispatch, a binding for another target or pin than the task names now (a shipped
task retried after its target's default pin moved) is refused as `image_changed`
(target_moved) before the key, and a daemon that cannot answer whether it holds the
bound image is a refusal, not a throw. The image-wait entry is released when the
wait ends, not only on transition.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uched and never strands a build The schema version is read before this factory creates its tables, so a newer registry is refused without being written to. A caller counts as a build's waiter before its `onBuild` hook runs, so a hook that throws cancels the build like any leaving waiter instead of leaving it running with nobody waiting. The runtime releases its registry even when closing the factory throws, and the sandbox lane's setup closes the registry it opens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the upgrade The handoff refusal names the tag's `<key>` suffix (controller and server alike), the intake comment says targets available at the pin, and the README says builds are shared per target and pin, what happens to work orders dispatched before images were bound, and that built images accumulate on the daemon until a reaper exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This branch was successfully deployed
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.
Framework-gaps item 4, PR 1 of 2 (plan:
docs/superpowers/plans/2026-09-25-images-built-on-demand.md, Tasks 1-17). A target's image is now built the first time a work order needs it. Nobody has to runtarget:prepareper pin anymore.What changes
<FACTORY_STATE_DIR>/images.sqliteis keyed by a recipe digest over the target, pin, platform, pinned base, Dockerfile hash, image context, lockfile path, resolve assertions and command cwd. Builds are single flight per key and bounded byFACTORY_MAX_IMAGE_BUILDS(default 1).FACTORY_IMAGE_BUILD_TIMEOUT_MS(default 30 min) plus a queue bound caps each wait. Each build is cancelled when its last waiter leaves, and its log is stored as evidence.target.jsoncarries no images. Each target pinsbaseImageby digest instead.target:prepareonly warms the registry.received, the approved digest is re-checked after the wait, and an operator cancel abandons the build.image_bound. The verifier, the oracle proof and approve's re-verification then run that image by ID, with policy and environment identity computed from it. A binding that is missing, unreadable, moved to another target or pin, or gone from the daemon is refused on the record (image_unbound,image_changed). It never falls back to the registry or a tag.factory dispatchkeeps following a build past its request timeout, using the journalled wait bound. It ends on a refusal, a restart, or that bound.target:preparelines are gone. The sandbox global setup buildscli-flagsanddevkitinto a per-run registry. Both workflow-audit fixtures move in the same commit.The builder still runs the recipe tag, which is now key-scoped. PR 2 (Tasks 18-21) moves the builder to the bound ID and checks the image's build labels. Examples only, so there is no changeset.
Review
Every task had a spec review and a quality review, and the branch then had a final trust-focused review. The fixes that came out of review are recorded in the plan's "As landed" notes:
Verification (local, Node 24)
test:sandbox, the Docker lane: 11 files passed and 1 skipped (target-cli, opt-in); 30 tests passed and 4 skipped. This includes the new proofs:devkitpin is built at intake with the prepare script's identitypnpm lint,node scripts/check-docs.mjs,workflow-contracts.test.mjsandpnpm test:release-integritypass, as do the factory packages' typechecks.clitarget was measured.🤖 Generated with Claude Code