{
"id": "org.cannadb.review",
"defs": {
"main": {
"key": "tid",
"type": "record",
"record": {
"type": "object",
"required": [
"subject",
"createdAt"
],
"properties": {
"body": {
"type": "string",
"maxLength": 50000,
"description": "Markdown-formatted review prose. Optional — a review can be ratings-only or tag-only. Capped at 5000 graphemes (50000 bytes) — enforced by the lexicon to keep reviews modest.",
"maxGraphemes": 5000
},
"images": {
"type": "array",
"items": {
"type": "blob",
"accept": [
"image/png",
"image/jpeg",
"image/webp"
],
"maxSize": 1000000,
"description": "A single review image."
},
"maxLength": 3,
"description": "Up to three image blobs attached to this review. Each image accepts common web image formats and is capped at 1 MB. Reviews are deliberately limited to three images to keep PDS storage burden modest; richer photo galleries belong in grow records (Phase 6)."
},
"effects": {
"type": "array",
"items": {
"type": "string",
"description": "A single effect descriptor."
},
"description": "Effect descriptors as the reviewer experienced them. Open vocabulary — see `flavors` for rationale. The AppView aggregates across reviews into community-derived effect percentages on the strain (the canonical \"top effects\" surface visitors expect on a strain page)."
},
"flavors": {
"type": "array",
"items": {
"type": "string",
"description": "A single flavor descriptor."
},
"description": "Flavor descriptors as the reviewer experienced them. Open vocabulary — the lexicon does not enforce a closed set; the AppView normalizes near-duplicates at index time and aggregates across reviews into community-derived flavor percentages on the strain."
},
"growRef": {
"type": "string",
"format": "at-uri",
"description": "Forward-compatible AT-URI reference to a grow record (`org.cannadb.grow`, Phase 6) backing this review. Optional and speculative — the grow record type does not exist yet and this field is a placeholder for the eventual review-grow linkage. Consumers should ignore this field until the grow lexicon ships."
},
"ratings": {
"ref": "#ratings",
"type": "ref",
"description": "Multi-axis numeric ratings on a 1–5 scale. All axes are independently optional — a reviewer may rate only `overall`, only some axes, or skip ratings entirely. The AppView averages each axis across reviews for the per-strain summary."
},
"subject": {
"type": "string",
"format": "at-uri",
"description": "AT-URI reference to an `org.cannadb.strain` record (any `kind`). Required. (The lexicon spec does not enforce collection constraints on at-uri; consumers must validate the ref resolves to an `org.cannadb.strain` record and reject reviews whose subject points elsewhere.)"
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "Client-declared timestamp when this review was originally written."
}
}
},
"description": "A review of a cannabis strain or cut. Reviews are where subjective experience lives — the reviewer's perceived flavors and effects, multi-axis ratings, prose body, and optional images. Reviews reference exactly one strain record via `subject` and aggregate at the AppView layer into per-strain summaries (community-derived effect/flavor percentages, average ratings). Publishing this record IS the claim — every field is contextualized by the publisher DID at display time. CannaDB editorial accounts do not publish reviews."
},
"ratings": {
"type": "object",
"properties": {
"ease": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Ease-of-grow rating, 1–5. Distinct from the strain record's `growDifficulty` claim — this is the reviewer's experienced ease."
},
"yield": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Yield rating, 1–5. Reviewer's subjective sense of how the strain produced — a grower-axis rating distinct from the strain record's structured `yield` claim."
},
"flavor": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Flavor rating, 1–5."
},
"overall": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Overall rating, 1–5."
},
"potency": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Potency rating, 1–5. Reviewer's subjective sense of strength — distinct from measured cannabinoid percentages."
},
"bagAppeal": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Bag appeal rating, 1–5. Reviewer's subjective sense of visual quality of the cured flower."
}
},
"description": "Multi-axis numeric ratings on a 1–5 scale. All axes are independently optional. Aggregations (averages, distributions) happen at the AppView layer."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}