0.1.0a11
Pre-release
Pre-release
Release 0.1.0a11.
An ENC:v3 fix release. Every fix here was found by verifying the browser
extension's new ENC:v3 support against a real install (Slack #my-hermes and
Discord #general) rather than by unit testing — on a real install, no v3
command could authenticate at all before this.
Fixes
- Resolve a v3 channel key stored under the extension's composite id, so
existing installs work with no state migration. The extension pushes
channel_key_setkeyed byslack:{team}:{cid}/discord:{guild}:{cid}
andsave_channel_keystores that verbatim, while a gateway event only
carries the platform's nativechat_id— so the exact-match lookup missed
every stored key and refused the command askey_not_bound_to_channel. The
composite is accepted only when its last segment is exactly the channel
id and its first segment is the event's platform, so the binding
stays as tight as the exact match it replaces. - Log the fixed
InvalidEncryptedEnvelopereason identifier on refusal (never
message text, keys or plaintext) — every refusal previously logged the same
sentence while needing completely different fixes, which made a live
rejection undiagnosable without replaying the wire by hand. - Authenticate a top-level Slack command under its real context. The Slack
adapter's defaultreply_in_threadsession keying stampsthread_idwith a
synthetic root equal to a top-level message's ownts; Slack assigns that
ts only after the send, so the extension provably encrypted with
thread_root=Noneand every genuine top-level v3 command was refused as
authentication_failed. The command AAD now canonicalizes to
thread_root=Nonewhen the routed root IS the command's ownmessage_id
(mirror of the documented Discord auto-thread canonicalization), while reply
routing keeps the synthetic thread. Canonicalizing requires two
adapter-stamped markers to agree, so a captured top-level token still cannot
be replayed into a genuine thread.
Changes
- Bump the pinned version surfaces to 0.1.0a11.
- Document the Slack synthetic-thread-root canonicalization in
docs/dev/SLACK_E2E.md§2.
Test plan
-
uv run pytest— full suite green; 11 new tests in
tests/extension/test_extension_e2e_v3.py(composite-id resolution,
platform-mismatch and partial-suffix refusals, synthetic-root top-level
acceptance, genuine-thread acceptance, top-level-token-in-thread
refusal, thread-token-at-top-level refusal, non-Slack no-canonicalize,
degeneratemessage_idfail-closed, replay through the dispatch hook,
reply_in_thread=falseparity) -
ruff check/ruff format --check/mypy --strict src tools - Adversarial review (independent agent): verdict SOUND, no
CRITICAL/HIGH/MEDIUM findings; its two hardening suggestions and five
coverage gaps were all landed - Live gateway round trip on the production install (2026-08-01): a
top-levelENC:v3command from the real Slack client decrypted by the
gateway, agent reply re-encrypted in-thread with the inbound kid; same
round trip verified on Discord including the auto-thread path - TestPyPI dry run + fresh-venv discovery check (release workflow)
Pairs with mordredagent/mordred-extension#11 and #12 — the extension
side of the same wire format.