Skip to content

Main's nightly: a vanished disk is refused at ROOT's hold instead of panicking, and the reds #506 and #527 left - #535

Merged
Japabu merged 15 commits into
mainfrom
nightly-green2
Sep 27, 2026
Merged

Japabu merged 15 commits into
mainfrom
nightly-green2

Conversation

@Japabu

@Japabu Japabu commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

This branch works through main's nightly reds. It starts from #534's run (36285169430) and then re-measures after #527 on main's run at 1ce7183 (36290616312). #527 changed which tests were red. This PR fixes everything in both runs that has a fix here, and files the rest.

What changed, per decision

1. ROOT's hold: a disk that does not answer is named, never a panic (kernel/src/rootfs.rs, kernel/src/gpt.rs)

usb_transport_break has been red since #506. Its transport_gives_up boot leaves the gate's disk offline but still registered. hold_source asked gpt::claimable for ROOT's partition, and the offline disk's table read failed. claimable answered Unusable before it had looked at the boot stick, and hold_source panicked. A device crashed the kernel. The issue file named usb-port-gone as the cause. The measured cause is the transport_gives_up sub-boot: in the main boot the gone disk is never probed.

  • gpt::seek reads every disk and names the ones that did not answer, instead of stopping at the first. It answers its own Unnamed { Ambiguous, Unusable }, so hold_source has no arm for a claim error that no table read makes.
  • When a disk that answered carries ROOT's partition, its span is held. A silent disk that answers later either lacks the partition, or carries it again and makes every claim of it Ambiguous.
  • In every other case, ROOT's GUID is withheld from every claim through gpt::withhold, and claimable answers KernelDriven, the same answer a claim of the held span gets. The other cases are: on no disk that answered, carried twice, refused by its table, or no span a view can hold. Neither path refuses the boot.
  • claimable keeps its answers: a silent disk still makes it Unusable.
  • transport_gives_up asserts that the hold line's silent list is exactly the gate's disk, [16 + index], with the index read off usb-gate: disk N designated.
  • The withhold path has its own boot. The new actuator partclaim-root-withheld makes device block 0 of every NVMe disk refuse reads across rootfs::hold_source only, and then answer again. On InternalDisk the one disk carries ROOT, so the hold finds that disk silent and withholds the GUID. The third boot of partition_claim_gives_up then claims ROOT's GUID. The disk now answers for that GUID and nothing holds its span. The boot wants PermissionDenied (KernelDriven's word) and the kernel's partclaim: <ROOT> is where ROOT was read from, and the kernel withholds it.

2. The i8042 probe runs first in the device phase (kernel/src/main.rs)

screen_diag_boot has been red since #506, which moved storage behind init's spawn. That left the i8042 probe 86 rows above Boot: complete. The T14's panel holds 67 rows, so the line that answers "why is the keyboard dead" was off the flashed machine's screen. Before #506 it ran after storage: 57 rows up in run 36111884575. It now runs first in the device phase, which puts it after storage again, 16 rows up.

This move has a cost, which is recorded rather than fixed here. A panic in the storage phase now meets the i8042 as firmware left it, and the panic panel reads the key that retires its reset bound off port 0x60. The panics in question are NVMe or xHCI init, hold_source, and the DATA and FAT mounts. The ordering is the one that shipped before #506. Moving the probe back would bring back the screen_diag_boot red. The cost is filed as issues/panic-path/a-storage-phase-panic-reads-its-key-off-an-unconfigured-i8042.md.

3. fsync-budget-spent refuses each run's first attempt once (kernel/src/object/ops.rs, kernel/src/syscall/device.rs)

home_budget_refusal_retried and log_flush_retry timed out on #534's nightly. The actuator refused the first attempt of every until_answered run. logd flushes after every round it wrote a line in, and each refused-then-retried flush commits four kernel records. Those records are the next round's lines, so the next flush is refused too, and the loop never stops. Measured: 220 retried /log flushes in 2 s on the dev host. In CI the storm reached _0003.log by 28 s, and ===READY=== never reached the console.

  • The actuator now refuses once per run: a file's fsync keyed by its FileId, and a claimed partition's read, write and flush keyed by device, GUID and kind.
  • The key is a closure that only an actuator kernel calls. A shipping partition transfer no longer takes described's lock to build a key that nothing reads. allow(dead_code) on Run and allow(unused_variables) on until_answered stay: without boot-actuators the closure is passed and never called.
  • home_budget_refusal_retried judges that the storm is absent, over a fixed window. An absence has no event to wait on, and the site says so.
  • The loop is still a logd defect on a device whose every flush overruns its budget. It is filed as issues/filesystem/logd-flushes-the-records-its-own-refused-flush-made.md.

4. The stop drains the console queue before the last word (kernel/src/syscall/machine.rs, kernel/src/log/console.rs)

Since #527, a program's console line waits in log::console's queue for klogd, and the stop never drained that queue. A line queued just before the stop was written after Rebooting. by the power-off's flush_final, or not at all. This is issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md. It also has the shape of #534's metal_job_reboot and quiesce_wakes_on_the_last_exit reds.

  • quiesce() now drains every queued line under the wire right after quiesce::stop(), before Syncing filesystems....
  • The record backlog stays with klogd, so a deep backlog on a slow UART no longer runs ahead of the sync. As a result, at the stop a queued line can reach the wire ahead of records committed before it. Records and queued lines were already interleaved only chunk by chunk.
  • The new actuator console-queue-at-the-stop is the deterministic stimulus. It queues one line once every holder is stopped, and keeps klogd off the queue from the stop's claim on.
  • quiesce_stops_the_machine arms it and judges the line above the last word.
  • The issue stays open: a stop that did not stop every holder can still leave a line after the drain.

