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.