# Data contracts — schema `inbound-library/v1` Consumers may rely on every field listed here. Any change that breaks one of them bumps the schema version. ## Layout | Path | Mutability | Versioned in git | | --- | --- | --- | | `registry/topics.json` | edited by commit | yes | | `registry/sources/.json` | edited by commit | yes | | `registry/subscriptions/.json` | edited by commit | yes | | `config/library.json` | edited by commit, no secrets | yes | | `data/items/` | immutable original bytes | no | | `data/deliveries/.json` | immutable | no | | `data/notices/.json` | immutable | no | | `data/derived/.json` | rebuildable, replaceable | no | | `data/digests///.md` | rebuildable, replaceable | no | | `data/state/*.json` | cursor and hub-sent state | no | `data/` is gitignored because mail can be private. A record's id is the SHA-256 of its canonical JSON (sorted keys, no whitespace). An item's id is the SHA-256 of its bytes. `check` rehashes both. ## Item The original bytes, exactly as delivered. For mail, the item is the full RFC 822 message. Each attachment is also an item of its own. ## Delivery (provenance) One record per delivery of an item. | Field | Meaning | | --- | --- | | `schema` | `inbound-library/v1` | | `item` | item id | | `channel` | `imap` or `attachment` | | `status` | `routed` (a source matched) or `quarantined` (no source) | | `source` | source id, or `null` | | `received_at` | IMAP INTERNALDATE (UTC ISO 8601) | | `mailbox` | `/`; for `imap` only | | `message_id`, `sender`, `subject`, `list_id` | for `imap` only; `sender` is lowercased | | `forwarded_from` | for `imap`: the original sender from the first forwarded header block in the body (`From:`/`Von:` after a forward marker), or `''` | | `authentication_results` | raw headers, recorded as provenance, not as trust | | `parent_item`, `filename`, `content_type` | for `attachment` only | IMAP UIDs are kept out of the delivery record on purpose. After a UIDVALIDITY change, the rescan reproduces identical records. When a source is registered later, a quarantined mail gets an additional `routed` delivery. The quarantined record stays as history. ## Derived `subject`, `sender`, `date`, `text` (text/plain, otherwise stripped HTML) and `attachments` (`item`, `filename`, `content_type`). It can be rebuilt from the item at any time. ## Topic In `topics.json`: `{"version": n, "topics": {"": ""}}`. Names use dots, from domain to specific, for example `intelligence.daily-brief`. Add new topics here first; sources and subscriptions may only use registered topics. ## Source | Field | Meaning | | --- | --- | | `id` | the file name without `.json` | | `owner_repo` | the repository that registered it | | `channel` | `email` or `url` | | `match` | for `email`: `from` (required), `subject` (substring), `list_id`, `forwarded_from` (the original sender of a forwarded mail) | | `url` | for `url` | | `topics` | list of registered topics | | `cadence` | `daily`, `weekly` or `irregular`; daily and weekly email sources are watched for silence (36 h or 9 days) | | `visibility` | `shared` (any matching repo) or `restricted` (owner only) | A `url` source is a pointer. The library does not fetch it, so it produces no items or notices. Repos find these sources with `sources --repo `. Extra descriptive fields such as `publisher` and `notes` are allowed. ## Subscription | Field | Meaning | | --- | --- | | `repo` | the file name without `.json` | | `agent` | State Hub agent name that receives notices | | `topics`, `sources` | matched by either | | `channels` | a subset of `hub`, `pull`, `digest` | | `cadence` | `daily` or `weekly`; required for `digest` | ## Notice `{schema, repo, delivery, item, source, received_at}`. It is deterministic, so each (repo, delivery) pair gets exactly one notice. Hub delivery sends one message per subscriber per run. A failed send stays pending and is retried on the next run.