docs/RESEARCH-ROADMAP.md E5 asks for "a low-/no-code
'propose a cited passage + an eval case' path with provenance fields (source, license,
fetch_date, lang, topic) enforced and a representational-harm checklist". This directory is
the worked example that path is tested against, in the same spirit as
examples/herb-garden-plugin/ for the eval plugin API.
The corpus is the bottleneck every persona in docs/USER-RESEARCH.md
runs into, and unsourced plant-care text is exactly what goes wrong in this domain (EV1,
EV4). So contribution is not a free-text form: a proposal is reviewed mechanically before a
human spends attention on it.
No code. Open a corpus proposal issue. It collects the parts only you can supply — species, source, license, fetch date, the passages, the eval question and the phrase the answer must contain, and the harm checklist — and a maintainer transcribes it into a proposal file, filling in the mechanical remainder (schema version, submitter and date attribution, section titles, the eval case's id, sources, and provenance). It is a deliberate subset of the YAML below, not a field-for-field mirror of it; the review both doors end at is the same.
YAML. Start from the template, fill it in, and review it locally — all offline:
uv run sprout propose template > proposals/my-plant.yaml
uv run sprout propose check proposals/my-plant.yaml # review just this one
uv run sprout propose check # what CI runs: every proposalsprout propose check with no arguments walks the repository and reviews every file
with the shape of a proposal, wherever it was filed — not just this directory. That is
what make propose-check and the propose-check CI step run, so a contributor's proposal
in proposals/ is gated exactly as hard as this example. Three ways it
fails closed:
- a proposal outside the declared submission locations (
proposals/,examples/corpus-proposal/) is an error — a misfiled proposal is reported, never skipped; - a file that reads as a proposal but does not parse is a failure, not a skip;
- discovering no proposals at all is a failure, because a gate that reviews nothing is not a gate.
sprout propose check (src/sprout/propose.py) is a pure function of the proposal, the
corpus already on disk, and the date. It reuses the maintainer-side corpus workbench
(EXP-12, src/sprout/corpus_report.py) rather than re-implementing its rules, so a
contribution is held to exactly the standard the shipped corpus is held to:
| Area | Enforced |
|---|---|
| Location | the file sits in a declared submission location; anything else is an error, not a skip |
| Identity | slugified species, not already in the corpus, botanical name present |
| Provenance | license on the contribution allowlist, http(s) URL, synthetic prose confined to the example.invalid placeholder host (host equality, not a substring of the URL), non-synthetic content gated on an expert sign-off |
| Dates | ISO-8601, never in the future; E7's citation-freshness SLA against the topic the passage carries, so toxicity prose gets the stricter SLA even under the template's default topic: care (stale is a warning, unusable is an error) |
| Languages | every languages.supported language proposed together; EN/ES section-count parity; no heading left untranslated |
| Topics | the reference-language passage covers the corpus's canonical topic taxonomy |
| Chunk quality | no sentence longer than chunk.max_words; the "names its plant" extraction-safety heuristic |
| Safety | every proposed sentence run through the shipped never-certify-"safe" guard (guards.asserts_safety) — a certification cannot enter the corpus any more than it can leave through the answer path |
| Representational harm | every checklist box affirmed and attributed; the medicinal/edibility box cross-checked against an EN/ES claim vocabulary |
| Eval case | loads as a DatasetItem, id not already taken, cites one of the proposed documents, and every expected_fact appears verbatim in the passage |
changes-requested— at least one error. Not mergeable as authored.ready-for-expert-review— mechanically clean, but safety-bearing (toxicity or ingestion prose, or a toxicity eval case) with no committed sign-off. This tool cannot stand in for the licensed veterinary toxicologist / poison-control clinician — or, for Spanish copy, the native horticulture reviewer — thatdocs/RESEARCH-ROADMAP.mdrequires, and it does not pretend to. Recording the gate as a machine-checked state is the point.ready-to-merge— mechanically clean and either not safety-bearing or carrying anexpert_reviewblock whose sign-off artifact is a committed Markdown document underdocs/audits/that names this species, its reviewer, and the date they signed. Pointing the field at some other file that happens to exist does not discharge the gate.
propose check exits non-zero only on changes-requested, so the merge-blocking CI step
(propose-check, inside the eval-a11y job and make verify) catches real defects — in
any submitted proposal, not only this one — without manufacturing a clinician's approval.
--require-expert-review tightens it for a maintainer about to merge.
parlor-palm.yaml proposes Chamaedorea elegans with EN + ES passages
across all five corpus topics, a groundedness case pinned to the watering passage, and a
completed harm checklist. It reviews with zero findings and lands on
ready-for-expert-review — the honest end state, because it carries toxicity prose. Nothing
here is merged into corpus/: a proposal is reviewed, not auto-applied.