9.7 KiB
9.7 KiB
AGENTS.md
Repo shape
rfc.txt(FRX — Federated Retrieval Exchange, Draft 0.5) is the normative spec;src/is the Phase 1frxdimplementation (single crate, two binaries).frxdis the member node (init/add/index/serve/relay/query/status);frxis the thin client (search/query/status). Relay and node roles are separate subcommands.- Commands:
cargo build,cargo test(85 tests: unit insrc/; e2etests/phase1.rs; conformancetests/conformance.rs; aggregates + member directorytests/aggregates.rs; registrytests/registry.rs; federation/isolation/admissiontests/federation.rs; concurrency/restarttests/concurrency.rs; real subprocess CLItests/cli.rs; 1000-doctests/scale.rs; purge-log absencetests/purges.rs; shared fixturestests/common/mod.rs). No CI/lint config. - E2E pattern: relay + nodes in-process on ephemeral ports with tempdir corpora; use
tests/common/mod.rshelpers (spawn_relay*,query_envelope,poll_messages,register) for new coverage. Raw relay polls return envelopes (payload underbody), not response bodies.
Editing the spec
- Read all of
rfc.txtbefore editing; it is the sole source of truth and is deliberately terse. - Keep the plain-text single-file format. Don't restructure into Markdown files unless asked.
- Invariants I1–I9 (§2) are normative; proposals contradicting them (scores in responses, topic taxonomy, announce stream, dispute messages, replayable broadcast, in-protocol pricing/settlement) are out of scope by design.
- Appendix B (Purge Log) is normative: a rejected mechanism may only be re-proposed if the written rationale is addressed.
- §10 Open Issues are known gaps, not oversights (e.g., signature canonicalization blocks Phase-1 interop). Check it before "fixing" something.
- Use the spec's vocabulary — member/querier/responder, aggregates, source/enrichment members — not client/server or search-engine terms.
- A new
MUSTis only legitimate if it is observable at the boundary, deterministically verifiable by a peer, beneficial to the counterparty, and not derivable from local policy. Ranking/ordering/presentation fails this test and stays local (§5); scores never travel (I6). - I2 is not blanket anti-centralization: shared coordination (identity, admission, contract) is centralized in the MA because common state is cheaper held once; decisions that consume local information (matching, relevance, sharing, retention) stay local. Off-wire conduct (link handling, retention, gating) is contract, not conformance.
Implementation notes
- Wire format matches Draft 0.5: envelope
{type, from, key, ts, nonce, body, sig}withfrom= identifier,key= pubkey; signatures cover the JCS canonical form of the unsigned envelope under prefixFRX/0.5(src/crypto.rs). Ourcanonical_jsonis JCS-compatible only for the restricted schema (ASCII keys, integers, no floats) — golden vectors intests/conformance.rspin the bytes and signature; revisit before claiming interop with non-Rust stacks. - Relay addresses transport mailboxes by
key(unicastto= recipient pubkey; queues keyed by pubkey); the identifier is protocol identity only. Registry binding checksmap[key].id == from. - Built: envelope/query/response, Tantivy index, aggregates, member directory. Not built: dashboard UI, directory watching, TLS, lineage/delegation. Responses travel relay-mediated unicast; transport is HTTP long-poll, not SSE.
- Economics is out of protocol scope (I4: aggregates advise, contracts govern): no receipt, citation, pricing, or settlement fields or message types exist or may be added.
- Relay verifies signatures and ±300s timestamp skew, carries only
querybroadcasts, holds no history (queues drained on poll), and requires challenge–response proof of key possession for mailbox polls. Per-member queues are isolated: a lagging member gets 429 + Retry-After with amissedcount; publishers and other members are never stalled. Relays MAY flood to configured peers (/v1/federation,--peer+--url, hop-bounded, seen-set dedup without suppressing identical direct publishes) and MAY gate senders against a registry (--registry+--ma-key). - Responder searches only collections marked shared (I9), stays silent when nothing matches, and emits results with honest
truncated/more_availableand no scores (I6). BM25 order is a local implementation detail, not protocol surface. - Index layout: Tantivy at
<data_dir>/index, collections manifest at<data_dir>/collections.toml;exposure(metadata|full) gates whethercontentis returned. - Egress checks live in the responder path (
src/node.rsrespond), not the relay — keep private collections unreachable there. - Appendix B is executable policy:
tests/purges.rshas one absence test per rejected mechanism (12 rows). Add a negative test there before ever re-proposing one, and only if the rationale is addressed. add/reindexreset a collection (delete by manifestname) before re-adding, so deleted files don't linger; collection identity is its name, and same-named collections replace each other.- Relay backpressure is global: any member's full queue 429s every publisher until drained (visible per §3, but one lagging member can stall the firehose — revisit before scale).
- Member authority (Draft 0.5 §6): the MA-signed registry snapshot is authoritative when configured (
[node] registry= file path or URL,ma_keypinned; monotonic version — rollback and forgery close the node; file path is mtime-reloaded, URL is fetched at start + every 60s and cached to<data_dir>/registry-cache.json, so outage fails static). Keys carry optional validity windows (not_before/not_after); rotation =registry add-keythenrevoke-key. <data_dir>/members.toml(name, pubkey, class,previouskeys, mtime-reloaded) is a dev/local fallback used only when no registry is configured; empty directory without a registry is open bootstrap only whendev_bootstrap = true(RFC §6: explicit dev flag). Receivers drop content-bearing responses from enrichment-class senders (metadata-only, §6).- MA tooling:
frxd registry init|add|add-key|revoke-key|remove|list|set-relays|show|serve(signedregistry.json+ma-key.hexin--dir);frxd init --id/--registry/--ma-key;frxd key show|rotate;member add --previous <old>for the fallback path. A node with no[node] relaysdiscovers them from the registry snapshot (doc.relays). - Aggregate semantics are our implementation choices from a terse spec: requests are
aggregateenvelopes carrying onlyperiod; replies carrysent(broadcasts that month) /passed(responses consumed from that member); granularity floor is enforced as YYYY or YYYY-MM only (finer rejected), yearly rolls up months. Revisit with §10 sufficiency review.
Known gaps (Phase 2/3, intentional — don't fake them)
- No dashboard UI, no directory watching (new files need
reindex), no TLS, no user-supplied URL ingestion, no node-side (bilateral) rate limiting, no SSE streaming (long-poll only), no unicast confidentiality (HPKE). - Receipts/settlement are out of protocol scope, not unimplemented (I4; Appendix B row 2). Lineage and delegation remain §10 open issues — unspecified without a supply stream, so not buildable as written; don't invent them silently.
- Node query dedup is by
qidonly; replay inside the ±300s skew window remains possible (no nonce cache at nodes), relays have no directory/admission, and there is no end-to-end encryption — relays see everything in clear.
Technical plans (deliberately not in the RFC)
- Record plans here — not as spec edits — when they are implementation/demo choices rather than protocol surface.
- Demo plan: build a useful end-to-end demo on GDELT and Common Crawl (CC-NEWS; sometimes called "OpenCrawl" in discussion) as enrichment members / backfill seeding. RFC §6 and Appendix A already name both as example derived corpora, so no new mechanisms are required; enrichment members are metadata-only exposure.
- Phase 1 (two-node query/response) is built and tested; the enrichment demo layers on top of it.
- Language: Rust (settled, matches §7). Decided by the engine requirement, not preference: Tantivy gives in-process Lucene-class BM25 + incremental indexing; C/C++ embedded alternatives are worse (Xapian GPL-2+, CLucene unmaintained, SQLite FTS5 thin), plus single static musl binaries for the install story and memory safety on the untrusted network/crypto path. Don't re-litigate.
- frxd modes (one binary, config toggles, no code required of publishers): querier (broadcast/local-first search), responder (match incoming queries against shared collections, sign), local index (watch dirs, extract text, explicit shared marking per I9). Use RFC terms querier/responder, not "subscriber/publisher".
- Roles are not exclusive: a single node may issue queries and answer them concurrently (I5, §3 "any member"). Implement querier/responder as independent enable flags — never an exclusive mode enum or fixed deployment role.
- Matching accuracy is a project-health concern: start lexical (Tantivy), plan a hybrid cheap lexical gate + optional local embedding rerank (two-stage ingestion, Appendix A); embedding model stays local and replaceable (I2/I5).
- Identity/registry (RFC Draft 0.5 §4/§6): MA-hosted FQDN identifiers first (
<label>.frx.<ma-domain>, no DNS needed by users), signed versioned registry snapshot with the MA key pinned; envelopefrom= identifier,key= pubkey; registry outage fails static. Member-hosted identities, MA anchor rollover, and unicast confidentiality are §10 open. Implementation phases: A (signed registry snapshot) and B (identifier +key+ JCS on the wire) are built and tested. Prioritize frictionless onboarding (users may be department-level and cannot create DNS).