docs/waves/engine_debug_connector_plan.md
Engine Debug Connector Plan
Status: D0-01 through D0-04 done; later external and mutable capabilities remain deferred
Owner lane: engine-wasm + browser
Related reference:
docs/waves/ENGINE_DEBUG_CONNECTOR_RESEARCH.md
Purpose
Define an attachable, read-only debug connector for the running WML engine so a host-integrated debugger or external local tool can observe bounded runtime state and event flow without mutating core engine behavior.
This is a diagnostics surface, not a transport/runtime control plane. D0-01 defines contracts and ownership, and D0-02 implements the bounded engine-owned recorder and sanitized snapshot source. Host sessions, Tauri commands, and activation policy are implemented by D0-03. Consumer polling cadence, UI, and bounded capture are implemented by D0-04.
D0-01 Decisions
- The engine owns debug event/snapshot DTOs and, in D0-02, the recorder and sanitization boundary.
- The browser host owns enablement policy, opaque process-local sessions, lifecycle, and, in D0-03, IPC commands.
- The debugger consumer owns presentation and polling cadence only; it never masks raw data after the fact or controls runtime semantics.
- The connector is disabled by default in every profile. A local host policy must enable it before
openDebugSession; the open request contains no enable or unmask override. - MVP supports one concurrent host-owned session. Multi-session support is a future additive capability change.
- Polling is the only MVP delivery model. There is no push channel or network-listening debugger.
- Event order is defined only by decimal-string
seq.monotonicTimeMsis runtime-logical, monotonic time for operator context and is never a correctness or ordering key. No wall-clock timestamp crosses the contract. - Sensitive unmasking is unsupported. Masking happens before values enter debug DTOs.
- All lifecycle operations return typed success/failure unions. Debug failures are non-fatal and must not mutate WML state, navigation, focus, script state, timers, or render output.
Canonical Contract Sources
The cross-language source of truth is Rust:
engine-wasm/engine/src/engine_debug_contract.rs: shared event, snapshot, lifecycle, failure, boundedness, and redaction DTOs plus the generated connector interfaceengine-wasm/engine/src/contract_codegen.rs: engine TypeScript projectionbrowser/src-tauri/src/contract_types.rs: browser-host projection of the shared Rust DTOsbrowser/src-tauri/src/bin/generate_contracts.rs: browser TypeScript projection
Generated consumers are:
engine-wasm/contracts/generated/runtime-dtos.tsbrowser/contracts/generated/engine-host.tsbrowser/contracts/engine.ts
engine-wasm/contracts/wml-engine.ts re-exports the generated DTOs and exposes the connector as a
separate host-owned interface. It is intentionally not part of WmlEngineCommon; D0-01 does not
promise native/WASM runtime methods or session behavior.
Additive Debug Connector Surface
openDebugSession(request) -> EngineDebugOpenSessionOutcome
pollDebugEvents(request) -> EngineDebugPollEventsOutcome
getDebugSnapshot(request) -> EngineDebugSnapshotOutcome
closeDebugSession(request) -> EngineDebugCloseSessionOutcome
Request sequencing:
openDebugSession({ protocolVersion: 1 })returns an opaquesessionId, initial cursor, and capabilities when local policy is enabled.pollDebugEvents({ sessionId, cursor, maxEvents })returns a bounded batch and next cursor.getDebugSnapshot({ sessionId })returns a sanitized bounded point-in-time view correlated bycapturedSeq.closeDebugSession({ sessionId })releases the host session. Close is idempotent: a session already closed by the same host returns success withclosed: false.
Session identifiers and cursors are opaque strings, process-local, non-persistent, and invalid after host restart. Decimal sequence strings preserve exact ordering across JavaScript and Rust without exceeding JavaScript’s safe-integer range.
Boundedness Baseline
The generated ENGINE_DEBUG_CONTRACT_BASELINE pins the D0 defaults:
| Limit | Value |
|---|---|
| Protocol version | 1 |
| Enabled by default | false |
| Concurrent sessions | 1 |
| Engine event capacity | 2048 |
| Default events per poll | 100 |
| Maximum events per poll | 256 |
| Snapshot runtime variables | 256 |
| Snapshot timers | 64 |
| Text value bytes | 4096 |
D0-02 must use fixed-capacity drop-oldest storage. A cursor older than the retained window is not a
fatal error: polling resumes at the oldest retained event and reports the exact unavailable count
in droppedCount. Malformed, foreign-process, or forward cursors fail with INVALID_CURSOR.
Snapshot collections report totalCount, returnedCount, and truncated. Runtime variables are
ordered by variable name; timers remain in deterministic runtime scheduling order; postfields
remain in request order. Values exceeding maxTextBytes use the non-value-bearing omitted shape
with bounded-output, rather than returning a partial secret.
Event Model
Every EngineDebugEvent contains:
seq: decimal monotonic sequence and sole ordering keykind: stable event-kind literalmonotonicTimeMs: runtime-logical monotonic time- optional
cardId - typed
payload
Initial kinds:
deck.loadcard.entercard.exitfocus.changeinput.edit.startinput.edit.draftinput.edit.commitinput.edit.cancelaction.acceptaction.externalnav.intentpostfield.resolvescript.invokescript.traptimer.scheduletimer.firetimer.cancel
postfield.resolve records fields in request order and identifies each resolution source as
variable, draft, card, or fallback. An event is valid only when kind matches the payload
variant. D0-02 must reject mismatched construction internally rather than publish ambiguous data.
Snapshot Model
EngineDebugSnapshot is a bounded explanation surface, not a memory dump. It includes:
- protocol version and captured sequence
- active card id and focused link index
- focused input edit name and sanitized value
- deterministically ordered runtime variables and collection summary
- sanitized pending external navigation target/request fields
- bounded timer summaries and collection summary
- event-buffer oldest/latest sequence, capacity, and cumulative dropped count
- viewport columns, sanitized base URL, and content type
No snapshot field grants mutation capability or implies a stable internal storage layout.
Sensitive Data and Redaction
EngineDebugValue is a discriminated union:
visible: carries a bounded valuemasked: carries only a reason, never the original valueomitted: carries only a reason, never the original or truncated value
The D0-02 sanitization implementation must apply, at minimum:
passwordinput types ->password-input- case-insensitive sensitive names such as
pin,pass,passwd,password,secret,token,credential, andauth->sensitive-name - variables derived from masked fields -> the same masked classification
- URLs containing user information or credential-bearing query material ->
credential-bearing-url - transport authorization/cookie/credential material ->
transport-secretor complete omission - policy-hidden or oversized values ->
policyorbounded-output
Raw transport credentials and cookies are not valid engine debug inputs and must never enter the engine recorder. Frontend-only masking is insufficient and forbidden as the primary control.
Failure Semantics
All errors use stable EngineDebugErrorCode, a deterministic non-sensitive message, and
retryable:
| Code | Meaning | Retryable |
|---|---|---|
DEBUG_DISABLED |
Local host policy has not enabled the connector | false until policy changes |
UNSUPPORTED_PROTOCOL_VERSION |
Requested version is not 1 |
false |
SESSION_LIMIT_REACHED |
The single MVP session is already open | true after close |
SESSION_NOT_FOUND |
Poll/snapshot session is absent or expired | false; reopen |
INVALID_CURSOR |
Cursor is malformed, foreign, or ahead of the source | false; reopen/snapshot |
INVALID_REQUEST |
Bounds or request fields are invalid | false until corrected |
DEBUG_SOURCE_UNAVAILABLE |
Recorder/source cannot currently serve data | true |
INTERNAL_ERROR |
Sanitized implementation failure | true |
An unknown or already-closed session on closeDebugSession is the idempotent success
{ closed: false }; SESSION_NOT_FOUND applies to poll and snapshot operations. Implementations
must not include raw internal errors, WML values, URLs, credentials, or panic payloads in messages.
Runtime and Host Determinism
- When disabled or unattached, the recorder is inert and cannot change runtime allocation order, navigation ordering, render output, or timing semantics.
- D0-02 instrumentation runs synchronously at existing deterministic runtime boundaries and does no blocking I/O.
- D0-03 controls recorder activation through the host policy/session lifecycle; the engine still owns event ordering and snapshot construction.
- Consumer polling rate never changes event sequence or runtime behavior.
- Closing or losing a consumer cannot fail, pause, or back-pressure the WML runtime.
Delivery Sequence and WBP-06/F0 Gate
D0-01lands the additiveEngineDebug*namespace and generated projections first.WBP-06and F0-01 through F0-03 are complete in a separate frame/input namespace.- The completed
F0-01frame/input types do not rename, fold into, or reuseEngineDebug*DTOs. Debug snapshots may reference future frame identifiers only through a later additive contract. D0-02implements the engine recorder/sanitizer against this baseline without session or UI ownership.D0-03implements host policy, open/poll/snapshot/close commands, and engine activation glue.D0-04implements an optional first-party consumer and capture/export workflow.
This sequence removed the contract-file collision that blocked WBP-06/F0. D0-02 and D0-03 are complete without changing the frame/input namespace; D0-04 remains the independently sequenced consumer work.
Delivery Status and Deferred Work
D0-02 (implemented)
- Runtime emission points cover the contract event families at existing deterministic boundaries.
- The engine-owned source uses fixed-capacity drop-oldest storage with decimal sequence/cursor and exact retained-window drop accounting.
- Snapshot construction bounds and orders variables/timers and reports collection/buffer summaries.
- Password inputs, sensitive names and derivations, credential-bearing URLs, transport material, and oversized values are masked or omitted before entering debug DTOs.
- Recorder activation/deactivation hooks remain separate from D0-03 host policy and sessions.
- Native and WASM tests cover event/snapshot parity, ordering, overflow, and secret canaries.
D0-03 (implemented)
- The local
WAVES_ENGINE_DEBUG_POLICY=enabledhost policy is the only enablement path. Missing, empty, differently cased, or otherwise unknown values remain disabled; no request can override policy or masking. - Four generated Tauri commands expose open, bounded poll, bounded snapshot, and idempotent close
through the existing
EngineDebug*outcome unions and restricted main-window capability. - Opaque UUID session ids are process-local and non-persistent. The broker retains at most one id, enforces the protocol-v1 single-session limit, and rejects poll/snapshot access from any other id.
- Opening a session starts a fresh engine-owned recorder and returns its initial cursor. Closing the
active session disables and releases the recorder; closing an unknown or already-closed id returns
{ closed: false }without affecting a newer session. - Poll bounds, decimal cursor validation, retained-window gap accounting, producer-side masking, and bounded snapshots remain engine-owned. Host lock/source failures map to deterministic, non-sensitive typed errors.
- Native host lifecycle tests cover disabled policy, protocol/session limits, cursor gaps, sanitized errors, snapshots, idempotent close, and close/reopen identity rotation. Generated frontend tests cover the four-command mapping and guarded outcome validation.
D0-04 (implemented)
- The existing docked/detached Developer Tools workspace includes an optional read-only Inspector. It opens one generated D0-03 session, takes bounded snapshots, and polls cursor-ordered batches only while an Inspector surface is visible.
- Frontend memory retains at most 512 projected events and renders at most 200 matching rows. Producer cursor gaps and frontend drop-oldest counts remain separate. Filters use one fixed event family and an 80-character query.
- Stop, session error, and application disposal cancel polling and close the process-local session; a later start requests a fresh identity. Typed failures remain isolated from ordinary browsing.
waves-engine-debug-capture-v1.jsonhas a 256 KiB UTF-8 ceiling and an explicit versioned allowlist. It omits session ids, wall-clock time, credentials, request bodies, raw WML/source, arbitrary errors, and masked originals. Full lifecycle, capacity, schema, and security details are recorded inbrowser/ENGINE_DEBUG_INSPECTOR.md.- Frontend tests cover disabled policy, open/poll/snapshot/close, the one-session limit, cursor gaps, sanitized errors, close/reopen, hidden polling pause, unmount cleanup, repeated capacity pressure, secret canaries, and keyboard/accessibility semantics.
Later capabilities
- external local tool bridge
- optional multi-session capability
- any remote transport, if separately designed and security-reviewed
Mutable debugger commands, raw secret access, runtime control bypasses, and a network-listening inspector remain outside the MVP.