You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Link the words their sources already identify: mint the 21,544 QIDs WordNet and Wiktionary name, run the identifier rungs lexicon-wide, and link on visit (after #172) #217
Most words have never been given the chance to link to the thing they mean, and the reason is not the ladder's rules but the registry's population. WordNet and Wiktionary already publish a Wikidata QID on tens of thousands of senses; the ladder's identifier rungs write a refers_to only when that QID's entity exists in the registry, and entities were only ever minted for three scopes (animals, emotions, culture). So a common noun like nepotism or justice carries its source's own identifier and still has no sense link, no concept, and therefore no Quotes, Artworks or Images shelf.
This issue mints the entities the sources already name and links the senses that name them, lexicon-wide once and on demand afterwards. It is the coverage work #172's title promised and did not deliver, done the way the project's rule requires: a match is an identifier a source asserted, never a name. No title matching, no search, no model.
Refs #172 (closed; the mechanism), #214 (the measurement), #165 (people enter through senses and credits; this is the same rule for concepts), #74 (the encyclopedia model), #69 §5 (the ladder), #209 (storage, which gates the bulk pass).
What is measured (devils_dictionary_v2, 2026-09-27, read-only, after #172's sweep)
English lexemes
count
total
1,541,669
with an active sense-backed refers_to today
20,700 (1.34 %)
whose senses carry a source-published QID (WordNet string or array, Wiktionary array)
47,716 WordNet · 7,948 Wiktionary
… realizable by the identifier rungs against today's registry
15,338, of which only 328 are not yet linked
… naming a QID absent from external_identifiers (verified)
40,370, of which 39,717 have no sense link at all
distinct QIDs named by senses
28,616
… absent from the registry
21,544
So the identifier rungs are exhausted against the registry (328 to gain), and the gap is 21,544 entities that nobody has fetched. Minting them realizes links for about 40,000 lexemes, taking sense-backed coverage from 1.3 % to roughly 4 % of English lexemes, at a cost of about 430 batched Wikidata requests (50 items per wbgetentities), inside one hour of the shared 600-an-hour budget.
The 13 probe words: war, coward, grief, love and power have a link; nepotism, family, solitude, justice, bank, pop art, situationship, narcissism do not. The spike in build A says which of the eight name an absent QID.
What exists (read on main @ 2a851e8)
The identifier rungs (Absorb.Linker, rungs 1–3: wiktionary_qid 0.95, wordnet_wikidata 0.90, wordnet_ili 0.85) read a QID or ILI the sense's source publishes and join it to a verified external_identifiers row. They make zero network calls. Linker.run/2 requires a scope; Linker.run_selected/2 runs only these three rungs over an explicit list of up to 500 lexeme, entity or source-record ids, and nothing in the application calls it yet. Each rung also retires links its evidence no longer supports (retire_unsupported/3, The ladder's links survive a source re-materialization #188).
The Wikidata absorb (Absorb.Sources.Wikidata) accepts an explicit bounded QID selection instead of a scope (seed_qids/2, explicit_qids/1), trims each entity to @keep_properties, classifies entity_kind from P31 (taxon, work, person, organization, place, event, else concept), writes external_identifiers as verified, and enrich/2 fetches one QID through Absorb.Clients.Wikidata under the shared wikidata budget. Its scope mode walks P171/P279/P31 parents to a root; the explicit mode must not.
Discovery's admission (Discovery.request/3): a page visit creates a recipe lazily, admits at most one refresh under a database lock, and an Oban worker does the I/O. PageEvidence.entities/2 reads tier 1 (refers_to) then tier 2 (candidates ≥ 0.85). A page whose senses have no link and no candidate never admits a concept-based provider.
Runs are rows in import_runs (task = link, link_selected, absorb, …) with stats.
Design: two passes, one rule
sense ──(source metadata: QID / ILI)──▶ [entity absent?] ──mint (Wikidata absorb, explicit QIDs)──▶ entity + verified identifier
│
└──▶ identifier rungs 1–3 (offline) ──▶ refers_to on the sense ──▶ PageEvidence tier 1 ──▶ shelves
pass A once, lexicon-wide, after #209: all 21,544 absent QIDs, then Linker over every lexeme that names one
pass B on visit, bounded, forever: this page's absent QIDs (≤ 5), then run_selected over this page's lexemes
Neither pass touches rungs 4–5 (title match, disambiguation) or corroboration: those stay scope-only and are #214's to evaluate on a sample.
Pass A: mint what the sources name, then link (once)
Spike, read-only, posted before any write: the 21,544 QIDs by naming source and by the P31 classes Wikidata reports for a 200-QID sample (how many will land as concept, person, place, …), and which of the 8 empty probe words name an absent QID.
Link: Linker.run_selected/2 in slices of 500 over every lexeme that names a now-present QID, as link_selected runs. Expected: ~40,000 lexemes gain a link; 0 candidates; 0 promotions.
When WordLive mounts a page whose senses name a QID with no verified identifier, and no attempt is recorded for that lexeme set, admit one background job for the page, exactly as a discovery refresh is admitted (advisory lock on the target, one in flight, never in the request path):
Wikidata.enrich/2 for at most 5 absent QIDs of this page (the rest wait for the next visit or pass A), each request on the shared wikidata budget; a budget refusal is a deferred attempt with backoff, not an error.
Linker.run_selected(lexeme_ids: page).
Record the attempt (schema below) with its outcome, so a page with nothing to gain is not retried before its cooldown (48 h, the discovery empty-refresh default).
Broadcast on the page's discovery topic so the open page re-reads PageEvidence and its concept-based shelves may now request. Definitions never wait; the first paint is unchanged.
A rule change re-versions nothing here: links are claims, not cache. If a rung's evidence changes, retire_unsupported/3 withdraws on the next run, as today.
Schema
One additive table. Everything else is existing rows: entities, external_identifiers, assertions/assertion_revisions (refers_to, methods as today), import_runs.
Table
Columns
Rules
lexeme_link_attempts
lexeme_id PK → lexemes.object_id ON DELETE CASCADE; attempted_at timestamptz NOT NULL; outcome text NOT NULL ∈ linked | nothing_to_link | deferred_budget | failed; qids_fetched int NOT NULL DEFAULT 0; links_written int NOT NULL DEFAULT 0; run_id → import_runs.id nullable; retry_after timestamptz
one row per lexeme, upserted; retry_after NULL after linked; the visit path reads it before admitting
mix ecto.gen.migration create_lexeme_link_attempts. No column on lexemes changes (enriched_at means something else and stays).
Wireframes
No new surface. The page already says, per shelf, sense-level or For the word "…", not a particular sense, and the About explains an empty. What changes is which words have a concept at all. Before/after screenshots of two probe words that gain a shelf are the visual acceptance.
Contracts
C1 Identifier, never name. Both passes write a link only from a QID or ILI the sense's own source published. No lemma search, no title match, no model.
C2 Bounded and budgeted. Pass A is one run with an explicit QID list and no parent walk. Pass B fetches at most 5 QIDs per admitted visit, under the shared wikidata budget, one job in flight per page, never in the request path.
C4 Re-runnable. A second pass A writes zero entities and zero revisions. A second visit inside the cooldown admits nothing.
C5 Honest attempts.nothing_to_link is recorded and shown in mix dd.link counts; a budget refusal is deferred_budget with retry_after, never silently dropped.
C6 Reviews stand. A link a reviewer rejected is not rewritten by either pass (existing retire_unsupported and write-path rules).
Builds
Spike (read-only, this issue's first comment): the counts above by source and P31 class on a sample; the probe words; the expected request count.
Pass B: the table, the admission, the job, the broadcast, tests with the Wikidata client stubbed (Req.Test), a LiveView test that a visit to a linkless page admits one job and a second visit admits none.
Acceptance
Spike posted with the numbers by source and P31 class, and the probe words that will gain a concept.
Pass B: a visit to a linkless page admits exactly one job; the job fetches ≤ 5 QIDs, links, records the attempt and broadcasts; the open page shows its new shelf without a reload; a second visit inside 48 h admits nothing; a budget refusal defers with retry_after.
Neither pass writes a lexeme_entity_candidate, a promotion, or a link without a source-published identifier.
mix dd.link prints the attempt outcomes; mix precommit green.
Rungs 4–5 lexicon-wide, corroboration outside scopes, the assessor (#101), which sense a shelf should follow (#214, then #101), the quote corpus (#213), any UI change.
Purpose
Most words have never been given the chance to link to the thing they mean, and the reason is not the ladder's rules but the registry's population. WordNet and Wiktionary already publish a Wikidata QID on tens of thousands of senses; the ladder's identifier rungs write a
refers_toonly when that QID's entity exists in the registry, and entities were only ever minted for three scopes (animals, emotions, culture). So a common noun like nepotism or justice carries its source's own identifier and still has no sense link, no concept, and therefore no Quotes, Artworks or Images shelf.This issue mints the entities the sources already name and links the senses that name them, lexicon-wide once and on demand afterwards. It is the coverage work #172's title promised and did not deliver, done the way the project's rule requires: a match is an identifier a source asserted, never a name. No title matching, no search, no model.
Refs #172 (closed; the mechanism), #214 (the measurement), #165 (people enter through senses and credits; this is the same rule for concepts), #74 (the encyclopedia model), #69 §5 (the ladder), #209 (storage, which gates the bulk pass).
What is measured (
devils_dictionary_v2, 2026-09-27, read-only, after #172's sweep)refers_totodayexternal_identifiers(verified)So the identifier rungs are exhausted against the registry (328 to gain), and the gap is 21,544 entities that nobody has fetched. Minting them realizes links for about 40,000 lexemes, taking sense-backed coverage from 1.3 % to roughly 4 % of English lexemes, at a cost of about 430 batched Wikidata requests (50 items per
wbgetentities), inside one hour of the shared 600-an-hour budget.The 13 probe words: war, coward, grief, love and power have a link; nepotism, family, solitude, justice, bank, pop art, situationship, narcissism do not. The spike in build A says which of the eight name an absent QID.
What exists (read on
main@2a851e8)Absorb.Linker, rungs 1–3:wiktionary_qid0.95,wordnet_wikidata0.90,wordnet_ili0.85) read a QID or ILI the sense's source publishes and join it to a verifiedexternal_identifiersrow. They make zero network calls.Linker.run/2requires a scope;Linker.run_selected/2runs only these three rungs over an explicit list of up to 500 lexeme, entity or source-record ids, and nothing in the application calls it yet. Each rung also retires links its evidence no longer supports (retire_unsupported/3, The ladder's links survive a source re-materialization #188).Absorb.Sources.Wikidata) accepts an explicit bounded QID selection instead of a scope (seed_qids/2,explicit_qids/1), trims each entity to@keep_properties, classifiesentity_kindfrom P31 (taxon, work, person, organization, place, event, else concept), writesexternal_identifiersas verified, andenrich/2fetches one QID throughAbsorb.Clients.Wikidataunder the sharedwikidatabudget. Its scope mode walks P171/P279/P31 parents to a root; the explicit mode must not.Discovery.request/3): a page visit creates a recipe lazily, admits at most one refresh under a database lock, and an Oban worker does the I/O.PageEvidence.entities/2reads tier 1 (refers_to) then tier 2 (candidates ≥ 0.85). A page whose senses have no link and no candidate never admits a concept-based provider.import_runs(task=link,link_selected,absorb, …) withstats.Design: two passes, one rule
Neither pass touches rungs 4–5 (title match, disambiguation) or corroboration: those stay scope-only and are #214's to evaluate on a sample.
Pass A: mint what the sources name, then link (once)
concept,person,place, …), and which of the 8 empty probe words name an absent QID.absorbrun with the QID list recorded on the run. Persons minted this way follow Why Voltaire is not in the registry: people enter through senses, not through credits — 6,674 persons are word-referents; decide on-demand minting, a people seed, or both #165's rule (they enter because a sense names them); they render as any entity does.Linker.run_selected/2in slices of 500 over every lexeme that names a now-present QID, aslink_selectedruns. Expected: ~40,000 lexemes gain a link; 0 candidates; 0 promotions.Pass B: link on visit (the safety net)
When
WordLivemounts a page whose senses name a QID with no verified identifier, and no attempt is recorded for that lexeme set, admit one background job for the page, exactly as a discovery refresh is admitted (advisory lock on the target, one in flight, never in the request path):Wikidata.enrich/2for at most 5 absent QIDs of this page (the rest wait for the next visit or pass A), each request on the sharedwikidatabudget; a budget refusal is a deferred attempt with backoff, not an error.Linker.run_selected(lexeme_ids: page).PageEvidenceand its concept-based shelves may now request. Definitions never wait; the first paint is unchanged.A rule change re-versions nothing here: links are claims, not cache. If a rung's evidence changes,
retire_unsupported/3withdraws on the next run, as today.Schema
One additive table. Everything else is existing rows:
entities,external_identifiers,assertions/assertion_revisions(refers_to, methods as today),import_runs.lexeme_link_attemptslexeme_idPK →lexemes.object_idON DELETE CASCADE;attempted_attimestamptz NOT NULL;outcometext NOT NULL ∈linked | nothing_to_link | deferred_budget | failed;qids_fetchedint NOT NULL DEFAULT 0;links_writtenint NOT NULL DEFAULT 0;run_id→import_runs.idnullable;retry_aftertimestamptzretry_afterNULL afterlinked; the visit path reads it before admittingmix ecto.gen.migration create_lexeme_link_attempts. No column onlexemeschanges (enriched_atmeans something else and stays).Wireframes
No new surface. The page already says, per shelf, sense-level or For the word "…", not a particular sense, and the About explains an empty. What changes is which words have a concept at all. Before/after screenshots of two probe words that gain a shelf are the visual acceptance.
Contracts
wikidatabudget, one job in flight per page, never in the request path.metadata["source_record_id"]provenance as today and are not source outputs (The ladder's links survive a source re-materialization #188).nothing_to_linkis recorded and shown inmix dd.linkcounts; a budget refusal isdeferred_budgetwithretry_after, never silently dropped.retire_unsupportedand write-path rules).Builds
docs/spikes/<date>-issue-<n>/like Sense-level shelves show on 1 % of words: the concept hop, the word-level tier from corroborated candidates, and promotion — so Quotes, Artworks and Images appear on most pages without a name match #172's sweep.Req.Test), a LiveView test that a visit to a linkless page admits one job and a second visit admits none.Acceptance
refers_tolinks written ≈ 40,000 lexemes; a repeat pass writes nothing; Sense-level shelf coverage and match quality: the measurement instrument (denominator, fixed sample, precision reviews) after #172 and #217 #214's sample and the 13 probes re-measured before and after; the Wikidata ledger shows the request count.retry_after.lexeme_entity_candidate, a promotion, or a link without a source-published identifier.mix dd.linkprints the attempt outcomes;mix precommitgreen.Decisions owed by the owner
personQIDs until Why Voltaire is not in the registry: people enter through senses, not through credits — 6,674 persons are word-referents; decide on-demand minting, a people seed, or both #165 is decided.Out of scope
Rungs 4–5 lexicon-wide, corroboration outside scopes, the assessor (#101), which sense a shelf should follow (#214, then #101), the quote corpus (#213), any UI change.