Room service: remote desktop rooms ([remote]); the host agent and the page join them (CX2b, stacked on #481) #489

Closed
dcharlot wants to merge 0 commits from wai/room-remote into main
Owner

Remote mode in the room service (CX2b)

Stacked on #481 (wai/remote-desktop, head 54589208): this branch contains it and lands after it. Base is main. The commits of this slice are the ones after 54589208.

Update (head d1e8965f): two owner decisions, made

  1. The session's records are the room's. GET /remote/room and GET /remote/state carry typed and clipboard text. They now answer only a caller that presents a grant this room issued, good now, with a proof of possession over the service's challenge. This is the same check POST /remote/control uses (http::caller). Otherwise they answer 401 with a challenge, or 403 {"error": "room"} for a grant the room's authority signed for another room. The per-client rate limit stays.
    • Tested in tests/remote_room.rs: no grant gives 401 on both endpoints; another room's grant with its proof gives 403 on both; a viewer's grant reads /remote/room; a controller's (bob's exchanged grant) reads /remote/state; the host's reads both. A viewer's grant with another key's proof gives 401.
    • The browser scenario reads the clock's session with the host's grant and a proof signed in node, and checks the bare request is 401.
    • Whether viewers should get input records at all, or only commitments with the text sealed to the host, is queued as a separate spec slice and is not done here.
  2. A floor statement §7.2 refuses changes nothing; it does not refuse the room.
    • Spec (remote-desktop.md): §7.2 now says such a statement is recorded, stated with its reason, changes nothing, and gets no effect from a host. §7.3 lets only accepted statements move the floor. §10, §11.2 step 3 and §11.3 (each refused floor statement is stated) follow. §12 moves floor_not_permitted, floor_not_eligible and floor_not_holder from the first table (refusals) into a new third table (reasons a verifier states). §13, SPEC.md §5 and the CHANGELOG are updated to match.
    • wai-rs: adds FloorRefusal, FloorPlace and the shared rule remote_desktop::floor_rule. Run::floor_refused records each refused statement, and the verifier states it instead of refusing the room.
    • The clock cuts no one for a floor statement. It computes each host's floor in the merged order from its lifecycle, the host's screen statements and every floor statement, using floor_rule. It shows the holder in GET /room's remote.holder and returns its floor in /remote/state's clock_floor.
    • The host's gate uses floor_rule too, so the clock, the host and the verifier run one rule.
    • remote_desktop_verify.py was changed from the spec text: it states a refused floor statement and does not apply it, and it reads §12's three tables.
    • The page's remote.js replay applies §7.2 the same way, and its tests now replay every accepted vector.
    • The corpus, regenerated (floor_refused is stated wherever a session has one):
      • 33 of the 39 earlier vectors are byte-identical, in their original places.
      • The six vectors that were refused for a §7.2 reason (floor_given_by_viewer, floor_given_to_viewer, floor_released_by_other, floor_requested_by_viewer, floor_taken_by_controller, floor_of_no_host) keep their names, places and rooms byte-identical. Their stated outcome changes from refuse to accept with floor_refused, because the rule changed. They could not stay unchanged.
      • Three whole floor sessions are appended: floor_give_by_non_holder, floor_release_by_non_holder, and floor_late_release, where the release lands 50 ms after the host's take. In each, the floor afterwards is the floor before, and every input's fate is the floor session's.
    • tests/remote_room.rs adds the race: alice, who does not hold the floor, gives it to herself, and bob's release lands after the host took the floor back. Both are stated (give/floor_not_permitted, release/floor_not_holder) by the verifier and by the clock. The floor holder ends as none in the verifier, the clock and the host alike, the session verifies, and only mallory is cut.
Check (update) Host Result
wai-rs --features remote_desktop: --lib remote_desktop::, --test remote_desktop, the registry wire-identifier test, registry export --check Linux build host pass
corpus regenerated (wai_remote_desktop_vectors gen), byte-compared against the old by vector Linux build host 33 unchanged, 6 outcomes changed as described, 3 appended
remote_desktop_verify.py live-conformance/remote local PASS: every vector, every reason of the three tables, all 19 mutated copies caught, every check dropped caught
room service, every target; remote_room looped 7 times Linux build host pass; 7/7
host agent, every target Linux and macOS build hosts pass, the hardware-encoder session among them; clippy -D warnings clean
clippy, wai-rs (remote_desktop) and room service Linux build host no new notes (only the existing epoch.rs one on main)
page modules remote.test.mjs local 10/10
browser e2e --only remote macOS build host 19/19, including "refused without a grant of the room: 401" and "the host's grant and its proof read the session's records: 200"; both sessions accept by remote_desktop_verify.py
repo checks and public names on the changed files local all exit 0 and clean
Guard broken (update) Guard Mutant Restored
the session's records without the check (admit_reader skips caller) --test remote_room exit 101: /remote/room with no grant answers 200, not 401 0
wai-rs refuses the room for a refused floor statement --test remote_desktop exit 101: corpus doesn't regenerate, stated outcomes differ 0
wai-rs gives a refused floor statement effect --test remote_desktop exit 101 0
the Python verifier refuses the room for one (raise Refuse(why)) remote_desktop_verify.py exit 1: floor_given_by_viewer refused 0
the Python verifier applies a refused give remote_desktop_verify.py exit 1 0
the clock gives a refused floor statement effect --test remote_room exit 101: "the clock refuses what the verifier refuses" 0
the page's replay applies a refused give node --test remote.test.mjs exit 1 0

CX2 (#481) made remote desktop a WAI live-room capability, but a room was self-hosted (wai-remote-host room) and every participant's page file carried its private key. This slice makes the room service run remote rooms: a visitor's page makes its own key, and the room service issues the host's, a controller's and a viewer's grants.

What is in it

joule-moq-room [remote] (src/remote.rs, small hooks in lib.rs, epoch.rs, http.rs, main.rs):

  • Config: host (a key, or empty: the first key that presents an invitation the authority signed, made by joule-moq-room invite <keys.toml> [<s>]), allow (all / listed with controllers / none), control (a controller's classes), width/height/fps (the screen profile), max_age_ms (the age bound), lead_ms. Refused beside e2ee, a watch room, co-performance or [age].
  • Descriptor: the roles are host (it publishes screen and holds the direct power), controller and viewer. Every policy permits meta and chat, so every principal sends the extension's statements. Every grant is listed with the room. A remote room has no composition.
  • Grants: the host's carries remote_host = true and the screen, a controller's remote_control = control, and a viewer's neither. All need a proof of possession at the relay, as every room grant does. An invitation is bound to the first key that presents it; a second key gets invitation_used.
  • The host's grant exchange: POST /remote/control {"to", "control"}. It takes the host's grant and a proof over a challenge the service issued (the co-performance caller() path). The new grant is chained to the old one (prev), takes effect on a tick at least lead_ms ahead, and the clock signs grant_exchange at that tick.
  • Clock: presence. It watches the host's screen track (join and publish screen when it appears, leave when it ends) and every other principal's events track (join/leave).
  • Clock: order. It follows every principal's events and takes each record in its chain's order, under the policy of the author's grant in effect (live-events §6, through wai-rs's RoomVerifier), and only after the author's join (live-rooms §9.2 step 4). Each statement must read (§4.2) and come from a principal who may sign it (§4.3). A floor request needs the control power and a take needs the host or the direct power (§7.2). The host's WDS1 is checked against §3.4 and the room's age bound, and becomes the screen in effect served in GET /room's remote. A principal whose record fails is cut at that record and removed (leave reason 2).
  • GET /remote/room serves the session as the clock holds it (§11.1). GET /remote/state verifies it with wai-rs and returns the floor, each input's fate, the latencies, the totals and the cuts. Both are bounded per client.

joule-moq-remote:

  • run --room <service-url> --key <file> [--invitation <hex>] --relay <ws>. The agent asks for its grant, follows the service's clock and signs no lifecycle. It states its screen once it holds its own join, learns participants from the clock's joins, and reads the room's grants again for a grant it doesn't hold. It stamps its records on the service's clock (the skew from now_us) and keeps max_age_ms within the room's bound. It hands control over with Command::Control (the service's exchange, with its grant and proof).
  • wai-remote-host key <file> makes a host key (0600). room mode stays for one desk, but no longer writes participant-<name>.page.json files that carry keys.
  • The gate orders participants' records by the same rules as the clock (RoomVerifier under the grant in effect, after the join, the same statement rules). A participant is cut at the same record, so the host and the clock hold the same records and the session verifies. A record of a principal the gate doesn't yet hold as present waits for its join. It is out of order once the gate holds the clock's chain past it, or after 10 s.
  • service::RoomService is a small HTTP/1.1 client with the challenge-and-proof flow. viewer::Viewer::join_service is the headless viewer under a key it makes.

joule-moq-web:

  • remote.js no longer reads a key file. RemoteViewer takes the grant the service issued for the key the page made, and reads its control power from that grant and from each exchange of it the clock signs. It sends nothing without a grant, before the clock has recorded it joining, outside its grant's nbf..exp, or of a class its grant in effect does not give.
  • app.js enters a remote room through the service: #join asks to control and falls back to viewer on 403; #watch asks to view. It waits for the host, and reads the room's grants again on a grant_exchange.
  • The browser scenario remote runs against the room service.

Design decisions, and why

  1. The clock "orders" by chain order, presence and per-record statement rules, not by a full replay of the room. The full rules (§5.3, the holder rules of §7.2, §6.2, §8.2) need the merged order across chains. A full rd::verify on every record would grow quadratically over a session. So the clock cuts what it can decide per record, and GET /remote/state runs the full verification when asked.
  2. A record out of order cuts its author and removes it. A record a verifier refuses makes the whole session refuse. Cutting at that record keeps the session the clock holds verifiable. The host applies the same cut, because if the host decided an input the clock dropped, its outcome would name an unknown input (outcome_unknown_input).
  3. Grant exchanges take effect at least lead_ms ahead, on the descriptor's tick, so the host holds the exchange before any input it governs, as co-performance's lead_ms does for the floor.
  4. Invitations are local to the service (signed by the authority, with an end, bound to the first key). Without a configured key, any first visitor could otherwise take the host's role.
  5. Kept separable from #482 ([world]). The remote mode has its own grant path (Epoch::grant_remote), its own watch (watch_remote), its own epoch builder (remote::epoch, which wraps Epoch::new), and its own module and HTTP routes. Its Epoch field sits apart from #482's. A trial merge of origin/wai/world-on-moq into this branch conflicts in the room service only on joule-moq-room/Cargo.toml's wai features line (resolve: both remote_desktop and live_world). One semantic step remains after both land: refuse [remote] with [world] (add cfg.world.is_some() to remote::service's refusal, or remote to #482's). The other conflicts in that trial merge (wai/*, room.e2e.mjs, run.sh, joule-moq-web.yml) are #481's against #482's, and exist without this PR.

Checks

Check Host Command Result
room service, every target Linux build host cd joule-moq-room && cargo test --locked --all-targets 17 unit + 5 integration tests pass, remote_room among them
the remote session, looped Linux build host cargo test --locked --test remote_room ×7 7/7 pass (13.4–13.8 s each)
host agent, every target Linux build host cd joule-moq-remote && cargo test --locked --all-targets -- --test-threads=1 13 unit + loopback pass; loopback ×3 more, 3/3
host agent on macOS, hardware encoder macOS build host the same 13 unit + 3 (…pure_function_encoder, …platform_hardware_encoder, …annex_b_access_units) pass
clippy, host agent Linux and macOS build hosts cargo clippy --locked --all-targets --no-deps -- -D warnings clean on both
clippy, room service Linux build host cargo clippy --locked --all-targets --no-deps no new notes (one note already on main: epoch.rs test if let Ok(_))
lock files Linux build host cargo metadata --locked --offline --format-version 1 in both crates ok; joule-moq-room/Cargo.lock gained only the joule-moq-remote entry (dev-dependency)
page modules local (node) node --test web/room/remote.test.mjs 9/9 (3 new)
browser e2e, remote macOS build host BROWSER_BIN=… e2e/run.sh --only remote 17/17; platform encoder …[email protected]…, hardware 1; page latency ~396 ms, source 1, with legs; both sessions (host's and clock's) accept by remote_desktop_verify.py
browser e2e, service-mode regression macOS build host e2e/run.sh --only service,e2ee,age,co,ageco,cowatch,origin,remote 170/170, 56/56 page unit tests
workflows parse local ruby -ryaml -e 'ARGV.each{|f| YAML.load_file(f)}' on the three touched ok
repo checks local check-tests-in-ci.py, check-workflow-concurrency.sh, check-spec-refs.py, check-wai-section-refs.py, check-no-vanity-metrics.sh, test-public-text-checks.sh, check-comparative-prose.sh, check-orphan-sources.py all exit 0
public names local check-public-names.sh on the changed files; commit messages and this body with grep -E changed files clean, except one hit on a line of joule-moq-web.yml that main already has (the CI browser path); messages and body clean

tests/remote_room.rs covers:

  • The host enters by invitation; a second key presenting it gets 403 invitation_used; a controller's request gets 403 control_not_allowed under allow = "none".
  • Two viewers make their keys.
  • The host's exchange gives alice control. Asking with the grant and no proof gets 401, with another key's proof 401, and as a non-host with its own proof 403.
  • Alice is given the floor and her click is applied. Bob is refused input_not_permitted.
  • Control and the floor pass to bob: his move is applied, and alice is refused input_no_floor.
  • Mallory's record stamped 1 ms before the one it follows is cut time_reversed at seq 1 by the clock and by the host, and the clock signs her leave reason 2.
  • The session verifies by wai-rs, both as GET /remote/room serves it and as the host holds it.

Falsifications

Each mutation was applied inside the build-host run (whose HEAD is the clean snapshot), the guard run, the file restored with git checkout --, and the guard run again.

Guard broken Mutation Guard Mutant Restored
issue remote_control to a viewer grant_remote's viewer arm carries remote_control remote::tests::a_remote_rooms_grants_mark_its_host_and_controllers; --test remote_room exit 101 (viewer's grant carries the power); exit 101 at "a viewer carries no control power" 0; 0
skip the proof of possession http::caller's failed check_proof returns the caller --test remote_room exit 101 at "the grant alone, with no proof" (401 expected) 0
the clock accepts an input out of order order() takes a record the verifier refuses --test remote_room exit 101 at "the clock removes mallory" 0
the page sends without its grant RemoteViewer.control ignores the grant's nbf..exp node --test remote.test.mjs exit 1: "sends nothing once its grant has ended" 0
the page sends before its join mayEmit() drops joined node --test remote.test.mjs exit 1: "takes its control power…" (sent before the clock's join) 0
the host takes a record out of order Gate::release ignores the verifier's refusal gate::tests::a_participants_records_are_taken_in_order; --test remote_room exit 101; exit 101 at "the host cut mallory where the clock did" 0; 0

Not done

  • No Windows host, no window capture, no host→viewer clipboard, no MLS join. A remote room is not end-to-end encrypted, and the service refuses [remote] with e2ee.
  • No TLS client in the host agent. It reaches the service over http:// and the relay over ws:// (the relay's WebSocket client has no TLS), so a host off the service's machine needs a local front that holds TLS.
  • Outcome and latency lookups are left to the verification. The clock's per-record order does not check the §6.2 and §8.2 lookups; /remote/state runs the full verification on request. (The floor is now computed by the clock with §7.2's rule; see the update above.)
  • Viewers still receive every input record. Commitments with the text sealed to the host are queued as a separate spec slice.
  • The clock holds every record of a room in memory, twice (its taken chains and wai-rs's verifier). A long, busy session needs a bound or a spill, as the co-performance log has max_objects.
  • The service's view of the room is not live in pages. GET /room's remote.screen is what the clock took; pages read the host's track.
  • Deferred to the owner: no deploy, no Caddy change, and no update of the site's room page.

Decisions

  • Settled, and done in this PR: (1) the session's records are served to a grant of the room with its proof; (2) a floor statement §7.2 refuses changes nothing.
  • Settled as proposed: (3) a self-hosted desk keeps headless viewers only, and the browser path goes through the room service; (4) the invitation stays service-local.
  • Left for Train E: refusing [remote] together with [world], once both are in one tree.

🤖 Generated with Claude Code


Closed: Landed on main through merge train E (#499, merge bf39ec9a); verified by content: this PR's head is in main

## Remote mode in the room service (CX2b) **Stacked on #481 (`wai/remote-desktop`, head 54589208): this branch contains it and lands after it.** Base is main. The commits of this slice are the ones after 54589208. ### Update (head d1e8965f): two owner decisions, made 1. **The session's records are the room's.** `GET /remote/room` and `GET /remote/state` carry typed and clipboard text. They now answer only a caller that presents a grant this room issued, good now, with a proof of possession over the service's challenge. This is the same check `POST /remote/control` uses (`http::caller`). Otherwise they answer 401 with a challenge, or 403 `{"error": "room"}` for a grant the room's authority signed for another room. The per-client rate limit stays. - Tested in `tests/remote_room.rs`: no grant gives 401 on both endpoints; another room's grant with its proof gives 403 on both; a viewer's grant reads `/remote/room`; a controller's (bob's exchanged grant) reads `/remote/state`; the host's reads both. A viewer's grant with another key's proof gives 401. - The browser scenario reads the clock's session with the host's grant and a proof signed in node, and checks the bare request is 401. - Whether viewers should get input records at all, or only commitments with the text sealed to the host, is queued as a separate spec slice and is not done here. 2. **A floor statement §7.2 refuses changes nothing; it does not refuse the room.** - **Spec** (`remote-desktop.md`): §7.2 now says such a statement is recorded, stated with its reason, changes nothing, and gets no effect from a host. §7.3 lets only accepted statements move the floor. §10, §11.2 step 3 and §11.3 (each refused floor statement is stated) follow. §12 moves `floor_not_permitted`, `floor_not_eligible` and `floor_not_holder` from the first table (refusals) into a new third table (reasons a verifier states). §13, SPEC.md §5 and the CHANGELOG are updated to match. - **wai-rs:** adds `FloorRefusal`, `FloorPlace` and the shared rule `remote_desktop::floor_rule`. `Run::floor_refused` records each refused statement, and the verifier states it instead of refusing the room. - **The clock** cuts no one for a floor statement. It computes each host's floor in the merged order from its lifecycle, the host's screen statements and every floor statement, using `floor_rule`. It shows the holder in `GET /room`'s `remote.holder` and returns its floor in `/remote/state`'s `clock_floor`. - **The host's gate** uses `floor_rule` too, so the clock, the host and the verifier run one rule. - **remote_desktop_verify.py** was changed from the spec text: it states a refused floor statement and does not apply it, and it reads §12's three tables. - **The page's `remote.js` replay** applies §7.2 the same way, and its tests now replay every accepted vector. - **The corpus**, regenerated (`floor_refused` is stated wherever a session has one): - 33 of the 39 earlier vectors are byte-identical, in their original places. - The six vectors that were refused for a §7.2 reason (`floor_given_by_viewer`, `floor_given_to_viewer`, `floor_released_by_other`, `floor_requested_by_viewer`, `floor_taken_by_controller`, `floor_of_no_host`) keep their names, places and rooms byte-identical. Their stated outcome changes from refuse to accept with `floor_refused`, because the rule changed. They could not stay unchanged. - Three whole floor sessions are appended: `floor_give_by_non_holder`, `floor_release_by_non_holder`, and `floor_late_release`, where the release lands 50 ms after the host's take. In each, the floor afterwards is the floor before, and every input's fate is the floor session's. - **`tests/remote_room.rs` adds the race:** alice, who does not hold the floor, gives it to herself, and bob's release lands after the host took the floor back. Both are stated (`give`/`floor_not_permitted`, `release`/`floor_not_holder`) by the verifier and by the clock. The floor holder ends as none in the verifier, the clock and the host alike, the session verifies, and only mallory is cut. | Check (update) | Host | Result | |---|---|---| | `wai-rs --features remote_desktop`: `--lib remote_desktop::`, `--test remote_desktop`, the registry wire-identifier test, `registry export --check` | Linux build host | pass | | corpus regenerated (`wai_remote_desktop_vectors gen`), byte-compared against the old by vector | Linux build host | 33 unchanged, 6 outcomes changed as described, 3 appended | | `remote_desktop_verify.py live-conformance/remote` | local | PASS: every vector, every reason of the three tables, all 19 mutated copies caught, every check dropped caught | | room service, every target; `remote_room` looped 7 times | Linux build host | pass; 7/7 | | host agent, every target | Linux and macOS build hosts | pass, the hardware-encoder session among them; clippy `-D warnings` clean | | clippy, wai-rs (`remote_desktop`) and room service | Linux build host | no new notes (only the existing `epoch.rs` one on main) | | page modules `remote.test.mjs` | local | 10/10 | | browser e2e `--only remote` | macOS build host | 19/19, including "refused without a grant of the room: 401" and "the host's grant and its proof read the session's records: 200"; both sessions accept by `remote_desktop_verify.py` | | repo checks and public names on the changed files | local | all exit 0 and clean | | Guard broken (update) | Guard | Mutant | Restored | |---|---|---|---| | the session's records without the check (`admit_reader` skips `caller`) | `--test remote_room` | exit 101: `/remote/room` with no grant answers 200, not 401 | 0 | | wai-rs refuses the room for a refused floor statement | `--test remote_desktop` | exit 101: corpus doesn't regenerate, stated outcomes differ | 0 | | wai-rs gives a refused floor statement effect | `--test remote_desktop` | exit 101 | 0 | | the Python verifier refuses the room for one (`raise Refuse(why)`) | `remote_desktop_verify.py` | exit 1: `floor_given_by_viewer` refused | 0 | | the Python verifier applies a refused give | `remote_desktop_verify.py` | exit 1 | 0 | | the clock gives a refused floor statement effect | `--test remote_room` | exit 101: "the clock refuses what the verifier refuses" | 0 | | the page's replay applies a refused give | `node --test remote.test.mjs` | exit 1 | 0 | CX2 (#481) made remote desktop a WAI live-room capability, but a room was self-hosted (`wai-remote-host room`) and every participant's page file carried its private key. This slice makes the room service run remote rooms: a visitor's page makes its own key, and the room service issues the host's, a controller's and a viewer's grants. ### What is in it **`joule-moq-room` `[remote]`** (`src/remote.rs`, small hooks in `lib.rs`, `epoch.rs`, `http.rs`, `main.rs`): - **Config:** `host` (a key, or empty: the first key that presents an invitation the authority signed, made by `joule-moq-room invite <keys.toml> [<s>]`), `allow` (`all` / `listed` with `controllers` / `none`), `control` (a controller's classes), `width`/`height`/`fps` (the screen profile), `max_age_ms` (the age bound), `lead_ms`. Refused beside e2ee, a watch room, co-performance or `[age]`. - **Descriptor:** the roles are host (it publishes `screen` and holds the direct power), controller and viewer. Every policy permits `meta` and `chat`, so every principal sends the extension's statements. Every grant is listed with the room. A remote room has no composition. - **Grants:** the host's carries `remote_host` = `true` and the screen, a controller's `remote_control` = `control`, and a viewer's neither. All need a proof of possession at the relay, as every room grant does. An invitation is bound to the first key that presents it; a second key gets `invitation_used`. - **The host's grant exchange:** `POST /remote/control {"to", "control"}`. It takes the host's grant and a proof over a challenge the service issued (the co-performance `caller()` path). The new grant is chained to the old one (`prev`), takes effect on a tick at least `lead_ms` ahead, and the clock signs `grant_exchange` at that tick. - **Clock: presence.** It watches the host's `screen` track (`join` and `publish` screen when it appears, `leave` when it ends) and every other principal's events track (`join`/`leave`). - **Clock: order.** It follows every principal's events and takes each record in its chain's order, under the policy of the author's grant in effect (live-events §6, through `wai-rs`'s `RoomVerifier`), and only after the author's `join` (live-rooms §9.2 step 4). Each statement must read (§4.2) and come from a principal who may sign it (§4.3). A floor request needs the control power and a take needs the host or the direct power (§7.2). The host's WDS1 is checked against §3.4 and the room's age bound, and becomes the screen in effect served in `GET /room`'s `remote`. A principal whose record fails is cut at that record and removed (`leave` reason 2). - **`GET /remote/room`** serves the session as the clock holds it (§11.1). **`GET /remote/state`** verifies it with `wai-rs` and returns the floor, each input's fate, the latencies, the totals and the cuts. Both are bounded per client. **`joule-moq-remote`:** - **`run --room <service-url> --key <file> [--invitation <hex>] --relay <ws>`.** The agent asks for its grant, follows the service's clock and signs no lifecycle. It states its screen once it holds its own `join`, learns participants from the clock's joins, and reads the room's grants again for a grant it doesn't hold. It stamps its records on the service's clock (the skew from `now_us`) and keeps `max_age_ms` within the room's bound. It hands control over with `Command::Control` (the service's exchange, with its grant and proof). - **`wai-remote-host key <file>`** makes a host key (0600). `room` mode stays for one desk, but no longer writes `participant-<name>.page.json` files that carry keys. - **The gate orders participants' records by the same rules as the clock** (`RoomVerifier` under the grant in effect, after the `join`, the same statement rules). A participant is cut at the same record, so the host and the clock hold the same records and the session verifies. A record of a principal the gate doesn't yet hold as present waits for its `join`. It is out of order once the gate holds the clock's chain past it, or after 10 s. - **`service::RoomService`** is a small HTTP/1.1 client with the challenge-and-proof flow. **`viewer::Viewer::join_service`** is the headless viewer under a key it makes. **`joule-moq-web`:** - **`remote.js` no longer reads a key file.** `RemoteViewer` takes the grant the service issued for the key the page made, and reads its control power from that grant and from each exchange of it the clock signs. It sends nothing without a grant, before the clock has recorded it joining, outside its grant's `nbf`..`exp`, or of a class its grant in effect does not give. - **`app.js`** enters a remote room through the service: `#join` asks to control and falls back to viewer on 403; `#watch` asks to view. It waits for the host, and reads the room's grants again on a `grant_exchange`. - **The browser scenario `remote` runs against the room service.** ### Design decisions, and why 1. **The clock "orders" by chain order, presence and per-record statement rules, not by a full replay of the room.** The full rules (§5.3, the holder rules of §7.2, §6.2, §8.2) need the merged order across chains. A full `rd::verify` on every record would grow quadratically over a session. So the clock cuts what it can decide per record, and `GET /remote/state` runs the full verification when asked. 2. **A record out of order cuts its author and removes it.** A record a verifier refuses makes the whole session refuse. Cutting at that record keeps the session the clock holds verifiable. The host applies the same cut, because if the host decided an input the clock dropped, its outcome would name an unknown input (`outcome_unknown_input`). 3. **Grant exchanges take effect at least `lead_ms` ahead,** on the descriptor's tick, so the host holds the exchange before any input it governs, as co-performance's `lead_ms` does for the floor. 4. **Invitations are local to the service** (signed by the authority, with an end, bound to the first key). Without a configured key, any first visitor could otherwise take the host's role. 5. **Kept separable from #482 (`[world]`).** The remote mode has its own grant path (`Epoch::grant_remote`), its own watch (`watch_remote`), its own epoch builder (`remote::epoch`, which wraps `Epoch::new`), and its own module and HTTP routes. Its `Epoch` field sits apart from #482's. A trial merge of `origin/wai/world-on-moq` into this branch conflicts in the room service only on `joule-moq-room/Cargo.toml`'s `wai` features line (resolve: both `remote_desktop` and `live_world`). One semantic step remains after both land: refuse `[remote]` with `[world]` (add `cfg.world.is_some()` to `remote::service`'s refusal, or `remote` to #482's). The other conflicts in that trial merge (`wai/*`, `room.e2e.mjs`, `run.sh`, `joule-moq-web.yml`) are #481's against #482's, and exist without this PR. ### Checks | Check | Host | Command | Result | |---|---|---|---| | room service, every target | Linux build host | `cd joule-moq-room && cargo test --locked --all-targets` | 17 unit + 5 integration tests pass, `remote_room` among them | | the remote session, looped | Linux build host | `cargo test --locked --test remote_room` ×7 | 7/7 pass (13.4–13.8 s each) | | host agent, every target | Linux build host | `cd joule-moq-remote && cargo test --locked --all-targets -- --test-threads=1` | 13 unit + loopback pass; loopback ×3 more, 3/3 | | host agent on macOS, hardware encoder | macOS build host | the same | 13 unit + 3 (`…pure_function_encoder`, `…platform_hardware_encoder`, `…annex_b_access_units`) pass | | clippy, host agent | Linux and macOS build hosts | `cargo clippy --locked --all-targets --no-deps -- -D warnings` | clean on both | | clippy, room service | Linux build host | `cargo clippy --locked --all-targets --no-deps` | no new notes (one note already on main: `epoch.rs` test `if let Ok(_)`) | | lock files | Linux build host | `cargo metadata --locked --offline --format-version 1` in both crates | ok; `joule-moq-room/Cargo.lock` gained only the `joule-moq-remote` entry (dev-dependency) | | page modules | local (node) | `node --test web/room/remote.test.mjs` | 9/9 (3 new) | | browser e2e, remote | macOS build host | `BROWSER_BIN=… e2e/run.sh --only remote` | 17/17; platform encoder `…[email protected]…`, hardware 1; page latency ~396 ms, source 1, with legs; both sessions (host's and clock's) accept by `remote_desktop_verify.py` | | browser e2e, service-mode regression | macOS build host | `e2e/run.sh --only service,e2ee,age,co,ageco,cowatch,origin,remote` | 170/170, 56/56 page unit tests | | workflows parse | local | `ruby -ryaml -e 'ARGV.each{\|f\| YAML.load_file(f)}'` on the three touched | ok | | repo checks | local | `check-tests-in-ci.py`, `check-workflow-concurrency.sh`, `check-spec-refs.py`, `check-wai-section-refs.py`, `check-no-vanity-metrics.sh`, `test-public-text-checks.sh`, `check-comparative-prose.sh`, `check-orphan-sources.py` | all exit 0 | | public names | local | `check-public-names.sh` on the changed files; commit messages and this body with `grep -E` | changed files clean, except one hit on a line of `joule-moq-web.yml` that main already has (the CI browser path); messages and body clean | **`tests/remote_room.rs` covers:** - The host enters by invitation; a second key presenting it gets 403 `invitation_used`; a controller's request gets 403 `control_not_allowed` under `allow = "none"`. - Two viewers make their keys. - The host's exchange gives alice control. Asking with the grant and no proof gets 401, with another key's proof 401, and as a non-host with its own proof 403. - Alice is given the floor and her click is applied. Bob is refused `input_not_permitted`. - Control and the floor pass to bob: his move is applied, and alice is refused `input_no_floor`. - Mallory's record stamped 1 ms before the one it follows is cut `time_reversed` at seq 1 by the clock and by the host, and the clock signs her `leave` reason 2. - The session verifies by `wai-rs`, both as `GET /remote/room` serves it and as the host holds it. ### Falsifications Each mutation was applied inside the build-host run (whose HEAD is the clean snapshot), the guard run, the file restored with `git checkout --`, and the guard run again. | Guard broken | Mutation | Guard | Mutant | Restored | |---|---|---|---|---| | issue `remote_control` to a viewer | `grant_remote`'s viewer arm carries `remote_control` | `remote::tests::a_remote_rooms_grants_mark_its_host_and_controllers`; `--test remote_room` | exit 101 (viewer's grant carries the power); exit 101 at "a viewer carries no control power" | 0; 0 | | skip the proof of possession | `http::caller`'s failed `check_proof` returns the caller | `--test remote_room` | exit 101 at "the grant alone, with no proof" (401 expected) | 0 | | the clock accepts an input out of order | `order()` takes a record the verifier refuses | `--test remote_room` | exit 101 at "the clock removes mallory" | 0 | | the page sends without its grant | `RemoteViewer.control` ignores the grant's `nbf`..`exp` | `node --test remote.test.mjs` | exit 1: "sends nothing once its grant has ended" | 0 | | the page sends before its join | `mayEmit()` drops `joined` | `node --test remote.test.mjs` | exit 1: "takes its control power…" (sent before the clock's join) | 0 | | the host takes a record out of order | `Gate::release` ignores the verifier's refusal | `gate::tests::a_participants_records_are_taken_in_order`; `--test remote_room` | exit 101; exit 101 at "the host cut mallory where the clock did" | 0; 0 | ### Not done - **No Windows host, no window capture, no host→viewer clipboard, no MLS join.** A remote room is not end-to-end encrypted, and the service refuses `[remote]` with `e2ee`. - **No TLS client in the host agent.** It reaches the service over `http://` and the relay over `ws://` (the relay's WebSocket client has no TLS), so a host off the service's machine needs a local front that holds TLS. - **Outcome and latency lookups are left to the verification.** The clock's per-record order does not check the §6.2 and §8.2 lookups; `/remote/state` runs the full verification on request. (The floor is now computed by the clock with §7.2's rule; see the update above.) - **Viewers still receive every input record.** Commitments with the text sealed to the host are queued as a separate spec slice. - **The clock holds every record of a room in memory, twice** (its taken chains and `wai-rs`'s verifier). A long, busy session needs a bound or a spill, as the co-performance log has `max_objects`. - **The service's view of the room is not live in pages.** `GET /room`'s `remote.screen` is what the clock took; pages read the host's track. - **Deferred to the owner:** no deploy, no Caddy change, and no update of the site's room page. ### Decisions - **Settled, and done in this PR:** (1) the session's records are served to a grant of the room with its proof; (2) a floor statement §7.2 refuses changes nothing. - **Settled as proposed:** (3) a self-hosted desk keeps headless viewers only, and the browser path goes through the room service; (4) the invitation stays service-local. - **Left for Train E:** refusing `[remote]` together with `[world]`, once both are in one tree. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- **Closed:** Landed on main through merge train E (#499, merge bf39ec9a); verified by content: this PR's head is in main
The extension text (wai/extensions/remote-desktop.md); the remote_desktop module of wai-rs behind
its own feature: the statements WDS1, WDI1, WDO1, WDF1 and WDL1, the grant attributes
remote_host and remote_control, the host's rules of §5.3 (decide), the floor in effect, latency
legs, and the verifier over a room the live-rooms verifier replays. The corpus
live-conformance/remote/ written by wai_remote_desktop_vectors from fixed seeds: a floor session,
an open session whose control passes by grant exchange, a gap, and refused vectors giving every
reason of both tables of §12. Its second implementation, remote_desktop_verify.py, written from
the text and sharing no code with wai-rs, agrees on every vector, fails every mutated copy and
catches each of its 32 checks dropped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An agent that reads the screen, or a test harness, states the instant it held the frame verified
against its receipt; no photon is involved, and the record measures the path to the viewer alone.
wai-rs and remote_desktop_verify.py read source 1 to 3.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The host is its room's clock and its screen's publisher: it makes a room (descriptor and grants,
the host marked remote_host, each participant's control classes in remote_control) and the lines
a relay's configuration takes for it; signs each participant's join when its events track
appears; publishes the screen as the room's screen track, one frame a group with its receipts,
and a screen statement naming the encoder; decides every input by wai-rs's rules at its place in
room-clock order (gate.rs) and injects what they accept on a thread that captures after it
injects, so the outcome it signs names the first frame that can show the input; and passes the
floor on request or when told.

Behind small traits: capture (a pattern source; the macOS display stream; X11 on Linux), encode
(an H.264 encoder with every macroblock I_PCM, a pure function every decoder decodes; the macOS
media framework's hardware encoder, AVC or HEVC, read back as Annex B), and input (a recorder;
the macOS event services; the Linux kernel's input device). Platform API names are in the
adapter files, each line marked for the public-name check.

tests/loopback.rs: a session over a relay that requires grants with their proofs, three viewers,
floor handover, refusals for no control power, no floor and a paused screen, latency records,
and the session's chains verified whole by wai-rs; run with the pure-function encoder, and on
macOS again with the hardware encoder. A workflow runs it on linux-arm64 and macos.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
web/room/remote.js: input statements, floor statements and latency records written byte for byte,
every statement read, the host's rules and the floor replayed over a room's records, the table
from keyboard codes to HID usages; and RemoteViewer, which draws the host's screen (the page's
WebCodecs decoder, AVC or HEVC in Annex B, every frame verified against its receipt first), shows
what the host captures and who holds the floor, batches pointer, keys, composed text and pasted
text into input statements under the grant's classes, asks for and gives back the floor, and signs
a latency record of each applied input's frame at the display refresh that showed it.
remote.test.mjs checks it against wai/live-conformance/remote/, the host agent's key table among
it. The page draws a room with no composition descriptor, and names a screen track by its
namespace.

e2e: the remote scenario runs wai-remote-host with a pattern source (the platform's hardware
encoder on macOS), two pages, the floor asked for and given, a click and a key applied with their
latency records, an input with no control power refused, and the session the host records
verified by remote_desktop_verify.py.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A participant that joins late fetches what the clock and the host said before it came (live-rooms
§10.6), the host's screen statement among it. `room` writes participant-<name>.page.json, the room
page's test-room file; `run --record` writes the session as a verifier takes it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai.live.remote: a corpus member's type named, for clippy's type_complexity
Some checks failed
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
5458920846
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The room service keeps every room as one host's screen shared into it (WAI
remote-desktop): it grants the host (remote_host), a controller (the control
power, by the allow rule) and a viewer, takes the host by its key or by an
invitation its authority signed, hands control over by the host's grant
exchange (its grant and a proof over the service's challenge), watches the
host's screen track and every principal's events, and takes each record in its
chain's order, removing a principal whose record is out of it.

The host agent runs in a room a service keeps (run --room): it asks for its
grant, follows the service's clock, and orders participants' records by the
same rules. The desk (wai-remote-host room) stays, without page files that
carry keys.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
tests/remote_room.rs: the host agent enters by an invitation (a second key
refused), two viewers make their keys, the host hands control over by a grant
exchange with its grant and proof (no proof, another key's proof, and a non-host
refused), a click under the floor applied, a viewer refused input_not_permitted,
the floor handed to a second controller, a record out of its chain's order cut
and its author removed by the clock and by the host, and the session verified by
wai-rs as the clock holds it and as the host holds it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
remote.js no longer reads a key file: RemoteViewer takes the grant the room
service issued for the key the page made, reads its control power from that
grant and from each exchange of it the clock signs, and sends nothing without a
grant, before the clock records it joining, or of a class its grant in effect
does not give. app.js enters a remote room through the service (#join asks to
control, #watch to view) and reads the room's grants again on an exchange. The
browser scenario runs against the room service: the host agent by invitation, a
controller's page and a viewer's, and the sessions the host and the clock hold
verified by remote_desktop_verify.py.

Also the room service's README, example and workflows for [remote].

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The viewer's control power is its grant in effect's while that grant is good,
from its nbf to before its exp (live-rooms §4.3): a page whose grant has ended
holds none and sends nothing. readGrant reads the grant's window.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
joule-moq-room: GET /remote/room bounded per client, as /remote/state is
Some checks failed
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
74cab2ccac
Co-Authored-By: Claude Opus 5.5 <[email protected]>
remote-desktop §7.2/§7.3: a floor statement its rules refuse is recorded, stated
with its reason and changes nothing; a verifier computes the floor without it and
a host gives it no effect, so a release crossing the host's take leaves a room
that verifies. §12 moves floor_not_permitted, floor_not_eligible and
floor_not_holder to a third table of stated reasons; §11.3 states each refused
floor statement.

wai-rs: FloorRefusal and the shared rule remote_desktop::floor_rule; the verifier
states floor_refused. The corpus regenerated: 33 of the 39 earlier vectors are
byte for byte as they were; the six refused-floor vectors keep their rooms and
are now accepted with floor_refused; three whole floor sessions are appended (a
give by a non-holder, a release by a non-holder, a late release).
remote_desktop_verify.py follows the text: states and does not apply a refused
floor statement, reads §12's three tables. remote.js's replay applies §7.2.

joule-moq-room: the clock computes the floor by floor_rule and cuts no one for a
floor statement; GET /remote/room and /remote/state answer only a grant this room
issued with its proof (401 otherwise, 403 for another room's grant). The host's
gate uses floor_rule. The browser scenario reads the session with the host's
grant and proof.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
joule-moq-room: name the floor replay's types; the reader check's refusal boxed
Some checks failed
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
d1e8965f70
Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot closed this pull request 2026-10-26 20:59:37 -04:00
Some checks failed
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 15s
WAI conformance / does this change touch it (pull_request) Successful in 12s
wai-web wasm32 / does this change touch it (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been cancelled
wai-quantum studio and desktop / does this change touch it (pull_request) Has been cancelled
wai-quantum studio and desktop / studio · browser (pull_request) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
wai-pack / wai-pack · does this change touch it (pull_request) Has been cancelled
wai-pack / wai-pack · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been cancelled
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Has been cancelled
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Has been cancelled
joule-moq-room room service / joule-moq-room, every target (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Has been cancelled
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!489
No description provided.