Skip to content

Whole-repo review waves W1–W4 and the input-reader integration - #54

Merged
GCdePaula merged 227 commits into
mainfrom
claude/repo-code-review-103cc5
Oct 24, 2026
Merged

GCdePaula merged 227 commits into
mainfrom
claude/repo-code-review-103cc5

Conversation

@GCdePaula

@GCdePaula GCdePaula commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

What

Waves W1–W4 of the 2026-10-13 whole-repo review, plus the input-reader review branch (claude/input-reader-review-fda7ad, adapted), as one stack.

Plan, decisions, and what each wave landed: docs/review/2026-10-13-whole-repo-review.md.

  • W1 correctness and safety fixes.
  • W2 operator surface: error causes in logs, URL redaction, verdict-driven exit levels, the internal listener split, fee-oracle math.
  • W3a test and benchmark harvest: one test_support, storage fixtures in storage::test_helpers, the benchmark crate trimmed.
  • Input reader: any-error range splitting, refusal of logs without a position, read/submit provider split, safe-deployment gate, append-only safe_inputs.
  • W3b production refactors, CI and release hardening, Rust 1.99 and dependency refresh, reproducible guest builds.
  • W4 docs lean pass: one fact, one home; AGENTS.md 478 → 347 lines; the scheduler spec rewritten for porters; module headers within 4 lines (about −1.2k net lines).
  • Sepolia sequencer wallet (@stephenctw, aa0fc0f): re-signed with author kept, because main requires signed commits; same tree as aec6530.
  • Cartesi Machine v0.22.0 with its paired kernel, now released from cartesi/linux (v6.18.56-ctsi-1); Dave bindings at tag v3.0.0-alpha.6. The watchdog and testsi now check every cmio response reaches the machine (v0.22 drops a refused one silently), and the watchdog ranks a pending yield and a halt above an exhausted budget. Facts re-checked in docs/cartesi-machine.md.
  • Watchdog grounded on Dave's sling node and the v0.22 CLI epoch driver (dave@dbcca7d4, no epoch hash): yields read from the HTIF header as the step does, a pending input yield outranks halt, init refuses a bootstrap the tick cannot run in place. Reference and deliberate differences: host semantics, watchdog README.

Risk and compatibility

  • Guest template hash moves (Rust 1.99, alloy-core 1.7.3, guest refactors, W4 comment edits, Sepolia address, v0.22 emulator and kernel); behavior is pinned by tests/fixtures/consensus_vectors_v1.json. Hashes: guest image record. The next release's notes must state the new hash.
  • v0.22 loads nothing v0.21 stored: a new deployment's watchdog starts from a new state directory; keep the old one and a v0.21 watchdog release to replay its checkpoints and evidence (upgrades).
  • Guest builds were not reproducible: cross adds rust-src only on a later run, so the hash depended on build-cache history. just canonical cross-toolchain now runs before every guest build.
  • Operator-visible: watchdog init refuses an image whose files lack write permission; submitter log keys tx_nonce → wallet_nonce; a 3xx at setup's chain-id check exits 30; an undecodable batch from our own key aborts run (terminal); genesis-dump PermissionDenied exits 1; L1 error text is one line, capped, without the RPC URL.
  • CI: actions pinned by SHA and checked by scripts/verify-action-pins.sh; runs on PRs and pushes to main/v*; cargo-deny; runner pinned to ubuntu-24.04.
  • Release-only steps run first at the next tag: gh replaces action-gh-release, download-artifact 8 / upload-artifact 7, build-push-action 7. Worth a rehearsal with a throwaway tag on a fork.

Verification

  • cargo test --workspace, clippy -D warnings on every W3b commit, fmt, Lua tests and luacheck.
  • just canonical-test-guest, including the golden consensus cases, on cold-built images; two cold v0.22 builds agree.
  • Rollups e2e 49/49 (Rust and C hosts, duality), compare harness, docker smoke; watchdog e2e 26/26, including sling's bare-metal seam machines.
  • Each wave, the v0.22 bump and the watchdog grounding got a read-only adversarial review; surviving findings are folded into their commits.

Fees stay in log space. The batch-scoped nonce with a sequencer-filled
look-back would make replay state prunable and let the fee move to the
batch; record it with its blocking gap (after a recovery rollback, a
re-signed op does not exclude the original), and unblock Track 5
pending a sync with Bart.
Four server helpers turned a PermissionDenied bind into an eprintln and
None, and 42 callers returned early, so a sandbox that denies bind
reported the HTTP, WS and snapshot suites green without running them.
The helpers now panic on any bind error and return the runtime.
A manual dispatch checked out the dispatching branch but named every
artifact, image and the GitHub release after inputs.tag, and recorded
GITHUB_SHA as the commit; a missing tag was then created at the default
branch's head. A push now builds the pushed commit, and a dispatch builds
the named tag by its fully qualified ref, so a branch or SHA input
cannot stand in and a missing tag fails. Manifests and the image record
the checked-out commit.

Tag pushes were never marked pre-release, so each alpha became the
latest release; SemVer pre-release tags (with '-') now are.
download-deps verified the kernel against a .sha512 downloaded from the
same release, which catches transfer corruption but not a replaced
asset. The kernel is part of the canonical template hash, so it now
must match an in-tree pin, like every other downloaded input.
- The 429 test raced a spawned request for the lane queue's one slot and
  could hang if it lost; the server now starts with the queue full.
- The transient-poster-error test asserted after a fixed 250 ms and
  failed under load; it now waits for the first submission.
- Two e2e scenarios slept 7 s for the submitter's tick; they now wait
  until the submitter's tx is in Anvil's mempool.
- init deleted every checkpoint when config.json was missing, including
  an agreed head; it now removes only an interrupted init's bootstrap
  checkpoint and refuses otherwise.
- Idempotent init now validates an existing config.json the way tick
  does, instead of reporting success and failing the next tick.
- clear created a missing state directory and then reported that no
  divergence was latched; it now refuses a missing directory.

config.json joins the backup list, since init no longer regenerates it
over an agreed head.
With 0, the submitter's idle and transient arm slept for zero and re-ran
its frontier and nonce RPCs back to back. The flag now takes the same
range(1..) as the other poll intervals.
run and flush-mempool created the data dir and its lock file before
finding no database, then exited 30 with a message that named no path.
Only setup creates the directory now; run and flush refuse a missing one
before taking the lock, and SetupNotComplete names the data dir. An
unreadable or non-directory path stays an I/O error (exit 1).
TipAlreadyOpen was classified retry (exit 20), so the next boot would
find the Tip and admit. Under the process lock only another writer or
an inspection bug can produce it, and admitting would hide that; it now
refuses (exit 30), like its postcondition sibling TipMissingAfterOpen.
Aging-driven StaleDecision still retries.
The lane checks the shutdown flag only at the top of its loop. When
graceful HTTP shutdown dropped the queue's senders after that check, the
next turn returned ChannelClosed, which the drain treats as an error, so
a clean SIGTERM could exit 1. ChannelClosed with shutdown requested now
returns Ok. A lane test drives the race deterministically, and the
in-crate server tests assert a clean stop instead of tolerating
ChannelClosed.
fold_replay's comment claimed everything <= C was enforced, but only the
post-drain empty-fridge check caught out-of-range inputs, and a replay
batch above C leaves nothing in the fridge, so it was folded silently.
Seeds and replay inputs now assert their block against the stop block;
the fridge check stays as a backstop. The only caller already bounds its
inputs. Also drops a review codename from a comment.
The header reserves NOT_FOUND and INVALID_DUMP for dump lifecycle calls,
but the adapter mapped them to I/O errors from validate, execute and
drain too, where the lane treats I/O as retryable: an engine bug became
a restart loop instead of a terminal stop. The adapter now maps them to
Internal outside from_dump/create_dump, and the reference engine reports
them only from those two calls.
Every other client call is bounded, but subscribe() awaited the WS
handshake with no deadline and hung on a stalled server; the harness
wrapped it in its own timeout. It now fails with HandshakeTimeout after
the configured request timeout.
…ding

