Files
amethyst/commons
Claude fad7347ca9 feat(marmot): encrypted media v2 end to end
`marmot.group.encrypted-media.v2` (0x800b) had a codec, a policy type and
a key schedule in quartz, and not one caller. The group could not carry a
media policy, nothing derived a v2 key, and Android still sent MIP-04
attachments — which peers on the current profile no longer create.

The policy is now group state: read through `MarmotGroupState`, committed
through `setEncryptedMediaPolicy`, and honoured by the sender. Both of its
lists are ordered on purpose — `default_blob_endpoints` order IS the
upload/fetch fallback priority — so reordering one is changing where the
group uploads, not reformatting it.

Sending and receiving run through the group's own MLS exporter, so the
blob store sees ciphertext and its hash and is storage rather than a party
to the conversation. `amy marmot media` drives the whole loop
(policy/set-policy/send/get), and Android picks v2 per group: a group
carrying `0x800b` gets a v2 reference, one that does not keeps MIP-04, and
the frozen v1 policy at `0x8008` is never reinterpreted as v2.

The URL normalizer is now shared rather than approximated twice. The media
policy says its base URLs use the same WHATWG normalization the avatar
component defines, and it was instead checking a hand-rolled structural
subset — which rejected `https://host//double/`. That URL is normalized:
WHATWG keeps the path as a segment list and only `.` and `..` are special,
which the reference `url` crate confirms. So the old check refused group
state the reference implementation produces, the exact failure the shared
normalizer exists to prevent. `http` is permitted for a blob store and not
for an avatar, because the components differ there and a self-hosted store
on a private network is real.

Two smaller things the reading turned up. `0x8007` was implemented but
missing from the advertised supported-component list, so a group requiring
an avatar URL would have refused our KeyPackage. And the note claiming we
do not advertise the agent-stream send/fanout capabilities has been false
since the sequence store landed.

Epoch 0 deliberately does NOT carry the media policy: the reference
implementation's own epoch-0 GroupContext does not, and adding it unasked
would both diverge from that and require every joiner to advertise
`0x800b` before it could be added. The conformance vector caught that when
the default went the other way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq
2026-09-09 16:49:39 +00:00
..