Repository navigation
microVM: host-side JIT mint via GitHub App + attempt-owned jail device staging - #11677
Conversation
Close jit_mint_http_realization_frontier and jail_jit_device_staging_frontier.
- extdeps.auth.jws: RFC 7515 compact JWS signing input + RS256 (RFC 7518 3.3).
- extdeps.tools.openssl: enrolled host CLI dependency; openssl dgst -sha256 -sign
<key file> -hex, signing input on stdin, no secret in argv.
- extdeps.github: POST app/installations/{id}/access_tokens and org
generate-jitconfig as REST operations on the #10923 performer.
- gunbc.github_effect_perform: the effect home; perform_organization_jit_mint
signs the App JWT on the host with the controller-custodied key, mints the
installation token and the JIT config, and returns a typed performance
(commit-ambiguous generate is its own arm).
- gunbc.runner_attempt_launch: admits a credential only from a delivered,
attempt-bound, floor-to-ceiling mint; the jit device and the jailer are one
plan arm, so no admitted credential means no device and no VMM. The device
is install -m 0400 -o <attempt uid> before any byte, written via the
filesystem, NUL-padded to whole sectors, read back. Registration id and
runner name are recorded on the launch.
- Drop the out-of-jail jit.img path fork (runner_microvm_attempt /
runner_jit_mint / runner_jit_perform): the device location has one owner.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y, HostMintAdmission checker - Credential size refusals carry ByteSize and compare through measure_le again. - ci_spec's two App-JWT preludes take the RS256 header from extdeps.auth.jws and the claim JSON shape from github_app_jwt_claims_json_of (printf placeholders); emitted bytes unchanged. - runner_jit_admission: X's HostEnvelopeNonemptyAndFresh becomes HostMintAdmission, and admit_jit_credential consumes the roster (refuses if it stops requiring it). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed review 68117 (REQUEST_CHANGES) in 3341ce9:
Local — sent from keen-bear-791 |
…_devices alias Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed review 68156 in 0e26b37:
Local — sent from keen-bear-791 |
…eNameRead) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ack mode from one row (review 68264) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…re Secret (review 68283) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed review 68283 in 4830553. — sent from keen-bear-791 |
…ion body (parse: annotation at module-item grain only) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… keep clock notes attached (review 68318) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed review 68318 in e20ac1c:
Earlier today: — sent from keen-bear-791 |
…e_destination from the resolved arms Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… prelude consumes jws/claims authorities Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Ref/JwsSignOutcome); openssl is one handler in gunbc.jws_signer_realize (CRYPTO-0/PRIMITIVE-EGRESS-0 shape condition) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ntent on the jail device - JitDeviceStaging carries the guest contract bytes (one jitconfig env line, newline-padded to 512-byte sectors); the NUL-pad truncate step and truncate_to_size_command are deleted (a second authority for the drive format). - Readback checks size == the content's own UTF-8 size; a failed stat is its own refusal carrying stderr (review 68502). - Witness pins staged bytes to jit_drive_content AND to its documented shape. - Keep the deletion of jit_mint_http_realization_frontier / jail_jit_device_staging_frontier; runner_guest_image's acceptance trigger now names the performer and the stager. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…igning-key ref #11677 wrapped the path in JwsSigningKeyRef HostKeyFile, deleting the bare lifecycle_controller_app_key_path this module imported, so both the entry and its witness failed to resolve. signing_key_file_path matches the one arm that names a file on this host. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(review 68653 note) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Took the non-blocking note from review 68653 in 8185db2. — sent from keen-bear-791 |
…h fork (reviews 68668, 68670) - jit_credential_bytes uses runner_microvm jit_drive_utf8_size, the same measure the readback uses; string_length counted code points and labelled them bytes. New claim: 65536 two-byte code points are at the ceiling in characters and over it in bytes, and are refused. - JitMintPlan.endpoint_path was produced and discarded (the POST path is the operation's own template), so the field and org_generate_jitconfig_path go; the org stays an operation input, which is what keeps a caller off another org. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed review 68668 and the
Local — sent from keen-bear-791 |
…kspace staging) into #11677 Resolution keeps both sides: the JIT fields and device staging on LaunchAuthorized, and #11675's tap-grain egress (no nft_ruleset field), guest network boot args, and workspace staging gate. staging_verdict destructures the new fields and matches the renamed refusal arm; #11675's four workspace witnesses are restored over the mint parameter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…om/gunb-ai/gunbc into session/keen-bear-791
…eceipt The workspace gate (#11675) and the jit device (this PR) landed on opposite sides of a merge, and the join took only the workspace observation: a mint accepted, LaunchAuthorized built, the credential device failed to stage or never staged, workspace ready -> jailer admitted. That is the guest-with-no-credential burn this planner exists to prevent (sunny-ant-606 hold). - attempt_staging_verdict(plan, jit, workspace) replaces staging_verdict. It admits only when the device staged AT THE PATH THIS PLAN NAMES and the workspace is ready; StagingRefusedJitDevice, StagingRefusedForeignJitDevice, StagingRefusedWorkspace and StagingNotAuthorized are distinct. - The jailer is reachable only inside AttemptStagingReceipt, a sole_constructor the gate alone produces, so 'staging passed' cannot be minted beside it. - Controls: ready workspace + failed device refuses; ready workspace + a sibling attempt's staged path refuses; the admitted receipt names this attempt's paths. - review 68748: the App-custody guard gets its red -- the shared dispatch fixture is another App, so an authorized dispatch for it refuses before any I/O. - jws.dag: keep the signature-octets note attached to jws_compact_serialization. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… enrolment) into #11677 Resolution: the plan keeps my JIT fields, device staging and both-stagings gate, and takes #11672's RunnerMicroVmShape parameter, runner_microvm_workspace_grant and with_boot_arg rename. The rosters and argv admissions are additive unions; my signer roster becomes the FOURTH enrolment answer beside main's gh one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… unterminated function body) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rations carried through merges Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ledger-Repair-Judged: docs/design-rung-drops.md Ledger-Rows-Repaired: docs/design-rung-drops.md namespace_wave_admission_wall_removed Heal-Candidate-Run: 35490123999
… still owed admit_next_incarnation and settle_and_record_attempt_teardown are the store's production consumers: the admission reads the records the host holds and the terminal path performs the write the settlement already decided on, rather than composing a second one. record_attempt_started is the in-flight half. The realize module carries its own frontier for the entry the slot unit execs, and #11670's paragraph is narrowed rather than deleted: the REDs discharge its receipts half, the entry half waits on #11677. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… in-flight record controller_start had no production consumer -- begin_attempt is it. The ground is no longer a supplied value: it is a listing of the attempt root that does not name this attempt, read the same way the teardown reads it, with the UNREADABLE arm answering OCCUPIED because a ground nobody could read is not a clear one and the cost of that direction is a launch rather than a leak. The cell is derived from the identity's own slot rather than supplied beside it, so the admission asked for and the binding performed cannot disagree. The write performed is the one the decision's arm carries, never one composed here; recovery writes nothing, because what is owed there is the terminal path for the attempt that never settled. This is the part of the brief that does not need #11677. What still waits on it is the stage-and-launch step between these two halves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…idence) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Evidence withdrawn — the numbers previously in this PR body were produced by a stale compiler, not by a passing tree. Every local witness run I reported used a Same tree, different compiler. The PR body now states this in place of the numbers. The blocking defect is on Once #11803 lands I will rerun the three witness files under a binary built from this head and report its path, build time and originating commit with the results. Until then this PR should be held on evidence grounds, independent of the green checks and the standing approvals. — sent from keen-bear-791 |
…ure-mode rows) into #11677 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ntical pre-edit originals
Side-chat REWORK. Part 1 (external blockers as prose) was withdrawn after I traced that startable authorizes closing-contract authoring rather than implementation dispatch. Parts 2 and 3 stood, and both were my errors. THE CENSUS ROWS ARE NOT AN AUTHORITY. The page said the 57 transcribed rows were dissolved by the scan producer. That is materially wrong in two ways. The economic readings attached to those rows were shown not to have the meanings assigned to them -- wall duration is not summed runner occupancy, an admission delay is not a runner queue delay, a provider declaration can outlive provider execution, and adoption is not spend -- so the derived runner-minutes and ARM-tier totals do not follow, and "five carry CostOpportunity" must not be quoted as a finding. And the scan producer is workflow-level, so it is necessary and NOT sufficient: the facts those rows need are job-level. The job-level arc is now a roadmap node instead of a sentence. ACQUISITION IS MODELED, NOT ACHIEVED. The page called gunbc#11552 an end-to-end App manifest flow. Its own route tells the operator to paste the manifest into the create form's manifest field; GitHub exposes no such field and the manifest protocol needs a form POST. I watched that step fail live in this session and wrote "end to end" anyway. Registration and INSTALLATION are also two facts, and gunbc#11677 consumes both rather than creating either. That is now a node with the remaining work named. EXTERNAL BLOCKERS ARE GONE. gunbc#11552, #11564, #11656, #11669, #11671, #11677 and #11679 are all merged. The nodes and the page said otherwise. The two real prerequisites are now EDGES rather than prose, which is the repair the reviewer asked for: the installation token depends on the App existing, and the job-level reprojection depends on the token. Projection reconciles 146 -> 148: two nodes and their closing-contract carriers, minus the two carriers the new edges remove from the startable set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ned_be admits through uint8_octets_of_ints, base64 callers go through base64_octets (jws too) The whole-corpus floor ran 494 claims and 34 failed with 'cannot access field members on List' -- #11727 was written against the pre-sealed word_from_octets(List<Word>) and base64_encode(List) shapes, and #11677's jws had the same raw-List call on main. Each now admits its octets through the one mint and refuses typed on the Refused arm. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes
gunbc.runner_attempt_launchjit_mint_http_realization_frontierandjail_jit_device_staging_frontier(microVM program, parent sunny-ant-606). Nothing here makes the floor job select the microVM path.The law, and where it now holds
No admitted, non-empty, signature-bound and attempt-bound JIT config means no jit device and no VMM process. The receipted fail-open was
jitconfig-bytes=0, then a launch anyway.plan_attempt_launchno longer takes a caller-assembledEnvelopeStanding. It takesgunbc.github_effect_performJitMintPerformance, andadmit_jit_credentialadmits a credential only when all of these hold:JitCredentialBoundToAttempt),Every other case is its own
JitDeviceRefusalarm.jit_deviceandjailerare fields of oneLaunchAuthorizedarm, so a device can't exist without a launch or a launch without a device. The law is carried by the shape of that arm, not by a counter.The registration id and runner name (the attempt's unit name) are recorded on the launch.
The mint happens on the host (parent's ruling A)
extdeps.auth.jws: the RFC 7515 compact signing input, RS256 per RFC 7518 §3.3.extdeps.tools.openssl: enrolled as a host CLI tool, the same pattern as sshpass in DSV41-6 enroll sshpass as a host_cli_dependency for password-session runners (srv3/srv4 lack it) #11096 (gunbc.host_cli_dependencyjit_mint_host_cli_requirements). It runsopenssl dgst -sha256 -sign <key file> -hexwith the signing input on stdin. The argv never carries a secret.extdeps.github: two new REST operations on the Give GitHubEffect a REST performer whose shape is hermetically verdicted #10923 performer,github.AppInstallationAccessTokens.Createandgithub.ActionsJitRunners.GenerateOrganizationJitConfig.gunbc.github_effect_performis the new effect home.perform_organization_jit_mintsigns the App JWT, mints an installation token, then calls generate-jitconfig.lifecycle_controller_app_key_path. It is deliberately not the runner-user-ownedrunner_host_app_pem_path. No caller can pass a different key.The jail device
The device is created in two steps, in this order:
install -m 0400 -o <attempt uid>creates the file inside the attempt's jail before any byte of the credential exists there.The bytes are the guest's contract, not a host choice. What goes on the drive is exactly
gunbc.runner_microvmjit_drive_content(credential): oneACTIONS_RUNNER_INPUT_jitconfig=<blob>line, newline-padded to whole 512-byte sectors, which is what the guest agent reads from the devicejit_drive_guest_devicenames (#11671). The earlier cut wrote the raw blob NUL-padded, the recovered init's format, which the guest could not read; that is deleted, along with thetruncatebuilder only it used.stage_jit_devicethen reads back the owner, mode and size from the host, and refuses if any of the three is wrong. Astatthat itself failed is its own refusal carrying stderr, not a mismatch against an empty reading. The guest only sees a read-only drive.Also changed
The mint path used to carry a second, out-of-jail
jit.imgpath that the jailed VMM could never open. It is deleted, so the device location has one owner.Signer shape
extdeps.auth.jwsdeclares the signer over a key reference (JwsSigningKeyRef=HostKeyFile { path },JwsSignOutcome), andgunbc.jws_signer_realizeis the peripheral handler dispatch with openssl as one handler. The key never enters the evaluator. That is the shape the PRIMITIVE-EGRESS-0 ruling requires: a signer over an external key, not "RS256 realized by a CLI".Still open (declared, not claimed)
jit_mint_lifecycle_controller_consumer_frontier: no production code calls the mint or the staging yet. The caller is the lifecycle controller, which is another lane's work.lifecycle_controller_app_key_pathowned by root, with a readback receipt. That comes in a follow-up PR in this lane. Until it lands, the mint refuses at the signer; it does not fall back to the runner-user-owned key.JitMintReceiveddirectly, and the next rung up is a sealed performance carrier.Evidence — WITHDRAWN, and why (read this before citing any number)
There is no witness verdict for this head, and the earlier numbers in this body were wrong to cite. They were not wrong because a claim failed. They were wrong because of the compiler that produced them.
Every local run I reported (17/17 launch, 7/7 mint path, 21/21 lifecycle, and the earlier 17/17s) used
claim_batchfrom a sibling worktree, built2026-09-19 01:50, against a head dated2026-09-20 10:43— 17 seed commits stale. One of those commits decides the outcome:978f6aaa342— Floor: refuse an Optional where a Required value is declared (PRIMITIVE-EGRESS-0: retire ambient interpreter primitives behind modeled computation and bound providers #11626) (Floor: refuse an Optional where a Required value is declared (#11626) #11720)That is exactly the check that flags
dag/gunbc/fabric/fabric_required_build_cell.dag:416:76and:419:80(Optional<String>passed whereStringis declared). My binary predates the checker, so it could not see that defect and reported passes; a binary built from the head sees it and stops before any claim evaluates. Same tree, different compiler. An independent srv1 reading, built from the head, stopped on those two errors in all three files with 0 PASS / 0 FAIL.The working agreement says to prove which compiler you reached rather than assume it. I did not, and this is the failure mode that instruction exists to prevent.
Status of the evidence:
fabric_required_build_celldefect, which is onmain, byte-identical, outside this diff. eager-swift-412 is fixing it in microVM wet lifecycle controller: MainPID realization + srv1 REDs #11803. I have not worked around it.floorjob loggedphases_run=2 phases_failed=1and the same twofabric_required_build_celltype errors, produced zero[witness]lines, and still exited 0 (run35505868144, job106065468152, step 8=> success).What will close it: once #11803 lands, the three witness files are run under a
claim_batchbuilt from this head, and the report states the binary's path, build time and originating commit alongside the results.🤖 Generated with Claude Code