docs/waves/maintenance_work_items.md
Waves Maintenance Work Items
Purpose: track maintenance, quality, and technical-debt work that should be folded into normal delivery.
Status keys:
todoin-progressblockeddone
How To Use
- Keep items scoped and testable.
- Link each item to concrete files and acceptance checks.
- Prefer pairing one maintenance item with each feature/contract ticket when feasible.
Current Queue
Completed maintenance tickets are archived in:
docs/waves/MAINTENANCE_WORK_ITEMS_ARCHIVE.md
M1-00 Architecture hardening sprint (next in line)
Status:in-progressScope:
browser/engine-wasm/transport-rust/
Build:
- Execute remaining
M1-*items as the active maintenance sprint before new feature expansion.
Accept:
- Sprint checklist and status are reflected in:
docs/waves/WORK_ITEMS.mdbrowser/README.mdengine-wasm/README.mdtransport-rust/README.md
Notes:
- Active execution is currently anchored to
docs/waves/SPRINT_PLAN_2026-03_MASTER_PRIORITIZED.md. M1-14,M1-15, and the firstM1-16/browser responsiveness slices landed in#109and#110.- The
M1-08boundary-module split is complete. Record any newly discovered hotspot as an additive ticket instead of reopening the closed item.
M1-14 Browser host boundary hardening (CSP + DOM injection sinks)
Status:donePriority:P0Files:
browser/src-tauri/tauri.conf.jsonbrowser/frontend/src/app/browser-shell-template.tsbrowser/frontend/src/app/defaults.tsbrowser/frontend/src/main.ts
Build:
- Replace permissive renderer hardening posture (
csp: null) with explicit CSP suitable for the Tauri-hosted app shell. - Remove/contain HTML string interpolation paths for boot URL rendering (
innerHTMLbootstrap template path). - Keep startup URL normalization deterministic and safe for attribute/DOM insertion.
Tests:
pnpm --dir browser/frontend testpnpm --dir browser/frontend buildcd browser/src-tauri && cargo test
Accept:
- Renderer does not rely on disabled CSP.
- Boot URL flow does not allow unescaped HTML insertion surfaces in shell bootstrap.
- Existing browser shell behavior remains intact in local and network modes.
Notes:
- Security audit follow-up for high-severity host boundary risk.
- Landed explicit production and development CSP policies in Tauri config (
csp+devCsp) instead of permissive/null posture. - Boot URL assignment path is contained to post-mount input property assignment (no runtime URL interpolation into shell HTML template strings).
M1-15 Engine parser recursion guardrails (untrusted deck DoS hardening)
Status:donePriority:P0Files:
engine-wasm/engine/src/parser/wml_parser/actions.rsengine-wasm/engine/src/parser/wml_parser/nodes.rsengine-wasm/engine/src/parser/wml_parser/mod.rsengine-wasm/engine/src/engine_tests.rs
Build:
- Add bounded recursion/depth and node-count guardrails in parser tree walks for card/action/node traversal.
- Ensure maliciously deep nested markup fails deterministically with recoverable parse errors (no runtime crash/stack overflow).
Tests:
cd engine-wasm/engine && cargo test- Add regression fixtures/tests for deeply nested WML payload rejection behavior.
Accept:
- Engine rejects pathological nesting with deterministic error surface.
- No process crash or panic path from crafted deep deck structures.
Notes:
- Security audit follow-up for high-severity parser crash/DoS risk.
- Parser traversal now enforces explicit nesting-depth and visited-node budgets with deterministic
error surfaces (
Parse limit exceeded: ...) across action, card-node, inline-node, and text-extraction walks. - Added parser-level and engine-level regression tests for depth and node-budget exhaustion paths.
M1-16 Transport/engine payload size guardrails (memory pressure hardening)
Status:donePriority:P1Files:
transport-rust/src/lib.rstransport-rust/src/responses.rsengine-wasm/engine/src/engine_public_api.rsbrowser/contracts/transport.ts
Build:
- Enforce explicit response-body/deck-size limits before decode/parse.
- Keep error mapping deterministic when payload limits are exceeded.
- Avoid unnecessary memory amplification for oversized payload paths.
Tests:
make test-rust-transportcd engine-wasm/engine && cargo test- Add fixture coverage for over-limit payload rejection.
Accept:
- Oversized responses/decks are rejected with stable transport/engine error surfaces.
- Normal-size WML/WBXML paths remain unchanged.
Notes:
- Security audit follow-up for medium-severity resource exhaustion risk.
- Landed transport-side explicit
PAYLOAD_TOO_LARGEclassification and host contract regeneration in#109. - Added host/engine oversized deck boundary coverage in Tauri and WASM tests in
#109. - Current baseline rejects oversized payloads deterministically before decode/parse escalation in the active transport/engine/browser handoff path.
M1-17 Network fetch destination policy guardrails (SSRF/probing reduction)
Status:donePriority:P1Files:
transport-rust/src/lib.rsbrowser/src-tauri/src/lib.rsbrowser/contracts/transport.tsdocs/waves/TECHNICAL_ARCHITECTURE.md
Build:
- Define explicit destination policy for
fetch_deckhost command (default-safe behavior + documented override for developer workflows). - Keep policy consistent with WAP/WML navigation expectations and existing transport contract boundaries.
Tests:
make test-rust-transportcd browser/src-tauri && cargo test- Add policy-path tests for blocked/allowed destination classes.
Accept:
- Host fetch path has documented/tested destination constraints appropriate for desktop host threat model.
- External-intent navigation remains deterministic under policy decisions.
Notes:
- Security audit follow-up for medium-severity SSRF/internal probing exposure if renderer compromise occurs.
M1-18 network::wsp codec is dead code relative to the live native fetch path (2026-07-23)
Status:donePriority:P2Files:
transport-rust/src/network/wsp/pdu.rstransport-rust/src/network/wsp/header_block.rstransport-rust/src/network/wsp/header_registry.rstransport-rust/src/network/wsp/encoding_version.rstransport-rust/src/network/wsp/session.rstransport-rust/src/network/wsp/decoder.rstransport-rust/src/network/wsp/encoder.rstransport-rust/src/wsp_registry.rstransport-rust/src/wsp_capability.rstransport-rust/src/native_fetch.rsdocs/waves/NETWORK_PROFILE_DECISION_RECORD.mddocs/waves/TRANSPORT_RUST_PHASE_PLAN.mddocs/waves/WORK_ITEMS.md(T0-20,T0-27)
Finding:
transport-rust/src/network/wsp/*(~2,700 lines acrosspdu.rs,header_block.rs,header_registry.rs,encoding_version.rs,session.rs,decoder.rs,encoder.rs) is a well-structured, bounds-checked, fixture-tested WSP/PDU codec. It is dead code on the live fetch path:grepconfirms nothing outside its own#[cfg(test)]modules calls into it exceptwsp_registry.rs(thin re-export wrapper) andwsp_capability.rs(consumed bynetwork::wsp::session/pdu, not the reverse) — and neitherwsp_registry::*norwsp_capability::*has any caller outside their own test modules either. The chain is self-contained and never reaches production code.- The actual production native-transport fetch path (
transport-rust/src/native_fetch.rs,encode_connectionless_request/decode_connectionless_wsp_reply/decode_content_type_value/decode_text_string/decode_uintvar/encode_uintvar, roughly lines 335-600) hand-rolls its own minimal uintvar/content-type/header/PDU parsing from scratch and does not importcrate::network::wspat all.
Why the split exists(evidence, not guesswork):
network::wsp::*landed 2026-03-06 in#77(feat(wsp): add header registry and code-page policy baseline, closing ticketT0-20) and#78(test(transport): add get reply interop replay baseline).native_fetch.rslanded the very next day, 2026-03-07, in#89(“Native WSP Fetch”, closing ticketT0-27, “Native connectionless WSP GET fetch path”).T0-27’s own written plan indocs/waves/WORK_ITEMS.mdexplicitly directs: “Reuse existing WDP adapter and WSP codec/session modules rather than introducing browser-side protocol logic”, and lists the new file’s intended path astransport-rust/src/network/native_fetch.rs(nested undernetwork/, alongsidewdp/andwsp/).- What actually shipped is
transport-rust/src/native_fetch.rs(top level, not nested undernetwork/). It does reusecrate::network::wdpas directed, but it never importscrate::network::wspand instead reimplements the WSP wire codec from scratch.T0-27was nonetheless markeddone. - No commit message, PR description, or doc found anywhere in
docs/waves/explains or acknowledges this divergence from the ticket’s own stated plan. This reads as unplanned implementation drift (the WSP codec module and the fetch executor were built one day apart by the same author but never actually wired together), not a deliberate “reference implementation for later” design — there is no ADR-style note asserting that as intent. Say so plainly rather than inventing a rationale. - Compounding the confusion:
docs/waves/NETWORK_PROFILE_DECISION_RECORD.mdnameswap-net-core(which the doc defines as thenetwork::wsp-based in-processWDP -> WTP -> WSPstack) the “active implementation target profile” and listswsp_registry.rs/wsp_capability.rsas its fixture lane;docs/waves/TRANSPORT_RUST_PHASE_PLAN.mdlabelswap-net-core“(current)”. Both are accurate about profile intent but easy to misread as “this is what runs” — the byte-level codec actually executing on the native fetch path today is the hand-rolled one innative_fetch.rs, notnetwork::wsp.
Decision(repo owner, 2026-07-23):
- Do not delete
network::wsp::*— it is wanted for future use and is the spec-conformant target implementation. - Do not wire it into
native_fetch.rsin the near term — too large a change to fold into an unrelated pass; treat as a dedicated follow-up. - A short doc comment now sits above
native_fetch.rs’s hand-rolled encode/decode section pointing back at this entry so the split isn’t a landmine for the next person who finds two WSP parsers and doesn’t know which one is real.
Recommendation:
- Open a dedicated follow-up ticket (“wire
network::wspintonative_fetch.rs”) to replace the hand-rolledencode_connectionless_request/decode_connectionless_wsp_reply/decode_content_type_value/decode_text_string/decode_uintvarfunctions with calls intonetwork::wsp::{encoder,decoder,pdu,header_block}, preserving the existingFetchDeckResponsemapping andTRANSPORT_PROFILE_WAP_NET_COREgating behavior. This should close the gap between the documentedwap-net-coreprofile and what the profile actually executes. - Until that lands, treat
network::wsp::*test coverage as spec-conformance regression coverage only, not evidence about the live fetch path’s correctness.
Accept(for the follow-up ticket, not this entry):
native_fetch.rs’s WSP wire encode/decode calls intonetwork::wspinstead of duplicating parsing logic.cargo teststays green fortransport-rust; native fetch behavior is unchanged for existing passing fixtures/smokes.docs/waves/NETWORK_PROFILE_DECISION_RECORD.md/TRANSPORT_RUST_PHASE_PLAN.mdupdated to reflect thatwap-net-coreis genuinely live end-to-end, not just profile-gated.
Resolution(2026-07-24): Implemented in this pass, overriding the 2026-07-23Decisionto defer, per explicit repo-owner instruction. TheDecisionandWhy the split existssections above are preserved as the historical record of why the split existed and why deferral was originally chosen; they are no longer the active call.
- Added
transport-rust/src/network/wsp/connectionless.rs, which owns the connectionless (session-less, WDP/UDP) WSP framing:encode_connectionless_request,decode_connectionless_reply,encode_uintvar/decode_uintvar,decode_content_type_value,decode_text_string,decode_wsp_status_code, plus typedWspConnectionlessEncodeError/WspConnectionlessDecodeErrorenums. It reusesnetwork::wsp::header_registryassigned numbers (encode_pdu_type/decode_pdu_typefor theGet/Post/Replyoctets,encode_header_field_name_on_pagefor well-known header names) and thenetwork::wsp::header_blocktypes (WspHeaderBlock/WspHeaderField/WspHeaderNameEncoding) as its header representation.network::wspis therefore on the live fetch path, no longer test-only. - Deleted the hand-rolled codec from
native_fetch.rs(encode_connectionless_request,encode_connectionless_request_headers,encode_connectionless_content_type,decode_connectionless_wsp_reply,encode_uintvar,decode_uintvar,decode_content_type_value,decode_text_string,decode_wsp_status_code,decode_well_known_media, and the localNativeReplystruct) along with theNOTE(wsp-codec-duplication)comment block that pointed at this entry.native_fetch.rsnow owns no wire-format code: it keeps only fetch-level mapping (encode_native_wap_request,negotiated_accept_media,connectionless_request_headers,build_kannel_request_uri). - Scope note:
network::wsp::pdu/sessioncould not be called directly without changing bytes on the wire. Their fixture-defined framing is the connection-oriented form (no transaction id, single-octet block lengths,u16reply status, text-string header values), while the gateway path speaks the connectionless form (transaction id prefix,uintvarblock lengths, single-octet assigned-number reply status, short-integer well-known values). Sharing happens at the assigned-number/header-model layer instead;pdu.rs/session.rsremain the connection-oriented reference implementation. - Fixed a latent encoder bug carried over from the deleted code:
encode_uintvarset the continuation bit on the final octet for values >= 128, so any request URI or POST header section of 128+ bytes emitted a length field a conformant peer would mis-parse. The shared implementation sets the continuation bit only on non-final octets; covered byuintvar_roundtrips_multi_octet_values_with_canonical_continuation_bits. - Tests: codec-level cases moved into
connectionless.rs(get_request_uses_transaction_id_pdu_type_and_uintvar_uri_length,post_request_encodes_content_type_header_block_and_body,post_content_type_rejects_nul_bytes,decode_content_type_value_*,reply_*,status_code_mapping_covers_each_assigned_class);native_fetch.rskeeps the fetch-level wire assertions and gainsencode_native_wap_request_rejects_unsupported_methodsandconnectionless_request_headers_negotiate_a_single_accept_media_type.make lint-rust-transportandmake test-rust-transportpass. - Not done here:
docs/waves/NETWORK_PROFILE_DECISION_RECORD.mdanddocs/waves/TRANSPORT_RUST_PHASE_PLAN.mdwere left unchanged; thewap-net-coreprofile documentation refresh remains open.
M1-19 fetch_policy.rs pre-flight destination check is shallow relative to the enforced check (2026-07-24)
Status:donePriority:P3Files:
transport-rust/src/fetch_policy.rs(classify_destination_host,validate_fetch_destination)transport-rust/src/fetch_runtime/execution.rs(PolicyDnsResolver)transport-rust/src/native_fetch.rs(resolve_fetch_destination_addresses)
Finding:
classify_destination_hosttreats every non-localhost/*.localhosthostname asPublic— a fast pre-flight check only. The authoritative SSRF guard is the DNS-resolution-time check (PolicyDnsResolverinexecution.rs, andresolve_fetch_destination_addressesfor the native path), confirmed present, correctly ordered, and tested. Not a live hole today, but nothing stops a future refactor from trusting the shallow pre-flight check in isolation and quietly dropping the resolution-time enforcement.
Recommendation:
- Add a regression test (or an inline invariant comment referencing the resolution-time check) that fails loudly if
validate_fetch_destinationis ever called as the sole gate without the resolver-level check also running.
Accept:
- A future refactor that removes the resolution-time check without also updating the pre-flight check fails a test, rather than silently reopening the class of bug.
Resolution:
- Added
transport_resolution_time_check_blocks_private_answer_that_shallow_preflight_alone_would_allowintransport-rust/src/tests/fetch_mapping.rs, which asserts the shallow pre-flight (classify_destination_host/validate_fetch_destination) passes a non-localhost domain, then assertsvalidate_resolved_destination_addresses(the shared function backing bothPolicyDnsResolverand the native path) independently rejects a simulated private DNS answer for that same host. Combined with the pre-existinghttp_client_rejects_private_dns_answer(execution.rs) andresolve_destination_socket_addr_rejects_private_peer_under_public_only(native_fetch.rs) wiring tests, a removal of either resolution-time call site now fails a test. No enforcement logic changed.
M1-20 Gateway-bridged fetch profile force-sets AllowPrivate with no invariant test
Status:donePriority:P3Files:
transport-rust/src/fetch_runtime/execution.rs(destination_policyoverride forwap:///waps://traffic)transport-rust/src/gateway.rs(GATEWAY_HTTP_BASE)
Finding:
- For the default
gateway-bridgedprofile,destination_policyis force-set toAllowPrivatebecause the upstream target is the operator-configuredGATEWAY_HTTP_BASE, not the original WAP URL. This is intentional and appears safe (the gateway base is env-configured, not attacker-influenced) but rests entirely onGATEWAY_HTTP_BASEnever becoming attacker-settable — there’s no test pinning that invariant.
Recommendation:
- Add a test asserting
GATEWAY_HTTP_BASEis read only from process environment/config, never from request-supplied data, so a future change can’t silently make it attacker-influenced without breaking a test.
Accept:
- A regression test exists that would fail if
GATEWAY_HTTP_BASE(or its equivalent) became derivable from untrusted input.
Resolution:
- Added
transport_build_gateway_request_ignores_request_supplied_gateway_base_overridesintransport-rust/src/tests/request_gateway_policy.rs. It callsbuild_gateway_requestwith headers and URL path/query attempting to smuggle an alternate base (GATEWAY_HTTP_BASE/X-Gateway-Http-Base/X-Forwarded-Host/Hostheaders, matching query key) and asserts the resolved gateway URL is unaffected — first against the default base, then against an operator-configuredGATEWAY_HTTP_BASEenv value, confirming the env value always wins over any request-supplied data. No enforcement logic changed.
M1-21 fetch_host.rs gateway-transport-fallback host scope unconfirmed
Status:donePriority:P3Files:
browser/src-tauri/src/fetch_host.rs(default_fetch_transport_fallback)transport-rust/src/gateway.rs
Finding:
default_fetch_transport_fallback()(the gateway-bridged fallback path for failedwap/wapsGET requests) readsWAVES_FETCH_TRANSPORT_FALLBACKwith no host/scope restriction visible infetch_host.rsitself; enforcement of where theGatewayBridgedprofile can actually connect lives entirely intransport-rust/gateway.rs, outside this file’s audit scope when it was originally reviewed.
Recommendation:
- Confirm (with a test, not just a read-through) that the gateway transport path is constrained the same way
native_fetchis — i.e. it can’t be pointed at an arbitrary host via this fallback env var.
Accept:
- A test demonstrates the gateway-fallback path cannot be redirected to an arbitrary non-configured host.
Resolution: Invariant confirmed to hold — no gap found.
WAVES_FETCH_TRANSPORT_FALLBACKis a strict two-value enum switch (disabled|gateway-bridged) parsed indefault_fetch_transport_fallback(); it carries no host/URL value itself and cannot smuggle one. When it selects the fallback,fetch_deck_with_transport_executoralways retries with the hardcodedFetchTransportProfile::GatewayBridged, which routes through the samebuild_gateway_request/GATEWAY_HTTP_BASEmachinery pinned by the M1-20 test — never a value derived from the fallback env var, request headers, or the originalwap://URL’s own host/port. Addedfetch_deck_command_gateway_fallback_cannot_be_redirected_by_fallback_env_valueinbrowser/src-tauri/src/tests/fetch_commands.rs, an end-to-end test using the real (unmocked) transport: a native attempt to an unreachable loopback port triggers the fallback, and the fallback is proven to land only on a local TCP listener bound to the test’sGATEWAY_HTTP_BASE, receiving the original wap path unchanged. No enforcement logic changed.
M1-22 Residual ad hoc Result<_, String> on live transport paths (2026-07-24)
Status:todoPriority:P3Files:
transport-rust/src/gateway.rs(build_gateway_request)transport-rust/src/fetch_body.rs(ReadBodyError::ReadFailed(String))transport-rust/src/wbxml_decoder.rs(decoder returnsResult<String, String>throughout)transport-rust/src/wbxml.rs(decode_wmlc)transport-rust/src/native_fetch.rs(build_kannel_request_uri)transport-rust/src/responses.rs(decode_textual_wml_payload,decode_utf16_payload)
Finding:
- The live fetch modules historically used ad hoc
Result<_, String>while thenetwork::subsystem used per-module typed error enums. The M1-18/M1-19 pass (2026-07-24) converted the highest-value call sites it already touched: destination validation/resolution now returnsfetch_policy::FetchDestinationError(classification is by variant, not by message prefix), and connectionless WSP encode/decode now returnsWspConnectionlessEncodeError/WspConnectionlessDecodeError. - The remaining
Result<_, String>surfaces above were deliberately left alone to keep that pass scoped. None of them currently drive a classification decision by string matching, so this is readability/robustness debt rather than a live defect — but each is one refactor away from becoming another string-matched boundary.
Recommendation:
- Convert the listed functions to typed errors module by module, smallest blast radius first (
gateway.rs, thenfetch_body.rs, thennative_fetch::build_kannel_request_uri, then the WBXML decoder chain, which is the largest and should be its own change). - Preserve the exact rendered message text at every public boundary; the transport error codes and messages in
FetchDeckResponsemust not change. - Do not convert error strings that are already asserted verbatim by fixtures without updating the fixture in the same change.
Accept:
- No live transport module returns a bare
Result<_, String>for a multi-variant failure. - Error classification anywhere in
transport-rustis by type/variant, never by message content. make lint-rust-transportandmake test-rust-transportstay green; public error codes and messages are unchanged.
M1-23 Script error taxonomy label table is hand-mirrored in TypeScript instead of generated (2026-07-24)
Status:donePriority:P3Files:
browser/frontend/src/app/browser-presenter.ts(SCRIPT_ERROR_CATEGORY_LABELS)engine-wasm/engine/src/engine_script_types.rs(ScriptErrorCategoryLiteral/ the category enum it mirrors)browser/scripts/generate-contract-wrappers.mjsengine-wasm/contracts/wml-engine.ts
Finding:
browser-presenter.tsdefinesSCRIPT_ERROR_CATEGORY_LABELSas a hand-writtenRecord<string, string>mapping each script-error category to a human-facing label, with a comment above it admitting it “mirrors the engine’sScriptErrorCategoryLiteraltaxonomy.” Nothing generates this table and nothing drift-checks it against the Rust enum it mirrors — it is exactly the cross-language duplication patternM1-11/M1-12closed for wire types, but that codegen pipeline was never extended to cover human-facing label/taxonomy tables. A new script-error category added to the engine falls back to a generic label in the host UI until someone remembers to hand-edit this unrelated file.
Recommendation:
- Extend
browser/scripts/generate-contract-wrappers.mjs(or a small sibling generator) to emit the label table from the Rust source of truth — either from doc comments/attributes on the enum variants, or from a small#[derive]-driven constant table exported alongside the existingts-rscontract types — and havebrowser-presenter.tsimport the generated table instead of hand-authoring it. - Cover this under
pnpm --dir browser run contracts:checkso a missing/stale label fails CI the same way a stale wire type does today. - This is a small, scoped codegen extension, not a new pipeline; do not build a second, parallel generator.
Accept:
SCRIPT_ERROR_CATEGORY_LABELS(or its replacement) is generated, not hand-authored.- Adding a new script-error category on the Rust side and forgetting the TypeScript label fails
contracts:checkinstead of silently falling back to a generic label. - No behavior change to today’s rendered labels.
Resolution:
ScriptErrorCategoryLiteral, its serialized literals, and its UI labels now come from one macro-backed Rust definition inengine_script_types.rs; thenonefallback remains intentionally unlabeled.- The existing Rust contract generator emits
SCRIPT_ERROR_CATEGORY_LABELSintobrowser/contracts/generated/engine-host.ts, and the existing AST wrapper generator discovers and re-exports that value frombrowser/contracts/engine.ts. - Contract generation rejects any non-fallback category whose Rust metadata has no label. A focused generator regression covers that rejection, and an end-to-end temporary-category check confirmed
pnpm --dir browser run contracts:checkexits nonzero with the missing category named in the error. browser-presenter.tsimports the generated table. Presenter tests pin all four existing labels and preserve the genericscript errorfallback fornone, unknown, and absent categories.engine-wasm/contracts/wml-engine.tsis unchanged because the runtime wire shape and semantics did not change; the generated table is host presentation metadata, not a new engine method or payload field.
M1-03 Engine API generator design and bootstrap (non-priority)
Status:todoFiles:
engine-wasm/contracts/wml-engine.tsbrowser/contracts/engine.tsscripts/(new generator entrypoint)docs/waves/CONTRACT_REQUIREMENTS_MAPPING.md
Build:
- Add a generator path that can emit TypeScript engine API/types/facade scaffolding from engine-owned source metadata.
- Keep this item as non-priority within the sprint (design/prototype unless explicitly promoted).
Tests:
- Generator dry-run in CI or local script check.
Accept:
- Manual engine API sync burden is reduced with a documented generate path and prototype output.
M1-08 Split high-churn files into boundary modules
Status:doneFiles:
engine-wasm/engine/src/lib.rs(done)engine-wasm/engine/src/engine_runtime_internal.rs+engine_runtime_internal/*(done)engine-wasm/engine/src/parser/wml_parser/*(done)browser/frontend/src/main.ts(done)browser/src-tauri/src/lib.rs+browser/src-tauri/src/engine_bridge/*+browser/src-tauri/src/tests/*(baseline split done)transport-rust/src/lib.rs+transport-rust/src/fetch_runtime/*+transport-rust/src/tests/*(baseline split done)browser/frontend/src/app/browser-controller.ts+browser/frontend/src/app/shell-event-bindings.ts+browser/frontend/src/app/keyboard-intent-router.ts(residual split done)
Build:
- Move code into boundary modules (
api,state,actions,mapping,ui) without broad behavior changes.
Tests:
- Existing project test/build commands remain green.
Accept:
- High-churn files are reduced and responsibilities are easier to review.
Notes:
- Engine-side decomposition has landed and merged.
- Browser and transport boundary decomposition baselines have landed.
- Browser-side follow-up landed additional probe/timer/focused-edit coordinators in
#109. - Residual cleanup landed:
browser-controller.ts(1021 lines) still mixed shell/DOM event-listener binding, run-mode orchestration, keyboard-intent routing/queueing, and transport-URL loading. Extracted two more boundary modules following the existing constructor-injected coordinator pattern:ShellEventBindings(DOM listener wiring – binds/unbinds all shell button/input/window event listeners against a caller-supplied action map, owns the#btn-backelement directly) andKeyboardIntentRouter(keyboard-intent resolution/routing plus the serialized keyboard action queue that guards against interleaving with a concurrent engine timer tick). BrowserController still defines what each routed action does and owns run-mode orchestration and transport-URL loading directly; it is now 764 lines. Both new modules have their own isolated unit tests (shell-event-bindings.test.ts,keyboard-intent-router.test.ts). No behavior changes; this was a pure structural extraction. - Remaining scope is residual opportunistic cleanup only if new hot files emerge or a boundary proves unstable under feature work; do not let this preempt active compliance/runtime tickets.
M1-09 Engine-host frame interface migration execution
Status:todoFiles:
docs/waves/ENGINE_HOST_FRAME_MIGRATION_PLAN.mddocs/waves/ENGINE_HOST_FRAME_WORK_ITEMS.mdengine-wasm/contracts/wml-engine.tsbrowser/src-tauri/src/contract_types.rsbrowser/contracts/generated/engine-host.ts
Build:
- Execute the
F0-F4migration program to move active hosts onto structured frame/input contracts. - Keep migration additive and parity-gated until legacy path retirement.
Tests:
cd engine-wasm/engine && cargo testcd browser/src-tauri && cargo testpnpm --dir browser/frontend testpnpm --dir browser/frontend build
Accept:
- Active host paths use
EngineFrame+EngineInputEvent. - Contract and coverage docs are updated in the same migration PRs.
M1-10 Engine contract parity guardrail hardening
Status:doneFiles:
browser/scripts/generate-contract-wrappers.mjsengine-wasm/contracts/wml-engine.tsbrowser/contracts/engine.tsbrowser/contracts/generated/engine-host.ts.github/workflows/ci.yml
Build:
- Keep engine contract wrappers generated from generated host-contract exports, not manual sync.
- Enforce drift through deterministic codegen checks in CI (
contracts:check) and keep wrapper alias surfaces explicit.
Tests:
pnpm --dir browser run contracts:check- repo-hygiene CI lane remains green.
Accept:
- Contract-surface drift across engine canonical, generated host contract, and browser wrapper aliases is blocked for covered contract types.
Notes:
- Parity-script lane retired in favor of generator-owned wrappers plus codegen drift checks.
- Guardrail now enforced through
contracts:checkin repo hygiene and browser-shell CI lanes.
M1-11 Browser contract-wrapper codegen alignment
Status:doneFiles:
browser/contracts/engine.tsbrowser/contracts/transport.tsbrowser/contracts/generated/*browser/src-tauri/src/bin/generate_contracts.rsbrowser/scripts/generate-contract-wrappers.mjs
Build:
- Make browser wrapper contracts thin generated aliases/adapters with no duplicate hand-authored shape definitions.
- Ensure wrapper drift checks fail if wrapper outputs diverge from generated source contracts.
Tests:
pnpm --dir browser/frontend exec tsc --noEmitpnpm --dir browser run contracts:checkin repo hygiene lane.
Accept:
- Browser contract wrappers no longer require manual shape synchronization beyond intentional adapter aliases.
Notes:
- Contract wrapper generation now emits
browser/contracts/engine.tsandbrowser/contracts/transport.tsfrombrowser/scripts/generate-contract-wrappers.mjs. - App-specific transport session/history types moved to
browser/contracts/transport-app.tsto keep generated wrappers contract-focused. - Wrapper generation now runs through
browser/scripts/generate-contract-wrappers.mjswith AST-derived export sets from generated contract files (no static wrapper blobs in Rust). - CI drift enforcement for wrappers/contracts uses
pnpm --dir browser run contracts:check.
M1-12 Engine/transport error taxonomy artifact generation
Status:doneFiles:
browser/contracts/generated/engine-host.tsbrowser/contracts/generated/transport-host.tsbrowser/contracts/engine.tsbrowser/contracts/transport.tsbrowser/scripts/generate-contract-wrappers.mjsdocs/waves/SPEC_TEST_COVERAGE.md
Build:
- Keep transport and engine error-taxonomy surfaces sourced from generated contracts and wrapper aliases.
- Avoid parallel manual error-union definitions outside generated/wrapper contract modules.
Tests:
pnpm --dir browser run contracts:check- Browser/frontend typecheck validates alias consumer compatibility.
Accept:
- Error-code/class/category surfaces are generated and parity-checked instead of manually synchronized.
Notes:
- Standalone taxonomy fixture/check lane retired.
- Taxonomy drift control now relies on generated contract source + wrapper codegen drift checks.
- Transport and engine consumer surfaces use generated contract exports/aliases rather than duplicated manual unions.
M1-13 Contract schema fixture generation and validation lane
Status:doneFiles:
browser/contracts/generated/*engine-wasm/contracts/wml-engine.tsbrowser/contracts/engine.tsbrowser/contracts/transport.tsbrowser/scripts/generate-contract-wrappers.mjs.github/workflows/ci.yml
Build:
- Keep wrapper contract generation deterministic and derived from generated contract modules.
- Keep CI focused on codegen drift checks and compile-time consumer validation rather than additional parallel parity scripts.
Tests:
pnpm --dir browser run contracts:checkpnpm --dir browser/frontend exec tsc --noEmit
Accept:
- Contract drift checks are codegen-driven and resilient to formatting/ordering changes.
Notes:
- AST-driven wrapper generation landed in
browser/scripts/generate-contract-wrappers.mjs. - Standalone parity-schema fixtures/scripts retired in favor of codegen-first drift checks.
M0-07 Historical backlog pruning pass
Status:todoFiles:
docs/waves/WORK_ITEMS.mddocs/waves/WORK_ITEMS_ARCHIVE.md(new)docs/waves/MAINTENANCE_WORK_ITEMS.mdREADME.md
Build:
- Move pre-implementation kickoff/planning-only ticket groups that are no longer actionable into a dedicated archive file (
docs/waves/WORK_ITEMS_ARCHIVE.md), not inline in active boards. - Keep active queues focused on executable work items only.
Tests:
- Manual consistency pass for active-vs-archive file boundaries, status markers, and README references.
Accept:
- Active boards only contain actionable work; historical tickets remain preserved in archive file with traceable IDs.
M0-08 Cross-board status sync cadence
Status:todoFiles:
docs/waves/WORK_ITEMS.mddocs/waves/WORK_ITEMS_ARCHIVE.mddocs/waves/MAINTENANCE_WORK_ITEMS.mdbrowser/README.mdengine-wasm/README.mdtransport-rust/README.mdREADME.md
Build:
- Define and document a standing rule: when a ticket status changes, update corresponding board and README “next slice/checklist/snapshot” pointers in the same PR.
- Keep updates additive and deterministic across layers.
Tests:
- Manual PR checklist verification against one representative ticket transition.
Accept:
- Status transitions no longer leave board/readme mismatches after merge.
M0-09 Board file-reference hygiene sweep
Status:todoFiles:
docs/waves/WORK_ITEMS.mddocs/waves/WORK_ITEMS_ARCHIVE.mddocs/waves/MAINTENANCE_WORK_ITEMS.mddocs/wml-engine/work-items.mdengine-wasm/README.mdbrowser/README.mdtransport-rust/README.md
Build:
- Run a docs reference sweep to replace stale file paths and module names after refactors (for example parser/module splits).
- Ensure file references in tickets remain specific enough to guide implementation but avoid dead-path drift.
Tests:
- Manual path-existence pass for referenced file paths in active and archive boards.
Accept:
- Board/readme file references resolve to existing paths or clearly intentional globs; no known dead file paths remain.
M0-10 Archive index and movement policy
Status:todoFiles:
docs/README.mddocs/waves/WORK_ITEMS.mddocs/waves/WORK_ITEMS_ARCHIVE.mddocs/waves/MAINTENANCE_WORK_ITEMS.md
Build:
- Add a short “active vs archive” index entry in docs navigation and define when tickets move into archive files.
- Document that archived tickets are historical and reactivation requires new follow-up IDs on active boards.
Tests:
- Manual docs navigation pass from
docs/README.mdto active and archive boards.
Accept:
- Contributors can quickly locate active vs archived work boards and follow one consistent archival rule.