{"path":"research/what-counts-as-evidence/corpus-map-code.md","content":"# How evidence is represented and computed in the Deliberus code (as of 2026-09-26)\n\nScope: this reads the code, not the docs (docstrings count). Every claim below cites\n`file:line` in the repository. Numerical examples were computed by calling\nthe shipped functions directly (`deliberus.graph.qbaf`, `deliberus.graph.badge_inputs`)\nin this session, not re-derived by hand.\n\nSurvey: `grep -rn \"evidence\" deliberus --include=*.py | wc -l` gives **325** hits.\nThey concentrate in `evidence_routing.py` (65), `api.py` (39), `truth_graph.py` (36),\n`extraction/schemes.py` (20), `decomposition_judgement.py` (16), `graph/qbaf.py` (13),\n`evidential_weight.py` (13), `cq_fallbacks.py` (12), `slicing_stability.py` (11),\n`graph/store.py` (11), `staleness.py` (10). Most hits are prose in docstrings and\nprompts. The load-bearing code is a small core: `graph/badge_inputs.py`,\n`graph/qbaf.py`, `evidential_weight.py`, `support_semantics.py`, and the three\ncontribution endpoints in `api.py`.\n\n**The one-paragraph answer.** In code, \"evidence\" is not a type of thing. It is a\n**position in the graph**: any `Claim` node that sits at the tail of a `SUPPORTS` or\n`ATTACKS` edge. Evidence moves a claim's strength along two channels and only two.\n(a) The **direct channel**: an incoming scheme-bearing edge contributes only through\nthe answers to that edge's critical questions (CQs). Each answer is itself a claim\nthat `SUPPORTS` a CQ polarity claim, and the code **counts answers** (0.15 each,\ncapped at 0.45) rather than weighing them. (b) The **decomposition channel**: a\nparent reads each `DECOMPOSES_INTO` child either at the child's own recursive badge\nor, if the child is unexamined, at a crude prior made from counts of incoming\nsupports and attacks (0.3 each) and user votes (0.15 each). Every property a\nscientist would call evidential quality (methodology tier, sample size, effect size,\nconfidence interval, study count, heterogeneity, `evidence_type`, `epistemic_status`,\nthe per-edge extraction `strength`) is stored, and **none of it reaches the badge**.\n\n---\n\n## 1. The data model of evidence\n\n### 1.1 One node type: `Claim`, with evidence metadata as optional fields\n\n`ClaimBase` (`deliberus/extraction/models.py:152-176`) is the shared contract for every\nclaim-producing process. The evidence-relevant fields:\n\n| Field | Type / allowed values | Line |\n|---|---|---|\n| `type` | `ClaimTypeLit` = `empirical`, `normative`, `definitional`, `value_premise` | `models.py:18`, `:163` |\n| `epistemic_status` | `EpistemicStatusLit` = `observed`, `causal_hypothesis`, `prediction`, `counterfactual`, `feasibility`, `normative_assertion` | `models.py:19`, `:164` |\n| `evidence_type` | `EvidenceTypeLit` = `citation`, `quantitative`, `anecdotal`, `none` | `models.py:20`, `:165` |\n| `source_span` | free string, \"Exact quote from source text\" | `models.py:166` |\n| `attributed_to` | free string or None | `models.py:167` |\n| `methodology_tier` | `MethodologyTierLit` = `meta_analysis`, `systematic_review`, `rct`, `quasi_experimental`, `observational`, `case_study`, `anecdotal`, `expert_opinion`, or None | `models.py:23-26`, `:169` |\n| `effect_size` | float or None | `models.py:170` |\n| `effect_size_type` | free string (description suggests `cohens_d`, `odds_ratio`, `risk_ratio`, `correlation`, `percentage`) | `models.py:171` |\n| `confidence_interval_lower/upper` | float or None | `models.py:172-173` |\n| `sample_size` | int or None (\"Total sample size across studies/observations\") | `models.py:174` |\n| `study_count` | int or None | `models.py:175` |\n| `heterogeneity` | float or None (\"such as I²\") | `models.py:176` |\n\nSubclasses: `AtomicClaim` adds `decomposition_notes`, `confidence` (computed, \"Not\nLLM-generated\"), `claim_kind` (`models.py:181-185`). `ImplicitPremise` adds\n`connects_from` / `connects_to` (`models.py:291-300`).\n\n`ClaimKind` (`models.py:81-86`, Literal at `:98`): `extracted`, `cq_positive`,\n`cq_negative`, `implicit`, `human`. Two more kinds are written by code outside that\nenum: `minted_report` (`reported_speech.py:62`) and, in effect, cross-source premise\nproposals written as `implicit` with `confirmed: false` (`cross_source_premises.py:150-160`).\n\nParallel `Enum` classes exist for non-Pydantic code (`models.py:31-51`) plus two that\nconcern edges: `EdgeProvenance` = `extraction`, `auto_similar`, `auto_connect`, `human`\n(`models.py:68-72`) and `TrustTier` = `high`, `medium`, `low` (`models.py:75-78`).\nThe live graph also carries provenances **outside** `EdgeProvenance`: `split`\n(`support_semantics.py:144`) and `cross_source_premise` (`cross_source_premises.py:171`).\n\n### 1.2 `compute_confidence`: a lookup table, not a judge\n\n`models.py:265-286`:\n\n```\nCONFIDENCE_BASE = observed 0.85, causal_hypothesis 0.55, prediction 0.40,\n                  counterfactual 0.45, feasibility 0.50, normative_assertion 0.70\nEVIDENCE_BONUS  = citation +0.10, quantitative +0.05, anecdotal 0.0, none -0.10\nconfidence = clamp(base + bonus, 0, 1), rounded to 2 places\n```\n\nIt is called after focused extraction (`focused.py:143`), for implicit premises\n(`implicit_premises.py:111`) and for user decompositions (`api.py` decompose path,\naround line 2575). The result is stored as `c.confidence` (`graph/store.py:167`,\n`:484`). **`c.confidence` is read by no strength computation.** It reaches the\nsynthesis prompt through `truth_graph.py` (queried at `:522`, `:586`, `:657`) and\ndisplay surfaces. Note that `normative_assertion` (0.70) outranks `causal_hypothesis`\n(0.55): the table encodes how confidently a claim is *asserted*, not how well it is\n*evidenced*.\n\n### 1.3 `METHODOLOGY_BASE_WEIGHT`\n\n`models.py:253-262`: `meta_analysis 0.70`, `systematic_review 0.68`, `rct 0.65`,\n`quasi_experimental 0.55`, `observational 0.45`, `case_study 0.38`, `anecdotal 0.32`,\n`expert_opinion 0.30`. The comment (`:248-251`) says it is the \"QEM base weight for\nevidence edges\", and QEM's gravity makes it the prior evidence shifts away from.\nSection 6 shows that no production path ever delivers a tier to this table.\n\n### 1.4 Edges\n\nEdge types that carry claims into claims: `SUPPORTS`, `ATTACKS`, `QUALIFIES`,\n`REFRAMES`, `DECOMPOSES_INTO`, `DEFINES` (`graph/store.py:537-539`\n`VALID_RELATIONSHIP_TYPES`). There are also non-propagating relations written\nelsewhere: `REPORTS` (reported speech), `CLASSIFIES_TERMINUS`, `ARGUES_REQUIREMENT`,\n`SIMILAR_TO`, `ANSWERS` (polarity claim → Question), `ABOUT` (Question → claim),\n`EXTRACTED_FROM` (claim → Source), `USES_CONCEPT`, and user votes `AGREES_WITH` /\n`DISAGREES_WITH`.\n\nEdge properties set by `create_relationship_edge` (`store.py:423-450`) and\n`store_relationships` (`store.py:542-624`): `strength`, `provenance`, `trust_tier`,\n`confirmed`, `explanation`, `reasoning`, `scheme`, `schemes` (JSON), `scheme_confidence`\n(JSON), `scheme_cluster`, `scheme_note` (the `does_not_fit` reason). The correction\npipeline adds `cq_params` (`correction_pipeline.py:163-178`). Lifecycle properties:\n`superseded_by`, `superseded_reason` (`store.py:251-259`, `:278-290`). Decomposition edges\nadditionally may carry `support_interpretation`, `requirement_claim`,\n`requirement_claim_at`, `coverage` (`badge_inputs.py:77`, `api.py:2720-2726`).\n**No edge carries a timestamp.**\n\n`Relationship` (the LLM response model for extraction pass 3a, `models.py:196-216`)\nasks for a `strength` in [0,1] plus `schemes`, `scheme_confidence`, `no_fit_reason`.\nThe `_confession_carries_a_reason` validator fills a missing reason with\n`\"(no reason given)\"` (`:209-216`).\n\n### 1.5 Question nodes and polarity claims (the CQ machinery)\n\n`CQInstance` (`models.py:310-320`): `template_id`, `scheme`, `question_text`,\n`positive_text`, `negative_text`, `params`, and generated IDs. On storage\n(`store.py:~690-762`) each CQ becomes:\n\n- a `Question` node with `parent_from` / `parent_to` (the edge it questions) and\n  `cq_template_id`;\n- two polarity claims, `cq_positive` and `cq_negative`, created with\n  `claim_type=\"empirical\"`, `evidence_type=\"none\"`, `confidence=0.5`,\n  `attributed_to=\"deliberus_system\"` (`store.py:735-740`). `epistemic_status` is not\n  passed, so it takes `create_claim_node`'s default `\"observed\"` (`store.py:141`);\n- `ANSWERS` edges from both polarity claims to the Question;\n- a scheme-bearing `SUPPORTS` edge from `cq_positive` and an `ATTACKS` edge from\n  `cq_negative` **to the target claim**, strength 0.5, provenance `extraction`\n  (`store.py:752-756`).\n\nSince 2026-09-21 a question can also stay **latent** (rendered from\nscheme + template + `cq_params` on read, never stored) until someone answers it, at\nwhich point `latent_questions.materialise` (`latent_questions.py:136-~200`) creates\nthe same three nodes.\n\n### 1.6 Who sets each field\n\n| Field | Set by | How |\n|---|---|---|\n| `type`, `epistemic_status`, `evidence_type` | LLM, Pass 2b DECONTEXT prompt | Listed as bare enums with no definitions (`focused.py:84-86`) |\n| `methodology_tier` + 7 quantitative fields | LLM, **only** via the Pydantic field description | No prompt text asks for them (`grep methodology deliberus/extraction/*.py` finds only `models.py`) |\n| `confidence` | code | `compute_confidence` |\n| `evidence_type` on user evidence | hard-coded `\"anecdotal\"` | `api.py:2773` (add-evidence), `:2858` (answer-cq) |\n| `epistemic_status` on user evidence | hard-coded `\"observed\"` | `api.py:2772`, `:2857` |\n| `type` on user evidence | hard-coded `\"empirical\"` | same |\n| `evidence_type` on decomposition children kept from a split | hard-coded `\"none\"` | `api.py:2556` |\n| `evidence_type` on implicit premises | LLM, unprompted (prompt lists type, epistemic_status only: `implicit_premises.py:39-40`) |\n| `epistemic_status` on cross-source premises | hard-coded `\"unstated\"`, **outside the Literal** | `cross_source_premises.py:155` |\n| `epistemic_status` on minted reported content | hard-coded `\"observed\"` with `evidence_type=\"none\"` | `reported_speech.py:378-379` |\n| `recorded_at` | code, at first write | `store.py:170`, `:488` (COALESCE: first write wins) |\n| `valid_at` | deterministic tier-A date extraction inherited from the source | `temporal_extraction.py:104-120` |\n| `invalid_at` | human ratification only | `store.py:305-343` |\n| edge `strength` | LLM (extraction), 0.7 default (human), 0.5 (polarity, proposals), 0.8 (user decomposition) | `store.py:548`, `:424`, `:754`, `api.py:2587` |\n\nThe extraction prompt gives the model **no definitions** for `evidence_type` or\n`epistemic_status`. It says only \"reasoning: Think about what type this is, what its\nepistemic basis is\" (`focused.py:81`) followed by the bare value lists (`:85-86`).\nSelf-evaluation later checks `epistemic_status_correct` only (`self_eval.py:41`,\n`:114`). No pass checks `evidence_type`.\n\n---\n\n## 2. Every path by which evidence enters the graph\n\n### 2.1 Extraction (the bulk of the graph)\n\n- **Pass 1 scout** (`extraction/scout.py:17`) defines an argument structure as \"a\n  conclusion ... together with the premises, evidence, or reasoning that support or\n  attack it\". Evidence is not separated from premises here.\n- **Pass 2 focused** (`focused.py:92-148`): decompose into raw atoms, then\n  decontextualize and classify each (sets `type`, `epistemic_status`,\n  `evidence_type`, optionally the quantitative fields).\n- **Pass 2c implicit premises** (`implicit_premises.py:22-50`): at most 1-2 per\n  argument, \"Prefer fewer\", necessity required. Runs on the Temporal path only (CLAUDE.md\n  reports `/extract/stream` never calls it; not re-verified here).\n- **Pass 3a relationships**: LLM emits `from_claim`, `to_claim`, `relationship`,\n  `strength`, `schemes`, and `no_fit_reason` (`models.py:196-207`). Stored by\n  `store_relationships` (`store.py:542-624`) with `provenance=\"extraction\"`,\n  `trust_tier=\"high\"`, `confirmed=True` by default. An argumentative edge with no\n  scheme is stored as `does_not_fit` (`store.py:580-587`).\n- **CQ generation** then writes Question + polarity claims per scheme-bearing edge\n  (section 1.5).\n- Claims are stored by `store_claims` (`store.py:461-534`), which writes the\n  quantitative fields only when non-null (`:521-529`).\n\n### 2.2 `POST /claims/{claim_id}/add-evidence` (`api.py:2744-2800`)\n\nRequest: `EvidenceRequest(text ≤ 5000, polarity: str)` (`api.py:2463-2466`).\nBehaviour:\n\n1. Creates a new claim: `claim_type=\"empirical\"`, `claim_kind=\"human\"`,\n   `epistemic_status=\"observed\"`, `evidence_type=\"anecdotal\"`, `confidence=0.5`,\n   `attributed_to=<user email>`, `argument_label=\"user_evidence\"` (`api.py:2769-2775`). *(Since\n   2026-10-03 the person's name, with the users-table id as `contributor_id`:\n   `deliberus/contributor.py`.)*\n2. `rel_type = \"SUPPORTS\" if req.polarity == \"supports\" else \"ATTACKS\"`\n   (`api.py:2766`). **Any polarity string other than \"supports\" becomes an attack.**\n3. `create_relationship_edge(graph, evidence_id, claim_id, rel_type)` with defaults:\n   strength 0.7, provenance `human`, trust `high`, `confirmed=True`, **scheme `\"\"`**\n   (`store.py:423-426`).\n4. Embeds the claim, then calls `run_correction_pipeline` in\n   `try: ... except Exception: pass  # Non-critical` (`api.py:2786-2790`). This is a\n   silent swallow of the kind CLAUDE.md's hard-won gotchas forbid.\n\n`run_correction_pipeline` (`correction_pipeline.py:124-220`) asks an LLM for schemes and\nslot params. **If no scheme is returned it returns 0 before writing any scheme**\n(`:151-152`), and the edge keeps `scheme=\"\"`. The badge query requires\n`r.scheme IS NOT NULL AND r.scheme <> ''` (`badge_inputs.py:38`), so such an edge is\ninvisible to the direct channel. The scheme names the LLM returns are not validated\nagainst the enum, and `scheme_cluster` is written as `\"\"` (`:174`).\n\nThe user never states *what kind* of evidence they are adding (study, anecdote,\nstatistic, testimony). The system always labels it anecdotal and observed.\n\n### 2.3 `POST /claims/{claim_id}/answer-cq` (`api.py:2803-2891`)\n\nRequest: `AnswerCQRequest(text, polarity: 'positive'|'negative', question_id)`\n(`api.py:2469-2473`).\n\n1. If `question_id` is a latent handle, `materialise` mints the Question and its\n   polarity pair first (`api.py:2819-2826`).\n2. Finds the polarity claim for `positive` → `cq_positive`, anything else →\n   `cq_negative` (`:2836`).\n3. Creates an answer claim: `empirical`, `observed`, `evidence_type=\"anecdotal\"`,\n   `confidence=0.6`, `argument_label=\"cq_answer\"` (`:2851-2860`).\n4. `SUPPORTS` edge answer → polarity claim (`:2863`).\n5. Runs the correction pipeline on that edge, again inside a silent `except: pass`\n   (`:2871-2875`).\n\nThis is the **only** contribution that moves a direct-channel badge (section 3).\nAnswering \"negative\" means *supporting the negative polarity claim*: there is no path\nby which an answer *attacks* a polarity claim, and `ATTACKS` into a polarity claim are\nnot counted by the badge query at all (`badge_inputs.py:163-170` counts only\n`SUPPORTS`).\n\n### 2.4 `POST /claims/{claim_id}/decompose` (`api.py:2482-2641`)\n\nCreates `claim_kind=\"human\"` children, from either the person's own words (run through\n`focused.extract_claims`) or kept parts of a machine-proposed split. Each child is\nlinked by `DECOMPOSES_INTO` at strength 0.8 (`api.py:2587`) with default provenance\n`human` and `confirmed=True`. Kept parts get `evidence_type=\"none\"` and inherit the\nparent's `epistemic_status` (`:2553-2557`). Weighing detection and the correction pipeline\nrun on the children.\n\n### 2.5 `POST /claims/{claim_id}/parts/{part_id}/requirement` (`api.py:2657-2741`)\n\nMints a `normative` / `normative_assertion` / `evidence_type=\"none\"` claim with the\ntext `\"<W>\" requires \"<X>\": <reasoning>`, links it by `ARGUES_REQUIREMENT` to the part,\nand stamps `d.requirement_claim` on the decomposition edge. That claim's own badge then\nsets how strongly the part caps the whole (section 3.4). It is evidence *about the\nstructure of an argument*, not about the world.\n\n### 2.6 `POST /claims/{claim_id}/vote` (`api.py:4044-4091`)\n\nWrites `AGREES_WITH` / `DISAGREES_WITH` from a `User` node, one per user per claim,\nwith no timestamp. **Votes do not enter the direct channel.** They enter only the\ndecomposition channel's count prior for an **unexamined child** (0.15 per net vote,\n`badge_inputs.py:73-74`, `:89`) and the same signal in the hinge (`hinge.py:37`,\n`:44-51`). So a vote can move a *parent's* badge while leaving the voted claim's own\nbadge untouched.\n\n### 2.7 Reported speech (`deliberus/reported_speech.py`)\n\n`process_extraction_reported_speech` (`:417-452`) screens claims with the regex pre-filter\n`looks_reported` (`:152-181`), asks an LLM to split report from content, validates\n(`:192-229`), and `mint_reported_content` (`:342-~410`) writes a\n`claim_kind=\"minted_report\"` claim (`type='empirical'`, `epistemic_status='observed'`,\n`evidence_type='none'`, `attributed_to=<reported party>`, `minted_from`, `minted_by_source`)\nplus a `REPORTS` edge. `REPORTS` is named in the badge query's exclusion list\n(`badge_inputs.py:47`), so it never propagates. The minted node is written with a raw\n`MERGE ... SET` that does **not** set `recorded_at` (`reported_speech.py:370-393`).\n\n### 2.8 Auto-connect and the keeper (cross-source links)\n\n`auto_connect` (`extraction/auto_connect.py:242-549`) generates candidate pairs from\ncosine similarity (threshold 0.65, `:146`), concept routes and structural kinship\n(`:254`, `:273`). With the keeper on, each pair first gets a seven-key verdict\n(`keeper.py:57-78`): `same` → `SIMILAR_TO` with `merge_candidate=true`, no propositional\nedge (`auto_connect.py:434-460`); `contains`/`overlaps` → `SIMILAR_TO` with\n`separability_proposal` (`:474-480`) and then still classified; `opposes` → attack\ncandidate; `unrelated` / `cant_tell` → stop. Surviving relations are written with\n`provenance='auto_connect'`, `trust_tier='medium'`, **`confirmed=false`**, and no\nscheme (`auto_connect.py:514-538`). The `PairClassification` model puts `related` as a\nrequired gate before the relationship menu (`auto_connect.py:70-79`). A stance guard\nmay attach `stance_caution` when the two sources are on opposed sides (`:500-511`).\n\n### 2.9 Cross-source premises (`extraction/cross_source_premises.py`)\n\n`store_premise_proposals` (`:131-179`) writes implicit claims with\n`confirmed: false`, `provenance='cross_source_premise'`, `epistemic_status: 'unstated'`,\nand `SUPPORTS`/`ATTACKS` edges at strength 0.5, `confirmed=false`, no scheme. It uses a raw\n`CREATE`, so no `recorded_at`.\n\n### 2.10 Staleness and validity (time as counter-evidence)\n\n- `POST /graph/staleness/sweep` (`api.py:2199-2210`) runs `staleness.run_sweep`, which\n  only **flags** claims (`staleness.py:109-139`).\n- `POST /claims/{id}/validity` (`api.py:2220-2255`) calls `set_validity_with_ground`\n  (`store.py:305-343`): requires a reason, mints a \"Validity verdict: lapsed\" claim that\n  `QUALIFIES` the target, and sets `c.invalid_at`, `c.invalid_reason`,\n  `c.validity_verdict_claim`. `restore=True` clears it (`store.py:346-354`).\n- `POST /edges/supersede` (`api.py:2145-2183`): supersede, restore, or retarget an edge\n  onto a decomposition part (`store.py:357-399`).\n\n### 2.11 Machine splits (`proposed_split.py`, `extraction/decompose_claim.py`)\n\nA machine split writes parts with `provenance='split'` and `confirmed` unset\n(`proposed_split.py:27-31`), and the coverage verdict on the edge (`:70`, `:216-226`).\nA part flagged `invented` presumes no cap (`support_semantics.py:209-210`).\n\n---\n\n## 3. The exact arithmetic\n\n### 3.1 Constants in one place\n\n| Constant | Value | Where |\n|---|---|---|\n| `NEUTRAL_POINT` | 0.5 | `qbaf.py:32`, `support_semantics.py:62` |\n| `EVIDENCE_SHIFT` (per answer on a polarity claim) | 0.15 | `badge_inputs.py:17` |\n| `EVIDENCE_SHIFT_CAP` | 0.45 (so a polarity value saturates at 0.95) | `badge_inputs.py:19` |\n| `SUBCLAIM_EVIDENCE_WEIGHT` (per incoming support/attack on an unexamined child) | 0.3 | `badge_inputs.py:22`, `hinge.py:36` |\n| `SUBCLAIM_VOTE_WEIGHT` (per net vote on an unexamined child) | 0.15 | `badge_inputs.py:23`, `hinge.py:37` |\n| `CORROBORATIVE_WEIGHT` (per decomposition child offset) | 0.3 | `support_semantics.py:65` |\n| `NEUTRAL_BAND_LOW / HIGH` | 0.48 / 0.52 | `qbaf.py:51-52` |\n| Display bands | red <0.2, amber 0.2-0.48, neutral 0.48-0.52, blue 0.52-0.8, green ≥0.8, gray = None | `qbaf.py:55-77` |\n| Edge-badge bands (per edge detail) | green ≥0.8, blue ≥0.5, amber ≥0.2, red <0.2 (no neutral band) | `qbaf.py:178-185` |\n| Requirement weights | tagged 1.0, presumed part 0.5, standard question 0.25 (unbuilt), proposal 0.0 | `support_semantics.py:122-125` |\n| `MAX_BADGE_DEPTH` | 8 | `badge_inputs.py:227` |\n| Staleness horizons | quantitative 3y, citation 4y, anecdotal 2y, meta_analysis 5y | `staleness.py:35-40` |\n\nA duplicate copy of 0.15 / 0.45 is hard-coded in `truth_graph.py:817-818`, outside\n`badge_inputs` (the drift the extraction comment in `badge_inputs.py:4-7` warns against).\n\n### 3.2 The QEM core (`qbaf.py:90-117`)\n\n```\nh(x) = max(x,0)^2 / (1 + max(x,0)^2)\nqem_strength(w, E) = w + (1-w)·h(E)   if E > 0\n                   = w - w·h(-E)      if E < 0\n                   = w                if E = 0\n```\n\n### 3.3 Direct channel: from an answer to a claim badge\n\nStep 1, polarity claim value (`badge_inputs.py:160-179`). For each Question on the edge\nbetween the claim and the other end, count the claims with a live `SUPPORTS` into each\npolarity claim (edge not superseded, source not `invalid_at`):\n\n```\npos = 0.5 + min(n_pos_answers × 0.15, 0.45)\nneg = 0.5 + min(n_neg_answers × 0.15, 0.45)\n```\n\nAn edge with no Questions gets one anonymous agnostic entry (0.5, 0.5) (`:181-182`).\nClaim-level questions (the weighing shape, `parent_from == parent_to == claim`) are\nmapped with an orientation so one question enters two mirrored edges (`:102-150`).\n\nStep 2, edge strength (`qbaf.py:131-162`): `E_edge = Σ (pos − neg)` over the edge's\nCQs, then `qem_strength(0.5, E_edge)`. (A `methodology_tier` would replace the 0.5 base,\nbut `edges_for_badge` never passes one: `badge_inputs.py:297-306`.)\n\nStep 3, claim energy (`qbaf.py:280-291`): for each incoming edge,\n`contribution = edge_strength − 0.5`, added for `SUPPORTS`, subtracted for `ATTACKS`,\nignored for `QUALIFIES` / `REFRAMES` / `DEFINES` (`polarity_of`, `qbaf.py:80-87`).\n`polarity` is mandatory, and its absence raises (`qbaf.py:263-268`).\n\nStep 4, weight (`evidential_weight.py:85-116`): the number of inputs (edges plus\nchildren) whose strength differs from 0.5 by more than 1e-9. If weight is 0 the badge is\n`UNEXAMINED` / gray / `strength: None` (`qbaf.py:319-331`), or `NO DATA` with strength 0.5\nwhen there are no edges or children at all (`qbaf.py:273-275`). `Reading` enforces at\nconstruction that zero weight has no direction (`evidential_weight.py:55-67`).\n\nStep 5, claim strength: `qem_strength(0.5, E_claim)`, then `min(·, ceiling)` if a\ndecomposition cap applies (`qbaf.py:333-337`). Term and colour from `display_band`; the\n`neutral` band reads `UNSETTLED` (`qbaf.py:363-369`). The scheme-flavoured headline\nterm (for example `CREDIBLE`) is used only when the direct channel's energy is at least\nthe decomposition channel's (`qbaf.py:344-357`).\n\n**Worked example (computed).** Claim C has one incoming `SUPPORTS` edge from E with\nscheme `expert_opinion` (six CQs).\n\n| State | Edge strength | C strength | C badge |\n|---|---|---|---|\n| No answers | 0.500 | None | UNEXAMINED (gray) |\n| One positive answer on one CQ | 0.511 | 0.500 | UNSETTLED |\n| Three positive answers on one CQ (saturated) | 0.584 | 0.504 | UNSETTLED |\n| Two CQs saturated | 0.724 | 0.524 | SUPPORTED (blue) |\n| All six CQs saturated (18 answers) | 0.940 | 0.581 | CREDIBLE (blue) |\n| Six CQs saturated on each of 4 identical edges | 0.940 each | 0.813 | CREDIBLE (green) |\n| Same edge, one supporting and one attacking, both saturated | 0.940 each | 0.500 | UNSETTLED, weight 2 |\n\nThree consequences follow directly.\n\n1. **Double damping.** QEM is applied twice: once at the edge, once at the claim, both\n   from 0.5. Eighteen confirming answers move C to 0.581. A single edge can never reach\n   green; four fully answered supporting edges are needed.\n2. **A one-CQ scheme can never leave the neutral band on one edge.** `lack_of_evidence`\n   has exactly one CQ (`schemes.py:457-462`), and `full_slippery_slope`, `opposites`\n   one each. Saturated, that edge gives 0.584 and the claim 0.504, which is `UNSETTLED`\n   forever. The comment at `qbaf.py:47-50` (\"one decisively-answered supporting edge\n   yields 0.524\") is true only for a scheme with at least two CQs, each answered three\n   times.\n3. **Answers are counted, not weighed.** The answer claim's content, its own badge,\n   attacks on it, its `evidence_type`, who wrote it and whether it duplicates another\n   answer do not matter. Only `COUNT(evidence)` matters (`badge_inputs.py:168`).\n   Three one-word answers from one account saturate a CQ exactly as three independent\n   RCTs would.\n\nThe question-ordering price (`question_ordering.py:27-105`) clamps a question to\n`(1.0, 0.0)` or `(0.0, 1.0)`. **Those values are unreachable through the counting rule**,\nwhich can only raise `pos` or `neg` from 0.5 toward 0.95 and never lower either. So\n`worth_asking` is priced on a hypothetical that no sequence of answers produces. (With\nthe clamp, one single-CQ edge gives C = 0.529, which crosses into blue; the reachable\nmaximum is 0.504, which does not.)\n\n### 3.4 Decomposition channel\n\nChild strength (`badge_inputs.py:64-100`, `:230-294`). Each live `DECOMPOSES_INTO` child\nenters at:\n\n- its own recursive badge strength, if that badge is examined (weight > 0); otherwise\n- the **count prior** `qem_strength(0.5, (n_support − n_attack)·0.3 + (n_agree − n_disagree)·0.15)`.\n\nComputed: one incoming support gives 0.541, two give 0.632, three give 0.724, one attack\ngives 0.459, and one support plus one attack gives 0.500. Ten net agree votes give 0.846.\n\nThe count prior counts **every** live incoming `SUPPORTS` / `ATTACKS`, scored or not,\nscheme-bearing or not, **including `confirmed=false` proposals** from `auto_connect` and\n`cross_source_premises`, because the subclaim query filters supersession and\n`invalid_at` but not `confirmed` (`badge_inputs.py:64-79`). So add-evidence on a\n*decomposition child* moves the parent immediately (0.3 energy), while the same\nadd-evidence on a *leaf with no parent* leaves it UNEXAMINED until someone answers CQs.\n\nAggregation (`qbaf.py:196-226`, `support_semantics.py:294-332`). Children are grouped by\n`support_interpretation`:\n\n```\nnecessary     energy = (min(strengths) − 0.5) × 0.3\nalternative   energy = (max(strengths) − 0.5) × 0.3\ncorroborative energy = Σ (s − 0.5) × 0.3\n```\n\nUntagged or `undecided` children count as **necessary** in the badge\n(`undecided_as=\"necessary\"`, `qbaf.py:223-226`, founder ruling 2026-08-31). Elsewhere\n`interpreted_energy` defaults to corroborative (`support_semantics.py:297`).\n\nCeiling (`support_semantics.py:251-279`): each child with requirement weight *w* > 0\ncaps the whole at `1 − w·(1 − s)`. The weight comes from `requirement_weight`\n(`:226-248`): an explicit `necessary` tag gives 1.0; `corroborative` / `alternative` give\n0; otherwise the argued requirement claim's badge (below 0.5 closes the cap, above 0.5\ncan only raise the weight to `(s − 0.5)/0.5`); otherwise the presumption by provenance\n(`:186-223`): 0.5 for `extraction`, `human`, `split`, or a missing provenance; 0.0 for\n`confirmed=false`, for any other named provenance, and for `coverage=\"invented\"`. A flag,\n`DELIBERUS_RELEVANCE_PRESUMPTION`, off by default, can scale the presumption by a machine\nrelevance reading (`:176-183`).\n\nWorked example: children at 0.9, 0.9, 0.3, untagged, presumed 0.5 each. Energy is\n`(0.3 − 0.5)·0.3 = −0.06`, so strength is 0.498 (UNSETTLED). The ceiling is\n`1 − 0.5·0.7 = 0.65`, which does not bind.\n\nNote one asymmetry. The **requirement weight is not applied to energy**. An\n`auto_connect` `DECOMPOSES_INTO` child with presumption 0 imposes no cap, but it still\njoins the \"necessary\" group in `interpreted_energy` and can become the `min()` that\ndrags the parent's energy. Separately, `subclaim_badge_strengths` includes such children\nbecause the subclaim query does not filter `d.confirmed` (`badge_inputs.py:64-79`).\n\n### 3.5 What \"examined\", \"UNEXAMINED\", \"UNSETTLED\", \"NO DATA\" mean in code\n\n- **NO DATA**: no incoming scheme-bearing, live, confirmed edges and no decomposition\n  children. Strength 0.5, gray (`qbaf.py:273-275`).\n- **UNEXAMINED**: edges or children exist but every input sits exactly at 0.5.\n  `strength: None`, `weight: 0`, gray, with a note distinguishing \"attached, none scored\"\n  (`qbaf.py:319-331`).\n- **Examined**: weight ≥ 1, meaning at least one input differs from 0.5. **One answer\n  anywhere is enough.**\n- **UNSETTLED**: examined, with final strength in [0.48, 0.52) (`qbaf.py:363-369`).\n- `partition_by_reading` (`truth_graph.py:932-963`) sends NO DATA and UNEXAMINED to\n  `unexamined`, strength ≥ 0.52 to `supporting`, and everything else, **including\n  UNSETTLED**, to `challenging`.\n\n### 3.6 A second, divergent strength implementation\n\n`truth_graph.compute_subgraph_badges` (`truth_graph.py:770-845`) feeds `/query`, the\nsynthesis context and the ledger (`:990`). It is **not** `compute_badge_recursive`:\n\n- it does not exclude `REPORTS`, `SIMILAR_TO`, `CLASSIFIES_TERMINUS`,\n  `ARGUES_REQUIREMENT`, superseded edges, `confirmed=false` edges or `invalid_at` sources\n  (compare `truth_graph.py:787-793` with `badge_inputs.py:36-60`);\n- it looks up Questions in one direction only (`parent_from = s, parent_to = c`,\n  `:800-801`);\n- its answer counts ignore supersession and validity (`:804-806`);\n- it passes no subclaims, so the decomposition channel and the necessary cap are\n  absent;\n- it duplicates the 0.15 / 0.45 constants (`:817-818`);\n- it passes an edge `methodology_tier` read from `r.methodology_tier`, which is never\n  written (section 6).\n\nThe drift guard `test_every_weighing_seam_also_filters_ratified_invalid_evidence`\n(`tests/test_staleness.py:73-82`) inspects only `badge_inputs` and `hinge`, so it does not\ncover this path.\n\n### 3.7 Hinge and question ordering\n\n`hinge.py` propagates over the decomposition tree only, using the same count prior and\nthe same cap (`hinge.py:44-111`), then clamps each descendant to 1.0 and 0.0 and reports\n`|σ_root(granted) − σ_root(denied)|`. Its docstring states the scope: \"sensitivity flows\nthrough the decomposition channel only\" (`hinge.py:18-22`). SUPPORTS and ATTACKS enter\nonly as local counts, never as strength flow.\n\n`question_ordering.question_sensitivities` (`question_ordering.py:27-105`) handles the\ndirect channel (the clamp problem is described in 3.3). `worth_asking` is true when a\nquestion is unanswered and a decisive answer would cross a display band (`:92-102`).\n\n### 3.8 Evidence routing and fair share: built, not adopted\n\n`evidence_routing.py` offers `validate_routing` (routing can't invent evidence,\n`:67-111`), `fair_share_discounts` (evidence shared by *k* children counts 1/*k* each,\nunrounded on purpose, `:128-156`), `orphaned_evidence` as a losslessness signal\n(`:159-171`), and `discounted_subclaim_energy` (`:208-231`). Its own docstring says\n\"Nothing here changes shipped scoring\" (`:47-49`). Its only production caller is\n`slicing_stability.variant_fair_share` (`slicing_stability.py:108-114`), a diagnostic.\nSince untagged children now aggregate by `min`, fair share applies only to children\nexplicitly tagged corroborative (`evidence_routing.py:214-222`).\n\n---\n\n## 4. Evidence-shaped schemes and their CQs (verbatim)\n\nAll from `deliberus/extraction/schemes.py`. Cluster badge terms are at `:569-581`.\n\n**Source-based cluster** (terms CREDIBLE / QUESTIONABLE)\n\n- `expert_opinion` (`:222-232`):\n  - expert_credibility: \"{expert} is credible as an expert source in {domain}\"\n  - expert_field_match: \"{expert} is an expert in the specific field that {claim_topic} belongs to\"\n  - expert_assertion: \"{expert} actually asserted {specific_claim}\"\n  - expert_trustworthy: \"{expert} is personally reliable and trustworthy as a source on {claim_topic}\"\n  - expert_consistency: \"{specific_claim} is consistent with what other experts in {domain} assert\"\n  - expert_evidence_based: \"{expert}'s assertion about {claim_topic} is based on evidence\"\n- `position_to_know` (`:233-240`):\n  - position_access: \"{source} is in a position to know whether {specific_claim} is true\"\n  - source_honest: \"{source} is an honest and trustworthy source regarding {claim_topic}\"\n  - source_assertion: \"{source} actually asserted that {specific_claim}\"\n- `witness_testimony` (`:241-248`):\n  - witness_present: \"{witness} was in a position to observe {event}\"\n  - witness_reliable: \"{witness} is a reliable observer of {event}\"\n  - witness_consistent: \"{witness}'s account of {event} is consistent with other evidence\"\n- `popular_opinion` (`:249-256`):\n  - popularity_evidence: \"There is evidence that a large majority believes {specific_claim}\"\n  - popular_informed: \"The popular belief that {specific_claim} is based on adequate information rather than bias or incomplete evidence\"\n  - popular_merit: \"{specific_claim} has been evaluated on its own merits with evidence, independent of its popularity\"\n- `popular_practice` (`:257-263`):\n  - practice_documented: \"The practice of {action} is actually widespread as claimed\"\n  - practice_normative: \"The prevalence of {action} is relevant to whether it should be adopted\"\n\n**Causal cluster** (WELL-EVIDENCED / UNSUPPORTED)\n\n- `cause_to_effect` (`:264-271`):\n  - causal_strength: \"The causal generalization that {cause} leads to {effect} is well-supported by evidence\"\n  - cause_present: \"There is adequate evidence that {cause} is present in this case\"\n  - causal_interference: \"There are no other factors that could prevent {effect} despite {cause} being present\"\n- `effect_to_cause` (`:272-279`):\n  - alternative_causes: \"There are no alternative causes that could explain {effect} besides {proposed_cause}\"\n  - base_rate: \"The base rate of {proposed_cause} occurring is high enough to make it a plausible explanation for {effect}\"\n  - mechanism_plausible: \"There is a plausible mechanism by which {proposed_cause} would produce {effect}\"\n- `correlation_to_cause` (`:280-287`):\n  - confounders: \"The correlation between {variable_a} and {variable_b} is not explained by confounding variables\"\n  - temporal_order: \"{variable_a} precedes {variable_b} temporally, supporting a causal direction\"\n  - dose_response: \"There is a dose-response relationship between {variable_a} and {variable_b}\"\n- `sign` (`:288-294`):\n  - sign_reliable: \"The correlation between {indicator} and {indicated} is strong enough to be reliable\"\n  - sign_alternatives: \"There are no other factors that could explain {indicator} besides {indicated}\"\n\n**Abductive cluster** (BEST EXPLANATION / CONTESTED)\n\n- `evidence_to_hypothesis` (`:434-441`):\n  - explanatory_power: \"{hypothesis} satisfactorily explains {evidence}\"\n  - competing_hypotheses: \"No competing hypothesis explains {evidence} better than {hypothesis}\"\n  - search_thorough: \"The search for alternative hypotheses to explain {evidence} has been thorough\"\n- `abductive` (`:442-448`):\n  - best_explanation: \"{hypothesis} is the best available explanation for {observation}\"\n  - alternatives_eliminated: \"Alternative explanations for {observation} have been adequately considered and found wanting\"\n- `ignorance` (`:449-456`), clustered abductive (`:175`):\n  - search_adequate: \"A thorough enough search has been conducted to determine whether {proposition} is true\"\n  - burden_correct: \"The burden of proof is correctly allocated regarding {proposition}\"\n  - proof_strength: \"The required strength of proof for {proposition} has been appropriately set\"\n- `lack_of_evidence` (`:457-462`), one CQ:\n  - absence_meaningful: \"The absence of evidence for {proposition} is meaningful (evidence would be expected if {proposition} were true)\"\n\n**Analogy cluster, evidence by instance** (STRONG ANALOGY / WEAK ANALOGY)\n\n- `example` (`:329-336`):\n  - example_representative: \"{example_case} is representative of the general pattern claimed about {general_domain}\"\n  - example_true: \"The claimed outcome actually occurred in {example_case}\"\n  - counter_example: \"There are no counter-examples where {claimed_outcome} did NOT occur in similar cases\"\n- `analogy` (`:321-328`): relevant_differences, source_true, counter_analogy (see file).\n- `precedent` (`:337-343`): precedent_similar, precedent_changed (\"Circumstances have not changed since {past_case} in ways that undermine the precedent\" is the only CQ in the set that names temporal decay).\n\n**Character cluster touching source reliability** (RELEVANT / FALLACIOUS)\n\n- `bias` (`:383-390`): bias_exists, bias_influences, claim_independent.\n\n**Consequence-cluster CQs that ask for evidence**: `negative_consequences.negative_evidence`\n\"The evidence that {action} leads to {negative_outcome} is strong\" (`:306`),\n`positive_consequences.causal_link_action` (`:298`), `fear_appeal.fear_justified`\n\"The fear of {threat} is justified by evidence, not manufactured\" (`:516`).\n\n**Later additions**\n\n- `argument_from_weighing` (Deliberus-native, `:214-221`): weighing_covering_value,\n  weighing_compression_check, weighing_horizon_typing. The full CQ1-8 live in\n  `deliberus/weighing.py` and are instantiated claim-level.\n- `does_not_fit` and `default_inference` (`:548-563`) share the Yu & Zenker meta-level set\n  `_META_CQS` (`:199-204`): premise_acceptable, warrant_acceptable, exception_absent,\n  rebuttal_absent. `default_inference` is kept out of the prompt since 2026-09-13\n  (`:630-635`).\n\n**Every CQ has equal weight.** No CQ is marked as a defeater versus a qualifier,\nand no CQ is marked as needing a burden of proof. Each contributes `pos − neg` to the\nedge energy identically. A CQ about confounders counts the same as one about whether the\nexpert actually said it. There is **no scheme for statistical or study evidence as\nsuch** (no \"argument from a study\", \"from statistics\", \"from meta-analysis\", \"from\nreplication\"). Study-based support must go through `cause_to_effect`,\n`correlation_to_cause`, `expert_opinion` or `does_not_fit`.\n\n---\n\n## 5. Provenance, attribution, time\n\n### 5.1 Provenance\n\n- **Claim level**: `claim_kind` (asserted `extracted` / `implicit` / `human` vs\n  scaffolding `cq_*` vs `minted_report`), `attributed_to`, `argument_label`,\n  `source_span`, `EXTRACTED_FROM` → `Source`. `count_safe_summary`\n  (`reported_speech.py:242-266`) splits asserted from minted totals. CLAUDE.md reports it\n  has no production caller; that was not re-verified here.\n- **Edge level**: `provenance`, `trust_tier`, `confirmed`, `generator`, `via_concept`,\n  `stance_caution` (auto-connect). **`trust_tier` is read by no strength code.**\n  `confirmed` is read by the direct-channel query (`badge_inputs.py:58`) and by the\n  requirement presumption (`support_semantics.py:211-215`).\n- **Source level**: `Source` has `url`, `title`, `text_length` (`store.py:452-458`) and,\n  when tier-A dating succeeds, a published date. **A source has no author, venue,\n  reliability, or type field.**\n\n### 5.2 Attribution normalization (`deliberus/attribution.py`)\n\nThe module docstring (`:1-44`) describes the dirty field (135 distinct values) and a\nrole vocabulary `self` / `third_party` / `unknown` (`:52-60`). `normalize_attributions`\n(`:243-270`) produces a reviewable mapping artifact that is lossless and never overwrites.\nCLAUDE.md records that nothing has been written to the graph from it. No strength code\nreads `attributed_to`. `stance.py` uses source-level opposition to rank candidate\nconflicts (`stance.py:69-250`), and the stance guard in auto-connect uses the same signal\nto flag agreement links between opposed sources.\n\n### 5.3 Time\n\n- `recorded_at`: transaction time, first-write-wins, set by `create_claim_node`\n  (`store.py:170`) and `store_claims` (`store.py:488`). Paths that write claims with a raw\n  `MERGE`/`CREATE` skip it: minted reported content (`reported_speech.py:370-393`) and\n  cross-source premises (`cross_source_premises.py:150-160`).\n- `valid_at`: content time. Tier A is deterministic regex (URL date path, copyright line,\n  \"Published:\" line, top-of-text ISO date; `temporal_extraction.py:36-101`) and is\n  **inherited from the source's publication date** as the youngest defensible date\n  (`:104-120`, `c.valid_at_provenance = 'inherited_source_published'`). Tier B (LLM\n  per-claim content dates) is built but gated (`:143-172`). Tier C (event-bound\n  validity) is explicitly out of scope (`:17-19`).\n- `invalid_at`: only set by human ratification (`store.py:305-343`); filtered at every\n  evidence seam in `badge_inputs` (`:59`, `:69`, `:72`, `:114`, `:167`) and `hinge`\n  (`:192`, `:195`).\n- **Staleness daemon** (`staleness.py`): only claims that are the *source* of a live\n  `SUPPORTS`/`ATTACKS` are candidates (`:92-106`). A claim flags when its age (from\n  `valid_at`, else `recorded_at`) exceeds the horizon for its `evidence_type`\n  (`:59-89`). **`evidence_type=\"none\"` has no horizon and never flags. `meta_analysis`\n  is a horizon key but not an `evidence_type` value, so it is dead** (`:36`). Flags move\n  nothing (`:109-139`).\n- Supersession (`superseded_by`) is the edge-level lifecycle, distinct from staleness\n  (`store.py:227-259`).\n- **Nothing on edges, votes or answers is timestamped**, except `requirement_claim_at`\n  and staleness flags. The graph cannot say *when* a piece of evidence was attached or\n  in what order evidence arrived.\n\n---\n\n## 6. Dead, inert, or set only by accident\n\n> **Status 2026-09-27.** This map is a snapshot of the code on 2026-09-26. Items 11, 12, 13, 14, 15,\n> 17 and 18 below, and § 3.6, were repaired the next day; item 15's \"unreachable\" was slightly wrong\n> (one synthetic stress-suite claim carried the mislabel it keyed on), and item 17's comment is\n> corrected but the arithmetic behind it is ledger row Q27. What changed: `TODO.md` § Evidence layer.\n\n1. **`methodology_tier` never reaches strength.** It is a *claim* property\n   (`store.py:187-189`, `:521-529`). `compute_edge_strength` reads it only when a caller\n   passes it (`qbaf.py:153-154`), and `edges_for_badge` never does\n   (`badge_inputs.py:297-306`). `truth_graph` reads `r.methodology_tier`, an **edge**\n   property (`truth_graph.py:704`, `:829-836`), and nothing in the codebase writes it\n   (`grep \"r.methodology_tier\\|methodology_tier =\"` finds only the claim setter and that\n   read). `METHODOLOGY_BASE_WEIGHT` is reachable only from tests\n   (`tests/test_truth_graph.py:9-66`).\n2. **A latent defect if methodology were wired.** `compute_edge_strength([],\n   methodology_tier=\"rct\")` returns 0.65. `count_evidential_inputs` would then count an\n   edge with *no answers* as examined, because it measures distance from 0.5. The edge\n   would contribute +0.15 energy on a tier label alone, which is the \"unexamined\n   manufactures strength\" defect the weight/direction split was built to stop.\n3. **Quantitative fields are display-only.** `effect_size`, `effect_size_type`, CI\n   bounds, `sample_size`, `study_count`, `heterogeneity` are stored when present and\n   rendered into the synthesis prompt as `N=`, `studies=`, `heterogeneity=`\n   (`truth_graph.py:1080-1094`). No code computes with them. No prompt asks for them. Whether\n   any live claim carries them was not measured in this session (no LAN access).\n4. **`evidence_type`** feeds only `compute_confidence` (display), the `needs_evidence`\n   gap (`truth_graph.py:747-751`), a +1 in decomposition readiness\n   (`decomposition.py:188-190`), and staleness horizons. It never touches strength.\n5. **`epistemic_status`** likewise feeds only `compute_confidence`, gaps and readiness.\n6. **`c.confidence`** is computed and never read by strength.\n7. **Edge `strength`** (the LLM's 0-1 in extraction; 0.7 / 0.8 / 0.5 elsewhere) is read by\n   the badge query but discarded (`badge_inputs.py:133`, `_strength`). It *is* used by\n   the bridging feed (`feed.py:313-314`, `:364` `AVG(r.strength)`) and by auto-connect's\n   `> 0.3` filter (`auto_connect.py:404`).\n8. **`trust_tier`** is written everywhere and read by no computation.\n9. **`scheme_confidence`** is stored as JSON and never read by strength.\n10. **Evidence routing and fair share** are built and tested but not adopted (3.8).\n11. **The confirmed filter is partial.** The direct channel excludes `confirmed=false`\n    (`badge_inputs.py:58`). The decomposition count prior, the claim-level question\n    evidence count and the per-CQ answer count do not (`:64-79`, `:109-117`,\n    `:163-170`). So an unconfirmed `auto_connect` or `cross_source_premise` `SUPPORTS`\n    into a decomposition child, or into a polarity claim, moves strength. The guard test\n    only asserts that the filter string appears in the source\n    (`tests/test_badge_recursion.py:109-126`).\n12. **User evidence is scheme-less until an LLM succeeds.** add-evidence writes\n    `scheme=\"\"`, and the correction pipeline leaves it that way on an empty LLM result\n    (`correction_pipeline.py:151-152`) or on any exception (silently, `api.py:2789-2790`).\n    Such an edge is invisible to the badge. CLAUDE.md's strength audit names this second\n    gate.\n13. **The correction pipeline's no-fit fallback is unreachable.** That path returns\n    before writing whenever the scheme list is empty (`correction_pipeline.py:151-152`),\n    so the `does_not_fit` fallback at `:167-169` never fires; `schemes[0]` always wins.\n    A user-contributed edge gets `does_not_fit` only if the LLM names it explicitly, and\n    this prompt tells the model \"If no clear scheme applies, return empty lists\"\n    (`correction_pipeline.py:64`), which contradicts the scheme list's own instruction to\n    never return an empty list (`schemes.py:643-644`).\n14. **Out-of-enum values written directly**: `epistemic_status='unstated'`\n    (`cross_source_premises.py:155`), provenance `cross_source_premise` and `split` (not\n    in `EdgeProvenance`), `claim_kind='minted_report'` (not in `ClaimKindLit`).\n15. **`DECAY_HORIZONS[\"meta_analysis\"]`** is unreachable (5.3).\n16. **`REQUIREMENT_WEIGHT_STANDARD_QUESTION = 0.25`** is defined and marked \"unbuilt\"\n    (`support_semantics.py:124`).\n17. **The `neutral` band comment** (`qbaf.py:47-50`) does not hold for one-CQ schemes\n    (3.3).\n18. **Polarity claims default to `epistemic_status=\"observed\"`** because\n    `store_questions_and_cqs` does not pass one (`store.py:735-740`, default at `:141`).\n    Every piece of CQ scaffolding therefore reads `confidence` 0.5 but status \"observed\".\n\n---\n\n## 7. Gaps between what the code can express and common evidence concepts\n\n| Concept | Exists? | What is there |\n|---|---|---|\n| Evidence as a distinct object (study, dataset, document, testimony) | **No** | Evidence is any claim in tail position. `Source` has no type. |\n| Source reliability separate from claim | **No** | `Source` has url, title, length, date only. Reliability exists only as CQ *text* (`expert_trustworthy`, `witness_reliable`, `source_honest`), answered per edge, and not reused across claims from the same source. |\n| Independence between sources / shared-origin correlation | **No** | Four edges from the same study count four times (3.3 table). `fair_share_discounts` handles one fact routed to several children, not several claims resting on one study. No sybil or source-independence defence. |\n| Likelihood ratio / Bayes factor / diagnosticity | **No** | Contributions are fixed offsets. The `sign_reliable` and `base_rate` CQs name the idea in text only. |\n| Prior probability of a hypothesis | **Partly** | QEM base weight 0.5 everywhere in practice; `base_weight` parameter exists (`qbaf.py:232`) but every caller passes the default. |\n| Sample size / precision weighting | **No** | `sample_size`, CI and `heterogeneity` stored, unused. |\n| Study-design hierarchy | **Stored, inert** | `METHODOLOGY_BASE_WEIGHT`, never wired (6.1). |\n| Effect size and direction | **Stored, inert** | Not signed against the claim, not compared across studies. |\n| Meta-analytic pooling, heterogeneity penalty | **No** | |\n| Replication / independent confirmation | **No** | `expert_consistency`, `witness_consistent` as CQ text only. |\n| Evidence of absence | **Scheme only** | `lack_of_evidence` (one CQ) and `ignorance` (three). No representation of \"a search was run and found nothing\" as data. With one CQ, `lack_of_evidence` cannot leave the neutral band on one edge. |\n| Defeaters (rebutting vs undercutting, Pollock) | **Partly** | ATTACKS on a claim approximates rebutting. CQ negative polarity approximates undercutting. The distinction is not typed. |\n| Evidence quality of an *answer* | **No** | Answers are counted, never weighed. An attacked or refuted answer still counts. |\n| Weight of evidence (Keynes) | **Yes, coarsely** | `Reading.weight` counts non-neutral inputs (`evidential_weight.py`). It does not grow with the number of answers inside one edge (one edge = one input). |\n| Balance of evidence vs absence of evidence | **Yes** | UNEXAMINED vs UNSETTLED, enforced at construction. |\n| Linked vs convergent premises | **Yes, for decomposition** | `support_interpretation` necessary / corroborative / alternative, weakest-part default, argued requirement cap. Not for direct SUPPORTS edges, which always sum. |\n| Chain of custody (who handled / transformed the evidence) | **No** | `source_span` quote, `EXTRACTED_FROM`, `attributed_to`. No record of paraphrase steps, translation (Pass 2 translates non-English to English, `focused.py:64`), or edits. |\n| Quote verification (span actually in source) | **No** | `source_span` is LLM-produced and never checked against the stored text. |\n| Page / timestamp locator | **No** | |\n| Temporal validity | **Yes, human-gated** | `recorded_at`, `valid_at` (tier A), ratified `invalid_at`, staleness flags. Event-bound validity out of scope. No edge time. |\n| Retraction / correction of an evidence source | **Partly** | `invalid_at` per claim, supersession per edge. No source-level retraction that cascades to its claims. |\n| Contributor attribution of evidence | **Yes** | `attributed_to=<email>` on user evidence and answers (since 2026-10-03 the person's name, plus `contributor_id`). No weighting by contributor (by design). |\n| Conflict of interest | **Scheme only** | `bias` scheme CQs. |\n| Base rates | **CQ text only** | `effect_to_cause.base_rate`. |\n| Confounding, temporal order, dose-response | **CQ text only** | `correlation_to_cause`. |\n| Evidence routing to sub-claims | **Built, unadopted** | `evidence_routing.py`. |\n| Machine vs human evidence distinction in strength | **Partly** | `confirmed=false` excluded from the direct channel only; `provenance` drives requirement presumptions. Answer counts ignore provenance. |\n| Asserted vs minted counts | **Yes, in a function** | `count_safe_summary`. |\n\n---\n\n## 8. Tests that pin evidence behaviour\n\n88 test files. Those that touch evidence semantics, with test counts:\n\n- `tests/test_qbaf.py` (61): QEM impact and strength properties (`:11-85`), edge strength\n  from CQs (`:87-141`), badge bands (`:143-225`), claim badge including\n  `test_all_unanswered_cqs_read_unexamined_rather_than_positive` (`:270`),\n  `TestPolarityAndDirection` (`:342-398`: attack lowers what support raises; symmetry;\n  cancellation; unevaluated edges manufacture nothing; neutral relations inert; missing\n  polarity refused), `TestExaminedIsAboutInputsNotTheSum` (`:400`),\n  `test_headline_term_follows_the_dominant_channel` (`:429`),\n  `test_examined_but_balanced_reads_unsettled_not_supported` (`:448`).\n- `tests/test_evidential_weight.py` (12): the Reading invariant (no weight ⇒ no\n  direction), cancellation still carries weight, weight does not saturate, children count.\n- `tests/test_correction_ux.py` (21): edge strength moves with answers\n  (`:12-70`), routes exist and require auth (`:74-135`), badge colour shifts (`:140-165`).\n  These test `compute_edge_strength`, not the endpoint's graph writes.\n- `tests/test_badge_recursion.py` (5): leaf weakness reaches root through necessary joins\n  (`:38`), unexamined children keep the count prior (`:53`), hinge craters when a\n  necessary part is denied (`:64`), weighing question on mirrored edges (`:84`), and a\n  **source-text** assertion that the confirmed filter string exists (`:109-126`).\n- `tests/test_requirement_cap.py` (14): tag/presumption/argued ordering, rebutted\n  requirement closes the cap, spurious part cannot drag 1:1, badge query names the\n  non-propagating types (`:147`), requirement endpoint mints once (`:167`).\n- `tests/test_support_semantics.py` (38): necessary / corroborative / alternative\n  aggregation and the ceiling.\n- `tests/test_evidence_routing.py` (23): validation, no invented evidence, exact\n  conservation of fair share, orphans.\n- `tests/test_staleness.py` (8): no date or no horizon means no flag, content time beats\n  transaction time, ratified claims are not re-flagged, validity needs reason and date,\n  verdict ground minted, and a **source-text count** of `invalid_at IS NULL` in\n  `badge_inputs` (≥4) and `hinge` (≥2) (`:73-82`).\n- `tests/test_truth_graph.py` (65): `methodology_base_weight` ordering and\n  `compute_edge_strength(..., methodology_tier=...)` (`:9-66`). These pin a feature no\n  production path uses.\n- `tests/test_hinge.py` (14), `tests/test_question_ordering.py` (10),\n  `tests/test_worth_asking.py` (10): sensitivity and band-crossing.\n- `tests/test_reported_speech.py` (34), `tests/test_keeper.py` (25),\n  `tests/test_auto_connect.py` (42), `tests/test_cross_source_premises.py` (4),\n  `tests/test_models.py` (29, including the Literal round trip), `tests/test_models_scheme.py`\n  (16), `tests/test_synthesis_ledger.py` (60), `tests/test_completeness.py` (14),\n  `tests/test_stance.py` / `test_stance_guard.py` (19 / 13).\n\n**What no test pins:**\n- that add-evidence produces a scheme-bearing edge, or what happens when it doesn't;\n- that `polarity` values other than \"supports\"/\"positive\" are rejected (they are not);\n- that `compute_subgraph_badges` agrees with `compute_badge_recursive` on the same claim;\n- that answer counts and the child prior exclude unconfirmed edges (they don't);\n- the reachable maximum of a single edge per CQ count (the neutral-band claim in\n  `qbaf.py:47-50`);\n- that `worth_asking` clamps correspond to reachable states;\n- any use of `sample_size`, `effect_size`, CI, `study_count`, `heterogeneity`;\n- that `evidence_type` / `epistemic_status` are assigned correctly (self-eval checks\n  `epistemic_status` only, at runtime, not in tests).\n\nTwo drift guards (`test_staleness.py:73-82`, `test_badge_recursion.py:109-126`) test the\n**presence of a string in source code** rather than behaviour. They pass while a sibling\nquery in the same file omits the same filter (item 6.11).\n\n---\n\n## Summary of the load-bearing facts\n\n1. Evidence is a graph position, not a type. Strength flows through two channels only:\n   CQ answers on incoming scheme-bearing edges, and decomposition children.\n2. The direct channel **counts** answers (0.15 each, cap 0.45 per polarity) and applies\n   QEM twice, so one fully answered six-CQ edge moves a claim to 0.581, and a one-CQ edge\n   can never leave `UNSETTLED`.\n3. All methodological and statistical metadata (tier, N, effect, CI, studies, I²,\n   `evidence_type`, `epistemic_status`, `confidence`, edge `strength`, `trust_tier`) is\n   stored and inert with respect to strength. The one wire meant to use tier reads an\n   edge property nobody writes.\n4. A second badge implementation in `truth_graph.py` feeds synthesis and diverges from\n   the canonical one on every filter, direction and the decomposition channel.\n5. The code has no notion of source reliability, source independence, likelihood ratio,\n   sample-size weighting, evidence of absence as data, chain of custody, or edge/answer\n   timestamps. It does have a clean weight/direction split, linked-vs-convergent\n   decomposition semantics with an argued requirement cap, human-ratified temporal\n   invalidation, and non-propagating reported speech.\n"}