Repository navigation
Docs/adr 0001 per device envelopes - #688
Closed
kosisochukwu1234 wants to merge 83 commits into
Closed
kosisochukwu1234 wants to merge 83 commits into
kosisochukwu1234 wants to merge 83 commits into
Conversation
Adds docs/README.md as the single entry point to the repository's documentation. Every .md file in the repo (excluding generated output and dependency directories) is listed exactly once with a one-line description. Documents are grouped by reader intent rather than by directory: new contributor, backend developer, frontend developer, contract developer, operator, security reviewer, and AI/data developer. A document needed by two roles is listed under both, since each section is meant to stand alone. Includes a "Start here" path for first-time contributors that ends at the contribution guidelines, and a maintenance section stating that the index must be updated whenever a document is added, moved, or removed. The root README now links to the index and to the architecture overview. Note: the issue refers to a top-level CONTRIBUTING.md and a docs/adr/ directory. Neither exists in this repository, so the "Start here" path ends at the Contributing section of the root README and calls out that it should be repointed if a dedicated CONTRIBUTING.md is added.
Adds docs/architecture-overview.md as the orientation entry point for the system: one Mermaid diagram showing all four apps, the stateful infrastructure (Postgres, Redis, object storage, Weaviate), the Stellar network, and the external LLM provider, plus a table naming the protocol on every edge. Each component gets a short paragraph covering its responsibility and, explicitly, what it must never do — the invariants that are easy to break accidentally (the gateway must not acquire a decryption path, Redis must not be treated as durable, the AI agent must not gate a transfer). Two end-to-end paths are traced concretely against the current code: sending an encrypted message (per-device envelope encryption through deliveryPipeline fan-out to delivery receipts) and executing a treasury withdrawal (on-chain propose/vote/execute with the listener maintaining an off-chain mirror). The document is explicitly scoped as orientation only and defers to the per-app documents, which it links throughout. Notes a real defect found while tracing path 2: the backend listener subscribes to a `proposal_executed` topic that no contract publishes — `proposals` emits the truncated symbols `executed` and `execut`. The per-event detail belongs in the contract events reference (codebestia#581); this document links to it. The issue refers to IMPLEMENTATION_DOCS.md as the existing long-form design spec. No such file exists in this repository, so this document links to the per-app docs as the deeper reference instead.
Adds contracts/docs/concepts-upgrades.md covering token_transfer's upgrade(env, new_wasm_hash) entrypoint end to end. - Authorization: the single admin.require_auth() gate, that the admin is fixed at initialize with no rotation path, and what a compromised admin key means (total, unrecoverable loss of control over the contract). - Procedure: build with the pinned release profile, upload to obtain the wasm hash, verify that hash locally with sha256sum and via an independent reproduction, invoke as the admin, then confirm by reading state back. - Storage compatibility: soroban-sdk encodes contracttype enums by variant-name symbol and structs as name-keyed sorted maps, so renames and type changes corrupt or orphan state while reordering is harmless. Tables split safe from unsafe changes on that basis, and note the rename of DataKey::Admin as the unrecoverable case. - Records that only token_transfer is upgradeable; group_treasury and proposals have no upgrade entrypoint, so changing them means a fresh deployment and repointing every consumer. - Notes the absence of any version() entrypoint or upgrade event: the wasm hash is the only on-chain version identifier.
Adds contracts/docs/contracts-events.md documenting all sixteen env.events().publish(...) call sites across token_transfer, group_treasury and proposals: topic tuple, data payload field by field, the state change each event signals, and whether the publish happens before or after the corresponding storage write. Emission order is called out per event because two of them — group_treasury's proposal_approved and proposal_rejected — publish before the proposal is persisted, setting the status on the in-memory struct first. Not observable to an off-chain consumer given transaction atomicity, but relevant when reading the contract. A summary table marks each event as consumed or unconsumed by the backend listener, and a dedicated section collects the five consumption gaps found while cross-referencing the contracts against stellarListener.ts: - the listener subscribes to `proposal_executed`, a topic no contract publishes. proposals emits `executed` and `execut` instead, so the status map entry is unreachable and a proposal never reaches the executed status in the off-chain mirror via the listener - group_treasury has no execution event tying a fund movement back to the proposal that authorised it - deposit and withdraw, the two value-movement events, have no consumer - only one contract id is watched for treasury topics, and both contracts publish a `proposal_created` with different payloads and different id widths (u32 vs u64) - vote-level events are unconsumed; on-chain votes are invisible to the backend until a transition event fires Also documents two payload gaps (DepositEvent and WithdrawEvent omit the token address despite per-token balance tracking) and notes that the proposer's auto-approval emits no withdraw_vote, so counting those events undercounts approvals by one. The `executed` / `execut` split is flagged as a naming defect rather than a constraint: symbol_short! permits 9 characters and "executed" is 8, so the truncation is unforced. Cross-links the backend chain listener source, the treasury API doc, and the deployment doc.
Adds docs/api-versioning.md. States the current position plainly: REST routes are mounted unprefixed in app.ts, socket events are addressed by bare name with no version in the payload or handshake, and no deprecation machinery exists anywhere in apps/backend/src. Records the three breaking changes that already shipped without one - sequenceNumber dropped from GET /sync envelopes, device made required on POST /auth/verify, and JWT-only registration retired at POST /devices. Defines what breaks for REST responses, socket payloads, and database-backed shapes, including the case where a migration changes a response without any route being edited, and the asymmetry that strict request schemas make an added optional field breaking in the other direction. Gives the deprecation procedure: announce with a sunset date, mark per surface, ship the replacement first, hold the window, then retire behind an explicit error naming the replacement. The retired POST /devices handler, which still answers 403 pointing at the link-challenge flow, is the precedent the procedure generalises. Sets a 90-day minimum window, derived from the 7-day envelope retention floor plus stale tabs and cached service workers. Documents device capability negotiation as the mechanism that already ships protocol changes without breaking older clients, and names the properties that make it work: a universal sealed_box baseline, absence normalising to that baseline rather than erroring, unknown values preserved and ignored, and pairwise per-message selection.
Adds contracts/docs/concepts-resource-budget.md covering the deployed size constraint and the wider resource picture around it. Documents the gate as CI actually implements it: 102,400 bytes per contract, enforced in the "Report WASM binary sizes" step of contracts-ci.yml, run per-contract across a fail-fast:false matrix, measured against the raw cargo build output with no wasm-opt step, and posted to the PR as an update-in-place comment keyed on a marker comment. Notes that the gate is per-contract rather than cumulative and that the step reports the full table before failing. Records current sizes measured from a local release build, with headroom: group_treasury 31,843 B (31.1% of gate), proposals 25,931 B (25.3%), token_transfer 10,539 B (10.3%). Includes the command to reproduce them, since sizes drift with soroban-sdk upgrades. Documents each [profile.release] setting and the cost of changing it, including that overflow-checks = true costs size deliberately and should not be traded away in contracts that move funds. Gives concrete size-reduction techniques in effort order: profile with twiggy first, run stellar contract optimize, replace panic string literals with a #[contracterror] enum, keep formatting machinery out of the shipped path, deduplicate generic instantiations, split along a real boundary, and only then raise the threshold. Closes with the resources beyond raw size — CPU instructions, memory, ledger entry reads/writes, events, rent/TTL, and transaction size — and identifies the constraint that actually binds first in this codebase: all three contracts keep per-proposal and per-voter data in instance storage, which grows unbounded and is read and written whole on every call, and several functions do linear scans over it.
Adds apps/ai_agent/docs/configuration.md. Documents that OPENAI_API_KEY is the only environment variable main.py reads, with required status, no default, and no .env loading, and lists the variables the service does not read - model selection, Weaviate host/port/key, HOST and PORT - since setting those silently does nothing. The model choices, bind address, collection name, search limit, high-value threshold and per-call timeouts are tabulated as hardcoded values, with the note that the 0.0.0.0:8000 literals only apply to direct python main.py execution and not to fastapi/uvicorn launches. Documents the missing-API-key behaviour precisely: the key is read per-request inside _openai_client(), so the process starts clean, /health returns 200 and every OpenAI-backed endpoint returns 500. Both halves are pinned by tests. Spells out the load-balancer consequence - an instance with a bad key passes its liveness probe, joins the pool and serves nothing but 500s - and records the endpoint-by-endpoint matrix, including the high-value /transfers/analyse branch that answers 200 without ever constructing a client. Documents the Weaviate connection as an unconfigurable per-request connect_to_local() on the client's localhost defaults, the 503 failure mode, that infra/docker-compose.yml ships no Weaviate service, and that a containerised agent cannot reach a sibling container over loopback. Cross-links the repo-wide .env.example as the single environment reference.
Adds apps/web/docs/styling.md. Documents the Tailwind v4 setup: no tailwind.config file, the single @tailwindcss/postcss plugin, and globals.css as the only global stylesheet - with the distinction between its :root variable block and its @theme inline mapping, which is why both bg-card and bg-[var(--card)] compile. Gives guidance on which idiom to use, since the arbitrary-value form is roughly twice as common and is the only one that takes an opacity modifier. Documents the seven colour tokens with their roles and how heavily each is used, the spacing ladder and typography scale the existing screens actually follow, and the Badge tinted-surface recipe that stands in for absent status tokens. Records the ~30 off-token gray/slate and raw-hex occurrences as drift rather than precedent. Covers breakpoints - defaults, mobile-first, only sm/md/lg in use with zero xl or 2xl - and the hand-followed class order, noting that nothing enforces it since there is no prettier-plugin-tailwindcss and no Tailwind ESLint rule. States extraction rules in both directions, anchored on the three- occurrence threshold, variants, accessibility behaviour and non-utility CSS, and notes the absence of any cn/clsx helper. Records that dark mode does not exist: one :root palette that is itself dark, no prefers-color-scheme block, no darkMode config, no theme provider or toggle. The single dark: variant in CopyButton can never activate, so nobody should assume dark-mode support works or write new dark: variants.
…s-web-reference docs: contract upgrades, API versioning, AI agent config, and styling conventions
docs: documentation index, architecture overview, contract events reference, and resource budget
Documents the standard Soroban test scaffolding, the three levels of auth mocking and the risk of blanket mock_all_auths() hiding a missing require_auth(), how to test expiry via the virtual ledger clock, and the proposals -> group_treasury cross-contract test setup.
Documents sw.js registration/update lifecycle via skipWaiting + clients.claim, the content-free push handler, click-to-route focusing an existing tab instead of opening a duplicate, and what does and does not work offline today (no precaching, no fetch interception, no background sync).
Documents the toast API vs inline error state, maps backend error responses (REST schema error format, per-endpoint docs) onto user-facing messages, states the hard rule that decryption failures render as UnavailableMessagePlaceholder rather than a generic crash, and covers current send-failure/offline behaviour including known gaps (silent socket errors, no send retry or offline queue).
States the WCAG 2.1 AA target and that conformance is currently checked manually (no automated a11y tooling in the repo yet). Documents keyboard navigation for the conversation list and composer, contrasts Modal's full focus-trap/restore implementation against the safety-number panel's current gap, explains the aria-live approach for incoming messages without stealing focus, flags the low-opacity text contrast risk, and gives a pre-merge checklist. Also registers all four new docs (contract testing, service worker, error handling, accessibility) in the documentation index.
docs: add contract testing guide, service worker, error handling, and accessibility docs
Adds docs/monorepo.md (workspace layout, Turbo task graph, shared tooling), apps/web/docs/components-ui.md (shared UI primitives reference), apps/web/docs/concepts-state-management.md (provider tree, Auth/Wallet contexts, ProtectedRoute), and apps/web/docs/testing.md (Vitest setup, fake-indexeddb, WebCrypto, socket mocking). Closes codebestia#544 Closes codebestia#568 Closes codebestia#572 Closes codebestia#573 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R8SebNynWtmCebVq8tpgxS
docs: add monorepo, UI components, state management and testing guides
…-docs-5qhb7p Claude/ci cd glossary docs 5qhb7p
Adds three documents covering gaps in the contributor-facing documentation, plus the matching rows in the documentation index. docs/testing.md - cross-app testing philosophy and conventions: - States the no-external-services rule explicitly, with the reasoning and the approved substitute for each dependency (ioredis-mock for Redis, a hand-built db mock for Postgres, LocalDiskObjectStore for S3/MinIO, fake-indexeddb and the web setup shims, pytest-mock fixtures for the AI agent, and the Soroban Env harness for contracts). - Documents the per-app runners and commands: vitest for backend and web, pytest for the AI agent, cargo test for contracts, plus the include and exclude patterns that decide which files are collected. - Explains the established Drizzle mocking pattern, including why the module under test is imported with a dynamic import after the mocks, and why .values() sometimes has to be both thenable and expose .returning(): the message insert chains .returning() for the generated id while the envelope insert only awaits .values(), so a stub supporting one shape silently records nothing for the other. - Documents that socket handlers must be driven through the enveloped dispatch event rather than a raw socket.on listener, since there is no raw listener and grabbing one bypasses envelope validation, the auth gate, and eventId idempotency. - Notes the shared in-process rate-limit counters that leak between tests, with the reset hooks for that and the other module-level state. apps/backend/docs/migrations.md - drizzle-kit migration workflow: - Establishes schema.ts as the source of truth and migrations as generated output, never hand-written first, with the one documented exception. - Documents the drizzle/ layout and meta/_journal.json as the file that decides what actually runs, and that a .sql file missing from it is silently skipped. - Documents the merge hazard concretely against the incident already in this repository's history: seven colliding 0001_* files and a journal entry with duplicate JSON keys, which left nine migrations unlisted and forced a squash back to a single baseline. - Gives the recommended conflict-resolution procedure - merge schema.ts, take the base branch's drizzle/ wholesale, drop your own migration, and regenerate - plus the checks that catch a collision before it lands. - Notes that drizzle/meta/ is Prettier-ignored because it is generated output. SECURITY.md - vulnerability disclosure policy: - Gives GitHub private vulnerability reporting as the private channel, with acknowledgement, triage, update, and fix windows. - States explicitly that vulnerabilities must not be reported through public issues, pull requests, or discussions, and why. - Defines scope across the backend, web client crypto, contracts, AI agent, and supply chain, with an explicit out-of-scope list and testing rules. - Cross-links docs/threat-model.md so reporters can tell an accepted residual metadata risk from a real finding. - Adds a contract-specific path: only token_transfer is upgradeable, so a bug in group_treasury or proposals cannot be fixed by a redeploy and funds already in a vulnerable instance may be unrecoverable. Closes codebestia#545 Closes codebestia#546 Closes codebestia#548
Document every table, column, index, check constraint, and FK relation in db/schema.ts, with a mermaid ER diagram and a dedicated section on where Drizzle's relations() blocks diverge from actual DB constraints, plus verified schema gaps (unindexed conversation_members, unused content_type enum, non-unique files.sha256, FK-less audit_logs actor/subject columns). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: add group_treasury API reference, presence/heartbeat, E2EE key lifecycle, and database schema docs
Documents that none of the three contracts ever extend TTLs, so every instance-storage key is at risk of archival, and gives operators the stellar CLI commands to extend or restore before/after it happens.
Plain-language companion to threat-model.md covering device linking and revocation, safety-number verification, why a newly linked device can't read old group history, and the AI assistant's E2EE exception.
Walks through require_auth coverage, init front-running vs. re-initialization, admin key powers, i128 overflow, zero/negative amounts, threshold edge cases, double voting, and storage archival for all three contracts, citing the exact code or test behind each answer and marking unproven items as open findings. Adds a contracts checklist section to the PR template that links it.
Explains membership and the approval threshold with a worked three-member example, that funds live in an on-chain contract rather than inside Clicked, why voting asks for a Freighter signature, and that proposal expiry/rejection never moves funds since nothing is reserved when a proposal is created.
docs: add contract and user-facing reference docs for treasury, devices, and contract security
…aterial and socket event guides
…nt how-to and crypto module map (codebestia#636 codebestia#634 codebestia#617 codebestia#624)
…docs docs: token transfer flow, testnet treasury tutorial, rest endpoint how-to, crypto module map
…l-prekey-keys-socket docs(backend): operational endpoints, prekey replenishment, key material, socket event guide
…on matrix, contract function how-to (codebestia#618, codebestia#619, codebestia#622, codebestia#635) concepts-message-edit-delete.md (codebestia#618): edit_message inserts a new linked message rather than mutating the original (editsMessageId always points at the root, not the previous edit in a chain, not a literal backward linked list despite how the read-side comment on supersededRows describes it); delete_message nulls ciphertext, hard deletes every message_envelopes row, and conditionally soft-deletes an attached file only if no other live message still references it. Documents the sender-only authorization rule for both; that a deleted message's envelope can never reach a device via /sync (both because the envelopes are already gone and because of an explicit isNull(messages.deletedAt) filter); that an edit's sibling-device requirement runs through the exact same findMissingSiblingDeviceIds send_message uses; the honest limit that delete cannot un-reveal plaintext a recipient already decrypted; and that delete_message actually double-emits message_deleted to two different room names every connected socket joins, so a client receives it twice, not once, a real finding from tracing the two io.to() calls against index.ts's connection setup, not something I assumed from the existing code comments alone. concepts-ai-assistant-bridge.md (codebestia#619): traces client event to rate-limit bucket to the hardcoded http://localhost:8000/chat call to the reply being persisted and fanned out through the same message_envelope pipeline as any other message. States plainly that the reply is stored unencrypted (same storage column named ciphertext, actually plaintext) and that the user's message leaves Clicked's infrastructure entirely and reaches OpenAI, per the AI agent's own api-chat.md. Documents the single try/catch collapsing every distinct failure mode, connection refused, non-2xx, malformed JSON, DB error, into one generic error with no way for a client to tell them apart. concepts-push-decision-matrix.md (codebestia#622): the full filter chain in pushFilter.ts as a table, confirming from presence.ts's isOnline that online status is checked per user via a single presence hash, not per device, so one online device suppresses push to every other device that user owns, before the separate per device isDeviceConnected check even runs. Layers in the 2 second coalescing window and 30 second per-device rate limit both push paths share via queueCoalescedPush, confirms the push payload carries no message content, and notes a real asymmetry: dispatchOfflinePush always passes an explicit recipientDeviceIds list but sendPushForMessage never does, so that filter stage is a no-op for the file-message path today. howto-add-contract-function.md (codebestia#635): worked example of a read-only function (is_initialized) and a state-changing one (set_paused, which also gates transfer and emits a new event) on token_transfer, chosen specifically because it is the one contract with an in-place upgrade path; both examples were actually compiled against the real contract and its existing test suite (8/8 passing) before being written up, not written from reading the code alone. Documents regenerating test_snapshots/ and that the CI check that fails if it's forgotten is simply cargo test's own storage-footprint mismatch detection, not a separate check. Covers the frontend side (building, simulating, signing, submitting via soroban.ts's existing transferToken pattern) and when the backend's Stellar listener (codebestia#331) needs its topic filter and decode logic extended for a new event, linking the events reference (codebestia#351). Added index entries for all four docs in docs/README.md, matching the existing per-doc one-line-description convention.
…known limitations register, scripts reference (codebestia#625, codebestia#637, codebestia#638, codebestia#639) apps/web/docs/concepts-x3dh-double-ratchet.md (codebestia#625): checked lib/x3dh.ts and lib/crypto/doubleRatchet.ts against the Signal specs directly, not from the issue description. Confirmed the Ed25519 to X25519 identity-key conversion and why (shared with the backend's signature verification), traced all four X3DH DH inputs on both the initiator and responder side against the spec's own DH1 to DH4 definitions, confirmed the HKDF salt being undefined is spec-compliant (not an omission, per RFC 5869 and this codebase's own HKDF implementation), and confirmed the one real spec addition this codebase makes, a global MAX_SKIPPED_KEYS cap across every chain combined, which the Signal spec does not define at all (it only specifies the per-chain MAX_SKIP this codebase also has). Verified every code snippet actually compiles and the existing test suite still passes by applying the worked examples to the real contract locally before writing them up, then reverting. Ran the x3dh.vectors.test.ts and doubleRatchet.test.ts suites directly to confirm both pass (2 and 4 tests respectively) before citing them. docs/flows/device-linking.md (codebestia#637): the challenge or verify handshake, traced against routes/devices.ts directly. A genuinely new finding from tracing it: emitDeviceChangeEvent (used by both linking and revocation) double emits its key change notice to two different room names every socket joins, the same pattern already documented for delete_message in the message edit or delete concepts doc. Documents that MLS key packages are specifically NOT deleted on revocation (only devicePrekeys rows are), and a real gap found via a repo wide search: there is no client side caller anywhere for the link challenge or verify endpoints, so the backend flow has no UI path today. docs/known-limitations.md (codebestia#638): seeded with all six items the issue asked for, each independently verified against current source rather than copied from the issue body. Found existing tracking issues for four of the six (auditLogsRouter: codebestia#648 and codebestia#650; hardcoded AI agent URL: codebestia#651; the four orphaned files: codebestia#679) and opened a new issue, codebestia#681, for the one item with no existing tracker: ratchetSession.ts being unwired and rejecting out of order messages. The sixth item (inconsistent NEXT_PUBLIC_API_URL defaults) is tracked by codebestia#629, a documentation issue that will itself need to describe this same inconsistency. Linked from CONTRIBUTING.md and docs/README.md per the acceptance criteria. docs/scripts-reference.md (codebestia#639): covers every script and Makefile target the issue named. Two real findings from checking rather than assuming: the Makefile's deploy-contracts target only wraps two of the three contract deploy scripts, missing deploy_proposals.sh entirely; and scripts/loadtest/ genuinely is invoked by CI, nightly plus manual workflow_dispatch, which I initially assumed it was not until a direct grep across every workflow file turned it up, corrected before writing the final version rather than publishing the wrong claim. Explains audit-crypto-deps.mjs's actual matching logic (exact name or subpath prefix) versus what its own top comment claims (does not actually walk the dependency tree), and that apps/backend/src/scripts/check- ciphertext-invariant.mjs is not invoked by any CI workflow at all, distinct from the similarly named vitest test security-ci.yml does run.
…odebestia#630 codebestia#640 codebestia#626 codebestia#627) Co-authored-by: Cursor <cursoragent@cursor.com>
docs: message edit/delete, AI assistant bridge, push decision matrix, contract function how-to
docs: X3DH/Double Ratchet notes, device-linking flow, known limitations register, scripts reference
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…oncepts Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…26-627 docs: landing components, style guide, identity trust, and MLS client (codebestia#630 codebestia#640 codebestia#626 codebestia#627)
docs: contracts storage/interfaces, web routing, and getting-started user guide
Store the real token contract ID on indexed transfers
Create build-and-config.md
|
👋 Hi @kosisochukwu1234, thanks for your contribution! Pull requests from contributors must target the This PR is being closed automatically. Please open a new PR (or retarget this one by reopening it after editing the base branch) against |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Records the architectural decision to store one
message_envelopesrow per recipient device rather than a single shared ciphertext per message indocs/adr/0001-per-device-envelopes.md.Details Covered
send_file_messageandask_assistantto ciphertext + envelopes #337).insertMessageEnvelopes, sibling-device coverage checks (fetchSiblingDeviceIds,findMissingSiblingDeviceIds) inapps/backend/src/lib/messageFanout.ts, targeted realtime delivery (deliverMessage), and uniform fan-out shapes for server-authored messages (buildBroadcastEnvelopes).apps/backend/src/services/envelopeGc.ts.device_set_mismatch.messageswithout per-device envelopes, referencing the next ADRs (docs/adr/0006-mls-for-groups.md/docs/adr/0002-signal-dms-mls-groups.md).Closes #641