Proposal 035: Agora — Topic-Addressed Record Relay and Shared Record Substrate¶
Based on:
doc/project/40-proposals/013-whisper-social-signal-exchange.mddoc/project/40-proposals/021-service-offers-orders-and-procurement-bridge.mddoc/project/40-proposals/023-federated-offer-distribution-and-catalog-listener.mddoc/project/40-proposals/024-capability-passports-and-network-ledger-delegation.mddoc/project/40-proposals/025-seed-directory-as-capability-catalog.mddoc/project/40-proposals/026-resource-opinions-and-discussion-surfaces.mddoc/project/40-proposals/032-key-delegation-passports.mddoc/normative/20-vision/VISION.md(Memarium, Swarm)doc/project/60-solutions/SOLUTIONS.mdorbidocs/AGENTS.md(Node-attached roles)
Status¶
Accepted
Date¶
2026-04-11
Executive Summary¶
Orbiplex today has several subsystems that each maintain their own append-only, signed, topic-shaped state:
- the seed directory replicates capability passports and service offers,
- the offer catalog holds federated offer listings,
resource-opinion.v1from proposal 026 authors opinions about external resources with no shared substrate to store or retrieve them,- and
whisper-signal.v1from proposal 013 dispatches privacy-bounded social rumors without any durable query surface.
Every one of these subsystems invents its own topic, its own relay, its own replication rhythm, and its own query API. The consequence is that adding one more kind of shared record — a comment under a URL, a note under a product, a public log entry under a workflow run — means designing one more bespoke store.
This proposal introduces one neutral substrate:
- Agora — a topic-addressed, append-only, signed record relay, operated by umbrella operators and consumable by any Node.
And one neutral envelope:
agora-record.v1
The key decisions are:
- a record is a signed, timestamped, content-addressed artifact authored by a participant and placed under one topic,
- a topic is addressed by an opaque
topic/keystring; the substrate does not interpret, type, or split it, and no single use case owns the shape of topic keys, - the record envelope is extensible by
record/kindandcontent/schema, so that opinions, comments, public logs, offer snapshots, or swarm gossip can all live side by side under one transport contract, - when a record is meaningfully about an external subject (a URL, a
product, a node, an org), it MAY carry an optional
record/aboutlist that references the resource identity model from proposal 026; this relation is orthogonal to topic placement and is not used as the primary key, - Agora relays are umbrella-operated and federated, following the same operational pattern as the seed directory (proposal 025) and the offer distribution layer (proposal 023),
- Matrix (via a dedicated homeserver deployment) is the reference backend
for the first implementation, but the
agora-record.v1envelope is backend-neutral and a later native transport may replace or complement it.
Agora is orthogonal to Memarium. Memarium, as described in the vision document, is the curated memory-and-knowledge layer that decides what must not disappear. Agora is a lower primitive: a durable, citeable log of public records indexed by topic. A community-scale Memarium space may use Agora as one of its underlying substrates, but it is not the only one and Agora does not depend on Memarium being present.
Context and Problem Statement¶
Several existing proposals implicitly demand a public, topic-addressed, append-only substrate:
- Proposal 013 (Whisper) defines a privacy-bounded rumor flow. Rumors are ephemeral by design and therefore do not need Agora, but the interest semantics and the threshold-reached events can cleanly degrade into durable records once a threshold is crossed.
- Proposal 023 (federated offer distribution) is a domain-specific append-only replication layer. It works, but it reinvents parts of what Agora would provide once, centrally.
- Proposal 025 (seed directory as capability catalog) is a similar replication layer with a different payload shape and a different query model.
- Proposal 026 (resource opinions) defines the opinion artifact and the generic resource identity model, but explicitly leaves the storage and discussion surface as a later attached layer.
Three separate systems, three separate relay mechanisms, three separate caches, three separate federation rhythms, three separate retention rules. The cost of adding the fourth one — comments on proposal pages, public notes under workflow runs, shared annotations under a code artifact — is disproportionately high.
The missing contract is not a schema. The missing contract is one neutral substrate that any future topic-shaped record can sit on without rewriting the transport.
What is absent today¶
| Need | Current state |
|---|---|
| One portable record envelope usable for many record kinds | Not present |
| A generic, backend-neutral topic addressing model shared across subsystems | Not present; each subsystem invents its own (proposal 026's resource identity addresses subjects, not topics, and is orthogonal to this question) |
| One umbrella-operated relay role for topic replication | Per-subsystem ad hoc |
| Subscription and replay under one topic | Per-subsystem ad hoc |
| A stable place for comments, public annotations, long-lived notes | Missing |
| A substrate for the community layer of Memarium | Missing |
Goals¶
- Define one neutral, signed, content-addressed record envelope
(
agora-record.v1) suitable for many record kinds. - Address topics by an opaque
topic/keystring, so that the substrate never has to interpret, type, parse, or split topic identifiers, and no single use case dictates their shape. - Keep resource identity (proposal 026) as an orthogonal axis, reachable
through an optional
record/aboutfield, so that the two questions where is this record? and what is this record about? remain cleanly separated. - Define the role of an Agora topic relay and its federation responsibilities.
- Define one minimal query and subscription API that any Node can call.
- Pick a reference backend (Matrix homeserver) but keep the envelope backend- neutral.
- Leave room for migrating existing subsystems (offer catalog, opinion storage) onto Agora without breaking them.
Non-Goals¶
- This proposal does not define how Memarium curates, indexes, or retrieves records semantically. Memarium remains a separate primitive.
- This proposal does not define E2E-encrypted private topics. Agora records are public-by-default at the relay level; private channels are a later proposal.
- This proposal does not define moderation, sanctions, or content policy. Those attach to record kinds, not to the substrate.
- This proposal does not mandate Matrix. Matrix is picked as the reference backend to avoid inventing a new transport before the envelope is proven.
- This proposal does not migrate existing offer catalog or seed directory storage onto Agora. That migration is a later decision, discussed under Consequences.
Decision¶
1. Record Identity and Addressing¶
Every Agora record MUST be addressable by the pair:
topic/key— an opaque string identifying the topic under which the record lives,record/id— a content-addressed identifier of the record itself.
1.1. Topic Key¶
topic/key is an opaque UTF-8 string. The substrate MUST NOT:
- parse
topic/keyfor internal structure, - split it on any delimiter,
- attach type semantics to any prefix or suffix,
- assume URI, URN, hierarchical, or resource-identity semantics.
The substrate MUST enforce only the following canonicalization rules:
- Unicode NFC normalization,
- printable characters only; control characters (C0, C1, DEL) are rejected,
- leading and trailing whitespace is rejected (not trimmed),
- maximum length of 512 bytes after UTF-8 encoding,
- empty
topic/keyis rejected.
Applications choose their own topic-key conventions. Common patterns include:
- namespace-prefixed path-like strings, for example
ai.orbiplex.proposals/035,mycompany/build-log, - random or content-derived identifiers, for example
01JRCY0Y7T4Y9JQK8K7R6K4M3Morsha256:4b7c..., - human-readable channel names scoped by operator, for example
example-coop/announcements.
Applications that want topics derived from resource identity (proposal 026)
MAY construct topic keys that embed the resource identity as a substring
(for example ai.orbiplex.opinions/url/sha256:4b7c...), but this is a convention of
the record kind contract, not a rule of the substrate.
Two applications that pick the same topic/key will share a topic. The
substrate does not enforce global namespacing; applications SHOULD use
operator- or project-scoped prefixes to avoid collisions. Kind contracts
MAY require stricter prefixes.
The recommended namespace scheme — six named prefix classes
(ai.orbiplex.<subsystem>/<name>, participant:<pid>/…,
node:<node-id>/…, federation:<fid>/…, local/…, private/…)
plus the rules that turn those prefixes into cryptographic ownership
at the relay ingress — is defined in proposal 046 (Agora Topic-Key
Namespace Conventions). That proposal is non-normative for the
substrate: topic/key remains opaque here; all enforcement lives in
relay policy and author-side hygiene. Deployments that adopt 046
run the one-compare core discriminator (starts_with("ai.orbiplex."))
and the five-check ingest policy before any 041 attestation tiering.
1.2. Record Identifier¶
record/id is a content-addressed identifier computed as:
record/id = "sha256:" + base64url-no-pad(sha256(canonical-json(record-without-id-signature-and-relay-fields)))
The relay fields excluded from this payload are relay/received-at,
relay/id, and relay/hops.
This matches the Orbiplex canonical hash convention already used by
node/capability/src/lib.rs::sha256_base64url for signed artifacts and
passport hashes, so that a single content-address prefix covers both the
capability layer and the record layer.
Content addressing is necessary so that citations across relays, mirrors, and cached views reliably resolve to the same record regardless of which relay served it.
1.3. Optional Subject Reference¶
A record MAY carry an optional record/about field that links the record
to one or more external subjects using the resource identity model from
proposal 026:
"record/about": [
{ "resource/kind": "url", "resource/id": "https://example.org/article" },
{ "resource/kind": "ean", "resource/id": "5901234123457" }
]
Rules:
record/aboutis OPTIONAL and MAY be empty or absent.- When present, each entry MUST follow the resource identity rules from proposal 026.
record/aboutis not a topic key and MUST NOT be used as the primary index by the substrate. It is a secondary, optional index that enables cross-topic queries of the form show me every record about this subject, regardless of where it was posted.- A kind contract MAY require
record/aboutto be present for records of a givenrecord/kind(for exampleopinionrecords MAY be required to carry exactly onerecord/aboutentry). The substrate itself does not enforce such rules.
2. agora-record.v1 Envelope¶
The MVP envelope is:
schema = "agora-record.v1"
Required fields:
schemarecord/idrecord/kindtopic/keyauthor/participant-idauthored/atcontent/schemacontentsignature
Optional fields:
record/about— list of{ resource/kind, resource/id }entries naming external subjects this record refers to (see section 1.3)record/parent— one parentrecord/idwhen the record is a reply, annotation, or successorrecord/supersedes— one priorrecord/idreplaced by this revisionrecord/policy— one policy record reference. For comment threads this points to athread-policyrecord whosecontent/schemaiscomment-thread-policy.v1.record/tags— short free-form tags (application use)record/lang— BCP 47 language tagauthor/nym-proof— whenauthor/participant-idcarries anym:did:key:...pseudonymous identity (see invariant 9 below), this optional field carries anym-authorship-proof.v1; in the M4 baseline the proof inlines enoughnym-certificate.v1material to verify the current nym without remote lookup, while optional refs may point to longer lineage, audit, or reputation history. The proof is part of the signed canonical bytes so the binding cannot be stripped in transit.relay/received-at— stamped by the relay on ingest; never part of the signed payloadrelay/id— identifier of the relay that first ingested the recordrelay/hops— relay-local hop count stamped by relay implementations; never part of the signed payload
Ingest invariants:
topic/keyMUST satisfy the canonicalization rules from section 1.1.record/idMUST equal the content hash of the canonical payload withrecord/id,signature,relay/received-at,relay/id, andrelay/hopsomitted.signatureMUST be a valid Ed25519 signature by the envelope author subject key named byauthor/participant-id. For ordinary records this is the participant key and is verifiable against the participant's current capability passport chain (proposals 024 and 032). For records whose kind permits pseudonymous authorship, invariant 10 defines the nym-key path.content/schemaMUST be present and MUST name a known content schema identifier; relays MUST accept unknown schemas but MAY mark them as non-indexable until a schema contract is registered.authored/atMUST be within the relay's configured clock skew window (default: ±10 minutes) relative to relay wall clock, or the record is flaggedclock_skewand still stored but excluded from live query results until the window passes.record/parentandrecord/supersedes, when present, MUST resolve to known records under the sametopic/keyor be flaggeddanglinguntil the parent appears.author/participant-idMUST carry a currently valid author subject: normallyparticipant:did:key:..., ornym:did:key:...only when the record kind explicitly permits pseudonymous authorship. Sanctioned or revoked author subjects are still stored but excluded from default query output.record/about, when present, MUST follow the resource identity rules of proposal 026. The substrate MUST NOT derivetopic/keyfromrecord/aboutand MUST NOT requirerecord/aboutto match the topic.record/policy, when present, MUST resolve to a known policy record under the sametopic/keyor be flaggeddanglinguntil the policy appears. The substrate validates only the reference shape. Kind-specific policy evaluators decide whether a missing or stale policy blocks publication.-
Pseudonymous authorship.
author/participant-idMAY carry anym:did:key:...identity in addition to aparticipant:did:key:...identity, when the record kind permits pseudonymous authorship (currently:whisper, see §3). In that case: -
the envelope signature MUST be Ed25519 by the private key of the nym, not of any underlying participant;
- the envelope MUST carry
author/nym-proofcontaining a currently validnym-authorship-proof.v1; in the M4 baseline this proof inlines anym-certificate.v1(proposal 015) that binds the nym to its issuing council's authorization scope and can be verified without a network lookup; - the ingest-attestation gate (proposal 041) MUST accept the nym certificate in place of a participant capability passport for ingest admission, under the consumer-local ingest mode configured for the topic;
- subject sanctions and revocations attach to the nym identity for the nym's bounded validity window; no attempt is made to retroactively correlate nym records with the underlying participant on the substrate layer.
3. Record Kinds and Content Schema Extension¶
record/kind names the what of the record. content/schema names the
shape of the payload inside content.
The envelope is generic: it does not prescribe the list of kinds, only the registration rule. Each record kind is defined by a separate, small schema contract that lives next to the subsystem that owns it. Examples for MVP:
record/kind |
content/schema |
Owner |
|---|---|---|
opinion |
resource-opinion.v1 |
proposal 026 |
comment |
plain-comment.v1 |
Agora base kind |
thread-policy |
comment-thread-policy.v1 |
Agora base kind |
gossip |
public-gossip.v1 |
Agora base kind |
annotation |
plain-annotation.v1 |
Agora base kind |
whisper |
whisper-signal.v1 |
proposal 013 |
whisper-durable |
whisper-threshold-record.v1 |
proposal 013 follow-up |
public-log |
public-log-entry.v1 |
application use |
Rules:
- An unknown
record/kindMUST be stored and served, but MUST be treated as opaque by the relay query layer until a schema contract is registered. - A schema contract MUST be a separate proposal or schema file; it MUST NOT be merged into this proposal.
- Applications MUST NOT assume that all relays understand all kinds.
4. Topic Relay Role¶
An Agora topic relay is an umbrella-operated Node-attached service whose responsibilities are:
- Ingest — accept signed
agora-record.v1records from authorized participants, verify them, content-address them, and write them to durable storage under theirtopic/key. - Federate — replicate records across peer relays according to a configured federation policy; relays do not have to replicate every topic, only the topics they carry.
- Serve — expose query and subscription APIs for Nodes.
- Retain — enforce per-topic retention rules (time, count, size).
- Exclude — honor participant sanctions and record-level flags by excluding records from default query output without deleting them.
Three operational modes are defined:
- Canonical relay — operated by an umbrella operator, publishes a stable base URL, holds the durable source of truth for a set of topics.
- Cache relay — operated by a Node, mirrors a subset of topics for local availability and offline operation. A cache relay does not have to be reachable by other relays.
- Origin relay — operated by the author's Node, used to sign and first- ingest the record before it reaches a canonical relay. An origin relay is ephemeral and MAY be the same process as a cache relay.
The deployment pattern is analogous to the seed directory (proposal 025) and the federated offer catalog (proposal 023): each umbrella operator runs one or more canonical relays, Nodes run cache relays, and federation happens between canonicals.
5. Query and Subscription API¶
Minimum Node-visible API contract. Every path parameter that carries a
topic/key MUST be percent-encoded by the caller; the substrate MUST NOT
interpret the decoded value beyond the canonicalization rules from
section 1.1.
5.1. List records under a topic¶
GET /v1/agora/topics/{topic-key}/records
?since={cursor}
&limit={n}
&kind={record-kind}
&include_flagged={bool}
Response:
{
"records": [ { ...agora-record.v1... }, ... ],
"next_cursor": "opaque-next-cursor",
"cursor_pruned": {
"requested_from": 5,
"first_available": 10
},
"query_attestation": { "...agora-query-attestation.v1...": "..." }
}
Cursors MUST be opaque. Relays MAY reorder within a given time slice but MUST be stable for a given cursor.
5.1a. Query attestations¶
Historical query responses SHOULD include an agora-query-attestation.v1
object under query_attestation.
The attestation binds:
- query mode (
topic-recordsorsubject-records), - topic key or subject resource,
- normalized filter after URL decoding and limit defaults,
- returned
record/idvalues in page order, next_cursor,- cursor-pruning notice, if any,
- deterministic response digest.
The digest is sha256: over canonical JSON using the Agora NFC profile
(jcs-nfc-sha256-base64url). It covers the query scope, normalized filter,
returned record ids, and pagination/pruning metadata. It intentionally does
not cover local HTTP headers or transport details.
An attestation MAY be unsigned in early local relays. In that case it is still
useful as a deterministic page proof for local audit and replay comparison,
but it does not prove relay accountability. Signed deployments SHOULD sign the
canonical JSON of agora-query-attestation.v1 with signature omitted.
Subject-index queries MUST attach the attestation after subscribe-policy filtering. Otherwise the proof would describe the unfiltered subject index instead of the response actually returned to the caller.
5.2. Fetch one record by content id¶
GET /v1/agora/records/{record-id}
Returns the stored record or 404. This endpoint MUST return the same
record regardless of which relay serves it, for a given record/id.
5.3. Subscribe to live updates¶
POST /v1/agora/topics/{topic-key}/subscribe
Opens a streaming connection (long-poll or server-sent events in MVP; WebSocket post-MVP) that delivers new records under the topic. The subscription is advisory and MAY be dropped by the relay under load.
5.4. Submit a record¶
POST /v1/agora/topics/{topic-key}/records
Body: agora-record.v1 signed by the author. Relay verifies invariants and
returns the canonical stored record (with relay/received-at, relay/id,
and relay/hops) or an error.
5.5. Cross-topic query by subject¶
GET /v1/agora/about/{resource-kind}/{resource-id}/records
?since={cursor}
&limit={n}
&kind={record-kind}
&include_flagged={bool}
Returns records whose record/about list contains an entry matching the
given resource identity, regardless of topic/key. This endpoint answers
questions like what records across all topics refer to this URL / this
product / this node?. It is a secondary index and MAY be served more
slowly than topic-keyed queries; relays MAY limit its result window.
This endpoint MUST NOT be used as a substitute for topic-keyed queries.
Authors who want their records to be findable under a subject SHOULD
populate record/about explicitly; the substrate does not synthesize
record/about from topic keys.
5.6. Local deployment channels and authentication¶
When Agora is attached to a Node as a supervised local service, it SHOULD expose the Agora HTTP API on its own port rather than through the daemon's request-response middleware proxy. SSE subscriptions are long-lived streams, and a dedicated port lets the operator decide whether the relay API remains on loopback or is exposed on a public interface.
The Node-attached deployment has three distinct communication relationships:
| Channel | Direction | Purpose | Token boundary |
|---|---|---|---|
| Lifecycle | daemon -> Agora | readiness, initialization, shutdown | daemon-generated middleware authtok, validated by Agora |
| Host capability API | Agora -> daemon | node identity, capability passport lookup/publication, future policy checks | daemon-generated host-capability authtok, validated by daemon |
| Agora API | UI/CLI/client -> Agora | ingest, query, fetch, subscribe | open in MVP; a separate client authtok MAY be enabled post-MVP |
The Agora middleware SHOULD declare its own operator-facing UI surface through
the middleware module report. The host UI may mount that surface under its local
middleware namespace, but the Agora component owns the Agora-specific views and
operator vocabulary. A Node-attached Agora service SHOULD expose a local
operator_visibility status read model covering relay endpoint, namespace
policy, topic ACL, authority-root summary, strict gates, Matrix posture,
HTTP/SSE limits, rate-limit persistence, retention summary, passport state, and
client-auth posture. Full authority-root lists should be shown only on a
client-authenticated local operator surface; unauthenticated status responses
should expose counts and redaction markers instead. This read model is
diagnostic only; it is not a federation protocol and does not authorize publish
or subscribe operations.
The host capability API is a local, authenticated, module-initiated channel. Agora MUST NOT implement node identity, Seed Directory publication, or capability-passport issuance itself. It asks the daemon for those host-owned capabilities, and the daemon remains the broker for identity and passport policy. In concrete Node deployments this is represented by environment variables equivalent to:
ORBIPLEX_HOST_CAPABILITY_BASE_URL
ORBIPLEX_HOST_CAPABILITY_AUTH_HEADER
ORBIPLEX_HOST_CAPABILITY_AUTHTOK_FILE
If client authentication is enabled for the Agora API, it MUST use a token separate from both lifecycle and host-capability tokens. Absence of such a token means open access for development and local-only MVP operation.
5.6.1. Agora CLI TLS trust configuration¶
Local CLI clients MAY connect to Agora over either http:// or https://
base URLs. The CLI TLS trust contract is deliberately small:
- the default HTTPS mode uses the built-in WebPKI root set and rejects self-signed or otherwise untrusted certificates;
--tls-ca-file PATHadds one or more PEM-encoded CA certificates to the trust store;ORBIPLEX_AGORA_TLS_CA_FILEprovides the same value from the environment, and the explicit CLI flag takes precedence;--tls-insecure-skip-verifyis a diagnostic-only bypass for local debugging. It MUST print an operator-visible warning and MUST NOT be used in production.
The custom CA mode is for local labs, private CA deployments, and small operator-controlled federations. It is not a substitute for explicit relay identity policy. Certificate or SPKI pinning remains a future extension for public relay deployments that need tighter endpoint continuity than CA trust alone.
5.7. Capability passport semantics for agora.relay¶
The middleware capability report and the capability passport are separate facts:
- the module report declares capability: "this supervised process can serve
agora.relay"; - the capability passport declares authorization: "the operator officially
offers
agora.relayfrom this node under this issuer, scope, and metadata".
Passport issuance MUST NOT be automatic merely because Agora reports
agora.relay. The operator issues the passport through the same host-owned
identity and capability flow used by other passport-backed capabilities. This
allows the passport to be signed by a high-assurance participant or delegated
operator key rather than by an ephemeral module identity.
Operationally:
- Agora without an
agora.relaypassport remains a valid local relay: UI, CLI, and local Python clients may still ingest and query records. - Agora with an
agora.relaypassport becomes discoverable through the Seed Directory and eligible for federation according to local policy. - At startup, a Node-attached Agora service SHOULD query the daemon for its
current
agora.relaypassport through the host capability API and expose the result in its status surface.
The agora.relay passport metadata schema is defined in
doc/project/60-solutions/008-agora/agora-relay-capability.v1.md. It includes at least the relay API
endpoint, relay role, relay domain, supported transport set, API version, and
advertised canonical topic set.
6. Reference Backend: Matrix¶
The first reference implementation of the topic relay role MUST use a Matrix homeserver (Dendrite or Conduit are preferred for footprint reasons) as the underlying durable substrate. Rationale:
- Matrix rooms map cleanly to Agora topics (one room per
topic/key). - Matrix events are already signed, content-addressed, and ordered in a DAG,
which matches the
agora-record.v1invariants. - Matrix federation provides cross-relay replication without Orbiplex having to invent transport.
- Matrix is already cited as prior inspiration for Orbiplex's messaging strata.
Mapping rules:
- A topic MUST be represented as a Matrix room. The room alias MUST be
derived deterministically from the opaque
topic/keyusing a fixed transformation, so that the same topic resolves to joinable rooms on every relay that carries it. The MVP derivation is:
alias = "#agora." + lowercase-hex(sha256(topic/key)) + ":" + relay-domain
The hash is computed over the UTF-8 bytes of the already-canonicalized
topic/key. Lowercase hex is used here — rather than the canonical
Orbiplex sha256_base64url convention used for record/id — because
the Matrix room-alias localpart is case-insensitive and restricted to
[a-z0-9._=/+-]; base64url would introduce uppercase letters that
Matrix would silently fold, breaking deterministic resolution. The
choice is a Matrix-compatibility concession, not a second content
address: the authoritative record identifier remains record/id.
Relays MUST NOT derive the alias from parsed or typed parts of the
topic key; the key is treated as an opaque byte string by the
derivation function.
2. The relay MUST maintain a separate mapping from topic/key to
human-readable Matrix room names for operator tooling. This mapping is
advisory; it MUST NOT be used as the canonical identifier of the topic.
3. An agora-record.v1 MUST be serialized into a Matrix event of type
org.orbiplex.agora.record.v1 whose content field carries the full
record envelope including topic/key and, when present,
record/about.
4. record/id is computed from the Agora canonical payload, not from the
Matrix event_id. Consumers MUST index by record/id for citation.
5. The Matrix homeserver's own event signature provides relay-level
authority; the Orbiplex signature field provides author-level
authority. Both MUST be verified.
6. Matrix E2E encryption MUST be disabled on Agora rooms in MVP; Agora
records are public by default.
7. The cross-topic record/about index from section 5.5 is maintained by
the relay outside the Matrix room model, as a secondary index over
ingested events. Matrix itself is not asked to answer subject-based
queries.
The envelope is backend-neutral. A later native Orbiplex transport may replace Matrix for topics that must not leave the Orbiplex federation, but such a transport is out of scope for this proposal.
6.1. Federation trust boundary¶
Matrix federation is a transport and replay channel, not an admission oracle.
A receiving Agora relay MUST NOT trust a donor relay merely because the record
arrived through Matrix or because the Matrix event itself is valid. The receiving
relay MUST treat the Matrix event as a carrier for an agora-record.v1 envelope
and run the same local admission pipeline it would run for any other inbound
record.
The receiver therefore verifies, at minimum:
- the
agora-record.v1envelope shape and canonicalrecord/id, - the envelope signature, author key, nym certificate, or delegated/proxy proof, as applicable,
- content-schema conformance for
content/schema, - topic ACL and relay-role policy,
- authority, capability, revocation, and custody policy required by the topic or record kind,
- idempotent deduplication by
record/id.
The Matrix homeserver signature and Matrix event id are transport provenance and diagnostics. They may support relay health, abuse analysis, or federation trust scoring, but they are not sufficient proof that the embedded Agora record is valid or locally admissible.
Operationally:
relay is transport
envelope is evidence
local policy is admission
This allows two Agora servers to synchronize accepted records without requiring full mutual trust between relay operators. A malicious or buggy donor may deliver bytes, but the receiver admits only records whose envelope and local policy pass.
7. Retention¶
Retention is configured per topic and per record kind, with relay-level defaults. Minimum retention policy dimensions:
max_age— oldest allowed record age,max_count— maximum number of records kept per topic,max_bytes— maximum total size per topic,pin_by_kind— kinds that MUST NOT be evicted regardless of age or count.
The relay MUST expose the active retention policy for every topic via
GET /v1/agora/topics/{topic-key}/retention. Authors MAY submit records
that exceed policy; the relay MAY reject, truncate, or accept-and-evict
depending on configuration, but MUST return a clear error in the reject
case.
Retention does not imply deletion from origin relays. Content-addressed records may persist in cache relays or in participant-local archives even after a canonical relay evicts them.
8. Sanctions and Exclusion¶
Agora is a substrate, not a moderation engine. It honors two exclusion signals without defining policy:
- Participant revocation — records authored by a participant whose
current capability passport chain is revoked MUST be excluded from
default query output. The records are retained so that audit queries
(
include_flagged=true) can still reach them. - Record-level flags — a separate artifact (out of scope for this
proposal) may flag a record as
hidden,under-review, orwithdrawn. Relays MUST honor these flags in default query output. Flag artifacts MUST be content-addressed and verifiable themselves.
No substrate-level moderation is defined. Kind-specific moderation rules belong in the schema contract for that kind.
Record Envelope Examples¶
An opinion about a URL. The topic is a named channel dedicated to URL
opinions; the subject relation is carried in record/about:
{
"schema": "agora-record.v1",
"record/id": "sha256:4Q7x3kCDfm2yVwrbn8Hj5tlsOe9zApiU6Gq3wXYAbCd",
"record/kind": "opinion",
"topic/key": "ai.orbiplex.opinions/url",
"record/about": [
{ "resource/kind": "url", "resource/id": "https://example.org/article" }
],
"author/participant-id": "participant:did:key:z6MkExample",
"authored/at": "2026-04-11T08:15:00Z",
"content/schema": "resource-opinion.v1",
"content": {
"schema": "resource-opinion.v1",
"opinion/text": "Useful overview, but the sourcing is thin in the final section.",
"opinion/lang": "en",
"opinion/rating": 1
},
"signature": { "alg": "ed25519", "value": "BASE64URL..." }
}
The same opinion, placed instead under a per-URL topic (an alternative
convention some applications may prefer). The substrate accepts both
shapes; which one to use is a decision of the kind contract for
record/kind = "opinion":
{
"schema": "agora-record.v1",
"record/id": "sha256:4Q7x3kCDfm2yVwrbn8Hj5tlsOe9zApiU6Gq3wXYAbCd",
"record/kind": "opinion",
"topic/key": "ai.orbiplex.opinions/url/sha256:4b7c9fzkMyAL8GBfQExamplePerUrlTopic01",
"record/about": [
{ "resource/kind": "url", "resource/id": "https://example.org/article" }
],
"author/participant-id": "participant:did:key:z6MkExample",
"authored/at": "2026-04-11T08:15:00Z",
"content/schema": "resource-opinion.v1",
"content": {
"schema": "resource-opinion.v1",
"opinion/text": "Useful overview, but the sourcing is thin in the final section.",
"opinion/lang": "en"
},
"signature": { "alg": "ed25519", "value": "BASE64URL..." }
}
A plain comment in a general channel. No external subject, no
record/about:
{
"schema": "agora-record.v1",
"record/id": "sha256:Zp2m9ThKqgX4r7v8C5n1Yb6jD3WlFsTaephQkU2mov8",
"record/kind": "comment",
"topic/key": "ai.orbiplex.announcements/default",
"author/participant-id": "participant:did:key:z6MkExample",
"authored/at": "2026-04-11T09:00:00Z",
"content/schema": "plain-comment.v1",
"content": {
"body": "Agora MVP landed on canary relay.",
"lang": "en"
},
"signature": { "alg": "ed25519", "value": "BASE64URL..." }
}
A comment thread under a proposal. The topic is freely named by the publishing application; the parent link points to the first record in the same topic:
{
"schema": "agora-record.v1",
"record/id": "sha256:TH7q1WknMyAL8GBfQC5XrExampleThreadStart01",
"record/kind": "comment",
"topic/key": "ai.orbiplex.proposals/035/discussion",
"author/participant-id": "participant:did:key:z6MkExample",
"authored/at": "2026-04-11T10:30:00Z",
"content/schema": "plain-comment.v1",
"content": {
"body": "Retention rules should be configurable per record kind, not only per topic.",
"lang": "en"
},
"record/parent": "sha256:PR0p05kFtQYABcD1eFgH2iJKlMnOpExampleParent9",
"signature": { "alg": "ed25519", "value": "BASE64URL..." }
}
A thread policy record can be published in the same topic before the root
comment. The root comment then references it through record/policy. The
policy is not embedded in plain-comment.v1; comments carry speech, while a
thread-policy record carries the access rule for joining the thread.
{
"schema": "agora-record.v1",
"record/id": "sha256:PolicyPhoneConfirmedExample001",
"record/kind": "thread-policy",
"topic/key": "ai.orbiplex.proposals/035/discussion",
"author/participant-id": "participant:did:key:z6MkExample",
"authored/at": "2026-04-11T10:29:00Z",
"content/schema": "comment-thread-policy.v1",
"content": {
"schema": "comment-thread-policy.v1",
"policy/thread-topic-key": "ai.orbiplex.proposals/035/discussion",
"policy/min-attestation": "phone-confirmed",
"policy/inheritance": "descendants",
"policy/may-tighten": true,
"policy/may-loosen": false
},
"signature": { "alg": "ed25519", "value": "BASE64URL..." }
}
The effective policy for a comment is inherited from the nearest applicable ancestor policy:
root or ancestor policy
-> child comment
-> child may attach stricter policy for its own subtree
-> child MUST NOT loosen inherited policy
Domain policy, not the Agora substrate, defines the ordering among attestation
levels such as phone-confirmed, national-id, or community-trusted.
A public log entry produced by a workflow run. The topic is derived from the run identifier, and there is no external subject to reference:
{
"schema": "agora-record.v1",
"record/id": "sha256:WF001rk5d8m2hxQP4N7bT6vLYcFj3gsOwueazR9ikmt",
"record/kind": "public-log",
"topic/key": "ai.orbiplex.workflow-runs/01JRCY0Y7T4Y9JQK8K7R6K4M3M",
"author/participant-id": "participant:did:key:z6MkWorkflow",
"authored/at": "2026-04-11T11:00:00Z",
"content/schema": "public-log-entry.v1",
"content": {
"step": "fan-out",
"outcome": "any_one_satisfied",
"dispatch_count": 3
},
"signature": { "alg": "ed25519", "value": "BASE64URL..." }
}
Hard MVP Scope¶
| Feature | MVP |
|---|---|
agora-record.v1 envelope |
Yes |
Content-addressed record/id |
Yes |
Opaque topic/key with canonicalization rules |
Yes |
Optional record/about linking to proposal 026 resource identity |
Yes |
Cross-topic query by record/about (secondary index) |
Yes |
| Matrix backend (Dendrite or Conduit) | Yes |
| Three relay roles (canonical, cache, origin) | Yes |
| Query by topic with cursor | Yes |
Fetch by record/id |
Yes |
| Subscribe via long-poll or SSE | Yes |
| Retention: age + count | Yes |
| Retention: size + pin-by-kind | Deferred |
| Record-level flags and hidden/withdrawn handling | Deferred |
agora-vault-entry.v1 encrypted opaque artifact vault |
Yes |
| E2E-encrypted private topics | Deferred |
| Kind-specific schema registry | Deferred |
| Native Orbiplex transport (non-Matrix) | Deferred |
| WebSocket subscription | Deferred |
Deferred items are post-MVP and MUST NOT be implemented before the MVP scope is clean and tested.
Agora Vault¶
Agora Vault is a separate encrypted-artifact surface, not a topic record
profile. It exists for cases where the system needs durable,
admission-controlled opaque storage but must not expose the author,
participant, nym, topic, or domain metadata that ordinary agora-record.v1
would reveal.
The canonical entry schema is agora-vault-entry.v1. Its public shape exposes
only:
vault-entry/id;- opaque
artifact/id; artifact/kind;- the cryptographic envelope;
- ciphertext.
The artifact payload and all domain metadata live inside ciphertext. A vault
entry that contains plaintext payload fields, lacks ciphertext, or lacks at
least one key envelope is invalid. Public lookup is GET
/v1/agora/vault-artifacts/{artifact_id} and returns ciphertext-bearing entries
only. Scoped list/get/delete use GET|DELETE
/v1/agora/vaults/{vault_subject}/artifacts.... In the supervised local Node
runtime those routes are gated by the Agora service client token and daemon host
capability dispatch. Federated or remote-provider deployments bind the same
operations to the frozen agora-vault@v1 capability/passport profile scoped to
the opaque vault_subject.
The MVP writer used by recorded messaging may emit key-wrap =
"dev-key-commitment". This is a development custody commitment to the
discarded symmetric key; it proves that ciphertext was produced but does not
make the entry decryptable by itself. Production recovery uses recipient key
wrapping under x25519-hkdf-sha256+xchacha20-poly1305.
The implemented MVP runtime exposes these concrete operations:
| Operation | Runtime path | Authorization boundary | Notes |
|---|---|---|---|
| Put encrypted artifact | POST /v1/agora/vaults/{vault_subject}/artifacts |
Agora client token locally; agora-vault@v1 remotely |
Request body is an agora-vault-entry.v1; invalid plaintext or missing key envelopes are refused. |
| List scoped artifacts | GET /v1/agora/vaults/{vault_subject}/artifacts |
Same scoped vault authority | Response includes list items with artifact/id, artifact/kind, vault-entry/id, stored/at, and optional ciphertext digest. |
| Get scoped artifact | GET /v1/agora/vaults/{vault_subject}/artifacts/{artifact_id} |
Same scoped vault authority | Returns the full encrypted entry after export schema validation. |
| Delete scoped artifact | DELETE /v1/agora/vaults/{vault_subject}/artifacts/{artifact_id} |
Same scoped vault authority | Performs a soft delete; scoped and public reads ignore deleted entries. |
| Public encrypted lookup | GET /v1/agora/vault-artifacts/{artifact_id} |
Public lookup by opaque artifact id | Returns filtered ciphertext-bearing entries only and does not reveal vault_subject, participant, nym, author, topic, or plaintext domain metadata. |
The supervised agora-service stores MVP vault entries in
agora-vault.v1.sqlite. The scoped uniqueness boundary is
(vault_subject, artifact_id), so the same opaque artifact/id may exist in
multiple vault subjects. Public lookup by artifact id may return more than one
ciphertext entry, but each entry remains subject-opaque.
The supervised agora-service also registers daemon host capability handlers:
agora.vault.put;agora.vault.list;agora.vault.get;agora.vault.delete.
Recovery is deliberately separated from the public Agora record surface.
agora-vault-ref.v1 is a plaintext record shape intended to be sealed inside
pseudonym-vault.v1; it maps recovered participant or nym material to the
provider, opaque vault/subject, capability references, and allowed artifact
kinds. A bare public participant id is not enough to list, get, or delete vault
contents.
Recorded messaging is the first profile that uses Agora Vault: it stores a
signed message-envelope.v1 as a generic encrypted artifact rather than as an
Agora topic record. Vault writes are best-effort: temporary failure does not
roll back message delivery, and the messaging outbox worker retries due
failed-retryable vault jobs.
Interaction With Existing Proposals¶
| Proposal | Relation |
|---|---|
| 013 — Whisper social signal exchange | whisper-signal.v1 is a first-class Agora content/schema under record/kind = "whisper", using pseudonymous nym:did:key:... authorship (§2 invariant 9) anchored by author/nym-proof with inline-first nym-certificate.v1 material; Agora is one of two distribution mechanisms (the other is direct node-to-node exchange, see proposal 013 §Distribution), and Memarium is the local storage surface for both. Threshold-reached events remain a separate whisper-durable kind for when a whisper crosses bootstrap thresholds and needs a different durability posture. |
| 023 — federated offer distribution | The offer catalog today maintains its own storage; a future migration may express offer snapshots as Agora records under an operator-chosen topic key (for example orbiplex/offer-catalog) with record/kind = "offer-snapshot", retiring the bespoke replication layer |
| 024 — capability passports | Author signatures on Agora records are verified via the same passport chain |
| 025 — seed directory as capability catalog | Seed directory remains a separate primitive for capability discovery; it MAY later publish its listings as records on an Agora topic for uniform federation |
| 026 — resource opinions and discussion surfaces | Proposal 026 remains the single source of truth for resource identity (resource/kind + resource/id). Agora does not embed resource identity in its primary key; it reaches proposal 026 only through the optional record/about field. resource-opinion.v1 becomes the first content/schema on Agora, with record/kind = "opinion", and opinion records carry the referenced resource in record/about |
| 032 — key delegation passports | Delegated signing keys used for authoring Agora records follow the passport verification rules |
| 034 — node operator binding | Orthogonal. Agora does not participate in operator assurance gating |
Relationship to Memarium¶
Memarium, as defined in the vision document, is the memory-and-knowledge layer: curated archives, community resources, cultural artifacts, with federation, replication, versioning, durability rules, and semantic retrieval. Memarium answers the question what must not disappear.
Agora is a narrower primitive. It answers the question where do topic- addressed public records live and how are they federated. It is a substrate, not a curator, and it makes no decisions about what is worth preserving.
The relationship:
- A community-scale Memarium space MAY use Agora as one of its underlying substrates for the ingestion of public records into the community layer.
- Memarium's private and personal spaces are out of scope for Agora. Personal notes, idiolectal archives, and private working spaces belong in a Node-local store.
- Memarium curation (indexing, semantic search, summarization) happens on top of Agora records or Node-local stores, not inside the substrate.
- A Node may run a cache relay for Agora without running any Memarium component, and vice versa.
When a separate Memarium proposal lands, it will describe how curation attaches to Agora records and Node-local stores, and will not redefine Agora.
Implementation Notes¶
The suggested first implementation path is:
- Relay shell — a Rust service that wraps a Dendrite or Conduit homeserver, exposes the Agora HTTP API, and translates calls into Matrix room operations.
- Record verification library — a standalone Rust crate that encodes
the canonical JSON rules, computes
record/id, verifies signatures, and enforces ingest invariants. Reusable across relay, Node, and any future native transport. - Node client — a thin client in
node/agora-clientthat lets the daemon submit, query, subscribe, and cache. - Middleware seam — Agora relay may run as a middleware-attached service on an umbrella operator's Node, following the same supervision pattern as proposal 019 (supervised local HTTP-JSON middleware).
The first concrete migration target is proposal 026: serve resource
opinions through Agora as record/kind = "opinion" with
content/schema = "resource-opinion.v1".
Implemented crate architecture¶
The implementation follows a stratified layering, where each crate has a single responsibility and communicates through thin trait or data contracts:
L4.b agora-http REST/SSE surface (framework-neutral)
L4.a agora-relay-matrix composition: SQLite store + Matrix transport
L3 agora-matrix-client MatrixRelayTransport<S: MatrixEventSink>
agora-matrix pure data bridge (alias, content translation)
L2 HttpMatrixEventSink thin Matrix CS API sink (reqwest + ruma types)
L1 MatrixCsClient typed join/send/sync over Matrix CS API
L0 AuthenticatedHttpClient bearer-auth HTTP + rate-limit retry
agora-relay-sqlite persistent local relay (SQLite WAL)
agora-relay-mem in-memory reference relay
agora-relay-trait AgoraRelayTransport / AgoraRelay / SequencedRecord
agora-core envelope verification, canonical JSON, signatures
Matrix transport: thin HTTP sink¶
The Matrix transport uses reqwest::blocking and ruma 0.14.1 (types
and endpoint definitions only, feature client-api-c) plus a small
hand-written Client-Server HTTP wrapper. This keeps the transport
auditable, avoids a full client state machine, and keeps Agora relay
semantics in the relay crates rather than in a Matrix runtime. The
MatrixEventSink trait decouples MatrixRelayTransport from the
concrete HTTP implementation while preserving a single production path
for the MVP.
Three Matrix CS API endpoints are used:
POST /_matrix/client/v3/join/{roomIdOrAlias}— idempotent room joinPUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}— send event with Agorarecord/idas deterministictxnIdfor server-side idempotencyGET /_matrix/client/v3/sync— one global long-poll loop per sink, events demultiplexed to per-(room, event_type) subscribers
Federated relay composition (L4.a)¶
MatrixBackedRelay<S: MatrixEventSink> composes SqliteRelay (local
persistent store) with MatrixRelayTransport<S> (federation channel)
and implements the full AgoraRelay trait (ingest + subscribe + query +
fetch). Key properties:
- Local-first ingest: records are persisted in SQLite before being forwarded to Matrix. Matrix failure does not block local persistence.
- Forward-only-fresh: duplicate records (by
record/id) are not forwarded to Matrix, preventing redundant federation traffic. - Relay metadata stamping:
relay/received-at(RFC 3339 UTC),relay/id, andrelay/hopsare stamped by the relay on every ingest, as specified in section 2. These fields are excluded from content address and signature (viaVerifiedRecordrelay metadata helpers), so stamping does not break the envelope invariant. - Inbound bridge: a background thread per topic subscribes to the Matrix transport, verifies inbound events, and ingests them into the local store. The bridge starts lazily (Cache mode) or eagerly (Canonical mode) and includes a cooldown to prevent tight respawn loops on transport failure.
- Retention sweep:
sweep_retention()enforcesmax_ageandmax_countper topic, then rebuilds the subject index to prune evicted entries. - Three relay roles are configuration, not separate implementations:
Canonical(eager inbound bridges for configured topics),Cache(lazy bridges on first subscribe/query),Origin(outbound only).
Subject index (L4.a internal)¶
The cross-topic record/about index from section 5.5 is implemented as
a SubjectIndex trait internal to agora-relay-matrix. It is populated
as a side-effect of ingest and queried by the HTTP API for subject-based
lookups. The MVP implementation is in-memory (InMemorySubjectIndex)
and rebuildable from the local SQLite store via
rebuild_subject_index(). The index is idempotent: re-indexing the same
record is a no-op. The trait supports clear() for rebuild cycles
triggered by retention sweeps or process restarts.
HTTP API surface (L4.b)¶
agora-http is a framework-neutral adapter that maps proposal 035 §5
endpoints to AgoraRelay + SubjectIndex calls. It depends on no HTTP
framework (axum, hyper, actix-web); a daemon or middleware layer
wraps it in actual HTTP routing. Key design choices:
- Opaque cursors: pagination uses base64url-encoded sequence numbers.
Clients must treat cursors as opaque strings. The
Cursortype encodes/decodes on the HTTP boundary; relay internals use bareu64sequences viaSequencedRecord. - SSE for subscriptions:
SseRecordStreamformats verified records as standard Server-Sent Events (event: agora.record,id: {record/id},data: {json}). - Topic mismatch validation:
POST /topics/{topic-key}/recordsrejects records whosetopic/keydoes not match the URL path. - Partial about-filter rejection: when exactly one of
about_kindorabout_idis present, the API returns an explicit error instead of silently ignoring the filter. include_flaggedwire compatibility: the parameter is accepted but has no effect in MVP; flag semantics are deferred per section 8.- Query attestations: historical query pages include
agora-query-attestation.v1. The reference adapter emits unsigned attestations that bind query scope, normalized filter, record ids, cursor metadata, and a deterministic digest. Relay signatures over attestations are a later signer/authority integration, not anagora-coreconcern.
Local runtime data-dir ownership¶
An Agora service instance has exclusive ownership of its local runtime data directory. This is the Agora middleware data directory, not the Node profile root. Under daemon supervision the default path is:
<node-data-dir>/middleware/agora-service/data
The Agora middleware data directory is a single-writer local state root, not a multi-process database contract. At most one live Agora service instance MUST use a given Agora middleware data directory at a time.
This ownership invariant covers the SQLite relay store, the subject index rebuild cycle, retention sweeps, rate-limiter snapshots, and any other process-local caches or timers derived from the same root. Adding file locks around individual snapshot reads and writes would protect only those I/O operations; it would not make two Agora runtimes share one coherent timeline, one cache state, or one retention policy execution.
Implementations SHOULD enforce the invariant with an exclusive startup-time lock file inside the Agora middleware data directory and fail fast with an operator-readable error when the lock is already held. Snapshot writes SHOULD remain crash-safe through the usual temp-file, fsync, and atomic rename discipline.
Per-file shared/exclusive locks MAY be added later as a compatibility mechanism for external diagnostic, backup, or migration tools that need to read snapshot files while Agora is running. Such locks MUST NOT be interpreted as permission to run multiple Agora service instances against the same Agora middleware data directory.
MVP implementation status vs this proposal¶
Implemented¶
- Envelope verification (
agora-core): content address, Ed25519 signature, schema version, topic key syntax (empty, length, whitespace, control chars). Cross-language fidelity verified against a Python verifier (agora-verifiermiddleware module). - Local relays (
agora-relay-mem,agora-relay-sqlite): ingest with idempotent duplicate detection, per-topic append log, monotonic sequence assignment,QueryFilter-based query withSequencedRecordresults, replay-capable subscriptions, fetch byrecord/id. - Relay metadata stamping:
relay/received-at,relay/id, andrelay/hopsstamped by the federated relay (agora-relay-matrix) on ingest, viaVerifiedRecordrelay metadata helpers which preserve the envelope invariant (relay fields are excluded from content address and signature). - Matrix transport (
agora-matrix-client): thin HTTP sink usingreqwest::blocking+ruma 0.14.1types. Three CS API endpoints (join, send with deterministictxnId, global sync with demux). - Matrix data bridge (
agora-matrix): deterministic room alias derivation (#agora.<hex-sha256-nfc>:<domain>), event typeorg.orbiplex.agora.record.v1, bidirectional record ↔ event content translation with full envelope verification on inbound. - Federated relay (
agora-relay-matrix):MatrixBackedRelay<S>composing SQLite + Matrix transport. Local-first ingest, forward-only-fresh, lazy/eager inbound bridges with cooldown, three relay roles (Canonical/Cache/Origin), retention sweep (max_age,max_count). - Subject index (
agora-relay-matrix): in-memorySubjectIndexfor cross-topicrecord/aboutqueries, idempotent, rebuildable from local store. - HTTP API surface (
agora-http): framework-neutral adapter for proposal 035 §5 endpoints. Opaque cursors, SSE subscriptions, topic mismatch validation, partial about-filter rejection. - Service and daemon integration (
agora-service+ supervised middleware): the relay runs as a daemon-managed local HTTP middleware service. The daemon supervises the process, provisions module/client auth tokens, and exposes capability-provider discovery while the service owns its REST/SSE surface. - Retention (age + count + scheduling):
sweep_retention()with automatic subject index rebuild after purge, driven by theagora-serviceretention sweep thread when topic policies are configured.
Ingest invariants still deferred or limited¶
The following invariants from section 2 use "flag, not reject" semantics in this proposal and are not blocking for the first deployment:
- Invariant 5 (
authored/atclock skew window) — not checked; the field is validated as non-empty only. - Invariant 6 (
record/parent/record/supersedesdangling detection) — implemented at the publisher edge inagora-service. The default is warn-and-accept to preserve eventual consistency;strict_parent_refs = trueturns unresolved references into HTTP 422. Federation ingress remains warn-only. - Invariant 7 (passport chain verification via proposals 024 and
032) — the Rust
AgoraSignaturestruct now carries a stratified shape withkey/public(the actual signing key bytes) and an optionalkey/delegationblock (delegation proof chain per proposal 032). Direct-key signatures are fully verified end-to-end (key match against the author's participant id is a hard block viaSignAdapterError::PublicKeyMismatch). Delegated signatures are accepted structurally and verified through theCapabilityDelegationVerifiertrait wired in L4.b of the 035-impl doc; the default deployment installs a permissive verifier, and hardened deployments plug in theagora-capability-bridgeproof checker. The legacysignature.key/refshape is retired and no longer appears in the schema or the code. - Invariant 8 (
record/aboutresource identity structural validation per proposal 026) — the array is accepted structurally; individual entries are not validated againstresource-ref.v1. - Schema-level patterns (
record/kind,content/schema,record/idlength bounds) — not enforced by the verification library; the library checks non-empty semantics only. Schema-level validation is a relay-side concern. - Topic key NFC normalization — the verification library checks syntax only (empty, length, whitespace, control chars). NFC normalization is applied by the canonical JSON layer before hashing and signing, so content addresses are stable regardless of input NFC form, but the relay does not currently reject or normalize non-NFC topic keys at the ingest boundary.
Still deferred / intentionally limited¶
- Subject index persistence: for MVP, the
record/aboutSubjectIndexis intentionally a derived, rebuildable view over the local relay store. It is not a source of truth and does not need its own persisted projector to satisfy the v1 contract.rebuild_subject_index()reconstructs it from the local SQLite store on startup; a SQLite-backed subject index should be added later only when listing volume, restart latency, or retention churn makes rebuild cost material. - Room-to-topic name mapping (section 6, rule 2): the relay does not yet maintain a human-readable room-name mapping for operator tooling.
Consequences¶
Positive:
- one portable record envelope replaces several bespoke storage contracts across future subsystems,
- proposal 026 gains a concrete storage and federation substrate without having to invent one,
- the offer catalog and seed directory gain a future migration target if umbrella operators want to consolidate replication on one substrate,
- Matrix reuse avoids inventing a federation transport,
- topic addressing is unified with resource addressing across Orbiplex.
Tradeoffs:
- introducing Matrix as a backend adds an operational dependency for umbrella operators running canonical relays; Nodes running cache relays can avoid it via HTTP-only cache mode,
- Agora records are public by default in MVP, which forces applications that need private channels to wait for a later proposal,
- content addressing forces strict canonical-JSON discipline on authors; any post-signature mutation invalidates the record,
- the offer catalog currently maintains its own storage; migration is a future decision, not a hard requirement of this proposal.
Alternatives Considered¶
- NATS JetStream as backend. Rejected because NATS is optimized for low-latency messaging, not citeable archive state, and because its cluster model assumes trusted operators rather than cross-umbrella federation. A later proposal may add JetStream as an optional backend for latency-sensitive record kinds that do not require long-term archival.
- Invent a native transport from the start. Rejected because the envelope value is independent of the transport, and shipping the envelope on a proven transport first lets the schema mature before the transport is locked in.
- Extend proposal 025 (seed directory) into a generic substrate. Rejected because the seed directory has a specific payload contract (capability passports and offers) that would blur if generalized.
- Extend proposal 023 (offer distribution) into a generic substrate. Rejected for the same reason.
Known Limitations¶
- MVP has no private topics. Any record submitted to Agora is considered public and visible to every participant that can reach the relay.
- MVP has no substrate-level moderation. Record-kind contracts must carry their own moderation rules.
- MVP has no formal schema registry for
record/kind; registration happens through proposals and schema files. - Matrix homeservers have their own operational footprint; small Nodes running cache relays only SHOULD NOT be required to run a full Matrix homeserver.
Open Questions¶
- Should Agora topics have a lightweight per-topic policy document (for
example
agora-topic-policy.v1) that constrains authorized record kinds, retention, and writer set, or should those constraints remain relay-wide? - Should
record/supersedesbe enforced as one-author-one-current-record per topic for specific kinds (for exampleopinion), or is that a kind-specific rule? Proposed: kind-specific, not substrate-level. - Should the offer catalog and seed directory migrate onto Agora? Proposed: not in this proposal. Evaluate after the MVP is in production for at least one umbrella operator.
- Should Agora records be signable by delegated keys issued under a capability passport, or only by the participant root key? Proposed: delegated keys are allowed via proposal 032 passport chains.
- Should the substrate offer any supported topic-key conventions (for example a recommended hierarchy grammar) even though it does not parse them, or should convention be left entirely to kind contracts? Proposed: publish a non-normative conventions note alongside the schema file, not a substrate rule.
- Should the
record/aboutcross-topic index be rate-limited or quota- bounded by relays to prevent abuse of subject-driven fan-out? Proposed: relays MAY impose result windows and rate limits, with defaults chosen during MVP deployment. - Should the relay expose a canonical federation tick rate and a federation lag metric, and if so, where should it be surfaced? Proposed: reuse the observability pattern from proposal 023.
Follow-Up¶
If adopted, the next artifacts should be:
- one schema file for
agora-record.v1, - one schema file for
plain-comment.v1as the first general-purpose base kind, - one schema file registering
resource-opinion.v1as an Agorarecord/kind = "opinion", - one implementation note describing the Matrix mapping and the Dendrite or Conduit deployment footprint,
- one separate proposal for private (encrypted) Agora topics,
- one separate proposal for Memarium, describing how curation attaches to Agora records and Node-local stores,
- proposal 040 (custodial redelivery and tombstones) defines the readback
tombstone vocabulary (
410 Gonewithretention_expired,removed_by_policy,storage_lost,superseded), operator sovereignty over re-acceptance, custodian negotiation viacapability-passport.v1, and the author-scoped sondage endpoints (count, listing, digest) gated byagora-author-proof.v1. Agora implementations SHOULD implement the tombstone surface as soon as the relay serves more than one node; pure local-only MVP relays MAY keep404as the sole absence signal, - proposal 041 (Agora ingest attestation and tiered access) defines the
author/attestation_refoptional signed envelope field, theagora-attestation-proof.v1short-lived proof token, the ingest mode matrix (open,allowlist,passport,passport_scoped,layered), a predicate grammar for tiered gates, and a stable refusal reason-code vocabulary symmetric with proposal 040's tombstone matrix. The verification primitive is placed as a reusableattestation-gatealongside signer and sealer in the daemon's trust-boundary zone and is consumable by every component that needs to ask "is this author attested under a passport I currently accept", not only Agora.