Skip to content
The technical standard

This is the formal specification UMI is built on — written for developers and people implementing the protocol. If you just want to understand, in plain words, how we protect the people who use these boards, read Privacy & retention.

The UMI Protocol

Version 0.1 — CANONICAL (keyed by the steward, 2026-07-14) United Moral Infrastructure · Steward: Jasiah, United Moral Infrastructure (a nonprofit being established as a 501(c)(3))

License: CC-BY-4.0. You may share and adapt this document with attribution.

Status: CANONICAL v0.1. This document was written from the reference implementation (umi-exchange): the citations throughout the codebase predate any written specification, so the specification was recovered from the behavior the code already enforces, then read and keyed by the steward. Section numbers are FIXED to citation-inventory.md — 60 sections, 529 citations — so every existing reference resolves. Change control: this text is frozen at v0.1; any future edit ships as a new version keyed by the steward. [derived] markers record which intents were confirmed from design documents at the key (all confirmed, 2026-07-14); reserved sections remain non-normative for v0.1.


Every promise this board makes is written down. The rules below say who can see what, what gets encrypted, and what gets shed on schedule. They live on this server, so the footer's link stays true even on an offline laptop.

The protocol is licensed CC-BY-4.0. Copy it, adapt it, build your own board on it. Prefer the plain file? Read spec.md.

Found a security problem? SECURITY.md says how to reach us. Write to us first, quietly — real people use these boards.

§0 — Purpose, scope, and how to read this document

The UMI Protocol defines how a community coordinates reciprocal aid — neighbours asking, neighbours answering — with dignity, consent, and privacy, and how independent instances may connect with each other by consent rather than surveillance. UMI is the infrastructure ("the pipes"); each community is the water. This document is the standard; umi-exchange is its reference implementation, and anyone may implement the protocol independently.

Requirement keywords — MUST, MUST NOT, SHOULD, SHOULD NOT, MAY — are used per RFC 2119.

Protocol versus implementation (the load-bearing distinction). This specification governs wire behavior, data semantics, privacy invariants, and state machines. It does NOT mandate an implementation. Django, HTMX, Alpine, PostgreSQL, the specific cryptographic library, the HTML templates — these are the reference implementation's choices, and a conforming implementation MAY choose differently. When this document names a mechanism (for example "MultiFernet over a KEK list"), it is describing the reference implementation as an existence proof; the normative requirement is the property (rotation-ready authenticated encryption with per-record crypto-shred), not the library.

Conformance levels. An implementation claims one or more levels; each level requires the sections listed in §13. Core (§1, §6–§8, §10, §12) — the reciprocal aid board. Casework (adds §3) — privacy-first intake and case records. Federation (adds §2, §4.1, §5, §9, §11) — consented connection between instances. Each level MUST fully satisfy every section it names; partial conformance MUST NOT be claimed.


§1 — Entities

The Core entities are Need (an ask), Offer, Match, and Consent. A Community scopes them all; a Member is a person's identity within one community, distinct from the authentication User (this distinction is normative and load-bearing for §8.6).

  • A Need MUST belong to exactly one community and carry a requester (a Member), a category, an urgency, a status, and an expiry.
  • Personally identifying free-text on a Need (for example, the name of a third party a need is raised on behalf of) MUST be stored encrypted per §12, never in plaintext columns.
  • A Match binds a Need to an Offer (or to a volunteering proposer with no standing Offer) and moves only through its state machine (§6).

§1.4–§1.6 — Need classification [derived; DESIGNED, non-normative for v0.1]. A need MAY carry a classification (type, urgency inference, routing hints). The reference implementation treats this as a future capability (docs/design/mobile-companion-and-classifier.md); it is reserved here and imposes no requirement at v0.1.


[derived from docs/federation-design.md; verified against apps/federation.]

  • An instance has a stable cryptographic identity. Two instances connect only through a pairwise, human-approved link (§2.2): a one-time pairing code plus key-thumbprint verification, approved on both sides, with a bounded time-to-live.
  • §2.3 — A link records the remote community's stable identifier and public key; nothing is shared by the mere existence of a link (link establishment is Stage A: identity only).
  • §2.5 — A Person (the casework subject, distinct from Member and User) is never federated; Person identity is local to its instance.

