lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

social-events.md (60272B)


      1 # Public Social Event Substrate
      2 
      3 Status: active implementation contract
      4 
      5 Scope: public Radroots social Nostr event models, codecs, and deterministic conformance vectors in
      6 this repository.
      7 
      8 ## Purpose
      9 
     10 The public social event substrate extends the Radroots event family beyond profile, farm,
     11 operational listings, and trade workflows while keeping relay runtime behavior, application
     12 projections, moderation services, and private Field business documents outside this repository's
     13 event-contract boundary.
     14 
     15 The target implementation is standards-first and Radroots-named. Event models live in
     16 `radroots_event`, canonical encode/decode behavior lives in `radroots_event_codec`, and
     17 deterministic fixtures live under `contracts/conformance`.
     18 
     19 Calendar behavior is based on
     20 [NIP-52](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/52.md)
     21 and its collection metadata uses
     22 [NIP-51](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/51.md),
     23 while text-note Reply threading follows
     24 [NIP-10](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/10.md),
     25 deletion requests follow
     26 [NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md),
     27 and kind-`1111` Comment threading follows
     28 [NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md),
     29 all at NIPs commit `bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91`. Calendar media uses the public
     30 Blossom primitives governed by the protocol pin in
     31 [`blossom-media.md`](blossom-media.md). The upstream NIP-52 rules and the stricter Radroots
     32 authoring and admission profile are separate contract layers.
     33 
     34 ## Implementation Inventory
     35 
     36 The repository implements strict authored and verified-projected kind `1` root-post profiles, a
     37 separate strict-authored and tolerant-inbound kind `1` NIP-10 Reply profile, a strict-authored and
     38 tolerant verified-inbound kind `1111` NIP-22 Comment profile, a strict-authored and tolerant
     39 verified-inbound kind `5` NIP-09 deletion-request profile with pure deterministic suppression
     40 evaluation, kind `7` `Reaction`, generic `List` entries, operational listing
     41 records through `OperationalListing`, the raw kind-`30402` profile partition and validated
     42 FoodAvailability authored, verified-admission, and revision contract, articles, generic public
     43 file metadata, calendar date events, calendar time events, reposts, generic reposts, calendar
     44 collections, RSVP events, and reports.
     45 
     46 The closeout contract requires:
     47 
     48 - complete model and codec coverage for the approved public social event families
     49 - kind and tag constants for the approved NIP surface
     50 - ordinary kind-1 compatibility reads plus strict Update, PhotoUpdate, and Ask authoring
     51 - strict marked direct and nested NIP-10 Reply authoring plus tolerant positional inbound admission
     52 - strict NIP-22 `AuthoredNip22Comment`,
     53   `RadrootsInboundNip22CommentProjection`, and
     54   `RadrootsAdmittedNip22CommentEvent` behavior without legacy `e_root` or
     55   `e_prev` authority
     56 - strict NIP-09 `AuthoredNip09DeletionRequest`,
     57   `RadrootsInboundNip09DeletionProjection`, and
     58   `RadrootsAdmittedNip09DeletionRequestEvent` request behavior plus a separate
     59   pure `RadrootsNip09SuppressionDecision` evaluator without storage authority
     60 - strict NIP-25 `Reaction` behavior where empty content is a valid like
     61 - explicit optional `published_at` support for NIP-99 classified-listing parity
     62 - NIP-65 relay-list validation evidence through `List`
     63 - conformance vectors and canonical-event witnesses for every new or upgraded social event family
     64 
     65 ## Approved Event Families
     66 
     67 The MVP public social substrate includes:
     68 
     69 - strict `AuthoredUpdate`, `AuthoredPhotoUpdate`, and
     70   `AuthoredAsk` publication types plus verified tolerant projection for
     71   ordinary NIP-01 kind `1` events
     72 - strict `AuthoredNip10Reply` direct and nested publication plus
     73   `RadrootsInboundNip10ReplyProjection` for marked and deprecated positional
     74   inbound NIP-10 replies
     75 - strict `AuthoredNip22Comment` publication plus
     76   `RadrootsInboundNip22CommentProjection` and
     77   `RadrootsAdmittedNip22CommentEvent` for kind-`1111` NIP-22 Comments rooted
     78   only in kind `30402`, `31922`, or `31923`
     79 - strict `AuthoredNip09DeletionRequest` publication plus
     80   `RadrootsInboundNip09DeletionProjection` and
     81   `RadrootsAdmittedNip09DeletionRequestEvent` for effect-free kind-`5` NIP-09
     82   event and address deletion requests, followed by pure deterministic
     83   suppression evaluation over verified targets and admitted requests
     84 - `Article` for NIP-23 kind `30023` long-form content
     85 - generic public `FileMetadata` for NIP-94 kind `1063`
     86 - strict authored `AuthoredCalendarDateEvent`, tolerant
     87   `ParsedNip52CalendarDateEvent`, and strict admitted
     88   `AdmittedCalendarDateEvent` models for NIP-52 kind `31922`
     89 - strict authored `AuthoredCalendarTimeEvent`, tolerant
     90   `ParsedNip52CalendarTimeEvent`, and strict admitted
     91   `AdmittedCalendarTimeEvent` models for NIP-52 kind `31923`
     92 
     93 The production-v1 public social substrate includes:
     94 
     95 - `Repost` for NIP-18 kind `6`
     96 - `GenericRepost` for NIP-18 kind `16`
     97 - strict authored `AuthoredCalendar`, tolerant
     98   `ParsedNip52Calendar`, and strict admitted `AdmittedCalendar`
     99   models for NIP-52 kind `31924`
    100 - strict authored `AuthoredCalendarEventRsvp`, tolerant
    101   `ParsedNip52CalendarEventRsvp`, and strict admitted
    102   `AdmittedCalendarEventRsvp` models for NIP-52 kind `31925`
    103 - `Report` for NIP-56 kind `1984`
    104 - operational-listing profile validation through `OperationalListing` at NIP-99
    105   classified-listing kind `30402`
    106 - strict FoodAvailability details, deterministic unsigned authoring, verified tolerant projection,
    107   NIP-01 admission, and strict revision validation for focused kind-`30402` inputs, with explicit
    108   exclusion of Operational Listing and marker-free generic NIP-99 candidates
    109 - relay-list kind `10002` validation through `List`
    110 
    111 ## Contract Decisions
    112 
    113 `Post` remains a compatibility read projection for ordinary kind `1`
    114 text notes and older optional social metadata. It is not an authored boundary.
    115 The public raw `imeta` encoder and its generic tag-builder implementation are
    116 removed so callers cannot turn mutable strings into purported strict media.
    117 
    118 ### Kind-1 Post Trust Layers
    119 
    120 Strict authored root posts are private-field typestates. Update and Ask content
    121 must be non-whitespace and every profile is bounded to 131072 UTF-8 bytes.
    122 PhotoUpdate and optional Ask media use between one and 64 NIP-92 `imeta` tags.
    123 Each image emits exactly `url`, `x`, `m`, `dim`, `size`, and `alt`, in that
    124 order, followed by ordered repeatable `fallback` fields. Primary URLs are
    125 unique and occur as exact substrings of content. MIME is parameter-free
    126 canonical lowercase and matches `^image/[a-z0-9][a-z0-9.+-]*$` exactly;
    127 dimensions are nonzero `u32` values; size is a nonzero `u64`; alt text is
    128 non-whitespace and at most 4092 UTF-8 bytes. Authored events emit at most 4096
    129 tag elements; every element is at most 4096 bytes and all tag elements together
    130 are at most 131072 bytes, including the Ask marker. The strict image state
    131 retains the exact validated
    132 `imeta` representation that the codec emits, so limit validation and encoding
    133 cannot diverge. Authored posts also fit within the 262144-byte compact signed
    134 event limit after exact JSON escaping, tag-array punctuation, and a worst-case
    135 20-digit NIP-01 timestamp are counted; decoded content and tag limits do not
    136 operate as independent escape hatches around that wire budget.
    137 
    138 Every authored primary image is a `AuthoredImage` backed by an approved,
    139 byte-verified Blossom descriptor. Every authored fallback is an approved
    140 Blossom hash-path URL with the same digest. This typestate proves local
    141 descriptor-to-byte agreement only. Successful BUD-02 upload completion remains
    142 a separate runtime precondition before signing.
    143 
    144 Ask is kind `1` and deterministically emits exactly
    145 `["t","radroots-ask"]`. PhotoUpdate is also kind `1`; kind `20` is outside
    146 this contract. Update emits neither the Ask marker nor `imeta`.
    147 
    148 Inbound projection accepts only a `RadrootsSignatureVerifiedEvent`. Any `e`
    149 tag, including an empty or malformed one, selects `ThreadExcluded` before Ask
    150 or media inspection. This is an exclusion-only candidate classification and
    151 does not claim that the event is a valid NIP-10 reply; strict NIP-10 parsing and
    152 promotion remain separately owned. `verify_and_admit_post_event` returns
    153 `RadrootsPostAdmissionOutcome`: only `Root(RadrootsAdmittedRootPostEvent)`
    154 exposes an exact product contract, while
    155 `ThreadExcluded(RadrootsThreadExcludedPostCandidate)` preserves the verified
    156 event and exclusion projection. For roots, exactly one two-element Ask marker
    157 after ASCII whitespace trim and ASCII case folding selects Ask. Multiple
    158 normalized markers fail projection, while a malformed marker shape is retained
    159 as an ordered diagnostic. Ask precedes PhotoUpdate even when attached media is
    160 malformed. PhotoUpdate requires one through 64 wholly qualifying `imeta`
    161 entries; a malformed or mixed set becomes Update with ordered diagnostics.
    162 Unknown fields and repeatable fallbacks preserve wire order, while duplicate
    163 known singletons disqualify media.
    164 
    165 Inbound HTTP(S) media references remain unverified structural strings. The
    166 projection performs no retrieval and makes no Blossom, byte, upload,
    167 reachability, image-decoding, or safety claim. The registry therefore keeps
    168 ordinary unsigned kind-1 identification on `radroots.social.post.v1`; exact
    169 Update, PhotoUpdate, and Ask contracts use `AdmissionOnly` and are returned only
    170 by the verified projection/admission boundary.
    171 
    172 The event-contract registry assigns an explicit authoring policy to every
    173 contract. Strict Profile, Update, PhotoUpdate, Ask, NIP-10 Reply, NIP-22
    174 Comment, NIP-09 Deletion Request, NIP-52 date Event, NIP-52 time Event, and
    175 FoodAvailability contracts are `TypedOnly`;
    176 `radroots.social.post.v1` is `ReadOnly`; ordinary generic-draft contracts
    177 remain `GenericDraft`.
    178 `GenericEventDraft::new` therefore rejects the strict Profile contract and
    179 every governed kind-1, kind-5, or kind-1111 contract with
    180 `contract_not_draft_authorable`. Serialized drafts record registry version `7`
    181 and are accepted only after deserialization revalidates the registry version,
    182 contract, kind, shape, policy, recomputed event id, and known fields. The
    183 authored-plan signing boundary repeats that validation, so stale version-`1`
    184 through version-`6` drafts must be rebuilt. Typed root posts, Replies,
    185 Comments, Deletion Requests, date/time Events, and FoodAvailability values
    186 enter Nostr signing only through opaque typed
    187 builders that expose timestamp selection and signing, but no raw tag/content
    188 mutation or public conversion to the upstream builder. Publication remains a
    189 transport concern operating on signed events. The opaque generic builder
    190 rejects kind `0`, every kind `1` event, kind `5`, and kind `1111` before a
    191 signer is consulted.
    192 
    193 ### NIP-10 Reply Trust Layers
    194 
    195 `AuthoredNip10Reply` is an opaque, bounded authoring state. Direct
    196 Replies contain one root reference; nested Replies contain one root and one
    197 distinct parent reference. Each reference carries a validated 64-character
    198 lowercase event id, a referenced-author pubkey, and an optional
    199 `NostrRelayHint`. The relay hint profile accepts only byte-stable ASCII
    200 WebSocket URLs: an exact lowercase `ws://` or `wss://` scheme; a canonical
    201 lowercase DNS, four-octet IPv4, or bracketed pure-hex RFC 5952 IPv6 authority;
    202 an optional canonical decimal port from `1` through `65535`; and an RFC 3986
    203 ASCII path-abempty and optional query whose percent escapes use uppercase
    204 `%HH`. DNS labels are 1 through 63 bytes, hosts are at most 253 bytes, and
    205 punycode (`xn--`) labels plus final all-decimal or `0x`-hex labels are rejected.
    206 The profile also rejects IDNA or percent-encoded hosts, legacy IPv4 forms,
    207 userinfo, fragments, controls, spaces, backslashes, and any
    208 normalization-dependent spelling.
    209 
    210 Relay syntax and Reply wire budgets are separate. The relay-hint type can hold
    211 a canonical URL whose path is longer than one tag element, while authored
    212 construction and verified inbound projection independently enforce the
    213 4,096-byte tag-element ceiling and return `reply_tag_element_too_large`.
    214 Content is non-whitespace and shares the root-post content, total-tag-byte, and
    215 compact signed-wire budgets.
    216 
    217 Strict authoring is deterministic. A direct Reply emits
    218 `["e",<root-id>,<relay-or-empty>,"root"]` followed by the root author's
    219 two-element `p` tag. A nested Reply emits the root `e` tag, then
    220 `["e",<parent-id>,<relay-or-empty>,"reply"]`, then the root and parent author
    221 `p` tags in that order; equal authors are emitted once. No other authored tag
    222 shape is accepted. Signing is exposed only through the sealed
    223 `Nip10ReplyBuilder`; publication remains transport-owned.
    224 
    225 Inbound projection requires a signature-and-id verified envelope. It accepts
    226 preferred marked `e` references, including the optional NIP-10 author hint, and
    227 supplemental unmarked `e` citations in the same event. An empty marker slot is
    228 treated as absent so a citation may retain its optional fifth-element author
    229 hint. Malformed supplemental references are ignored with ordered diagnostics
    230 without erasing an otherwise unambiguous marked Reply. The projection also
    231 accepts deprecated positional references where the first `e` tag is the root,
    232 the last is the parent, and intermediates are citations. Empty marker slots
    233 remain absent in this mode, including when a fifth-element author hint exists.
    234 Malformed intermediate citations become diagnostics; malformed root or parent
    235 anchors remain hard failures. Because NIP-10 makes
    236 participant propagation, relay hints, and referenced-author hints advisory,
    237 blank content or absent `p` tags do not erase an otherwise unambiguous inbound
    238 Reply. Malformed optional relay, author-hint, citation, and participant metadata is
    239 retained in the verified envelope and exposed as ordered typed diagnostics;
    240 valid relay hints are projected through the same `NostrRelayHint`
    241 profile used for authoring, and rejected hints remain verbatim in the ordered
    242 diagnostic's raw tag. This tolerant read-side behavior never weakens strict
    243 authored output.
    244 
    245 The registry's Reply `e` and `p` tag contracts describe normalized qualifying
    246 semantic references. They do not claim that every malformed optional raw tag
    247 retained by the verified envelope is itself a valid identifier-bearing tag.
    248 
    249 Any `e` tag excludes a kind-1 event from root-card admission before Ask or
    250 media classification. A thread-excluded candidate can be promoted only by the
    251 separate NIP-10 Reply admission boundary; a Reply carrying Ask or media
    252 metadata therefore remains a Reply and never becomes a root card. Admission
    253 proves only the Reply envelope's NIP-01 id and signature plus its NIP-10
    254 structure. It does not retrieve a referenced event or prove its existence,
    255 kind, signature, author, relay availability, or relationship to the declared
    256 author hint.
    257 
    258 The signer backend's externally supplied unsigned-event operation and the
    259 standard NIP-46 `sign_event` method are explicit low-level interoperability
    260 boundaries. Their signed results prove Nostr cryptographic authorization only;
    261 they do not confer a Radroots typed product-authoring claim and are not product
    262 authoring entry points. Relaying an already signed event is likewise a generic
    263 transport operation with no Radroots authoring claim.
    264 
    265 Each post and Reply operation owns exact valid and invalid case kinds in
    266 `contracts/operations.toml`. The canonical and packaged conformance suites
    267 execute every public Update, PhotoUpdate, and Ask authoring function, verified
    268 projection, admission function, and NIP-10 Reply authoring, projection, and
    269 admission boundary; they compare complete deterministic wire parts or
    270 projections and enforce stable negative error codes. Xtask validation pins the
    271 complete operation namespaces, ownership metadata, public types, and exact
    272 case-id-to-kind corpus; it rejects missing, duplicate, unclaimed,
    273 mis-prefixed, or substituted cases.
    274 
    275 ### NIP-22 Comment Trust Layers
    276 
    277 The strict Radroots [NIP-22](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/22.md)
    278 profile is kind `1111` and admits only event or address roots whose `K` kind is
    279 `30402`, `31922`, or `31923`. External `I`/`i` authority is unsupported, and
    280 kind `1` is deliberately excluded because text-note threads use the separate
    281 NIP-10 Reply boundary. Legacy `e_root` and `e_prev` tags have no authority in
    282 this contract.
    283 
    284 `AuthoredNip22Comment` is an opaque, non-Serde authoring state. Canonical
    285 tag order is `E,K,P,e,k,p` for a top-level event root,
    286 `A,K,P,a,e,k,p` for a top-level address root, and
    287 `E,K,P,e,k,p` or `A,K,P,e,k,p` for a nested event or address root,
    288 respectively. Authored `E` and parent or direct `e` event references always
    289 have four elements, retaining an empty relay slot when needed and ending with
    290 the asserted author hint. Authored `A`, `a`, `P`, and `p` tags have two
    291 elements plus an optional relay. The address-root revision `e` tag has two
    292 elements plus an optional relay and never an author hint. Top-level `k`
    293 repeats the supported root kind; nested `k` is exactly `1111`.
    294 
    295 Inbound projection accepts only a `RadrootsSignatureVerifiedEvent` and resolves
    296 the `E`/`A`, `K`, `P`, `e`/`a`, `k`, and selected `p` authority independently
    297 of tag order. Cardinality, shape, canonical numeric spelling, supported kind,
    298 coordinate kind and author, event author hints, and direct or nested
    299 relationships are hard validity rules. Supplemental unknown tags, `q` tags,
    300 and unselected valid lowercase `p` mentions remain available through the exact
    301 raw tags and projection. Inbound NIP-22 reference event IDs, public keys, and
    302 coordinate-author hex accept either ASCII hex case; typed projection normalizes
    303 those values to lowercase while exact raw tags retain the original spelling.
    304 Invalid advisory relay and participant metadata is reported through stable
    305 ordered diagnostics; a well-formed author hint that conflicts with required
    306 `P` or selected `p` authority is a hard error. This tolerance does not broaden
    307 the supported roots or authored forms.
    308 
    309 `RadrootsAdmittedNip22CommentEvent` keeps the projection bound to the envelope
    310 whose NIP-01 id and Schnorr signature were verified. Admission proves the
    311 Comment event and its strict profile only. It does not retrieve a root,
    312 revision, or parent or prove that the referenced event exists, carries the
    313 asserted kind, was signed by the asserted author, or is available at a relay.
    314 
    315 NIP-10 Reply and NIP-22 Comment references share
    316 `NostrRelayHint`. Relay syntax is independent of the Comment resource
    317 profile: content is limited to 131072 UTF-8 bytes, a Comment to 1024 tags, all
    318 tags to 4096 elements including tag names, each element to 4096 UTF-8 bytes,
    319 aggregate tag-element bytes to 131072, and the compact signed event to 262144
    320 bytes.
    321 
    322 The complete public operation namespace is exactly
    323 `social.comment.build_authored_draft`,
    324 `social.comment.project_verified_event`, and
    325 `social.comment.verify_and_admit_event`. Contract
    326 `radroots.social.comment.v1` is registry-v7 `TypedOnly` for authoring and
    327 `AdmissionOnly` for unsigned matching. Generic kind-`1111` signing and client
    328 publication are reserved before signer access.
    329 
    330 `contracts/conformance/vectors/comment/verified_profile.v1.json` is the
    331 canonical 114-case self-contained Comment corpus. Its authored inputs are
    332 explicit, and every projection or admission case carries its complete fixed
    333 signed event JSON rather than a mutation recipe, secret key, or generated base.
    334 The packaged mirror is byte-identical and exercises exact valid projections,
    335 diagnostics, resource boundaries, error precedence, and NIP-01 admission.
    336 
    337 ### NIP-09 Deletion Request Trust Layers
    338 
    339 The strict Radroots
    340 [NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md)
    341 profile is kind `5`. `AuthoredNip09DeletionRequest` is an opaque,
    342 non-Serde authoring state that requires at least one valid `e` event-id target
    343 or `a` NIP-01 replaceable/addressable coordinate. Each event target has a
    344 caller-asserted kind advisory in `0..=65535`. Replaceable coordinates of kind
    345 `0`, `3`, or `10000..=19999` require an empty identifier; addressable
    346 coordinates of kind `30000..=39999` preserve an opaque identifier.
    347 
    348 Authored construction normalizes and sorts event ids and coordinates, rejects
    349 duplicates after normalization, derives the complete unique ascending kind
    350 set, and emits two-element tags in exact `e`, `a`, then `k` groups. Event
    351 target kind values and inbound `k` tags remain advisory: the authoring and
    352 projection layers do not retrieve the referenced event or prove its kind.
    353 
    354 `project_verified_nip09_deletion_request_event` accepts only a
    355 `RadrootsSignatureVerifiedEvent`. It enforces kind `5` and the request resource
    356 budgets before target parsing. Unknown tags and trailing target elements remain
    357 in exact raw order. Valid targets are deduplicated by normalized identity,
    358 retain first-seen tag provenance, and are exposed in canonical sorted views.
    359 Malformed `e` or `a` targets are hard errors. Malformed, noncanonical, or
    360 duplicate `k` advisories produce ordered diagnostics. When a request contains
    361 only address targets, a valid advisory outside the target-coordinate kinds is
    362 also diagnosed; an event target makes that correspondence unprovable.
    363 
    364 `RadrootsAdmittedNip09DeletionRequestEvent` binds the projection to the exact
    365 envelope whose NIP-01 id and Schnorr signature were verified. Admission proves
    366 only a valid bounded deletion request. It does not look up a target, establish
    367 same-author authorization, calculate an address cutoff, replace or suppress an
    368 event, mutate a store, or decide whether another deletion request is immune.
    369 Those remain separate evaluator and storage responsibilities.
    370 
    371 `evaluate_nip09_suppression` is the pure evaluator boundary. It accepts one
    372 signature-verified candidate event and a set of admitted deletion requests,
    373 never mutates either input, reads no clock or store, and returns an explicit
    374 decision with canonically ordered evidence. A kind-`5` candidate is always
    375 immune. Every other candidate can be suppressed only by a request from the
    376 same author. An `e` target is time-independent: an exact event-id match
    377 qualifies even when the request predates the candidate. An `a` target must
    378 match the candidate's canonical replaceable or addressable coordinate and
    379 suppresses revisions whose `created_at` is less than or equal to the inclusive
    380 maximum cutoff among qualifying requests. A later replacement remains
    381 unsuppressed. Advisory `k` tags never participate in authorization or
    382 suppression. Request ordering and repeated qualifying inputs cannot change the
    383 decision.
    384 
    385 The complete public operation namespace is exactly
    386 `social.deletion_request.build_authored_draft`,
    387 `social.deletion_request.project_verified_event`,
    388 `social.deletion_request.verify_and_admit_event`, and
    389 `social.deletion_request.evaluate_suppression`. Contract
    390 `radroots.social.deletion_request.v1` is registry-v7 `TypedOnly` for authoring
    391 and `AdmissionOnly` for unsigned matching. Generic kind-`5` signing and client
    392 publication are reserved before signer access.
    393 
    394 Deletion-request content is limited to 131072 UTF-8 bytes, a request to 1024
    395 tags, all tags to 4096 elements including tag names, each element to 4096 UTF-8
    396 bytes, aggregate tag-element bytes to 131072, and the compact signed event to
    397 262144 bytes. The canonical 80-case self-contained corpus is
    398 `contracts/conformance/vectors/deletion/verified_profile.v1.json`. Authored
    399 inputs are explicit and every projection or admission input contains one
    400 complete compact fixed signed event JSON. The byte-identical packaged mirror
    401 contains no secret or private key, nsec, seed, generator, mutation, base, or
    402 boundary-expansion recipe, and expected projections expose no authorization,
    403 suppression, or store-mutation effect.
    404 
    405 Suppression behavior is governed separately by
    406 `contracts/conformance/vectors/deletion/suppression.v1.json` and its
    407 byte-identical packaged mirror. Its fixed inputs exercise direct event targets,
    408 inclusive address cutoffs, later replacements, requests that predate their
    409 targets, kind-`5` immunity, unrelated authors, mixed qualifying requests, and
    410 input-order independence. The evaluator produces evidence only; neither
    411 fixture nor operation authorizes a store mutation.
    412 
    413 ### NIP-09 Durable Reconciliation
    414 
    415 Event-store schema version `2` installs `0002_nip09` without changing the
    416 byte-pinned version-1 schema input. Its reconciliation-v1 hook re-verifies
    417 every durable raw envelope, rebuilds registry-v7 admission and
    418 addressable-feed-v1 raw heads, and persists normalized event-coordinate,
    419 deletion-request, event-target, and address-target facts under a fresh random
    420 32-byte source generation.
    421 
    422 The same generation partitions canonical addressable-head state and its
    423 append-only transition history. State records the raw head, admission result,
    424 visible or suppressed outcome, canonical NIP-09 evidence, and exact transition
    425 cause. Exact event targets are timestamp-independent; address targets retain
    426 the inclusive request-time cutoff; unauthorized requests remain evidence
    427 without suppressing another author's event; and kind-`5` requests remain
    428 immune. Raw envelopes and tags are never rewritten or removed to represent
    429 suppression.
    430 
    431 Initial reconciliation and repeated full rebuilds share one transactional
    432 marker-first protocol. The marker binds the exact prior generation, immutable
    433 raw counts and high-water sequence, and global transition floor. An impossible
    434 deferred foreign-key barrier makes every partial marker state uncommittable;
    435 the marker can close only after the rebuilt generation is internally complete.
    436 Successful rebuilds append a fresh generation and retain every historical
    437 generation, fact, state, and transition as immutable authority.
    438 
    439 Schema inspection, migration, rollback, and standalone status bind their
    440 catalog, ledger, history, and event-store reads explicitly to SQLite's `main`
    441 database. Before authority access or mutation, each relevant connection rejects
    442 temporary objects whose name or target table collides with declared
    443 event-store ownership, the migration ledger, or the reserved
    444 `radroots_event_store_` namespace using SQLite's ASCII-case-insensitive
    445 identifier semantics. Unrelated temporary and shared-schema state remains
    446 caller-owned. Full foreign-key reports may therefore retain unrelated
    447 shared-schema violations, while a violation whose child table is declared as
    448 event-store-owned or uses the reserved namespace fails with typed event-store
    449 integrity evidence.
    450 
    451 Projection cursors are bound to the active source generation. A projection
    452 version change, unbound legacy cursor, or source-generation change requires a
    453 typed rebuild ticket that captures the target generation and raw high-water
    454 sequence plus the exact prior cursor revision. Consuming that ticket after the
    455 rebuild commits the cursor at that captured high-water even if later raw events
    456 have arrived, then leaves those events for normal catch-up. It rejects
    457 concurrent cursor modification, replay, generation rotation, and same-value
    458 ABA replacement.
    459 
    460 The byte-pinned reconciliation manifest binds migration `0002`, registry-v7
    461 inventory, semantic input vectors, frozen v1/v7 source entrypoints, and the
    462 executable result vector at
    463 `contracts/conformance/vectors/event_store/nip09_reconciliation.v1.json`.
    464 It also binds the owned and borrowed transaction wrappers by production AST
    465 identity. The borrowed wrapper uses a nested savepoint, so a failed ingest
    466 cannot be ignored and then partially committed by its caller. Owned and
    467 borrowed ingest paths explicitly roll back a primary ingest failure and retain
    468 both failures if rollback also fails. The complete
    469 production AST identities of the versioned reconciliation core and shared
    470 raw-head storage modules are frozen independently of post-core product
    471 extensions. Post-core trade and transport-observation orchestration has no
    472 SQLx authority; it receives a private transaction capability whose fixed
    473 production methods may reach only literal SQL for the declared operation/table
    474 pairs, never protocol authority, cursors, migration history, attached
    475 databases, dynamic SQL, or schema mutation. The capability borrow ends before
    476 a final authenticated validator compares the core's source/generation
    477 authority, indexed raw and transition bounds, marker state, and main/temp
    478 schema versions and returns the receipt. Full cardinality and replay checks
    479 remain mandatory on explicit migration and rebuild integrity paths instead of
    480 running full scans for every ingest.
    481 The migration rejects unbounded local-cache reconciliation before any schema
    482 change and repeats the same capacity check under its serialized write
    483 transaction. Event and tag authority are then loaded in checked 512-row pages.
    484 Candidate heads consider only exact event- and address-target request indices;
    485 shared matches are merged once in canonical order without cloning request
    486 payloads.
    487 Schema inspection validates every applied hook in migration order rather than
    488 only the newest migration.
    489 This is local derived-state authority only. It does not assert relay deletion,
    490 network-wide disappearance, or authorization to mutate a remote store.
    491 The raw SQL pool is a fully trusted escape hatch rather than a security
    492 boundary; arbitrary DML or reproduction of the internal maintenance protocol
    493 is outside the supported mutation contract.
    494 
    495 ### Durable Visibility, Transition Feed, and Food Projection
    496 
    497 Event-store schema version `3` installs one central current-visibility authority,
    498 `radroots_event_store_current_visibility_v1`, for every persisted event. Ephemeral events never
    499 enter durable storage. Regular events are their own raw heads; replaceable and addressable events
    500 are compared with the current raw head for their coordinate. The authority composes that raw-head
    501 selection with registry-v7 admission and canonical NIP-09 evidence. All-event reads that claim
    502 current visibility, including `current_event_visibility_v1`, `event_visibility`, `visible_event`,
    503 and `visible_event_head`, must delegate to this view and fail closed on incoherent state. A current
    504 addressable product projection may instead join the transactionally maintained
    505 `radroots_event_store_addressable_head_state` materialization, but must bind the active generation,
    506 complete coordinate and head identity, admission, contract, visibility, and NIP-09 outcome. The
    507 FoodAvailability queries use that bounded specialization.
    508 
    509 The decision precedence is exact: a non-admitted event is `not_admitted`; otherwise an admitted
    510 event that is not the raw head is `not_current`; otherwise an admitted raw head with a suppressed
    511 NIP-09 outcome is `suppressed`; every remaining admitted raw head is `visible`. NIP-09 evidence is
    512 computed independently before the current-head decision, so an admitted `not_current` event can
    513 retain visible or suppressed historical evidence without becoming product-visible. Non-admitted
    514 events carry no NIP-09 evidence. Admitted events also retain the visible reasons for no authorized
    515 reference, an author mismatch, or an address cutoff that precedes the target. A persisted kind-`5`
    516 deletion request is immune, carries visible `deletion_request_immune` evidence with no request
    517 identifiers or cutoff, and cannot be suppressed by another kind-`5` request.
    518 
    519 Addressable transition feed version `1` accepts one through 64 input kinds, all in the
    520 `30000..=39999` range, and fingerprints their sorted, deduplicated canonical set. A cursor binds the
    521 active 32-byte source generation, feed version, exact scope fingerprint, and last scanned global
    522 transition sequence. With no cursor, scanning starts at the active generation floor. A cursor from
    523 another generation or scope is a mismatch, one below the floor is expired, one above the sealed
    524 high-water is ahead, and an absent sequence inside the sealed interval is corruption.
    525 
    526 Transition sequence is global rather than scope-local. A page therefore scans unrelated kinds and
    527 advances its cursor over them, checks sequence continuity, and reads at most 1024 transition rows.
    528 It returns at most the requested number of matching transitions, with a maximum request limit of
    529 64, and at most 4 MiB of visible raw-event JSON. If adding the next matching transition would cross
    530 either returned-item or aggregate-payload limit, the page stops before that transition and the next
    531 call retries it; a single matching row that alone exceeds the payload budget is a typed error. The
    532 page reports a fixed
    533 `source_high_water`, sets `has_more` from the last scanned sequence, and emits that sequence in its
    534 next cursor. It never advances past an unreturned matching transition.
    535 
    536 `RadrootsStoreProducedCanonicalEventV1` means the store selected a signature-verified, admitted,
    537 visible event at the instant represented by its containing transition. Historical transition pages
    538 can therefore carry a canonical payload whose event is `not_current` at the page's final
    539 high-water. The payload is replay input, not a detached proof of present visibility: consumers must
    540 apply every transition in sequence, including later retractions, and use the central current-
    541 visibility API when they need present-state authority. It does not mean that `raw_json` has a
    542 canonical JSON serialization. The wrapper exposes only the typed signed event id, author pubkey,
    543 created-at timestamp, kind, and verified opaque `raw_json`; it deliberately does not expose the
    544 backing `RadrootsStoredRawEvent` or duplicate its containing transition's generation/sequence
    545 witness. The remaining signed fields are available from `raw_json` through the verified event
    546 codec. Store `seq`, `inserted_at_ms`, `updated_at_ms`, source generation, and transition sequence
    547 are local database metadata and must not be persisted as cross-store event identity or replay
    548 authority. Consumers may transport `raw_json`, but must not infer cross-store equality from its JSON
    549 spelling instead of the signed event id.
    550 
    551 FoodAvailability projection version `1` has the sole scope kind `30402` and contract
    552 `radroots.food.availability.v1`. The post-core v2 capability applies pending global transitions in
    553 the same write transaction after protocol reconciliation. Its cursor advances over unrelated
    554 global transitions as well as matching ones. A visible admitted focused replacement is reverified
    555 and registry-v7 reprojected before insertion. A deletion, suppression, selected invalid event, or
    556 selected event outside the focused contract retracts the existing row for the author plus `d`
    557 coordinate. It never restores an older event; a later replacement created after an address-deletion
    558 cutoff can become visible and project normally. Projection rows are unique by generation, author,
    559 and `d`; image rows cascade with their parent, and projection insertion or deletion updates FTS5
    560 through database triggers in the same transaction.
    561 
    562 Point lookup uses author plus typed FoodAvailability identifier. Recent and search queries accept
    563 the `Any`, `Active`, or `Sold` status filter and a limit from 1 through 1000. Both return only the
    564 current visible projection and order by `published_at DESC, event_id ASC`. Search covers title,
    565 summary, content, and location. Its input is at most 256 UTF-8 bytes and 16 nonempty terms split on
    566 Unicode whitespace or control characters; terms are escaped as quoted FTS5 literals joined with
    567 `AND`, so caller input cannot introduce column selectors, operators, prefix matching, or grouping.
    568 Every returned projection row is bounded by the query limit and is individually checked against a
    569 fresh signature verification and registry-v7 reprojection of its immutable raw event, including its
    570 ordered image projection. Food point, recent, and search reads bind each row to the active
    571 generation's persisted addressable-head authority, including its exact head identity, admission,
    572 contract, and visible NIP-09 outcome. That state is maintained by the same transactional
    573 current-visibility predicate; bounded Food reads do not recompute deletion-reference history for
    574 every result row.
    575 
    576 For `RadrootsStoredFoodAvailabilityImageV1`, `qualifies()` means only that tolerant inbound image
    577 projection produced no structural diagnostics. An ordinary valid HTTP(S) URL with valid dimensions
    578 can therefore qualify while `blossom_sha256()` is `None`. A digest extracted from a Blossom-shaped
    579 URL is structural metadata, not evidence that bytes hash to it or that upload, retrieval, decoding,
    580 safety, or availability succeeded. Strict product authoring still requires an approved
    581 byte-verified descriptor, and publication still requires runtime BUD-02 upload-completion evidence.
    582 Blossom-specific client behavior must require the typed digest and the applicable runtime evidence,
    583 not `qualifies()` alone.
    584 
    585 The FoodAvailability cursor seals active source generation, feed version, projection version, exact
    586 scope fingerprint, hook-manifest SHA-256, last transition sequence, and a nonnegative projected-row
    587 count. The addressable feed seal binds the same generation's floor, high-water, and contiguous
    588 count. Each supported projection page compare-and-swaps the prior sequence and row count; it can
    589 advance by at most 1024 global transitions and change the row count by at most 64 and no more than
    590 the number of scoped transitions in that interval.
    591 
    592 Ordinary schema inspection and FoodAvailability point, recent, and search reads use one bounded
    593 fast authority check. It verifies the active generation, feed floor/high-water/count seal,
    594 feed/projection/scope/manifest identity, cursor equality with the source high-water, and the
    595 nonnegative sealed row count without scanning all projection or FTS rows. Explicit migration,
    596 supported rebuild, and conformance integrity paths must call
    597 `audit_food_availability_projection_v1`: the exhaustive audit reverifies and reprojects every
    598 projected signed event, compares all columns and ordered images, binds each row to the active
    599 generation's latest applicable transition for its exact Food coordinate and visible event, requires
    600 the exact admitted and visible head-coordinate witness set and sealed projection and FTS counts, compares
    601 each FTS shadow row, and runs SQLite's FTS5 integrity check. The governed Food read view joins
    602 immutable raw JSON and aggregates ordered image rows so a bounded point/recent/search result does
    603 not issue per-row SQL lookups before those cryptographic checks.
    604 
    605 The fast seal detects drift across supported typed transactions; it is not a claim to detect
    606 arbitrary direct SQL, disk modification, or FTS index corruption on every hot-path read. `pool()` is
    607 a trusted escape hatch outside the supported mutation guarantees. A caller that uses it assumes
    608 authority for any resulting state; only a governed migration, rebuild, or conformance path that
    609 invokes the exhaustive validator can re-establish integrity evidence for that state.
    610 
    611 `Reaction` uses strict NIP-25 semantics. Empty content, `+`, `-`, emoji, and custom reaction
    612 content are valid when the target tags are valid. Missing targets remain invalid.
    613 
    614 `Report` intentionally tightens NIP-56 for the Radroots type: a reported pubkey `p` tag is
    615 required for a valid report, including event and file or blob reports.
    616 
    617 Generic public `FileMetadata` remains separate from private `FarmFileMetadata` even
    618 though both use kind `1063`. The public generic model must cover the current simple NIP-94 tags,
    619 including URL, MIME type, SHA-256 hash, original hash, size, dimensions, blurhash, thumbnail, image,
    620 summary, alt text, fallback, `magnet`, `i`, and `service`.
    621 
    622 ### FoodAvailability Domain Boundary
    623 
    624 Kind `30402` routing first inspects only the raw first element of each tag. The focused marker set is
    625 exactly `radroots:price_unit` and `radroots:quantity`; the Operational Listing marker set is exactly
    626 `radroots:primary_bin`, `radroots:bin`, and `radroots:price`. Focused-only, operational-only,
    627 marker-free generic NIP-99, and mixed-marker inputs are distinct partition results. This happens
    628 before profile tag-shape validation, so a malformed one-element marker still counts. Matching is
    629 exact and case-sensitive. `ClassifiedListingPartition` names those results
    630 `FocusedFoodAvailability`, `OperationalListing`, `GenericNip99`, and `Ambiguous`. The partition is
    631 allocation-free, does not inspect the event kind, marker values, or tag arity, and does not establish
    632 that either profile is valid.
    633 
    634 `FoodAvailabilityDetails` and its focused domain values are checked before construction.
    635 The identifier is 1 through 512 UTF-8 bytes, contains no whitespace, and contains no Unicode
    636 control or format character. Content must contain at least one scalar outside Unicode whitespace
    637 and the information-separator range U+001C through U+001F, and is bounded to
    638 131072 UTF-8 bytes; it is not normalized or subjected to the stricter metadata-text policy. Title,
    639 summary, and location are trimmed, nonempty, control-free text bounded to 4096 UTF-8 bytes.
    640 `published_at` is a canonical nonzero `u64` decimal and can be checked as no later than a supplied
    641 `created_at`.
    642 
    643 Price and optional quantity are canonical unsigned plain decimals with at most 28 ASCII digits
    644 excluding the optional decimal point. Price may be zero; quantity must be positive. Currency is
    645 exactly three uppercase ASCII letters. The dedicated unit vocabulary is `g`, `kg`, `lb`, `oz`,
    646 `each`, `dozen`, `bunch`, `punnet`, `bag`, and `basket`, and quantity uses the same unit as price.
    647 Status is exactly `active` or `sold`.
    648 
    649 Image dimensions are two nonzero canonical `u32` decimal components in `WIDTHxHEIGHT` form.
    650 Validated details accept no more than 64 images, reject duplicate URLs or Blossom digests, and
    651 accept only `AuthoredImage` values backed by an approved, byte-verified image descriptor.
    652 That proof establishes local descriptor-to-byte agreement only. It does not establish BUD-02 upload
    653 completion, decoded raster dimensions, content safety, retrieval, reachability, or network
    654 availability.
    655 
    656 The details model deliberately has no farm, bin, route, pickup, delivery, order, checkout, or other
    657 commerce-workflow field. `authored_food_availability_to_wire_parts` accepts these details plus
    658 `created_at`, requires `published_at <= created_at`, and emits unsigned kind-`30402` wire parts. Its
    659 closed tag sequence is exactly `d`, `title`, `summary`, `published_at`, `location`, `price`,
    660 `radroots:price_unit`, optional `radroots:quantity`, `status`, then zero or more `image` tags. Price
    661 is exactly amount plus currency, quantity is exactly amount plus unit, and image is exactly URL plus
    662 dimensions. The decoded tag budgets and the 262144-byte compact signed-event budget are both
    663 enforced. The operation neither chooses `created_at` nor signs or transports the result.
    664 
    665 Inbound projection accepts only a `RadrootsSignatureVerifiedEvent`. Raw marker partitioning occurs
    666 before focused tag validation: Operational Listing and marker-free generic NIP-99 candidates are
    667 explicit exclusions, while mixed markers are an error. A focused candidate requires the complete
    668 core profile and rejects tags that purport to add buyer, checkout, delivery, exception, group,
    669 invite, order, payment, pickup, proof, provenance, receipt, route, `route_stop`, or task capability.
    670 Other optional NIP-99 tags remain outside the focused projection and are ignored. Accepted inbound
    671 decimal values are normalized without exceeding the 28-digit wire bound, and three-letter currency
    672 is normalized to uppercase.
    673 
    674 Inbound image observations are not authored media typestates. The projection retains at most the
    675 first 64 tags in wire order and records stable ordered diagnostics for count overflow, malformed
    676 shape, invalid HTTP(S) URL, missing or invalid dimensions, duplicate URL, and duplicate Blossom
    677 digest. Image diagnostics do not invalidate an otherwise complete focused core. They confer no
    678 Blossom approval, byte agreement, upload, decoding, retrieval, safety, or availability claim.
    679 
    680 `verify_and_admit_food_availability_event` performs NIP-01 id and Schnorr verification before that
    681 projection and returns either `RadrootsAdmittedFoodAvailabilityEvent` or an explicit verified
    682 non-focused exclusion. The admitted type binds the projection to the verified envelope and exposes
    683 the admission-only `radroots.food.availability.v1` registry contract. The contract is `TypedOnly`:
    684 strict authored details are its only product-authoring input, and unsigned kind or tag matching does
    685 not confer focused admission.
    686 
    687 Revision validation accepts two independently signature-verified events and re-applies the exact
    688 authored wire profile to each side; tolerant normalized or diagnostic-bearing projections cannot
    689 enter this comparison. Kind, author, `d`, and `published_at` must remain stable. The candidate must
    690 have a later `created_at`, or the lower event id when both timestamps are equal. Invalid previous
    691 and current inputs have side-specific errors.
    692 
    693 With the `events` feature, `build_food_availability_event` fixes `created_at` during
    694 typed construction, derives the exact strict wire parts, and returns a sealed builder with no raw
    695 tag, content, or timestamp mutation. With the `signing` feature, the builder supports local signing.
    696 Generic builder signing rejects focused and mixed-marker kind-`30402` events before signer access.
    697 Marker-free generic NIP-99 and operational-only builders remain available for explicit
    698 compatibility. Relay publication remains transport-only and establishes no Radroots authoring
    699 claim.
    700 
    701 Behind the explicit non-default `legacy-ingest` feature, legacy replica ingestion verifies
    702 kind-`30402` identifiers and signatures before acquiring its write transaction, then selects the
    703 raw addressable head before profile decoding. Only the
    704 Operational Listing partition can reach the legacy trade-product projection. A signature-valid,
    705 coordinate-valid, selected focused event or marker-free generic NIP-99 event advances the raw head
    706 as excluded; selected invalid focused, mixed-marker, and malformed operational profiles advance it
    707 as rejected. Every selected excluded or rejected replacement removes an older operational
    708 projection, so stale events cannot resurrect it. A signature failure changes neither the raw head
    709 nor the projection; a missing or invalid `d` tag fails before an addressable head can be selected.
    710 The feature-gated public head-only helper rejects kind `30402`, which must use profile-aware legacy
    711 ingestion so head and projection changes remain atomic. Neither helper is a Phase 1 product ingest
    712 boundary.
    713 
    714 The typed Nostr boundary does not prove BUD-02 upload completion. Every media-bearing caller must
    715 obtain successful Blossom upload evidence before signing or publishing; byte-verified descriptors
    716 alone prove only local descriptor-to-byte agreement. Typed outbox persistence and upload-evidence
    717 bridging remain separate runtime responsibilities.
    718 
    719 ### Calendar Trust Layers
    720 
    721 Kinds `31922`, `31923`, `31924`, and `31925` have three explicit,
    722 non-interchangeable layers:
    723 
    724 | Layer | Public role | What success establishes |
    725 | --- | --- | --- |
    726 | bounded structural wire or envelope | preserves the complete NIP-01 event while validating wire shape, identifier syntax, and resource limits | structural data only; it does not establish a matching event id or valid Schnorr signature unless the caller invokes the separate verification operations |
    727 | tolerant NIP-52 parse | one of the `RadrootsParsedNip52*` date event, time event, calendar collection, or RSVP types | the expected kind and the pinned baseline NIP-52 semantics parse successfully; observed standard fields and tolerated wire spellings remain distinguishable from canonical authored data |
    728 | strict Radroots admission | one of the corresponding `RadrootsAdmitted*` calendar types | the parsed value also satisfies the canonical Radroots identifier, reference, metadata, media, date, or UTC-day profile applicable to that kind |
    729 
    730 The calendar `*_parsed_from_event` helpers construct a structurally checked raw envelope alongside
    731 the tolerant projection. Their names do not mean that the event id or signature has been verified.
    732 Before a caller treats relay data as accepted, it must recompute and compare the NIP-01 event id,
    733 verify the Schnorr signature against the event author, dispatch the event to the matching kind
    734 `31922`, `31923`, `31924`, or `31925` parser, and then apply strict admission when the Radroots
    735 profile is required.
    736 The kind-specific parsers reject the wrong kind, but neither baseline parsing nor strict admission
    737 performs cryptographic verification. An admitted model is therefore valid only while it remains
    738 bound to the already id- and signature-verified envelope from which it was parsed.
    739 
    740 Strict authoring is the outbound counterpart, not a fourth inbound verification state.
    741 `AuthoredCalendarDateEvent`, `AuthoredCalendarTimeEvent`,
    742 `AuthoredCalendar`, and `AuthoredCalendarEventRsvp` have private checked fields and
    743 encode deterministic `Nip01EventWireParts`. Wire parts contain only `kind`, `content`, and
    744 `tags`; the owning runtime still supplies `created_at` and author identity, computes the event id,
    745 signs the event, and publishes it.
    746 
    747 ### Baseline NIP-52 Parse
    748 
    749 Kinds `31922` and `31923` use plain-text description content. Empty content projects to no typed
    750 description. The `d`, `title`, and `start` tags are required singletons, and `end` is an optional
    751 singleton with an exclusive boundary. The tolerant common projection covers the pinned standard
    752 fields:
    753 
    754 - optional `summary`, `image`, and `g`
    755 - repeated `location` values without collapsing them to one value
    756 - repeated participant `p` tags containing a 32-byte hexadecimal public key, optional recommended
    757   relay URL, and optional role
    758 - repeated category `t` tags and absolute-URI reference `r` tags
    759 - repeated calendar-inclusion request `a` tags whose coordinates identify kind `31924`, with an
    760   optional recommended relay URL
    761 - the deprecated singleton `name` as observed compatibility data; it never replaces the required
    762   `title`
    763 
    764 Other tags remain available only through the complete structural envelope. Baseline parsing does
    765 not silently promote unknown fields into the typed calendar contract.
    766 
    767 Kind `31922` uses semantic Gregorian `YYYY-MM-DD` values for inclusive `start` and optional
    768 exclusive `end`. Year zero, impossible dates, non-canonical date spellings, and an `end` that is not
    769 later than `start` are invalid. When `end` is absent, the event ends on the same date as `start`.
    770 Uppercase `D` is not defined for date-based events by the pinned NIP-52. The tolerant parser retains
    771 every observed uppercase-`D` tag as uninterpreted extension data so standards-compatible inspection
    772 remains lossless at that boundary; strict Radroots admission rejects any such date-event extension.
    773 
    774 Kind `31923` uses unsigned decimal Unix seconds for inclusive `start` and optional exclusive `end`.
    775 When `end` is absent, the event is instantaneous. At least one uppercase-`D` day index on which the
    776 event takes place is required, and the tolerant parser rejects indices outside the event interval.
    777 In line with NIP-52's `SHOULD` for multiple coverage tags, baseline parsing does not require either
    778 the start-day index or complete coverage. It also preserves duplicate, unordered, or leading-zero
    779 numeric `D` observations for
    780 diagnostics; those spellings are parser tolerance, not canonical authored output. Baseline parsing
    781 does not apply the Radroots 366-day admission limit. Optional `start_tzid` and `end_tzid` values must
    782 be exact identifiers in the bundled IANA Time Zone Database (`jiff-tzdb` `0.1.8`, TZDB `2026c`).
    783 When `end_tzid` is absent and `start_tzid` is present, the effective end time zone is `start_tzid`.
    784 
    785 An inbound baseline `image` is only a structurally valid absolute URI. It is not a Blossom claim,
    786 an approved reference, or evidence about bytes or network state.
    787 
    788 Kind `31924` is a calendar collection, not a generic list or list-set publication surface. Its bounded
    789 plain-text `content` is the NIP-52 detailed description and is required on wire even when empty.
    790 The required singleton `d` and `title` tags identify and name the collection. Repeated `a` tags
    791 contain only kind-`31922` or kind-`31923` addressable coordinates and may each carry their own
    792 recommended relay URL. A collection with no `a` tags is valid. The optional singleton
    793 `description` and `image` tags are NIP-51 list metadata; `description` is distinct from NIP-52
    794 description content and the parser does not merge one into the other. The baseline parser accepts a
    795 structurally valid absolute `image` URI without making a Blossom or network claim. Singleton text
    796 tags have exactly two elements; collection `a` tags have exactly two elements plus an optional relay
    797 element.
    798 
    799 Kind `31925` is an RSVP with bounded optional free-form note content. It has exactly one required
    800 `d` identifier, one required `a` coordinate for a kind-`31922` or kind-`31923` event, and one
    801 required `status` value: `accepted`, `declined`, or `tentative`. Optional singleton `e`, `fb`, and
    802 `p` tags respectively identify one exact event revision, the `free` or `busy` availability state,
    803 and the event author. The `a`, `e`, and `p` references each carry an independent optional
    804 recommended relay URL; a relay hint on one reference is never copied to another. The `p` form is
    805 an author hint without participant-role semantics. The `d`, `status`, and `fb` tags have exactly
    806 two elements; the `a`, `e`, and `p` tags have exactly two elements plus an optional relay element.
    807 Baseline parsing preserves `fb` observed on a declined RSVP for diagnostics but treats its effective
    808 value as absent, as required by NIP-52. Parsing a syntactically valid `e` tag does not establish
    809 that it is a revision of the referenced addressable event.
    810 
    811 ### Strict Radroots Calendar Profile
    812 
    813 Strict authored and admitted calendar metadata uses canonical nonempty text, canonical participant
    814 values, validated lowercase-scheme `ws`/`wss` relay URLs, and lowercase geohashes. Relay URL host,
    815 port, path, and query spellings are preserved rather than normalized. The authored common surface supports repeated locations,
    816 optional geohash and summary, repeated participants, categories, absolute-URI references, and
    817 kind-`31924` calendar-inclusion requests. It intentionally does not author deprecated `name` tags.
    818 
    819 Strict kind-`31922` events never emit or admit uppercase `D`. Strict kind-`31923` events require
    820 canonical decimal timestamps and the exact, ascending, duplicate-free sequence of every UTC-day
    821 index covered by the interval, where `D = floor(unix_seconds / 86400)` and `end` is exclusive.
    822 Authoring derives this sequence rather than accepting it from callers. Strict authored and admitted
    823 time events cover at most 366 UTC days.
    824 
    825 Registry-v7 marks both Event contracts `TypedOnly`. Their authored models
    826 convert through the same immutable authored-plan boundary as every other
    827 strict product profile, and sealed Nostr builders allow local or opaque-host
    828 signing without exposing kind or tag mutation. Historical plan decoding
    829 re-runs tolerant parsing, strict admission, and exact authored tag-order
    830 validation; deprecated `name`, reordered tags, incomplete day coverage, and
    831 other inbound-tolerated spellings cannot become strict outbound plans.
    832 
    833 Strict kind-`31924` and kind-`31925` identifiers are syntax-valid 128-bit values encoded as exactly
    834 22 unpadded base64url characters. This shape does not prove uniqueness; an authoring runtime must
    835 generate a fresh identifier for every collection or RSVP identity. Strict collection authoring and
    836 admission require canonical title and optional NIP-51 description text, validated relay hints, and
    837 duplicate-free event coordinates while still permitting an empty collection. Collection event
    838 references remain limited to kinds `31922` and `31923`.
    839 
    840 Strict RSVP authoring and admission require canonical `a`, optional `e`, and optional `p`
    841 references. An admitted `p` author hint must match the public key in the `a` coordinate. Authored
    842 RSVPs never emit `fb` when status is `declined`; inbound baseline and admitted values retain such an
    843 observation only for diagnostics and return no effective free/busy state. Neither baseline parsing
    844 nor strict admission proves that a referenced revision exists, that it matches the addressable
    845 event, or that the RSVP author is authorized to answer for another party.
    846 
    847 Calendar images have an explicit progression of trust. Tolerant inbound parsing accepts any
    848 absolute image URI. Strict inbound admission requires a structural Blossom hash-path URL, but does
    849 not prove approved-reference policy, byte agreement, upload completion, reachability, or image
    850 safety. Strict authored models accept only `AuthoredImage`, which wraps an approved,
    851 byte-verified Blossom descriptor declared as `image/*`. That typestate proves local
    852 descriptor-to-byte agreement only.
    853 
    854 A publication runtime must not sign or publish a media-bearing calendar draft until the exact blob
    855 has completed a successful BUD-02 upload and the runtime's bounded retrievability check has
    856 succeeded. Consumers remain responsible for bounded retrieval, redirects, content decoding, and
    857 format-safety policy. No calendar model upgrades an observed relay URL into either an upload receipt
    858 or an availability guarantee.
    859 
    860 Product routing uses surface-specific kind classifiers rather than a broad public-social set. Home,
    861 Events, Market, Map, and Profile public-content candidates are explicit. A kind-`30402` value alone
    862 does not select FoodAvailability: raw marker partitioning first distinguishes focused,
    863 operational, generic NIP-99, and ambiguous candidates, and only signature-verified focused admission
    864 returns the FoodAvailability contract. Report kind `1984` is a moderation/admin candidate, not
    865 normal feed content. Relay and HTTP auth kinds are transient and excluded from durable social and
    866 farm-ops candidate sets. Private farm operations candidates include the farm workspace manifest,
    867 farm CRDT change envelope, farm file metadata, and the supported NIP-29 group event subset.
    868 
    869 `RadrootsRelayList` is not a separate model type in the target contract. Operational listing records
    870 are represented through `OperationalListing`, and NIP-51 standard and list-set entries,
    871 including NIP-65 relay metadata kind `10002`, are represented through `List`. NIP-51
    872 taxonomy may classify kind `31924` as a calendar list, but generic list and list-set decoding or
    873 authoring must reject that kind; only the calendar-specific model and codec may parse or publish it.
    874 
    875 ## Exclusions
    876 
    877 This substrate does not include `RadrootsFeedItem`, `RadrootsMapPin`, NIP-72 community events,
    878 checkout or payment events, or public task, harvest, work-session, approval, or other Field business
    879 document event types.
    880 
    881 Task records, work sessions, harvest records, approvals, and similar Field business objects remain
    882 CRDT document semantics carried inside the CRDT change envelope unless a later contract explicitly
    883 promotes them.
    884 
    885 ## Consumer Boundary
    886 
    887 The public social surface is event and codec substrate first. Consumer packages may wrap these
    888 models and codecs, but this repository owns only the core Rust contracts and deterministic
    889 conformance evidence. Package-specific operation maps, bindings, and generated artifacts are outside
    890 this contract boundary.
    891 
    892 ## Conformance Boundary
    893 
    894 Every new social codec and every upgraded existing social codec must have deterministic valid and
    895 invalid conformance vectors before closeout. The FoodAvailability profile vector assigns exact
    896 valid and invalid case kinds to strict authoring, verified projection, combined verification and
    897 admission, and revision validation. The canonical NIP-22 Comment vector assigns all 114 exact
    898 case ids and kinds to its three governed operations and has a byte-identical packaged fixture.
    899 Other upgraded vectors must include reaction, operational-listing, farm, list, and list-set
    900 behavior whose public contract changes during the refactor.
    901 
    902 Social vectors are repo-owned and synthetic. They must not depend on application relay state, local
    903 databases, external services, root fixture catalogs, or ambient machine state.