Skip to content

ci: run and schedule the wasm probe on main - #367

Merged
EnRaiha merged 20 commits into
mainfrom
fix/wasm-clock-and-nightly-probe
Sep 27, 2026
Merged

EnRaiha merged 20 commits into
mainfrom
fix/wasm-clock-and-nightly-probe

Conversation

@EnRaiha

@EnRaiha EnRaiha commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

ci: run and schedule the wasm probe on main

Why

ci.yml was label-gated with no schedule, so no run reported on main — the
last one was a manual dispatch on 2026-05-07. The only wasm job compiled three
decoders with cargo check, and a check can neither execute code nor, without
--all-targets, build a test target. Both gaps were structural: a
target-specific failure could reach main with every job green.

That was not hypothetical. Changing the wasm job to execute the shared crates
turned up five failures on the first run, two of them in production code:

  • nodedb-wal/src/writer/flush.rs — flush_buffer's only write was guarded
    #[cfg(unix)], which is false on wasm32-wasip1 (target_family = "wasm",
    target_os = "wasi"). On wasm the function advanced file_offset, cleared the
    buffer and returned Ok with zero bytes written — silent data loss on
    append() + sync(). The author's own comment at the site ("on other targets
    (wasm32) this would be dead code") is the assumption that hid it.
  • nodedb-query/src/msgpack_scan/reader/skip.rs — checked_advance(buf, offset, 5 + len)
    with len read from the input as a u32. On 32-bit, 5 + 0xffff_ffff panics
    with attempt to add with overflow instead of returning None — against the
    module's own documented contract, "Returns None on truncated/invalid data —
    never panics". Four such sites in total.
  • nodedb-codec — checked_capacity scaled a decoded element count in usize,
    so on 32-bit a hostile count overflowed to Corrupt where 64-bit reports
    ResourceLimit.
  • nodedb-vector — two test targets imported #[cfg(not(target_arch = "wasm32"))]
    modules, so they failed to compile rather than skipping.
  • nodedb-wal / nodedb-mem / nodedb-crdt / nodedb-client — the test targets
    could not build at all, because tokio = { features = ["full"] } is inherited
    from the workspace and tokio rejects that feature set on wasm.

What changed

This closes two issues whose fixes travel together: (a) the wall-clock defect in the shared
crates (#364), and (b) the missing execution probe that is the only thing which verifies (a)
(#365). The sections below are split along that line so each can be reviewed on its own.

(a) The clock defect — #364

  • nodedb-types/src/clock.rs (in the branch's original commits) — one target-split
    since_epoch(): js_sys on wasm32-unknown-unknown, std elsewhere. All eighteen production
    reads route through it and no shared crate reads SystemTime::now() directly any more.

(b) The probe, and what executing it exposed — #365

  • .github/workflows/ci.yml — a nightly schedule on main. The run-ci
    label gate still keeps upstream churn out of PR runs; this is the only thing
    that reports on main itself.
  • .github/workflows/test.yml — the wasm job now runs
    cargo test --target wasm32-wasip1 for all sixteen shared crates under
    wasmtime, with the runner's stack raised to match the native job's
    RUST_MIN_STACK. Compile-only became execute.
  • .github/workflows/static-gates.yml and test.yml — the new probe gate
    runs in both, so it is checked on every PR as well as in the suite.
  • scripts/ci/check_wasm_probe.py — asserts the shape rather than the prose:
    ci.yml carries a schedule, the wasm job executes tests rather than checking,
    names a runtime with a raised stack, and covers exactly the sixteen crates.
    --self-test feeds each check a configuration built to violate it, so the gate
    cannot rot silently.
  • nodedb-wal/src/writer/flush.rs — a wasi arm that seeks to file_offset
    and write_alls (wasi has no positional write; the segment is append-only, so
    this is equivalent). The native path is byte-for-byte unchanged.
  • nodedb-wal/src/segment/atomic_io.rs — fsync_directory is a documented
    no-op on wasm; wasi preview 1 has no directory fsync. The file fsync and the
    rename still happen; what is dropped is the directory-entry crash guarantee.
  • nodedb-query/.../msgpack_scan/reader/ — checked_advance_len performs
    both additions checked, and every length-prefixed tag routes through it.
  • nodedb-codec/src/bounds.rs — the element-size scaling moves to 64-bit
    arithmetic and reports ResourceLimit on every target.
  • nodedb-*/Cargo.toml — native-only dev-dependencies moved behind
    [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies], so the wasm
    test builds no longer inherit tokio/full. Native resolution is still exactly
    full.
  • nodedb-wal test scaffolding — a wasi-safe temp dir helper replaces 149
    tempfile::tempdir() call sites (wasi's std::env::temp_dir() is unimplemented!() inside std,
    before any syscall), and the four crates' native-only dev-dependencies move behind
    [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] so the test builds stop inheriting
    tokio/full, which tokio refuses on wasm. Native resolution is still exactly full.
  • nodedb/src/control/planner/sql_plan_convert/dml/vector_primary.rs — the unrelated
    nonminimal_bool rewrite, explained below.
  • docs/wasm.md — records the three WAL guarantees a WASI target cannot
    give, now that they are measured rather than assumed.

How it was verified

Executed locally against this branch head, because CI has not run yet.

step command exit log
red python3 scripts/ci/check_wasm_probe.py --root <base workflow files> 1 20260927T025538-wasm-probe-gate-base-base.log
green python3 scripts/ci/check_wasm_probe.py --self-test && python3 scripts/ci/check_wasm_probe.py && cargo fmt --all -- --check 0 20260927T035141-wasm-probe-gate-on-head-fix.log
wasm suite cargo test --profile ci --target wasm32-wasip1 -p <sixteen crates> -- --skip top1_recall 0 4535 passed, 17 ignored (all sixteen crates)
native suite cargo test --profile ci -p <sixteen crates> 0 1836 passed over the seven crates this branch edits
hygiene cargo fmt --check && probe gate --self-test && preflight 0 20260927T033523-fmt-gate-preflight-final.log
clippy cargo clippy --workspace --all-targets --all-features --profile ci -- -D warnings 0 —

The red arm is the honest form of "the absence is the report": the gate run
against origin/main's workflow files fails with no schedule trigger and
no wasm job.

Side project

These are the exclusions the wasm job needs in order to be green at all. Each is a test or a guarantee the target genuinely cannot have, so they travel with the job as a side project rather than being folded silently into it — the reasons are the reviewable part.

  • top1_recall_on_training_set is skipped by name. It asserts an approximate-recall floor of 0.70 on a ten-vector synthetic set; it measures 0.60 on this target and passes natively. The training path is deliberately seeded, so the gap is float behaviour on a different codegen path, and the dataset is too small for the assertion to survive it. Skipped, not weakened.
  • Eight nodedb-codec zstd encoder tests are ignored on wasm in-source by #[cfg_attr(target_arch = "wasm32", ignore = ...)]. The target has no zstd encoder by design (compress_native returns CompressFailed; it decodes with ruzstd and encodes with LZ4), so they assert a property it does not have. The decoder-only tests — hostile frames, truncation — still run on wasm.
  • Tests that need std::thread::spawn, the DWB recover_record stub, or the mmap readers are gated #[cfg(not(target_arch = "wasm32"))]. No assertion was weakened or deleted: whole-crate #[test] counts are identical to the base (wal 294, mem 86, crdt 168, client 115).
  • wasm32-unknown-unknown remains uncovered. cargo check for that target fails in a transitive getrandom 0.3.4, which needs the wasm_js feature plus a --cfg getrandom_wasm_js RUSTFLAGS entry before the target compiles at all. The js_sys clock arm this branch's clock fix adds is therefore still not compile-verified anywhere. A real hole, stated rather than papered over.
  • A DWB configured use_direct_io: false on wasm still reports Active and cannot be read back. Found by Review 2 and left alone, because fixing it is the same "write half without the read half" mistake this PR already reverted once: DwbMode::Direct correctly reports Degraded(WriteFailed) on wasm (pwrite_all returns Unsupported), but an explicitly Buffered-mode DWB takes a plain seek-and-write path, mirrors its slots, fsyncs them, and reports DwbProtection::Active — while recover_record is still Ok(None) on that target. The protection is claimed and unusable in exactly the way the Direct path is designed to avoid. It predates this branch (identical at the base), and it is not introduced here. recover_record's own rationale says slot recovery needs pread, but scan_max_seq already reads slot prefixes on wasm, so a seek-based read-back looks tractable — that is the follow-up, not this PR.

Two items here deserve their own change rather than a corner of this one: the DWB read-back above, and the six byte-identical db-prefix strippers elsewhere in the tree. Neither is touched by this PR.

Unrelated lint carried in the diff

cargo clippy --workspace --all-targets --all-features --profile ci -- -D warnings aborted on
clippy::nonminimal_bool at nodedb/src/control/planner/sql_plan_convert/dml/vector_primary.rs:161,
base-identical and outside everything else here. It is fixed in this PR anyway, by the one-line
is_none_or rewrite clippy itself suggests: leaving it red would have failed the lint job and hidden
every other change in the branch behind a failure that had nothing to do with them. The sibling PR
#368 needs the same line, so it is deliberately the branch tip and lands first.

Review

Review 2, fresh context, read-only: PASS, 0 blockers, on commit
434d5849d. It independently confirmed no test deleted or weakened (identical
per-crate #[test] counts), the native paths byte-for-byte unchanged, that the
wasm flush_buffer cannot return Ok without the bytes landing (write_all
handles short writes and errors before the offset advances), that the
Corrupt → ResourceLimit change has no dependent matcher and keeps an accurate
requested, and that all fifteen checked_advance_len header/len pairs match
their tag widths.

Closes

Closes #364 — the wall-clock defect: 18 guardless reads, one target-split helper, no shared crate
reads SystemTime::now() directly.

Closes #365 — the process gap: a scheduled probe on main, and a wasm job that executes the shared
crates instead of merely compiling them.

… main nightly

`wasm32-unknown-unknown` has no std clock: `SystemTime::now()` panics with
"time not implemented on this platform". Four sites in this crate read it
directly — the wire timestamp, both HLC variants, and `Timestamp::now` —
so any of them panics the moment a wasm build calls it.

`clock::since_epoch()` now owns the split: `js_sys` on
`wasm32-unknown-unknown`, std everywhere else (`wasm32-wasip1` included).
Callers keep their own conversion, saturation, and pre-epoch fallback, so
native behaviour is unchanged. The site list in the TODO at
`wire_time.rs` shrinks by four; the remaining callers are routed as they
move to the helper.

The CI workflow gains a nightly schedule: the `run-ci` label gate keeps
upstream churn out of PR runs, but nothing then reports a red `main`, so
the first run to notice it is the next labelled PR.

Evidence: `cargo check -p nodedb-types --profile ci` and the same for
`--target wasm32-wasip1` pass. The `wasm32-unknown-unknown` check does
NOT run — `getrandom` needs a backend there (pre-existing, why the wiki
uses wasip1 for wasm compile checks), so the `js-sys` arm is not
compile-verified in this commit.
…he helper

Fourteen more `SystemTime::now()` reads across the wasm-compiled crates
now go through `nodedb_types::clock::since_epoch()`: the CRDT validator's
auth-expiry check and HLC stamp, its four CascadeDefer/retry stamps, the
DLQ enqueue stamp, the `DEFAULT NOW()` renderer, the `NOW()`
preprocessor, and the array HLC. Each keeps its own conversion, fallback
and error mapping, so native behaviour is unchanged; none of them panics
on `wasm32-unknown-unknown` any more.

`nodedb-vector`'s HNSW build timer is a log field, not a correctness
stamp: it is gated off on `wasm32-unknown-unknown` and the log reports 0.

The DLQ field's doc comment names the helper instead of `SystemTime::now`.

Evidence: `cargo check -p nodedb-crdt -p nodedb-sql -p nodedb-array
-p nodedb-vector --profile ci` passes, and the same for `--target
wasm32-wasip1`; rustfmt is clean on all seven files. The
`wasm32-unknown-unknown` target still cannot be checked: `getrandom`
needs a backend there (pre-existing, why the wiki uses wasip1).
…es for wasm

`clock::since_epoch()` is the one place a clock read happens on a wasm
target, so it gets the two assertions callers rely on: the clock answers,
and two reads do not go backwards (HLC monotonicity, retry stamps, auth
expiry all assume it).

CI gains a wasm compile job over the sixteen shared crates. The server,
cluster and raft crates are not wasm targets; these are the ones
NodeDB-Lite compiles into its wasm build, so a wasm-incompatible
dependency or a target-specific cfg mistake fails here rather than in the
Lite repo — which is how the array HLC's `SystemTime::now()` reached a
consumer at all.

`wasm32-wasip1` on purpose: `wasm32-unknown-unknown` needs a getrandom
backend the workspace does not select, so that check would fail on the
random source instead of the code under change.

Evidence: `cargo test -p nodedb-types --profile ci --lib clock::` → 2
passed; `cargo check --profile ci --target wasm32-wasip1` over the same
sixteen crates → clean.
`clone.rs` carried a private `current_wall_ms()` that read
`SystemTime::now()` directly — the duplicate the `wire_time` TODO pointed
at. It keeps its own contract (a pre-epoch clock is an `Err`, never a
silent sentinel) but now reads through `clock::since_epoch()`, so the
target split applies on every path.

The TODO is replaced by the decision it was asking for: the funnel is the
clock module, not that one helper, because callers legitimately differ on
what a pre-epoch clock means — `0` plus a one-time log here, an error
there.

Evidence: `cargo check -p nodedb --profile ci` → finished clean.
Copilot AI lite review requested due to automatic review settings September 23, 2026 02:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@EnRaiha EnRaiha added the run-ci Opt this PR into the full test suite; re-add to force a re-run label Sep 24, 2026
…rt the resource limit

An element count read from a frame is multiplied by its element size before the
64 MiB ceiling is compared. On a 32-bit target that multiplication overflows
`usize` for counts the 64-bit path rejects cleanly, so the same hostile frame
came back as `Corrupt` there and `ResourceLimit` here.

`checked_capacity` now does the scaling in 64-bit arithmetic, which fixes every
caller at once; `validate_value_count` in delta and double-delta and the
FastLanes header parse route through it rather than repeating the multiplication.
…oder

Zstd encoding is deliberately absent on wasm32: `compress_native` returns
`CompressFailed` there, because the target decodes with ruzstd and encodes with
LZ4. Eight tests roundtrip through `encode` or drive `ZstdEncoder`, so they
assert a property this target does not have and unwrap on it.

They are ignored by name and reason on wasm32 rather than weakened. The
decoder-only tests -- the hostile-frame and truncation checks -- carry no such
attribute and still run there, because decoding is the half the target does
implement.
Four sites added a length read from the input to a header width in plain
`usize`: the STR32/BIN32/EXT32 arms of `skip_value`, and `start + len` in the
string read path. `5 + 0xffff_ffff` panics on a 32-bit target instead of
returning `None`, which the module's own contract forbids -- it documents
"Returns None on truncated/invalid data -- never panics".

`checked_advance_len` performs both additions checked, and `checked_advance`,
`read_u16_be`, `read_u32_be`, `read_u64_be`, `read_str`, `read_str_advance`,
`read_bin_advance`, the field index and the field lookup all route through it.
Two test targets imported items that `src/lib.rs` gates off on wasm32, so the
whole target failed to compile there rather than skipping anything: the
`segment_backing` test in the index state module, and the entire
`tests/vector_suite` integration target, which drives `VectorCollection`,
`mmap_segment` and their `libc::madvise` path.

Both are native-only for a real reason -- mmap is the only backing
implementation -- so they are gated rather than rewritten, and they still run in
full natively.
…silently regress

The wasm job compiled the sixteen shared crates and stopped there. A check cannot
observe a runtime panic, and without --all-targets it never builds a test target
at all, so dev-dependency and test-only wasm breakage stayed invisible -- which
is how a #[cfg(unix)] write guard that is false on wasi, and a header + len
addition that overflows a 32-bit usize, both reached the tree with a green job.

The job now executes the suites under wasmtime for all sixteen crates. Two
nodedb-codec tests cannot hold on the target and are handled where the reason
lives: the eight zstd encoder tests are ignored in-source by target cfg, and the
one approximate-recall floor is skipped by name with its measurement recorded.

scripts/ci/check_wasm_probe.py guards the shape rather than the prose: ci.yml
must carry a schedule, the wasm job must execute tests rather than check, name a
runtime with a raised stack, and cover exactly the sixteen crates. Its
--self-test feeds each check a configuration built to violate it, so the gate
cannot silently rot. Wired into both the static gates and the reusable suite.
`flush_buffer`'s only write sat behind `#[cfg(unix)]`, and wasm32-wasip1
is `target_family = "wasm"`, not unix — so on wasm the block compiled out
while the lines after it still advanced `file_offset`, cleared the buffer
and recorded the flush. `append()`/`sync()` returned `Ok` with
`metadata(path).len() == 0`: every record was silently dropped.

- Gate the pwrite loop on `all(unix, not(target_arch = "wasm32"))` and add
  a wasi arm that seeks to `file_offset` and `write_all`s (wasi preview1
  has no positional write, and the file is only ever appended to). The
  shared `file_offset` advance below already accounts for the bytes.
- `write_error` must exist for the wasm arm too, so widen it to
  `any(unix, target_arch = "wasm32")`; the ENOSPC branch stays unix-only
  (on wasi `raw_os_error()` is None, so the generic `WalError::Io` branch
  is taken).
- `fsync_directory` has the same shape: wasi has no directory fsync and
  `File::sync_all()` on one returns EBADF, so on wasm it is a documented
  no-op. The rename still succeeds; the directory-entry crash guarantee is
  what the target cannot provide.
- Pin the write path with an assertion: `metadata(path).len()` must equal
  `file_offset()` after `sync()`. The test previously only checked
  `file_offset() > 0`, which the no-op satisfied.
The shared crates are now compiled and executed for wasm32-wasip1 in CI, so the
limitations are measured rather than assumed. Three are worth stating plainly
because each is a guarantee the native WAL provides and this target cannot:
there is no directory fsync in WASI preview 1 (so a rename is not crash-durable),
no positional write (so the writer seeks and writes sequentially, which is
equivalent for an append-only log), and no double-write buffer (so DWB Direct
mode reports Unsupported rather than silently weakening).
The four crates' dev-dependencies inherited `tokio = { features = ["full"] }`
from the workspace entry, and tokio's wasm guard rejects
fs/io-std/net/process/rt-multi-thread/signal there, so their test targets
could not compile for wasm32-wasip1.

A narrower workspace alias cannot fix this: `fluxbench` (a dev-dependency of
wal and mem) declares its own normal `tokio = { features = ["full"] }`, and
Cargo unifies features across the whole invocation, so no member-level
feature list can subtract `full`. The suggested alias would also have tripped
the guard itself, since `rt-multi-thread` is one of the rejected features.

- wal/mem: move `tokio` and `fluxbench` to
  `[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]`. Neither is
  reachable from a wasm test — the crates have no tokio call sites, and
  fluxbench is used only by a bench, which `cargo test` does not build.
- crdt: same move for its unused tokio dev-dependency.
- client: `#[tokio::test]` in src/traits/core/trait_def.rs needs only
  `macros` + `rt`, so the dev-dependency becomes a direct narrow spec, and a
  native-only `tokio = { workspace = true }` restores the exact pre-split
  `full` resolution off wasm.
`std::env::temp_dir()` is `unimplemented!("not supported by WASI yet")` in
std's wasi backend, so `tempfile::tempdir()` aborts before it reaches the
filesystem — no preopen and no `TMPDIR` can help. With the write path fixed,
this was the next thing that stopped the wal test targets from running.

- Add a wasi-safe helper (`tempdir_in(".")` on wasm32, `tempfile::tempdir()`
  otherwise — native unchanged) in src/lib.rs for the unit tests, and one in
  tests/wal_suite/main.rs plus a small local one in tests/faultbox_single_emit.rs
  for the two integration crates.
- Swap the 149 call sites (85 src, 64 tests) to the helpers. No assertion
  changes; on native the helper is byte-for-byte the old call.

The wasm runner preopens the working directory (`wasmtime --dir=.`), so the
temp dirs land there on wasm.
Three wasi gaps remain after the write path and temp-dir helpers, and each one
is a target limitation rather than a code defect, so the tests that need the
missing capability are native-only while every assertion stays intact:

- `std::thread::spawn`/`scope` fails with `Unsupported` on wasm32, and wasm
  targets abort on panic. Gates the shared-writer append test (wal), the DWB
  parallel-writer stress case (wal tests) and the two concurrent-reserve tests
  (mem).
- `DoubleWriteBuffer::recover_record` is a deliberate `Ok(None)` stub on wasm32
  (slot recovery needs `pread`/O_DIRECT), so the tests that assert a record
  comes back from it are native-only: the DWB unit tests in
  double_write/buffer.rs and double_write/recover.rs, and the DWB splice-back
  case in the wal crash-recovery suite. The non-recovery half of
  `batch_deferred_writes_and_flush` and the skip accounting in
  `oversized_record_is_reported_as_skipped` still run on wasm.
- `nodedb_wal::mmap_reader` is gated off on wasm32, so encrypted_replay's mmap
  import and its one mmap test are gated together; the other four cases in that
  file stay in the wasm run.

The mem suite drops from 86 to 83 on wasm and the wal lib from 218 to 195 plus
the native-only suite cases; native test counts are unchanged.
…-free

Follow-up to the previous gate commit, two fixes:

- `reader_ciphertext_invariant` and `reader_preamble_and_continuity` both
  import `nodedb_wal::mmap_reader`, which is absent on wasm32, so the wasm
  test target did not compile. cases/mod.rs is held elsewhere and its `mod`
  gates were not in place, so each file carries `#![cfg(not(target_arch =
  "wasm32"))]` instead — same effect, the module never resolves the import.
  `durability_stress` is a single native-only thread case and is gated the
  same way, which also removes the imports it alone used.
- Gating individual tests left items used only by them unreferenced on wasm:
  `double_write/recover.rs`'s whole test module is now
  `#[cfg(all(test, not(target_arch = "wasm32")))]` (every test in it asserts
  `recover_record`), and `governor/reserve.rs` gates `Arc`, `DatabaseId` and
  `TenantId` alongside the thread test that uses them.

The wasm test build is now warning-free as well as green.
The two reader cases are gated inside their own files rather than on the `mod`
line here, because every test in them drives the wasm-gated `mmap_reader`
module. The comment now says that, and says why `encrypted_replay` is gated
the other way -- four of its five tests are wasm-safe and keep running there.
The entry named O_DIRECT and positional reads as the reason. The stronger reason
is that slot recovery needs the read side too, and recover_record is a stub on
this target, so a slot written there could never be read back. A seek-and-write
fallback would mirror the slot, let the writer report DwbProtection::Active, and
deliver no protection — which is worse than the Degraded(WriteFailed) state the
writer reports today while the append still succeeds.
The wasm arm is a deliberate refusal, not a gap: a seek-and-write fallback would
let the writer mirror the slot and report DwbProtection::Active while
recover_record remains a stub on that target, so the protection would be claimed
and unusable. The comment now says that, and says what the target reports
instead.
cargo clippy --workspace --all-targets --all-features --profile ci -- -D warnings
aborted on this expression, so the lint job was red on the whole workspace and
never reached the wasm job at all. Clippy's own suggested rewrite is exactly
this one, and the predicate is unchanged: is_some_and over a negated match
became is_none_or over the match. The file is otherwise untouched, and it is
unrelated to the wasm work — it is here because a red lint job would have hidden
everything else in this PR.
@EnRaiha EnRaiha changed the title types: read the wall clock through one target-split helper, and check the shared crates for wasm ci: run and schedule the wasm probe on main Sep 27, 2026
@EnRaiha
EnRaiha marked this pull request as draft September 27, 2026 07:00
@EnRaiha
EnRaiha marked this pull request as ready for review September 27, 2026 07:10
The workflow sets `RUSTFLAGS: -C link-arg=-fuse-ld=mold` for the native jobs,
where mold is a real speedup. The wasm job inherits it, and this target links
through the `wasm32-wasip1` spec with `rust-lld`, which rejects the argument:

    error: linking with `rust-lld` failed: exit status: 1
    = note: rust-lld: error: unknown argument: -fuse-ld=mold

Every test binary that has to link therefore failed. The compile-only job this
replaces never linked, so the flag was inert there and the failure only appeared
once this job started executing tests.

The job now clears `RUSTFLAGS` for itself. That has to be the un-prefixed
variable: cargo gives `RUSTFLAGS` precedence over
`CARGO_TARGET_WASM32_WASIP1_RUSTFLAGS`, verified by building a scratch crate with
both set and watching the mold argument still reach the linker, so a per-target
override does not work here. A job-level `env` shadows the workflow-level one,
so the native jobs keep mold.
@EnRaiha
EnRaiha merged commit fa37f55 into main Sep 27, 2026
14 checks passed

@farhan-syah farhan-syah 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.

Removed from main. Resubmit the clock fix alone, in a new PR.

This PR merged, then main was reset to 1ff3551. A merged PR cannot reopen, so the next round needs a new PR.

Scope for the resubmit

Keep:

  • nodedb-types/src/clock.rs, and the 18 call sites routed through since_epoch() (#364). This part is correct and wanted.

Drop from this repo:

  • The wasm test job in test.yml, the probe gate in static-gates.yml, and scripts/ci/check_wasm_probe.py. Lite is the only wasm consumer, so wasm CI belongs in the lite repo.
  • The nightly schedule in ci.yml. NodeDB runs no scheduled CI. Gate on PR and push only.
  • The WASI changes in nodedb-wal, nodedb-mem, nodedb-crdt and nodedb-client (test gating, tokio feature splits, docs/wasm.md). Lite targets wasm32-unknown-unknown and never runs these crates under WASI.
  • Unrelated fixes in nodedb-codec, nodedb-query, nodedb-vector tests, and vector_primary.rs. Each is a separate change. Open its own PR with its own reproduction.

The 32-bit overflow in msgpack_scan/reader/skip.rs breaks a documented "never panics" contract. Open it as its own PR with a test.

Coverage for the js-sys arm

The clock.rs unit tests run only on std targets. Nothing in this repo runs the js_sys arm. Prove it in lite: nodedb-lite-wasm/tests/array.rs already reaches the array HLC that panicked.

Findings

Inline below. One blocker, two should-fix.

Comment thread nodedb-types/src/clock.rs
/// has millisecond resolution.
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
pub fn since_epoch() -> Option<Duration> {
Some(Duration::from_millis(js_sys::Date::now() as u64))

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.

Blocker. The doc on line 17 says None when the clock reads before the epoch. This arm never returns None.

Date.now() returns a negative f64 for a pre-epoch clock. as u64 saturates it to 0, so the function returns Some(0). Callers that map None to an error then store a zero timestamp silently. nodedb-array's HLC now_ms() is one of them.

Return None when Date::now() is negative, matching the std arm.

Comment on lines +66 to +69
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
let elapsed_ms = start.elapsed().as_millis() as u64;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
let elapsed_ms = 0u64;

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.

Should-fix. This is dead code. nodedb-vector/src/lib.rs gates pub mod builder with #[cfg(not(target_arch = "wasm32"))], so this file never compiles on wasm32-unknown-unknown.

Remove the three cfg arms in this file and keep Instant::now() as it was.

Comment thread nodedb-types/Cargo.toml
Comment on lines +44 to +45
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
js-sys = "0.3"

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.

Should-fix. Every other dependency in this crate uses { workspace = true }. Declare js-sys in the root [workspace.dependencies] and reference it from here. Add a blank line before the [target...] table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-ci Opt this PR into the full test suite; re-add to force a re-run

Projects

None yet

3 participants