Skip to content

fix(bridge): the transport is the bridge index.html installs, in tests too - #1014

Merged
cryptskii merged 2 commits into
mainfrom
fix/test-bridge-speaks-production-interface
Sep 26, 2026
Merged

cryptskii merged 2 commits into
mainfrom
fix/test-bridge-speaks-production-interface

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

What

Every transport path branched on window.DsmBridge.__callBin, a function only the jest stub installed: sendBridgeRequestBytes, callBoundaryMethod and callHostMethod each tried it first and fell back to sendMessageBin, the MessagePort transport public/index.html installs — so the transport the tests exercised was not the one the app runs.

Now the transport is the bridge index.html installs and nothing else: sendMessageBin for RPCs (its BridgeRpcResponse unwrapped as posted), startup / ingress / hostRequest for the boundaries (a wrapper failure reaches bridge.error as its message and propagates), __binary for readiness. AndroidBridgeV3 is exactly the seven members index.html installs.

Deleted

  • The pre-wait for dsm-bridge-ready under a 2.5 s timer — redundant: sendMessageBin waits for the port itself.
  • maybeUnframe, a 4-byte length-prefix guess in either byte order: Kotlin never length-prefixes, and no BridgeRpcResponse (its first byte is a field tag) can begin with its own length, so the guess never fit a real answer. Dead code, deleted rather than tested — a control that re-adds it stays green, as it must.
  • The __callBin fallbacks and the request re-encoders in the two boundary files; isNativeHostUnavailableError's match on a test stub's error text; the phantom declarations in bridgeTypes.ts and globals.d.ts (including a dsmBridge global the bridge gate bans).

Tests

The jest setup completes any test bridge that supplies sendMessageBin with the same startup/ingress/hostRequest composition index.html uses (reading sendMessageBin at call time), so every suite runs the production transport; the 20 test bridges now supply sendMessageBin, and two that also carried a sendMessageBin delegating to the phantom lost it. Three suites that had pinned the fallback's behaviour now assert what production does (invalid bridge response for <method>; a boundary failure on bridge.error as its message).

ci/bridge_rpc_names.py also holds the type's members equal to the keys index.html installs, and refuses a production source naming __callBin.

Verification

  • jest: 123/123 suites, 834/834 tests. tsc and lint clean. npm run build exit 0.
  • Mutation controls, each red on its named test: a fallback re-encoding the request in place of the bridge's ingress; a bridge without the port transport accepted. Gate controls, each failing naming the offender: a phantom member on the bridge type; a production source naming __callBin. Files restored byte-for-byte.
  • Purity, flow, scan, forbidden-symbol, bridge-contract, codegen, proto-guard, safety and bridge-RPC-name gates: all exit 0.

Recorded in CONFORMANCE_GAPS.md §6.29. Still Open there: index.html's sendMessageBin rejects a request after 30 minutes on a wall-clock timer, and its port handler tells a response from an async event by whether the first eight bytes match a pending id — transport-side, fail-closed, unchanged here.

…s too

Every transport path branched on `window.DsmBridge.__callBin`, a function
only the jest stub installed: `sendBridgeRequestBytes`, `callBoundaryMethod`
and `callHostMethod` each tried it first and fell back to `sendMessageBin`,
the MessagePort transport `index.html` installs — so the transport the tests
exercised was not the one the app runs. The production branch pre-waited for
`dsm-bridge-ready` under a 2.5 s timer and then proceeded, which was
redundant (`sendMessageBin` waits for the port itself), and ran the answer
through `maybeUnframe`, a length-prefix guess with no producer: Kotlin never
length-prefixes, and no BridgeRpcResponse can begin with its own length, so
the guess never fit a real answer. The bridge type declared the phantom,
`isNativeHostUnavailableError` matched a test stub's error text, and
`globals.d.ts` declared a `dsmBridge` global the bridge gate bans.

Now the transport is the bridge index.html installs and nothing else:
`sendMessageBin` for RPCs, its BridgeRpcResponse unwrapped as posted;
`startup` / `ingress` / `hostRequest` for the boundaries, a wrapper failure
reaching `bridge.error` as its message and propagating; `__binary` for
readiness. The pre-wait, the unframing, the fallbacks, the stub-text match
and the phantom declarations are deleted, and `AndroidBridgeV3` is exactly
the seven members index.html installs. The jest setup completes any test
bridge that supplies `sendMessageBin` with the same wrapper composition
index.html uses, reading `sendMessageBin` at call time, so the production
transport runs unchanged in tests; the 20 test bridges now supply
`sendMessageBin`, and two that also carried a `sendMessageBin` delegating to
the phantom lost it. `ci/bridge_rpc_names.py` also holds the type's members
equal to the installed keys and refuses a production source naming
`__callBin`.

Tests: transportCore.bridge.test.ts (a bridge without the port transport is
refused; a router query goes through the bridge's ingress wrapper),
bridgeDecoding.integration.test.ts (undecodable bytes answered as index.html
answers them; a boundary failure reaches bridge.error as its message),
headerService.test.ts (available only through the bytes-only bridge).
Mutation controls, each red on its named test: a fallback re-encoding the
request in place of the bridge's ingress; a bridge without the port
transport accepted. Gate controls, each failing naming the offender: a
phantom member on the bridge type; a production source naming __callBin.
Verified: tsc, lint, jest (every suite on the production transport), npm run
build, and the purity, flow, scan, bridge, codegen, proto, safety and
bridge-RPC-name gates.
Comment thread ci/bridge_rpc_names.py Fixed
Comment thread ci/bridge_rpc_names.py Fixed
Comment thread ci/bridge_rpc_names.py Fixed
Comment thread dsm_client/frontend/src/dsm/__tests__/transportCore.bridge.test.ts
Comment thread dsm_client/frontend/src/dsm/__tests__/transportCore.bridge.test.ts
Six `open(...).read()` calls (two flagged by code quality on #1014) left
their handles to the garbage collector. A `read_text` helper opens each file
in a `with` block. Gate output unchanged: 22 names sent, 22 handled, the
bridge object's 7 members typed as installed; its five negative controls
still fail naming the offender.
@cryptskii
cryptskii merged commit 055a407 into main Sep 26, 2026
19 of 21 checks passed
@cryptskii
cryptskii deleted the fix/test-bridge-speaks-production-interface branch September 26, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant