Repository navigation
RUST-0/1: Rust core feasibility and isolated WebAssembly foundation - #130
Merged
Merged
Conversation
RUST-0 research lane: architecture feasibility report, migration matrix, staged migration roadmap, and the isolated std-only spike that ports the UTF-16 offset boundary and the WD1.2 span-patch algebra to Rust. The spike's differential harness runs 167,316 cases against src/vrml/edit.js with 0 regressions; three mutation controls are detected. Research and evidence only. No production caller, no production dependency, no Electron, CI or release change. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
RUST-1: a Rust workspace under crates/ with two crates: - wrlforge-text (std only): UTF-16 <-> UTF-8 offsets, strict UTF-16 decoding, WD1.2 edit algebra (applyEdits, mapOffset, mapRange) with UTF-16 canonical ordering, line index, immutable text snapshots. - wrlforge-wasm: wasm-bindgen 0.2.129 boundary. Every JS string is gated by native String.prototype.isWellFormed before conversion, so an unpaired surrogate is refused (EENCODING) and never becomes U+FFFD. One isolated session type with WeakMap-branded facade objects; stale, foreign, forged and disposed handles fail closed. spikes/rust-1-wasm-boundary/ adds the reproducible build, a Node 24 proof (18 tests), a reusable differential framework (177,582 cases, RUST-0 digests reproduced, 0 unexplained regressions, 4 negative controls detected), boundary benchmarks, and an isolated Electron renderer proof under the unchanged editor CSP (12/12 checks). Isolated: no production caller, no package.json change, no change to main.js, preload.js, renderer, CI or packaging. macOS 15+ untested. RUST1-PRECISION remains proposed-pending-owner. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
RUST-1A follow-up to RUST-1 (Draft PR #130). Spike-only; no production caller, no production file changed, no dependency added. Defect A: the UTF-16 gate trusted String.prototype.isWellFormed, which a caller can replace. A lying method let malformed UTF-16 become U+FFFD silently (144/324 calls per lying mode, facade and raw exports). The gate now treats the method's answer as a claim and checks the converted text against the source code units: each U+FFFD must sit over a genuine 0xFFFD unit. Substituted surrogate -> EENCODING; lying false claim or a changed non-surrogate unit -> EENGINE. Defect B: a wasm trap poisoned only the engine that saw it; another engine on the same instance kept answering. Poison is now keyed to the wasm instance (glue.RawSession), so every engine over a trapped instance refuses. Separately loaded instances are unaffected. Adds adversarial UTF-16 gate and session-lifetime tests, a BigInt precision review of the 225 RUST1-PRECISION cases (registry unchanged, still proposed-pending-owner), and documents the raw-handle trust boundary, the charCodeAt/encodeInto trust root, and per-revision metadata growth as open limits. Release wasm SHA-256 56f678b0ac92af6f0c0698831e40280267613ae82ff2c7bb05ec475c8f609455 (66,609 bytes). Differential: 177,582 cases, 0 unexplained regressions, candidate digest unchanged. Linux only; macOS 15+ untested. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
There was a problem hiding this comment.
🟡 Changes recommended
Poison enforcement, differential independence, precision gating, and Electron CSP proof checks have unresolved correctness gaps.
6 open findings
Enforce poison guard in current() · New Require evalBlocked in production proof · New Validate all non-fatal loader blocking results · New Fail when Rust mappings disagree with oracle · New Use independent oracle for unsafe integer checks · New Update benchmark metadata for five-input workload · New
🧠 Review effort: Balanced
TAURI-RUST-MIGRATION-1. A new WRL Forge desktop app in apps/desktop-tauri with no Electron and no Node.js runtime. The Electron app is unchanged and remains the behaviour reference. - crates/wrlforge-vrml: direct Rust port of src/vrml tokenizer and parser plus Scene Tree / Inspector read projections. Parity with the JS parser on 673/673 files (65 fixtures + 608 new-items/item-categories). - crates/wrlforge-document: one canonical source buffer, revisions, undo/redo as exact inverse span edits through wrlforge-text (WD1.2), and a line-break view mapping so CRLF / lone CR / BOM survive a textarea. - src-tauri: port of src/editor/file-io.js (gzip by magic bytes, verified temp write, backup, atomic rename, external-change refusal, gzip preservation), Rust-owned sessions, native dialogs, no path from the WebView, in-window --smoke harness. - ui: Leptos 0.8 CSR compiled to Wasm; X_ITE 15.1.10 preview through a narrow, temporary JS adapter. Status and module map: docs/architecture/TAURI_RUST_MIGRATION_1.md. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
TAURI-RUST-MIGRATION-2. Port src/vrml/node-schema.js to wrlforge-vrml::node_schema: the query API is hand-translated; the data is a field-for-field projection of the committed JS file, generated by scripts/build-rust-node-schema.js (with --check). No field, type, default or constraint is invented. scripts/check-rust-node-schema-parity.js proves value equality against the JS module (54 nodes, 544 fields, 10 node classes, all counts). Signed-off-by: Ryan Bundy <ascendance@skate.fm>
…ctions TAURI-RUST-MIGRATION-2. Port src/vrml/field-edit.js (WD2-B) to wrlforge-vrml::field_edit: the same nine SF types, stable reason ids, Annex A lexical gates, schema numeric bounds (never clamped), token-span-only patches and re-parse round-trip proof. Node identity without a WD1.4/WD1.5 port: the caller names a node by its exact UTF-16 span in one revision; exactly one node must sit there, and after the edit the re-parse must find exactly one node at the shifted span with the same type, DEF, field and values. Stricter than JS: nodes in PROTO, EXTERNPROTO or interface scope refuse, and SFString line breaks refuse. wrlforge-document gains apply_source_transaction (one undo step; the result must equal the planned text) and map_span, which carries a span through the exact changes of an undo/redo or loses it, never guesses. The Rust tokenizer now skips a leading U+FEFF as signature trivia while spans still count it, so BOM files parse cleanly. Deliberate difference from the JS tokenizer; JS/Rust parity on test/fixtures stays 65/65. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
TAURI-RUST-MIGRATION-2. Node items in the Inspector show schema-typed fields with per-component controls. The UI sends raw text with the item id and the revision the Inspector was built from (doc_edit_field); Rust validates the revision, node, field, type and value, applies one verified transaction and returns the new view and node id. The editor, Scene Tree, Inspector and live preview adopt only Rust's reply. Refusals are shown in the Inspector and leave the document unchanged. Undo/redo carry the selected node through the exact changes (map_span); inputs bind the live value property so undo shows the old value. smoke.sh --inspector drives a real Inspector edit through the DOM (refusals, apply, preview, undo, redo, save); Rust independently checks the saved bytes differ from the typed-edit expectation by exactly the one intended token. Verified headless on copies of new-items/item-categories/decorative/velvet-thornwing.wrl in gzip, LF, BOM+CRLF and lone-CR forms (27/27 steps each, debug and release). Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Only allowed-items-to-test/cars/ and allowed-items-to-test/misc/ are approved for ad hoc real-item testing. Nothing below that root may be tracked or committed. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
UI-THEME-1. themes.css defines three built-in themes over one semantic --wf-* token contract: tokyo-night (default and the no-attribute CSS fallback), tokyo-night-storm and tokyo-night-light. Colour values are adapted from the MIT Tokyo Night VS Code theme (commit 7c0f11e, v1.1.2); the licence ships beside the stylesheet and the adaptation is recorded in OPEN_SOURCE_PROVENANCE.md (TN-1) and THIRD_PARTY_NOTICES.md. style.css now reads only tokens: surfaces, text, borders, buttons and their hover/pressed/selected/disabled states, tabs, tree, focus rings, status states, badges, scrollbars, editor selection/caret and viewport chrome. Syntax and transform-axis roles are reserved, unused. The rendered VRML scene is not themed. protocol::theme holds the theme registry and REQUIRED_TOKENS. Its tests fail on a missing or extra token, a raw colour in style.css, an undefined token reference, an external resource, or a text/control pair below WCAG AA (4.5:1 / 3:1) in any theme. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
…moke
UI-THEME-1. A labelled native <select> in the main toolbar lists the
three built-in themes. A change sets <html data-theme> in the same event
turn, then persists through the narrow theme_get / theme_set commands.
It never touches the document, revision, dirty flag, undo stack,
selection, Inspector or X_ITE scene.
src-tauri/settings.rs owns settings.json in Tauri's app config dir
({"schemaVersion":1,"themeId":...}). Only registry ids are accepted.
Missing, corrupt, oversized, wrong-version or unknown-theme files fall
back to Tokyo Night with a visible notice. Writes are temp + fsync +
read-back + rename and keep unknown keys; a failed write leaves the old
file byte-identical and is reported, non-fatally, in the toolbar. No
path crosses IPC and no capability or CSP change is made.
The UI body stays hidden until the persisted theme is applied (1.5 s
fallback), so a Storm/Light choice never flashes Tokyo Night widgets.
Diagnostics show severity in words and tree rows expose aria-selected,
so neither state is colour-only.
Smoke: --smoke-theme / --smoke-theme-expect / --smoke-theme-notice /
--smoke-theme-save-fails and --config-dir. The in-window run checks 23
surfaces per theme against their tokens, focus-ring visibility, document
invariants, no preview reload, backend rejection of unknown ids, and
Rust re-reads the settings file. smoke.sh always uses a per-file /tmp
config dir, never the user's preferences.
Signed-off-by: Ryan Bundy <ascendance@skate.fm>
UI-SYNTAX-1. wrlforge_vrml::highlight ports the classification of src/editor/language.js to Rust, from ONE parse result: lexical classes from the tokenizer (header, comment, keyword, ROUTE/TO, TRUE/FALSE/NULL, string, number, punctuation, invalid number / unterminated string) and identifier roles from exact AST ranges (node type, PROTO/EXTERNPROTO name, field, interface member, IS target, ROUTE event, DEF name, USE / ROUTE node reference, interface field type). An identifier the tree does not place keeps no class. No second grammar, no regex. Spans are half-open UTF-16 source offsets, sorted and disjoint. Tests cover headers, comments, DEF/USE, PROTO/EXTERNPROTO interfaces, IS, ROUTE, Script text inside strings, multiline strings, numbers, invalid numbers, unterminated strings, recovery, CRLF / lone CR / BOM and astral characters, and every committed vrml fixture (each span is exactly a token or comment of the parse). Dev-time parity harness (Node oracle only, not shipped): spikes/tauri-rust-migration-1/highlight-parity.sh compares the Rust spans with language.js; 65/65 committed fixtures consistent. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
UI-SYNTAX-1. Service::analyze now returns highlight spans from the SAME parse as the Scene Tree and diagnostics (no second parse), mapped through ViewMap to UTF-16 view offsets and sent [gap, len, class]-encoded (protocol::syntax) together with session, revision, view hash and view length. protocol::syntax::SYNTAX_CLASSES maps each class to an existing --wf-syntax-* token; no new colour token and no raw colour. The <textarea> stays the ONLY editing control: its glyphs are transparent, caret, selection, IME, clipboard and scrolling are native, and every edit still goes through the Rust document protocol. An aria-hidden, pointer-events:none layer behind it paints the widget's current text with identical text metrics, follows scrolling by transform, and is built from whole-line block chunks (<= 64 lines / ~8K units) so an edit relays out one chunk. Only chunks near the viewport carry colour spans (16,000-element budget). Safety: an analysis colours the text only when session, revision, view hash and view length all match the screen; stale or foreign replies are counted and discarded, and an older analysis no longer replaces a newer Scene Tree / Diagnostics. Between an edit and the next analysis, spans that touch the edit are dropped and later spans move over identical characters. Diagnostics get wavy (error) / dashed (warning) underlines from the same revision; the panel names that revision. A theme change is CSS only: no reparse, no repaint, no document change. Tests: protocol encode/decode, class registry, stylesheet mapping, and WCAG pairs for the underline colours; backend span mapping over BOM, CRLF, lone CR, multiline CRLF strings and astral characters; UI shift / segment / chunk logic. Every smoke run adds 21 syntax steps (colours, registration at four scroll positions, metrics, input interception, typing, paste, rapid input, stale-reply rejection, malformed source, undo / redo, theme recolouring, timing, reload). Opt-in WRLFORGE_SMOKE_ALIGN_HOLD_MS pauses for registration screenshots. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
…evision
Two stale-result races found while proving UI-SYNTAX-1 on real items.
Preview: the debounced preview and a forced one can overlap on a large
file. X_ITE aborts the superseded load ("Replacing world aborted"), and
that late result overwrote the status of the newer, successful load.
Each load now takes a sequence number; only the newest may report.
Scene Tree: the <ul> carries data-revision, the analysis revision its
items and offsets belong to. Root cause of the UI-THEME-1 cyclone smoke
failure: the harness set the analysis signal and clicked at once, but
Leptos re-renders a tick later, so it clicked an item from the startup
analysis -- (77, 122) is NavigationInfo in the original text, before the
22-unit smoke insert; the current span is (99, 144). The harness now
waits for the tree DOM to carry the current revision and reports it as
its own step, and waits for the newest preview load. Product note: a
user click that lands inside the analysis debounce after an edit still
uses the previous revision's offsets (pre-existing; not changed here).
Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Document::last_changes() now holds the SpanChanges applied by the most recent change of any kind -- a typed edit, an Inspector transaction, undo or redo -- so a selection can be carried through typing exactly as it is through history (map_span; never guessed). last_history_changes() stays as the same data for existing callers. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
A Scene Tree item id is a source span of one revision. A click or Enter on the tree is now refused (nothing selected, nothing changed) unless the rendered tree, the held analysis and the document are at the same revision with no edit in flight; the tree shows an updating state and is re-analyzed at once. Diagnostics and Inspector links follow the same rule. - doc_inspect takes the revision and answers Stale for any other one. - The selection carries its session and revision; Rust maps it through each typed edit (EditRequest.item) as it already did for undo/redo, or clears it. An id is never re-read against newer text. - Inspector replies land only for the newest request, the same selection and its revision; Apply on an older Inspector is refused locally. - doc_analyze / doc_inspect are async and parse a copy outside the session lock, so edits never wait behind a parse. - Input path: the pump diffs and hashes UTF-16 units once (no clones or re-encoding), line/column comes from the color layer's line index, and the window title is updated only when the dirty mark changes. - Smoke: 11 selection-safety steps and a per-stage keystroke breakdown. - American spelling throughout (color, behavior, canceled, ...). Measured: large-file keystroke cost is the WebKitGTK textarea's own relayout (cyclone ~188 ms, buswagon ~555 ms); our own share is 11 / 57 ms. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
wrlforge_vrml::create plans ONE new Box / Sphere / Cylinder / Cone object as a pure append to the document: a DEF'd Transform (translation, rotation, scale authored) holding a Shape with Appearance / Material / diffuseColor and the primitive geometry. Input is a closed Primitive choice; no caller-supplied text or template. Refuses non-'#VRML V2.0 utf8' text, blocking syntax errors, recovered nodes and capped parses, so the end of the text is provably top level. The DEF name is one no identifier token in the file uses. The result is re-parsed: old top-level spans unchanged, the exact structure at the planned span, or Refused. New lines use the document's dominant ending. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
File -> New World opens a path-less session over the canonical document (no file, no temp file). A dirty document is replaced only after a native Rust-run confirmation; Cancel changes nothing. Save on an untitled world runs Save As; later saves are the normal backup-first save. Close added. Toolbar Create menu (keyboard accessible, theme tokens) sends only the primitive and base revision to doc_create, which applies the planner's insert as one transaction (one undo step) and returns the new Transform's Scene Tree id for the new revision. The UI selects it in the tree, Inspector and source editor and loads the preview at once. A newly loaded document no longer shows the previous document's Scene Tree. ./smoke.sh --create drives the whole workflow through the real controls under Xvfb; Rust answers the dialogs from its plan and verifies the saved file byte for byte, the backup and the confirmation count. A read-only X_ITE pixel probe proves each object is drawn. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
PRODUCT_VISION.md records the owner's approved goal: one studio that combines direct visual 3D authoring (Spazz3D-inspired) and VRML source authoring (VrmlPad-inspired), built clean-room from public standards over one canonical document. Not a claim of completed features. The architecture tracker now states the approved future custom Rust renderer (not started; X_ITE stays temporary), documents VISUAL-1 and records the two unexplained advenbed2 smoke timeouts. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Remove the obsolete "never build a custom renderer" rule. X_ITE is the temporary working preview renderer; the owner has approved a custom Rust VRML97/X3D renderer as the future engine. It needs its own lane, replaces X_ITE only after compatibility tests pass and the owner approves, and must fit the canonical-document architecture. The README separates what ships today (X_ITE in Electron and the Rust/Tauri app) from the planned renderer, which is not implemented. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
wrlforge_vrml::pick translates the WD2-D resolver (src/editor/viewport-pick.js, PR #117) to Rust with the same steps, statuses and reason ids. It joins every link of an X_ITE hit snapshot to exactly one AST node by exact provenance span (shifted for a stripped BOM), checks node type and the containment chain by identity, and refuses USE / several parents / PROTO bodies / Inline scenes / sensors and Anchors / syntax errors / capped parses. A Shape promotes only to its own simple-object Transform (Box, Sphere, Cylinder, Cone). No name, type, index, offset-proximity or position fallback. Tests cover one Box, two primitives, identical and nested twins, anonymous nodes, DEF without and with USE, repeated USEs, PROTO, Inline, sensors, detached nodes, inexact spans, type disagreement, chain disagreement, and LF / CRLF / lone CR / BOM / Unicode / comments / repeated structures. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
A Select toggle in the viewport toolbar arms an explicit selection mode. A primary-button click (press + release within 4 px) asks the WD2-D X_ITE adapter, copied verbatim into dist/ by build-ui.sh, for a plain-data hit snapshot; doc_pick resolves it in Rust against the exact rendered text (revision + preview hash) and the UI selects the item through the same selection authority as the Scene Tree. Selection never moves the caret, edits the source, marks dirty, adds undo, reloads or saves. Preview generations (session, revision, hash, load number) are retired when a load starts or fails, the document changes, a document opens or closes, or the viewport browser is replaced; late replies are refused. Refusals show a short message and keep the existing selection. Capture listeners only observe the pointer; the pick runs one task after the release because X_ITE's viewer still counts the button as down during the capture phase (found with real X input). Test: smoke.sh --pick runs, per Tokyo Night theme, New World -> Box, Sphere, Cylinder, Cone -> real-viewport clicks -> Scene Tree / Inspector checks -> Transform and Material edits -> stale, late and last-valid-scene refusals -> Undo / Redo / Save As, then the WD2-C0 oracle matrix (P1-P22 + CRLF, CR, BOM, Unicode). Real X clicks are sent only inside the harness's own Xvfb, to a focused window of the app's own process. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Signed-off-by: Ryan Bundy <ascendance@skate.fm>
crates/wrlforge-vrml manipulate: proves a top-level Transform target (root statement index, optional unique unrouted DEF, readable frame, authored translation) and plans a one-axis translation edit through the typed field-edit planner. Other components keep their exact lexemes; a zero-distance move is Unchanged; non-finite and out-of-range values are refused. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Records the inventory of PRs #139-#143, the status, fix commit, evidence and limits of each finding, separates fixes on this branch from older PR #143 fixes, and lists suggested (unposted) GitHub replies. Also corrects the from_native_menu path in APP_SHELL_0.md. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
… copy STACK-REVIEW-1 follow-up to Copilot findings r4242714025 and r4242714046. check-xite-adapters.sh matched lines, so a valid declaration inside a /* ... */ block comment, a template literal or a dead block passed while the real declaration lacked the pinned version. Each adapter is now masked by xite-js-mask.awk (POSIX awk, no Node.js): comments, strings and template literals cannot match, the declaration must be the only write to its name and a direct statement of the one top-level IIFE, and anything the lexer cannot lex with certainty refuses. build-ui.sh verified the X_ITE source and then copied it unchecked. It now embeds through fetch-xite.sh --install, which verifies the copy itself (--verify-copy: pinned SHA-256 per file, no symlink, no unlisted entry) and removes dist/ on refusal. test-xite-guards.sh: 77 offline cases (28 unchanged + 33 declaration + 16 copy). With the previous scripts 18 of the 33 new declaration cases fail. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
PR #144 review 5486909537 (Copilot): r4244998855: fetch-xite.sh copied each manifest file with cp -P and verified only the copy. cp -P keeps only a final link and follows a link in a parent directory, so a linked package/dist/assets became regular files that passed. The fetch now verifies the EXTRACTED tree (version, package.json, a link in any component, hashes) before copying, copies through install_copy (which also refuses a linked source) and verifies the result. --from-tarball runs the same path on a local tarball under the same SHA-256 pin, so the fetch path has offline tests: 14 new cases, 91 total. r4244998880: about_dialog adds six steps, not five; the record says 6. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Independent QA HOLD on e3a1496: build-ui.sh checked the source adapters but never the copies it staged in dist/. - build-ui.sh hashes each adapter source before the source check, checks the sources before copying, and after every copy runs check-xite-adapters.sh --staged: each staged adapter must be a regular file (no symlink, not missing), carry the pre-check SHA-256, be byte-identical to its source, and pass the same declaration check. - Any build-ui.sh failure removes dist/ and exits non-zero. - test-xite-guards.sh: 60 new cases (151 total), including the real build-ui.sh in a temp layout with a test-only cp shim; each refusal must leave no dist/. - gate-linux.sh: one repeatable Linux gate with the exact test inventory. - STACK_COPILOT_REVIEW_1.md: the HOLD, the fix, the tests, the count reconciliation. X_ITE pin (16.5.0) and declaration rules unchanged; no Node.js in the build. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
The first gate-linux.sh run on 4f3fa65 failed at the backend build: tauri.conf.json bundles target/pkg-licenses, which a clean worktree does not have. The gate now takes it from TOOLS, like wasm-bindgen and the X_ITE cache, and documents the node_modules/x_ite prerequisite. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
The 8dba2a1 gate run misread three passing GUI reports: - texture writes texture-report.json, not report.json; - the native harness lists its checks ({name, pass, detail}), it does not count them; - the pick report writes "wrong 0", menu and dock write "0 wrong". Re-scored on that run's real reports: texture 46/46, native 46/46, WRONG_SOURCE_SELECTIONS 3 steps, 0 wrong. A nonzero count still fails. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
PR #130 review: TextSession.current() never checked the instance poison record, so a live session kept minting snapshots after a wasm trap. The poison check now runs first in every session, snapshot and engine call, facade-only ones included. Only the labels engine.poisoned, session.disposed and snapshot.revision stay readable. Test 8b drives two engines over one poisoned instance through every operation and proves a separate instance stays healthy. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
…tive control PR #130 review, two findings in electron/run.cjs: - the production verdict ignored evalBlocked, so the proof passed even if (0, eval)('1') succeeded; it now requires evalBlocked === 'EvalError'; - the negative-control verdict was Object.values(loaders).every(!ok), which is vacuously true for an empty record and ignored neg.fatal; it now requires a non-fatal result that reports fetchThenInitSync, glueDefaultAsync and embeddedBytesAsync, every one blocked. The decision moves to a pure electron/verdict.cjs so it is testable without Electron. The launch, lock, CSP comparison and cleanup are unchanged; renderer/editor.html is untouched. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
PR #130 review: precision-review.mjs counted rustSuccess.oracleRefused (Rust mapped an edit set the independent BigInt oracle refuses) but the exit condition ignored it. Reproduced on the no-ambiguity negative-control build: 1,049 such successes and exit 0. The gate now also requires zero oracle-refused successes; the inexact-success and genuine-regression gates are unchanged. The oracle moves byte-for-byte (function bodies diffed) to harness/bigint-oracle.mjs so the stage check can share it. review(), summarize() and exitCode() are exported for tests; RUST1A_PKG selects another artifact, as in utf16-gate-adversarial. The release review output is byte-identical to the starting SHA. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
…t the JS answer PR #130 review: unsafeIntegerInvolved accepted a case when the JavaScript baseline's own answer contained an unsafe value, so the answer under test justified its own classification. A lying or rounded baseline could turn a safe-in, safe-out Rust refusal into a pending precision difference, and a rounded safe-looking answer could hide a real precision case. The check now reads unsafe INPUT offsets from the case, and proves an unsafe RESULT with the independent BigInt oracle (harness/bigint-oracle.mjs) on well-formed mappings only. It ignores both answers. On the real run the harness summary is identical to the starting SHA apart from timing (225 pending, 0 regressions); the registry is unchanged. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
PR #130 review: loadInputs returns five inputs since the U+FFFD case was added, but the memory note still said four inputs, ~3.6 MB. The note, input count, UTF-16 unit count and UTF-8 byte count now come from describeInputs(inputs): 5 inputs, 5,237,166 units = 5,237,186 bytes, ~5.2 MB (10^6 B). A unitsNote states that the memory figures are MiB. The workload and the memory measurements are unchanged. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
Adds docs/architecture/RUST_1_PR130_REVIEW_FIX.md: the six Copilot findings, root causes, corrections, fix commits, test evidence, remaining risks and suggested (unposted) thread replies. The spike README lists the new modules, tests and the negative precision run. Signed-off-by: Ryan Bundy <ascendance@skate.fm>
fix(rust-1): resolve the six PR #130 Copilot findings
UI-MENU-1: desktop menus and command integration (1.4.5 Beta)
…workspace UI-DOCK-1: magnetic docking and resizable workspace (1.4.5 Beta)
APP-SHELL-0: version 1.4.5 Beta, command registry, docking and menu contracts (Draft)
…tion XITE-16-UPGRADE-2: integrate X_ITE 16.5.0 into the Rust/Tauri app (Draft)
…references RUST-STAB-2B: typed ROUTE, IS, and PROTO reference analysis
RUST-STAB-2A: Rust scope graph and safe reference analysis
RUST-STAB-1: crash recovery and source safety
VISUAL-4B: direct Rotate and Scale tools
VISUAL-4A: source-safe Extrusion authoring (draft)
TEXTURE-LOCAL-1: local textures in the packaged Tauri preview
PKG-LINUX-1: Linux AppImage and .deb for the Tauri app (draft)
NATIVE-RENDER-1: hidden native wgpu viewport (draft, not accepted)
feat(desktop): Rust/Tauri 2 desktop application — first vertical slice (TAURI-RUST-MIGRATION-1)
DJAscendance
marked this pull request as ready for review
October 12, 2026 12:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
crates/), a realwasm32-unknown-unknownrelease artifact, a lossless UTF-16 boundary, one isolated session type, a reusable differential framework, Node and Electron renderer load proofs, and measured boundary costs.Nothing in
main.js,preload.js,renderer/,src/,package.json/lockfile, CI, packaging or release workflows changes. No production module loads anything fromcrates/.RUST-0 findings (
docs/architecture/RUST_0_*.md)RUST-1 scope
Crates
wrlforge-textapplyEdits,mapOffset,mapRange, UTF-16 canonical tie-break, same-offset insertion refusal), line index (LF/CRLF/lone CR, UTF-16 columns), immutable snapshotsstdonlywrlforge-wasmRawSessionBoth crates
#![forbid(unsafe_code)]; no filesystem, network, clock, environment or process access. JS facade:crates/wrlforge-wasm/js/wrlforge-text.mjs.WebAssembly bridge
wasm-bindgen --target web; the facade takes an initialized module and is environment-neutral. Node loads withinitSync({module: bytes}); the renderer was proven with three loaders (fetch +initSync, glue default async, bytes embedded in a JS module — the shape an esbuildbinaryloader produces).__wbg_ptr) are never returned. Facade objects are frozen with no own properties.EENGINE.UTF-16 safety rules
"\uDC00"into"�"silently.JsString; Rust calls nativeString.prototype.isWellFormed()before conversion; ill-formed →EENCODING(field,unit= first unpaired surrogate,index); method missing →EENGINE. Never substituted, never normalized.TextDecoder ignoreBOM: true, tested). CRLF, lone CR, mixed endings and NUL are preserved.EEDITBOUNDARY/EOFFSETSURROGATE), never rounded.Sessions
Proof is module-private
WeakMapmembership, never a number. Rust re-checks a per-session serial and revisions from a never-reset counter capped atNumber.MAX_SAFE_INTEGER. Stale, foreign, forged, prototype-clone, cross-engine and disposed handles fail closed. Transaction verification compares the full text; no hash is trusted. WD1.4 object identity untouched; nothing written into VRML source.Evidence (Linux x86_64; re-run from the committed tree)
wrlforge_wasm_bg.wasm65,892 B, SHA-256a1e99690bf787e8542464f39fe1a1571cb05002e6912dd8ee3c4de0171910963(reproducible across rebuilds)contextIsolation: true,nodeIntegration: false,sandbox: true, no preload, no--no-sandbox; driven byVisualQaRunner, no leaked processrenderer/editor.html's CSP byte-for-byte; loads. Same CSP minus'wasm-unsafe-eval': all three loaders blocked. Existing source is necessary and sufficient;evalstays blocked. No CSP change.RUST0-BOUNDARY3,120,RUST0-ENCODING4,299); pending owner 225 (RUST1-PRECISION); 0 unexplained regressionsnpm run checkRegistry ids are harness classifications, not owner decisions D1–D9 (RUST-0's internal "D1"/"D2" labels are kept only as
rust0Label). No entry approves a production change.Performance (through the real boundary; median ms)
applyEdits(full text in + out)edit.jsapplyEditscheck_text)toUtf8/lineColapplyEditsstatelessopenSessionedit.jsfor a stateless edit. At 1.6 MB a renderer main-thread transfer costs 7–17 ms (≈ one 60 Hz frame). No speed claim is made; no X_ITE rendering claim.docs/architecture/RUST_1_PERFORMANCE.md.Licences and dependencies
wasm-bindgen=0.2.129 andjs-sys=0.3.106 (default features off — drops theunsafe-evalfeature andfutures-util). 13 third-party crates total, all MIT OR Apache-2.0 (unicode-identalso Unicode-3.0) — GPL-3.0-or-later compatible.crates/Cargo.lockcommitted; toolchain pinned to 1.95.0. Build toolwasm-bindgen-cli0.2.129 installed lane-locally (not committed). No White Dune or other upstream code. Productionpackage.jsondependency graph unchanged.Commits
9c97a05e92748fBoth DCO signed-off.
Files
Commit 1 (13, RUST-0; hash-verified against the original RUST-0 worktree):
docs/architecture/RUST_0_CORE_FEASIBILITY.md,RUST_0_MIGRATION_MATRIX.md,RUST_0_MIGRATION_ROADMAP.md;spikes/rust-core-feasibility/{.gitignore, Cargo.lock, Cargo.toml, README.md, differential.js, src/{edit,lib,main,offsets,wire}.rs}.Commit 2 (36, RUST-1):
crates/{.gitignore, Cargo.lock, Cargo.toml, rust-toolchain.toml}crates/wrlforge-text/{Cargo.toml, src/{edit,lib,line_index,offsets,snapshot}.rs}crates/wrlforge-wasm/{Cargo.toml, js/wrlforge-text.mjs, src/{lib,session}.rs}docs/architecture/RUST_1_{BOUNDARY_CONTRACT,PERFORMANCE,WASM_QA}.mdspikes/rust-1-wasm-boundary/{.gitignore, README.md, build.mjs}spikes/rust-1-wasm-boundary/electron/{main.cjs, proof.html, proof-no-wasm-eval.html, proof.mjs, run.cjs}spikes/rust-1-wasm-boundary/harness/{framework.mjs, registry.json, run.mjs, stages/edit-algebra.mjs, stages/planned.mjs}spikes/rust-1-wasm-boundary/{loaders/node.mjs, test/node-proof.test.mjs}spikes/rust-1-wasm-boundary/perf/{bench-core.mjs, inputs.mjs, node-bench.mjs, summarize.mjs}Not committed:
target/, generatedout/, local tool installs.Known limitations
harness/stages/planned.mjs), not implementations.Draft blockers (owner decisions)
RUST1-PRECISIONcases need owner adjudication (status staysproposed-pending-owner).Future requirements, documented only (not implemented here): UX-LOADING-0 — WRL Forge logo during X_ITE scene loading (prefer an application-owned overlay; do not assume
splashScreencan replace the image); APP-SPLASH-0 — optional startup splash, evaluated near product completion, with no artificial delay.Reproduce
See
spikes/rust-1-wasm-boundary/README.md.RUST-1A Follow-up
Commit
63f668038c4c80ae4f853e9a7076021cba15c6ba(DCO signed-off), fast-forward on top ofe92748f. Spike-only: zero production changes (src/,renderer/,main.js,preload.js,package*.json, CI, packaging untouched); no dependency added (encoding_rs@=0.8.42evaluated, not added). Full report:docs/architecture/RUST_1A_BOUNDARY_HARDENING.md. Linux only.Confirmed defects and corrections
String.prototype.isWellFormed, looked up at call time. A replaced method (() => true,1, inverted, or swapped after a session opened) let malformed UTF-16 become U+FFFD silently: 144/324 calls per lying mode, facade and every raw string export.take_texttreats the method's answer as a claim only. After conversion, each U+FFFD must sit over a genuine0xFFFDsource unit (UTF-16 positions are preserved by the WHATWG encoder). Substituted surrogate →EENCODINGwithunit= first unpaired surrogate; lyingfalseor a changed non-surrogate unit →EENGINE. Missing/throwing method stillEENGINE. Reads k units for k U+FFFD, not n.glue.RawSession, module-levelWeakMap). Every engine, existing session handle, and new engine on a trapped instance refuses withEENGINE. Separately loaded instances stay healthy.New regression tests
test/utf16-gate-adversarial.test.mjs— 16 tests: 18 entry points (9 facade, 9 raw) × 8 malformed + 10 valid inputs × 11 method modes (native, missing, deleted after init, true, false, throws,1/2,'yes'/{}/undefined, replaced after acceptance, genuine U+FFFD + surrogate, astral/pairs, inverted). Against thelossy-utf16mutant: 15/16 fail (as required).test/session-lifetime.test.mjs— 10 tests + 1 opt-in heavy real-trap test: open/dispose reuse, revision growth, stale/foreign/forged/disposed handles, instance poisoning, independent instances.harness/precision-review.mjs— independent BigInt oracle over the 225RUST1-PRECISIONcases.isWellFormed, facade + raw).Results (re-run at publication, Linux x86_64, Node v24.21.0, Electron 41.7.1)
cargo fmt --all --checkwasm32-unknown-unknown(-D warnings)cargo test --workspace --lockedtest/*.test.mjsRUST1A_HEAVY=1, run in a 10 GB memory-capped scope)editor.html;evalblocked; no-wasm-unsafe-evalcontrol blocked as expectedeb44188d…7480unchanged from RUST-1utf8-tiebreak646,no-ambiguity1,597,no-boundary2,723,lossy-utf161,108npm run checkArtifact
Release
wrlforge_wasm_bg.wasm: SHA-25656f678b0ac92af6f0c0698831e40280267613ae82ff2c7bb05ec475c8f609455, 66,609 bytes (RUST-1 wasa1e99690…0963). Gluewrlforge_wasm.jsunchanged (aabd7363…760c). Reproduced exactly from a rebuild. Build output is not committed.Performance (Node 24, median ms, RUST-1 → RUST-1A)
checkTextapplyEditscheckTextopenSession+disposeElectron renderer, 1.6 MB stateless edit: 16.6 / 17.3 ms median/p95 (RUST-1: 16.4 / 16.8). Rust is still not faster than
edit.js; full-document transfer dominates (unchanged RUST-2 blocker).Precision review findings
225 cases reviewed: P1 89 · P2 104 · P3 14 · P4 18. All 225 are safe
EOFFSETPRECISIONrefusals; 122 (P2 + P4) are inputs where the existing JavaScript returns a rounded, wrong value. 0 genuine regressions. All 54,437 Rust mapping successes equal the exact BigInt answer. No case is reachable from a real document (no case has all inputs below 2^30). Registry unchanged:RUST1-PRECISIONstaysproposed-pending-owner(SHA-256b7af1f67…c681). Recommendation only: approve asapproved-experimental(harness scope).Known limitations (not fixed)
__wbg_ptrread another session's text). Before production integration, the application must prevent untrusted code from obtaining or invoking raw handles, or must strengthen the raw boundary. Today there is no production caller, so no untrusted boundary exposes it.String.prototype.charCodeAtandTextEncoder.prototype.encodeInto. Verified at publication: a lyingcharCodeAtalone turns"hello"into"AAAAA"silently (RUST-1 and RUST-1A alike); lyingisWellFormedplus lyingcharCodeAtpasses a substitution. No same-realm check can close this; it is documented, not fixed.dispose(200,000 revisions → 4.3 MB). This is live session state, distinct from the reusable wasm memory high-water mark. A bounded-history or equivalent strategy is required before production integration.Still outstanding
RUST_1A_BOUNDARY_HARDENING.md§13; owner selects the tool.RUST1-PRECISIONapproval; revision-record strategy; acceptance of "facade is the isolation boundary, glue stays private" as a RUST-2 requirement.All RUST-0/RUST-1 blockers above remain in force. PR stays Draft.