[verified against apps/casework, apps/consent, apps/people.]

  • §3.2 — Sensitive case data MUST be encrypted at rest per §12.
  • §3.5 — Entities with a lifecycle MUST change state only through an explicit transition function; invalid transitions MUST be refused (the reference implementation raises a TransitionConflict, surfaced as HTTP 409).
  • §3.6 — Consent revocation freeze (normative). When a subject's consent is revoked, the implementation MUST stop new writes against that consent (no new notes, no export) and MUST re-check consent on any deferred or follow-up write. Revocation MUST take effect immediately for new actions; already-exchanged information is not retroactively recalled (it is treated the way a past conversation is).
  • §3.7–§3.8 — Case opening MAY use an emergency override that permits a null consent at creation (a database-level constraint allows this only under the override flag); consent MUST then be regularized.
  • §3.11 — Deployment MUST provision separate database roles so the append-only audit REVOKE (§8.3) binds the application's own runtime role.

  • §4.1 — One-action consent (normative). Any disclosure beyond a community's own boundary MUST be an explicit, single, member-owned action. Coordinators MUST NOT consent on a member's behalf. Only a redacted outline (category, urgency, coarse locality, a coarse time bucket) MAY cross a boundary before an accepted match; names and contact details MUST NOT.
  • §4.2–§4.3 — A consent record names its grantee, its scope (an explicit set of fields), its purpose, and its state; a scope check MUST verify that every requested field is within the granted scope.
  • §4.4 — Retention (the rule is protocol; the values are policy). Aged data MUST be shed on a schedule, by crypto-shred (§12), not merely hidden, and the schedule MUST be bounded and published. The specific values are each implementation's POLICY, not protocol constants; as the existence proof, the reference implementation sheds aged-need PII at 365 days, closed casework at 7 years, and revealed contact snapshots at 72 hours. A conforming implementation MUST publish its actual retention and MUST NOT claim a retention it does not enforce.

§5 — Federated sharing, discovery, and attestations (Federation level)

[derived from docs/federation-design.md; verified against apps/federation.]

  • A member-owned share (§4.1) publishes only the redacted outline to linked communities.
  • §5.4 — An instance MAY answer a signed, capability-gated attestation query about a member claim (for example, a verified tag). A self-reported claim MUST NOT be presented as verified.
  • §5.8 — [reserved] A 72-hour hard-erasure target is named in design; the honest path is crypto-shred (§12). Until a conforming erasure is shipped, an implementation MUST state its actual retention rather than claim §5.8.

§6 — Matching

  • A Match moves proposed → accepted | cancelled | expired, and accepted → fulfilled | unfulfilled | cancelled. Terminal states MUST be enforced by the transition function (§3.5).
  • §6.3 — [Federation] Cross-instance match state is re-synced by a signed GET of the authority instance's signed match state; item failures MUST NOT roll back siblings (§9.1).

§7 — Self-match boundary

  • §7 — An implementation MUST prevent self-matching across BOTH identity axes: the proposer's Member and the underlying User. For federated matches, a blind self-match token MUST allow the authority to detect a self-match without learning the counterpart's identity.

§8 — The exchange (the peak) (Core)

  • §8.2 — Contact revelation (normative; the most-cited rule in the system). Contact information MUST be revealed only after a match is accepted, and only to the match's participants and the community's coordinators. A volunteer who proposed without a standing Offer counts as a participant. Every disclosure MUST be audited (§8.3). No party outside this set MUST be able to obtain contact information through the match.
  • §8.3 — Append-only audit (normative). The audit log MUST refuse UPDATE and DELETE at the application layer AND at the database layer (a REVOKE of UPDATE/DELETE/TRUNCATE on the audit table from the runtime role). Client IP MUST be stored as a salted hash, never raw, and MUST be read from the trusted reverse-proxy header, never the spoofable left-most X-Forwarded-For. Audit actions MUST be short (the reference implementation caps at 32 characters) and MUST NOT contain PII.
  • §8.6 — Self-match prevention (normative). Proposer MUST NOT equal requester, and offer-owner MUST NOT equal requester, checked on both Member and User identity (§7).
  • §8.7 — Concurrency (normative). A match acceptance MUST lock the contended rows so a second concurrent acceptance fails cleanly (the reference implementation locks the Match and Need rows and returns HTTP 409 on the loser). Contended writes MUST NOT double-accept.

§9 — Federated delivery (Federation level)

[derived from docs/federation-design.md; verified against apps/federation.]

  • §9.1 — Delivery is per-item: each item is its own transaction; one item's failure MUST NOT roll back its siblings.
  • §9.2 — A discovery/query signature MUST bind the request's parameters, so a captured signature cannot be replayed against different parameters.
  • §9.3 — Outbound federation events MUST be queued inside the transaction that owns the state change they describe, and idempotent replays MUST re-carry their side effects.

§10 — Board operation (Core)

[verified against apps/needs, apps/accounts, apps/people.]

  • §10.1–§10.2 — Search and feed MUST be community-scoped; a query MUST NOT return another community's content.
  • Authentication endpoints MUST be rate-limited (the reference implementation throttles register and login per trusted IP).
  • §10.6 — Needs and offers MUST expire on a schedule.
  • §10.7 — Expiry and erasure of PII-bearing records MUST use crypto-shred (§12), not a soft hide.

§11 — Rate and retention caps (Federation level)

  • §11 — Federation endpoints MUST enforce per-peer wire caps (a bounded number of requests per period). Retention numbers for federated artifacts (shadow-record TTL, contact retention) MUST be bounded and published (values [derived]: shadow TTL 7 days; contact retention terminal on the local rule).

§12 — Encryption and crypto-shred (Core)

[verified against apps/people/crypto.py.]

  • §12.1 — Encrypted state transitions MUST go through the state machine (§3.5).
  • §12.2 — Envelope encryption (normative). PII fields MUST be encrypted with a per-record data key (DEK) that is itself wrapped by an environment-level key (KEK) list supporting rotation. Reading or writing an encrypted field MUST occur only through its model property; raw ciphertext columns MUST NOT be read or written directly. Crypto-shred: deleting a record's PII MUST null BOTH the ciphertext AND its wrapped DEK, rendering the record permanently unrecoverable, so that a shred is a true erasure and any census of encrypted fields stays clean.
  • §12.3 — Blind index [reserved; DESIGNED, non-normative for v0.1]. A deterministic keyed index over a name field, for equality lookup without decryption, is named in design and drafted in a gated branch. It imposes no requirement at v0.1; when shipped it becomes normative under a new version.

§13 — Conformance

An implementation MUST satisfy every section named by each level it claims.

Level Requires
Core §0, §1, §6, §7, §8, §10, §12
Casework Core + §3
Federation Core + §2, §4.1, §5, §9, §11

§4 consent semantics beyond §4.1 apply wherever consent is recorded. Reserved sections (§1.4–§1.6, §5.8, §12.3) impose no requirement at v0.1. An implementation claiming a level MUST NOT claim it on partial satisfaction.


Appendix A — Non-normative: future directions

Need classification (§1.4–§1.6), hard-timed erasure (§5.8), and the blind index (§12.3) are directions the reference implementation is moving toward, not requirements of v0.1. Lakes beyond the board and casework (a skills directory, a pantry tracker, pastoral messaging, and others) are future protocol surfaces and are out of scope for this version.

Appendix B — Non-normative: reference implementation notes

The reference implementation is umi-exchange: Django 5.2, PostgreSQL, Redis, HTMX, Alpine, Tailwind, WhiteNoise, Argon2. These are choices, not requirements (§0). The traceability table (traceability.md) maps each normative requirement above to the code and tests that enforce it in the reference implementation.