- AGENTS.md told every app to implement all four rejections; apps
  implement InvalidNonce and InsufficientFeeBalance, and the shared
  boundary alone produces InvalidMaxFee and NonceExhausted.
- The scheduler spec and README now say the canonical machine's
  compiled sequencer_address must equal the pinned submitter, which
  nothing checks at runtime.
- cockroach.md said the watermark is unavailable because the database
  is lost; recovery reads the fresh directory even when the old one
  survives.
- A lifecycle comment claimed the completion rule had production callers
  only on the run side.
The Track 3 plan still had Bart integrating scheduler replay over the
historical-L1 API. His client treats cockroach recoveries as out of
scope, and a planned shared recovery tool will produce client startup
state, so the cross-rebuild projection path receives bug fixes only.
The optimistic model describes a design that was not adopted. It stays
as a dev analysis tool behind check-optimistic, out of check-all.
- The testsi cycle-target entry is fixed in the vendored copy (relative
  target since 1283765); delete it.
- The RPC-URL entry also covers transport errors in the reader, poster,
  flusher and fee oracle, and the watchdog's persisted state.
- The SDK entry narrows to HTTPS for ingress; WSS waits for the new sync.
- The admission entry names where setup reaches the completion rule.
- The fee-observation entry records the per-poll stamp's purpose and cost.
The SDK, the harness and the sequencer's tests pinned reqwest 0.12, so the
build compiled two reqwest stacks. reqwest 0.13 puts RequestBuilder::query
behind a feature, which the SDK now enables.
testsi pinned thiserror 1 and two members pinned toml 0.8, each keeping
a second copy of those stacks in the build; both now use the workspace
version (toml moves into [workspace.dependencies]). The info.toml and
checkpoint.toml documents are flat integer tables, which toml 1.x reads
and writes the same way. The sequencer no longer declares
alloy-network-primitives, which nothing used.
The client rejected every https:// endpoint. It now accepts http:// and
https://, using reqwest's rustls feature, the TLS stack alloy already
builds, with the platform trust store. The WS feed stays plain ws://, so
an https client cannot subscribe; the subscribe() doc says so. A test
pins that an https request opens with a TLS ClientHello; certificate
rejection is recorded as a verification gap.
Thirteen error variants wrapped a #[source] but left it out of their
Display, and every log and abort sink prints Display only, so an exit
read 'snapshot at batch close failed' with no reason. They now end with
': {source}', the convention CommandError and BootstrapError already
use. Three feed and history messages also named the wrong operation.
The variant names what prepare() actually reads.
One helper names the path an I/O error happened at and keeps its kind.
The data-dir and dumps-dir creates, the database probes, the process
lock and the orphan sweep surfaced bare OS errors such as 'Not a
directory (os error 20)', which name no file. They now say which path
failed and keep the error kind, so exit codes do not change. The lock's
I/O variant carries its path.
…shes

The flusher watched each submitted no-op's hash for inclusion. When the
original batch tx won a slot instead, the no-op never landed and the
pass waited out its full 10-block timeout before re-checking. It now
waits, under the same timeout, until the account's Latest nonce reaches
the flush end, whichever tx consumed each slot. Errors still propagate;
the outer loop is unchanged.
Every failed command logged its exit line at ERROR, including expected
recovery (10) and retryable exits (20), which breaks ERROR-based
alerting. Terminal and unclassified exits keep ERROR; the others log at
WARN. The line now names the verdict and the supervisor action, and the
SIGABRT abort line says it is terminal.
The reader and the submitter logged every retried provider failure at
ERROR, once per poll during an L1 outage. They now log at WARN, and the
provider error names the failing call (eth_chainId,
eth_sendRawTransaction nonce=N, ...). A watermark write failure on the
submitter path logs as such instead of as a provider error. A test pins
that the mempool-conflict classifier still matches labelled messages.
The CANONICAL DIVERGENCE line named the nonce and input index only; an
operator then had to join tables to find the offending transaction and
compare the two sides. The page query now reads the transaction hash,
and the line carries it, the landed payload's hash and ours ("none" for
a foreign landing at a nonce we never sealed).
Module headers stay within four lines and name their owners instead of
restating them. protocol.rs says what the module holds and that its
acceptance predicate must agree with the canonical fold (I1); the
batch-submitter-as-parameter rationale stays once, on the
ProtocolTiming type doc, and leaves scheduler_accepts. fee.rs keeps the
encoding, the units it covers and the bit-for-bit porting rule, with
fee_to_linear as the conversion owner. SUGGESTED_MAX_FEE_SLACK says
what it is and how it differs from batch_policy.log_slack; the
FeeResponse type doc already says why wallets get the slack. The
wallet snapshot header names format.md as the layout owner and drops
an intra-doc link that did not resolve. The canonical-app shell and
the golden-case header lose their restated detail;
consensus_vectors.rs owns "never regenerated".
Three scheduler comments told how the code got here: inspect_state's
visibility "not pub(super)" because the harness is "now" a separate
crate, and two tests' notes on what used to be tested only partly. The
comments keep the present-tense reason and the property each test
pins. Folded into the guest batch so the template hash moves once.
Four doc comments, the schema comment on safe_accepted_batches, and
invariants I3 and I12 named Storage::append_safe_inputs, which became a
test seed in storage::test_helpers: rustdoc could not resolve the
links, and all of them named the wrong writer. They now name
append_ingested_safe_inputs_with_timestamp, which holds the
monotonicity asserts and maintains safe_accepted_batches in
production. The schema comment says a
parentless root carries the batch-tree anchor's nonce, 0 at genesis,
as its trigger enforces. register.md links the oracle outage policy at
its new threat-model subsection, and cartesi-machine.md drops a
'no longer exists' history note.
W4's guest commits trimmed comments in sequencer-core, app-core and
canonical-app and moved advance_expected_batch_nonce into the
submitter, so the template hash moved again. The cycle count to the
first yield is unchanged, as comment edits only shift the line numbers
rustc embeds in panic locations. Both moves ship in the same next
image, so the existing record gains a W4 row and new final values
instead of a second record: devnet 0xa0c7296d..., Sepolia
0x410cc0e1..., cold builds, consensus vectors byte-identical,
canonical-test-guest 15/15.
W4's progress line, what it delivered with the AGENTS.md before and
after counts, the porter spec, the threat-model restructure, the header
cap, the nonce-scan move and the README fixes. It also records what was
skipped as already fixed or deferred to the recovery tool, the FS1 and
B1 rows as decided and documented, N25 as done, and two items under
Not doing: a CI check for the size caps, and bounding archive downloads
per connection.
@GCdePaula GCdePaula changed the title Whole-repo review waves W1–W3b and the input-reader integration Whole-repo review waves W1–W4 and the input-reader integration Oct 14, 2026
@GCdePaula
GCdePaula requested a review from stephenctw October 15, 2026 10:17
@GCdePaula
GCdePaula marked this pull request as ready for review October 15, 2026 10:17
stephenctw
stephenctw previously approved these changes Oct 20, 2026

@stephenctw stephenctw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

stephenctw and others added 8 commits October 20, 2026 06:45
Pin the v0.22.0 emulator and the kernel its dependencies.lock pairs it
with, cartesi/linux v6.18.56-ctsi-1. machine-linux-image stops at
v0.21.0, so the kernel now downloads from cartesi/linux, its release
pin is renamed CARTESI_LINUX_RELEASE, and the pins check ties the
kernel filename to that tag. Guest tools stay at 0.18.0.

cartesi/linux tags name the kernel version, so no in-tree check can
pair the kernel with the emulator any more, as the old release-equals-
version check did. verify-toolchain-pins.sh --upstream, run before
each release, compares the kernel and guest tools tags with the
emulator's dependencies.lock and the kernel SHA-512 with the one its
release publishes.

v0.22 refuses machines stored by v0.21 (config archive version 8,
marchid 22), so every image and watchdog checkpoint is regenerated.
The template hash moves even for identical inputs: the machine
identifiers and the microarchitecture's state are part of the root
hash.

testsi moves to the v0.22 Rust bindings at Dave's v3.0.0-alpha.6 tag,
which pass the u32 cmio length the v0.22 header takes. The watchdog's
CLI oracle names the renamed input_file_index_* keys, since the v0.22
CLI rejects unknown --cmio-advance-state keys.
…dget

v0.22 reads a stopped machine's outcome in the order halt, manual
yield, then exhausted budget, so an RX_ACCEPTED or RX_REJECTED yield
on the budget's last cycle is an accept or a reject for the CLI and
Dave. The watchdog checked the budget first and would have latched a
dead canonical machine there instead, and refused a stored head in
that state. Check the flags first, in the emulator's order.

Reaching the budget end to end takes about 2^48 cycles, so a fake
cartesi module pins the order.
v0.22 makes a cmio response the machine cannot take a silent no-op:
with no yield pending, an advance at any yield but RX_ACCEPTED, or
data larger than the RX buffer. v0.21 refused those sends, and the
README's case for running in-process leaned on that refusal.

The watchdog never sends in those states, but it now asserts after
each send that iflags.Y cleared, which only delivery does, so a broken
precondition fails loud instead of reading the old yield as the
input's outcome.
v0.22 makes an advance-state response the machine cannot take a silent
no-op. testsi already checks for an rx_accepted yield before sending,
but an input larger than the 2 MiB RX buffer would be dropped: run
would return at the same yield, and advance_state would report an
accept with no outputs. Check after sending that iflags.Y cleared,
which only delivery does, and fail with InputNotDelivered otherwise.

The comments that named v0.21 now state the rules they rely on.
The page holds for the pinned emulator and asks for a re-check on
every bump. Against v0.22.0 three statements were false: a response
the machine cannot take is a silent no-op, not a refusal; the CLI
asserts an RX_ACCEPTED yield before an inspect query; and --no-revert
aborts at the first rejected input.

Correct those, and add what the page left out: how a version mismatch
is detected, why an emulator bump alone moves the template hash, the
outcome order at the budget limit, the SHARING_ALL refusals of
read-only and hard-linked files, the renamed --cmio-advance-state
keys, and --assert-rolling-template's outputs-root check. The kernel
now comes from cartesi/linux, and the storage measurements are
labeled as v0.21.0's.
v0.22 refuses to map a file for writing in place when it has no write
permission, and clone_stored hard-links read-only files, so a
bootstrap machine made read-only fails at the first tick: as root with
"is read-only and cannot be shared for writing", otherwise at open
with "Permission denied". v0.22 also loads nothing an older release
stored, and a clone reports that as an internal error, not as the
documented operator one.

Say to leave the bootstrap machine's permissions as --store sets them
(the PMAs file alone read-only), to keep a release per Cartesi
Machine version for replay and evidence, and to start a new
deployment's watchdog in a new state directory, keeping the old one
intact. List the messages in troubleshooting, and tell developers that
recipes reuse images stored by another emulator version.
The record keeps one ledger from the deployed baseline to the next
image, since every move since then ships in that image. It gains the
Cartesi Machine v0.22.0 and kernel 6.18.56-ctsi-1 row and new final
values: devnet 0xcd299178... (53967473 cycles), Sepolia 0xeaf87115...,
both from two agreeing cold builds. The devnet dapp and rootfs equal
W4's, so the template moved through the emulator and kernel alone; the
Sepolia dapp also carries the dedicated sequencer address from
aa0fc0f, which was not measured on its own. The review record's
progress line names the bump.
…ecutor

The input budget's seams and an advance the machine does not take were
pinned only against a fake cartesi module: the test guest cannot reach
them, since the budget lasts about 2^48 cycles per input. Build the
seam guest of sling's MachineStf tests (dave@dbcca7d4,
machine_stf.rs:930-963) in Lua instead: 4 KiB of RAM and five
instructions that clear fromhost, yield the tohost word in x19, then
yield x20 each time they resume. It needs no image and runs in
milliseconds.

The rows port sling's pending_input_yield_outranks_halt_and_budget
cases (:1072-1102) and its runner test (:1452-1487). An accept on the
budget's last cycle takes two inputs, at mcycle 4 and then 6, and ends
at the CLI's root hash. A budget that runs out before the yield, and an
exception, an unexpected reason or a rejection on the last cycle, each
make open refuse the machine. An advance after an exception trips the
delivery assert of 7bd5f56 on the real emulator.

The seam accepts declare 32 bytes of data, the outputs root libcmt
yields, because the CLI asserts that length on every accepted input.
…step does

status() classified a pending yield through receive_cmio_request,
which also reads the yield's data field and throws when its declared
length exceeds the 2 MiB TX buffer (e22 machine.cpp:1907-1911). A
guest that bypasses libcmt can declare such a length. The step and
sling read only iflags.Y and tohost's device, command and reason, so
they take the input, revert it or stop there, while the watchdog
raised an internal error on every tick, never passed that input and
latched nothing. It also ignored the device field, so a preset
template with Y set under another device read as accepted and then
tripped the delivery assert.

Read the header as sling's MachineStf::manual_yield_reason does
(dave@dbcca7d4, machine_stf.rs:120-139; sling since dave ecdc2694),
and as the emulator's own delivery check, isYieldedManualWith, does:
a manual RX_ACCEPTED, RX_REJECTED or TX_EXCEPTION yield under the
yield device, and an unexpected yield for anything else. The
exception's message is still read through receive_cmio_request, as
evidence only; when the emulator refuses it, the message names the
declared length instead. An unexpected yield's description now names
the device too.

libcmt bounds every length it declares, so no guest built on
sdk/guest reaches the oversized case, and only a preset image carries
Y under another device or command. The halt-first order is unchanged.

A unit test pins the classification against a fake that refuses an
oversized length as the emulator does. Five e2e rows build sling's
oversized and preset machines (machine_stf.rs:998-1037, 1109-1114).
The CLI aborts reading those yields, so the step's rule is their
oracle.
Every tick clones the head and loads the clone with SHARING_ALL to run
it in place, and v0.22 refuses to map a file without write permission
for writing. clone_stored keeps file modes, so a bootstrap machine made
read-only (chmod -R a-w, a read-only store, a re-packed tarball)
passed init, whose loads are all SHARING_NONE, and every idle tick,
including a genesis bootstrap's whole wait. The first tick with inputs
to compare then failed, and so did every later one, with no comparison
made. Sling refuses such a template at startup (dave@dbcca7d4,
storage/open.rs:149-165), and the CLI's --revert-mode=stored fails at
its startup SHARING_ALL load (e22 cartesi-machine.lua:2824-2828,
2966-2967).

Before it publishes the head, init now opens its clone of the
bootstrap machine through machine.open, the first tick's own step: a
clone, a SHARING_ALL load and the accepted-yield check. The emulator's
own rules decide (any write bit, the link count, running as root), so
there is no file list to keep in step with it. A failed load becomes
an operator error that names the bootstrap machine and suggests
chmod -R u+w, as sling's message does. The operator's bootstrap
directory is still loaded only with SHARING_NONE, and the probe costs
one more clone at init.

The README and the deployment guide say init checks this, and the
troubleshooting table gains the init message; the tick's message stays
for a head checkpoint that loses its write permission later.
status() checked iflags.H before iflags.Y, the emulator's run break
order, which the watchdog has followed since c0bebe8. The step reads
only the pending yield: send_cmio_response delivers an advance to any
RX_ACCEPTED yield, halted or not, and the uarch reset reverts any
RX_REJECTED one. Sling's MachineStf::terminal_fixed (dave@dbcca7d4,
machine_stf.rs:287-297) ranks the pending yield first, and its tests
deliver an input to a machine preset halted with an accept pending
(halted_yield_machine, :989; :1104, :1181). The watchdog refused such
a template at init.

Classify a pending yield first, then a halt, then an exhausted budget,
in the structure of terminal_fixed. Execution never sets both flags
(interpret stops while either is set, and delivery clears Y), so the
orders differ only on a preset image, which our recipes reject through
--assert-rolling-template. On such an image a range-source watchdog
now delivers input 0 and latches canonical_machine_dead there, the
state sling computes. An inspect source still fails init, because the
query cannot run on a halted machine; sling has no inspect path.

A halt reports an exit code only when tohost holds the halt device:
after delivery to a preset halt, tohost still holds the yield header,
whose data field is no exit code.

The CLI ends the epoch at boot on that image (cartesi-machine.lua:4577,
confirmed by running it), and Dave lists that among the CLI's named
exclusions, so the e2e row's oracle is the step's delivery on a fresh
load. The host-semantics doc now states the step's precedence and the
emulator's break order as two facts.
… driver

The host-semantics section named the CLI as the reference loop and said
Dave's PRT client implements the same rules, which any host must match
exactly. Dave treats the Solidity step as the authority and the release
CLI as no independent reference (computation-hash.md:217-219, 383-394),
and sling and the CLI differ at the corners the two previous commits
pinned.

State the step as the authority and name what the watchdog follows:
sling's MachineStf (feed, run_big, restore_rejected, terminal_fixed,
manual_yield_reason) at dave@dbcca7d4, and the v0.22.0 CLI's epoch
driver without a hash builder as the test oracle. Each corner sits at
the rule it qualifies. Neither reference checks iflags.Y after a send,
and both take the one undeliverable advance at an RX_ACCEPTED yield,
data over the 2 MiB RX buffer, as an unchanged accept, which the
alpha.10 InputBox's 65,536-byte cap rules out (Dave's
LeafTournamentGasFixture.sol:89-92, 444-450). Later inputs after a
fixed point reach sling as windows carrying the frozen state (dave
ea0318f3; epoch-lifecycle.md:70-78). Epochs change no machine state
(DaveConsensus instantiates the next tournament from the final state;
sling's roll_epoch), except past the 2^24-input epoch span, which is
accepted on cost (dimensioning.md:107-112). The Dave pin joins the
re-check rule.

The watchdog README points at that section and gains one bullet for
its deliberate differences from sling: the safe L1 head, the trusted
head, the fail-loud delivery assert (kept), stopping at a fixed point,
the trusted non-genesis start, no free-space floor, inspect, and the
epoch tail. It now says the watchdog runs the CLI's per-input loop
without its two per-input fsyncs (cartesi-machine.lua:3726, 3732), as
sling syncs only a batch's final boundary (advance.rs:10-15), and that
a scan stopped at a fixed point skips the InputBox count check.

The machine.lua comments follow: the module header names both
references; deliver() says the assert sees a no-op only where iflags.Y
was set, since callers send only at an accepted yield; the run loop
says why no other break reason occurs; and the executor no longer
claims to be exactly the CLI's stored mode.
A full disk while the working machine runs in place is no internal
error: the emulator's write through a shared mapping faults, the
in-process emulator installs no SIGBUS handler (only SIGWINCH and
SIGPIPE; e22 os.cpp:279, 645), and the tick dies before it writes
last_tick.json or status.prom. Sling keeps a 1 GiB floor for this
(machine_runner/mod.rs:70-80); the watchdog keeps none. The README's
failure classes now say a SIGBUS reaches only the stale-heartbeat
alert, the alert response names it, and the disk-usage note no longer
calls per-input clones nearly free, since every page a catch-up writes
takes real blocks.

The trusted-checkpoint bootstrap now says a Dave node seeds
mid-history only from the consensus's last settled root, which the
watchdog does not read, so a wrong checkpoint costs a false
state_mismatch. The troubleshooting row for an incomplete L1 view adds
a safe block replaced below the head when the error survives a
provider switch.

The version facts were overstated. Loading rejects another minor
release (archive version 8, marchid, and mimpid = major*1000+minor; e22
machine.cpp:196-208, machine-config.cpp:154-156), but a patch release
of the same minor loads stored machines and keeps the root hash by
design (CHANGELOG 0.15.2). The Cartesi Machine page and the upgrade
guide say so, and that the release the operator runs, not loading,
holds the watchdog to the template's exact version.
@GCdePaula
GCdePaula requested a review from stephenctw October 23, 2026 17:24

@stephenctw stephenctw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@GCdePaula
GCdePaula merged commit 3e92563 into main Oct 24, 2026
5 checks passed
@GCdePaula
GCdePaula deleted the claude/repo-code-review-103cc5 branch October 24, 2026 11:14
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.

2 participants