Commit Graph
75 Commits
Author SHA1 Message Date
Chris Tate f47ee03dc6 Support array copying and Uint8Array iteration
- Add static Array and Uint8Array copying, sorting, and join surfaces.
- Lower Uint8Array spread, destructuring, and iterator methods across C and LLVM.
- Pin Node 24 semantics with differential coverage and runtime size gating.
2026-07-29 18:03:00 -05:00
Chris Tate a992b644fb fix: align Array.toSorted ordering semantics 2026-07-29 15:30:48 -05:00
Chris Tate 9b0ad004f8 Fix top-level await rejection checkpoints 2026-07-29 01:32:26 -05:00
Chris Tate 05752fde96 Fix top-level await dependency rejection handling 2026-07-29 00:35:35 -05:00
Chris Tate 786f10dbf2 Fix top-level await cycle semantics 2026-07-28 23:27:25 -05:00
Chris Tate 2d9070cd54 fix top-level await rejection handling 2026-07-28 22:01:32 -05:00
Chris Tate e88ddc1678 Support top-level await
- Lower asynchronous ESM initialization with Node-compatible dependency ordering and caching.
- Finish top-level promises consistently across the C and LLVM runtimes.
- Add differential coverage for imports, rejection, and unsettled evaluation.
2026-07-28 11:19:04 -05:00
Chris Tate bc2cd2b7d1 Support Number.toFixed statically
- Lower Number.prototype.toFixed through the compiler IR and native backends.
- Match JavaScript rounding, range errors, and numeric edge cases in the runtime.
- Add differential coverage and update surface metadata and diagnostics.
2026-07-26 18:43:53 -05:00
Chris Tate 481dd9c974 fix: flush console output promptly
- Submit console, process, readline, and island writes before returning.
- Add live-pipe and SIGKILL parity coverage against Node 24.
2026-07-26 01:30:42 -05:00
Chris Tate a7538fd87d URL-string and URL-object arguments to the http and https clients
- `http.get("http://host/path")` and its https spelling now compile. The declarations only described the options-record form, so the first thing a from-scratch client writes was a type error — the shape worked under --dynamic, where the engine does not consult our declarations, which is how it read as a hello-world failure.
- A URL object is accepted the same way, as its href through the same parse. An unparsable input is the WHATWG "Invalid URL" TypeError and a scheme that is not the calling module's is ERR_INVALID_PROTOCOL, both catchable, matching Node rather than silently upgrading the dial.
- `request` and `get` take one signature with a union first parameter instead of Node's two overloads: `tls ? https.request : http.request` unions two function types, and resolving a call on that union collapses each side to a single signature, so an overload set misresolves in either declaration order. The lowering dispatches on the argument's own type.
- Both rows run on the LLVM backend, and the tls CA-store entries joined it too — the omission there was stale, so 2557-tls-ca-store moved off the C lane with identical output.
- Corpus 2644 and 2645 cover both schemes against loopback servers, so neither needs the network.
2026-07-24 23:24:53 -05:00
Chris Tate 46643230d8 Resolve hostnames at the client dial
- http, https, TLS and HTTP/2 clients now resolve the hostname before connecting. A client that dialed by name got ENOTFOUND regardless of the name — only literal addresses worked, which is most of what a from-scratch client writes.
- Only the dial takes the address. The original name stays on the request, which is what the Host header carries and what SNI and certificate verification see, so a resolved dial does not weaken the TLS check.
- node:net's own connect surface stays resolver-less: Node's async lookup semantics there are a separate piece of work, so `net.connect(port, hostname)` still refuses to resolve.
2026-07-24 23:24:41 -05:00
Chris Tate 44d05d8e74 Route the compiler's dyn world onto the engine's own operations
- Island values held in `unknown` arm the routed operation set (SCR_DYN_JSVAL): keyed reads and writes, calls and method dispatch, Object statics, and `??` all run in the engine, so a wrapped value answers exactly what Node answers instead of meeting a fence.
- Scalars normalize at the wrap, composites stay engine values by reference, and the round trip preserves identity — a value that crosses into the checked-dynamic tree and back is the same engine value.
- deepStrictEqual over a mixed engine-vs-native pair fails with the reason stated rather than guessing at an answer.
- Corpus 2582–2585 and an npm routed-row fixture pin the behavior; the dyncheck expectations move with it.
- The retired "DOM" nickname is swept out of the sources and corpus in the same pass (29 comment sites) — the world is the checked-dynamic tree, its values are dyn values.
2026-07-24 19:53:10 -05:00
Chris Tate 27e7537cec Crypto introspection, the TLS CA store, and the http2/rejection tail
- The crypto introspection statics (getFips and the cipher/hash/curve name lists) bake at the call site, and the crypto constant table joins http2's under a generalized per-module baked-constants lowering.
- The CA-store introspection surface lands in its own unit (scr_tls_ca.c) behind its own link gate, so a getCACertificates-only binary never pulls mbedTLS: getCACertificates, rootCertificates, setDefaultCACertificates.
- http2.createSecureServer grows the eager request handler and the runtime options record, riding the same server-options walk as tls.createServer; string-typed 'data' listeners decode.
- process once/off for 'unhandledRejection' plus the sibling 'rejectionHandled' registry, removal by closure identity per the warning-registry stance.
- Merge resolution unions this lane with main's fs ladders, createRequire erasure, and dyn secure-context work; the runtime's retired "DOM" nickname is spelled "dyn" throughout.
2026-07-24 19:49:54 -05:00
Chris Tate 2c0f01ad93 HTTP Agent and dynamic-handle compatibility
- Add Agent connection accounting and HTTP/socket compatibility surfaces
- Support dynamic Buffer at and slice access
- Pin behavior with differential corpus coverage
2026-07-24 18:52:19 -05:00
Chris Tate e947502af6 The long tail of engine-held values: iteration, promises crossing, and the driver through its plugin pipeline
- for-of, destructuring, and spread over engine-held dyn values ride the engine's own iterator protocol with V8's exact non-iterable spellings; for-of over dyn lowers generally
- Static promises cross into the engine as thenables; Promise.all over mixed and non-literal entries runs the engine's own combinator; engine-array fulfillments exit by reference
- The re-minted silent-wrong-answer path is closed: island-literal members whose values poison fence at the read with the captured diagnostic
- The checked-dynamic tree replaces the old nickname across comments and internal docs
2026-07-24 17:17:00 -05:00
Chris Tate 07c9a4128c node:stream/consumers compiles statically; createRequire erases to static requires
- text, json, and buffer settle real promises over the readable machinery with Node's settle timing and rejection set; arrayBuffer and blob fence with their representation reasons
- createRequire over this file's URL is compile-time plumbing: builtin specs become namespace imports, relative JSON bakes as validated documents, npm packages ride the require-condition graph, unresolvable names throw Node's catchable MODULE_NOT_FOUND
- module.builtinModules bakes as the compat target's exact list

# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	packages/compiler/src/frontend/lowering/lowerer.ts
2026-07-24 12:13:08 -05:00
Chris Tate aac4f9390b Buffer views alias for real; stream ticks interleave with process.nextTick in Node's order
- Buffer slice and subarray become true aliasing views over the retained owner — mutating through a view was silently computing wrong bytes
- Each stream tick posts a one-to-one marker on the user tick queue, so stream emissions interleave with process.nextTick in enqueue order, Node's order by construction; the station remains for teardown
- Stream emitters model the pre-created event-key shape governing eventNames order; push honors defaultEncoding with Node's unknown-encoding errors

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-24 12:11:31 -05:00
Chris Tate 034195f588 Profile-declared integer boundary slots prove or refuse at compile time
- An interval-and-wholeness inference over the lowered IR proves integrality and range for every value reaching a declared i64/u64 slot, with loop-header widening precise enough that the bounded counter loop proves its exact range
- Refusals carry the slot path, the failed obligation with evidence, and the author's concrete fix as SC4021, SC4022, and SC4023
- Declared slots are both obligation and assumption: call sites check arguments, callee bodies assume their classes; integer returns emit exact machine-integer wrappers and inbound params trap through the structured host-contract family
- Math.trunc and Math.ceil compile statically

# Conflicts:
#	packages/compiler/src/library/profile.ts
2026-07-24 12:07:29 -05:00
Chris Tate 01b47dd78b Promise.all over non-literal 'any[]' arguments runs engine-side; island values bridge into Promise-typed slots with the any[] settle payload
- The loadPlugins shape end-to-end: Promise.all(plugins.map((p) => loadPlugin(p))) — a NON-literal argument the checker spells any[] — lowers to the ENGINE's own Promise.all over the lifted array, and the INFERRED Promise<any[]> return coerces through the island→static promise bridge with the new SCR_ISLP_JSVAL_ARR settle payload: the fulfillment exits Array.isArray-gated BY REFERENCE at the settle, a lying fulfillment rejecting with the boundary TypeError (trust-but-verify at the settle).
- coerceToExpected's jsval branch gains the promise-slot arm generally: island values flowing into Promise<void>/Promise<any>/Promise<any[]>-typed slots bridge instead of fencing (the passing-a-package-promise-into-promise-typed-parameters fence retires for these inners); other inners keep the named fence.
- Corpus 2605 extended with the non-literal all + inferred-return rows, byte-identical to Node under both backends and SAN-clean; dyncheck/errors/island suites green.
2026-07-24 11:51:34 -05:00
Chris Tate c1be329607 Static promises cross into the island as real engine thenables; Promise.all over mixed island/static entries runs the engine's own combinator
- jsvalIn (and the island-literal member path behind it) accepts STATIC promises whose fulfillment is in the reverse bridge's payload domain — void/number/boolean/string/'any', plus the NEW Promise<any[]> payload (SCR_ISLP_JSVAL_ARR: a native array of engine cells re-enters as a fresh engine array over the SAME engine values). scr_jsval_from_promise emits from jsMarshal in both backends; the IR validator admits the arm.
- Promise.all with MIXED entries where some entry is an island value (the withPlugins shape: Promise.all([loadBuiltinPlugins(), loadPlugins(plugins)])) lowers to the ENGINE's own Promise.all — island entries pass through, static promises bridge as thenables, and the combined result stays an island value the static side awaits through the existing island→static bridge. All-static tuples keep the typed fence hint.
- Corpus 2605 (unawaited static promise in an 'any' slot answers typeof "object" and awaits engine-side; the mixed-all + .flat() chain) is byte-identical to Node under both backends; SAN clean. Composite fulfillments (Promise<{...}[]>) stay fenced with the promise type named — the payload-domain residue.
2026-07-24 11:32:57 -05:00
Chris Tate 10696c70f8 Profile-declared i64/u64 integer slots: surface, wiring, wrappers, host-contract trap
- LIB_PARAM_CLASSES/LIB_RETURN_CLASSES grow i64/u64 (the prove-or-refuse pair); u8/u32/i32 stay param-only with the message updated to point at the landed machinery
- sidecar.integer_slots declares contract slots by sidecar path (Msg.count, Point.x, helpers.f.params[0], helpers.f.return); the projection spells each declared slot i64 (the frozen format-1 vocabulary — u64 is the stricter compile-time obligation over the same wire spelling), refuses non-number or unresolvable paths, and emits integer_slots as the resolved-decision attestation in declaration order
- compileLibrary runs the integer-boundary inference over export-map and sidecar-declared slots and refuses SC4021 (representability) / SC4022 (wholeness) / SC4023 (range), each carrying slot path, failed obligation with evidence, and the author's fix; u8/u32/i32 plumbing classes seed parameter shapes for free precision
- Both emitters grow i64/u64 wrappers: inbound params range-check through scr_library_i64_in/u64_in (the SC4012 host-contract trap — silent rounding past 2^53 is a coercion the author never wrote), returns convert exactly under the discharged proof
- Profile-loader suite pins the new surface; the pre-ask-4 pins updated
2026-07-24 11:29:30 -05:00
Chris Tate 6b6445ab90 Retire the "DOM" nickname in comments, internal docs, and message prose: the checked-dynamic tree / dyn values
- The ratified rename rider (world-unification §7 lane 5): "DOM" collided with the browser DOM and, post-lane-1, the structure is no longer a document tree. Comments and doc prose now say "the checked-dynamic tree" for the noun and "dyn" adjectivally; runtime symbol names (scr_dyn_*), the error.domClone libCall, and the real web DOMException family are unchanged.
- The few user-facing strings carrying the nickname move with it (the structuredClone cyclic message, the dc subscriber/publish fences, two emitter ICE texts); diagnostics snapshots regenerated (96 pass). One private compiler constant (DOM_PROTO_METHOD_NAMES -> DYN_PROTO_METHOD_NAMES) rides along.
- Mechanical sweep, 142 files, comments/prose only — no behavior change.
2026-07-24 11:14:58 -05:00
Chris Tate b98d726382 for-of, destructuring, and spread over island values held in 'unknown' drain the engine's own iterator; engine arrays exit into 'any[]' slots
- The iteration pack's JSVAL arm is ARMED (scr_dyn_iter_pack, scr_dyn_arr_push_spread, scr_dyn_pack_push_spread_iter route through the new iter_drain ops row): the ENGINE's iterator protocol drains into a fresh DOM array — engine arrays, generators, Maps, Sets, and Symbol.iterator implementations step exactly as Node runs them, elements wrapping back scalar-normalized.
- for-of over checked-dynamic values LOWERS now (both backends): the source packs once through dyn.iterPack and a hidden index loop binds each element as a dyn value (new dyn.arrLen/dyn.arrAt accessors) — an eager snapshot, the matchAll stance. Pattern heads, pre-declared and var heads, and labeled break/continue all ride the desugar. Non-iterables throw V8's for-of CallPrinter spellings, threaded into the engine-side guard.
- The validated exit gains the jsval-element-array arm (scr_jsval_exit_jsval_arr, both backends): an 'any' value exits into 'any[]'- and 'any[] | undefined'-declared slots Array.isArray-gated, elements BY REFERENCE (identity crosses, the spine is a snapshot) — the withPlugins loadPlugins(plugins) boundary, the driver frontier's first named poison.
- Corpus 2604 and npm case jsval-iterate (package-minted Set/Map/generator/Symbol.iterator) are byte-identical to Node under both backends; dyncheck's jsval-iterate pin flips from the retired island fence to the armed answers; SAN clean over the drain/wrap/exit churn.
2026-07-24 11:04:10 -05:00
Chris Tate 65bab3d8cd Named capture groups compile end to end
- lowerRegexLiteral drops the (?<name> fence: patterns and \k<name> backreferences run natively in libregexp, and .groups on exec/match/matchAll results desugars into a compile-time record projection over the honest slice — the checker's own Record<string,string> hybrid with overflow entries in group declaration order — whenever the regex is statically known (literals, const bindings, new RegExp("..."), matchAll rows via for-of bindings, stored drains, spread rows, element access)
- Node's exact shapes: a group-less regex answers .groups === undefined, nullable receivers throw Node's TypeError on the unit arms, ES2025 duplicate names project the participating alternative, and destructuring rides — flat, aliased, and the nested { groups: { year } } pattern — via the groups wrap plus a general overflow read for undeclared keys of index-signature shapes
- scr_put_substitution resolves $<name> against lre_get_groupnames with Node's edge answers: nonexistent and nonparticipating names substitute empty, $< stays literal when the pattern has no named groups, an unterminated $<name stays literal
- s.match(re) also claims a nullable string receiver (the Dict<string> member spelling) through the checked narrow; an untraceable .groups read keeps the SC2020 fence with a hint naming the traceable shapes
- Differential corpus 2604-2607 (both backends, SAN), runtime C substitution/backreference cases, refreshed diagnostics snapshots and surface manifest; corpus 1614's deliberate deferred fence moves to a Proxy wrap now that hybrid record reads compile
2026-07-24 10:41:37 -05:00
Chris Tate 6f2de332db Readable push encodings: defaultEncoding decodes string pushes, explicit literals override, unknown spellings take Node's ERR_UNKNOWN_ENCODING
- The Readable defaultEncoding option lowers to a push-side decode default (Buffer.from(chunk, enc)); push(chunk, enc) with any literal encoding decodes per call and overrides the default, utf8 included
- Unknown literal encodings become Node's runtime TypeError — at construction for the encoding/defaultEncoding options (nodeThrowExpr, the ladder pattern), at the call for push
- on/once/off admit Node's ignored extra arguments when they are pure reads (the suite's stray-third-argument shape); effectful extras keep the fence
- readable.pushStrEnc/readable.pushEncoding land in both backends; corpus 2607-stream-push-encodings pins the decode, override, and both throw sites differentially
2026-07-24 10:30:47 -05:00
Chris Tate 2785bb09f9 Stream ticks ride the user nextTick queue: FIFO with process.nextTick, Node's order
- Every deferred stream emission posts a raw marker on the user tick queue (scr_next_tick_raw) and each marker dispatches exactly one stream tick, so resume_/emitReadable_/endReadableNT interleave with user nextTicks in enqueue order — in Node they are the same queue
- Cures the setEncoding-with-buffered-bytes shape: a nextTick registered after on('data') now observes the flowed chunks, and stream emissions run before microtasks at each checkpoint instead of after
- The loop station stays as the drain for anything a marker never reached and as the uncaught-throw cleanup of undelivered ticks
- Corpus 2606-stream-ticks-are-nextticks pins the interleavings (buffered decode, split multi-byte char, tick/data/tick ordering, end-after-tick) differentially on both backends
2026-07-24 09:47:43 -05:00
Chris Tate 6e9467821f Stream emitters model Node's kShapeMode: pre-created event keys govern eventNames order
- Stream constructors reserve their class's known _events names (Readable close/error/data/end/readable, Writable close/error/prefinish/finish/drain, Duplex the union) — empty reserved buckets are invisible to every read but hold the first-registration rank, so eventNames lists them before earlier user events, exactly Node's shape optimization
- Shape mode reproduces Node's removal split: removeListener keeps an emptied name's rank (events[type] = undefined), the named removeAllListeners deletes a populated key (and resets wholesale when the count drains, kShapeMode quirk included), the no-argument wipe resets everything and leaves shape mode
- Corpus 2605-stream-event-names pins the order, rank survival, drain, and wipe behaviors differentially on both backends
2026-07-24 09:40:01 -05:00
Chris Tate 135aec0383 Buffer slice/subarray become true aliasing views; typed arrays gain element fill and Buffer.from(x.buffer)
- scr_bytes_subarray: a same-elem view retaining the owner (the DataView rule, chain depth 1); subarray() and Buffer's slice() lower there, plain typed arrays' slice() keeps the copy — mutations through views are visible both ways now, byte-exactly with Node
- Buffer.from(x.buffer, byteOffset?, length?) lowers as the dataViewNew view construction (the syntactic .buffer peel); the fallback lib gains the ArrayBuffer overload
- fillElem: TypedArray.prototype.fill on non-u8 receivers, per-element coercion with slice-clamped indices; u8 keeps the Buffer fill family
- Both backends emit the new intrinsics; corpus 2604-bytes-views pins aliasing, offset composition, swap-through-slice, clamping, and fill coercion differentially
2026-07-24 09:28:37 -05:00
Chris Tate 417ece6724 node:stream/consumers compiles statically: text, json, and buffer settle real promises
- scr_sc_text/json/buffer accumulate every chunk through a native 'data' listener (string chunks as their utf8 bytes, the Blob rule) and settle at the terminal point through the finished watcher — Node's own eos timing, verified byte-exact including resolution landing after 'close'
- Rejections match Node's set: the stream's error, ERR_STREAM_PREMATURE_CLOSE on an early close, json's SyntaxError on malformed text, and the async-iterable TypeError for a stream with no readable side
- lower-stream's stream/consumers arm lowers text/json/buffer over one lowered stream argument (json answers a checked-dynamic promise — the JSON.parse unknown stance); arrayBuffer and blob fence module-qualified with pointed hints, since neither value has a representation
- Both backends emit sc.text/sc.json/sc.buffer; the trio joins the may-throw seeds, the loop-liveness set, and moduleUsesStream's link gate; the SC1010 hint, its snapshots, and the surface manifest gain stream/consumers
- Corpus 2604/2605 pin the consumers differentially on both backends and under ASan + RC audit: split multibyte sequences across chunks, encoded string-chunk streams, settle ordering, empty streams, and all four rejection arms
2026-07-24 09:27:44 -05:00
Chris Tate 88ff8eaa60 Library links stay timer-free with the real fs.exists; the regex size arm re-bases
- fs.exists' async fire compiles out under SCR_LIB and the compile-time scan refuses the surface with the event-loop family
- The regex-free size ceiling re-bases +8KB for the engine-handle arms and validation-ladder strings
2026-07-24 08:33:32 -05:00
Chris Tate 8cb44e4345 Record order-parity baselines and the island destructuring fence for the merged train
- Baselines for the renumbered fixtures, purely additive
- iterPack loud-fences engine-held sources instead of claiming not-iterable on values Node iterates
2026-07-24 08:15:35 -05:00
Chris Tate a16ee028ee Keyed writes land on dynamic receivers; array destructuring walks dyn sources
- Evolving-any receivers holding checked-dynamic values take keyed writes with ToPropertyKey-exact key spelling; dyn-keyed reads gain the same key stringification
- Array destructuring over dyn sources packs via the spread walk with V8's exact non-iterable TypeError wordings
- The dyn keyed-write miss error appends the primitive's rendering, matching Node

# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-exprs.ts
#	packages/compiler/src/ir/validate.ts
2026-07-24 08:09:34 -05:00
Chris Tate e4bd46c826 fs, net, dgram, tls, and stream misuse throws Node's validation ladders
- Per-API validators in the checked-dynamic lane run Node's argument checks in Node's order, then the real operation or a rendered fence thrown after validation
- fs.exists is genuinely async including the synchronous-false wart; mkdtempSync and lchmod are real
- Destructured sub-namespace bindings resolve as their module; function-armed unions cross into the checked-dynamic world
- dgram state errors carry their ERR_SOCKET codes

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-24 08:07:01 -05:00
Chris Tate c48a21b9ef Object.create, array entries and keys iterators, and variadic-spread Object.assign
- Object.create(null) is a real null-prototype dictionary: inspect, toString, and deepStrictEqual answer Node's forms; the dynamic tier routes to the engine's own Object.create for live delegation
- Object prototypes fence by name in static builds — an own-copy stance is observably wrong under Node's empty-own-set answers and live proto mutations
- entries() and keys() walk any static array live in for-of heads and iterator-helper chains
- Object.assign(target, ...sources) packs spreads with JS's evaluation order before any copy, with V8's position-dependent error texts

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
#	packages/runtime/src/scr_json.c
2026-07-24 08:03:58 -05:00
Chris Tate 613d205a25 Routed operations over engine-held dyn values: keyed access, calls, method dispatch, and nullish tests
- Keyed reads and writes, dynCall, and method dispatch on engine-held values route through the engine, which runs its own prototypes JS-exact
- One generic host shim carries boxed dyn functions into engine calls over the uniform thunk signature
- Nullish coalescing and optional chains unfence for every dyn value, engine-held or not
- Mixed island-vs-DOM deepStrictEqual comparisons loud-fence instead of minting a fabricated failure
- The formatter driver's support slice runs past its flatMap wall
2026-07-24 07:59:12 -05:00
Chris Tate b49134cdcb tls option bags and the stream misuse arms throw Node's validation ladders
- The tls server/secure-context walker validates every present typed option before the pem walk and its fences: the ciphers/passphrase/ecdhCurve string contracts, the engine trio's string-or-null-or-undefined clause, handshakeTimeout/sessionTimeout number contracts with validateInt32's exact range text, ticketKeys' view contract and exactly-48-bytes value ladder (Received renders the byte length, Node's shape), and minVersion/maxVersion's ERR_TLS_INVALID_PROTOCOL_VERSION with %j rendering. Literals carrying a validated key ride whole to the walker (JS sources), and createSecureContext gains the runtime-record form (tls.createSecureContextDyn) — a validated { cert, key } bag builds the real context.
- tls.connect validates checkServerIdentity's function contract with Node's presence semantics (a PRESENT key replaces the builtin verifier even holding undefined — the defaults spread); tls.getCACertificates walks validateString plus the documented name set before its fence.
- stream.finished/stream/promises.finished over a provably-non-stream value throw Node's isNodeStream ERR_INVALID_ARG_TYPE; socket write's two-argument encoding form implements the stream_base typecheck (write(string, 'buffer') is the synchronous "Second argument must be a buffer") with utf8 spellings as the plain write; Readable.toWeb's type option answers Node's one-of ladder.
- Unions with boxable FUNCTION arms now cross into the DOM (canConvertToDyn + both backends' union-to-dyn walkers) — the invalid-input probes iterate [1, null, () => {}, true]; scr_dyn_specific_type renders functions as Node's `function ${name}` (anonymous keeps the trailing space).
- Corpus 2581 (tls) and 2582 (stream, both backends) pin the ladders byte-identically vs Node.
2026-07-24 02:27:38 -05:00
Chris Tate 10c4613f1f net and dgram misuse throws Node's validation ladders instead of fencing
- net.createServer with a provably-non-object argument, connect option bags (literal, computed-key, and runtime-record spellings) walking Node's Socket-constructor order — the objectMode trio's ERR_INVALID_ARG_VALUE, validatePort's ERR_SOCKET_BAD_PORT, host's string and autoSelectFamily's boolean contracts — and the autoSelectFamilyAttemptTimeout range ladder (validated and then inert, the single-dial simplification's sibling); setDefaultAutoSelectFamilyAttemptTimeout validates from 1 and keeps Node's sub-10ms floor.
- listen's and dgram.createSocket's options.signal answer validateAbortSignal's ERR_INVALID_ARG_TYPE for provably-non-AbortSignal values; plausible signals keep the fence (abort-driven close has no lowering yet).
- dgram.Socket.send replicates Node's signature shuffle over DOM arguments: slice type/bounds contracts (ERR_BUFFER_OUT_OF_BOUNDS over Buffers, subarrays, DataViews, and strings), the buffer-list per-element contract with the list as the Received tail, the unconnected port ladder with the specific-type tail and trailing period, the null/undefined-only address gate, and the connected-state ERR_SOCKET_DGRAM_IS_CONNECTED arms — a fully-validated unconnected single-payload send RUNS; callback/list/connected forms meet the post-validation fence. The dgram state errors now carry Node's ERR_SOCKET_* codes, and dgram bind/connect/close callbacks accept checked-dynamic wrappers through the dynCheck boundary.
- Corpus 2579 (net, both backends) and 2580 (dgram) pin the ladders byte-identically vs Node; order-parity baselines re-recorded.
2026-07-24 02:02:52 -05:00
Chris Tate 2090c8807a The DOM gains the island kind: SCR_DYN_JSVAL wraps engine values by reference
- scr_dyn_from_jsval scalar-normalizes at wrap time (engine numbers/strings/booleans/null/undefined become native DOM kinds through the strict exits), so JSVAL nodes only ever hold engine objects/arrays/functions; the ops install like the promise-release hook, keeping the always-linked DOM core island-free
- typeof, truthiness, String(), and === route to the engine per use (scr_dyn_jsval_ops), the narrowing helpers (scr_dyn_isl_typeof_is/is_array/is_error) answer the engine's truth, and scr_jsval_from_dyn unwraps the SAME cell back — the identity round trip, nested members embedding by reference
- every un-armed DOM walk throws the loud "on an island value held in 'unknown' is not supported yet" ladder: keyed writes, method dispatch, calls, callback checks, 'in', spread, Object.keys/values/entries/hasOwn/assign/defineProperties, JSON.stringify/%j, structuredClone (options included), util.inspect (engine typeof named), deepStrictEqual (engine-identical answers true honestly), and dc subscription — never a silent wrong answer, never a false "is not a function"/"not iterable" claim
- assert's SameValue and strict_eq compare the ENGINE values (two wraps of one value are equal; a DOM copy is a different object, Node's answer too), and failure reports render wrapped operands as the depth-elided [island value] placeholder (the handle stance)
2026-07-24 01:58:31 -05:00
Chris Tate ccf0f04d4c fs misuse throws Node's validation ladders instead of fencing
- The checked-dynamic fs.*Chk lane (JS sources) replicates Node-order argument validation over DOM values: exists/mkdtemp/readFile callback contracts, mkdtemp's prefix slot, assertEncoding on readFile/opendirSync/stream options, watchFile's path/listener pair, createReadStream/createWriteStream's options.fd and path contracts, fs.read's buffer/fd/offset/length/position ladder, and the lchmod family's cb/path/mode ladder with parseFileMode's octal-string and uint32-range arms — each throwing Node's exact ERR_INVALID_ARG_TYPE / ERR_INVALID_ARG_VALUE / ERR_OUT_OF_RANGE texts.
- A validated pass meets the honest tail: the real operation where one exists — fs.exists is REAL now (async answers through the callback, Node's synchronous-false wart for unvalidatable paths kept), mkdtempSync runs through the ladder, lchmodSync/fsPromises.lchmod apply lchmod(2) on macOS and answer Node's not-a-function / ERR_METHOD_NOT_IMPLEMENTED shapes elsewhere — or the compiler-rendered SC2020 fence thrown AFTER validation (scr_throw_lowering_fence), so Node's errors always come first.
- error.propTypeThrow joins argTypeThrow as the property-flavored always-throwing lowered arm; scr_dyn_prop_type_fail / scr_dyn_arg_value_fail / scr_num_received become shared runtime vocabulary for the remaining option-bag ladders.
- Destructured sub-namespace bindings (const { promises } = fs) resolve as the module they are, so fs.promises members reach their lowerings through that spelling.
- Corpus 2578 pins the whole surface byte-identically on both backends; order-parity baselines re-recorded for the new fixture.
2026-07-24 01:33:36 -05:00
Chris Tate 9f422210b1 Object.create(null) lands as the null-prototype dictionary; other prototypes fence by name
- Static builds mint a DOM object flagged prototype-free: own-member walks are already Node's null-proto answers; inspect prefixes [Object: null prototype] (nested/empty/beyond-depth forms), o.toString() throws Node's "is not a function" (dyn.toString carries the source spelling), and deepStrictEqual gains the prototype gate, diff rendering included
- Under --dynamic Object.create routes through the engine's own Object.create for null and engine-held prototypes — live delegation, shadowing writes, and mutation-through-proto all Node-exact
- DOM and static-record prototypes fence with the reason instead of the design doc's own-copy stance: Object.keys/inspect/JSON answer the EMPTY own set in Node and prototype mutations show through live, which no copy answers honestly
- Element writes on plain dyn receivers ride dyn.keySet generally (the result[key] = value memo idiom; unmapped static-any receivers probe their lowered world), unblocking the dictionary's use
- Corpus 2581 (static, byte-for-byte both backends) and 2582 (@dynamic engine route); diagnostics object-create-fences pins the three named fences
- Documented residue: String()/template/join over a null-proto DOM value answer [object Object] where Node throws — the emitted String(unknown) walkers are never-throw by contract
2026-07-24 01:23:17 -05:00
Chris Tate c5a67d24f5 Lower keyed writes and array destructuring over checked-dynamic receivers
- Runtime-keyed writes on receivers that lower dyn (untyped evolving lets holding object literals, JSON.parse results, unknown slots) emit dyn.keySet — number/bool/dyn keys stringify per ToPropertyKey, values convert into the DOM with closures crossing as the callable kind — and the dyn-keyed read stringifies bool/dyn keys the same way, so an object built up through runtime keys dispatches its stored closures back out.
- Array destructuring over dyn sources packs once through the new scr_dyn_iter_pack (arrays element-by-element, strings by code point, bytes by byte) and binds positions as DOM index reads: holes, past-the-end undefined, defaults firing exactly on undefined, and nested patterns all ride it; non-iterable kinds throw V8's exact destructuring TypeError, spelled statically for identifier and identifier-callee sources, kind-worded otherwise.
- dyn.keySet's strict-mode TypeError now quotes the primitive receiver's rendering ("on number '5'"), V8's wording; rest elements over dyn sources and dyn-source defaults that cannot convert into the DOM keep honest fences.
- Fixtures 2578-2581: the warning-catcher harness shape (runtime-keyed writes of closures plus the Object.keys map arm), DOM keyed-write ops with Node's non-object-receiver TypeErrors, the destructure family with all TypeError wordings, and the island keyed write under --dynamic — byte-identical against Node through both backends, plain and sanitized.
2026-07-24 01:11:33 -05:00
Chris Tate 48f9854fb3 Variadic-spread Object.assign lands over checked-dynamic targets
- Object.assign(target, ...sources) packs every source into one fresh DOM array before any copying (JS's ArgumentListEvaluation: a throwing spread leaves the target untouched), then copies own enumerable keys left to right and answers the target — identity, like JS
- V8's spread TypeError texts split by position: the single-last spread keeps the optimized apply-path texts (nullish spells the expression), every other position takes the iterator-protocol texts describing the value (scr_dyn_pack_push_spread_iter)
- scr_dyn_assign's copy walk now rides the entries walk, so array/string/bytes sources contribute their index keys exactly as Object.keys lists them (two-arg form included)
- Targets: dyn values, fresh object literals (no alias exists — the DOM build is unobservable), and nullish units (Node's ToObject TypeError, catchably)
- Corpus 2578 pins the support.js shape, identity, ordering, source-kind matrix, and every error text byte-for-byte on both backends
2026-07-24 00:50:02 -05:00
Chris Tate 9c277a3016 Buffer pre-dial writes on deferred sockets — the queued agent request's head no longer drops
- a socket whose dial hasn't started (the agent's maxSockets queue, the caller-lookup wait) has no fd but is logically connecting: write bytes now buffer and flush at establishment instead of silently dropping, which left the queued exchange (and the program) hanging
- all five differential fixtures (2573-2577) match Node byte-for-byte
2026-07-24 00:25:35 -05:00
Chris Tate 0423093a99 Node's validation ladders throw where implemented APIs were fenced
- Buffer compare/equals/constructor ladders, URLSearchParams WHATWG brand checks and arity ladders, URL.revokeObjectURL arity, and the max-listeners family answer Node's exact typed errors
- ToPrimitive string-hint coercion runs user toString/valueOf with throw propagation; assert.throws accepts expected-object variables
- Reusable always-throwing libCall shapes (error.nodeThrow, error.argTypeThrow) seed the remaining validation-ladder work
- Two emittedReadable lifecycle bugs fixed; ERR_* codes stamped across the bytes/crypto/fs range ladders

# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-07-23 23:37:03 -05:00
Chris Tate 53bad7830e Typed-lane socket compat: pause/resume/setNoDelay/destroySoon, finish, bytesWritten/readable, handle identity ===, Agent type maps to dyn
- the static net.Socket lane gains pause()/resume()/setNoDelay() (chaining, like Node), destroySoon(), on/once('finish'), and the bytesWritten/readable reads — the same runtime entry points the dyn dispatcher uses
- strict equality over runtime handle kinds is pointer identity (c.pause() === c — JS object equality; validator and both backends)
- http/https Agent TYPES map to dyn (the value IS the DOM handle), the stdlib member fence steps aside for dyn-mapped receivers, and ClientRequest joins DYN_HANDLE_KINDS so typed client requests box by reference
- fallback declarations catch up (Socket flow control, the full Agent surface incl. getName/defaultPort/protocol); fixture 2575 pins flavor-independent dyn Buffer at()/slice()
2026-07-23 23:33:20 -05:00
Chris Tate b06540dce6 The max-listeners argument ladders answer Node's exact errors
- Instance setMaxListeners over a non-number routes through the checked-dynamic ladder (emitter.setMaxChk): ERR_INVALID_ARG_TYPE with the rendered Received tail, the existing ERR_OUT_OF_RANGE range gate for numbers, and a well-typed dyn still applies.
- events.defaultMaxListeners is a real module property now: reads answer the process-wide default, writes (statement and expression position) validate through emitter.setDefaultMaxChk with Node's own message slot ("defaultMaxListeners"); the qualified static EventEmitter.setMaxListeners(n) shares the ladder under its "setMaxListeners" slot, and the per-target static form with a provably-non-emitter target throws ERR_INVALID_ARG_TYPE on "eventTargets".
- error.argTypeThrow joins error.nodeThrow as the generic always-throwing lowered arm whose Received tail renders at runtime.
- Corpus 2560 pins the ladders byte-identically on both backends.
2026-07-23 22:55:52 -05:00
Chris Tate a6f43649c5 URL.revokeObjectURL's arity contract and fs._toUnixTimestamp
- URL.revokeObjectURL() with no argument throws Node's ERR_MISSING_ARGS ('The "url" argument must be specified') — the check runs before any registry lookup, so the zero-argument contract is exact without blob machinery; the one-argument form and createObjectURL keep their fences.
- fs._toUnixTimestamp lowers over a DOM time value: numeric strings pass ToNumber's loose-equality gate, finite numbers pass (negatives answer now/1000, Node's shape), and everything else throws Node's exact ERR_INVALID_ARG_TYPE with the rendered Received tail.
- Corpus 2559 pins both ladders byte-identically on both backends.
2026-07-23 22:42:55 -05:00
Chris Tate 82cc41ab0a Checkpoint: dyn-handle compat members and the http Agent surface
- ServerResponse/IncomingMessage/Socket members that fenced on checked-dynamic values now dispatch: flushHeaders, getHeaders, setTimeout, cork/uncork with a real writableCorked counter and coalescing flush, writeHead's raw-array form, write/end encodings and deferred callbacks, req.pause/resume with buffered drain, destroyed/readable/bytesWritten/res.req reads, setNoDelay, destroySoon, end(cb), and dyn Buffer at()/slice()
- new http.Agent/https.Agent lowers to a checked-dynamic handle: literal option parsing, getName, destroy, sockets/requests/freeSockets snapshots, settable defaultPort, and REAL maxSockets accounting (over-limit requests defer their dial and queue); keepAlive: true throws the named pooling fence
- the request path threads the agent (http.requestAgent/https.requestAgent rows, both backends); dyn keys now stringify for indexing and the in-operator over dyn receivers (dyn.hasKey)
- differential fixtures 2573-2577 pin both families against Node
2026-07-23 22:40:41 -05:00
Chris Tate ccb4cfa1ae The emittedReadable flag follows Node's emitReadable_/read() lifecycle
- emitReadable_ clears emittedReadable AFTER the 'readable' emit and only when it really fired — a listener observes true during the pass, exactly Node's readable.js.
- read() clears the flag for absent sizes too (Node's `if (n !== 0)` sees NaN there); only read(0) leaves it standing.
- Corpus 2558 pins the lifecycle byte-identically on both backends. The suite's needReadable/resumeScheduled probes remain blocked by the documented stream-tick station divergence (stream ticks drain at the next checkpoint instead of interleaving the user nextTick queue), not by flag semantics.
2026-07-23 22:34:03 -05:00
Chris Tate 528ef595d4 URLSearchParams' WHATWG argument ladders answer Node's exact errors
- Method values through Function.prototype.call/apply with a receiver that is provably not a URLSearchParams throw ERR_INVALID_THIS ('Value of "this" must be of type URLSearchParams') — the brand check runs before any conversion; searchParams-typed and dyn receivers keep their existing paths.
- Too few arguments throw ERR_MISSING_ARGS with Node's per-method messages before any conversion (the WHATWG arity ladder).
- Name/value slots convert with ToPrimitive's string hint: statically-string args lower directly, symbols throw V8's 'Cannot convert a Symbol value to a string' TypeError, and every other value coerces through the DOM — a user toString/valueOf member is CALLED and its throw propagates (scr_dyn_string_coerce_js, the new dyn.toStringCoerce libCall); func-carrying records box just their toString member into a DOM carrier.
- error.nodeThrow is the new compiler-resolved Node-parity throw for always-throwing lowered arms (the global.undefRead polymorphic-result pattern), shared by these ladders and future ones.
- Corpus 2557 pins the whole surface byte-identically on both backends, including the stringifier's post-sort re-serialization.
2026-07-23 22:23:26 -05:00