5. Two stop judges read the log as #527 left it (tests/common/power.rs, tests/common/usb.rs, src/bootlog.rs)

  • usb_reset_hands_devices_back: the judge took the text's last Rebooting.. Since Logging: records from every producer, and a kernel that waits on nobody #527 that is the next loader pass's log-tail: copy, and the copy is printed newest first. The judge now takes the last Rebooting. that is not a log-tail: line. It lives in bootlog::nothing_after_the_last_word, with a host #[test] over a crafted log-tail copy and a spawn after the real word.
  • usb_flush_optional: the judge wanted Shutting down. in /log. /log now ends at init's stop line, so the judge wants that line (bootlog::stopping_line).

6. updatecase's reboot asks init's power port (tests/updatecase/system.toml, tests/common/ssh.rs, tests/ssh-client-host)

Four update tests stalled on main's nightly. #527 moved every stop onto init's power port, but this config still gave toybox syscap = ["power"] and no connector. The host's reboot over ssh was "accepted" and the machine never went down. toybox now receives = ["power"], as in system.toml.

  • The client's fire now reports exited <n> when the program comes back. A reboot that ended the machine never comes back. ssh_fire refuses every answer except accepted, closed or silent, so a refused reboot fails its test by name instead of stalling it. metaltalk's own judge already accepts only those three.

Each red, and where it stands

red run status
usb_transport_break both fixed (1)
screen_diag_boot both fixed (2)
home_budget_refusal_retried #534's the storm fixed (3); green on 1ce7183
log_flush_retry #534's same storm (3); green on 1ce7183
quiesce_leaves_the_volume_whole #534's green on 1ce7183 and locally
quiesce_wakes_on_the_last_exit #534's green on 1ce7183; queue drain (4)
metal_job_reboot #534's green on 1ce7183; queue drain (4)
soundd_log_stall #534's green on 1ce7183 and locally
usb_reset_hands_devices_back 1ce7183 fixed (5)
usb_flush_optional 1ce7183 fixed (5)
update_boots_the_new_kernel, update_falls_back_from_a_dying_kernel, update_floor_is_the_images_own, update_refusals_boot_the_other_slot 1ce7183 fixed (6)
gate A, audio_tone_load.smp1 median 1ce7183 and this branch the instrument; runner samples unchanged across #527 (z under 3.09); filed, not fixed
wake_storm_cost, swap_crash_rolls_back, i8042_health_cadence 1ce7183 each red once wide and green alone twice; sighting and findings filed
portability-windows both the declared frontier, continue-on-error

Nothing was quarantined.

Gates, each the command's own exit code (dev host, TCG)

At a4f68c5, after the review. Each named QEMU test was run alone, one at a time. The full tier was not run here, and the orchestrator schedules it.

  • cargo test --test toyos-build -- --nightly partition_claim_gives_up (with the new withheld boot): EXIT=0.
  • --nightly usb_transport_break: EXIT=0. --nightly partition_claim (3): EXIT=0. --nightly quiesce (6): EXIT=0.
  • --nightly update_ (7): EXIT=0. home_budget_refusal_retried, log_flush_retry, metal_job, fsync: EXIT=0 each.
  • cargo test --lib (toyos-build, 397 tests): EXIT=0. cargo test --workspace --exclude toyos-build: EXIT=0.

Before the review, at 64c4636:

  • --nightly usb_transport_break: base EXIT=1 (the rootfs.rs:177 panic, wide and alone), fix EXIT=0 on three runs.
  • --nightly screen_diag_boot: base EXIT=1, fix EXIT=0. usb_flush_optional: base EXIT=1, fix EXIT=0.
  • --nightly update_: base update_boots_the_new_kernel EXIT=1 (stalled), fix EXIT=0 (7 of 7).
  • Neighbours i8042 (13), screen_ (22), pre_idle, keyboard, console_locale, console, reboot, shutdown, stop, usb_reset_hands_devices_back: EXIT=0.

Negative controls and oracles (high-risk: the ROOT hold, the stop path, a block-layer actuator)

  • ROOT withhold. Each deletion was applied as a checked patch onto a4f68c5 and shown to build, then --nightly partition_claim_gives_up was run and the tree was restored clean.
    • The if WITHHELD.lock().contains(&target) { ... } block deleted from gpt::claimable: MUT_EXIT=1, wide and alone. The guest said ROOT, withheld when its disk did not answer the boot's hold,: expected PermissionDenied, and the claim was minted.
    • crate::gpt::withhold(guid); in rootfs::withhold. The bare deletion does not build, because -D dead-code fires on gpt::withhold being unused. As if false { crate::gpt::withhold(guid); } it builds and gives MUT_EXIT=1, wide and alone, with the same guest line.
    • Oracle: the ABI's refusal word, read by a separate process (the guest), for a GUID that the host drew into the image with the gpt crate and that the kernel's parser did not choose.
  • ROOT hold, held span. The whole fix reverted onto 1ce7183 as a checked patch gives EXIT=1 with PANIC: ... cannot be held: Unusable. silent.push(0) in gpt::seek gives MUT_EXIT=1 on usb_transport_break, wide and alone: disks that did not answer: [0] against [17]. Oracle: the recorded real failure, main's nightly reds since The loader puts ROOT in memory and the kernel mounts it from there #506.
  • Stop drain. Negative control: the queue-only drain disabled as a checked patch that builds (if false { crate::log::console::drain_for_the_stop(); }, on a4f68c5), --nightly quiesce_stops_the_machine MUT_EXIT=1, tree restored clean. Alone: 1 line(s) reached the console after the boot's last word: console: a holder's line, queued once the stop had stopped every holder. Wide it also caught a real program line, {0.801 tid=6 pid=6 test-runner} quiesce-writer: 5 2. The same control on the earlier whole-backlog drain was EXIT=1 too. Oracle: the recorded red in the issue (618e68e, quiesce_stops_the_machine).
  • fsync-budget-spent once per run. Negative control: the kernel half reverted with the new judge kept gives EXIT=1 with 220 flush(es) retried in the 2 s after the guest's wide and 131 alone. Oracle: CI run 36285169430's console, the storm to _0003.log.
  • Judges (5). match None::<&&str> { in bootlog::nothing_after_the_last_word builds and gives cargo test --lib bootlog::tests::a_spawn_after exit 101, red on the spawn-after-the-word assertion. The old judge restored gives EXIT=1 on usb_reset_hands_devices_back.
  • reboot over ssh (6). updatecase put back to syscap = ["power"] as a checked patch gives --nightly update_boots_the_new_kernel MUT_EXIT=1 in 4 s, wide and alone: `reboot` over ssh answered "exited 1", so it did not end the machine. Before this change, the same config stalled the test to its timeout.

What I am unsure of

  • log_ring_keeps_the_owners_slots is red beside the other log_ guests on both arms: 3 of 14 on this branch and 2 of 13 on origin/main, in interleaved --nightly log_ runs. Each red is the owner-word race: a ring's owner is named only when logd reads init's registration. Filed as issues/kernel/a-log-rings-owner-is-named-only-when-logd-reads-its-registration.md. The closing fix is in toyos/src, which this brief may not touch.
  • Gate A reds when a KVM runner's median lands above the dev host's TCG sample. That is issues/audio/gate-a-has-no-runner-baseline.md, which needs a per-host baseline, a schema decision. On this lane harm was null: 0 dropouts, 0 underruns, 0 ceiling breaches. The same lane passed on this branch's nightly at dbf4ace and on the one before Logging: records from every producer, and a kernel that waits on nobody #527.
  • The stop's queue-only drain changes the drain the review read. The review's own remedy was to drain records only up to the queue's oldest line. I did not take it, because records and queued lines share no order key to cut at.

Round-2 review fixes (877b8c9)

  • rootfs::hold_source called withhold from four arms, and the only test reaching any of them was Ok(None). Restructured so the found partition and its view are computed as one Result<(found, view), &'static str> keyed on the refusal reason, and withhold(guid, why, &sought.silent) is called from the single Err arm of the outer match. The log text is byte-identical. The review's named mutation, replacing an arm's own return withhold(...) with return, no longer has a line to apply to: each arm now yields a plain Err(why) value, and the one remaining withhold call is the one the existing partition_claim_gives_up boot already exercises.
  • Deleted the false and branch-scoped lines from issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md: main has carried the console queue since Logging: records from every producer, and a kernel that waits on nobody #527, and the nightly-green2 branch name rots at the merge.
  • Aligned the put row in toyos_ssh's usage doc-comment with its neighbours.
  • cargo test --test toyos-build -- --nightly partition_claim_gives_up: EXIT=0. cargo test --workspace --exclude toyos-build: EXIT=0.

Nightly on this branch

Run 36297455432 on c271588 (this branch merged with main at 16d2e64). It predates the review round.

An earlier run on dbf4ace (36292135439) was cancelled once the merge landed. Before cancelling, guest (12) had shown the usb_reset_hands_devices_back judge red, which is fixed in 15408f8. Its audio (2) passed.

Nightly 36314576406

Run on a4f68c5, two commits before the head. None of the reds is this branch's. Named runs on the dev host (QEMU 11.1.1, TCG), one at a time: branch at 877b8c9, and main at 16d2e64 in its own worktree. CI ran QEMU 11.1.0.

red verdict branch main
xhci_flap (guest 1) main's defect, cause pinned EXIT=0 EXIT=0
lan_swap (guest 1) main's, a recorded compromise reached EXIT=0 (12 dials turned away) EXIT=0 (11)
handle_kill_policy (guest 8) main's, the same text on main's nightly EXIT=0 EXIT=0
log-gate: FAILED line (guest 8) not a red EXIT=0 EXIT=0
  • xhci_flap: issues/hardware/a-collapsed-replug-is-enumerated-only-when-another-port-event-arrives.md. When a collapsed replug's teardown completes, slot_gone leaves the port Settled with the device still in it and CSC unacknowledged. poll steps no port unless one is dirty or outstanding, so the device waits for an unrelated event. The same sentence was red on wt/toyos-lld at a55d62c, an ancestor of main (run 36287592139). On the dev host, a printed serial shows every other collapse stuck for about 700 ms until the next cycle's edges arrive. That makes the committed 4-cycle gate green by parity. Checked patches, each reverted in the same script:

    • CYCLES = 3 reds (EXIT=1).
    • CYCLES = 3 plus self.ports_dirty = true; after torn_down() is green (EXIT=0).
    • 4 cycles with the fix: all four collapses are seen as such, each re-enumerated 100 ms after its teardown (EXIT=0).

    QEMU's hcd-xhci.c is byte-identical at v11.1.0 and v11.1.1. The fix belongs to the driver's owner and is not in this PR. Recommend Test suite, first pass: a disabled list replaces the quarantine, sysret waits on its report, update_* reboot through the power connector #542's disabled list until it lands.

  • lan_swap: issues/build/lan-swap-redial-spent-its-ceiling-on-a-nightly-shard.md. The host's redial was turned away 64 times and gave up. The guest's own console completed the swap (in service at 6.141 s), and logd listened again at 1.176 s but admitted no reader after that. This is the ceiling issues/diagnostics/a-swaps-redial-asks-again-with-no-event-to-wait-on.md records, on the 82574 bench. swap_crash_rolls_back hit it on main's nightly at 1ce7183. This branch changes nothing on the swap path: Ssh::swap, metalswap, Stream::redial, logd, netd and init are untouched, and the fire change reaches only ssh_fire and Ssh::fire. Recommend Test suite, first pass: a disabled list replaces the quarantine, sysret waits on its report, update_* reboot through the power connector #542's disabled list.

  • handle_kill_policy: issues/kernel/handle-kill-policy-census-grew-one-sharedmem-on-two-nightlies.md. Main's nightly at 16d2e64 (run 36306830048, guest 8) failed the same way, byte for byte: [("SharedMem", 9, 10)], with identical first-census counts. It was green on the nightlies at 1ce7183 and at c271588, which already carries 16d2e64, so it is a rate. It is consistent with issues/kernel/deferred-release-outlives-its-syscall.md: the settle takes two readings 10 ms apart, and the red boot reports TLB shootdown waits of up to 13667 us. Which process held the tenth region is not shown. Recommend disabling it via Test suite, first pass: a disabled list replaces the quarantine, sysret waits on its report, update_* reboot through the power connector #542's list.

  • log-gate: FAILED: cpu7 seq 517 …: printed by log_reserve_window_negative, which passed. That test is the negative control that removes the reserve bracket, and this line is the refusal it owes ([log] unbracketed: …). Main's nightly printed the same shape (cpu0 seq 726) under the same passing test. Both named runs print it and pass.

  • audio (2): known, issues/audio/gate-a-has-no-runner-baseline.md, and not fixed here. portability-windows: the declared frontier.

No code changed after this run: 00ec52e adds only the three issue files. cargo run -- --known-red answers NO for all four names.

🤖 Generated with Claude Code

https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK

Japabu and others added 12 commits September 27, 2026 05:31
`usb_transport_break` has been red since #506: its `transport_gives_up`
boot leaves the gate's disk offline and still registered, `hold_source`
asked `gpt::claimable` for ROOT's partition, the offline disk's table read
failed, `claimable` answered `Unusable` before it had looked at the boot
stick, and `hold_source` panicked the boot on it. A device crashed the
kernel.

`gpt::seek` now reads every disk and names the ones that did not answer
instead of stopping at the first. `hold_source` holds ROOT's span when a
disk that answered carries it: a silent disk that answers later either
lacks it, or carries it again and makes every claim of it `Ambiguous`.
Every other outcome (on no disk that answered, carried twice, refused by
its table, no span a view can hold) withholds ROOT's GUID from every claim
through `gpt::withhold`, which `claimable` answers `KernelDriven`, the
same answer a claim of the held span gets. Neither path refuses the boot.
`claimable` keeps its answers: a silent disk still makes it `Unusable`.

`transport_gives_up` now asserts the hold line names the disk the gate
left offline.

Measured:
- fix: `cargo test --test toyos-build -- --nightly usb_transport_break`
  EXIT=0, three runs.
- negative control, the whole fix reverted onto 1ce7183 as a checked
  patch: EXIT=1, `PANIC: panicked at src/rootfs.rs:177:19: boot: the
  partition ROOT was read from, ..., cannot be held: Unusable`, wide and
  alone.
- mutation, a silent disk not recorded (`silent.clear()`): EXIT=1 on the
  new assertion, wide and alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
`screen_diag_boot` has been red since #506. #506 moved storage behind
init's spawn, which left the i8042 probe in the peripherals phase, before
NVMe, xHCI, the USB disks, ROOT's hold and the mounts. The first `i8042:`
line then sat 86 rows above `Boot: complete`. The diagnostic boot's panel
shows the log's tail, and the T14's panel holds 67 rows, so the line that
answers "why is the keyboard dead" was off the flashed machine's screen.
QEMU's 3-page log no longer showed it on its last page either.

Before #506 the probe ran after storage (run 36111884575's diag boot: 57
rows above the end). It now runs first in the device phase, which is
after storage again. Nothing between the two positions reads a key: no
task runs before `smp::set_ready`.

