Skip to content

Ground two exact-RO predicate labels; the canary refuted the third (#236) - #235

Merged
realmarcin merged 4 commits into
mainfrom
feat/ground-exact-ro-predicates
Aug 4, 2026
Merged

Ground two exact-RO predicate labels; the canary refuted the third (#236)#235
realmarcin merged 4 commits into
mainfrom
feat/ground-exact-ro-predicates

Conversation

@realmarcin

@realmarcin realmarcin commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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:

residual label edges RO term already mapped as
positively regulates 37 RO:0002213 promotes → RO:0002213
negatively regulates 16 RO:0002212 inhibits/suppresses/prevents → RO:0002212

Residual 1274 → 1221 edges, 572 → 570 labels. These extend a convention already in mappings/predicate_grounding.tsv:58-65 rather 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: 6 CHEMICAL → CHEMICAL, 6 GENE_OR_PROTEIN → CHEMICAL, 1 GENE_OR_PROTEIN → CAPACITY. Zero conformed. Asserting pyruvate RO:0002411 oxaloacetate tells a reasoner pyruvate is a process.

Reverted by dropping the row and re-applying from a clean corpus, so the curation_history counts stay honest rather than being patched after the fact.

The canary's actual result — this inverts the PR's original claim

I credited label-correspondence with making the loop safe to run unattended. It does not. validate_id_label_correspondence.py compares an (id, label) pair against the ontology and nothing else — it has no view of the edge the CURIE lands on, and predicate_id is an unbound pattern-constrained string in the schema, so nothing in qc checks domain or range.

OK_CANONICAL: 308 → 311 was 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 = judgementdoes 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

just qc exits 0; 214 tests pass.

🤖 Generated with Claude Code

realmarcin and others added 3 commits August 3, 2026 20:00
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>
@culturebot-reviewer

culturebot-reviewer Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude finished @realmarcin's task in 4m 43s —— View job


Review — PR #235

The 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.


🟡 RO:0002411 is a process→process relation; all 13 edges connect material entities

RO defines causally upstream of as "p is causally upstream of q iff p and q are processes, p precedes q…" — domain and range are BFO:0000015. Every one of the 13 grounded edges violates that:

file edges subject node_type → object node_type
data/traits/metabolism/propionic_acid_fermentation.yaml 6 CHEMICALCHEMICAL
data/traits/metabolism/three_hydroxypropionate_bicycle.yaml 6 GENE_OR_PROTEINCHEMICAL
data/traits/morphology/sphere_shaped.yaml 1 GENE_OR_PROTEINCAPACITY

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_carboxylasemalonyl_coa) and sphere_shaped.yaml:134 (mreB_elongation_machineryelongation_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 — promotesRO:0002213 and inhibitsRO: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.

Fix this →

--- · feat/ground-exact-ro-predicates

@culturebot-reviewer culturebot-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@culturebot-reviewer

culturebot-reviewer Bot commented Aug 4, 2026

Copy link
Copy Markdown

Claude finished @realmarcin's task in 2m 25s —— View job


Review — PR #235 (re-review after 0921b14b)

The revert is complete and the remaining change is sound. Approving.

RO:0002411 is fully gone. No occurrence anywhere in mappings/ or data/traits/ (the only hits repo-wide are data/raw/biolink-model.yaml and prose in NEXT_TASKS.md / proposal docs, all pre-existing). The three files it touched — propionic_acid_fermentation.yaml, three_hydroxypropionate_bicycle.yaml, sphere_shaped.yaml — now diff empty against origin/main, so re-applying from a clean corpus rather than patching worked: no orphan curation_history entry claiming a grounding that isn't there. causally upstream of 13 is back in reports/predicate_grounding_residual.tsv where it belongs.

The 53 that remain reconcile exactly. 53 added predicate_id fields across 34 files (37 × RO:0002213, 16 × RO:0002212), 34 GROUND_CAUSAL_PREDICATES history entries — one per file, none doubled. Both labels dropped out of the residual and nothing else moved.

Two things I checked specifically because they're where this kind of writer usually over-reaches, and both are clean:

  • No prefix bleed. data/traits/environment/temperature_delta_mid2.yaml:123 carries positively regulates expression of — a different relation that starts with the mapped string. It was left ungrounded and sits at line 300 of the residual. Exact-label matching, not startswith.
  • The quoted-scalar reflow is genuinely cosmetic. ~16 canonical_examples[].note values got re-wrapped; every one is a "…" → plain-scalar round-trip with identical parsed text. (The PR body says "a canonical_examples note in plant_pathogen.yaml" — it's more files than that, same nature.)

One incidental thing worth naming so it isn't a surprise later: reports/label_drift.tsv gains severity and detail columns in this diff, but no script in this PR produces them — scripts/validate_id_label_correspondence.py:903-904 already emitted that header on main. The artifact on main was stale from an earlier validator change, and this PR's regeneration swept the correction in. Benign, and arguably an argument for audit-derived-reports having caught it sooner.

On my earlier 🔵: keeping the 53 RO:0002213/RO:0002212 edges and quantifying the process→process fraction (13% / 8%) in a follow-up is the right call. Those inherit the promotes/inhibits convention at mappings/predicate_grounding.tsv:58-65 rather than introducing it, and settling it corpus-wide isn't this PR's job.

CI is green across qc, validate-strict, label-correspondence, pr-sanity, and Curation history. No history/records/ entry, but that directory holds one file corpus-wide — dormant convention, not a defect.

The canary produced a more useful result by failing than it would have by passing, and the commit message says so plainly instead of quietly dropping the row. That's the part worth keeping when the loop fans out.


Approved
· feat/ground-exact-ro-predicates

@realmarcin realmarcin changed the title Ground the three exact-RO predicate labels — canary for the grounding loop (#214 §9) Ground two exact-RO predicate labels; the canary refuted the third (#236) Aug 4, 2026

@culturebot-reviewer culturebot-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@realmarcin
realmarcin merged commit 7540ac4 into main Aug 4, 2026
7 checks passed
@realmarcin
realmarcin deleted the feat/ground-exact-ro-predicates branch August 4, 2026 03:40
realmarcin added a commit that referenced this pull request Aug 4, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant