Skip to content

compat: W43 five-hour Node/Bun corpus sprint - #81

Merged
Sunrisepeak merged 195 commits into
rewrite_bun_in_mcppfrom
agent/corpus-coverage-w43
Aug 3, 2026
Merged

compat: W43 five-hour Node/Bun corpus sprint#81
Sunrisepeak merged 195 commits into
rewrite_bun_in_mcppfrom
agent/corpus-coverage-w43

Conversation

@speak-agent

Copy link
Copy Markdown
Collaborator

Goal

Run the first measured five-hour W43 sprint toward 100% of the audited runnable Node and Bun native corpora. This PR tracks issue #80 and implements the approved design and execution plan.

The sprint target is net +30 to +45 fully green files: Node +22 to +33 and Bun +8 to +12. The final 100% goal remains open until every audited runnable file is green.

Fresh same-binary baseline

Base: 163cb6d3fedb9d22cf069cb5f8e20fcb2bc76049

Baseline source head: 0cfcd5915947c0f709e482165e96aef9531bc337

Frozen binary SHA-256: 5e17080bb82a6c5ac46c56c8c66070409371e46e368bdf285304f42695f8d395

Corpus Full raw result Audited runnable result Gap
Node test/parallel 3,138 pass / 4,433 3,138 / 3,898 (80.50%) 760
Bun test/** 1,041 green / 1,902 1,041 / 1,804 (57.71%) 763
Combined 4,179 / 6,335 (65.97%) 4,179 / 5,702 (73.29%) 1,523

Node categories: pass 3,138; fail 660; skipped 535; timeout 97; OOM 3.

Bun categories: green 1,041; test-failure 704; all-skipped 73; timeout 44; blocked-external 19; no-tests 6; ahead-of-reference 4; load-error 4; OOM 4; crash 3.

Reproduction:

python3 tools/integration/node_corpus_runner.py --bin <git-common-dir>/w43/baseline/mbun --root "$PWD" --out target/integration/w43-node-baseline --jobs 4 --timeout 15
python3 tools/integration/bun_corpus_runner.py --bin <git-common-dir>/w43/baseline/mbun --root "$PWD" --cwd compat/bun --discover compat/bun/test --sample-per-group 100000 --out target/integration/w43-bun-baseline --jobs 4 --timeout 30

Exclusions are not passes. The runnable denominators are floors and cannot shrink through new skips.

Execution model

  • Five logical lanes per wave, executed in rolling batches because the session has three physical worker slots.
  • Every lane is three-stage: stable Red + pinned-upstream mapping; Green semantic translation; Refactor + serial proof.
  • compat/ is read-only; no assertion weakening.
  • Workers run only bounded focused tests. The coordinator owns builds, integration, full corpora, metrics, and milestone comments.
  • Full before→after evidence and final-head CI are required before the sprint verdict.

Current state

  • Issue-first design and detailed implementation plan
  • Fresh Node and Bun full baseline on one binary
  • Wave A five lanes
  • Wave B five lanes
  • Final same-binary full verification and CI audit

The baseline build initially exposed a worktree-local package-cache mismatch: the installed JSC package contained GCC 15.1 libstdc++.a while the pinned compiler was GCC 16.1. The generated dependency copy was refreshed from the recipe-required GCC 16.1 archive; no repository source or corpus input changed, and this recovery is not counted as compatibility progress.

- #80

Define the audited runnable-corpus denominator, five-hour quantitative gates, source-driven three-stage workflow, bounded parallel ownership, and PR evidence contract.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #80

Define exact baseline gates, Wave A worklists, rolling concurrency, integration checks, full verification, and PR milestones.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #80

Record same-binary Node and Bun full-corpus categories, provenance, and the audited runnable gap before source implementation.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
@speak-agent

Copy link
Copy Markdown
Collaborator Author

Checkpoint 0 — fresh full baseline, before implementation

  • Base: 163cb6d3fedb9d22cf069cb5f8e20fcb2bc76049
  • Current head: 7d137ccc3815d7bb4c55b58ade3c9c7510cedc78
  • Frozen binary SHA-256: 5e17080bb82a6c5ac46c56c8c66070409371e46e368bdf285304f42695f8d395
  • Measurement elapsed: Node 917.4 s; Bun 1085.4 s; same binary, sequential full runs

Node full corpus

pass fail skipped timeout OOM total
3,138 660 535 97 3 4,433

Raw 3,138 / 4,433 = 70.79%; audited runnable 3,138 / 3,898 = 80.50%; gap 760.

Bun full corpus

green test-failure all-skipped timeout blocked-external no-tests ahead load-error OOM crash total
1,041 704 73 44 19 6 4 4 4 3 1,902

Raw 1,041 / 1,902 = 54.73%; audited runnable 1,041 / 1,804 = 57.71%; gap 763. Test-level: 33,861 passed / 13,452 failed / 50,163 ran / 467,237 expects. Resource profile: 4G memory, 512 tasks.

Combined audited runnable: 4,179 / 5,702 = 73.29%; exact remaining gap 1,523.

Wave A fixed lane targets: A1 Node zlib/Buffer +3–5; A2 Node assert +2–4; A3 Node permission +2–4; A4 Bun N-API +3–5; A5 Bun test runner +3–5. Five logical lanes run in rolling batches on three physical worker slots.

No W43 source implementation has started. Current CI state for this checkpoint: pending/not yet audited. The final 100% target remains open; the first sprint target is net +30 to +45 with zero stable-green regressions and no timeout/OOM increase.

Build provenance note: the first build was rejected because a worktree-local installed JSC package cached GCC15 libstdc++.a under a GCC16 build. Refreshing only that generated dependency to the recipe-required GCC16 archive produced the frozen binary; this is environment recovery, not compatibility progress.

- #80

Record five disjoint baseline-confirmed lists, fixed targets, struck checks, and source ownership before worker dispatch.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #83
- #80

Enforce Node's published string length before JSC allocation and capture the buffer output limit at the observable zlib require edge.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #82
- #80

Install the Node assert Assert constructor with strict aliases, instance diff
propagation, and partial deep assertion dispatch. Add a JSC runtime probe.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #84
- #80

Keep Node filesystem adapters from masking or deferring permission errors, and check mkdtemp permissions against the six-character template resource at the native boundary.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #83
- #80

Pass the captured zlib output cap into native decoders, preserve one-shot repeat coercion, and surface registry hook failures.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #82
- #80

Remove the incomplete partial-deep implementation and discard unsupported
skipPrototype state. Preserve instance receivers, and only attach diff metadata
to AssertionError instances. Expand the JSC runtime probes for retained behavior.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #83

- #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #82

- #80

Result: safe partial +1; target +2 to +4 remains open.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>

# Conflicts:
#	modules/jsc/tests/test_node_compat_bridges.cpp
- #83
- #80

Initialize the standalone JSC member test in Node dialect before its first runtime evaluation, matching the compat/node CLI path.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #83

- #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
Move permission translation behind the public fs validators and preserve pinned Node's immediate callback rejection order for access and ownership calls. Add a real JSC runtime regression covering invalid arguments, path-like getters, denial timing, and the synchronous-denial APIs.\n\n- #84\n- #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
Invoke immediate permission-denial callbacks for their side effects while preserving the wrapped fs API return value. Extend the runtime regression to require undefined from access, chown, and lchown when callbacks return a value.

Refs #84 and #80.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #84

- #80

Coordinator-authorized boundary: narrow bootstrap fs validation-to-dispatch seam.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
Replace the one-shot due-only timer drain with the runtime's bounded event-loop pump. This waits for the host timer's real 1ms deadline without an arbitrary sleep and makes the composite callback assertion deterministic.\n\nRefs: #85\nRefs: #80\n\nSigned-off-by: Sunrisepeak <speakshen@163.com>\nCo-authored-by: Codex (GPT-5) <>
Record the issue references and attribution for the preceding timer-deadline test correction.

Refs: #85
Refs: #80

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #85

- #80

Measured as a pre-existing one-millisecond timer race across all lane trees.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #80

- #85

Record seven focused Node gains, composed member and impact gates, the frozen Wave-A binary, honest lane misses, and batch-two dispatch.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
@speak-agent

Copy link
Copy Markdown
Collaborator Author

W43 interim checkpoint — Wave A batch 1 composed

Head: 6f93696 (product/test integration checkpoint 7e84e13)
Wave-A binary SHA-256: 4efb3448ebd7b2e34f0eda1e15dff74201aaae25ed7ce9a01ca607f21ca4b0b6

Real focused results against the frozen baseline:

  • A1 Node zlib/Buffer: 0/5 -> 4/5 (+4; target +3 to +5)
  • A2 Node assert: 0/8 -> 1/8 (+1; target +2 to +4, explicitly missed; incomplete comparator removed)
  • A3 Node permission: 0/10 -> 2/10 (+2; target +2 to +4)
  • Batch total: 0/23 -> 7/23; all seven new greens passed serial bounded reruns

Coordinator gates:

  • fresh no-cache build: pass
  • JSC member suite: 29/29
  • permission member suite: 150/150 checks
  • focused corpus diffs: no green-to-non-green move
  • Node impact gate: 653 files, no regression
  • Bun impact gate: 180 files, no regression
  • compat corpus gitlinks unchanged

The initial full JSC gate exposed a pre-existing one-millisecond async-hooks test race. Issue #85 replaced its one-shot due-only timer drain with the existing bounded event-loop pump; measured exact test is now 10/10 without weakening assertions.

This is focused batch-1 evidence, not a post-Wave-A full Node/Bun corpus result. A4 N-API finalizer (+3 target) and A5 test-runner settlement/mock (+3 combined target, recorded as two distinct mechanisms) are now running in parallel from the frozen composed binary.

- #87
- #80

Track callback and returned-Promise completion independently so a synchronous done() cannot hide a later rejection. Add a real JSC runner regression for the dual-completion path.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
- #88
- #80

Retry late mock cache lookups through a canonical package path without loading unknown modules, then fan replacements through the existing named-import subscriptions. Add a symlinked-package member regression.

Signed-off-by: Sunrisepeak <speakshen@163.com>
Co-authored-by: Codex (GPT-5) <>
Run queued N-API finalizers while holding the JSC API lock, start each callback from a clean exception boundary, and transfer callback errors through the existing process uncaught path.

Refs: #86

Refs: #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
Arm the shared fatal channel directly when the configurable uncaught dispatcher is absent or throws, stop the finalizer drain after fallback, and dispatch a dual-stored exception only once.

Refs: #86

Refs: #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
Honor the shared uncaught dispatcher boolean result so a fatal false return stops the remaining finalizer batch without overwriting the fatal state. Cover two queued finalizers behaviorally.

Refs: #86

Refs: #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #86

- #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #87

- #88

- #80

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
- #80

Record the reviewed five-lane same-binary +13 focused result, serial and impact gates, remaining reds, and scope boundary before Wave B.

Signed-off-by: Sunrisepeak <speakshen@163.com>

Co-authored-by: Codex (GPT-5) <>
speak-agent and others added 12 commits August 3, 2026 06:15
`async function f(){}` in statement position is an AsyncFunctionDeclaration,
not an expression statement. parse_statement_ had no case for it, so it fell
through to the expression path and kept parsing postfix: a following
statement-leading `[` became a computed member access, which then rejected the
legal trailing comma in `[a, b,]` with "Unexpected ]". That took
test-runner-global-setup-teardown.mjs down before a single test ran.
node keeps four builtins requirable only WITH the `node:` scheme
(lib/internal/bootstrap/realm.js schemelessBlockList): test, test/reporters,
sqlite, quic. mbun registered each under both keys, so a bare
`require("test")" returned the builtin runner and a userland
node_modules/test could never shadow it. builtin_module now misses on the bare
form so it falls through to the ordinary resolver; the `node:` spec is a
different string and is unaffected. The BuiltinModule shim in
node_process_extra already modelled the same list for module.isBuiltin.
The four --expose_externalize_string globals were installed unconditionally as
lazy accessors returning undefined without the flag. Their PRESENCE is the
observable, not their value: the REPL completes globals from
ObjectGetOwnPropertyNames(globalThis) (node repl.js filteredOwnPropertyNames
keeps every identifier-shaped own name, enumerable or not), so a flagless
process offered `isOneByteString` for the input `I`.

Greens test-repl-tab-complete.js.
__mbun_drain_timers() gated firing on `T.fired < 200000` with T.fired only
ever incremented, so the 200,000th timer in a process was the last one to
fire: the queue stopped draining, the loop went idle, and the process exited
0 with no output. setTimeout's clear/refresh/repeat leak fixtures schedule
115 * 2_000 = 230_000 timers and died silently ~85% through the measurement
loop, which is why all three scored as an empty stdout.

Reset the counter whenever a drain finishes without exhausting its per-call
budget: everything due was fired, so the workload is not running away. Only
an unbroken run of saturated drains -- a callback that keeps re-queueing
due-now work -- can still reach the cap.
fetch.preconnect was a no-op stub, so all 12 tests in
test/js/web/fetch/fetch-preconnect.test.ts failed -- 11 waiting forever for a
connection that was never dialed, and one because the URL validation never ran.

Dial the origin and park the socket in the same keepalive hive doFetch()
checks out from, so the first request to that origin reuses it. Validation
order and messages follow Bun__fetchPreconnect (webcore/fetch.rs:228): WHATWG
parse -> scheme -> hostname -> port. Only http:// is dialed; poolTake() hands
a `tls` entry to the request writer as if the handshake had completed, so an
https preconnect would poison the hive and is left as validate-then-no-op.

--fetch-preconnect <URL> is now parsed (repeatable, both spellings) into a
runtime list exposed as __mbunHttpNative.preconnectUrls(); the net JS image
dials each before the entry module runs, mirroring run_command.rs
do_preconnect.
node reads its main module from stdin when there is no file to run: an
explicit `-` operand, or a bare invocation whose stdin is not a terminal
(a terminal is the REPL instead). mbun printed its help text for the
first form and "Script not found \"-\"" for the second. `-` is not an
option, so it keeps its argv slot -- `mbun - --opt` puts --opt at
process.argv[2].

Separately, node's --print is a BOOLEAN option, separate from --eval's
string, so `-p -e 42` is one eval whose result is printed. mbun took
args[1] as the source unconditionally, so `mbun -p -e 42` evaluated the
token "-e" and died with "ReferenceError: e is not defined".

Greens test-stdin-script-child.js, test-stdin-script-child-option.js.
node marks every option kAllowedInEnvvar or not (src/node_options.cc).
The ones it withholds decide WHAT the process runs rather than how --
printing a version or help text, an eval string, the REPL, a syntax
check, the test runner, or the `--` terminator. Meeting one in
NODE_OPTIONS is fatal before any JS runs, with
"<argv0>: <opt> is not allowed in NODE_OPTIONS" and exit 9.

mbun ignored the environment entirely here. That is now observable
rather than merely wrong: with node's stdin-main-script behaviour in
place, `execFile(execPath, {env: {NODE_OPTIONS: '--eval'}})` no longer
prints help and exits -- it blocks on a stdin pipe the parent never
closes.

Implemented as node's DENY set, not the complement of its allow set:
mbun accepts node flags it has not modelled everywhere else, so an
allowlist here would turn every uncatalogued flag into a hard startup
failure -- a much larger claim than the one node is making.

Greens test-cli-node-options-disallowed.js.
node reports a failed --check through its ordinary uncaught-exception
printer (lib/internal/main/check_syntax.js re-throws the parse error),
which is a five-part frame: `<file>:<line>`, the offending source line,
a caret, a blank line, then the error on a line of its own. mbun put
the filename and the error on ONE line, so
/^SyntaxError: Unexpected identifier\b/m never matched.

Greens test-cli-syntax-piped-bad.js.
node lib/internal/util/trace_sigint.js arms a PROCESS-wide SIGINT
watchdog, so a worker is refused with ERR_WORKER_UNSUPPORTED_OPERATION.
It was absent entirely, so the call died as "not a function" with no
.code. Defined only in the worker branch, following process.execve
above: on the main thread mbun has no watchdog to arm, and an absent
property is the honest report of that.

Greens test-trace-sigint-in-worker.js.
Two categories that produced nothing. Both are sited where node sites
them, not wrapped from outside the module -- the moments node marks (a
ServerResponse being constructed, a connect request completing) are not
reachable from the trace agent.

node.net.native: one nestable-async `connect` span per connection
attempt, opened where node queues the uv connect request and closed
when the attempt resolves. The pipe form carries node's path_type /
pipe_path pair (an abstract socket is the leading-NUL name with the NUL
dropped), the TCP form ip / port, and the end the status. The end is
bound to 'connect'/'close' rather than to 'error': adding an 'error'
listener would suppress the throw an unhandled socket error owes the
program.

node.http: http.server.request from the ServerResponse constructor to
_finish, http.client.request from the ClientRequest constructor to the
parsed response head -- node's own four sites.

The agent gains emitGroup/emitGroupArgs for this: a compound-category
test plus a recorder that writes the compound string verbatim, with the
TRACE_EVENT_*1/*2 form putting its key/value pairs directly in `args`
rather than under `args.data`.

Greens test-trace-events-net.js, test-trace-events-net-abstract-socket.js,
test-trace-events-http.js.
node writes `(node:<pid>, thread:<tid>)` when the environment that
exited is a worker's and `(node:<pid>)` when it is the main thread's;
mbun always wrote the latter, so the worker half of the test matched
zero thread ids. mbun runs a worker as a child process, so the tid
comes from worker_threads rather than from a thread id this process
could ask the OS for.

Greens test-trace-exit.js.
speak-agent and others added 7 commits August 3, 2026 06:30
js_parser lowers `import(x)` to `__mbun_dyn_import(require, x)`, capturing
the module's require BY NAME. A CJS module that declares its own `require`
in an enclosing scope therefore hands the helper that binding instead of the
wrapper parameter -- and emscripten's node preamble does exactly this:

  if (ENVIRONMENT_IS_NODE) {
    let { createRequire: e } = await import("module"); ... var require = e(t);
  }

`var` hoists the shadow over the whole function, so the name is still
undefined when the lowered import runs, and every emscripten-built package
died with "req is not a function (In 'req(requested, attr, 1)')" the moment
it initialised -- @electric-sql/pglite and pg-gateway both.

Fall back to a cwd-rooted require when the captured binding is not callable,
the same shape builtins/s3.cppm already uses. The branch is only reachable
where the call previously threw outright, so it is strictly additive: bare
specifiers (all the shadowing preamble ever asks for -- "module", "fs",
"path") resolve identically.

Both files advance past this blocker to an independent emscripten WASM-init
stall, so neither turns green yet; corpus green is unchanged at 44/59.

Gate: js/bun/resolve + js/bun/module 36 files identical before/after
(19 green, 207 pass / 76 fail, 48264 expects); third_party reachable set
44 green before and after.
…led GET

bun splits the manifest/tarball fetch failure exactly on whether the edge is
required: add_error_pretty for a required one, add_warning_pretty for an
optional one, both over the same "GET {url} - {status}" body
(PackageManager/runTasks.rs:485-503 and :835-852). mbun's fail_dep_ returned
early for every optional failure, so an optional dependency whose tarball 404s
installed silently.

Adds a fail_fetch_ seam used only at the three fetch-settle sites (manifest
failure, tarball request construction, tarball response), carries the messages
out on registry_install::Summary::warnings -> InstallSummary::warnings, and
lets the CLI print them as 'warn: ...' on stderr. Every other optional failure
-- os/cpu mismatch, no matching version, an unsafe name -- stays silent, which
is why this is a separate seam rather than a flag on fail_dep_.

cli/install/bun-install-retry.test.ts 7 pass/3 fail -> 9 pass/1 fail.
…lapsed time

bun's save_lockfile prints 'Saved lockfile' on stderr for every subcommand that
writes one; only 'mbun install' echoed it, so a successful 'mbun add' wrote
bun.lock silently. Two further gaps in the same printer: the 'installed' line
reported the RANGE written into package.json ('installed BaR@^0.0.2') where bun
reports the concrete version that landed in node_modules ('installed
BaR@0.0.2'), and the summary line carried no ' [N.NNms]' suffix -- which the
corpus strips with /\s*\[[0-9.]+m?s\]\s*$/, a regex that only matches when the
suffix is present, so its absence left a spurious trailing line.

cli/install/bun-install-retry.test.ts 'retries on 500' now reaches its
node_modules/.cache assertion (69 -> 76 expect() calls evaluated in the file).
…hared /tmp gate lists

W48's cli/install brief claimed the group had never had a lane; struck.tsv
records wave 65 measuring all 73 files, landing a 200-line parser, gaining +0,
and writing 'do not re-run this vein expecting files'. The W48 lane reproduced
that conclusion at the cost of its budget.

Separately, a lane reported that the corpus runner mutates its --list file. It
does not; the runner writes only inside --out. Five concurrent lanes sharing
bare /tmp paths for gate lists is what turned a 36-line list into 1301 lines.
Recording the correction so future lanes do not defend against a bug that does
not exist.
11 manifest files in 7.5 lane-hours against a 37-file target; composed delta is
+20 because one lane's fix carried 10 collateral greens its manifest could not
show. Two of the five lanes returned 0 for reasons that were my briefing
errors, both recorded in the rows so the next planner inherits them.
@Sunrisepeak

Copy link
Copy Markdown
Owner

W48 verdict — +20 measured, and three latent defects worth more than the count

Frozen binary f65f960b7ac7d212…. Verified across 2,643 node files + the child-process group + per-lane bun gates.

Lane result

Lane Goal Delivered (manifest) Lane-h files/h Note
C1 — bun cli/install 8 +0 1.8 0 reproduced wave 65's retired-vein conclusion — my briefing error
C2 — bun js/web 8 +1 0.6 1.8 found the timer lifetime quota
C3 — bun js/third_party 5 +0 2.4 0 corrected my manifest bug and its own tooling claim
C4 — node cli/trace 8 +9 2.2 4.0 plus 10 collateral greens on its own gate
C5 — node repl/runner 8 +1 0.5 2.1 found the async function parser bug

11 manifest files in 7.5 lane-hours; goal accuracy 0.30. Composed measurement is higher than the manifest sum because C4's fix carried collateral: node +18, bun +2 = +20 this wave.

Total progress

Corpus Baseline (W43) Now Δ Audited runnable % of runnable Remaining
Node test/parallel 3,168 / 4,433 3,258 +90 3,898 83.6% 640
Bun test/** 1,041 / 1,902 1,071 +30 1,804 59.4% 733
Combined 4,209 / 6,335 4,329 +120 5,702 75.9% 1,373
Wave Node Bun Total Lane-h files/lane-h Goal accuracy
W44 +43 +8 +51 10.6 4.8 1.65
W45 +7 +5 +12 7.2 1.7 0.88
W46 +15 +3 +18 7.8 2.3 0.40
W47 +6 +6 +12 6.8 1.8
W48 +18 +2 +20 7.5 2.7 0.30

Cumulative regressions across all five waves: 1 (test-abortsignal-any.mjs, the accepted cost of ending a hidden global AbortSignal replacement).

Estimate to 100%

1,373 runnable files remain. At the five-wave mean of ~23 files/wave, that is ~60 waves / ~190 coordinated wall-hours for what is reachable. The architecture-gated remainder is unchanged and now fully priced: QUIC 237 (60-100 lane-hours for the floor, 200-300 for all of it), inspector+debugger 132 (8-14 engineer-weeks, and no per-file gate so no partial credit exists), workers-as-processes ~10, JSC readonly-assign wording 4, the prebuilt's dead execution-time-limit callback 4, ContextifyScript/ModuleWrap, nghttp2 ≥2, unvendored acorn 6.

Realistic landing on today's architecture stays 96-98% of runnable.

Three latent defects, none of which a file count would have surfaced

A silent truncation in the timer loop. __mbun_drain_timers() gated firing on T.fired < 200000 with T.fired only ever incremented — written as a runaway guard, behaving as a lifetime quota. The 200,000th timer a process fires is the last one it ever fires; the queue then stops draining, the loop goes idle, and the process exits 0 with no output and no error. Proof of blast radius: fetch.stream.test.ts went from Ran 50 to Ran 54 on the same file — four tests had been silently dropped. Any long-running process burning 200k timers was being truncated mid-run without a trace.

A parser correctness bug. parse_statement_ had no case for async function, so it fell through to the expression path and kept parsing postfix: a statement-leading [ after an async function declaration became a computed member access, which then rejected the legal trailing comma in [a, b,]. Plain function, function* and class were all fine — only async function.

Stack strings are not node-compatible for external code. err.stack lacks V8's TypeError: <message> header line, so express's error page renders bare frames. Same family as W47's finding that builtins evaluate with an empty sourceURL, which makes agent-base — which sniffs the stack for node:https: — pick the wrong protocol and break https-proxy-agent and everything on it. Filed together as #102.

Also filed: #100node:http server throughput measured at 1,344 req/s against Bun.serve's 22,599, keepalive confirmed working (0.00 new connections per request) and 50× concurrency buying only 1.3×, i.e. a ~1 ms per-request serialization floor on the path express, koa and fastify all sit on. #101 — the global console never routes through util.inspect, so object formatting diverges from node even though util.inspect itself is already correct.

Two coordinator failures, both from not consulting the record

  1. I briefed cli/install as "never had a lane". struck.tsv records wave 65 measuring all 73 files, landing a ~200-line parser, gaining +0, and writing "do not re-run this vein expecting files." The W48 lane reproduced that conclusion exactly, at the cost of its whole budget. check_struck.py belongs before a brief is written.
  2. My manifest generator sorted by fewest-failing-assertions, which is degenerate: a fully skipped file has zero failing assertions, so the sort selects for unreachable files. The js/third_party manifest captured 15 of the 21 upstream skip-gated files in that subtree. Rule now: an entry needs ran > 0 and failed > 0, and the classification histogram gets checked before dispatch.

A third correction went the other way: a lane reported that the corpus runner mutates its --list file. It does not — the runner writes only inside --out. Five concurrent lanes sharing bare /tmp gate-list paths is what turned a 36-line list into 1301 lines. The lane retracted it with the sharper reasoning: the list grew between runs, not during one, and a mutating reader would have corrupted it in place on first read.

The file is zero bytes and referenced by nothing -- a stray artifact from the
W48 repl/runner lane. GCC tolerated it; clang globs *.cppm as module interface
units and rejects it outright:

  modules/jsc/src/js_engine.cppm:1:1: error: missing 'export module'
  declaration in module interface unit

which is why the gcc lane went green while the llvm lane stayed red on the same
head.
Both corpora re-measured in full on ONE frozen binary (f65f960b7ac7d212) on
2026-08-03, rather than carried forward as accounting:

  Node   3,267 / 4,433 (73.7%), runnable 3,267 / 3,919 (83.4%)
  Bun    1,076 / 1,902 (56.6%), runnable 1,076 / 1,806 (59.6%)
  Both   4,343 / 6,335 (68.6%), runnable 4,343 / 5,725 (75.9%)

The coverage line is dropped from the title -- a number in an H1 goes stale the
moment anything lands, and the table below is the place that carries its own
measurement date and binary.

Both READMEs were behind by different amounts (the Chinese one still read 3,065
/ 934), so both are updated from the same run. The runnable denominators GREW
this round -- Node 3,898 to 3,919 -- because 21 files that used to skip
themselves now run; that is recorded explicitly so the wider denominator is not
mistaken for a softer one.

The self-skip breakdown is regenerated by grouping this run's own logs instead
of carrying the previous table forward, and the two large blocks are priced:
QUIC needs no protocol work (node builds on vendored ngtcp2 and the linked
OpenSSL 3.5.1 exports everything that backend calls) but does need node's ~19k
-line binding against JSC; the inspector block has no per-file gate, so no
partial slice can score and flipping the flag early converts skips into
failures.

Elysia keeps its row with its own measurement date -- its node_modules is not in
this checkout, so it could not be re-run and is not presented as if it had been.
@Sunrisepeak

Copy link
Copy Markdown
Owner

Final same-binary full measurement — both corpora, and CI is green

Frozen binary f65f960b7ac7d212…, both corpora executed in full (no sampling, no
manifests), sequentially rather than concurrently so neither run measured the other's load.

Corpus Result Rate Runnable Rate
Node test/parallel 3,267 / 4,433 73.7% 3,267 / 3,919 83.4%
Bun test/** 1,076 / 1,902 56.6% 1,076 / 1,806 59.6%
Combined 4,343 / 6,335 68.6% 4,343 / 5,725 75.9%

Node categories: pass 3,267; fail 565; skipped 514; timeout 85; OOM 2.
Bun categories: green 1,076; test-failure 666; all-skipped 73; timeout 45; blocked-external 18;
OOM 7; no-tests 5; crash 4; load-error 4; ahead-of-reference 4.

Against the W43 baseline this PR opened on — Node 3,138, Bun 1,041 — that is Node +129,
Bun +35, +164 combined
, all on one binary rather than carried forward as accounting.

Two things worth stating precisely:

  • The runnable denominator grew, it did not shrink. Node went 3,898 → 3,919 because 21 files
    that used to skip themselves now actually run. The audit rule is that exclusions are never
    passes and the denominator is a floor; this round it moved the honest way.
  • My in-flight accounting was conservative by 9 files. Through W48 I carried Node at 3,258
    from baseline-plus-verified-deltas; the full run says 3,267. Worth knowing that per-file
    accounting under-counts slightly rather than over-counts, since that is the direction that
    keeps a claim safe.

CI

d3a1053 is green on all three lanes — build-test (gcc@16.1.0), build-test (llvm@22.1.8)
and macos-probe.

The llvm and macOS lanes had been red on the same head where gcc was green, and it was one cause:
modules/jsc/src/js_engine.cppm was a zero-byte file referenced by nothing, left behind by a
W48 lane. GCC tolerated it; clang treats every *.cppm as a module interface unit and rejects it:

modules/jsc/src/js_engine.cppm:1:1: error: missing 'export module' declaration in module interface unit

Deleted. I also swept the tree for the same hazard — no other empty tracked file exists — so this
does not need a second round trip.

README

The coverage line is out of the H1 in README.md: a number in a title goes stale the moment
anything lands, and the table carries its own measurement date and binary. Both READMEs are
updated from this run — the Chinese one was further behind (3,065 / 934) — and its self-skip
breakdown is regenerated by grouping this run's own logs rather than carrying the old table
forward: QUIC 238, V8 inspector 180, missing ESLint 29, no OpenSSL CLI 10, Amaro 6,
Windows-specific 6, other 45.

The Elysia row keeps its own 2026-07-30 date and is labelled as not re-run: its node_modules is
not in this checkout, so it could not be measured, and it is not presented as if it had been.

@Sunrisepeak
Sunrisepeak marked this pull request as ready for review August 3, 2026 06:39
@Sunrisepeak

Copy link
Copy Markdown
Owner

Merge record — protected surfaces, maintainer instruction, what was bypassed

Merging on the maintainer's explicit instruction. Recording the governance trail here because hagent/charter.md §5 requires it.

This PR changes five protected surfaces, which §5 defines as a constitutional change that an agent may not self-merge without maintainer sign-off:

  • .agents/skills/dev-process/SKILL.md
  • .agents/skills/mcpp-style-ref/SKILL.md
  • .github/workflows/ci.yml
  • hagent/agents.md
  • hagent/zh/agents.md

What was bypassed: the base branch policy requires a review, and a normal gh pr merge refuses (the base branch policy prohibits the merge). The merge is performed with administrator privileges, which skips that review gate. The maintainer's instruction is the §5 sign-off; the review gate itself is genuinely skipped, not satisfied, and that is stated plainly rather than glossed.

What was NOT bypassed:

  • CI is green on the merged head d3c742a across all three lanes — build-test (gcc@16.1.0), build-test (llvm@22.1.8), macos-probe.
  • mergeable = MERGEABLE — no conflicts against rewrite_bun_in_mcpp.
  • Both corpora were re-measured in full on one frozen binary (f65f960b7ac7d212) rather than carried forward as accounting: Node 3,267/4,433, Bun 1,076/1,902, and the README now states that measurement with its date and binary.
  • Workspace members: 97/97.

205 commits, 340 files. Against the W43 baseline this PR opened on: Node +129, Bun +35.

@Sunrisepeak
Sunrisepeak merged commit 4c5cce1 into rewrite_bun_in_mcpp Aug 3, 2026
3 checks passed
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