plan: docs/plans/0007-compute-provider-kcp.yaml item: M1-FC-INVENTORY written: "2026-10-12" gold_standard: >- hono-compute-provider lib/compute-provider-firecracker (TypeScript), with lib/microvm-firecracker, lib/node-image-firecracker, lib/oidc-issuer-hono (the provisioning enricher and issuer) and the atproto-market lib/common/cloud-init-common modules a firecracker guest's user_data composes. Each entry is one learning the Go side (cmd/socialweb-nodeboot, cmd/socialweb-nodeimage, internal/nodevm, internal/nodefirecracker, and the planned internal/vmruntime + internal/computevm ComputeVM controller) must meet, must not inherit, or that is dropped. parity_boundary: >- M2-BACKPORT: the VMSpec handed to vmruntime.Prepare, the seed drive's bytes, fc.json, the bid config record and the issuer's droplet view. One recorded RFP fixture (testdata/compute-parity/: vm record, requester, accept bundle, contract) is run through ts/hono-compute-provider/test/compute_parity_test.ts (the firecracker provider, microvm stubbed at that boundary) and internal/computevm/parity_test.go (the Go controller, unit start stubbed at the same boundary); names and addresses the fixture marks as allocated are excluded. A parity id written TestParityX names one case run by BOTH files against that fixture; any other id names the test file and case that proves the entry. fields: id: FC- source: TypeScript file:line or file:start-end, relative to the root of this repository behaviour: what the TypeScript does and why, including the live reading that taught it go_side: "meets: yes | partial | no | not-applicable (the behaviour stays TypeScript by design), and where" parity_test: the test ids that will prove the Go side (or prove the removal) status: "port | removed-legacy (non-iroh guest transport or the guest JSR registry: not ported, still in the TypeScript) | removed (removed from the TypeScript by 0008 M1-STRIP-LEGACY-TRANSPORTS; a source written path@:lines cites the code as it was at that commit, the last one before the removal) | superseded-by-" covers: exported functions, constants and logger events of compute-provider-firecracker/mod.ts and microvm-firecracker/mod.ts this entry accounts for (checked by hono-compute-provider test/requirements_inventory_test.ts) legacy_decision: >- docs/research/2026-10-11-vm-lifecycle/user-decisions.md (2026-10-12, multi-server-to-kcp section 7 cont.): STRIP ALL NON-IROH GUEST TRANSPORTS as legacy (cloud-init-common tunnel / fedproxy-ssh / fedproxy-web / web-terminal modules, websocat ProxyCommand paths, relay-fqdn guest addresses); this removes the guest JSR registry. Entries with status removed-legacy are not ported; entries with status removed are also gone from the TypeScript (0008 M1-STRIP-LEGACY-TRANSPORTS; atproto-market b78adc1 and hono-compute-provider 54d1b8f carry them). requirements: - id: FC-USERDATA-REQUIRED source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:634-645 behaviour: >- An RFP with no user_data is refused before a name, a work dir or an address is allocated. On this provider the user_data IS the guest: a guest started without one reaches Running with no sshd, no transport and no secrets, unreachable. go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp refuses an RFP with no user_data before a name exists; the ComputeVM controller waits for the user_data Secret and fails a ComputeVM whose Secret holds no user-data before Prepare clones anything (internal/computevm userData, boot). parity_test: - TestParityAnRfpWithoutUserDataIsRefusedBeforePrepare status: port covers: [createComputeProviderFirecracker] - id: FC-GUEST-NAME source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:116 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:151-162 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:646-647 behaviour: >- A guest's name is fc-<12 hex of a hash of the requester DID>-<8 hex of a random UUID>. The one name is the providerId, the runner container's name, the work dir's last segment, the cloud-init instance-id and local-hostname, and the droplet id the provisioning nonce is bound to (associateWithDroplet). It never reaches the requester as an address (FC-OIDC-REPORTER-NO-HOSTNAME). go_side: meets: yes detail: >- M2-BACKPORT: One name: the ComputeVM name is the providerId, the HostProcess child vm-, the unit socialweb-vm--, the work dir /handles//vms/, the seed's instance-id and local-hostname (internal/vmruntime Prepare) and the droplet id the nonce is bound to. parity_test: - TestParityTheGuestNameIsTheProviderIdInstanceIdAndNonceBinding status: port - id: FC-ENRICH-ORDER source: - ts/atproto-market/lib/market-bidder-compute/mod.ts:376-387 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:652-671 behaviour: >- user_data is enriched in a fixed order, each step reading what the earlier ones wrote: (1) the bidder injects the accept bundle (with guest_onnetwork_url) before provision; (2) the OIDC enricher adds the provisioning-token exchange and its on-network reporter; (3) withOnNetworkReporter (no-op when a reporter is present); (4) withOnNetworkBaseUrl (no-op when base_url is named); (5) injectJsrUrl (legacy, FC-JSR-REGISTRY); (6) the host address is the requester-named relay.localhost address, else the runtime's host address, logged as guest_host_address; (7) pointGuestAtHost (TLS port rewrite, /etc/hosts, .curlrc); (8) the result is written to /user-data.yaml, which is exactly the seed. go_side: meets: not-applicable detail: >- Decided in M2-BACKPORT: pure enrichment stays TypeScript (compute-provider-common, applied by compute-provider-kcp before the ComputeVM is written); the Go side writes the enriched bytes into the cidata drive unchanged and never re-composes them. parity_test: - TestParityTheSeedUserDataEqualsTheProvidersByteForByte status: port covers: [guest_host_address] - id: FC-ENRICH-ONCE source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:322-348 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:524-559 - ts/atproto-market/lib/common/cloud-init-common/mod.ts:168-214 - ts/atproto-market/lib/common/cloud-init-common/mod.ts:276-345 behaviour: >- The reporter and base_url steps detect themselves by marker (guest-onnetwork.service, the base_url path) and are idempotent; buildUserData dedupes write_files by path (later wins, first position keeps), set-unions packages and bootcmd, puts runcmdPrepend before the base runcmd and re-asserts the resolver once at the end. Two steps are NOT idempotent: the OIDC enricher mints a new nonce and token per call, and pointGuestAtHost appends its /etc/hosts runcmd and .curlrc entry again. So enrichment must run exactly once per guest. go_side: meets: not-applicable detail: >- compute-provider-kcp enriches once and stores the result on the ComputeVM spec; a controller restart, re-adoption or a re-Prepare reads the stored bytes and never enriches again. parity_test: - TestParityAReadoptedComputeVMKeepsItsSeedBytes - ts/hono-compute-provider/test/compute_parity_test.ts::"enrichment runs once per ComputeVM" status: port - id: FC-ACCEPT-BUNDLE source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:164-170 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:937-938 - ts/atproto-market/lib/common/cloud-init-common/mod.ts:346-369 - ts/atproto-market/lib/market-bidder-compute/mod.ts:376-384 behaviour: >- The accept bundle {$type market.accept, accept, rfp, bid, bid_config (strongRef wrapper {uri, cid, value}), vm, guest_onnetwork_url} is written as JSON at the accept path (DEFAULT_ACCEPT_PATH_VM, /root/secrets/publicdomainrelay.com/market/accept.json), root:root 0600, its parent created 0700 by a prepended runcmd. It is per contract and never baked into an image. go_side: meets: not-applicable detail: >- Composed in TypeScript; the Go side carries it inside the seed bytes. M2-VM-RUNTIME's cidata drive must hold the user-data verbatim so the path and mode survive. parity_test: - TestParityTheAcceptBundleIsAtTheAcceptPathWithMode0600 status: port covers: [injectAcceptBundle] - id: FC-ONNETWORK-URL source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:172-182 - ts/atproto-market/lib/market-bidder-compute/mod.ts:383 behaviour: >- The guest reports to the URL in the bundle's guest_onnetwork_url (the bidder's own /v1/on-network, which holds the accept-to-receipt map and submits the wrapped event); with none, this provider's issuer URL + /v1/on-network. go_side: meets: not-applicable detail: >- The route stays TypeScript (compute-provider-kcp, M4-ONNETWORK; a guest-facing DenoPod after M4-BIDDER-VAULT). The port derivation from the tunnel subscriber's --ingress-proxy-host flag in guestOnNetworkUrl is legacy (FC-LEGACY-TUNNEL-MODULE). As built in Go (plan 0034): the guest posts to the bidder GuestControlPlane's POST /v1/events at http://192.0.2.1:, with the ingress URL as fallback; /v1/on-network is gone. parity_test: - ts/hono-compute-provider/test/firecracker_onnetwork_reporter_test.ts::"the bundle's guest_onnetwork_url is what the guest reports to" - TestParityTheSeedUserDataEqualsTheProvidersByteForByte status: port covers: [onNetworkUrlFromBundle] - id: FC-ONNETWORK-TARGETS source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:184-211 behaviour: >- The report is tried at the URL as written and, when the user_data names ` relay.localhost` and the URL's host is a .localhost name, again with curl --resolve ::: the guest's /etc/hosts line for a relay name may be the microVM's gateway, which carries no arbitrary port. go_side: meets: not-applicable detail: TypeScript enrichment; on the Go side the address in the alias is the constant pasta gateway (FC-HOST-ADDRESS). parity_test: - ts/hono-compute-provider/test/firecracker_onnetwork_reporter_test.ts::"a relay name behind a dispatcher is also tried at the address the guest reaches it on" status: port covers: [onNetworkTargets] - id: FC-REQUESTER-NAMED-HOST source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:510-522 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:667 behaviour: >- A host address the requester already named (` relay.localhost`, the market network's guestHostAliases) is the one the provider uses for every .localhost name: cloud-init resolves before the provider's runcmd can append a second line, so a different address would be the line that never wins. go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp points every .localhost name at the address the requester named (addressTheRequesterNamed), else the constant pasta host address 192.0.2.1; pasta forwards both to the host. parity_test: - ts/hono-compute-provider/test/firecracker_network_test.ts::"a host address the requester already named is the one the guest is given" - TestParityARequesterNamedRelayAddressWins status: port covers: [addressTheRequesterNamed] - id: FC-HOST-ADDRESS source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:53-65 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:297-302 behaviour: >- The address a guest dials this host at is the runner container's default bridge gateway (backend.defaultGateway()), not pasta's gateway, which is the container itself and answers nothing. go_side: meets: no detail: >- No container on the Go side. Decided 2026-10-11: every guest gets the identical address plan inside its private pasta user+net namespace and the host address is pasta's mapped gateway, a constant vmruntime exports and compute-provider-kcp uses for enrichment (M2-VM-RUNTIME). parity_test: - TestParityTheHostAddressIsThePastaMappedGateway status: superseded-by-M2-VM-RUNTIME - id: FC-ONNETWORK-REPORTER-FALLBACK source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:213-304 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:338-348 behaviour: >- When no OIDC enricher composed a reporter, the provider composes its own (two reporters would race to publish the same event): waits up to 180 s for /run/guest-fqdn, posts {acceptUri, acceptCid, address} to each target, 5 rounds 6 s apart, stops at the first 200, logs to journal+console. Script and unit ride in user_data, never in the image. go_side: meets: not-applicable detail: >- TypeScript enrichment. Ported with the rules the live run taught the OIDC reporter: ordered after iroh.service, never posts a hostname (FC-OIDC-REPORTER-NO-HOSTNAME); its tunnel-subscriber ordering is FC-LEGACY-REPORTER-TUNNEL-ORDERING. parity_test: - ts/hono-compute-provider/test/firecracker_onnetwork_reporter_test.ts::"user_data that already reports the FQDN is left alone" - TestParityTheSeedUserDataEqualsTheProvidersByteForByte status: port covers: [onNetworkReporterModule, withOnNetworkReporter] - id: FC-ONNETWORK-BASE-URL source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:306-336 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:469-482 behaviour: >- With no issuer, the provider writes /base_url (the report URL's origin, 0600) so the reporter does not fall back to the public dispatcher and 404. It is skipped whenever the user_data names that path, because provisioning-token.service has ConditionPathExists=!base_url and a pre-written base_url would stop the token exchange. go_side: meets: not-applicable detail: TypeScript enrichment; carried in the seed bytes. parity_test: - ts/hono-compute-provider/test/firecracker_onnetwork_reporter_test.ts::"the guest is told where to report, without an OIDC exchange having to succeed" status: port covers: [withOnNetworkBaseUrl] - id: FC-OIDC-REPORTER-NO-HOSTNAME source: - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:484-541 - ts/atproto-market/lib/abc/compute-request/mod.ts:269-275 behaviour: >- Live hat-8 2026-10-11T23:53Z (hono-compute-provider 033a8c0, atproto-market 6099aac): the reporter waited 60 s and then posted $(hostname), and the requester polled wss://fc-1379be663dd9-762b64d5/ until timeout. Now it waits 600 s for /run/guest-fqdn, is ordered After=iroh.service with TimeoutStartSec=900, logs to journal+console, never falls back to the hostname, and on giving up dumps pending jobs and the transport units' status and fails the unit; the requester classifies a single-label address as a hostname and keeps waiting. With no base_url the reporter now exits 1 instead of falling back to a public dispatcher, and a provisioning whose issuer has no http(s) base URL is refused at provision time (plan 0007 M3-OIDC-ISSUER-INSTANCE). go_side: meets: not-applicable detail: TypeScript enricher; carried in the seed bytes. parity_test: - ts/hono-compute-provider/test/oidc_onnetwork_reporter_test.ts::"the oidc reporter never publishes the guest's hostname as its address" - ts/hono-compute-provider/test/oidc_onnetwork_reporter_test.ts::"the oidc reporter waits for the transport long enough for a slow boot and says why when it gives up" - ts/hono-compute-provider/test/compute_parity_test.ts::"the reporter has no fallback base URL" - ts/hono-compute-provider/test/oidc_issuer_instances_test.ts::"the on-network reporter has no hardcoded base URL and fails closed without one" status: port - id: FC-IROH-LISTENER-ORDERING source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts:562-726 behaviour: >- Live hat-8 2026-10-11T23:53Z (atproto-market ad659c1): `systemctl enable --now ssh` in runcmd never returned and iroh.service was ordered After=ssh.service, so no ticket reached /run/guest-fqdn in time. Now iroh.service is After=network-online.target only (dumbpipe dials 127.0.0.1:22 per stream), runcmd enables and starts it --no-block before ssh, ssh starts --no-block, the ticket is written as iroh:// to /run/guest-fqdn through a temp file and mv, IROH_SECRET persists 0600 so the endpoint id survives restarts, Restart=always. Re-run: ticket 6.5 s after boot, ssh ready ~20 s after the request. iroh is the only guest transport kept. go_side: meets: partial detail: >- The module stays TypeScript and rides in the seed. The guest image (M1-GUEST-IMAGE) must not bake any unit that orders iroh after ssh; dumbpipe is still fetched from GitHub at boot by setup-iroh.sh unless the image carries the pinned version. parity_test: - ts/atproto-market/test/cloud_init_snapshot_test.ts::"iroh listener never waits on sshd and no runcmd step blocks on a unit start" - TestParityTheSeedUserDataEqualsTheProvidersByteForByte status: port - id: FC-BID-CONFIG-TOKEN-PATH source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:27 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:35 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:659-675 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:460-465 - ts/atproto-market/lib/common/cloud-init-common/mod.ts:362-490 behaviour: >- The bid config record (com.publicdomainrelay.temp.compute.config.wif.simple) carries accept_path, issuer_uri, to_issue exchange-firecracker-oidc, actx (last segment of the bidder DID), and under SECRETS_PREFIX /root/secrets/digitalocean.com/serviceaccount: actx_path team_uuid, token_path token, url_path base_url; url_route /v1/oidc/issue; subject actx:{actx}:plc:{did-plc-key}:role:{role}. token_path must be the file provisioning-token.sh writes: before 4d7da59 it named another path and setup-secrets.service, which reads bid_config.value (or the inline record) from accept.json, waited for a token that never appeared. go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp createBidConfig writes the same wif.simple record (accept_path, issuer_uri, exchange-firecracker-oidc, actx, SECRETS_PREFIX token, team_uuid and base_url paths). parity_test: - TestParityTheBidConfigRecordEqualsTheProviders - ts/hono-compute-provider/test/firecracker_secrets_path_test.ts::"the firecracker bid config points the guest at the token the provisioning script writes" status: port covers: [SECRETS_PREFIX, WIF_SIMPLE_NSID] - id: FC-GUEST-TLS-PORT-AND-CURLRC source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:524-559 - ts/atproto-market/request-vm-ssh/mod.ts:329-333 behaviour: >- With guestTlsPort set (a market network dispatcher off 443), every https://.localhost URL without a port is rewritten to :, /root/.curlrc gets `resolve = *::`, and every .localhost name gets a prepended runcmd line ` ` in /etc/hosts; a user_data that is not YAML is refused. .curlrc reaches systemd units only when CURL_HOME is set, which the market network requester does with `systemctl set-environment CURL_HOME=/root` in bootcmd. go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp applies pointGuestAtHost with the bidder's guest TLS port before the ComputeVM is written; the seed carries the bytes verbatim. parity_test: - TestParityTheGuestTlsPortRewriteHostsAndCurlrc status: port - id: FC-NETWORK-RANGE-INDEX source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:108-110 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:132-149 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:570 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:648 behaviour: >- Each guest gets a /30 out of rangeBase (default 172.30.0.0): pasta .0, gateway .1, guest .2. The index is a process-local counter, never reused, 16384 blocks, exhausted rather than wrapped. go_side: meets: no detail: >- Decided 2026-10-11: an IDENTICAL address plan for every guest inside its private pasta namespace; the network-index allocator is deleted. nodevm.Network has one default plan today (internal/nodevm/spec.go:37-43, 156-232). parity_test: - TestParityEveryGuestGetsTheSameAddressPlan - internal/vmruntime TestPrepareWritesASeedDriveAndFcJsonAndNoHostToolIsExecd status: superseded-by-M2-VM-RUNTIME covers: [networkFor] - id: FC-GUEST-MAC source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:118-130 behaviour: >- The guest MAC is 06:00:, so the guest's network setup configures the address the host routed; it is derived only from an IPv4 address. go_side: meets: yes detail: >- nodevm.DefaultGuestMAC 06:00:ac:10:00:02 encodes 172.16.0.2 by the same rule (internal/nodevm/spec.go:39, 241-242); with the identical plan it is the constant fc.json network-interfaces MAC. parity_test: - TestParityFcJsonMacEncodesTheGuestAddress status: port covers: [guestMacFor] - id: FC-GUEST-IS-NOT-GATEWAY source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:202-208 behaviour: >- A guest addressed as its own gateway is refused before anything starts: it would ARP for itself and its frames would be answered by the interface that sent them. go_side: meets: yes detail: nodevm Network.Validate refuses guest == gateway and pasta == gateway (internal/nodevm/spec.go:156-232). parity_test: - TestParityTheConstantAddressPlanValidates status: port - id: FC-RESOLVER-GATEWAY source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts:117-129 - ts/atproto-market/lib/common/cloud-init-common/mod.ts:287-319 behaviour: >- /etc/resolv.conf names the DNS gateway first, then 1.1.1.1 and 9.9.9.9, then any nameserver a caller wrote; it is written in write_files and re-asserted last in bootcmd and in runcmd. The default gateway 172.30.0.1 is the first block of the TypeScript provider's range. go_side: meets: yes detail: >- M2-BACKPORT: The identical guest network's gateway is 172.30.0.1 (internal/guestnet Gateway, pasta --dns-forward), the same address as DEFAULT_DNS_GATEWAY. parity_test: - TestParityTheResolverNamesTheConstantGatewayFirst status: port - id: FC-PACKAGES-GUARD source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts:207-249 behaviour: >- Packages the modules ask for leave cloud-init's apt module and become the first runcmd entry, a dpkg -s guard that runs apt only for what the image lacks; package_update is false; names are validated before entering a shell command. A baked image therefore runs no apt at boot. go_side: meets: not-applicable detail: TypeScript composer; the image must carry the packages (FC-PREINSTALL-MANIFEST). parity_test: - TestParityTheSeedUserDataEqualsTheProvidersByteForByte status: port - id: FC-PREINSTALL-MANIFEST source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:350-508 behaviour: >- The image's baked list is derived from module ids only (a module function is refused: it closes over one contract, e.g. its accept.json). A package, file or prepended command is baked only when the module emits it identically with and without instance values; everything else is left to boot. The last module to write a path wins; prepended commands keep their order; argv commands are shell-quoted; files owned by anyone but root, apt sources and non-command runcmd entries are refused rather than baked wrong. go_side: meets: partial detail: >- cmd/socialweb-nodeimage applies a manifest in a chroot on the bidder (preinstall.go:32 LoadPreinstall, :140 chrootPreinstall; ensure.go:209 refuses an image that does not carry it). M1-GUEST-IMAGE bakes the manifest in scripts/build-guest-image.sh on a build host instead. parity_test: - ts/hono-compute-provider/test/preinstall_manifest_test.ts - build-pipeline TestTheImageCarriesThePreinstallManifest status: port covers: [preinstallManifest, shellCommandLine] - id: FC-IMAGE-ENSURE source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:597-611 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:940-942 - ts/hono-compute-provider/lib/node-image-firecracker/mod.ts:19-126 behaviour: >- `socialweb-nodeimage -ensure -json [-reuse-stale] [-preinstall p] -config c`; the last JSON line is the status {state present|built|reused, fingerprint, dir, config, rootfsMiB, ...}; a build that exits non-zero is an error unless the state is present or reused. Ensured at setup and lazily on first provision, then cached; the fingerprint is the image dir and is reported as imageFingerprint. Logged node_image_ready / node_image_status. go_side: meets: yes detail: >- Today cmd/socialweb-nodeimage/ensure.go:96 (Ensure) and internal/nodefirecracker/manifest.go:40 (RecordedFingerprint). Superseded: internal/guestimage fetches, verifies and stores images keyed by (hypervisor, arch, sha256) from versions.json pins. parity_test: - internal/guestimage TestAnImageWhoseShaDiffersIsRefused - internal/guestimage TestAnImageIsFetchedOnceAndReusedByHash status: superseded-by-M1-GUEST-IMAGE covers: [node_image_ready] - id: FC-IMAGE-REUSE-STALE source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:54 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:598-608 behaviour: >- reuseStale lets a bidder boot an earlier image whose fingerprint differs from what the inputs name, with a warning node_image_reused {askedFor, using}. go_side: meets: yes detail: >- cmd/socialweb-nodeimage -reuse-stale today. Superseded: a pinned sha256 mismatch is refused; there is no stale reuse. parity_test: - internal/guestimage TestAnImageWhoseShaDiffersIsRefused status: superseded-by-M1-GUEST-IMAGE covers: [node_image_reused] - id: FC-IMAGE-PROVENANCE source: - ts/hono-compute-provider/lib/node-image-firecracker/mod.ts:73-109 behaviour: >- The image is built on the bidder from that host's own kernel and module tree by socialweb-nodeimage (root, chroot, apt, a go toolchain), so two bidders boot different kernels. go_side: meets: no detail: >- Decided 2026-10-11: images are built by scripts/build-guest-image.sh on a build host per hypervisor x arch, pinned by sha256, shipped as assets; bidders need no root, chroot, apt or go. parity_test: - build-pipeline TestTheGuestKernelConfigHasTheGracefulStopAndVirtioMmioDrivers - build-pipeline TestTheRootfsCarriesNoSshHostKeyAndEnablesGrowpart status: superseded-by-M1-GUEST-IMAGE - id: FC-IMAGE-ROOTFS-SIZE source: - ts/hono-compute-provider/lib/node-image-firecracker/mod.ts:40-49 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:572 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:600 behaviour: >- ensure must report a positive rootfsMiB: a provider compares the contract's disk with it, and a template larger than the disk cannot be shrunk. The provider records it but does not yet use it. go_side: meets: yes detail: >- M2-BACKPORT: The controller logs the image rootfs size beside the contract's disk at prepare; guestdisk.GrowTo never shrinks the clone below it. parity_test: - TestParityAContractDiskBelowTheImageNeverShrinksTheRootfs status: port - id: FC-DISK-FROM-CONTRACT source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:613-615 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:675 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:685 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:22 behaviour: >- The contract's disk is parsed like mem and passed as diskMib whatever the number; a spelling nothing parses leaves the guest at the image's own size. go_side: meets: yes detail: >- M2-BACKPORT: spec.vm.disk is parsed like mem (internal/computevm ParseMiB) and the clone is truncated up to it; a spelling nothing parses keeps the image's own size. parity_test: - TestParityTheVMSpecDiskEqualsTheContract - ts/hono-compute-provider/test/disk_semantics_test.ts::"a contract that asks for a bigger disk boots a guest grown to it" - internal/vmruntime TestTheSeedIsASeparateCidataDriveAndTheRootfsIsNotEdited status: port - id: FC-MEM-AND-VCPU source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:683-684 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:868-883 behaviour: >- mem matches /^\s*(\d+)\s*([KMGT])?i?B?\s*$/i: no unit is MiB, K rounds to at least 1 MiB, G x 1024, T x 1024 x 1024; anything else leaves the image default. vcpu is passed only when it is a positive number. go_side: meets: yes detail: >- M2-BACKPORT: internal/computevm ParseMiB parses spec.vm.mem by the same rule; cpus is used only when positive; anything else leaves the image default (compute-provider-kcp writes 0 and an empty string when the RFP names none). parity_test: - TestParityMemAndVcpuParseLikeTheProvider - ts/hono-compute-provider/test/firecracker_network_test.ts::"the memory an RFP asks for is read in the units the lexicon writes it in" status: port covers: [parseMemMiB] - id: FC-ISSUER-DROPLET-VIEW source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:608-614 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:710-745 behaviour: >- The issuer's droplet for a guest this process holds is {id, tags [oidc-sub:plc:, oidc-sub:role:]}; /v1/oidc/prove builds the subject actx::plc::role: from the three-part oidc-sub tags and issues a token naming droplet_id. An unknown id is undefined and the exchange is refused. containerName and networks went with the host-side key scan (FC-HOSTKEY-SCAN, 0007 M4-HOST-KEYS). go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp getDroplet reads {id, oidc-sub tags} from the ComputeVM (dropletOf); host keys come from the guest's report into status.market. parity_test: - TestParityTheIssuersDropletViewEqualsTheProviders - ts/hono-compute-provider/test/oidc_hostkeys_test.ts::"a firecracker guest's proven host keys reach the bidder under its provider id; pasta forwards no inbound port" status: port covers: [createFirecrackerComputeProvider] - id: FC-HOSTKEY-SCAN source: - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts@964f1fd44fef:106-152 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts@964f1fd44fef:576-599 behaviour: >- provisioningValidate learned the guest's ed25519 host key by ssh-keyscan, through `container exec` into the runner container when the droplet named one, retrying 300 s, and checked the guest's prove-sshd signature over its provisioning token against it. go_side: meets: not-applicable detail: >- Deleted, not ported. Removed from the TypeScript by 0007 M4-HOST-KEYS: the guest reports its host keys with a prove-sshd signature over its provisioning token (FC-GUEST-HOSTKEYS-REPORT) and provisioningValidate verifies the prove signature against the reported key bound to the same token (ssh-keygen -Y verify; the old check-novalidate never compared the signer with the scanned key). parity_test: - ts/hono-compute-provider/test/oidc_hostkeys_test.ts::"provisioningValidate verifies against the reported key and nothing in lib execs ssh-keyscan" status: removed - id: FC-GUEST-HOSTKEYS-REPORT source: - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:551-600 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:672-708 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:616-644 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:739 behaviour: >- The enricher writes the provisioning token to a 0600 file and composes cloud-init-common's hostKeysReporterModule (guest-hostkeys.service, ordered before provisioning-token.service): the guest posts every /etc/ssh/ssh_host_*_key.pub with a prove-sshd signature over the token to /v1/host-keys. The issuer binds the ed25519 key to the token's nonce only when the signature verifies with it (403 otherwise; a different key for the same nonce 409), then the provider maps the nonce to the guest it provisioned and hands {id, keys} to the bidder (ComputeProvider.onHostKeys), which publishes vm.hostKeys; events guest_hostkeys_forwarded, guest_hostkeys_unknown_guest (404), guest_hostkeys_no_listener (503). go_side: meets: no detail: >- Planned with M4-ONNETWORK / M5-BIDDER-COMPONENTS: the guest-facing DenoPod serves /v1/host-keys beside /v1/on-network and the ComputeVM's nonce maps the report to its receipt; the seed carries the reporter. parity_test: - ts/hono-compute-provider/test/oidc_hostkeys_test.ts - ts/atproto-market/test/cloud_init_hostkeys_test.ts - ts/atproto-market/test/vm_hostkeys_bidder_test.ts status: port covers: [guest_hostkeys_forwarded, guest_hostkeys_unknown_guest, guest_hostkeys_no_listener] - id: FC-PROVISIONING-EXCHANGE source: - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:426-575 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:680-714 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:602-611 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:652-654 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:695 behaviour: >- The enricher mints a 15-minute provisioning token (sub actx::role:provisioning:nonce:) and composes provisioning-token.sh/.service: sign the token with the guest's ed25519 host key (namespace prove-sshd), POST it to /v1/oidc/prove, write token, team_uuid and base_url under SECRETS_PREFIX; root ssh dir 0700; key-only root login. The nonce is bound to the guest name only after boot succeeded and is single use. go_side: meets: not-applicable detail: >- Stays TypeScript (the issuer moves to the bidder's guest-facing DenoPod after M4-BIDDER-VAULT); carried in the seed bytes. The DigitalOcean /etc/shadow edit is harmless on firecracker. parity_test: - ts/hono-compute-provider/test/oidc_workload_identity_integration_test.ts - TestParityTheSeedUserDataEqualsTheProvidersByteForByte status: port - id: FC-IN-MEMORY-STATE source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:566-570 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts:65-78 behaviour: >- The guests map, the rbac refs, the watches, the address index and the issuer's nonce store live in process memory: a bidder restart forgets every running guest (no watch, no droplet view, no prove), and only destroy-by-name still finds the work dir. go_side: meets: no detail: >- Re-population from ComputeVM plus live VMM state by the tri-state Verdict (M2-CONTROLLER); nothing a restart needs lives only in memory. parity_test: - internal/computevm TestAKcpRestartStopsNoVMAndEveryVMIsReadoptedAfter - internal/computevm TestACoreRestartUnderTheProcBackendAdoptsEveryVMUnit status: superseded-by-M2-CONTROLLER - id: FC-OIDC-ISSUER-MOUNT source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:904-927 behaviour: >- The provider mounts the OIDC issuer its own guests are told to call on its own serve when the relay connects (serviceUrl from ingressRef); without it /v1/oidc/prove answers 404, no accept bundle secrets are fetched and the guest reads as one that never joined. go_side: meets: not-applicable detail: >- Stays TypeScript; decided 2026-10-12 the bidder's guest-facing endpoints (OIDC issuer, /v1/on-network) split into their own DenoPods after M4-BIDDER-VAULT. parity_test: - ts/hono-compute-provider/test/compute_provider_kcp_test.ts::"the issuer and the on-network route are mounted on the provider's serve" status: port covers: ["firecracker oidc issuer mounted"] - id: FC-ONNETWORK-ROUTE source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:32-45 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:807-863 behaviour: >- POST /v1/on-network: an OIDC bearer the issuer validates; acceptUri and acceptCid required (400); the accept-to-contract map keyed `#` must know it (404 UnknownAccept); a vm.onNetwork record {address, createdAt} is created and wrapped in a signed market.event {receipt, payload} that is submitted to the requester's submitEvent when the accept named one, firehose otherwise; a submit failure is logged and does not fail the guest. go_side: meets: not-applicable detail: >- Kept by compute-provider-kcp, which also applies status.market {onNetworkUri, address, reportedAt} (M4-ONNETWORK). As built in Go (plan 0034): GuestControlPlane POST /v1/events {acceptUri, acceptCid, record} replaces /v1/on-network; record.$type is vm.onNetwork (with kind), guest.publicKey or guest.signalWebRTC. parity_test: - ts/hono-compute-provider/test/compute_provider_kcp_onnetwork_test.ts status: port covers: - ON_NETWORK_NSID - EVENT_NSID - SUBMIT_EVENT_NSID - "guest.onNetwork: unknown accept ref" - "guest.onNetwork submitEvent failed" - "guest.onNetwork recorded with event" - "guest.onNetwork recorded (no event wrapper)" - "firecracker guest event routes mounted" - id: FC-RBAC-RECORDS source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:70-82 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:714-724 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:749-755 behaviour: >- After a boot, an rbac record for the guest's role and requester is created when a provisioner is configured; its ref is kept per guest and the record is deleted on destroy and on the guest's own exit, a failed delete swallowed. go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp creates the rbac record after Running and deletes it on destroy and when the guest is gone. parity_test: - TestParityTheRbacRecordIsCreatedOnProvisionAndDeletedOnRelease status: port - id: FC-PROVISION-RESULT source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:673 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:726-745 behaviour: >- provision returns providerId = the name and metadata {name, ip, mode firecracker, sshReady true, vmmPid, console, imageFingerprint}, logging provisioning microvm and microvm provisioned. sshReady is asserted, not measured: readiness is the guest's on-network report. go_side: meets: yes detail: >- M2-BACKPORT: compute-provider-kcp returns {name, ip, mode kcp, sshReady true, vmmPid, console, imageFingerprint} (plus hostAddress, imageRef, imageSha256) from ComputeVM status. parity_test: - TestParityTheProvisionResultMetadataEqualsTheProviders status: port covers: ["provisioning microvm", "microvm provisioned"] - id: FC-BOOT-RESULT-JSON source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:28-38 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:132-163 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:262-275 behaviour: >- Boot is complete when result.json appears in the work dir with non-empty name, guest_ip, socket, console, work_dir and positive vmm_pid, pasta_pid (fingerprint optional); polled every 300 ms; logged microvm_booted. go_side: meets: yes detail: >- nodeboot writes result.json today (cmd/socialweb-nodeboot/main.go:356-385). Superseded: there is no resident writer; Start records RuntimeRef {backend, unit, InvocationID, pid, startTime, hostBootID, vmmID} and Running is firecracker GET / {id == vmmID, state Running}. parity_test: - internal/vmruntime TestStartRecordsTheRuntimeRefAndPollsGetRootForRunning status: superseded-by-M2-VM-RUNTIME covers: [microvm_booted] - id: FC-BOOT-TIMEOUT source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:77 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:276-295 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:689-694 behaviour: >- A boot is bounded (180 s default): a runner that exits before reporting, a runner that vanished, or the deadline each fail the boot with the runner's last output, and the provider then stops the guest and removes its work dir before rethrowing (microvm_stop_failed when that fails). go_side: meets: yes detail: >- M2-BACKPORT: The boot is bounded (vmruntime Await, firecracker GET / Running) and the TypeScript side bounds the whole ComputeVM at 300 s; a failed boot is Failed with the VMM log and console tail, its unit stopped and its work dir removed before the status is written. parity_test: - internal/vmruntime TestCleanupRemovesTheWorkDirAndSocketForEveryTerminalOutcome status: port covers: [microvm_stop_failed] - id: FC-WORKDIR-TWO-WRITERS source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:210-222 behaviour: >- The work dir is written by the bidder (spec, result read) and by the runner as another uid (rootfs copy, seed, result), so it is chmod 0777. go_side: meets: no detail: >- Must not be inherited: the work dir is 0700 and one uid; the VMM's slot uid gets POSIX ACL entries on exactly its own files (M2-VM-RUNTIME). parity_test: - internal/vmruntime TestPrepareGrantsTheSlotUidItsOwnWorkFilesByACLAndNothingElse - internal/vmruntime TestTheVMMUidOwnsNoBidderFile status: superseded-by-M2-VM-RUNTIME - id: FC-RUNNER-CONTAINER source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:165-180 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:224-260 behaviour: >- Each guest boots inside a privileged docker container (pasta sandboxes itself and no capability subset sufficed) holding /dev/kvm and /dev/net/tun, the image dir mounted read-only and the work dir mounted read-write at the same paths. go_side: meets: no detail: >- Decided 2026-10-07/11: no docker; one transient unit per VM whose tree is pasta -> firecracker in a per-VM subuid user namespace, /dev/kvm by the kvm group (M2-VM-RUNTIME, M0-VM-USERNS). parity_test: - internal/vmruntime TestTheVMUnitsTreeIsPastaAndTheStandInOnly - internal/vmruntime TestTwoVMUnitsMapDisjointSubRanges - ts/hono-compute-provider/test/microvm_work_dir_test.ts::"container labels parse from key=value pairs and refuse anything else" status: superseded-by-M2-VM-RUNTIME covers: [createFirecrackerMicrovm, parseLabels] - id: FC-SEED-TRANSPORT source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:649 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:671 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:106-112 behaviour: >- user_data is a file in the work dir named by boot-spec.json; the runner (nodeboot) writes it into the rootfs copy as a NoCloud seed with debugfs. go_side: meets: no detail: >- Today cmd/socialweb-nodeboot/main.go:516-580 (writeSeed, debugfsRun). Decided 2026-10-12: a separate pure-Go NoCloud cidata drive; the rootfs is not edited. parity_test: - internal/vmruntime TestTheSeedIsASeparateCidataDriveAndTheRootfsIsNotEdited - internal/vmruntime TestPrepareWritesASeedDriveAndFcJsonAndNoHostToolIsExecd status: superseded-by-M2-VM-RUNTIME - id: FC-CONSOLE source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:34 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:193-197 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:766-771 behaviour: >- The console path is reported in the result; on exit the runner container's last log lines (tail 60, 4000 chars, 2000 logged) go into the bidder's log with microvm_exited, so tenant console bytes reach the host's logs. go_side: meets: no detail: >- Decided 2026-10-11: the console is a capped file in the work dir (StandardOutput=append:/console.log), rotated by the controller, deleted with the work dir; tenant data never enters the host journal. parity_test: - internal/vmruntime TestTheConsoleFileIsCappedByRotation status: superseded-by-M2-VM-RUNTIME - id: FC-GRACEFUL-STOP source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:303-318 behaviour: >- Stop is kill + rm of the runner container (SIGKILL): the guest is never asked to shut down. go_side: meets: no detail: >- Decided 2026-10-11: SendCtrlAltDel over the API socket, a measured grace (0.21 s on an idle guest, E8), then the unit stops with KillSignal=SIGKILL; aarch64 goes straight to SIGKILL. parity_test: - internal/vmruntime TestStopSendsCtrlAltDelThenWaitsTheGraceThenStopsTheUnit - internal/vmruntime TestStopOnAarch64GoesStraightToStopUnit status: superseded-by-M2-VM-RUNTIME - id: FC-GUEST-DEATH-WATCH source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:40-45 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:181-191 - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:331-355 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:705-712 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:761-789 behaviour: >- Live E8 defect a (hat-8, fixed by hono-compute-provider 96147f0): a guest that powered off was never noticed. Now each guest's runner is polled every 5 s; when it exited or was removed the provider drops the guest, releases its rbac record, stops it (removing the work dir) and fires GuestGone {id, reason, exitCode} to every listener, a failing listener isolated (microvm_exited, microvm_exit_handling_failed, guest_gone_listener_failed). A destroy cancels the watch and never reports. go_side: meets: yes detail: >- M2-BACKPORT: Not inherited: the VMM is pasta's only child, the controller consumes the unit watch and every Lost verdict reaches compute-provider-kcp's onGuestGone (vm.terminated); a guest that powers off resets through the system-shutdown hook compute-provider-kcp adds, so firecracker exits. parity_test: - internal/vmruntime TestAVMMThatExitsOnItsOwnEndsItsUnitAndLeavesNoProcess - internal/computevm TestAVMMExitObservedByWatchMakesTheComputeVMLostWithinOneEvent - ts/hono-compute-provider/test/firecracker_guest_lifecycle_test.ts::"a guest that dies on its own is reported gone, released and cleaned up" status: port covers: [microvm_exited, microvm_exit_handling_failed, guest_gone_listener_failed] - id: FC-WORKDIR-REMOVAL source: - ts/hono-compute-provider/lib/microvm-firecracker/mod.ts:303-330 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:757-759 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:791-805 behaviour: >- Live E8 defect b (32 dirs, 63 GB on hat-8; fixed by 96147f0): work dirs were never removed. Now stop removes the work dir only after the runner is confirmed gone (kept while it exists: removing it under a live guest deletes the disk it runs on), logging microvm_work_dir_removed; destroy of a well-named guest this process never booted still stops it and removes its dir, any other unknown name is logged microvm_destroy_unknown. go_side: meets: yes detail: >- M2-BACKPORT: Not inherited: the finalizer goes only after Cleanup removed the work dir and the API socket (checked by stat) for released, Lost and Failed VMs; orphan work dirs of this instance go after 10 minutes. parity_test: - internal/vmruntime TestCleanupRemovesTheWorkDirAndSocketForEveryTerminalOutcome - internal/computevm TestTheFinalizerStaysUntilTheWorkDirIsGoneForReleasedLostAndFailed - internal/computevm TestAWorkDirWithNoComputeVMAndNoUnitIsRemovedAfterTheGrace - ts/hono-compute-provider/test/firecracker_guest_lifecycle_test.ts::"a work directory is kept while its container still exists" status: port covers: [microvm_work_dir_removed, microvm_destroy_unknown] - id: FC-VM-TERMINATED source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:776 - ts/atproto-market/lib/market-bidder-compute/mod.ts:58-69 - ts/atproto-market/lib/market-bidder-compute/mod.ts:202-242 behaviour: >- A GuestGone reason `runner container exited|removed` is mapped by the bidder to vm.terminated (exited with code 0 -> guest_poweroff, else vmm_exited), published as a signed market.event to submitEvent or the firehose, with a tombstone so a late vm.delete answers 200. Live hat-8 2026-10-12T01:44Z: 5.6 s from guest poweroff to the requester's session end, no vm.delete sent. go_side: meets: yes detail: >- The string heuristic is replaced by explicit Lost reasons (HostRebooted -> host_reboot, GuestPoweroff -> guest_poweroff, UnitGone/UnitFailed/VMMExited -> vmm_exited, OOMKilled -> oom, IdentityMismatch -> identity_mismatch). GuestPoweroff is a VM whose VMM logged "Firecracker exiting successfully. exit_code=0": the guest powered off or reset (reboot=k, the i8042 reset), and firecracker exited by itself. The unit status cannot tell it apart: the unit watch reads a VMM killed by SIGKILL and a clean exit alike as inactive, result success, code 0 status 0. parity_test: - internal/computevm TestEveryLostVerdictCarriesTheTerminatedReason - ts/hono-compute-provider/test/compute_provider_kcp_test.ts::"a ComputeVM going Lost fires onGuestGone with its reason" status: superseded-by-M4-TERMINATION-EVENT - id: FC-OAUTH-SCOPE-NEW-COLLECTIONS source: - ts/typescript-helpers/lib/oauth-scope/mod.ts:33-34 - ts/typescript-helpers/lib/oauth-scope/mod.ts:139-150 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:833-847 behaviour: >- Live hat-8 2026-10-12T01:32:39Z: the bidder's vm.terminated publish failed with 403 ScopeMissingError because the collection was not in BIDDER_OAUTH_SCOPE. Every collection the bidder writes (onNetwork, market.event, wif.simple, terminated, and next vm.hostKeys) needs the role-set scope (typescript-helpers 5b071fe), the regenerated QR client scope deployed (did-key-associator 3cde452) and a re-sign-in; a token refresh keeps the old scopes. go_side: meets: not-applicable detail: >- Any record the Go-backed provider adds (vm.hostKeys first) follows the new-NSID checklist before a live run. parity_test: - ts/atproto-market/test/role_scope_coverage_test.ts - ts/did-key-associator/test/oauth_scope_drift_test.ts status: superseded-by-M1-PROTOCOL-CHECKLIST - id: FC-JSR-REGISTRY source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@1fd0c5234b55:614-629 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@1fd0c5234b55:710 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@1fd0c5234b55:719 - ts/hono-compute-provider/lib/compute-provider-local/mod.ts@1fd0c5234b55:614-632 - ts/hono-compute-provider/lib/compute-provider-digitalocean/mod.ts@1fd0c5234b55:151-162 - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:1257-1263 behaviour: >- The provider serves the workspace's unpublished packages from a host JSR registry on TCP port 0 (0.0.0.0) and points the tunnel subscriber at it with JSR_URL, because jsr.io answers 404 for them. Only the tunnel transport fetches packages at boot. go_side: meets: not-applicable detail: Removed, not ported (non-iroh guest transports stripped; the guest JSR registry goes with them). parity_test: - TestParityTheSeedNamesNoJsrUrlAndNoTunnelSubscriber - ts/hono-compute-provider/test/no_guest_jsr_registry_test.ts::"no guest JSR registry is served and no user_data names JSR_URL" status: removed covers: ["jsr registry mounted on TCP"] - id: FC-LEGACY-TUNNEL-MODULE source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:370-433 - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:1249-1255 - ts/atproto-market/lib/market-bidder-compute/mod.ts@d5b75305c3ba:85-117 behaviour: >- The tunnel module runs the deno tunnel subscriber that dials the relay dispatcher (--ingress-proxy-host) and writes the dispatcher FQDN to /run/guest-fqdn; guestOnNetworkUrl derives the report URL's port from that flag. go_side: meets: not-applicable detail: Removed, not ported. parity_test: - TestParityTheSeedNamesNoJsrUrlAndNoTunnelSubscriber - ts/atproto-market/test/no_legacy_guest_transport_test.ts::"the user_data registry holds no tunnel, fedproxy-ssh, fedproxy-web or web-terminal module" - ts/atproto-market/test/market_bidder_guest_onnetwork_url_test.ts::"a guest reports to the relay's own /v1/on-network" status: removed - id: FC-LEGACY-FEDPROXY-SSH source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:441-590 - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:1238-1247 behaviour: The fedproxy-ssh module (fedproxy client SSH transport through the relay). go_side: meets: not-applicable detail: Removed, not ported. parity_test: - ts/atproto-market/test/cloud_init_snapshot_test.ts::"the registry holds only iroh, secrets and k3s" status: removed - id: FC-LEGACY-FEDPROXY-WEB source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:593-647 behaviour: The fedproxy-web module (fedproxy client web ingress for a guest port). go_side: meets: not-applicable detail: Removed, not ported. parity_test: - ts/atproto-market/test/cloud_init_snapshot_test.ts::"the registry holds only iroh, secrets and k3s" status: removed - id: FC-LEGACY-WEB-TERMINAL source: - ts/atproto-market/lib/common/cloud-init-common/mod.ts@d5b75305c3ba:649-844 behaviour: The browser web-terminal module (a web terminal reached through fedproxy). go_side: meets: not-applicable detail: Removed, not ported. parity_test: - ts/atproto-market/test/cloud_init_snapshot_test.ts::"the registry holds only iroh, secrets and k3s" status: removed - id: FC-LEGACY-WEBSOCAT source: - ts/atproto-market/lib/requester-xrpc/mod.ts@d5b75305c3ba:602-631 - ts/atproto-market/lib/requester-xrpc/mod.ts@d5b75305c3ba:868-912 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts@1fd0c5234b55:327 behaviour: >- The requester reaches a relay-FQDN guest with ssh ProxyCommand `websocat --binary wss:///`, and the provisioning script kicks setup-websocat.service after its token exchange. go_side: meets: not-applicable detail: Removed, not ported; the requester's ssh path is iroh only, with known_hosts from vm.hostKeys (M4-HOST-KEYS). parity_test: - ts/atproto-market/test/vm_hostkeys_requester_test.ts - TestParityTheSeedNamesNoJsrUrlAndNoTunnelSubscriber - ts/atproto-market/test/no_legacy_guest_transport_test.ts::"the ssh ProxyCommand for a non-iroh address is refused instead of a websocket bridge" status: removed - id: FC-LEGACY-RELAY-FQDN-ADDRESS source: - ts/atproto-market/lib/abc/compute-request/mod.ts@d5b75305c3ba:264-270 - ts/atproto-market/lib/abc/compute-request/mod.ts:269-275 - ts/atproto-market/lib/compute-request-xrpc/mod.ts:410-413 behaviour: >- A guest address that is not an iroh ticket or an IP is taken as a relay FQDN the requester polls over wss; a single-label name is a hostname and skipped. go_side: meets: not-applicable detail: Removed, not ported; the only reportable guest address is iroh://, and a dotted name is refused (guestAddressKind "refused"). parity_test: - ts/atproto-market/test/no_legacy_guest_transport_test.ts::"a guest address that is a relay name is refused, only an iroh ticket is a guest address" status: removed - id: FC-LEGACY-REPORTER-TUNNEL-ORDERING source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@1fd0c5234b55:296-297 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@1fd0c5234b55:316-318 - ts/hono-compute-provider/lib/oidc-issuer-hono/mod.ts@1fd0c5234b55:389-390 behaviour: >- The reporters are ordered After= and Wants= tunnel-subscriber.service and dump its journal on giving up. go_side: meets: not-applicable detail: Removed, not ported; the reporter orders on iroh.service only (FC-IROH-LISTENER-ORDERING). parity_test: - TestParityTheSeedNamesNoJsrUrlAndNoTunnelSubscriber - ts/hono-compute-provider/test/no_guest_jsr_registry_test.ts::"the guest's on-network reporter waits on the iroh listener, never a tunnel subscriber" status: removed - id: FC-LEGACY-PASTA-SSH-FORWARD source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@964f1fd44fef:111-113 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts@964f1fd44fef:737 behaviour: >- pasta was told to forward 0.0.0.0/22 in the runner's namespace to the guest's sshd, for the host-side keyscan and direct TCP probes. go_side: meets: not-applicable detail: >- Removed, not ported: a guest is reached only over its own iroh endpoint (outbound dials, no inbound port) and host keys are guest-reported; pasta gets no -t forward. Removed from the TypeScript by 0007 M4-HOST-KEYS together with the host-side key scan it served (FC-HOSTKEY-SCAN); the OIDC token exchange proves the guest's host key through the reported key (FC-GUEST-HOSTKEYS-REPORT). parity_test: - internal/vmruntime TestTheVMUnitSpecPastaHasNoInboundForward - ts/hono-compute-provider/test/oidc_hostkeys_test.ts::"a firecracker guest's proven host keys reach the bidder under its provider id; pasta forwards no inbound port" status: removed - id: FC-ADOPT-AFTER-RESTART source: - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:131 - ts/hono-compute-provider/lib/compute-provider-firecracker/mod.ts:808-840 behaviour: >- After a bidder restart the provider adopts each guest its ContractStore names instead of booting it again: it reads the guest.json the provision wrote (with the host boot id), re-watches a runner that is still up and serves it to the OIDC issuer again; a guest whose runner is gone is reported gone (vmm_exited, or host_reboot when the boot id changed) so the bidder emits vm.terminated. Added by 0007 M4-CONTRACT-STORE-FILE (hono-compute-provider ac3a1f5) after live restarts leaked guests. go_side: meets: partial detail: >- The ComputeVM controller re-adopts from ComputeVM objects plus live unit identity (Running|Lost|Unknown) and compares the recorded boot id; the TypeScript store and guest.json are replaced by ComputeVM status. parity_test: - internal/computevm TestARunningComputeVMIsAdoptedAfterARestartNotBootedAgain - internal/computevm TestABootIDChangeMarksEveryComputeVMLostAsHostReboot status: superseded-by-M2-CONTROLLER covers: [readHostBootId, microvm_adopt_unknown, microvm_adopted, microvm_gone_at_adopt]