Repair the last 16 enables edges whose object cannot satisfy the range (#334) - #355
Conversation
#334) biolink gives enables (RO:0002327) the range 'biological process or activity', which of CausalNodeTypeEnum only BIOLOGICAL_PROCESS, PATHWAY and MOLECULAR_FUNCTION satisfy. #315 widened the audit from its original TRAIT-only test to that full range and surfaced 33 edges; #351 took 17. These are the remaining 16, and ENABLES_RANGE_VIOLATION is now 0. THERE WAS NO SINGLE SWEEP. Six idioms are mixed together and the object type does not classify them - the same lesson `reduces` recorded when its two senses turned out not to be separable that way. Every decision is in DECISIONS in the migration script with the sentence from the record that justifies it: - 4 needed only REPOINTING, because the process the subject actually feeds was already in the graph. cytoplasmic_na was pointed at the Mrp complex while the record said "the substrate that sustains alkaliphile ANTIPORT activity", and that antiport node sits three lines above. - 1 was simply BACKWARDS, and its own description said so: "high-affinity terminal oxidase ENABLING oxygen respiration at low O2 tensions" had been written as oxygen enables the oxidase. Reversed onto the graph's existing aerobic_respiration node, which is both domain- and range-correct. - 3 had the wrong NODE TYPE rather than the wrong predicate. An object labelled "even peptidoglycan distribution" is described as "evenly distributed INSERTION of peptidoglycan" - a process. "Treatment resistance and persistence" is a disposition, not a quality. And buoyancy was typed QUALITY here while intracellular_inclusion.yaml already typed the same concept TRAIT - a typing inconsistency of exactly #352's shape, resolved toward TRAIT. - 3 moved onto existing relations that fit better than enables ever did: `produces` for an operon making a chemical, `has output` for a PATHWAY subject (the documented activity/non-activity split), and `transports` for FtsW, which FLIPS lipid II rather than enabling it. - 2 were never causal: Scy is "a COMPONENT of the tip-organizing centre" (part of), and Hfq increases RpoS (promotes). - 5 needed a term that does not exist upstream. MINTED METPO:2007900 `powers` for those 5, proposed in proposals/metpo_traitmech_v10. An ion motive force, a proton motive force, or torque generation, operating a molecular machine. RO has no relation for it because RO models energetics at the PROCESS level while all five objects are protein complexes - the same gap `exports`/`imports` were minted for. The label is not a coinage: all five edge descriptions already used the verb "power". object_types is gated to GENE_OR_PROTEIN because an energetic driver feeding a process satisfies enables' range already and must stay there; leaving it open would let the term absorb edges that are not broken. Two gates widened deliberately and recorded in the table: GENE_OR_PROTEIN added to `transports` subject_types (a flippase is a protein, not a pathway), TRAIT added to `confers` (a structural trait conferring a functional disposition, which the corpus already carried and lacked a subject type for). EDITS ARE STRUCTURAL, NOT TEXTUAL. All 15 files round-trip byte-identical through emit_trait_yaml, verified before touching them, so they are parsed, modified and re-emitted. Earlier migrations spliced raw lines and broke YAML three times and once turned "proper polar growth" into "proper powth". I made two mistakes editing predicate_grounding.tsv and caught both by reading the diff rather than the exit code. csv.writer with escapechar='\\' rewrote the "" quote-doubling in FOUR UNRELATED rows into \", corrupting data it had no business touching. And I filled the new row's columns 4-6 from their names instead of from a real row: they are the SSSOM mapping predicate, source ontology and match confidence, not the causal predicate. Redone with QUOTE_MINIMAL and the columns copied off an existing row; the diff is now exactly 2 changed rows and 1 added. MEASURED, not predicted: ENABLES_RANGE_VIOLATION 16 -> 0, MICROBE_DOMAIN_ON_NONORGANISM stays 0, audit-graphs unchanged at 1533 findings (218 FRAGMENTED_GRAPH, 1303 UNREACHABLE_FROM_TRAIT) - the repairs repointed edges without stranding a node. The only baseline churn is key moves: two evidence-snippet findings and ten gas_vesicle UNREACHABLE details that now name both TRAIT anchors because buoyancy became one. Same node set, same counts, verified by diffing them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @realmarcin's task in 5m 36s —— View job Review — PR #355Checked every one of the 16 repairs against the graph it lives in: node types on both ends of each new edge, the gate rows in Two things to fix. 🟡 The reversed cytochrome bd edge kept evidence that no longer supports it
The description was rewritten to a new claim — "high-affinity terminal oxidase, sustaining aerobic respiration at the low O2 tensions where low-affinity oxidases cannot operate" — but the evidence block was left untouched: - reference: DOI:10.1128/jb.00389-22
notes: '"which of these terminal enzymes is active in electron transfer depends
on the availability of the final electron acceptor: fumarate or oxygen"; supports
electron-acceptor-dependent branch switching.'That quote is about which oxidase is active given the available acceptor. It says nothing about affinity or about low O2 tensions, so the assertion the edge now makes is uncited — and the trailing clause "supports electron-acceptor-dependent branch switching" describes the edge that was just deleted. This is the one place in the PR where a claim changed rather than a predicate; everywhere else the description already stated what the new predicate says. The high-affinity reading comes from the node's own description, which is not itself sourced. Either attach a reference that states the high-affinity/low-O2 property, or keep the reversal and write a description the retained quote actually supports. 🟡
|
There was a problem hiding this comment.
Two 🟡 items. (1) data/traits/environment/facultative_oxygen_preference.yaml:133-143 — the reversed cytochrome bd edge kept evidence (DOI:10.1128/jb.00389-22) that supports acceptor-dependent branch switching, not the rewritten claim about high affinity at low O2 tensions; the notes clause still describes the deleted edge. (2) The new powers row grounds seven edges, not five — reports/predicate_grounding_residual.tsv goes 6 unmapped -> 4 blocked_by_node_type, picking up ph_optimum_mid2.yaml (pmf -> f0f1_atp_synthase) and carboxydotrophic.yaml (pmf -> atp_synthase). Both read correctly, but the proposal's counts and its round-trip plan ('the five edges' predicate_id') would miss them at minting time. Full review in the PR comment.
…ounds (#355 review) Four findings, all correct. THE REVERSED CYTOCHROME BD EDGE. This was the one place in the PR where a CLAIM changed rather than a predicate, and I rewrote it to "high-affinity terminal oxidase, sustaining aerobic respiration at the low O2 tensions where low-affinity oxidases cannot operate" while leaving the evidence untouched. The retained quote is about WHICH oxidase is active given the available acceptor and says nothing about affinity or low O2, so the new assertion was uncited; the high-affinity reading came from the node's own description, which is not itself sourced. Rewritten to what the quote actually supports rather than attaching a citation I do not have, and the evidence note's trailing clause, which still described the deleted edge, now describes this one. POWERS GROUNDS SEVEN EDGES, NOT FIVE. Verified rather than taken on faith: the gate also matches ph_optimum_mid2.yaml and carboxydotrophic.yaml, whose proton_motive_force -> ATP synthase edges predate #334 and read correctly under the definition. My own reports showed it and I did not read them. Nothing needs regrounding, but three statements understated it and the round-trip plan is the one that would have cost someone: it said the placeholder lives in "the five edges' predicate_id", so whoever swaps in the minted ID would have missed two that a grounding pass had stamped. Counts corrected in the mapping note, the ROBOT template and the proposal, with both records named. While counting I also confirmed the gate behaves as designed: of the four `powers` edges left blocked_by_node_type, THREE are blocked correctly - their objects are processes, which satisfy enables' range and belong on RO:0002327. The fourth is blocked only because phototrophic.yaml types proton_motive_force as CAPACITY where carboxydotrophic.yaml types the byte-identical assertion's subject STATE. That concept carries four different node types across the corpus and this PR is what turned the inconsistency into something that decides groundings, so it is filed as #356 and referenced from the proposal rather than left to be rediscovered. HFQ. The migration argued this edge does not assert anything about translation - the object is the sigma factor - and then left the description reading "Hfq enables sRNA-dependent translation of RpoS". True sentence, wrong edge. Now states what `promotes` asserts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All four correct. The second one I could have caught by reading my own reports. 🟡 The reversed cytochrome bd edgeYou're right that this was the one place in the PR where a claim changed rather than a predicate, and I didn't hold it to the standard the other fifteen met — everywhere else the description already stated what the new predicate says, so the evidence carried over intact. Here I wrote a new claim and left the citation behind. Fixed by writing what the retained quote supports, rather than attaching a citation I don't have:
The evidence note's trailing clause — which still described the deleted edge — now describes this one. The high-affinity reading came from the node's own description, which isn't itself sourced, so it doesn't belong in an edge claim. 🟡
|
|
Claude finished @realmarcin's task in 3m 6s —— View job Re-review — PR #355Checked commit
🟡 Cytochrome bd — resolvedThe description is now "Cytochrome bd is one of the terminal oxidases through which aerobic respiration proceeds; which terminal enzyme is active depends on the final electron acceptor available." The second clause is what 🟡
|
There was a problem hiding this comment.
Both 🟡 items resolved.
Cytochrome bd — the description now states what DOI:10.1128/jb.00389-22 supports (acceptor-dependent terminal branches) instead of the unsourced high-affinity/low-O2 claim, and the notes clause describes the surviving edge rather than the deleted one.
powers counts — re-counted independently: 11 powers edges corpus-wide, 4 residual, so 7 ground across 6 records, matching the corrected proposal. Three of the four blocked are blocked correctly (process/pathway objects belong on RO:0002327); the fourth is #356. The round-trip plan now names seven edges. (phototrophic.yaml missing from the residual report row is the example_files cap of 3 in scripts/ground_causal_predicates.py:224, not a miscount.)
🔵 One residual, non-blocking: proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3 — the observations column still says 'all five corpus objects are protein complexes' (contradicting covers 7 in the adjacent column), and the example quote 'Ion motive force powers rotation of the flagellar motor' now sits after the clause about the two pre-existing edges, reading as an example of them when it is motile.yaml's. proposal.md got both right; only the upstream-bound ROBOT row is stale.
Baseline churn verified as key moves only, pages regenerated in the same commit, CI green.
#325 landed as "enforce it", and I said in this PR's review response that if it did, this change is one of the ones that should get a retrospective record. Following through rather than leaving main with a grandfathered gap on the first day the gate exists. ONE record for the whole change, per the granularity #325 settled, and targeting the migration script rather than any of the 15 records it edited: the script IS the artifact that says what drove the change, and it is reviewable in a way that 15 copies of the same sentence are not. The per-file curation_history: blocks stay as they are. They record what changed in each file; this records which model, which tool, which issue and which PR - none of which CurationEvent has a slot for. Scaffolded with `just new-history` and validated with `just validate-history` (no issues found). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-review — PR #355 (new commit)
|
The previous commit staged it by accident: I switched branches with #325's working-tree changes still present, and `git add history/` picked up the README edit along with the record I meant to add. That edit is #325's policy change and belongs in #325's PR, not here. Restored to main's content, forward-only. The commit that introduced it is already pushed, and rewriting a pushed branch to hide a mistake is worse than a two-commit trail that shows it. The history record itself, which IS this PR's business, stays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @realmarcin's task in 3m 3s —— View job Re-review — PR #355 (commits
|
| n | idiom | edges |
|---|---|---|
| 1 | repoint, keep enables |
ph_range_high cytoplasmic_na |
| 1 | reversed | facultative_oxygen_preference |
| 3 | retyped | human_pathogen, gas_vesicle (both → confers), cell_width_large (keeps enables) |
| 3 | produces / has output / transports |
piezophilic, chemolithoheterotrophic, sphere_shaped |
| 2 | part of / promotes |
filament_shaped, stress_response |
| 5 | powers |
the five |
| 1 | manifests as |
intracellular_inclusion → METPO:2007400 |
Two errors, and they nearly cancel: the repoint count is 1 rather than 4, and the manifests as edge is missing from the enumeration entirely. Corrected, it reads 1+1+3+3+2+5+1 = 16.
Worth fixing now rather than shrugging at, because history/README.md says a record is "written once and never edited afterwards" — a correction after merge costs a whole second record. It's one sentence.
(The PR body's table carries the same two errors; that one is editable.)
Everything else the record claims checks out
- "16 edges across 15 trait records" — 16
DECISIONSentries over 15 distinct files. ✔ - "Selection rule: every edge reported … as
ENABLES_RANGE_VIOLATION" —conf/predicate_domain_audit_baseline.tsvdrops exactly 16 rows, allENABLES_RANGE_VIOLATION, and their file/graph pairs are theDECISIONSkeys withmotile.yamlappearing twice as it should. ✔ - "audit-graphs unchanged at 1533" — baseline is 1533 lines, 11 changed / 0 net. ✔
- Vocabulary (
EDIT/changed,kind: infrastructure, explicit--pathfor a non-recordkind) follows the README's own rules. ✔
🔵 Still open from last round
proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3 — observations still reads "all five corpus objects are protein complexes" against covers 7 causal-graph edges two columns left, and the motile.yaml quote still sits after the clause about the two pre-existing edges. Unchanged since I raised it; still non-blocking, still the row that goes upstream.
Requesting changes on the 🟡 only because the file it's in is the one this repo declares immutable after write. Nothing else in the two commits needs touching.
There was a problem hiding this comment.
🟡 The new history record's idiom tally sums to 18 for a change it says repaired 16 edges.
history/infrastructure/enables-range-migration/2026-08-08T003430Z-claude-code-9da277.yaml — 4 repointed … 1 reversed … 3 retyped … 3 produces/has output/transports … 2 part of/promotes … 5 powers = 18, two lines below Repaired 16 edges across 15 trait records. Per DECISIONS in the migration script the repoint count is 1, not 4, and the manifests as edge (intracellular_inclusion -> METPO:2007400) is missing from the enumeration entirely. Corrected it reads 1+1+3+3+2+5+1 = 16.
Raising it rather than shrugging because history/README.md says a record is 'written once and never edited afterwards' — a correction after merge costs a second record. It is one sentence. The PR body's table carries the same two errors.
Everything else in the two commits checks out: the README revert is a true no-op against main, the record validates and matches the layout and shape of existing records, the presence check is satisfied, and the record's other claims (16 edges / 15 files, the ENABLES_RANGE_VIOLATION selection rule, audit-graphs unchanged at 1533) verify against the diff. CI green. Targeting the migration script rather than the 15 trait records is the right call.
🔵 Still open, non-blocking: proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3 observations still says 'all five corpus objects' against 'covers 7' in the adjacent column, and the motile.yaml quote still reads as an example of the two pre-existing edges.
Full review in the PR comment.
The record said "Six idioms: 4 repointed ... 5 powers", which sums to 18, two lines below "Repaired 16 edges". Two errors that nearly cancelled: the repoint count was 1 rather than 4, and the manifests as edge (intracellular_inclusion) was missing from the enumeration entirely. Recounted from DECISIONS, which is the source of truth: 1 repoint, keep enables ph_range_high 1 reversed facultative_oxygen_preference 3 retyped human_pathogen, gas_vesicle, cell_width_large 3 produces / has output / transports 2 part of / promotes 1 manifests as intracellular_inclusion 5 powers =16 Fixed in the record NOW rather than after merge, because history/README.md says a record is written once and never edited - a correction afterwards costs a whole second record, and the reviewer was right that this is the one artifact here where that is true. The same two errors were in the PR body table and, worse, in docs/CURATION_PLAYBOOK.md, which asserted "four of #334's sixteen needed only repointing" as guidance for future curators. Corrected: one needed only that, and a second was fixed by reversing it onto a node already present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Correct, and thank you for catching it in the one artifact where it couldn't be fixed later. Recounted from
Your diagnosis was exact — two errors that nearly cancelled: the repoint count was 1, not 4, and the The PR body table is corrected too. One thing you didn't flag, and it's the worse instance. The same two errors were in
|
|
Claude finished @realmarcin's task in 2m 48s —— View job Re-review — PR #355 (commit
|
There was a problem hiding this comment.
🟡 Two more #334 counts in docs/CURATION_PLAYBOOK.md, both added by this PR and both in the paragraph ef340806 just corrected.
Line 177: "five of the sixteen edges had the right predicate and the wrong node type" — per DECISIONS three edges were retyped (treatment_resistance_persistence, buoyancy, even_pg_distribution) and only one of those kept its predicate (cell_width_large, which stays on enables); the other two also moved to confers. The sentence's own second example, "tolerance to antimicrobial treatment", is treatment_resistance_persistence — whose predicate did change — so it is not an instance of the claim it illustrates.
Line 185: "Twice in #334 the edge was simply backwards" — once. facultative_oxygen_preference is the only DECISIONS entry that rewrites subject, and the corrected line 194 nine lines below already describes that same edge from the other side.
This is the file you rightly called the worse place for a wrong count, since it is guidance rather than record.
The 🟡 from the last round is resolved: the record now reads 1+1+3+3+2+1+5 = 16, re-derived independently from DECISIONS (16 entries / 15 files, one object-only repoint, one subject rewrite, three retype blocks, five powers). just validate-history clean, CI green on all seven runs, no data artifact touched by this commit.
🔵 Still open, non-blocking: proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3 observations still says 'all five corpus objects' against 'covers 7' in the adjacent column, and the motile.yaml quote still reads as an example of the two pre-existing edges. 🔵 The record now says 'Seven idioms' while the script it targets still says 'Six distinct idioms' and numbers them 1–6.
Full review in the PR comment.
Both wrong, both added by this PR, both in the paragraph the last commit had just edited without checking the rest of it. "five of the sixteen edges had the right predicate and the wrong node type" - three had the wrong node type, and of those exactly ONE also had the right predicate (cell_width_large, which keeps enables). The other two moved to confers as well. The reviewer's sharpest point is that the sentence's own second example undercut it: "tolerance to antimicrobial treatment" is treatment_resistance_persistence, whose predicate DID change, so it was never an instance of the thing the sentence claimed. Rewritten so the number supports the heading it sits under - check the node type first, because rewriting the predicate around a mistyped node preserves the error and hides it. "Twice in #334 the edge was simply backwards" - once. Only facultative_oxygen_preference rewrites a subject, and it is the one the sentence then quotes. Line 194, which I had just corrected, already said as much from the other side. This is the third round of counting errors in the same file, so I stopped counting by hand and re-derived every #334 claim the playbook now makes straight from DECISIONS: three of the sixteen had wrong node type True one of those needed only a retype True once backwards True one needed only repointing True The playbook is the artifact where a wrong number misdirects a future curator rather than merely misrecording history, which is why it deserved that check before the first correction rather than after the second. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both correct, both mine, and both in the paragraph I'd just edited without re-reading the rest of it. 1. "five of the sixteen edges had the right predicate and the wrong node type." Three had the wrong node type, and of those exactly one also had the right predicate — Your sharpest point is that the sentence's own second example undercut it: "tolerance to antimicrobial treatment" is 2. "Twice in #334 the edge was simply backwards." Once. Only This is the third round of counting errors in the same file, so I stopped counting by hand and re-derived every That check belonged before the first correction, not after the second — and you're right that this file is where it matters, since a wrong number here misdirects a future curator rather than merely misrecording what happened.
|
|
Claude finished @realmarcin's task in 2m 8s —— View job Re-review — PR #355 (commit
|
| playbook claim | DECISIONS |
|---|---|
| three of the sixteen had the wrong node type | 3 retype blocks — treatment_resistance_persistence, buoyancy, even_pg_distribution ✔ |
| for one of those the predicate was already right | cell_width_large has no predicate key, keeps RO:0002327 ✔ |
the other two needed confers as well |
both carry METPO:2007700 ✔ |
| once backwards | one entry rewrites subject ✔ |
| one needed only repointing | one entry rewrites object alone ✔ |
Both illustrative quotes are verbatim against the node descriptions they come from, and the second one is no longer an instance of a claim it contradicts. The high-affinity phrase on line 188 is quoting the node's own description as evidence of direction, which is the right use of it and consistent with how the cytochrome bd edge was resolved. just qc, pytest, validate-strict, history, pr-sanity, vendored-sync all green; no data artifact touched by this commit.
🔵 The migration script still carries the description that was removed as uncited
scripts/migrate_enables_range_edges.py:68-70. The DECISIONS entry for the reversed cytochrome bd edge still writes
Cytochrome bd is a high-affinity terminal oxidase, sustaining aerobic respiration at the low O2 tensions where low-affinity oxidases cannot operate.
which is the sentence c8b30ab2 took out of the YAML for being unsupported by the retained quote. The corpus is correct; the script isn't, and re-running it wouldn't reveal that (it would fail MISSING EDGE on the old subject/object).
Worth a line only because this file is target.path in an immutable history record and is what you and I have both been treating as the source of truth for what this migration decided. Same class as the ROBOT row below — stale text in an artifact, not wrong data.
🔵 Still open, unchanged (fourth round)
proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3 — observations still says "all five corpus objects are protein complexes" against covers 7 causal-graph edges across 6 trait records two columns to its left, and motile.yaml's quote still sits after the clause about the two pre-existing edges. proposal.md has both right.
🔵 "Seven idioms" vs "Six distinct idioms"
scripts/migrate_enables_range_edges.py:10 still numbers them 1–6 with manifests as folded into idiom 6, while the record now says seven.
Approving. The playbook now says what DECISIONS says, which is the thing that mattered — a wrong count there misdirects, and this was the file where it had gone wrong three times. The three remaining items are free-text staleness in artifacts whose data is correct; fix them if you're pushing again, but none is a reason to hold the merge.
· fix/334-enables-range
There was a problem hiding this comment.
Both 🟡 from the last round are fixed, and I re-derived the corrected playbook numbers from DECISIONS rather than from the commit message: three retype blocks (treatment_resistance_persistence, buoyancy, even_pg_distribution), exactly one of which keeps enables (cell_width_large) while the other two carry METPO:2007700; one subject rewrite; one object-only repoint. Both illustrative quotes are verbatim against the node descriptions they come from, and the second is no longer an instance of a claim it contradicts. CI green; no data artifact touched by this commit.
🔵 Three non-blocking residuals, all free-text staleness in artifacts whose data is correct:
scripts/migrate_enables_range_edges.py:68-70— the DECISIONS entry for the reversed cytochrome bd edge still writes the 'high-affinity ... low O2 tensions where low-affinity oxidases cannot operate' description that c8b30ab removed from the YAML as unsupported by the retained quote. The corpus is right; the script disagrees with it, and re-running would fail MISSING EDGE rather than surface that. Worth a line only because this file istarget.pathin an immutable history record and is the cited source of truth for what the migration decided.proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3— observations still says 'all five corpus objects are protein complexes' against 'covers 7 causal-graph edges across 6 trait records' two columns left, and motile.yaml's quote still reads as an example of the two pre-existing edges. proposal.md has both right.scripts/migrate_enables_range_edges.py:10still says 'Six distinct idioms' and numbers them 1-6 while the record now says seven.
Approving. The playbook now says what DECISIONS says, which is what mattered — a wrong count there misdirects a curator rather than merely misrecording history, and that is the file it had gone wrong in three times.
… (#357) * Make a history record required, once it is one record per change (#325) DECISION: enforce it, after fixing the granularity that made enforcing it unreasonable. The issue asked two things: whether a bulk migration warrants a record at all, and whether it should be enforced. Answering the second first was the mistake available here - as written the convention could not be enforced without doing damage. WHY NOT DROP IT. I had leaned toward folding everything into the per-file curation_history: block as duplication. That is wrong, and the README's own justification (merge-conflict avoidance) is not what distinguishes the two - curation_history is per-file, so it does not conflict either. What distinguishes it is that HistoryOutcomeEnum has `no_change`. A session that audited a trait and correctly concluded nothing needed changing produces no file edit, so curation_history has nothing to attach to and the work is invisible. Same for `blocked` and `needs_followup`. Add model, agent_tool, agent_version and structured issue/PR links, none of which CurationEvent has a slot for, and it is a real capability rather than a second copy. WHY THE GRANULARITY HAD TO CHANGE FIRST. "One record per session per target" is right for hand curation, where session and target coincide - the sulfur_globule record is a paragraph of real reasoning about one graph. Read literally it also makes a 128-file migration owe 128 near-identical stubs, burying the three substantive records the directory exists for. That is destroying the signal in the name of provenance, and blocking on it would have been a fair thing to route around. So: one record per CHANGE, targeting the migration script, which IS the artifact that says what drove it and is reviewable in a way that 128 copies of one sentence are not. No schema change - `infrastructure` and `other` kinds already exist. WHY BLOCKING. The old comment said a hard gate "trains people to route around it". The measurement disagrees: of 134 commits that modified trait records, 2 added a history record. Nobody routed around the gate because there was no gate. Meanwhile 275 trait records carry an issue number hand-typed into a `changes` string - the same provenance in a form nothing can query, and my own 334 commits did it, which is how I know it is substitution rather than forgetting. The rule lives in scripts/audit_history_records.py, not in workflow YAML. Shell embedded in a workflow cannot be unit-tested or run locally before pushing, which is how this repo keeps ending up with gates nobody can exercise. Five tests, including one through the CLI, since the workflow calls the CLI and a wiring error would leave the rule correct and unused. Two bugs found while wiring it, both mine: - `set -euo pipefail` plus a `just ... | tee` pipeline aborts the step the moment the audit fails, so the job summary would never be written - losing the remediation text on exactly the runs that need it. Now `|| status=$?`. - the job never installed `just`. Added setup-just, same pinned action the sibling pr-checks-present.yaml uses. CANARY, against real diffs rather than fixtures: A fix/334-enables-range vs main 15 traits, 0 records -> exit 1 B this branch vs main 0 traits, 0 records -> exit 0 C A plus one scaffolded record 15 traits, 1 record -> exit 0 C used `just new-history` for real (the no-claw fallback) and the record passed `just validate-history`. That record is now on PR #355, so main does not get a grandfathered gap on the day the gate lands. Deliberately NOT checked: that the record is ABOUT the change. The cheap proxy - does target.path name a changed file? - is wrong for exactly the migration case, where the honest target is the script rather than any record it edited. The guard against an empty record is validate-history, which fails while the --details TODO placeholder is unfilled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix the pathspec, and stop three places stating the superseded rule (#357 review) Both findings correct, and the second is a live bug rather than prose. THE PATHSPEC UNDER-MATCHED. A git pathspec is not a shell glob: git's `*` already crosses `/`, so `data/traits/**/*.yaml` must STILL consume the literal slash in `**/` and therefore requires at least one intervening directory. Verified in this checkout: git ls-files -- 'scripts/**/*.py' -> 0 git ls-files -- 'scripts/*.py' -> 41 Nothing bypasses it today because all 477 trait files sit under a category directory. But the workflow's own trigger is `paths: data/traits/**`, which is GitHub Actions semantics and DOES match a top-level file - so a trait added at data/traits/x.yaml would START the job and then clear the gate reporting "0 trait records changed". Silently permissive, which is the failure the test file's docstring claims to pin against. Proved it with a probe file: the `**` form saw 0, the `*` form saw 1. Same shape on the other side: a record at history/foo.yaml would not have counted toward presence. Both globs lose the `**/`. `data/traits/*.yaml` is strictly more inclusive and matches the same 477 files today. The reviewer's sharper point was WHY the unit tests could not see this: the glob lives in collect(), which had no tests at all, while missing_record() was thoroughly covered. Two tests now build a real git repo with a top-level and a nested trait and assert collect() returns both. Confirmed they FAIL against the old globs and pass against these, so they pin the fix rather than restate it. THE SUPERSEDED RULE SURVIVED IN THREE PLACES, which is this PR's own argument turned on itself - an unenforced convention drifts, and here the drift was on day one in the artifacts a reader hits first: - history/README.md's lede, 73 lines above the section that re-scopes it. - the justfile comment sitting directly above `new-history`, the command the failure message tells people to run. - src/traitmech/schema/history.yaml, which states the OPPOSITE policy verbatim: "Presence of a record is *advisory* - CI warns, it does not block, because a hard gate ... trains people to route around it." The first two are TraitMech's own and are fixed. The schema is NOT edited here on purpose: it is vendored byte-identical from claw, which is private and unreachable from this repo's CI, and `src/traitmech/schema/history.yaml` is not in check_vendored_sync.sh's checked set - so a one-copy edit would create drift nothing detects, which is the gap #209 tracks. Filed as #358 and documented in the README, so the divergence is stale by design rather than by neglect. SOFTENED THE OVER-CLAIM. I said three times that scaffolding an empty record "does not work". The guard is a schema pattern rejecting the literal "TODO: replace this placeholder" prefix and nothing else - `--details 'see PR'` passes. That is the design (this gate asks whether provenance was recorded, not whether it was recorded well), but stating it as closed invites someone to rely on it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

Closes #334.
ENABLES_RANGE_VIOLATION16 → 0.biolink gives
enables(RO:0002327) the range biological process or activity, which ofCausalNodeTypeEnumonlyBIOLOGICAL_PROCESS,PATHWAYandMOLECULAR_FUNCTIONsatisfy. #315 widened the audit from its original TRAIT-only test to the full range and surfaced 33 edges; #351 took 17. These are the last 16.There was no single sweep
The issue said as much, and it's right: six idioms are mixed together, and the object type does not classify them — the same lesson
reducesrecorded when its two senses turned out not to be separable that way. Every decision lives inDECISIONSin the migration script, each with the sentence from the record that justifies it.enablesproduces/has output/transportspart of/promotesmanifests aspowersA few worth naming:
cytoplasmic_napointed at the Mrp complex while the record said "the substrate that sustains alkaliphile antiport activity" — and that antiport node sits three lines above it.molecular_oxygen enables terminal_oxidase_cytochrome_bdwas backwards, and the node's own description said so: "high-affinity terminal oxidase enabling oxygen respiration at low O2 tensions."buoyancywas typedQUALITYhere whileintracellular_inclusion.yamlalready typed the same conceptTRAIT. That's a typing inconsistency of exactly The corpus types 'capacity to X' two ways: TRAIT in some records, CAPACITY/STATE in others #352's shape; resolved towardTRAIT.ftsW_flippase enables lipid_ii— a flippase flips its substrate. That'stransports.The one new term
METPO:2007900powers, proposed inproposals/metpo_traitmech_v10— an ion motive force, a proton motive force, or torque generation, operating a molecular machine.RO has no relation for it because RO models energetics at the process level while all five objects are protein complexes. That is not a new observation here — it's the same gap
METPO:2007804(exports) andMETPO:2007805(imports) were minted for, whose note already reads "RO models transport at the process level, so no RO relation admits the GENE_OR_PROTEIN subject these edges use."The label is not a coinage. All five edge descriptions already used the verb:
object_typesis gated toGENE_OR_PROTEINon purpose: an energetic driver feeding a process satisfiesenables' range already and must stay onRO:0002327. Leaving the gate open would let this term absorb edges that aren't broken, and the violation it exists to fix would stop being visible.Two gates widened deliberately, both recorded in the table:
GENE_OR_PROTEINadded totransports' subjects (a flippase is a protein, not a pathway), andTRAITadded toconfers' (a structural trait conferring a functional disposition — the corpus already carried it and lacked a subject type admitting it).Structural edits, not textual
All 15 files round-trip byte-identically through
emit_trait_yaml, verified before touching them, so they're parsed, modified and re-emitted. Earlier migrations spliced raw lines with regexes, broke YAML three times, and once turned "proper polar growth" into "proper powth".Two mistakes I made and caught
Both in
predicate_grounding.tsv, both found by reading the diff rather than the exit code:csv.writer(escapechar='\\')rewrote the""quote-doubling in four unrelated rows into\"— corrupting data it had no business touching, which is the failure this file exists to prevent. The tell was a 8-insertion/7-deletion diff for what should have been 2 edits and 1 addition.Redone with
QUOTE_MINIMALand the columns copied off a real row. The diff is now exactly 2 changed rows + 1 added.Measured, not predicted
Graph health is unchanged — the repairs repointed edges without stranding a node. The only baseline churn is key moves: two evidence-snippet findings whose edge key changed, and ten
gas_vesicleUNREACHABLEdetails that now name both TRAIT anchors becausebuoyancybecame one. Same node set, same counts, verified by diffing them rather than trusting the totals.just qcgreen · 502 tests pass · ruff clean ·just verify-proposal metpo_traitmech_v10PASS.