1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
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-<learning>
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@<commit>:lines cites the code as it was at that commit, the last one before the removal) | superseded-by-<plan item>"
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-<name>, the unit
socialweb-vm-<hid>-<name>, the work dir <state>/handles/<hid>/vms/<name>, 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 <workDir>/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:<port>, 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 `<ip> relay.localhost` and the
URL's host is a .localhost name, again with curl --resolve <host>:<port>:<ip>: 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 (`<ip> 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 <SECRETS_PREFIX>/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://<ticket> 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://<name>.localhost URL without a port is
rewritten to :<guestTlsPort>, /root/.curlrc gets `resolve = *:<port>:<hostAddress>`, and every
.localhost name gets a prepended runcmd line `<hostAddress> <name>` 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:<the guest IPv4's four octets in hex>, 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:<plc>,
oidc-sub:role:<role>]}; /v1/oidc/prove builds the subject actx:<actx>:plc:<plc>:role:<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 <issuer>/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:<team>:role:provisioning:nonce:<n>) and
composes provisioning-token.sh/.service: sign the token with the guest's ed25519 host key (namespace
prove-sshd), POST it to <issuer>/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 `<acceptUri>#<acceptCid>` 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:<work>/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://<fqdn>/`, 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://<ticket>, 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]