Measured:
- before: `cargo test --test toyos-build -- --nightly screen_diag_boot`
  EXIT=1, `"i8042:" is not on screen five seconds after the boot
  finished`, wide and alone.
- after: EXIT=0, the first `i8042:` line 16 rows above the end.
- the suites the probe's position can move, after: `--nightly i8042`
  EXIT=0 (13 tests), `screen_` EXIT=0 (22), `pre_idle` EXIT=0,
  `keyboard` EXIT=0, `console_locale` EXIT=0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…ry flush's

`home_budget_refusal_retried` and `log_flush_retry` timed out waiting for
`===READY===` on the nightly (run 36285169430). Both arm
`fsync-budget-spent`, which ran the first attempt of every
`until_answered` run under an operation already over. Since logd owns
`/log`, logd flushes after every round it wrote a line in, and each
refused-then-retried flush commits four kernel records. Those records are
the next round's lines, so the next flush is refused too. The storm never
ends. In CI it reached `_0003.log` by 28 s and `===READY===` never reached
the console.

The actuator now refuses once per run: a file's `SYS_FSYNC` keyed by its
`FileId`, and a claimed partition's read, write and flush keyed by device,
unique GUID and kind. That matches what the actuator stands in for: a
loaded host whose budget ran out once, not on every flush forever. Each of
its tests still has its own first attempt refused. The loop itself is a
logd defect on a device whose every flush overruns its budget. It is filed
as issues/filesystem/logd-flushes-the-records-its-own-refused-flush-made.md.

`home_budget_refusal_retried` now judges the storm's absence: no flush
retried in the 2 s after the guest's.

Measured:
- negative control, the kernel half reverted as a checked patch with the
  new judge kept: `cargo test --test toyos-build -- --nightly
  home_budget_refusal_retried` EXIT=1, `220 flush(es) retried in the 2 s
  after the guest's` wide and `131` alone.
- fix: EXIT=0.
- every arm of the actuator, after: `--nightly log_flush_retry` EXIT=0,
  `partition_claim` EXIT=0 (3), `fsync` EXIT=0 (6), `quiesce` EXIT=0 (5),
  `home_` EXIT=0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
…he last word

Since #527, a console holder's line (every program's, through logd) waits
in `log::console`'s queue for `klogd`. `klogd` takes a chunk of records and
then a chunk of the queue per hold of the wire. The stop never drained that
queue. A line queued just before the stop was written either after
`Rebooting.` by the power-off's `flush_final`, or not at all if the reset
came first. This is issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md.
It is also the shape of the nightly's `metal_job_reboot` and
`quiesce_wakes_on_the_last_exit` reds: `===READY===` never reached, or a
drain after `===READY===` with no kernel line in it, because the job's own
kernel lines had gone out ahead of the queued marker.

