Ground two exact-RO predicate labels; the canary refuted the third (#236) - #235
Conversation
The head of reports/predicate_grounding_residual.tsv is not the free-text
quality floor NEXT_TASKS section 4 described. Three of its top entries are the
verbatim labels of RO terms whose paraphrases were already mapped:
positively regulates 37 edges RO:0002213 ('promotes' maps here)
negatively regulates 16 edges RO:0002212 ('inhibits' maps here)
causally upstream of 13 edges RO:0002411 (no paraphrase mapped)
So the canonical label was left ungrounded while its synonym was not.
Canary before fan-out: these three rows are added and validated ALONE, before
any trait file is touched. The label-correspondence gate is what makes the
grounding loop safe to run unattended — it resolves each CURIE against OAK and
fails on a mismatch — so it has to be shown working on the first unit rather
than assumed.
Verified it actually checked them rather than skipping: OK_CANONICAL goes
308 -> 311 with these rows and SKIPPED_NO_ADAPTER stays at 132.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
scripts/ground_causal_predicates.py --apply across 37 trait files. Residual drops 1274 -> 1208 edges, 572 -> 569 distinct labels. Verified the writer changed only what it should: parsing HEAD against the working tree for all 37 files, the sole differences are 66 added predicate_id fields and exactly one appended curation_history entry per file. The writer also reflows some quoted scalars — e.g. a canonical_examples note in plant_pathogen.yaml — which the same comparison shows is cosmetic, since the parsed documents are otherwise identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both gates built earlier today fired on this change rather than being remembered, which is the point of them: audit-derived-reports reports/*_grounding_residual.tsv stale (#214) audit-derived-reports pages/ stale (#230) pages/ moves in all 489 files because the footer stamp is derived from the corpus (#228) and the corpus gained 37 curation_history entries: it now reads "Corpus as of 2026-08-04 02:57 UTC". That is the stamp behaving as designed — a data change with no rebuild used to leave it untouched. causal_graph_audit.tsv is unchanged, correctly: grounding a predicate does not alter connectivity. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @realmarcin's task in 4m 43s —— View job Review — PR #235The three CURIEs resolve and their labels are exact. That part is right, and the canary/regeneration discipline is good. But the canary found something it didn't report: exact label match is not sufficient for these three terms, and the gate the PR nominates as the safety mechanism cannot see it. 🟡
|
| file | edges | subject node_type → object node_type |
|---|---|---|
data/traits/metabolism/propionic_acid_fermentation.yaml |
6 | CHEMICAL → CHEMICAL |
data/traits/metabolism/three_hydroxypropionate_bicycle.yaml |
6 | GENE_OR_PROTEIN → CHEMICAL |
data/traits/morphology/sphere_shaped.yaml |
1 | GENE_OR_PROTEIN → CAPACITY |
Concretely, data/traits/metabolism/propionic_acid_fermentation.yaml:131 now asserts pyruvate RO:0002411 oxaloacetate — two nodes typed CHEMICAL, one of them grounded CHEBI:16452. A reasoner reading that infers pyruvate is a process. Same shape at three_hydroxypropionate_bicycle.yaml:123 (acetyl_coa_carboxylase → malonyl_coa) and sphere_shaped.yaml:134 (mreB_elongation_machinery → elongation_capacity).
RO:0002411 appears nowhere in mappings/predicate_grounding.tsv before this PR, so this pattern is entirely introduced here — unlike the promotes/inhibits rows, there's no precedent to inherit.
Two of the three shapes have no correct RO answer at all. CHEMICAL → CHEMICAL pathway succession is a reaction step, not RO causal-upstream; the corpus's own is converted to (11 edges) is the honest label and it is still, correctly, ungrounded. For GENE_OR_PROTEIN → process, the material-entity branch (RO:0004034 / RO:0004035, acts upstream of, positive/negative effect) is where RO puts gene-product→process claims — but none of the three targets here are processes either. My read is that all 13 should be reverted and causally upstream of returned to the residual as a judgement call, not that a substitute CURIE should be swapped in.
🟡 The gate does not check what the PR credits it with checking
label-correspondence… resolves each CURIE against OAK and fails on a mismatch — so it had to be demonstrated on the first unit rather than assumed.
scripts/validate_id_label_correspondence.py compares an (id, label) pair against the ontology's canonical label and synonyms. That's all. It has no view of the edge the CURIE lands on, and predicate_id in src/traitmech/schema/traitmech.yaml:377 is a bare pattern-constrained string with no binding — so nothing in just qc, audit-graphs, or validate-strict inspects domain/range. OK_CANONICAL: 308 → 311 is a true statement about three TSV rows and says nothing about 66 edges.
That gap is the actual canary result, and it inverts the PR's conclusion. The tier boundary isn't "exact label = mechanical, paraphrase = judgement" — causally upstream of was an exact match and still needed judgement. If the loop is going to fan out, mappings/predicate_grounding.tsv needs a column recording which node_type pairs a predicate may connect, and the grounder needs to enforce it; otherwise the next unit fails the same way with a green gate.
🔵 The same objection applies more weakly to the 53 RO:0002213/RO:0002212 edges
Also processual relations, and the objects include autoinducer (CHEMICAL), fhl_complex / sigB / rpos (GENE_OR_PROTEIN), and rod_shaped_trait / rhizosphere_trait (TRAIT). I'm not asking for changes here — promotes→RO:0002213 and inhibits→RO:0002212 already carry this in mappings/predicate_grounding.tsv:58-65, so the PR is extending an existing corpus convention rather than inventing one. Worth a separate decision; not this PR's to relitigate.
Otherwise clean. The mapping rows are well-annotated, the residual delta (1274→1208 = 66) reconciles exactly, the 489-file pages/ churn is genuinely just the footer stamp plus the expected GROUND_CAUSAL_PREDICATES history entries and predicate_id fields (checked the non-stamp lines directly), and causal_graph_audit.tsv being unchanged is right. The canonical_examples quoted-scalar reflow is cosmetic as described. No history/records/ entry, but that directory holds one file corpus-wide, so the convention is effectively dormant — not a defect.
I could not execute just qc here (Python execution is not permitted in this environment), so the "exits 0 / 214 tests pass" claim is unverified by me — though given nothing checks domain/range, a green run is consistent with the finding above rather than evidence against it.
There was a problem hiding this comment.
RO:0002411 has domain and range BFO:0000015 (process), but all 13 edges it was applied to connect material entities — CHEMICAL→CHEMICAL in propionic_acid_fermentation.yaml, GENE_OR_PROTEIN→CHEMICAL in three_hydroxypropionate_bicycle.yaml, GENE_OR_PROTEIN→CAPACITY in sphere_shaped.yaml. Zero satisfy the relation's definition, and RO:0002411 is new to the corpus in this PR, so the whole pattern is introduced here.
The label-correspondence gate cannot catch this: it compares an (id, label) pair against the ontology and has no view of the edge the CURIE lands on. predicate_id is an unbound pattern string in the schema, so nothing in just qc inspects domain/range. That is the real canary finding, and it inverts the PR's conclusion — 'causally upstream of' was an exact label match and still needed a curator's judgement.
Suggest reverting the 13 RO:0002411 groundings and returning that label to the residual, and adding an applicable-node_type column to mappings/predicate_grounding.tsv enforced by the grounder before any fan-out. The 53 RO:0002213/RO:0002212 edges have the same weakness but inherit it from the existing promotes/inhibits rows — not this PR's to fix.
Full detail: #235 (comment)
Both 🟡s were right, and the second matters more than the first. RO:0002411 `causally upstream of` is defined over occurrents — "the end of p precedes the end of q, and p is not an occurrent part of q" — and all 13 edges it grounded connect material entities: 6 CHEMICAL->CHEMICAL, 6 GENE_OR_PROTEIN->CHEMICAL, 1 GENE_OR_PROTEIN->CAPACITY. Zero conform. Asserting `pyruvate RO:0002411 oxaloacetate` tells a reasoner pyruvate is a process. Unlike the two regulation rows, nothing in the corpus mapped RO:0002411 before, so this pattern was introduced here with no precedent to inherit — and two of the three shapes have no correct RO answer anyway. Reverted by dropping the mapping row and re-applying from a clean corpus, so the curation_history counts stay honest rather than being patched. 66 edges -> 53; `causally upstream of` is back in the residual at 13, where it belongs as a judgement call. The larger finding is that the gate does not check what I credited it with. label-correspondence compares (id, label) against the ontology and nothing else; it has no view of the edge, and predicate_id is an unbound string in the schema. OK_CANONICAL 308->311 was true of three TSV rows and said nothing about 66 edges. `causally upstream of` was an EXACT label match and still wrong, so the tier boundary this PR proposed — exact match mechanical, paraphrase judgement — does not hold. Filed as #236 with the fix: a node_type-pair column on the mapping, enforced by the grounder. Kept: the 53 RO:0002213/RO:0002212 edges. Those extend an existing convention (promotes/inhibits have mapped there for some time) rather than inventing one. They are processual relations used mostly between material entities — 13% and 8% process->process across all 206 and 102 corpus edges — which is a real question, quantified in #236, but a corpus-wide curation decision rather than this PR's to settle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @realmarcin's task in 2m 25s —— View job Review — PR #235 (re-review after
|
There was a problem hiding this comment.
Revert is complete and clean. RO:0002411 is gone from mappings/ and data/traits/ entirely, and the three affected files diff empty against origin/main — re-applying from a clean corpus rather than patching means no orphan curation_history entry. 'causally upstream of' (13) is back in the residual.
The remaining 53 edges reconcile exactly: 37 RO:0002213 + 16 RO:0002212 across 34 files, 34 history entries, both labels dropped from the residual. No prefix bleed — 'positively regulates expression of' in temperature_delta_mid2.yaml:123 was correctly left ungrounded. The canonical_examples quoted-scalar reflow is a parse-identical round-trip.
Keeping the RO:0002213/RO:0002212 edges and deferring the process/material-entity question to a follow-up is right; those inherit the promotes/inhibits convention at predicate_grounding.tsv:58-65 rather than introducing it.
Incidental: reports/label_drift.tsv gains severity/detail columns that validate_id_label_correspondence.py:903-904 already emitted on main — a stale artifact corrected by this regeneration, not a change from this PR.
CI green across qc, validate-strict, label-correspondence, pr-sanity, Curation history.
The precondition for fanning out the grounding loop. #235 established that an exact label match is not sufficient to ground a predicate, and that nothing in the repo could have caught it. `causally upstream of` IS the label of RO:0002411, so it passed the id-label gate — but RO defines the relation over occurrents and all 13 corpus edges connect material entities. That gate compares a CURIE to its label and never looks at the edge; predicate_id is an unbound string in the schema; nothing in qc inspects domain or range. mappings/predicate_grounding.tsv gains subject_types and object_types — pipe-separated CausalNodeTypeEnum names, or * for any. All 99 existing rows are */* explicitly rather than by omission, so the file states its own permissiveness. ground_causal_predicates.py builds a node_id->node_type map per graph and refuses edges outside the declared types; they stay in the residual, because a wrong grounding is worse than a missing one. causally upstream of is restored WITH a process constraint and grounds zero edges, deliberately: the CURIE is right, the corpus usage is not, and recording that beats leaving 13 edges silently residual. Running it names exactly the three shapes the #235 review identified. Five review rounds hardened it beyond the mechanism: - The residual TSV is the durable work queue, and listed blocked labels identically to unmapped ones. It now carries status and blocked_by, so a curator reads "blocked_by_node_type / RO:0002411" rather than trying to map it again. - Type names are validated against CausalNodeTypeEnum read from the schema, not hardcoded. A typo would otherwise silently block everything or nothing. - blocked was keyed by a rendered string and the label recovered by splitting on " (" — which truncates `positively influences (saturating)`, a real corpus label, flagging the wrong row. Keyed by tuple now, with a test using that label. - .claude/commands/ground-or-propose-metpo.md, the tracked file that drives this loop, explicitly instructed `causally upstream of`->RO:0002411 as a freebie. Removed; the column contract and a skip rule for blocked rows added. - A blanket "these are all processual relations" claim I added there was wrong for half the list. Checked against RO instead: enables is material-entity to process, produces is defined over material entities, BFO parthood is domain-neutral. Replaced with the rule that generalises — read each relation's own domain and range. NEXT_TASKS section 9 no longer queues `causally upstream of` and no longer credits label-correspondence with making this loop safe. No corpus change: 0 newly grounded, residual unchanged at 1221. Whether to constrain the regulation relations — RO:0002213 is 13% process-to-process, RO:0002212 is 8% — remains the separate curation decision, which now has a mechanism to express. Closes #236. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First unit of the grounding backfill (NEXT_TASKS §9), run as a canary before any fan-out. The canary did its job: it refuted the premise it was testing. Read the last section first if you read nothing else.
What landed
Two mapping rows and 53 grounded edges across 37 files:
positively regulatespromotes→ RO:0002213negatively regulatesinhibits/suppresses/prevents→ RO:0002212Residual 1274 → 1221 edges, 572 → 570 labels. These extend a convention already in
mappings/predicate_grounding.tsv:58-65rather than inventing one.What was reverted, and why it matters more
causally upstream of(13 edges) was the exact label of RO:0002411 and passed every check — and was still wrong. RO defines it over occurrents; all 13 edges connected material entities: 6CHEMICAL → CHEMICAL, 6GENE_OR_PROTEIN → CHEMICAL, 1GENE_OR_PROTEIN → CAPACITY. Zero conformed. Assertingpyruvate RO:0002411 oxaloacetatetells a reasoner pyruvate is a process.Reverted by dropping the row and re-applying from a clean corpus, so the
curation_historycounts stay honest rather than being patched after the fact.The canary's actual result — this inverts the PR's original claim
I credited
label-correspondencewith making the loop safe to run unattended. It does not.validate_id_label_correspondence.pycompares an(id, label)pair against the ontology and nothing else — it has no view of the edge the CURIE lands on, andpredicate_idis an unbound pattern-constrained string in the schema, so nothing inqcchecks domain or range.OK_CANONICAL: 308 → 311was a true statement about three TSV rows and said nothing about the 66 edges those rows grounded.So the tier boundary this PR originally proposed — exact label = mechanical, paraphrase = judgement — does not hold. An exact match still needed judgement. Filed as #236, with the fix: a
node_type-pair column on the mapping, enforced by the grounder, as the precondition for fanning out.Left standing, quantified but not relitigated
Both regulation relations are also processual, and the corpus uses them mostly between material entities — RO:0002213 is 27/206 process→process (13%), RO:0002212 is 9/102 (8%). That predates this PR and is a corpus-wide curation decision; the numbers are in #236 so it can be made deliberately.
Verification
OK_CANONICAL308→311,SKIPPED_NO_ADAPTERunchanged at 132.predicate_idfields and exactly onecuration_historyentry each. Thecanonical_examplesquoted-scalar reflow is cosmetic — the parsed documents are otherwise identical.audit-derived-reportscaught thereports/andpages/staleness itself (Derived grounding residual reports drift from the corpus with nothing to catch it #214, Gate pages/ staleness in qc, now that regenerating is deterministic #230) rather than my remembering.pages/moves in all 489 files because the footer stamp is corpus-derived (pages/ are stale and unverifiable: 119 differ from the data, and a build timestamp hides it #228) and the corpus gained history entries.causal_graph_audit.tsvis correctly unchanged — grounding a predicate does not alter connectivity.just qcexits 0; 214 tests pass.🤖 Generated with Claude Code