Scope
A lease request that triggers a download gives the requester no sign of it. The progress stream has queued, provisioning, booting and reclaiming, and nothing for a download. The requester sees silence for 10 to 20 minutes, then a grant or a timeout. Only an operator watching simlock events --follow sees component.install-started.
After this task the requester sees a downloading stage that names the component, on every surface that carries lease progress: the CLI's stderr lines, MCP progress notifications, and the HTTP lease-request resource and its event stream. The stage says whether the request is still waiting behind another download, and carries a percentage when the installer tool prints one.
Technical spec
File references are to main at 5ece1df. Find code by symbol name.
Modules touched
src/core/wait-queue.ts: LeaseProgress gains { stage: "downloading"; component: string; waiting: boolean; percent?: number }. component is the string from RuntimeMissingError.component, carried unread. percent is a whole number from 0 to 100.
src/core/component-installer.ts:
- A call whose install starts running is told
{ stage: "downloading" } with no percent at that moment, before the driver reports anything. ComponentInstallerProgress allows a downloading report without percent.
- A call that joins an install already running is told its latest report at once.
- Nothing else changes:
waiting is still reported to a call behind another install.
src/core/lease-acquisition-coordinator.ts (#resolveAndDrive, at the components.install call): pass onProgress. Map waiting to { stage: "downloading", component, waiting: true }. Map downloading to waiting: false, with percent rounded down when present. Send through queue.notifyProgress. Skip a report equal to the last one sent for that waiter, so a percentage is sent once per whole number. A call that ends as already-installed or not-needed without ever waiting sends nothing.
src/contract/schemas.ts: leaseProgressSchema gains the member, field for field. component at most 64 characters; percent an integer from 0 to 100.
src/cli/index.ts: nothing to add if the progress line is printed from the push as is. The line is {"push":"progress","stage":"downloading","component":"26.4","waiting":false,"percent":41}.
src/mcp/server.ts: STAGE_BASE_PROGRESS gains downloading, placed after queued and before provisioning or reclaiming. Progress within the stage follows percent; a waiting request sits at the stage's base. The message names the component. Later stages' bases move up so every stage still starts above the one before.
src/http/app.ts: serializeRequest gains the downloading case with component, waiting and percent. The event stream sends it as event downloading.
src/gateway/: no logic change. A worker's downloading push is relayed unchanged, like the other stages. Update the progress type copy in test-support.ts.
src/simlock-client/types.ts: the progress type follows the contract.
- Docs:
docs/CLI.md: the stage in the progress list; remove "does not yet reflect an in-flight download" under --allow-download.
docs/HTTP-API.md: the stage on GET /v1/lease-requests/{id} and its event stream.
docs/CLIENT.md: the stage in the progress type.
docs/EVENTS.md and docs/internal/EVENTS.md: remove the "does not yet reflect" sentence.
docs/internal/KNOWN-PITFALLS.md: remove the "no progress push" part of "Component downloads" and fix the heading.
docs/internal/IDEAS.md: remove "Requester-visible download progress".
Contract and event changes
- The
progress push gains the stage downloading. It ships as part of protocol 9. Do not bump the protocol, unless a release tagged after v0.2.0 already carries protocol 9 when this is built; in that case bump to 10 and say so in the PR.
- No event change.
Rules in play
- ADR 0010 §3 (the installer is the only source of install progress).
- ADR 0009, the routing rule that a failure after a progress push is final: a
downloading push counts as a progress push like any other.
architecture.md rule 2 (component is carried unread by the core), rule 5 (progress reaches the requester by a direct call chain, not the bus), rule 13 (the removed doc sentences).
safety.md rule 10: a gateway bounds component and percent through the schema before relaying.
documentation.md rule 2.
testing.md rules 1 to 4.
Tests
- A lease request that starts a download gets
downloading with the component and waiting: false before provisioning.
- A request behind another download on the same platform gets
downloading with waiting: true, then waiting: false when its own install starts.
- A request that joins a download already running gets
downloading with the latest percentage at once.
- A percentage the driver reports twice is sent to the requester once.
- A percentage of 41.7 is sent as 41.
- A request whose runtime is installed gets no
downloading stage.
- A request without
allowDownload for a missing runtime gets no downloading stage.
- A request that waited and then needed no install got
waiting: true and then goes to provisioning with no waiting: false.
GET /v1/lease-requests/{id} shows downloading with component, waiting and percent while the install runs.
- The lease-request event stream sends a
downloading event.
- An MCP client gets a progress notification for the stage whose value is above
queued and below provisioning.
- MCP progress never goes down across
queued, downloading, provisioning, booting.
- A gateway relays a worker's
downloading push unchanged.
- A push with
percent above 100 or a component over 64 characters is refused by the schema.
Done when
- A lease request with
--allow-download for a missing runtime prints a downloading progress line that names the component, before provisioning.
- With a fake-driver install that reports progress, the lines carry a rising
percent.
- The same stage shows on
GET /v1/lease-requests/{id} and on its event stream.
- An MCP client gets a progress notification for the stage.
- A second request that joins a download already running sees the stage too.
- A request that needs no download never sees the stage.
docs/CLI.md and docs/HTTP-API.md list the stage, and the "does not yet reflect an in-flight download" notes are gone. The matching entries in docs/internal/KNOWN-PITFALLS.md and docs/internal/IDEAS.md are removed.
pnpm check is green.
Out of scope
- Bytes done, speed, or time left.
- Cancelling a download in flight.
- Progress for
simlock component install; it has its own push.
- Any gateway logic. A gateway starts no lease-triggered download; it only relays what a worker sends.
Depends on
Approval
Written by an agent.
Scope
A lease request that triggers a download gives the requester no sign of it. The progress stream has
queued,provisioning,bootingandreclaiming, and nothing for a download. The requester sees silence for 10 to 20 minutes, then a grant or a timeout. Only an operator watchingsimlock events --followseescomponent.install-started.After this task the requester sees a
downloadingstage that names the component, on every surface that carries lease progress: the CLI's stderr lines, MCP progress notifications, and the HTTP lease-request resource and its event stream. The stage says whether the request is still waiting behind another download, and carries a percentage when the installer tool prints one.Technical spec
File references are to
mainat 5ece1df. Find code by symbol name.Modules touched
src/core/wait-queue.ts:LeaseProgressgains{ stage: "downloading"; component: string; waiting: boolean; percent?: number }.componentis the string fromRuntimeMissingError.component, carried unread.percentis a whole number from 0 to 100.src/core/component-installer.ts:{ stage: "downloading" }with no percent at that moment, before the driver reports anything.ComponentInstallerProgressallows adownloadingreport withoutpercent.waitingis still reported to a call behind another install.src/core/lease-acquisition-coordinator.ts(#resolveAndDrive, at thecomponents.installcall): passonProgress. Mapwaitingto{ stage: "downloading", component, waiting: true }. Mapdownloadingtowaiting: false, withpercentrounded down when present. Send throughqueue.notifyProgress. Skip a report equal to the last one sent for that waiter, so a percentage is sent once per whole number. A call that ends asalready-installedornot-neededwithout ever waiting sends nothing.src/contract/schemas.ts:leaseProgressSchemagains the member, field for field.componentat most 64 characters;percentan integer from 0 to 100.src/cli/index.ts: nothing to add if the progress line is printed from the push as is. The line is{"push":"progress","stage":"downloading","component":"26.4","waiting":false,"percent":41}.src/mcp/server.ts:STAGE_BASE_PROGRESSgainsdownloading, placed afterqueuedand beforeprovisioningorreclaiming. Progress within the stage followspercent; a waiting request sits at the stage's base. The message names the component. Later stages' bases move up so every stage still starts above the one before.src/http/app.ts:serializeRequestgains thedownloadingcase withcomponent,waitingandpercent. The event stream sends it as eventdownloading.src/gateway/: no logic change. A worker'sdownloadingpush is relayed unchanged, like the other stages. Update the progress type copy intest-support.ts.src/simlock-client/types.ts: the progress type follows the contract.docs/CLI.md: the stage in the progress list; remove "does not yet reflect an in-flight download" under--allow-download.docs/HTTP-API.md: the stage onGET /v1/lease-requests/{id}and its event stream.docs/CLIENT.md: the stage in the progress type.docs/EVENTS.mdanddocs/internal/EVENTS.md: remove the "does not yet reflect" sentence.docs/internal/KNOWN-PITFALLS.md: remove the "no progress push" part of "Component downloads" and fix the heading.docs/internal/IDEAS.md: remove "Requester-visible download progress".Contract and event changes
progresspush gains the stagedownloading. It ships as part of protocol 9. Do not bump the protocol, unless a release tagged after v0.2.0 already carries protocol 9 when this is built; in that case bump to 10 and say so in the PR.Rules in play
downloadingpush counts as a progress push like any other.architecture.mdrule 2 (componentis carried unread by the core), rule 5 (progress reaches the requester by a direct call chain, not the bus), rule 13 (the removed doc sentences).safety.mdrule 10: a gateway boundscomponentandpercentthrough the schema before relaying.documentation.mdrule 2.testing.mdrules 1 to 4.Tests
downloadingwith the component andwaiting: falsebeforeprovisioning.downloadingwithwaiting: true, thenwaiting: falsewhen its own install starts.downloadingwith the latest percentage at once.downloadingstage.allowDownloadfor a missing runtime gets nodownloadingstage.waiting: trueand then goes toprovisioningwith nowaiting: false.GET /v1/lease-requests/{id}showsdownloadingwithcomponent,waitingandpercentwhile the install runs.downloadingevent.queuedand belowprovisioning.queued,downloading,provisioning,booting.downloadingpush unchanged.percentabove 100 or acomponentover 64 characters is refused by the schema.Done when
--allow-downloadfor a missing runtime prints adownloadingprogress line that names the component, beforeprovisioning.percent.GET /v1/lease-requests/{id}and on its event stream.docs/CLI.mdanddocs/HTTP-API.mdlist the stage, and the "does not yet reflect an in-flight download" notes are gone. The matching entries indocs/internal/KNOWN-PITFALLS.mdanddocs/internal/IDEAS.mdare removed.pnpm checkis green.Out of scope
simlock component install; it has its own push.Depends on
simlock component installinstalls a component from the CLI, the client and HTTP #217Approval
Written by an agent.