`quiesce()` now drains every record and queued line under the wire right
after `quiesce::stop()` has stopped every holder, before `Syncing
filesystems...`.

`console-queue-at-the-stop` is the deterministic stimulus. It queues one
line once every holder is stopped, and keeps `klogd` off the queue from the
stop's claim on. `quiesce_stops_the_machine` arms it and judges that line
above the last word.

Measured:
- negative control, the drain disabled as a checked patch that builds
  (`if false { ... }`): `cargo test --test toyos-build -- --nightly
  quiesce_stops_the_machine` EXIT=1, `1 line(s) reached the console after
  the boot's last word: console: a holder's line, queued once the stop had
  stopped every holder`, wide and alone.
- with the drain: EXIT=0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Both were red on main's nightly at 1ce7183 (run 36290616312), wide and
alone. Each judge's premise was the log before #527.

- `usb_reset_hands_devices_back`: `nothing_after_the_last_word` took the
  text's last `Rebooting.`. Since #527 the next loader pass prints the
  boot's newest records under `log-tail:`, newest first. The last
  `Rebooting.` in the text is that copy, and the older records under it
  read as spawns after the last word ("3 of 4 reset path(s) unmet: a
  process started after "Rebooting."... "| log-tail: ... spawn: ...").
  The judge now takes the last `Rebooting.` that is not a `log-tail:` line,
  and reads to the next loader pass as before.
- `usb_flush_optional`: it wanted `Shutting down.` in `/log`. Since #527
  `/log` ends at init's stop line, because the stop stops logd with every
  other thread, so the kernel's last word is on the console alone. The
  judge now wants init's stop line (`bootlog::stopping_line`).

Measured, `cargo test --test toyos-build -- --nightly <name>`:
- `usb_reset_hands_devices_back`: the old judge restored as a checked
  patch EXIT=1 (`3 of 4 reset path(s) unmet`), the new one EXIT=0.
- `usb_flush_optional`: before EXIT=1 (`the shutdown's last line never
  reached the file`, wide and alone), after EXIT=0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Four update tests stalled on main's nightly at 1ce7183 (run 36290616312),
wide and alone: `update_boots_the_new_kernel`,
`update_falls_back_from_a_dying_kernel`, `update_floor_is_the_images_own`
and `update_refusals_boot_the_other_slot`. #527 moved every stop onto
init's `power` port, so the `reboot` applet (`toyos::power::stop`) asks
init, which has logd make the log whole and then stops the machine.
`tests/updatecase/system.toml` still gave toybox `syscap = ["power"]` and
no `power` connector. The host's `reboot` over ssh was "accepted" and the
machine never went down.

toybox now receives `power`, as it does in `system.toml`. The `SysCap`
power right goes with the change, because no applet in this image uses it
any more.

Measured: `cargo test --test toyos-build -- --nightly
update_boots_the_new_kernel` before, EXIT=1 (STALLED after the reboot,
wide and alone). After, `--nightly update_` EXIT=0 (7 of 7).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
… not fix

Run 36290616312, read after #527:
- gate A's `audio_tone_load.smp1` median against the dev host's TCG sample:
  a new sighting added to issues/audio/gate-a-has-no-runner-baseline.md.
  It is the instrument. Harm was null, and the same lane passed on this
  branch's nightly and on the one before #527.
- `wake_storm_cost`: a third sighting added to its issue, green alone.
- `swap_crash_rolls_back` and `i8042_health_cadence`: each red once wide and
  green alone twice, filed as findings.
- `log_ring_keeps_the_owners_slots`, seen on the dev host: a ring's owner is
  named only when logd reads init's registration, so a child that floods
  first takes the owner's slots. Filed with the mechanism and the rates.
  The closing fix is in `toyos/src`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Five more interleaved rounds after the merge of 16d2e64: branch 0 of 5,
origin/main 2 of 5, each red the owner-word race.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
@Japabu Japabu changed the title Main's nightly green: ROOT's hold names a silent disk instead of panicking, and the rest of the reds Main's nightly: a vanished disk is refused at ROOT's hold instead of panicking, and the reds #506 and #527 left Sep 27, 2026
@Japabu
Japabu marked this pull request as ready for review September 27, 2026 10:22
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #535 at 64c4636 (reviewer.md, round 1)

Readiness: PR CI at 64c4636 is green (run 36312296089: abi-split success; host is skipped on pull requests by design). Nightly 36297455432 ran on c271588. The two commits after it touch only issues/: all 12 guest shards, tcg, host and audio (1) are green; audio (2) is red on gate A; portability-windows is the declared frontier. Every branch-named test carries base and fix exit codes in the body.

Net lines, git diff --shortstat origin/main...HEAD: +431/-56. By area: kernel +198/-43 (net +155), tests +74/-13 (net +61), issues +159.

BLOCKER

  • kernel/src/gpt.rs:73, kernel/src/rootfs.rs:422 — the withhold path has no test. It carries this change's isolation claim: the running slot's GUID "is withheld from every claim... no disk's answer, now or later, can hand it out". This is a security boundary, and nothing can turn red on it. Mutation that survives every test: delete the if WITHHELD.lock().contains(&target) { ... return Err(ClaimError::KernelDriven); } block in gpt::claimable, or delete crate::gpt::withhold(guid); in rootfs::withhold. A deterministic actuator is cheap:

    1. Add a boot actuator that calls page_cache::refuse_table_reads() immediately before rootfs::hold_source() in kernel_main. Add it to the page_cache.rs:27 registration condition.
    2. Right after the hold, store u64::MAX back into FAIL_BLOCK. Every disk is silent at the hold, so ROOT is withheld, and the disk answers afterwards.
    3. partition_claimant claims ROOT's GUID in that boot. The test requires KernelDriven's word and the partclaim: ... withholds it line.

    Under either mutation, the claim finds ROOT's span unheld on a disk that now answers, and it succeeds. The test must go red there.

