Skip to content

Receiver policy: version pin + per-op callback replaces per-field declaration; asset handles - #15

Merged
lannbot merged 1 commit into
mainfrom
receiver-policy
Sep 6, 2026
Merged

Receiver policy: version pin + per-op callback replaces per-field declaration; asset handles#15
lannbot merged 1 commit into
mainfrom
receiver-policy

Conversation

@lannbot

@lannbot lannbot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Replaces #13's per-field declaration model with a smaller mechanism carrying the same fail-safe property, and adds asset handles. #14's hardening and driver split are kept and wired into.

Policy model

Policy { version, check(op), query?(name) } on MountOptions / DriverOptions.

  • version pins the PROTOCOL VERSION from the proto header; createDriver refuses any other. Upgrading the receiver library cannot silently widen what a policy reviewed — bumping the pin is the embedder saying "I have read what changed".
  • check(op) sees createElement / setAttribute / setProperty / addListener / bindMarker with interned strings resolved and the element's tag (tracked through clone-template / bind-path). Templates are checked once at register-template, flattened into the same shapes. A returned reason aborts the stream with a PolicyError { opIndex, op, reason }. No drop-and-continue.
  • query?(name) gates the WIT queries imports; a refusal answers none/false, never throws.
  • Strict decoding whenever a policy is present: unknown ops, fields and enum values reject instead of skipping. One boolean per skip branch.

Removed: accept/events/queries name lists, ALL_*_FIELDS, SURFACE_V1, compilePolicy, the bitmask decoder path, the event-payload field filter (events.ts is byte-identical to pre-#13). The design record keeps the declaration alternative as a considered-and-removed option: same guarantee for one integer instead of a hundred-name list in two languages.

Assets

SetAttribute.value / TemplateAttr.value become oneof { text; asset }. asset is an opaque handle resolved by resolveAsset; absent hook + asset value is an error. Wire-compatible (text keeps its field number and type); bench/wire-baseline.json unchanged. Rust: Batch::set_attribute_asset.

Registration-time pinning

Backends and PolicySink resolve template tags, attribute names and values at register-template, so a later Intern overwrite cannot make the applied DOM diverge from what the policy approved. remote.ts now rejects an un-interned template ref at registration, as native.ts always has.

Also

  • PROTOCOL VERSION: 1 in the proto header → stream_dom_proto::PROTOCOL_VERSION (build.rs) and the receiver's PROTOCOL_VERSION (a test re-reads the .proto).
  • Two pre-existing deno lint failures in Receiver: fail closed on hostile streams; split the DOM driver out of mount #14's test shims fixed (no-this-alias).
  • docs: Policy section rewritten, "Assets are handles" decision added, open question 10 updated.

Gates

just check, just test (95 deno tests, all cargo suites in both workspaces), deno fmt --check, deno lint receiver — all pass locally.

…laration; asset handles

Replaces #13's declaration model (accept/events/queries name lists,
SURFACE_V1, compilePolicy, bitmask strict decoding, event-field filter)
with a smaller mechanism of the same fail-safe property; keeps #14's
hardening and driver split.

- Policy { version, check(op), query?(name) }. `version` pins the
  `PROTOCOL VERSION` from the proto header; createDriver refuses any
  other, so a receiver upgrade cannot silently widen what a policy
  reviewed. `check` sees createElement/setAttribute/setProperty/
  addListener/bindMarker with interned strings resolved and the element's
  tag (tracked through clone-template/bind-path); templates are checked
  once at register-template, flattened into the same shapes. A rejection
  aborts the stream with a PolicyError. `query` gates the WIT queries;
  refusal answers none/false.
- Strict decoding whenever a policy is present: one boolean per skip
  branch; unknown ops, fields and enum values reject.
- `PROTOCOL VERSION: 1` in the proto header, mirrored as
  stream_dom_proto::PROTOCOL_VERSION (build.rs) and the receiver's
  PROTOCOL_VERSION (test re-reads the .proto).
- SetAttribute.value / TemplateAttr.value gain an `asset` arm resolved by
  `resolveAsset`; wire-compatible, bench baseline unchanged.
  Batch::set_attribute_asset on the Rust side.
- Backends and PolicySink pin template tags, attribute names and values at
  registration, so a later re-intern cannot make the applied DOM diverge
  from what the policy approved. remote.ts now rejects an un-interned
  template ref at registration, as native always has.
- events.ts restored byte-for-byte to its pre-#13 form.
- docs/design.md: Policy section rewritten for this model (records the
  declaration alternative and why it was removed); new "Assets are
  handles" decision; open question 10 updated.
@lannbot lannbot changed the title Receiver policy hook, strict decoding, protocol version pin, asset handles Receiver policy: version pin + per-op callback replaces per-field declaration; asset handles Sep 6, 2026
@lannbot
lannbot merged commit 1974923 into main Sep 6, 2026
1 check passed
@lannbot
lannbot deleted the receiver-policy branch September 6, 2026 20:28
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.

2 participants