Skip to content

spec: add the core/room domain, unifying rooms and DMs - #47

Merged
Mearman merged 4 commits into
mainfrom
feat/core-room-domain
Sep 11, 2026
Merged

Mearman merged 4 commits into
mainfrom
feat/core-room-domain

Conversation

@Mearman

@Mearman Mearman commented Sep 11, 2026

Copy link
Copy Markdown
Member

Closes #31.

Adds core/room: one domain covering both named, persistent rooms and two-party DMs, gated by a single room:member capability for the four ordinary participation verbs (room.send/room.read/room.leave/room.members), mirroring exec:pty's own one-resource-many-verbs shape. room.join/room.invite are deliberately ungated -- no token field at all -- since a room with no prior relationship has nothing to gate first contact on; approval mints a token on the spot and returns it as an extension field on the existing manage-ok response.

Membership proof is rooted in the room-path's own shape: an owner-named room (<owner-hex>/<local-name>) roots trust at the owner named in the path, but a DM path (the sorted pair of the two participants' hex device-ids) roots trust at the verifier itself -- if either named party were an acceptable root, a sender could self-issue a token and message a stranger unsolicited.

Two things found and fixed while building this, not left as known gaps:

  • Unbounded delegation. Any bearer of room:member could mint a narrower child carrying the identical verb (narrowing preserves the capability), so any member could transitively admit anyone to any depth, and kicking one member's grant would take down their entire invited subtree since only a token's own issuer may revoke it. Fixed with a new ? delegations-remaining: uint claim on token-claims -- absent means unbounded (every existing token keeps its current meaning), present means a child's value must be strictly less than its parent's. General, not room-specific: exec:pty has the identical latent bug and gets the fix for free.
  • A pre-existing cddl.js bug, unrelated to this domain's own design. The vendored CDDL lexer never processed backslash escapes inside string literals at all, so a .regexp pattern needing an escaped backslash (dm-room-path's \\+, and the already-shipped namespaced-domain-id's \\.) generated a JS regex with an extra literal backslash, silently matching the wrong thing. Fixed at the source: webdriverio/cddl#91/#92 upstream, cherry-picked into ExaDev/cddl.js#11 (released as 1.0.1), bumped here.

message-ref = { id: bstr, relation: tstr } gives room.send's optional refs array one open, typed relation ("reply", "forward", anything future) instead of a separate ad-hoc field per relation -- the same "open on purpose" precedent capability-scope.kind already set.

Nine new conformance vectors (7 frame, 2 token) round-trip through both the TS conformance test and Rust's conformance-check binary with zero Rust source changes -- confirms manage-command-params's Json catch-all arm and capability-scope's already-open kind field genuinely cover a brand new domain with no typed Rust arm needed, not merely assumed.

Test plan

  • just spec && just conformance -- all 9 new vectors PASS in both TS and Rust
  • just build && just test && just lint -- all green
  • cargo test && cargo clippy --all-targets -- -D warnings && cargo fmt --check -- all green, zero Rust diff

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-11T14:38:35.966987Z ca35fd1 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

room-path names the device-id(s) whose signature makes a room:member
membership token valid, rooted differently by shape: an owner-named
room (<owner-hex>/<local-name>) roots trust at the owner in the path;
a DM (the sorted pair of exactly the two participants' hex device-ids)
roots trust at the verifier itself, since either named party being an
acceptable root would let a sender self-issue a token and message a
stranger unsolicited.

room.send/room.read/room.leave/room.members share one capability,
room:member, mirroring exec:pty's existing one-resource-many-verbs
pattern. room.join/room.invite are deliberately ungated -- no token
field at all -- since a room with no prior relationship has no token
to gate first contact on; approval mints a token on the spot, carried
back as an extension field on the existing manage-ok response rather
than a new frame shape.

message-ref gives room.send's optional refs array one open, typed
relation ("reply", "forward", and any future kind) instead of a
separate ad-hoc field per relation, the same "open on purpose" pattern
capability-scope.kind already uses.
1.0.1 fixes a lexer bug where a backslash-escaped character inside a
CDDL text-string literal kept its backslash instead of being resolved
to the literal character it escapes. Without this, core/room's own
dm-room-path rule (and the already-shipped namespaced-domain-id rule)
generate a Zod regex that silently matches the wrong thing.
Adds the room-path/room-send/room-read/room-leave/room-members/
message-ref/room-join/room-invite schemas and the delegations-remaining
claim, generated against cddl.js 1.0.1 so dm-room-path's escaped-plus
regex is correct rather than double-escaped.
Covers room.send (with a reply message-ref), room.read, room.leave,
room.members under a room:member token, the ungated room.join/
room.invite pair with no token field at all, and the manage-ok grant
response riding manage-ok's own extension tail. Two new token vectors
exercise the delegations-remaining fix directly: an owner's root grant
capped at one further re-delegation, and a delegated child narrowed to
zero -- the exact chain that closes the unbounded-admission bug.

All nine round-trip through both the TS conformance test and Rust's
conformance-check binary with zero Rust source changes, confirming the
manage-command-params Json catch-all and capability-scope's open kind
field already cover a brand new domain without any typed Rust arm.
@Mearman
Mearman force-pushed the feat/core-room-domain branch from ca35fd1 to bbe321c Compare September 11, 2026 14:45
@Mearman
Mearman merged commit 22181d7 into main Sep 11, 2026
6 checks passed
@Mearman
Mearman deleted the feat/core-room-domain branch September 11, 2026 14:47
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.

core/room: unified rooms+DMs domain, plus noticeboard entries on core/data

1 participant