NOTE

  • kernel/src/rootfs.rs:171 — never refusing the boot is right. ROOT runs from memory. After this change, a disk's silence costs one claim target and never the kernel. The only way to reach ROOT's bytes is a partition claim: PCI claims of the NVMe and xHCI controllers are KernelDriven, and GPT tables lie outside every partition. So withholding the GUID closes the same door the held span closes. That holds only once the BLOCKER's test exists.
  • kernel/src/rootfs.rs:181 — the panic! arm for Owned | KernelDriven | Exhausted is reachable only through the type. Give Sought::found its own error enum {Ambiguous, Unusable} so the arm cannot be written.
  • tests/common/usb.rs:2744 — the assertion only checks that the list is non-empty. It does not check that it names the disk the gate left offline. Surviving patch: in gpt::seek, Ok(Unread::Silent) => silent.push(DeviceId(0)). Compare the list with the gate's own device id.
  • kernel/src/main.rs:588 — the i8042 move does change something functional. panic_console::read_key polls port 0x60 through keyboard_controller::poll_byte, and a key press is what retires the panel's reset Bound. A panic in the storage phase now meets the controller as firmware left it: translation, scanning and the port-1 clock are all unconfigured. The panics in question include NVMe or xHCI init, hold_source, and the DATA or FAT mounts, and last week's rootfs.rs:177 panic was one of them. This order matches the pre-The loader puts ROOT in memory and the kernel mounts it from there #506 one (xHCI and NVMe first), so it has shipped before. Record it in issues/ or accept it knowingly. The keyboard IRQ pinning (BSP) and the order relative to xHCI's handoff are unchanged from pre-The loader puts ROOT in memory and the kernel mounts it from there #506.
  • kernel/src/object/ops.rs:250, kernel/src/syscall/device.rs:433,442, kernel/src/object/ops.rs:317 — Run::Claim(claim.partition_on(), ...) is evaluated in shipping kernels on every partition read, write and flush. Each evaluation takes described's lock plus reference.with, only to build a key that nothing but the actuator reads. Build it lazily under cfg(feature = "boot-actuators"), for example by passing &DeviceClaim or a closure. The allow(dead_code)/allow(unused_variables) attributes then go too. The actuator itself cannot be reached in a shipping build: the accessor is const fn false without boot-actuators, and assert_actuators_match_features refuses a shipping image that names one.
  • kernel/src/syscall/machine.rs:105 — the drain is bounded. WIRE is held only by klogd (a kernel task, never stopped) and by drains that stand in for it, and every write under it is bounded: the UART by THRE_SPIN_LIMIT, virtio by wait_used's ANSWERS. What changed is that the whole record backlog now goes on the wire before sync_all, not after it at flush_final. On a slow UART with a deep backlog, that time comes out of the sync, and the watchdog is already disarmed. Draining records only up to the queue's oldest line would keep the order without spending the sync's time.
  • tests/common/power.rs:2714 — nothing_after_the_last_word's red arm has no stimulus, as the body admits. It is a pure function over text, so a host #[test] with a crafted log-tail copy and a real spawn after the real word costs a few lines. Surviving patch: match None::<&&str> {.
  • tests/common/storage.rs:553 — judging an absence over a fixed 2 s window is a flat wait. The margin makes it acceptable: 220 and 131 retries against 0. Say that at the site or bound it on an event.
  • tests/common/update.rs:191,224 — the answer from ssh_fire(..., "reboot") is printed and never judged. A refused reboot (reboot: refused, exit 1) stalled four tests to their timeouts when it should have failed them by name. This is the exit status nobody reads behind §6.
  • Judges (§5): both premises are corrected, not hidden. "Shutting down." is written by quiesce() after quiesce::stop() has stopped logd, so since Logging: records from every producer, and a kernel that waits on nobody #527 it cannot be in /log, and init's stop line is the last line logd owes. Rebooting. under log-tail: is the next loader pass's newest-first copy. The updatecase change matches system.toml's receives = ["power"].
  • Audio (§6): the attribution is sound. No path in this diff touches audio timing in steady state. Its only effects outside boot and stop are one relaxed atomic load per klogd pass in actuator kernels, which is const false in shipping, and the partition-transfer key above, which soundd never takes. The runner's audio_tone_load.smp1 samples do not differ from the pre-Logging: records from every producer, and a kernel that waits on nobody #527 sample (z = 1.40, -1.20, 0.84). Main's own nightly at 16d2e64 passed audio (2) and at 1ce7183 failed it, with the same code minus this diff. The lane will keep flipping on main after this lands, because the gate's red is not quarantined anywhere.

REMOVE

  • PR body §2: "No task runs before smp::set_ready, so nothing reads a key between the old position and the new one" — false: the panic panel reads keys.
  • PR body §1: "transport_gives_up now asserts that the hold line names the disk the gate left offline" — it asserts only that the list is non-empty.
  • issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md: "The likely fix is ... No deterministic stimulus exists yet: ..." — contradicted by the file's own last section.
  • kernel/src/syscall/machine.rs:103: "Every console holder is stopped, so the queue only shrinks from here" — false on a stop that fell short, which the issue itself records.

SEND BACK

… cheap

The review sent #535 back because nothing tested the withhold path: deleting
the `WITHHELD` check in `gpt::claimable`, or `crate::gpt::withhold(guid)` in
`rootfs::withhold`, survived every test.

- `partclaim-root-withheld` (new actuator): device block 0 of every NVMe disk
  refuses reads across `rootfs::hold_source` alone, then answers again. On
  `InternalDisk` the one disk carries ROOT, so the hold finds it silent and
  withholds the GUID; `partition_claim_gives_up`'s third boot then claims
  ROOT's GUID, which the disk now answers for and nothing holds, and wants
  `PermissionDenied` (KernelDriven's word) plus the kernel's
  `partclaim: ... withholds it` line.
- `gpt::seek` answers its own `Unnamed { Ambiguous, Unusable }`, so
  `hold_source`'s match has no arm for a claim error no table read makes.
- `transport_gives_up` wants the hold line to name the gate's disk alone
  (`16 + index`, the index off `usb-gate: disk N designated`).
- `nothing_after_the_last_word` moves to `bootlog` with a host `#[test]`
  over a crafted log-tail copy and a spawn after the real word.
- `ssh_fire` refuses any answer but accepted/closed/silent; the client's
  `fire` reports `exited <n>` when the program came back, which a `reboot`
  that ended the machine never does.
- The run key `fsync-budget-spent` refuses once is a closure, evaluated only
  in actuator kernels: a shipping partition transfer no longer takes
  `described`'s lock to build a key nothing reads.
- The stop drains the console queue only; the record backlog stays `klogd`'s,
  so the sync is not spent behind a slow wire.
- The false "the queue only shrinks from here" comment and the issue's
  contradicted paragraph are deleted; the storage-phase i8042 ordering is
  filed as issues/panic-path/a-storage-phase-panic-reads-its-key-off-an-unconfigured-i8042.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
@Japabu

Japabu commented Sep 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #535 at a4f68c5 (reviewer.md, round 2; last reviewed head 64c4636)

Readiness: PR CI at a4f68c5 is green: run 36314453901 has abi-split success, and host is skipped on pull requests by design. The added withheld boot's EXIT=0 is in the body. The full tiers for this head (nightly 36314576406) are judged separately.

Net lines, git diff --shortstat origin/main...HEAD: 29 files, +656/-109.

  • Production (kernel/): +245/-55, net +190.
  • Tests: +220/-49, net +171. That is tests/ at +164/-49 plus src/bootlog.rs at +56, which is the judge moved out of tests/common/power.rs plus its host test.
  • Issues: +191/-5.

Since 64c4636: kernel +74/-39, tests and src +164/-54.

Round-1 BLOCKER

  • kernel/src/gpt.rs:388, kernel/src/rootfs.rs:209, the withhold path had no test: CLOSED. I re-ran both mutations as checked patches (git apply --check) onto a4f68c5 with cargo test --test toyos-build -- --nightly partition_claim_gives_up. Each built one kernel (boot-actuators,test-actuators), and each was red wide and alone on the new boot. The other two boots stayed green.
    • Deleting the if WITHHELD.lock().contains(&target) { … } block from gpt::claimable gave MUT1_EXIT=1. The guest said ROOT, withheld when its disk did not answer the boot's hold,: expected PermissionDenied, and the claim was minted.
    • Replacing crate::gpt::withhold(guid); in rootfs::withhold with if false { crate::gpt::withhold(guid); } built under -D dead-code and gave MUT2_EXIT=1 with the same guest line.
    • After each run I restored the tree with git checkout -- <file>. git status --short is empty at a4f68c5.
  • InternalDisk does exercise the claim, and the round-1 recipe could not have. page_cache::instrumented wraps NVMe only (kernel/src/main.rs:482). On the default profile ROOT rides the USB stick, so refusing NVMe block 0 there would have left ROOT held.
    • On InternalDisk the one NVMe carries ROOT.
    • The boot asserts is not held because it is on no disk that answered before the claim, so the refusal half is measured.
    • Mutation 1's minted claim shows the disk answered again after answer_table_reads, so the second half is measured too.
    • The decision in gpt::seek and rootfs::hold_source reads every disk in gpt::DISKS the same way, whatever the transport.

Round-1 NOTEs

  • Sought::found answers gpt::Unnamed, and the unreachable panic! arm is gone: closed.
  • transport_gives_up checks [16 + gate] exactly. The body's silent.push(0) control is red with [0] against [17]: closed.
  • nothing_after_the_last_word is in bootlog with a host test.
    • I checked by reading that the test's first case catches dropping the !line.contains(LOG_TAIL) filter.
    • Its third case catches dropping the take_while(LOADER_FIRST_LINE).
    • The body shows exit 101 for match None.
    • Closed.
  • ssh_fire refuses every answer except accepted, closed and silent. exited <n> cannot come from a stop that happened: init's stop (userland/init/src/main.rs:901-918) answers only a refusal, and toyos::power::stop returns only on MSG_REFUSED. Closed.
  • Run::Claim is built inside a closure that only staged_spent calls. A shipping partition transfer no longer takes described's lock. Closed.
  • The storm's absence over a fixed 2 s window is now stated at the site (tests/common/storage.rs:604): accepted.
  • The i8042 ordering is filed as issues/panic-path/a-storage-phase-panic-reads-its-key-off-an-unconfigured-i8042.md, with its evidence and an exit condition: closed.
  • The drain's stated cost is accepted (kernel/src/log/console.rs:114). A queued line can now reach the wire ahead of records committed before it, but only at the stop and only on the console.
    • The console never had a total order between the two streams. klogd interleaves up to CHUNK of each per hold, and each line carries its own timestamp.
    • /log is untouched.
    • The backlog is still written before the reset by the drain_inline under the last word, after the sync, where it was before this branch.
    • The alternative spends a slow UART's whole backlog ahead of the sync with the watchdog disarmed. No shared order key exists to cut at, so the "up to the oldest queued line" remedy is not available.

BLOCKER

None.

NOTE

  • kernel/src/rootfs.rs:176-178,198: withhold is called from four arms, and the new boot reaches only Ok(None). This patch survives every test: - Err(Unnamed::Ambiguous) => return withhold(guid, "it is carried twice", &sought.silent), / + Err(Unnamed::Ambiguous) => return,. The same holds for the Unusable arm and for the Err(()) arm.
    • Today the harm is small. While both copies stand, a claim is refused Ambiguous, and a departed disk stays in gpt::DISKS and reads silent, so the claim is refused Unusable.
    • Fix: have the match yield why, and call withhold(guid, why, &sought.silent) once. Then the one boot covers every arm.
  • tests/common/usb.rs:2751: 16 + gate restates USB_DEVICE_ID_BASE (kernel/src/drivers/usb_storage.rs:17). If the base moves, the test fails loudly rather than silently. Having usb-gate: disk N designated print the disk's DeviceId would leave one declaration.
  • tests/ssh-client-host/src/main.rs:25: the put row lost a column in the usage table.

REMOVE

  • issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md:26-27: "main has no queue — a holder wrote the wire itself — so this is the branch's." This is false: Logging: records from every producer, and a kernel that waits on nobody #527 put the queue on main (origin/main kernel/src/log/console.rs:186).
  • issues/kernel/a-holders-queued-line-can-reach-the-console-after-the-last-word.md:36: "On nightly-green2". The branch name rots at the merge.

READY

hold_source computed one Result per arm and called withhold from four
sites, so the only tested arm was Ok(None); the other three could drop
their withhold silently and nothing would notice. Fold the found
partition and its view into one Result<(found, view), &'static str>
keyed on why it failed, and call withhold once from the single Err
arm the existing test already exercises. The log text is unchanged.

Also: drop the false and the branch-scoped lines from the console
queue issue (main has had the queue since #527, and the branch name
rots at the merge), and align the `put` row in toyos_ssh's usage
table with its neighbours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Japabu added a commit that referenced this pull request Sep 27, 2026
Both are sent back by the review of #542, and the owner rejects the first.

- The red-streak gate (`src/ci.rs`'s `nightly-red` second step, `RED_STREAK`
  and everything that read the run history, and nightly.yml's `actions: read`)
  goes whole. A red is a red: a flaky test is disabled at once with its issue,
  so nothing waits two nights to find out that it reproduces.
- `tests/common/update.rs` waits on the machine under `qemu::GUEST_WEDGED`
  again, as every other wait does. `Spans`, `A_BOOT`, `A_STOP`, `A_REBOOT`,
  `A_DEATH`, `QemuInstance::boot_ceiling` and `take_pending` go. `A_STOP`
  was a timing verdict and not a hang bound. It left out the stop's sync,
  which only `block::DEADMAN` bounds (120 s), and it counted
  `quiesce::PARK`, which that module calls a budget and not a bound. On the
  one-wide KVM lane it came to a flat 12 s.
- `since_the_reboot` goes with them. It existed to show a refused `reboot`,
  and #535's `ssh_fire` now refuses one by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Nightly 36314576406 at a4f68c5 reddened lan_swap and xhci_flap in guest (1)
and handle_kill_policy in guest (8). None of them is this branch's. Each is
filed where its code lives, with the recommendation that it goes on #542's
disabled list when that lands.

- xhci_flap: a lost wake in main's driver. Slot_gone's Teardown arm leaves
  the port Settled with the device in it and CSC unacknowledged, and poll
  steps no port unless one is dirty or outstanding. The same sentence was red
  on wt/toyos-lld at a55d62c (run 36287592139). On the dev host, QEMU
  11.1.1 TCG, a printed serial shows every other collapse stuck about 700 ms
  until the next cycle's edges. The committed four-cycle gate is green by
  parity. At CYCLES = 3 it is red (EXIT=1), and adding
  `self.ports_dirty = true;` after torn_down() makes it green (EXIT=0). All
  measurement patches were applied checked and reverted, and the tree is clean.
- lan_swap: the redial ceiling that is already recorded
  (issues/diagnostics/a-swaps-redial-asks-again-with-no-event-to-wait-on.md),
  reached on the 82574 bench. The branch changes nothing on that path.
- handle_kill_policy: the same failure text, byte for byte, on main's nightly
  at 16d2e64. Consistent with the deferred release this branch does not touch.

Named runs, dev host, one at a time, branch 877b8c9 / main 16d2e64:
xhci_flap 0/0, lan_swap 0/0, handle_kill_policy 0/0,
log_reserve_window_negative 0/0. The shard-8 `log-gate: FAILED` line is that
negative control's owed refusal, printed by a passing test on both trees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
@Japabu
Japabu added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit a637f5c Sep 27, 2026
2 checks passed
Japabu added a commit that referenced this pull request Sep 27, 2026
One conflict, the ssh client's usage block: main's `fire` answer `exited <n>` and
this branch's `probe` line, both kept. `tests/updatecase/system.toml`
carries main's side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014iqcj4jDKpaiDX8B7CMvmK
Japabu added a commit that referenced this pull request Sep 27, 2026
page_cache.rs stays deleted. #535's `partclaim-root-withheld` moves off its
read-fault injector onto `block::unanswered`, which gains `answer`: the kernel
drives only USB disks here, so the actuator refuses block 0 of each of those
across `rootfs::hold_source` alone. main.rs keeps the branch's
`gpt::probe_usb_disks` and drops the kernel mounts main still carries; main's
move of `platform_devices` into the device phase merges as it is.

partclaim.rs: both arms. `root_withheld` boots the partclaim config off its USB
stick (`Profile::UsbDisk`) with the crafted disk beside it, because an NVMe boot
here puts ROOT on a disk the kernel does not drive and would withhold it with
no actuator armed at all.

power.rs: the branch's `OTHERS`, with main's `console-queue-at-the-stop`
actuator and its judge.

storage.rs: `home_budget_refusal_retried` stays deleted with the kernel's NVMe
fsync path. #535's claim that a refused flush is refused once and not on every
`logd` flush moves to `log_flush_retry`'s first boot, where `/log`'s flush is
fsd's claim flush: no `partclaim: a flush durable on attempt` line in the 2 s
after the guest's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu
Japabu deleted the nightly-green2 branch September 28, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant