Skip to content

Examples in the curated opening: an exemplar item kind, one provenance projection, and the manual proof (integration under #193) #212

Description

@holden

Scope revision, 27 September 2026. This replaces the body posted earlier today. The audit found that the earlier text restated constraints already owned by #181, #190, #196, #197 and #203, and named no schema, seam or wireframe. Three things move out: the bot nomination adapter goes to #197 (the paragraph to add there is under Handoffs); the cross-issue wording fixes go to #190, #198 and #201/#194; the discovery "candidate adapter" is withdrawn, because the seam it wanted already exists (#181 S3) and the open product decision it assumed is #193's to make. What stays is the work only this issue can do. A second audit of the earlier body (27 September) asked for four clarifications; two were already answered by this rewrite (the exemplar item kind; keeping bot persistence out), and two are taken below: Prerequisites and sequencing, and the one named discovery path under Discovery.

Purpose

An exemplar (#181 layer 2: a person, a work, a passage that someone cited as an example of a meaning, with a rationale and evidence) cannot be selected into a saved composition today, and nothing on the page tells a reader where an example came from beyond one sentence. This issue adds the one item kind the composition schema lacks, one provenance shape that every surface draws from, and proves the whole path by hand: a human nominates, a reviewer accepts, an operator composes and publishes, the page renders it with inference and providers offline, and the card says why it is there.

It fits the one model the site already has. Every "X is an example of Y" is an assertion_revisions row between two objects, told apart by predicate, method, actor kind and source: WordNet's instance_of under method: "source", a person's illustrates under "curated", later a persona's under "persona:<slug>@<v>" and, when #189 lands, votes on the same revisions. A composition is not a fourth kind of claim. It is a selection of eligible claims and content, with its own approval. This issue adds no predicate, no actor kind, no vote, no candidate store and no event log.

Verified starting point (origin/main @ c6bd882, devils_dictionary_v2, 27 September 2026)

Fact Where
editorial_composition_items.item_kind IN ('content', 'sense_quotation', 'work'); assertion_revision_id is an optional annotation with ON DELETE SET NULL and no shape rule. No item kind can hold an entity subject. 20260926233642_create_curation_foundation.exs lines 407–476
Curation.Eligibility accepts a claim that is current, active and passes Claims.visible(:public). It does not require accepted. curation/eligibility.ex:370-379
Claims.visible(:public) hides rejected/withdrawn, and hides an unaccepted nomination only when the subject is entity/person. The illustrates-wide gate is #190, unbuilt. claims.ex:434-470
Claims.Contributions.propose/6 takes a user scope only and hardcodes method: "curated". No code creates a bot actor. claims/contributions.ex:86-133
Examples.for_page/3 items already carry claim.{method, rationale, nominated_by, evidence, review_state, manifest} and sources[]; signals.featured_at is always nil. examples.ex:385-460, 655-690
The seeder writes metadata manifest, manifest_checksum, row, row_key, curator and the resolved sense external_key on the revision. examples/seeder.ex:355-367
Published.current/1 returns %{composition, version, receipt, lead, highlights, withheld} and withholds without substituting. Nothing in the web layer calls it. curation/published.ex:39-59
PR #202 (accepted, unmerged): Curation.Opening with Highlight{kind: :artwork | :quotation}, Reason{kind: :policy | :source_match | :editorial, author}, Review{state, selected_by, reviewed_by}. branch codex/consolidation-2026-09-27
PR #210 (unmerged): Runtime.Packet is built from Bierce entries, definitions and sense quotations only. It carries no exemplar and no discovery result. same branch, curation/runtime/packet.ex
Discovery: a result promotes to the register only through /connect, prefilled with subject, object (the sense), evidence_revision and evidence_locator for a corpus candidate that has an object_id and a sense_id; an Artsy card prefills subject alone. ConnectionLive reads those params. The metadata.from_result provenance #181 S3 described is not written today. components/culture.ex:1617-1633, artwork.ex:105, live/connection_live.ex:172-206
Providers that leave a durable identity record behind a result (identity_record/1): CineGraph, Commons, the Met, Open Library, PoetryDB, Wikiquote. The news, stock-photo, GIF and music providers do not, by design (README promise 6). discovery/providers/*.ex

Live counts: instance_of current active 66,573; illustrates current active 5 (all test rows), 10 revisions in all; assertion_reviews 2; assertion_votes 0; no community source row; actors: import 11, user 2, unknown 1, bot 0; curation_configurations, curator_profiles, editorial_compositions all 0; discovery_results 1,729, of which 639 carry an object_id.

The dev database also carries the unmerged runtime migrations from PR #210, applied in the 13:11Z incident on #194 and awaiting the owner's rollback decision. Nothing here depends on them; all tests run on an isolated MIX_TEST_PARTITION.

Vocabulary

Three assertions that share a subject and must never be confused:

A provenance is the answer to "why is this on the page", assembled from those records and only those. It is a read projection with no table of its own.

What this issue owns

Owns Does not own (and who does)
The exemplar item kind and its eligibility Bot nominations, propose/6 bot scope, curation_nominations, no-reopen of rejected claims: #197, gated by #190
Examples.Provenance, drawn on the card, the chip, the person page and the opening The public illustrates gate: #190
The manual proof: nominate → accept → compose → publish → read offline Runs, ballots, decisions, selection-v1: #196/#197/#203
The exemplar mapping into Opening.Highlight The reader reading compositions at all (#156 Phase 2) and the page binding (#194)
Deterministic tests for the above Refresh, freshness, leases: #198

Schema: one additive migration

Read against the live editorial_composition_items. Nothing existing changes; the new kind is a fourth value of the existing column with its own shape rules. mix ecto.gen.migration add_exemplar_composition_items.

Change Rule
item_kind CHECK gains 'exemplar' in editorial_composition_items_shape
item_object_id required for an exemplar at insert (the existing trigger already demands it). It is the claim's subject: an entity/* or content/* object.
assertion_revision_id required for an exemplar at insert. New composite FK (assertion_revision_id, item_object_id) → assertion_revisions (id, subject_object_id) so the claim is about the object shown, the way content_revision_id is tied to content_id. Needs a unique index on assertion_revisions (id, subject_object_id), which is free because id is the key. ON DELETE SET NULL (assertion_revision_id); required_references already records it, so a deleted claim withholds the item (:claim_deleted).
content_revision_id allowed for an exemplar only when the subject is a content object (a passage or quotation): pins the words shown, through the existing composite FK. NULL for an entity subject; entities have no revisions, and the label and image are read from the registry at read time under the existing retire/split check.
sense_revision_id, locator, words_sha256, catalog columns, source_record_revision_id NULL for an exemplar (CHECK)
meaning_sense_revision_id / meaning_lexeme_id as today, exactly one; must agree with the claim's object (below)
role highlight only. The lead stays a content definition (K10 unchanged).
selection_origin manual today; panel_recommendation/human_override/human_added arrive with #197 for every kind, not here

No table for candidates, nominations or provenance. No change to assertions, assertion_revisions, assertion_reviews, predicates or predicate_endpoint_rules.

Eligibility of an exemplar item (Curation.Eligibility)

An exemplar item stands when all of these hold, else it is withheld with the named reason:

Check Reason when it fails
the revision is current and active, predicate illustrates :claim_not_current
its latest review is accepted (not merely visible) :claim_not_accepted
it passes Claims.visible(:public) and Claims.Visibility rights redaction :claim_not_visible
its object sense is the item's meaning_sense_revision_id's sense, or its object concept is refers_to by the item's meaning_lexeme_id :meaning_mismatch
the review context's fingerprint still matches the endpoints (the card's changed since review) :claim_context_changed (decision 2)
the subject object is active, and a content subject's revision is current existing :object_deleted / :content_not_current

The fingerprint the reviewer accepts includes the claim revision id and the review-context fingerprint, so a re-worded claim or a moved sense needs a new composition review (#196 rule 4). This is stricter than the public gate on purpose: a highlight that leans on a claim waits for a reviewer even while the page's card does not. It does not depend on #190: accepted is required here regardless of what the public gate does.

Display identity

Within one arrangement, no two items may show the same thing: an exemplar item and a work item for the same object, or two exemplars of one object under two meanings. The key is the subject object id, plus the content revision's words for a content subject. Compositions.create_version/3 refuses with {:duplicate_display_identity, object_id} (check whether slice 1's arrangement rule already covers this before adding it).

The provenance projection

One function, Examples.Provenance.of/2, over an Examples item, a CompositionItem or an Opening.Highlight, for a viewer. It returns typed stages, with :unknown where the record is silent and :none where the record says nothing happened. It reads only through Claims.visible/2 and Claims.Visibility, so a hidden nomination has no stage at all, and no count, tombstone or label betrays it.

%Examples.Provenance{
  id: "ex:<assertion_id>" | "inst:<family>" | "sel:<composition_item_id>",
  source: [%{slug, name, tier, assertion_id, record_url | nil}] | :none,      # every source, kept apart; never merged into one
  nomination: %{by: %{kind: :user | :bot, label, actor_id}, origin: :form | :manifest | :agent | :unknown,
                manifest: %{slug, checksum, row} | nil, rationale, meaning: %{sense_key, gloss, sense_revision_id},
                evidence: [%{role, url | nil, attribution | nil, locator | nil}], at} | :none,
  agent: %{profile: %{slug, version}, model: %{digest, config}, run_id, proposal_id, note} | :none | :unknown,  # :none for human work; filled by #197
  review: %{state, by: %{label} | nil, decided_at | nil, context_changed?: boolean} | :none,
  selection: %{kind: :ranked, signals: %{...}}                                   # the examples section: Rank.order/1, nothing else
           | %{kind: :composed, composition_id, version, position, selection_origin, selected_by: %{kind, label}, note | nil}
           | :none,
  publication: %{receipt_id, authority_kind, actor: %{label}, committed_at, superseded_at | nil, withdrawn_at | nil} | :none
}
Stage Read from :none means :unknown means
source assertions.source_id, origin_key, source_records.url not source-listed (an exemplar) never
nomination assertions.{origin_actor_id, submitted_by_actor_id}, assertion_revisions.{rationale, metadata, method, inserted_at}, assertion_evidence not cited (an instance) a claim with no actor and no manifest metadata (the five test rows)
agent method prefix; curation_nominations and its run when #197 lands method: "curated" method: "persona:…" before #197's records exist
review assertion_reviews latest, review_contexts.fingerprint vs current endpoints no review yet never
selection Rank.order/1 signals, or editorial_composition_items + version author not in a composition (ordinary list inclusion is ranking, not an event) never
publication editorial_composition_publications receipt for the item's version unpublished never

Rules the projection must keep:

  • A page view is not an event; a manifest replay is not a nomination. Nothing is written by reading, and :held replays leave nomination.at alone.
  • Row update time is never a publication time. Only committed_at on a receipt is.
  • Public display identities only: actor label, never user_id, email or IP. Never a rationale, evidence URL or title of a claim the viewer may not see.
  • The same struct feeds every surface. The card, the chip, the person page, the opening disclosure and Curation transparency: equal votes, reproducible selection, profiles and version history #203's history read one function, so attribution cannot disagree.

Wireframes

Exemplar card in #examples, disclosure open (the card and its copy exist; the disclosure is new):

┌─────────────────────────────────────────────────────────────────────┐
│ [thumb]  Jeff Bezos · person                                        │
│          cited as an example of  sense 1 · a person who lacks…      │
│          "Owns the Washington Post and, in 2025, …"                 │
│          evidence · The Guardian, 3 March 2025 ↗                    │
│          · nominated by holden · accepted                           │
│          ▸ Why this example is here                                 │
│          ┌───────────────────────────────────────────────────────┐  │
│          │ Nominated   by holden, from manifest first-v1 row 3,  │  │
│          │             on 25 Sep 2026, under sense 1 of coward   │  │
│          │ Model       none involved                             │  │
│          │ Reviewed    accepted by <reviewer> on 26 Sep 2026     │  │
│          │ Shown here  ranked by 1 supporting citation           │  │
│          │ Opening     selected by holden (v2), published 27 Sep │  │
│          │             ↗ How this was curated                    │  │
│          └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

Instance chip: no new UI. The chip's title already carries the reason sentence; its provenance link is the existing /connections/:id for each contributing assertion, one per source, never merged:

[ Korean War ]  title: "WordNet and Wikidata name it under a sense of war."
   ↳ /connections/812 (wordnet)   ↳ /connections/40211 (wikidata)

Opening highlight for an exemplar (PR #202's tile, kind: :exemplar), reasons split by register:

┌──────────────────────────────┐
│ [image]                      │
│ Jeff Bezos                   │
│ as an example of  coward, 1  │
│ ─ source ─ Wikidata · CC0    │
│ ─ cited  ─ "Owns the…" holden│
│ ─ note   ─ "…" selected by   │
│            holden, reviewed  │
│ ▸ Why this is here           │
└──────────────────────────────┘

Person page, cited as group, gains one line per composition that features the claim:

Cited as an example of  coward (sense 1) · by holden · accepted
  featured in the opening of /define/coward since 27 Sep 2026

Contracts

  • C1 Separation. Creating, reviewing or publishing a composition version writes no assertions, assertion_revisions or assertion_reviews row. The existing slice-1 test asserts this; extend it to the exemplar kind.
  • C2 Accepted, not visible. An exemplar item is eligible only on an accepted latest review. Pending, disputed, rejected and withdrawn claims of every subject kind are withheld from Published.current/1 and never counted, whatever Claims.visible/2 says.
  • C3 No inheritance. A new claim revision, a moved sense or a changed review context withholds the item until a new composition review. Approval never transfers.
  • C4 Deletion. Deleting the claim revision nulls the reference and withholds the item for good (required_references, slice-1 R7). Deletion cannot make a withheld item eligible.
  • C5 Both histories. A human nomination later selected by an operator (or, after Persona panel pilot: five versioned editors, fixed quorum and ten-page evaluation #197, by a panel) keeps nomination and selection as separate stages with separate actors and times. Nothing rewrites the nomination.
  • C6 One display identity per arrangement (above).
  • C7 Offline. Published.current/1 and the provenance projection make no provider call and no inference call. A test runs them with the discovery registry empty and Req stubbed to raise.
  • C8 Ordering untouched. Rank.order/1 and the instance chip order do not change. A composition selects; it does not reorder #examples.
  • C10 The bot boundary is closed, and tested closed. Contributions.propose/6 refuses any scope that is not an internal user ({:error, :unauthorized}), and a test asserts this for a bot actor scope. The agent stage of the projection is the input contract Persona panel pilot: five versioned editors, fixed quorum and ten-page evaluation #197's adapter must satisfy: profile and version, model digest and config, run and proposal ids, and a note. This issue defines that stage and reads it as :none or :unknown; it adds no code that accepts a bot nomination.
  • C9 Visibility everywhere. The disclosure, the person page line and the opening apply the same Claims.visible/2 and rights redaction as the card. A hidden adverse nomination leaves no trace in any of them.

Discovery, restated in one table (no adapter)

Seam What exists This issue
Shelf → register /connect prefilled from a corpus candidate card (subject, sense, evidence revision, locator) on a result with an object_id (639 of 1,729 on dev) unchanged, plus: ConnectionLive writes metadata.from_result and provider on the revision when the prefill came from a shelf, so the projection can say found on the Quotes shelf under nomination.origin: :form
Result without an object_id cannot be a subject; minting is #105 step 5, unbuilt out of scope; #193's open decision on transient items stands
Refresh or expiry of a result the claim and the composition item hold registry ids, never result ids already true; a test proves a deleted discovery_results row changes nothing
Instances → shelves never (PageEvidence reads refers_to only) unchanged

The one supported path for the acceptance test. A quotation from a provider with identity_record/1 (PoetryDB, Wikiquote or the public-domain corpus) on a word's Quotes shelf → its card's connect link → ConnectionLive with subject, sense, evidence_revision and evidence_locator prefilled → propose/6 → a reviewer accepts → Build 1's exemplar item with content_revision_id pinning the words → published → Published.current/1. The records reused: the result's object_id (the content object), its source_record_revision_id (the evidence), the sense revision (the meaning). Nothing is copied from discovery_results, and the result row may be deleted by retention at any point after the claim exists without changing anything. A Met artwork follows the existing work item kind and needs no claim. Results with no registry object (Guardian, Bing, Pexels, Unsplash, Openverse, GIPHY, Spotify) are not offered the link today and are refused by Compositions.create_version/3 with {:ineligible, :no_registry_object}; minting them is #105 step 5, not this issue. There is no "deferred" state: an item is eligible or it is not, with the reason.

There is no "candidate boundary" here. When #197 builds a packet, its exemplar candidates come from Examples.exemplars/3 filtered by the eligibility above; that is one function call, not a new object.

Prerequisites and sequencing

What must be on main before each build starts, and what this issue may deliver itself in a coordinated PR when the owner of a seam has not started it. Closure does not wait for any umbrella issue to finish.

Build Needs on main first May deliver here, coordinated Blocked by
1 nothing beyond c6bd882 (#206 merged) the migration, Eligibility, Compositions, the proof test nothing
2 nothing beyond c6bd882 Examples.Provenance, the card disclosure, the person-page line nothing
3 PR #202 (accepted, unmerged) for the Opening structs Opening.Highlight kind: :exemplar; an OpeningReader over Published.current/1 if #156 Phase 2 has not started, with #156 keeping ownership of the view model; a dev-only gate to render it, like #202's ?opening=fixture #202's merge only. Not #194's binding, not #198, not the whole of #156

Nothing here waits for #190, #197, #209 or the PR #210 audit. Every test runs on an isolated MIX_TEST_PARTITION; no build writes to the shared dev database. The #209 storage move gates heavy runs, and there are none here.

This issue closes when Builds 1 and 2 are merged and Build 3 is merged or, if #202 is still unmerged at that point, delivered as a PR stacked on it with its record-backed test green. "Rendered on a real page" means the dev-gated published reader on a page whose composition was published through Publications; the public binding is #194's.

Builds

Build 1: the item kind and the manual proof. The migration, Eligibility, Compositions.create_version/3 accepting %{kind: :exemplar, assertion_revision_id:, meaning:}, Published.current/1 returning it, and one integration test on an isolated partition: propose/6 (user) → Claims.review accept → provision → version → operator review → publish → current/1 shows it → each of rejected, withdrawn, superseded, deleted, context-changed, meaning-mismatched and unaccepted withholds it with its reason → no claim row written. No UI.

Build 2: the provenance projection and the disclosure. Examples.Provenance.of/2; the card disclosure; the person page line; the five legacy test rows render :unknown honestly; leakage tests with a fictional adverse person and a pending non-person work (the #84 fixtures) across card, count, person page and disclosure.

Build 3: the opening mapping. Opening.Highlight gains kind: :exemplar and reads the same projection; lands with #156 Phase 2 once PR #202 merges. Its struct change and a record-backed unit test can ship first behind the existing dev-only gate.

Acceptance

Decisions owed by the owner

  1. Accepted-only for highlights while the public gate is person-only (recommended: yes; C2). The alternative, "visible is enough", would let a pending nomination of a work into the opening.
  2. Changed since review withholds, or labels? In #examples the card stays up with a label (Examples backbone: the record's instances (WordNet, Wikidata P31) as the pre-adjudicated layer under the cited exemplars (#105), one read model, one section, two registers — so /define/war names its 43 wars now and /define/coward can carry an attributed example later #181 R3). In a composition, recommended: withhold (:claim_context_changed), because the operator accepted a specific claim text.
  3. Should the person page say "featured in the opening of…"? Recommended: yes, in Build 2; it is the reverse of selection and costs one query.
  4. Chip provenance: link to /connections/:id per source (recommended, no new UI), or a disclosure like the card's.

Handoffs

Out of scope

Bot or assessor nominations (#197, #101), the illustrates-wide gate (#190), votes and tallies (#189), runs, ballots and selection-v1 (#196/#197/#203), refresh (#198), the page binding (#194), minting subjects from shelf results (#105 step 5), personal or domain panels (#201), and any change to instance ordering. Do not reopen #181 or seed its draft sample claims to complete this issue.

Done means: an accepted exemplar can be a highlight in a real saved composition, is withheld the moment its claim stops being accepted, and every surface that shows an example answers "why is this here" from the same records, saying unknown where the record is silent.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions