Skip to content

Publish explicit workload results to durable local mailboxes for microVM chaining #5255

Open

Description

@auser

Outcome

A standalone MVM host can run microVM A, publish A's explicit application result to an authorized durable mailbox when A completes, and start microVM B with a granted inbox so B can consume that result—even if A has already exited and been torn down. Keep stdout/stderr as bounded diagnostics and the current CLI/log-stream behavior; do not reinterpret either stream as the application result or auto-mailbox its contents.

This is a focused completion-to-next-workload vertical slice of the mailbox program #4622, not a replacement for its local service #4629 or durable storage #4630. Related continuation/conversation design: #5126. Reuse their canonical contract, authorization, and storage decisions rather than building a parallel queue. V1 is same-host, standalone MVM, with no mvmd, guest NIC, fleet scheduler, cross-host transport, automatic execution graph, or exactly-once external effects.

Current seam and risks to resolve

  • crates/mvm-cli/src/exec/guest_run.rs streams stdout/stderr and returns exit status; crates/mvm-cli/src/exec.rs tears down the transient VM after dispatch. A baked-entrypoint capture currently returns empty stdout/stderr (crates/mvm-cli/src/exec/baked_entrypoint.rs); a result must not depend on CLI capture mode or scraping logs.
  • A granted guest currently gets one /run/mvm/mailbox/outbox file (crates/mvm-agentd/src/mailbox_outbox.rs). The guest listener serves that file on host dial; launch-time dialing is bounded (crates/mvm-hostd/src/mailbox_launch.rs). A long-running job that writes only at completion can miss that window.
  • The host service's accepted queue is in memory (crates/mvm-hostd/src/mailbox_service.rs) and its process is tied to the source VM (crates/mvm-hostd/src/mailbox_process.rs). Its receive helper is not a durable consumer-facing inbox. An outbox acceptance is not proof of destination persistence or application processing.
  • The outbox helper accepts up to 1 MiB bodies while launch service framing is configured to 64 KiB. Resolve effective bounds and failure feedback rather than claiming oversized results were delivered.

Proposed flow to design and implement

  1. An admitted workload gets explicit, scoped send authority to a destination mailbox. It writes or submits a bounded, versioned application result (opaque bytes plus declared content type) separately from stdout/stderr. Define the precise result-write/terminal-exit ordering and behavior for no result, nonzero exit, timeout, crash, and multiple writes. Include run/plan identity and a stable message ID; an exit code is outcome metadata, not a substitute for the application result.
  2. On completion, the host/guest handoff validates the result and grant, seals/signs with the existing mailbox contract as appropriate to the chosen assurance tier, and durably admits the message to the destination mailbox before reporting a successful publication. Publishing must work for jobs longer than the current launch-time dial window and must not depend on the transient VM's state directory surviving teardown.
  3. A separately admitted successor VM obtains Receive authority for that mailbox, reads the typed result, and acknowledges it after the application accepts it. Retry/redelivery after interruption uses stable IDs and recipient deduplication; specify what a lost ACK means. The host can then launch B using the normal launch path with a grant; no implicit scheduler is required.
  4. Keep publication, destination acceptance, delivery, and application acknowledgement distinct. A successful application exit with failed publication must be visibly distinguishable from a published result and must not silently trigger B. Define retention/expiry and full-queue refusal; no silent drops or unbounded retries.

Design and implementation checklist

  • Reconcile this slice with ADR-057, Add dedicated bounded mailbox vsock service and call/cast API #4629, Persist encrypted mailbox journals, snapshots, and effect receipts #4630 and Design durable mailbox conversations and workload continuation #5126: identify which interfaces and durability prerequisites exist now, which must be completed first, and any ADR amendment required. Record the chosen boundary and dependency order in this issue before changing behavior; do not duplicate those issues' general-purpose service/storage scope.
  • Specify and review one state machine for result submitted → source completion → durable destination acceptance → successor read → ACK, with explicit error/unknown outcomes for crash and timeout. Decide whether publication is part of run success or a separately reported outcome, and define no-result/nonzero-exit behavior.
  • Define the versioned result content contract (opaque payload, content type, message/run identity, outcome metadata), effective byte limits, idempotency and expiry. Keep arbitrary stdout/stderr out of mailbox payloads by default and preserve existing CLI/log-stream behavior.
  • Bind send and receive to the correct destination mailbox, tenant, recipient, signed capability and execution epoch. Reconcile the current host-sealed guest plaintext path with ADR-057's assurance claims; no new ambient mailbox authority, plaintext logging, or unsupported host-blind claim.
  • Implement explicit workload result emission, including a completion trigger that works after long runs and returns a typed publication verdict instead of relying on the initial 30-second guest dial or a single mutable outbox file.
  • Integrate destination admission with the MVM-owned durable mailbox store (Persist encrypted mailbox journals, snapshots, and effect receipts #4630), outside the source VM lifecycle; reject full/expired/unauthorized messages and recover accepted messages across source teardown and host process restart.
  • Expose a granted successor inbox read/ACK path using Add dedicated bounded mailbox vsock service and call/cast API #4629's mailbox service; bound redelivery and deduplicate by stable message ID without claiming exactly-once application effects.
  • Provide one documented same-host A → B example showing explicit result submission, A exit, B launch/read/ACK, while stdout/stderr remain available as diagnostics; do not require mvmd.
  • Add focused unit, mock-I/O, CLI and supported live-boundary/BDD tests for success, no result, nonzero exit, timeout/crash, >30-second completion, full queue, size boundary, wrong tenant/recipient/grant, stale epoch, replay/duplicate and lost ACK, restart and teardown, plus preservation of stdout/stderr. Prove a failed publication cannot be mistaken for a delivered result.
  • Update user docs and the CLI reference for any new commands/flags and the precise guarantees; run repository-required workspace, clippy, gated and relevant Linux/builder-VM validation, report what could not run, and attach evidence before checking off implementation items.

Non-goals and tracking rule

No generic orchestration engine, cross-host movement, automatic forwarding of raw logs, unbounded bulk result transfer, or new mailbox cryptography/storage fork. Do not mark a checkbox complete based on a design alone when it requires executable behavior; keep blocked dependencies unchecked and link their evidence. Close only when every checkbox above is verified.

Activity

  1. added
    enhancementNew feature or request
    mailboxesSigned pass-by-value mailbox program
    on Nov 9, 2026
  2. auser commented on Nov 9, 2026

    @auser
    CollaboratorAuthor

    #5255 pre-implementation decision record / dependency gate (source: mvm@63e751d428f2eb1c1f3effef43cc88a89dfb206b)

    Status: blocked for the requested durable A → B implementation; all issue checkboxes stay unchecked. This is a proposed slice contract for review, not an accepted ADR amendment, delivered service, storage witness, or test result. I checked current source and the live issues rather than inferring completion from the presence of DTOs. ADR-057 is accepted architecture with no implementation/security witness (§1–6, §6–7). #4628 remains open (codec roundtrip and MVMD migration boxes); #4629 remains open (all local-service acceptance boxes), with #5057 requiring coordination with the active service contribution; #4630 remains open/blocked on #4629 (no MVM-owned recoverable encrypted destination journal/ACK state). #5126 is a proposed conversation/replacement design, not an implementation; its opt-in old-epoch conversation transfer does not need to gate a new B with its own freshly admitted inbox, but must not be assumed to work.

    Source seam and gap. The currently granted outbox is one mutable guest file carrying only recipient + body, no content type, result identity, run identity, or ACK (guest outbox, frame). The host seals it as a host-derived sender to a local loopback address under a Send-only Call template (16 KiB payload cap); it is not a B-directed workload Cast or independently authenticated guest sender (template, sealer caveat). The agent sends that file only when the host dials; launch retries 300 × 100 ms and then stops (dial). The accepted queue and replay state are memory-only; receive() removes plaintext without a lease/ACK, and its helper dies with the VM (service, lifetime). Transient teardown removes VM state (teardown); stdout/stderr streaming/capture and exit status are a separate CLI path (dispatch). The contract allows 1 MiB plaintext, but the existing guest launch frame is capped at 64 KiB and its generated capability at 16 KiB; effective current default body cap is 16 KiB, not 1 MiB. A larger result cannot be claimed delivered (bounds, frame gate).

    Proposed single state machine (distinct observations). not_submitted → explicit, once-per-run submitted (immutable bytes, stable message ID + payload commitment; no stdout/stderr) → observed source terminal exit → publishing → destination-signed accepted_durable (only after encrypted destination envelope + dedup identity commit) → leased_to_B → B application acceptance → acked. A clean zero exit without submission is no_result, not an empty mailbox message; nonzero exit, timeout, or crash are terminal workload outcomes and do not publish a staged result in V1 (retain bounded diagnostic/forensic status, never auto-trigger B). A second differing submission is refused; a byte-identical same-ID retry gets the original status, never a second message. Distinguish refused/full/expired/unauthorized/oversized from transport/host crash unknown (query/reconcile by stable ID; do not equate a lost receipt with rejection). Redeliver unacked accepted results with bounded attempts (ADR default eight) and explicit expiry/dead-letter disposition; a lost ACK means possible duplicate processing, not exactly-once external effects. Only a verified accepted_durable receipt makes A's publication successful or permits an operator to start B; A's workload exit and publication verdict remain separate reported fields. If publication fails after exit zero, report exit=0, publication=refused|unknown (never published); preserve existing process exit and stdout/stderr diagnostic behavior and choose an explicit CLI error/reporting contract during integration review.

    Proposed result/content and authorization boundary. A separate versioned application-result payload should carry opaque bytes, declared content type/version, admitted run/plan identity, outcome metadata (exit_code as metadata), and stable operation/message ID with payload binding. Encode it as a bounded value inside canonical MailboxEnvelope/MailboxMessageKind::Cast, not a second envelope/queue, and make the content type and maximum body the minimum of canonical, signed capability, framed-service, and destination-store limits (including overhead); reject before admission and return a typed refusal. A may explicitly submit over the dedicated mailbox channel while still alive; the host must retain a bounded, recoverable pending intent outside A's VM state and reconcile terminal exit with destination admission before teardown. Do not keep dialing only during startup or infer a result from stdout/stderr. A's plan must name a destination mailbox and recipient and permit Send; B's independently signed plan must grant Receive for that same tenant/domain/mailbox/recipient and current execution epoch. Verify authority signature, subject, operation, kind, content type, expiry, revocation/policy, tenant, recipient, message ID, and both relevant epoch bindings before admission or disclosure (canonical fields, capability checks). Workload-origin assurance must not be advertised for a host-derived shared sender key; current guest-plaintext/host-sealing is at most the trusted-host tier. A host-blind or guest-to-guest claim requires separate sender keys/custody and witnesses, not relabeling. No plaintext in ordinary ciphertext stores, logs, audit, or errors. Proposed V1 retention for review: 24-hour default expiry, maximum seven days, bounded dedup/tombstones through expiry plus 24 hours, and explicit full/disk/key-loss refusal; final values belong in #4630's policy/ADR review, not a second store here. Permanent local host/disk/key loss is outside this durability promise.

    Dependency order / exact next step. (1) Reconcile #4628's remaining wire/codec evidence; coordinate a current source pin and patch ownership via #5057 before touching #4629 service code. (2) Complete/review #4629's independently bounded, grant-checked local call/cast/reply and Receive API, including a post-start live submission/typed verdict; do not repurpose the launch-time outbox as the destination inbox. (3) Complete #4630's standalone MVM-owned destination journal, stable-ID dedup/lease/ACK/recovery and durable receipt, outside the source VM lifetime; settle the actual frame/capability/store caps and retention policy there. (4) Review a narrow ADR-057 amendment/successor for the result completion/publication verdict and whether the current sender-epoch check applies to the immutable accepted message or to B's later delivery (strict refuse/pin unless authenticated current-epoch delivery and old-owner fencing are established); avoid assuming #5126 opt-in transfer. (5) Only then wire #5255's CLI/guest result API and documented A → B demonstration with mock-I/O, CLI, BDD, and builder-VM live tests: success/no-result/nonzero/timeout/crash, >30 s finish, full/size limits, wrong tenant/recipient/grant/stale epoch, conflicting duplicate, lost ACK, host restart/source teardown, and unchanged stdout/stderr. No mvmd, second queue, billable host, or shared-branch landing is needed for this path.

    No source behavior changed or validation run in this dependency-gated pass. Please keep the implementation, test, docs, and evidence boxes unchecked until the prerequisites and corresponding witnesses exist; do not close #5255.

  3. auser commented on Nov 9, 2026

    @auser
    CollaboratorAuthor

    Scope correction from requester: typed workload reports first

    The immediate goal is not to design or implement microVM A → microVM B forwarding. It is for workloads to report their useful output and failures as typed mailbox messages that authorized readers can consume; stdout/stderr alone are not sufficient UX/DX. Preserve the existing stdout/stderr CLI stream as diagnostics. Build the result/error report into the canonical MVM mailbox contract and MVM-owned local service/store, not a separate log queue. A guest recipient can be added later with a Receive grant; no new VM-to-VM protocol is needed. This supersedes the proposed A → B priority in my preceding comment, but does not by itself establish a new ADR, mark any acceptance checkbox complete, or silently remove the existing A → B acceptance criteria in the issue body.

    Working dependency order for this goal: (1) Reconcile the remaining #4628 codec/receipt contract evidence needed for stable typed payloads; its optional MVMD migration box is not a prerequisite for the local report. (2) Coordinate #4629 service ownership/pin through #5057, then implement the bounded grant-checked long-running guest cast/publication path and authorized host/user read/ACK; full general call/reply work in #4629 need not be conflated with the report feature. (3) Implement the needed #4630 encrypted destination journal, stable-ID dedup, ACK, bounded retention and crash/restart recovery outside the source VM; do not duplicate its general-purpose store or claim its snapshot/effect-receipt boxes complete. (4) Wire a versioned WorkloadReport (explicit application result, typed failure/status, run/plan and stable message identity; if desired, distinct bounded stdout/stderr diagnostic chunks with truncation/overflow reported), completion/publication verdict, CLI/docs and tests on top. Publication success requires durable destination acceptance; exit success without publication must remain distinguishable. Do not auto-label raw logs an application result.

    MVMD is a separate integration dependency, not an alternative local data plane: tinylabscom/mvmd#254 must consume canonical mvm-contract types and offer discovery/routing/authorized access without becoming required by standalone MVM. Under ADR-057 and #254 an ordinary coordinator may carry opaque ciphertext and receipt metadata but may not read payload plaintext or mint a destination receipt. If 'mvmd consumes it' means the coordinator itself decrypts as a recipient, that needs an explicit new trust/authority decision and review before code; if it means MVMD exposes the report to a separately authorized reader, that fits the current boundary. The MVMD checkout is not attached here, so implementation in that repository requires attaching it.

    Exact next step: confirm the MVMD consumption meaning, reconcile #4628 evidence and #5057 ownership/pin, then revise #5255's outcome/checklist through an explicit scope change so its original unchecked A → B items are retained as a follow-on rather than checked off or silently erased. Until then all boxes remain unchecked and #5255 stays open. No code or validation is claimed by this scope note.

  4. auser commented on Nov 9, 2026

    @auser
    CollaboratorAuthor

    Priority clarification from the requester: keep #5255's original A → B / VM-to-VM consumption outcome and unchecked acceptance checklist on the roadmap, but defer it. The immediate work is canonical codec/receipt reconciliation in #4628, then the standalone bounded local service (#4629) and durable mailbox state (#4630), then typed workload output/error reporting to authorized readers. The preceding scope-correction comment described this immediate reporting goal, not permission to erase, check off, or close #5255's VM-to-VM criteria. No guest-to-guest forwarding or replacement/continuation semantics need to be designed to deliver the first reporting slice.

  5. auser commented on Nov 11, 2026

    @auser
    CollaboratorAuthor

    Architecture choice for immediate typed workload reporting (VM-to-VM remains deferred)

    The requester delegated this decision. Choose a standalone, software-only, explicitly trusted-host accepted_local_durable tier. MVM needs no MVMD, remote checkpoint service, special hardware or new key-holder daemon to publish a typed result/error to its canonical local mailbox and let an authorized reader consume it after the source VM exits. This does not mark #5255's original A → B acceptance checklist complete: VM-to-VM consumption stays on the roadmap, not this immediate reporting milestone. One MVM-owned mailbox service/journal remains the data plane; stdout/stderr remain diagnostics, not the result payload.

    Publication guarantee: introduce a separately versioned/named local-crash-durable outcome/proof. It means the exact immutable encrypted envelope, stable-ID dedup and required delivery/ACK state were committed and fsynced in host-owned storage outside the source VM's state before a signed local receipt is issued. A local chain head detects corruption/truncation on ordinary process crash/restart, not malicious rollback of an entire host. Do not emit or silently reinterpret the existing AcceptedDurable outcome through FileJournalAnchor or arbitrary success callback; old clients must fail closed rather than mistake a local receipt for independent rollback evidence. Reserve that stronger outcome for a separately configured independent checkpoint profile. A lost verdict is unknown until stable-ID reconciliation; zero workload exit and successful publication are separate observations, and failed publication cannot trigger success UX. A later authorized reader checks the current signed Receive grant, boot/session/epoch and lease, and explicitly signs ACK after processing; never auto-ACK.

    No new external key holder, but distinct versioned local keys: retain custody in MVM's existing host key directory, not another daemon or the source VM state. Source review found the current mailbox_launch::derived_keys uses a single unversioned host-signer.ed25519 for authority, encryption and journal derivation; host_keypair::load_or_init_at may create a missing signer, and there is no historical signer registry. Replacing it would invalidate plan-pinned authority and make old ciphertext unreadable. Therefore the local tier must introduce separate, versioned, domain-separated history and payload-purpose key material in that same protected host directory, referenced by key ID + tenant/domain/mailbox/epoch and loaded with load-existing only on recovery. Never fall back to the erased per-launch secret or silently mint a new key for existing history. Pin the historical authority signer ID/public key to accepted records; V1 refuses signer rotation while any message, lease or ACK retention depends on it (old-signer retention and replay policy would be a separately reviewed extension). Root-owned, no-follow, mode-0600 key files and manifest need atomic/fsynced creation, bounded retention and mismatch/missing-version/rotation recovery tests. The host remains the trusted key holder; no host-blindness or host-loss claim follows. If a product requirement forbids any persistent host key, plaintext recovery after source teardown is impossible under the current ciphertext scheme. Software alone cannot prove host-wide antirollback without an independent witness or monotonic trusted hardware.

    Later stronger tier: an optional separately operated checkpoint can CAS opaque signed journal tips (and, if needed, an independent key-custody adapter) via MVMD/fleet integration; MVMD never receives report plaintext or recipient keys and cannot sign destination receipts. No such provider exists or is required for the standalone local tier. An ordinary host administrator/hypervisor can inspect memory, destroy local state or roll back a whole host; this local tier makes no host-blind, host-loss, or independent rollback claim. A request for those stronger claims must fail closed until separately evidenced, not silently downgrade.

    Dependency order for the immediate goal: (1) amend ADR-057 and the canonical MVM receipt/capability contract with a distinct local guarantee and fail-closed version negotiation; (2) extend the existing #4630 journal and versioned local host-key custody with bounded local crash/restart/replay/lease/ACK/rotation/refusal witnesses, leaving independent-CAS AcceptedDurable unavailable; (3) adapt #4629's single hostd service and granted Receive/ACK to an explicit admit_local/bind_local seam using Casey's immutable verified original-envelope admission view, not a renamed strong receipt, outside source-VM lifetime; (4) wire #5260's explicit typed result/error Cast and signed-plan report policy, CLI/SDK publication and authorized reader UX; (5) validate source teardown, long-run completion beyond the old dial window, host-process restart, wrong authority/stale boot/epoch/replay/size/full/lost verdict/lost ACK, supported live builder-VM path and cold-boot/first-publication timing. No remote call belongs on the guest boot hot path. Keep current production mailbox admission refusal and Dev ephemeral Send/Reader Unavailable until the local tier and security witnesses actually pass. Leave #4630's full-host rollback/cross-host/snapshot/effect criteria and #5255's deferred A → B boxes unchecked; close neither on design alone.

    Alternatives rejected for V1: mandatory remote CAS/key service contradicts standalone MVM/no external dependencies and introduces availability/boot latency; relabeling a same-host anchor as existing AcceptedDurable misleads old clients; using the existing unversioned signer as a silently rotating history root loses accepted state. This is a design decision, not an implementation, test or release claim.

  6. auser commented on Nov 11, 2026

    @auser
    CollaboratorAuthor

    Implementation evidence for the contract-only local-durability slice (not a service claim): private clean branch feat/mailbox-workload-report@79599dddffeca4f004d0b2c4f1d5f0e8f309c68f contains 0ec7e57c24 canonical DTO/ADR and follow-up exact-retry rule. DeliveryOutcome::AcceptedLocalDurable is valid only on a receipt-only V2 DestinationReceipt; V1 receipt bytes/outcomes remain unchanged. Canonical serde refuses V1+local, V2+old outcomes (including bare Duplicate), and unknown versions; V2 has a distinct reserved signature domain mvm.mailbox.receipt.v2\0. ADR-057 states the trusted-host/local-process-crash guarantee and that an exact same-ID/same-ciphertext retry must return the original byte-identical signed V2 receipt after current authority and committed-state checks, not a V1 Duplicate that obscures its tier. Tests: mvm-contract mailbox_receipt 3/3, mailbox_delivery 8/8, workload_report 3/3; mvm-core --test mailbox_crypto two focused tests; protocol-feature no-default check, cargo check --workspace, cargo clippy --workspace -- -D warnings, fmt and diff check passed at DTO pin; follow-up receipt test 3/3/fmt/diff passed. An attempted bare --no-default-features check failed on existing stream feature gating without protocol; the correct protocol-feature check passed. The first workspace check attempt timed out during compilation, the incremental retry passed. Full cargo test --workspace, gated/Linux and live checks were NOT run on this branch (disk ~70 GiB free / 99% used). Core signer/verifier remains V1-only; #4630 has no production local custody/admit_local provider and guest reader remains disabled. No box should be checked for end-to-end reporting, no push/PR/main landing occurred. This is source-pinned contract progress only.

  7. auser commented on Nov 12, 2026

    @auser
    CollaboratorAuthor

    Continuing #4630 retention work after the requester said Continue without supplying a fixed duration. I am taking the smaller fail-closed interface: optional versioned MailboxGrant.retention in the signed plan, absent on legacy plans and required before any production local-durable admission; no guessed duration or implicit upgrade. Host quotas remain separate ceilings, and expiry+retention arithmetic must be checked before compaction. I will keep the existing report policy and V1 signed bytes unchanged and will not enable production on the DTO alone. Please flag an overlapping contract edit or a required first-report retention value before integration review; the local guest receipt and custody stack remains private.

  8. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    Proposed architecture and implementation plan — service-blind mailbox demo

    Status: Design attachment for review; not an accepted ADR, implementation claim, GO witness, or issue-checklist update. Source basis: feat/mailbox-demo at 6adee26313f26c83c90ece34754076354b24403f plus the byte-verified 34-file uncommitted continuation and later uncommitted reader/Ready changes. Related: #4622, #4629, #4630, #5260, #4185, #5259 and Studio's local-client handoff. This issue remains the A→B acceptance owner.

    Product goal and two time horizons

    First GO, ordinary Apple Silicon HVF, no specialized hardware or external service: a logical inbox is Ready before B exists; a real admitted guest A Casts an encrypted immutable value and receives destination-signed AcceptedLocalDurable; A exits; later admitted B obtains per-message key release, decrypts inside its guest, and signs an ACK after application acceptance. B publishes a separate encrypted result; an authorized native operator endpoint reads and explicitly ACKs through typed mvm-client/Studio. The mailbox owner, router, journal and ordinary host services intentionally hold neither payload-opening keys nor payload plaintext. The operator endpoint is explicitly allowed to display plaintext; keys never enter Studio's webview. A locked or absent operator endpoint yields ReleaseUnavailable for a fresh B, while already-authorized ciphertext may remain accepted under fresh policy. Host-plaintext stdout/stderr/console capture is typed Unavailable for this profile in the first GO; encrypted diagnostics are separate work. Other profiles' diagnostics are not silently changed.

    Longer-term product objective: protect all sensitive workload data from a malicious host operator, including payloads in use, diagnostics, tools, volumes, snapshots and key release. This is a required future security capability, not a claim made by the first GO or a requirement that every mailbox use special hardware. Encryption at rest/in transit on ordinary HVF cannot hide plaintext in B's active guest memory from that hypervisor. A policy requiring malicious-active-host confidentiality must either use independently qualified protected execution or move the plaintext computation to a separately trusted endpoint/topology; it refuses an ordinary local HVF execution that cannot enforce the claim. Hardware remains optional for the ordinary-hardware mailbox. Residual routing metadata, lengths, timing and access patterns need an explicit separately reviewed privacy policy; do not promise literal invisibility of all metadata. ADR-001 currently excludes the malicious host and must be amended with independently reviewed backend-specific evidence before that future claim is advertised.

    Architecture chosen and alternatives

    1. Choose operator/customer-controlled inbox encryption identity before B exists. The native operator endpoint generates and retains the inbox private encryption key in the macOS credential store, explicitly unlocked for a short inactivity-limited session. Other platforms refuse this profile until a provider is qualified. No hardware-only Keychain API, cloud synchronization, external service, or fallback to a host-owned payload key is required. A versioned issuer-authenticated public descriptor binds tenant/domain, logical owner address/epoch, encryption key/version, policy, profile and expiry; owner receipt-signing and history/anchor keys remain separate. No address, socket or public descriptor alone grants Send/Receive.
    2. Choose fresh per-message encryption and narrow release. A verifies the descriptor and issuer-signed Send inside the admitted guest, encrypts the value with a fresh message key, wraps it to the inbox public key and signs its immutable ciphertext commitment. The operator endpoint, after checking B's fresh admitted plan/boot, guest-generated recipient key, Receive grant, owner epoch, policy and revocations, rewraps only that message key to B. Never distribute a long-lived inbox private key to B. B decrypts in its guest. For an operator read, a separately issuer-signed operator grant and per-request key proof authorize a lease, with decryption only in the native mvm-client operator endpoint. The operator's unlocked session never extends a grant or lease.
    3. Keep the existing canonical MVM mailbox service/journal, but add a new authenticated ciphertext envelope/key-wrap version. The present host-sealed owner path takes A plaintext and retains a host payload key (crates/mvm-hostd/src/mailbox_owner_outbound/send.rs, mailbox_owner_custody.rs, mailbox_owner_registry/inbox.rs); it is legacy trusted-host behavior, not the new default. Do not relabel old records or receipts, reinterpret V1 bytes, duplicate the queue, or enable a plaintext fallback. Qualify a vetted standard encryption/key-wrap implementation compatible with guest no-std/build and the existing X25519/AEAD closure; do not hand-roll a new HPKE-like protocol merely to avoid a dependency. Signature and AEAD associated data bind the full tenant/domain, sender/boot/epoch, owner address/epoch, message ID, content type, expiry, capability/policy and encryption-key version. The owner validates public authority, ciphertext commitment, bounds, quota, dedup and revocation without decrypting; it fsyncs journal, delivery state and local anchor before signing V2 AcceptedLocalDurable. That receipt does not attest plaintext validity, application processing, device-loss recovery or whole-host rollback resistance. Stronger AcceptedDurable remains disabled without an independent anchor.
    4. Optional offline recovery, not Ready-gating. Recovery: none is explicit and warns that operator-key loss makes accepted ciphertext unreadable. An opt-in customer-held encrypted recovery bundle is exported through the native endpoint and must pass a restore/decrypt challenge before being labeled verified; no secret or passphrase enters the webview. Key loss returns KeyUnavailable, preserves historical signed obligations, and requires revocation plus a newly signed key version for new sends. No silent re-key, missing-key provisioning, or assumption that an absent endpoint implies lost custody. A fresh, bounded signed revocation snapshot still gates new work.

    Rejected: moving today's payload key to Keychain (host service still decrypts); handing every B the long-lived inbox key (broad retained access after revocation); making a persistent local custody guest mandatory (adds lifecycle and still lacks protection from an ordinary hypervisor); making hardware mandatory for default mailbox availability.

    Authority and lifecycle rules

    • Ready before B: issuer and allocator approve finite signed owner policy, retention and quotas; the operator publishes only a signed public encryption descriptor. Ready follows manifest, key-purpose, journal genesis and anchor reconciliation, not candidate preparation or a control reply. Owner startup or restart verifies exact historical and current authority without recreating a missing key or genesis.
    • Actual A/B admission: plan.json, public boot files and a live PID are not proof of admission. A fixed-schema, required-durability launch witness must originate at the real opaque AdmittedPlan and authenticated guest boot, bind plan ID/nonce, boot and guest keys, process generation and applicable owner scope, and be checked for both A Send and B Receive. This is a trusted-host process-authority control, not protection from a malicious hypervisor; a host-signed audit event alone does not prove otherwise. Missing or unwritable evidence refuses independent-owner operations even in Dev. Separate issuer-signed Send/Receive grants remain required.
    • Acceptance vs delivery: A retains the same sealed bytes/ID through bounded retries. A lost receipt is unknown until exact-ID reconciliation, never assumed acceptance. Local acceptance is outside A's VM lifetime. For B, the owner durably fences a fresh possession challenge and signs/journals a lease binding accepted A identity/epoch, owner epoch, B's current boot/session/reader epoch, ciphertext commitment and deadline before forwarding ciphertext. B ACKs the exact lease only after application acceptance. Current revocation/expiry stops new disclosure and even in-flight ACK commits under the revoked grant; accepted ciphertext and replay evidence remain for fresh authority. A lost ACK may redeliver, never exactly-once effects. Owner epoch movement or B replacement must durably fence old authority; no silent epoch rebasing.
    • Operator: distinct issuer-signed Receive/ACK grant, operator signing/encryption identities, per-request proof, bounded durable replay identities and exact retry semantics. Opening, refreshing or closing a view does not ACK. Exhausted replay capacity, stale policy, locked Keychain, missing historical key, wrong principal or unsupported profile are typed refusals; uncertain commits remain unknown until recovery. Studio is only React → typed Tauri commands → mvm-client; no CLI subprocess, journal/key read, webview-held secret or fixture fallback.
    • Teardown and recovery: stopping A or B cannot erase accepted owner history. Retain ciphertext, receipts, leases, ACK tombstones, key-version references and signed retention through expiry; corruption/partial fsync/wrong signer/missing key refuse rather than inventing a shorter history. Software-only local fsync cannot detect rollback of the entire host. Operator key loss can make accepted data unreadable without falsifying its historical acceptance receipt.

    Implementation order and objective exit checks

    Each phase writes positive and primary refusal tests first. Do not activate a DTO or endpoint before its complete authority and recovery path exists.

    Phase Work Exit evidence
    0 — architectural gate Amend/review ADR-001 and ADR-057 plus the key-custody contract for service-blind default, legacy profile, explicit future malicious-host objective, envelope version, plaintext locations, typed refusals and actual backend matrix. Qualify a vetted crypto suite/library and macOS native credential-store behavior; measure guest binary/dependency impact. Independent security/ADR approval, canonical version/downgrade vectors, no implicit provider fallback. The proposed plan is not itself approval.
    1 — provision and guest sealing Implement separate operator signing/inbox encryption identities, macOS native store with explicit short unlock, optional encrypted bundle/verified restore, signed public descriptor and A guest-generated message/sender key material. Replace host-visible plaintext outbox for the new profile with stable sealed bytes. Keychain lock/logout/reboot/key-loss, wrong descriptor/key/version/tenant, changed retry, weak/unsupported crypto and no-webview-secret refusals; host handoff contains no payload plaintext.
    2 — ciphertext-only owner/A Extend the one owner manifest and journal with no payload-opening key in the new profile; preserve purpose-separated history/anchor/receipt keys and versioned legacy history. Bind real A launch evidence and current issuer grant, quotas, expiry and exact Ready owner before ciphertext admission. Ready before B; actual A Cast, V2 local receipt, A teardown, exact retry/restart; synthetic plan/boot/PID, owner substitution, disk/anchor/key/corruption failures refuse.
    3 — B release/lease/ACK Bind real B admitted boot, independent Receive grant, guest recipient key, operator-controlled per-message release, challenge, durable owner lease and signed ACK in journal/recovery/checkpoint. Live later B decrypts and ACKs; locked operator, wrong B/boot/epoch/key, expired/revoked grant, replay, lost response and restart have typed witnessed outcomes.
    4 — operator/Studio B encrypts a typed result to the authorized operator; expose bounded discovery, leased read and explicit ACK via mvm-client, then thin Tauri adapters and a real Reports inbox. Preserve simulated previews separately and update Studio's pinned MVM source only after client contract review. Read/ACK after source teardown and restart; wrong operator/key/lease refuse; navigation never ACKs; current/unknown/refused/last-known/simulated remain distinct.
    5 — GO witness and gates Build guest agent and demo workloads from the same exact tree into a verified image; rebuild packaged host helpers and entitled HVF supervisor. Run native Apple Silicon A→B→operator with authority negatives, locked release, crash/restart, retention and teardown. Measure admission-inclusive cold boot, first acceptance and release separately. Actual guest and operator evidence, not mock/stand-in/build/cache-only proof. Capture exact revision, fixture provenance, commands and outcomes.

    Validation on one exact revision

    Focused contract/no-std codec, crypto vector, custody, journal/replay, guest mock-I/O, client thin/native, Studio UI and live-boundary tests precede broad gates. Run cargo fmt --all -- --check, cargo test --workspace, cargo check --workspace, cargo clippy --workspace -- -D warnings, cargo run -p xtask -- check-rust-footprint, just check::gated, and just ci in MVM; run Studio just ci and just ci-thin against a verified sibling-source topology. Use stable /tmp and adequate disk. Linux-only/Nix work stays in the project builder VM; native HVF uses the allowed macOS exception. Do not substitute a different command for a failing requested gate. Cached kernel/rootfs files or an old guest agent cannot witness new guest behavior. Add positive and negative tests for tenant/domain/principal, wrong Send/Receive/ACK, substituted descriptor, stale process/boot/epoch, revoked/expired grant, tamper, replay, quota, key loss, partial write, crash, lost receipt/ACK and refusal to run the stronger profile on HVF.

    GO is withheld until the full Ready-before-B, real A encrypted Cast, later live B Receive/signed ACK, B-to-operator result, typed mvm-client/Studio read and explicit ACK, refusals, restart/recovery and teardown are observed on Apple Silicon HVF with all required checks passing on one exact source state. HVF GO witnesses the software-only service-blind default, not the later malicious-active-host goal. All #5255/#5260 unfinished acceptance boxes remain open. No push, PR or shared-branch merge is implied.

    Review decisions still required before coding the new authority

    Security reviewers must approve the versioned envelope/key-wrap suite and vetted implementation (the workspace already has X25519/AEAD primitives, not approval to assemble a protocol), public descriptor issuer/rotation, fixed native unlock-session ceiling, optional encrypted bundle format/KDF and restore proof, legacy history access/migration, and audit/diagnostic exclusion. Set ceilings from operational/security evidence rather than inventing a default. For the future malicious-host objective, select a defensible independently trusted execution or off-host processing boundary, revise ADR-001, then separately qualify key release, rollback and every plaintext egress; do not imply ordinary HVF satisfies it.

  9. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    Proposed execution plan — service-blind A → B → operator mailbox GO

    Companion design: service-blind architecture and threat boundary. Status: implementation instructions for review, not completed work, ADR approval, a GO claim, a rollout authorization or a change to unchecked issue criteria. Source basis: feat/mailbox-demo@6adee26313f26c83c90ece34754076354b24403f plus the verified uncommitted 34-file continuation, the later inert owner_reader DTO/core verifier, ADR draft and exact-Ready signer regression. Isolated unfinished operator/guest reader diffs and the separate typed-report branch are not automatically part of this source: compare and security-review their exact bytes before integration. #4629/#4630 own service/journal work, #5260 owns typed reporting, and Studio consumes only a reviewed client contract. Keep #5255 and #5260 open while any acceptance box is unchecked.

    Execution rules and dependencies

    Read AGENTS.md, ADR-001, ADR-057, specs/contracts/key-custody-boundary.md, this issue and #5260 before editing. This changes authorization, key custody, public APIs and the security model: obtain maintainer/independent security review of the proposed ADR amendments and versioned protocol before enabling new authority. Work in the attached topic worktrees; do not overwrite the 34 transferred files, push, open a PR or merge shared main without authorization. For each task, write positive and primary error-case tests first. A returned host control reply, socket peer, synthetic plan/PID, historical receipt, catalogue entry or simulated UI row never supplies authorization. Preserve legacy records under their original signed profile and retention; no in-place upgrade. Every new production entrypoint refuses until its full checks and recovery path exist.

    The dependency order is contract/security gate → operator public key and guest ciphertext → ciphertext-only owner journal → admitted A source and real A Cast → B release/lease/ACK → B result/operator read/ACK → Studio → native HVF/whole-revision gates. Guest encryption and key-free journal work can proceed in parallel after the signed envelope/key-wrap contract is fixed. The future malicious-active-host privacy objective is a distinct qualification track: ordinary HVF can witness service blindness, not confidentiality of active guest memory. If a signed policy requests the stronger property, unqualified HVF refuses; ordinary mailbox use does not require special hardware.

    T0 — Security and compatibility contract (blocks activation)

    Areas: ADR-001, ADR-057, specs/contracts/key-custody-boundary.md, crates/mvm-contract/src/protocol/mailbox.rs and its owner-policy/reader modules; crates/mvm-core/src/mailbox.rs. Record the default service-blind profile, explicit native operator endpoint, plaintext/metadata table, retention/revocation/session rules, versioned public encryption descriptor, immutable ciphertext envelope and key wrap, typed ReleaseUnavailable/KeyUnavailable/Unknown/profile refusals, and legacy V1 handling. Allocate a distinct canonical envelope version without changing the meaning of receipt-only V2 AcceptedLocalDurable or V1 AcceptedDurable; version-one clients reject unknown new bytes. Define separate sender, owner receipt, inbox encryption, B recipient/ACK, operator signing/encryption and optional recovery identities. Specify a fixed AEAD associated-data set including tenant/domain/address/owner epoch, sender boot/epoch, message ID, content type, expiry, policy/capability and key version. Refusal of unsupported protection occurs before payload use; neither guest decryption nor possession grants mailbox ACK authority.

    Crypto decision gate: evaluate a maintained RFC 9180-compatible key-wrap implementation for the actual pinned Rust toolchain, guest no_std/size limits, standard vectors and dependency closure. Existing X25519/AEAD dependencies do not authorize a hand-written HPKE variant. Select one audited suite and explicit algorithm/version IDs in the ADR; if none fits the guest boundary, stop for security review rather than ship bespoke crypto. Validate canonical roundtrips, no-std protocol feature, wrong-key/recipient, altered AAD, version downgrade and boundary-size tests before T1/T2. Never make one general host sign-arbitrary-bytes verb.

    T1 — Mac operator key provisioning and optional recovery

    Areas: new narrow native operator custody module under crates/mvm-client/src/ backed by an explicitly selected macOS credential-store provider; use existing keyring/security-framework integration only after verifying unlock semantics. Do not reuse mvm-core::crypto::keystore::default_provider: its ordered fallback to file/environment is inappropriate for an inbox private key. Generate an inbox encryption identity before B exists; persist its private key only under the operator's app identity, not under owner custody or Studio. Require native explicit unlock, bounded inactivity timeout and separate per-release grant checks; a retry or UI refresh cannot renew an expired grant. The signer/issuer approves a public descriptor for one exact logical owner and key version. Lock/logout/reboot/denied/missing/rotated-key paths yield typed refusals, never new-key creation for old history. Other platforms refuse this profile until their provider is independently qualified.

    Offer optional offline encrypted recovery export, never a Ready precondition. A native export encrypts the customer-held bundle under a reviewed password KDF/AEAD, verifies a restore/decrypt challenge before recording verified offline bundle, and stores no passphrase or private key in webview, logs or owner process. Without export record Recovery: none and explicit device-loss irrecoverability. A lost key yields KeyUnavailable for old messages; retain ciphertext and receipts through signed retention, revoke the old descriptor for new sends and provision a newly authorized key version. Test creation/reopen, wrong app identity, locked store, inactive session, restore/wrong passphrase, changed bundle, loss and rotation. No cloud sync or silent provider fallback.

    T2 — Guest-authored ciphertext and stable pending work

    Areas: crates/mvm-agentd/src/mailbox_outbox.rs, src/bin/mvm-guest-agent/mailbox.rs, workload-facing SDK/example; crates/mvm-core/src/mailbox/frame.rs and crypto helper; crates/mvm-build/src/guest_agent_build.rs only for the exact guest runtime assets needed. A verifies the issuer-signed descriptor and current Send/plan/boot scope inside its guest; generates a fresh per-message DEK, encrypts the typed result before the guest/host boundary, wraps the DEK to the inbox public key and signs the exact ciphertext/header with a guest-held per-boot sender key. The host may verify that signature against admitted boot evidence but cannot mint the guest result. Preserve immutable sealed bytes, stable message ID, content type, run/plan identity and bounded plaintext-free attempt metadata on retry; sender-owned pending work is not acceptance. Do not send the current plaintext OutboxMessage/host-sealed SignedMailboxMessage for this profile. Effective size is the minimum of guest frame, signed Send grant, owner quota and encrypted overhead, checked before transport. Test guest → mock I/O (ciphertext only), wrong descriptor/issuer/tenant, tamper, stale boot, same-ID changed bytes, >30-second submission, quota/size and lost receipt; build the current guest binary, not an older cached image.

    For this profile, host-visible stdout/stderr/console capture reports typed Unavailable; it must not become an alternative plaintext result path. Existing CLI diagnostic behavior for other profiles stays separate.

    T3 — One independent, ciphertext-only owner and journal

    Areas: crates/mvm-hostd/src/mailbox_owner_custody.rs, mailbox_owner_registry.rs and /inbox.rs, mailbox_owner_process.rs, mailbox_owner_outbound/send.rs; crates/mvm-core/src/mailbox/journal.rs, /recovery.rs, /checkpoint.rs. Version the owner manifest: the new profile retains history/anchor/receipt keys, original issuer/allocator signatures, signed descriptor commitment, key version and finite quotas/retention, not a payload-opening key. Ready is published only after exact registry/custody/journal/anchor checks; B need not exist. The process and router receive only ciphertext and wrapped DEKs. Verify A signature, issuer Send, admitted source, policy/revocation, expiry, bounds, ciphertext commitment and replay before queue allocation. Fsync canonical ciphertext, stable-ID dedup and delivery state outside A's VM state; only then issue destination-signed V2 AcceptedLocalDurable. Exact retry recovers the original signed receipt; a different body under the same ID conflicts. Existing host-held-key histories remain recoverable only under their signed legacy profile, never a default fallback or rewritten genesis. Test Ready-before-B, wrong/missing owner policy, partial custody, key absence, disk/full quota, tamper/truncation, restart, collision, interrupted anchor commit, retention/checkpoint and A teardown. A process stand-in is a scoped test, not the live GO witness.

    T4 — Real admitted A and B evidence, not synthetic host files

    Areas: crates/mvm-hostd/src/plan_admission.rs, audit emitter/verifier, guest readiness, mailbox_process.rs, mailbox_owner_source.rs, mvm-vmm host generation binding, and corresponding tests. The current load_admitted_plan accepts configuration data and the PID marker is liveness, not proof of admission. Establish one fixed-schema, required-durability witness from the opaque real AdmittedPlan after authenticated guest boot; bind plan ID/nonce, guest sender/recipient/ACK keys, boot and backend process generation. Chain-sign and fsync before independent-owner Send or Receive; re-verify exact current state at signer issuance and owner consumption. An absent, unwritable, stale, revoked or mismatched witness refuses even in Dev. Do not reinterpret the existing best-effort plan.launched event as this witness or let an arbitrary audit append become its constructor. Test synthetic plan+live stand-in refusal, legitimate admitted guest path, substituted process generation/PID reuse, changed boot/key/nonce, failed readiness, crash before/after durable write, signer rotation and failed helper start. This control protects against false local authority, not a malicious hypervisor that owns the host signer.

    T5 — Later B Receive, narrow release, durable lease and ACK

    Areas: crates/mvm-contract/src/protocol/mailbox/owner_reader.rs, crates/mvm-core/src/mailbox/owner_reader.rs, reader wire/journal/recovery/checkpoint; crates/mvm-hostd/src/mailbox_owner_process.rs and per-VM helper; crates/mvm-agentd/src/bin/mvm-guest-agent/mailbox_reader.rs. Re-review the existing inert B DTO and isolated paused guest-route diff against the new ciphertext/key-wrap contract before integrating. Host-witnessed B assignment is domain-separated, bounded and tied to exact Ready owner, current admitted plan/boot/process generation and issuer-signed Receive-grant commitment. It is not an owner MailboxGrant or standalone Receive authority. B generates an ephemeral recipient key in guest; the unlocked operator endpoint verifies current grant/boot/session/owner/epochs/revocation and rewraps only the requested message key to B, never its inbox private key. A locked or absent endpoint returns ReleaseUnavailable; ciphertext acceptance remains distinct. The owner durably fences possession and journals a lease binding A's original sender epoch, owner epoch, B's reader epoch/boot/session and ciphertext commitment before delivery. B decrypts internally and signs the exact lease after application acceptance. The owner rechecks authority and journals signed ACK/replay before terminal success; revocation/stale boot refuses old in-flight ACK, retaining history for new authority. Test authentic B, wrong key/session/tenant, stale assignment, replay, revoked/expired grant, locked release, lost lease/ACK, duplicate and crash/restart. Never expose receive_next_owner directly as a socket or operator API.

    T6 — B result, native operator read/ACK and Studio

    Areas: crates/mvm-client/src/mailbox_owner.rs, crates/mvm-core/src/client/mod.rs, crates/mvm-client/src/local.rs plus mock/gateway typed refusals; the separately owned #5260 typed-report path; Studio src-tauri/src/commands/ and lib.rs, ui/src/lib/tauri.ts and a real Reports inbox. B's result is a distinct guest-authored, endpoint-encrypted message; stdout/exit/host observation are not its payload or acceptance proof. Create typed bounded client operations for stable logical inbox discovery, non-destructive/leased read, unlock/release status, explicit operator ACK and provenance. The owner verifies a separate issuer-signed operator Receive/ACK grant and key proof on every request; a durable bounded replay fence makes exact retries recover the committed outcome and changed requests conflict. Keychain decryption stays in the authorized native mvm-client endpoint; Tauri maps typed DTOs/refusals only, and thin Studio returns CapabilityUnavailable. React never stores a key, reads journal files, shells out or auto-ACKs on mount/refresh/close. Preserve the existing simulated preview as simulated. Update Studio's pinned MVM revision deliberately only after the client API is reviewed; the direct Delta checkout's ../mvm dependency is not a valid source topology. Tests: wrong operator/key/tenant/owner, expired/revoked grant, read without ACK, exact/conflicting ACK retry, lost response/unknown, source/B teardown, thin refusal and current/unknown/refused/simulated UI isolation.

    T7 — Live HVF and release evidence

    Areas: a new ignored native-HVF mailbox lifecycle test beside crates/mvm-client/tests/launch_lifecycle_live_hvf.rs, a provenance-bound guest workload image and a bounded harness/runbook. The existing scripts/mailbox-live-witness.sh proves a VM-bound mailbox, not an independent owner, and the writer/reader examples are not in the standard OCI guest binary set. Build the guest agent, A and B demo workloads from this exact source into a verified image; rebuild mvmctl, packaged mvm-host-agent, mvm-signer-helper, mvm-mailbox and entitled mvm-hvf-supervisor through the existing embedding/supervisor path. Use the verified kernel/rootfs preparation pattern in scripts/e2e-launch-modes.sh; do not select old cache hits as proof of a new guest. On an isolated home, record Ready before B exists, real A encrypted Cast and signed local receipt, A teardown, later B authorized release/decrypt/lease/signed ACK, B encrypted result, mvm-client/Studio operator read and explicit ACK, locked endpoint/wrong-grant/replay/expiry/full refusals, owner restart/exact recovery and retention-safe cleanup. Check guest and host-process plaintext locations, not merely receipt signatures. Record exact source tree, artifact digests/provenance, commands, exit codes and p50/p95/p99 for admission-inclusive cold boot, first acceptance and key release separately. No mock socket, process stand-in or successful build is GO.

    Final one-revision gates and reporting

    Use stable /tmp and sufficient free space. Run focused tests after each task and, on one exact final source revision, the named checks:

    cargo fmt --all -- --check
    cargo test --workspace
    cargo check --workspace
    cargo clippy --workspace -- -D warnings
    cargo run -p xtask -- check-rust-footprint
    just check::gated
    just ci

    Run canonical contract protocol/no-std checks, relevant hostd/core/guest/client focused targets and Studio just ci and just ci-thin against a verified sibling-source snapshot. Any failed --no-default-features check remains a reported failure until fixed; a default-feature pass is not a substitute. Linux-only/Nix operations stay inside the project builder VM; explicit native HVF tests may run on Apple Silicon. Do not check an issue box, call a tier production-ready, or say GO until matching tests and a real live witness justify that exact claim. Stronger V1 AcceptedDurable needs an independently witnessed rollback anchor; a host-blind active malicious host claim needs a separately qualified protected execution or off-host processing boundary and ADR-001 revision. Neither is inferred from the software-only HVF demo.

    Completion report: enumerate accepted/refused/unknown observations, actual checks and revisions, unrun checks, plaintext/key locations, claims not made, retention/recovery result, measured latency and remaining risks. Keep #5255/#5260 open until their original unchecked criteria are genuinely satisfied; update matching user/CLI/Studio docs when behavior changes. No shared-branch landing or external resource use is authorized by this plan.

  10. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    Security review of the proposed plan — blocking contract amendments

    This proposed amendment refines architecture and execution plan. It supersedes their ambiguous custody, release, header, recovery and diagnostics wording; neither earlier comment is production approval. Independent read-only review found eight blocking decisions. No code, ADR, acceptance checkbox, or security claim changed by this comment. The user is the ADR/security approver of the exact subsequent text, not an automatic approver of implementation or GO.

    1. Native operator principal and unlock

    The first supported production operator endpoint is the signed macOS Studio Tauri host executable (io.mvm.studio, subject to verified release Team ID and code-signing requirement), calling native mvm-client code. A library is not itself a Keychain identity. The production item uses the Data Protection Keychain, app-only access group (no shared group widening), kSecAttrSynchronizable=false, device-only/unlocked accessibility and SecAccessControl.userPresence or an equivalent password-capable native user-presence policy. Apple documents that userPresence can use a passcode without enrolled biometrics; no Secure Enclave-only enrollment is required. Qualify these attributes and app-signing requirements on the exact supported macOS version before adoption; if the OS provider does not enforce them as required, refuse rather than use a file/environment fallback. Development builds use distinct key namespaces and cannot open production items. CLI and unsigned/foreign executables cannot access the first production inbox key. An unlocked login Keychain is not an unlocked application release session.

    Propose two independent finite ceilings for review: 5 minutes inactivity and 15 minutes absolute from native user-presence unlock. These numbers are provisional until a real lock/logout/sleep/UX test and security-owner approval; do not present them as observed defaults. Only an explicit native operator action may begin a new session; automated requests, retries, polling, window focus and background dispatch do not refresh either deadline. Explicit lock, screen lock, sleep/wake, logout, process exit and provider failure terminate the session and zeroize cached private material. A release already under way must recheck session, grant, revocation and lease immediately before its durable release commit and response; an unattended request never triggers an OS prompt. The native process is an authorized plaintext endpoint: its short-lived DEK and display plaintext RAM are within its trust boundary; disable crash dumps/telemetry of secrets. No key enters the webview.

    Proof: signed correct app vs foreign/unsigned app; login Keychain unlocked but release session locked; session expiry, restart and concurrent lock/release; no background prompt and no session extension from retry.

    2. Pre-B provisioning transaction

    Preparing holds only an inert candidate signing identity. The operator endpoint creates an inbox encryption pair and proves private-key possession without disclosing the key, e.g. by opening a fresh challenge encrypted to its proposed public key and signing the exact challenge, descriptor and owner-policy commitments with its separate operator signing identity. The independently pinned issuer and allocator approve the exact tenant/domain/address, owner epoch, encryption public key/version/profile, policy epoch, expiry, quotas and finite retention. Registration under the existing writer lock atomically reserves the address and exact dual signatures; Ready follows verified custody, public descriptor commitment, journal genesis and local anchor. A competing descriptor, old key version, changed owner policy or stale revocation floor refuses. Owner address remains tied to its receipt-signing identity, not encryption key. Failed key creation/approval before Ready leaves an inert/orphaned candidate; do not silently mint a new private half for an already signed address. Descriptor expiry prevents new Send; old accepted ciphertext retains its historical key-version reference through signed retention. Rotating the encryption key under a stable logical address needs a signed version transition and old-key availability; no historical ciphertext rewrite or old grant rebasing. Current revocation state uses the already defined owner-private bounded operator snapshot in ADR-057; a new signed snapshot format would require separate review. Persist high-water marks and refuse missing/backwards/future/stale state for new work.

    Proof: substituted key before approval, competing descriptors, crash before/after Ready, stale descriptor replay, mismatched issuer/allocator, and old ciphertext after authorized key rotation.

    3. Two-step recoverable release transaction

    Define a domain-separated canonical ReleaseRequest/ReleaseVerdict. Bind accepted message and ciphertext/wrapped-key commitment, historical inbox key version, exact owner-signed lease, tenant/domain/address and owner epoch, B's admitted plan/boot/session, fresh guest-generated recipient public key plus possession proof, Receive-grant commitment, policy/revocation generation, replay ID and finite deadline. Order: (a) owner durably commits its challenge/lease before releasing ciphertext; (b) native operator endpoint verifies current grant, key/session/lease and revocations and durably fences the exact request ID/commitment with recipient-encrypted DEK response bytes; (c) only then return those bytes to B. Never wait for interactive unlock while holding the owner journal writer lock. A crash between (a) and (b) leaves a lease with no release, which expires/redelivers. A crash after (b) but before reply yields historical unknown; exact retry may return retained wrapped bytes only while current release authority still passes, otherwise report committed history without disclosing a key. Changed input under an old ID conflicts. If lock/revocation/lease replacement wins before (b), refuse. Recheck immediately before (b); a release cannot recall plaintext or a key already delivered.

    Proof: every crash point, two concurrent releases, changed recipient with same ID, lost response, lock/revocation racing commit, and replaced/expired lease.

    4. Public and protected fields of the new envelope

    The current V1 header is inside the ciphertext; a key-free owner cannot inspect it. Allocate a new canonical envelope version, not a reinterpretation of V1 or the V2 receipt version. Its public, sender-signed authenticated header contains only the fields needed for the owner to check sender key/grant, tenant/domain/address, sender boot/epoch, owner epoch, message ID/ordering/nonce, content-type selector, length/expiry, capability/policy and encryption-key version. Public header/route/length/timing leakage is explicit. An encrypted manifest and payload contain the semantic result/error and any sensitive content; B verifies that protected fields agree with the signed public header after decryption. An owner can authenticate stored ciphertext and check public limits but cannot validate application plaintext or promise successful decryption. The sender's signature and AEAD associated data cover the public header, encrypted manifest/payload and all wrapped-key bytes and identities. The operator endpoint rewraps the message DEK; it does not change the immutable accepted ciphertext or sender signature. A validly signed but undecryptable value gets an explicit recipient-signed poison/NACK disposition with finite attempts/dead letter and preserved acceptance history, never a forged host plaintext verdict. Wrong algorithms, nonce/entropy failure, mismatched manifests and swapped wraps refuse or terminalize as specified.

    Candidate suite for review, not selected implementation: RFC 9180 HPKE X25519HkdfSha256 / HkdfSha256 / AesGcm256 from hpke 0.15.1 with minimal alloc,x25519,aes and an approved RNG; default features pull in unrelated algorithms. Its declared MSRV 1.87 fits MVM's pinned 1.97.1, and its X25519 3/AES-GCM 0.11 dependency lines match existing workspace majors. Existing MVM uses X25519 for a vsock handshake and AES-GCM for at-rest encryption, but does not currently implement this mailbox HPKE composition. Measure guest binary/dependency closure, run RFC vectors and guest no-std/target builds, review the crate and nonce usage before freezing wire IDs. No bespoke KEM/KDF/AEAD composition as a shortcut. References: crate/source/features, suite types, Apple Keychain synchronization and native access control.

    5. Non-circular guest identity and launch witness

    The launcher that actually consumes opaque AdmittedPlan creates a fresh guest boot challenge and binds the boot material it attaches to one backend process generation. Before any independent-owner A Send or B Receive, measured guest-agent startup responds over the existing authenticated control channel with possession proofs for its new guest-held sender/recipient and ACK keys and the exact plan/boot challenge; the launcher checks the response then durably emits a fixed-schema launch witness. Signer helper and owner verify that current witness, original plan/nonce, current generation and issuer grant, not caller-assembled plan.json/PID or a generic audit event. No mailbox effect runs while boot initialization is still pending, even if the VM has started. Termination/restart invalidates old boot authority. Ordinary mailbox services lack the guest sender private key; an active HVF hypervisor could still inspect or impersonate a guest, so do not claim authenticity against a malicious active host on this profile.

    6. Historical facts, owner epoch and lost keys

    Separate current Send/Release/ACK mutations from authenticated historical receipt/committed-ACK lookup. Once committed, a receipt/ACK remains historical truth after A exits or a grant is revoked; revocation forbids new admission, disclosure and ACK mutation, not proof of an old commit. Exact lost-response lookup requires scoped issuer/subject proof and journal/anchor reconciliation, not an unauthenticated reader. Owner helper crash/restart must meet ADR-057's monotonic owner-epoch/fencing rule before new serving; keep old accepted identities/receipts under original epochs and a signed handoff to the new activation. Do not silently reuse an old owner epoch, move an address or re-sign an old receipt. Owner history/anchor/signing-key loss makes history Uncertain/unavailable and forbids new acceptance; operator payload-key loss makes decryption unavailable but does not invalidate a sound ciphertext journal or historical signed receipt. Optional backup verification is a time-stamped observation, not assurance that a bundle still exists today. The original local fsync scope is ordinary process-crash recovery; no whole-host rollback/power-loss/host-blind upgrade is implied.

    7. Structural plaintext gate and Studio display boundary

    Before booting the service-blind profile, require enforcement for every potential plaintext egress: host console/stdout/stderr/transcript capture disabled at the producer, no host-readable sensitive writable share, host tool execution, plaintext input staging, memory snapshot/warm fork or protected-checkpoint export unless a separate encrypted endpoint contract is actually enforced. Unsupported requested controls refuse before execution rather than display Unavailable after plaintext has already reached a host sink. Bounded payload-free audit codes and public route metadata remain permitted. Add a negative production/source gate for host-sealing calls on the new profile and instrumented canary tests; inspection is evidence, not proof of malicious-host secrecy.

    Studio React may display decrypted payload text as the authorized operator UI, but never keys or wrapped DEKs. The native mvm-client endpoint decrypts only under a current operator grant; the webview receives bounded display values, clears them on lock, navigation and authority/source loss, does not persist/cache/export/copy them by default, and does not include them in telemetry or error reports. Native OS screenshots and active host inspection remain outside the ordinary-hardware confidentiality claim.

    8. Explicit result/publication/consumer state matrix

    A guest/host outcome Publication B transition
    Zero exit, no submitted report NoResult No B trigger; no empty result fabricated.
    Guest result/error submitted, any later exit code Pending until destination receipt; separately record signed host exit/timeout/crash observation B starts only on authenticated AcceptedLocalDurable and fresh Receive, never on exit 0 alone.
    Multiple exact retries of one ID and ciphertext Recover original receipt after authority and historical proof One logical accepted value; redelivery possible.
    Same ID, different bytes or second conflicting once-per-run result Typed conflict/refusal No B trigger from the conflicting submission.
    Source timeout/crash before guest submission No guest report; distinct host terminal observation No B trigger.
    Source exits while publication is refused/full/expired Refused separately from exit status No B trigger.
    Source exits after dispatch but verdict lost Unknown; reconcile exact historical signed receipt without minting a new Send from a dead boot B waits until authenticated acceptance is established.
    Accepted message cannot be decrypted/processed by B Acceptance remains true; B signs bounded retryable/terminal NACK, then encrypted dead letter under retention No success/ACK; later grant/reader may be required.

    A's guest must submit immutable sealed bytes before teardown; the host may retain a bounded ciphertext-only pending attempt outside A's VM state, but cannot make a new Send after A's boot ends. On source exit, publication has a bounded completion/reconciliation window independent of application exit. Reporting distinguishes exit outcome, publication verdict and consumer ACK; stdout/stderr are never substituted for a report.

    Measurable gates still requiring owner sign-off

    • Unlock ceiling: proposed 5-minute idle / 15-minute absolute; run native user-presence and lock/sleep/restart tests before fixing it in signed/operator policy. No auto renewal.
    • Durability: name file and parent-directory fsync, journal/anchor commit point, retained key availability and OS/filesystem assumptions; inject each interruption. Do not claim arbitrary power-loss or full-host rollback.
    • Restore: opt-in encrypted bundle must restore into an empty distinct operator context and decrypt challenges for every retained key version before marking verified. No bundle is allowed; lost key remains an explicit irreversible state.
    • Performance: specify sample count, message size, hardware, cold/warm and unlocked/locked conditions before setting p50/p95/p99 budgets; measure admission and first acceptance separately. Never weaken checks to hit latency.
    • Provenance: record exact Git commit plus dirty-tree/patch identity, guest image and helper digests, Studio pin and dependency snapshot. Fresh guest binary is mandatory; a cached older rootfs cannot witness these changes.

    Approval requested: approve these proposed protocol/lifecycle rules as the basis for concrete ADR-001/057 amendments and T0 test vectors, including the provisional unlock ceilings for a native trial. This approval would not certify cryptographic implementation, Keychain enforcement, production operation, malicious-host secrecy, or GO. The eight design-review blockers are closed in the plan only when the exact ADR, DTO shape, real provider behavior and tests agree; keep their acceptance boxes open meanwhile.

  11. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    Correction to the security-review amendment, §5: read “the actual authenticated guest-agent startup responds”, not “measured guest-agent startup.” Ordinary HVF does not provide independently attested guest measurement against a malicious active host. The launch witness is a trusted-host process-authority check only; protected measurement belongs to the separately qualified stronger tier. This correction changes no implementation or acceptance status.

  12. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    Architecture approval checkpoint: the maintainer approved the reviewed ADR-061 service-blind design direction in the linked implementation conversation. The proposed specs/adrs/061-service-blind-mailbox-custody.md now records Accepted architecture; implementation and claims remain gated and cross-references the still-authoritative ADR-001/057 boundaries and key-custody contract. This is a local uncommitted documentation change, not an implemented profile, released claim, production approval or permission to merge. The draft plan in this comment and its security refinement remain execution guidance; the ADR-061 release-commit rule explicitly treats a late owner commit after operator lock as committed, disclosure unknown, never as key disclosure. All original #5255 acceptance boxes remain unchecked pending actual guest/owner/operator/Studio contracts, full gates and live HVF evidence. Historical host-sealed messages are not relabeled as service-blind.

  13. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    Native macOS custody qualification update to the implementation plan and accepted-but-unimplemented ADR-061: the first operator principal is the signed Studio Tauri host executable, not the mvm-client library by itself. The native mvm-client provider must explicitly select the Data Protection Keychain on every item operation, non-sync device-only when-unlocked access and OS user-presence control; signed production and development app identities/groups are distinct. A foreground unlock establishes a bounded app session with one LAContext; background/retry operations reuse it with interaction disabled and require fresh when-unlocked Keychain reads. Inbox private and release-signing keys are not cached across operations; no generic keyring→file/env fallback. Correct native Team ID, distribution signing channel and entitled app identity remain installation-time inputs, not guessed values. Real Data Protection Keychain testing mutates the user's one current-user keychain: it must be opt-in with separately authorized dummy-item add/read/scoped-delete, not a normal test suite or a temp keychain substitute. The user will be asked before that test.

    The revised ADR deliberately does not promise complete screen-lock detection. Observed lock/session-loss invalidates locally; a fresh provider denial prevents a new access, but an unobserved lock after a successful read and before a bounded response write cannot be retroactively cancelled, and already displayed plaintext cannot be revoked. A timed-out owner commit after app lock is historical committed, disclosure unknown, never permission to send a key without a new valid session and current grant. This is a reviewable limitation of the ordinary-hardware profile, not a malicious-host guarantee. Implementation, native provider qualification, full checks and HVF GO remain open.

  14. auser commented on Nov 15, 2026

    @auser
    CollaboratorAuthor

    ADR-061 contract refinement during T0 (accepted architecture, not implemented authority): the new endpoint-encrypted envelope namespace is V3, with local hpke_suite=1 for RFC 9180 X25519/HKDF-SHA256/AES-256-GCM and payload_suite=1 for separate AES-256-GCM. This does not change V1 envelope or receipt-only V2 AcceptedLocalDurable. mvm-contract stays no_std+alloc and transports bounded typed JSON; it does not define signatures over compact serde_json field order. mvm-core will use domain-separated RFC 8785 JCS transcripts for signatures and SHA-256 commitments, with independent-language vectors and byte-identical sealed retries required before activation. The isolated DTO worker's earlier bare cargo test -p mvm-contract --no-default-features --lib compile failure (23 existing stream/chrono feature-gating errors) remains explicitly reported; passing the protocol-feature check does not substitute for it. No full-workspace or live witness has run on these contract changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestmailboxesSigned pass-by-value mailbox program

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions