Skip to content

refactor(protocol): generate TypeScript contracts and enforce complete tool policies - #389

Draft
NianJiuZst wants to merge 4 commits into
Tencent:mainfrom
NianJiuZst:codex/protocol-contract-check
Draft

NianJiuZst wants to merge 4 commits into
Tencent:mainfrom
NianJiuZst:codex/protocol-contract-check

Conversation

@NianJiuZst

@NianJiuZst NianJiuZst commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #388 when the agreed implementation is merged.

Draft for design discussion. The architecture, compatibility policy, and rollout still need maintainer agreement.

Problem and resulting behavior

Protocol changes currently require updating Rust payloads, handwritten TypeScript mirrors, JSON Schema files, dispatcher registrations, and method classifications separately. These copies can drift: the old TypeScript error union omitted Rust's user_aborted, and the checked-in handshake/session-stop schemas omitted fields already sent on the wire.

This PR uses the Rust method catalog and payload types as the shared source, generates extension contracts, validates the transport boundary, and requires complete handler and local-policy coverage. Evaluation retains the existing bounded timeout/cancellation behavior; an unresolved evaluation does not retain the session queue indefinitely.

Implementation

  • The Rust catalog ties each method to its wire name, owner, browser effect, and parameter/result types. Extension handlers and policies must cover every extension method; daemon policy exhaustively matches Method using named fields. Effect-classification rationale remains next to effect().
  • scripts/protocol.mjs generates 41 extension methods, 193 contract definitions, and 105 standalone JSON Schemas. Schemars 1.2.2 exports receiving and sending contracts separately. protocol:generate updates both generated directories; protocol:check detects missing, changed, or unexpected files in either directory without modifying source files.
  • dump-schema derives method roots from the catalog, skips unconstrained daemon-private payloads by schema/ownership rather than a hardcoded Schemars type name, and supports boolean schema roots. Checked-in schemas include audit_enabled, handshake audit responses, and window_released.
  • Generated TypeScript, immutable normalizers, and Ajv standalone validators replace handwritten DTO mirrors. Optional typed nulls normalize to omission; arbitrary JSON and dictionary nulls remain intact. The extension ships generated validation functions without a runtime schema compiler.
  • Envelope and parameter checks run before typed dispatch. Result checks run after the handler and report protocol_error with effect_state: "unknown" on mismatch; a malformed result does not prove that the action had no effect. Failed session-start result validation cleans up the allocated session.
  • Evaluation keeps the base branch's timeout/cancellation lifecycle. Neither a deadline nor a cancel acknowledgement proves that page JavaScript stopped. Execution settlement and recovery require a separate design with an explicit teardown path; this refactor makes no new execution-safety guarantee after timeout.
  • The existing browser archive regression bundles its dependencies before serving them to Chrome, so generated protocol imports resolve in that harness. Its test cases and assertions are unchanged.

Generate and check

pnpm protocol:generate
pnpm protocol:check
cargo run -p bsk-protocol --bin dump-schema --locked -- --out-dir /tmp/bsk-schema

Standalone schemas are compared from a temporary export. The substantial JSON diff includes regeneration under Schemars 1.x, including ordering and description formatting changes.

Validation — 2026-10-07

Local environment: macOS, Rust 1.94.1, Node 26.10.0, Chrome for Testing 153.0.8010.12. Frontend type/lint/build validation used a clean copy of tracked source with workspace dependencies, excluding unrelated untracked audit files.

Check Result
protocol:check Pass: 41 methods, 193 definitions, 105 standalone schemas
Standalone schema validation All 105 compile with Ajv; handshake/session-stop fields verified
Out-of-tree generation probe Synchronized output accepted; stale/missing/extra standalone schemas and stale TypeScript rejected; check mode preserves input files
cargo test --workspace --locked -- --test-threads=2 830 passed, 1 ignored
Rust formatting and production-target Clippy Pass (--workspace --lib --bins --locked -- -D warnings)
Extension unit suite 2,421 passed, 122 browser/configuration skips
Extension TypeScript, Biome, Stylelint, production build Pass
Existing archive browser regression Pass in real Chrome; generated-module resolution failure fixed
Browser input/debugging regression group, sequential 11 passed, 1 failed; see baseline limitation below

A separate out-of-tree probe exercised the real daemon → built extension → isolated Chrome path. It used a private daemon home, fresh browser profile, local fixture, and a copy of the extension with only the daemon endpoint redirected to an ephemeral loopback port:

  1. Evaluate new Promise(() => {}) with timeout_ms: 50: returns timeout; a subsequent evaluation returns 4; session.stop succeeds.
  2. Keep an unresolved promise referenced on the page: the session idle reaper still stops the session after the caller times out.
  3. Explicitly cancel an unresolved evaluation: the existing bounded cleanup reports cancel_cleanup_timeout, and the session can then be stopped.

Baseline/environment limitations: The local Chrome 153 performance regression fails because fcp_ms.value is absent. The same assertion fails on an isolated copy of base commit 3f10983c09fcd443c7420830ab984c9d409954bf; performance code is unchanged by these fixes. An initial heavily parallel run also hit startup/CDP timeouts; the complete Rust rerun passed, and the browser rerun cleared those failures. Hosted CI is reported separately by GitHub checks.

No new test cases, repro scripts, fixtures, screenshots, or run logs are committed. Existing tests have interface and harness adaptations; additional probe evidence is described here.

Compatibility and remaining design decisions

  • Protocol 1.3 and minimum compatible protocol 1.0 remain unchanged. Strict validation is a behavior change; these checks do not establish compatibility with every released peer or all real-browser result shapes.
  • Empty console/network results emit entries: []; Serde readers still accept omission. More precise debug enums and result structures constrain previously permissive shapes.
  • Browser permissions, ownership, readiness, URL/regex semantics, and actual side effects remain handler responsibilities.
  • Generated validators remain committed and minified. Generator choice, generated-code review conventions, compatibility rollout, and whether runtime enforcement should land separately remain maintainer decisions.

…licies

Refs Tencent#388. Derive extension contracts and validators from the Rust catalog, enforce exhaustive handler and execution policies, and preserve evaluation settlement across caller deadlines.

This branch has not been deployed

No deployments
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.

RFC: unify the Rust-TypeScript protocol contract and require complete tool policies

1 participant