Skip to content

Docs/adr 0001 per device envelopes - #688

Closed
kosisochukwu1234 wants to merge 83 commits into
codebestia:mainfrom
kosisochukwu1234:docs/adr-0001-per-device-envelopes
Closed

kosisochukwu1234 wants to merge 83 commits into
codebestia:mainfrom
kosisochukwu1234:docs/adr-0001-per-device-envelopes

Conversation

@kosisochukwu1234

Copy link
Copy Markdown

Summary

Records the architectural decision to store one message_envelopes row per recipient device rather than a single shared ciphertext per message in docs/adr/0001-per-device-envelopes.md.

Details Covered

  • Context & Motivation: Multi-device cryptographic isolation, Phase-1 sealed-box and Signal Double Ratchet compatibility, and self-sync requirements across sender sibling devices (Multi-device self-sync (sender's own other devices) #188, Migrate send_file_message and ask_assistant to ciphertext + envelopes #337).
  • Decision: Atomic transaction-bound persistence via insertMessageEnvelopes, sibling-device coverage checks (fetchSiblingDeviceIds, findMissingSiblingDeviceIds) in apps/backend/src/lib/messageFanout.ts, targeted realtime delivery (deliverMessage), and uniform fan-out shapes for server-authored messages (buildBroadcastEnvelopes).
  • Alternatives Considered & Rejected:
    • One shared conversation ciphertext with a static shared key.
    • User-level envelopes (encrypting only to the recipient user).
    • Dual-pipeline routing (shared ciphertext for file/bot messages vs envelopes for text).
  • Consequences & Operational Costs:
    • Linear database growth ($O(\text{messages} \times \text{devices})$) mitigated by the background cleanup job in apps/backend/src/services/envelopeGc.ts.
    • Sibling-device enforcement on send and edit, rejecting payloads missing sibling envelopes with device_set_mismatch.
  • Exception for MLS Groups: Notes that MLS group messages store a single ciphertext per epoch on messages without per-device envelopes, referencing the next ADRs (docs/adr/0006-mls-for-groups.md / docs/adr/0002-signal-dms-mls-groups.md).

Closes #641

Chidimj and others added 30 commits August 30, 2026 03:12
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
Dannyorji and others added 28 commits October 2, 2026 00:59
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
…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.
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
@github-actions

Copy link
Copy Markdown

👋 Hi @kosisochukwu1234, thanks for your contribution!

Pull requests from contributors must target the dev branch — only the repo maintainer merges into main.

This PR is being closed automatically. Please open a new PR (or retarget this one by reopening it after editing the base branch) against dev.

@github-actions github-actions Bot closed this Oct 25, 2026
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.

ADR: per-device envelopes instead of shared ciphertext