Skip to content

Repair the last 16 enables edges whose object cannot satisfy the range (#334) - #355

Merged
realmarcin merged 6 commits into
mainfrom
fix/334-enables-range
Aug 8, 2026
Merged

Repair the last 16 enables edges whose object cannot satisfy the range (#334)#355
realmarcin merged 6 commits into
mainfrom
fix/334-enables-range

Conversation

@realmarcin

@realmarcin realmarcin commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Closes #334. ENABLES_RANGE_VIOLATION 16 → 0.

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 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 reduces recorded when its two senses turned out not to be separable that way. Every decision lives in DECISIONS in the migration script, each with the sentence from the record that justifies it.

n idiom fix
1 the process the subject feeds was already in the graph repoint, keep enables
1 the edge was backwards reverse onto the existing process node
3 wrong node type, not wrong predicate retype
3 an existing relation fits better produces / has output / transports
2 never causal at all part of / promotes
1 a presentation, not a consequence manifests as
5 nothing upstream fits mint powers

A few worth naming:

  • cytoplasmic_na pointed 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_bd was backwards, and the node's own description said so: "high-affinity terminal oxidase enabling oxygen respiration at low O2 tensions."
  • buoyancy was typed QUALITY here while intracellular_inclusion.yaml already typed the same concept TRAIT. 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 toward TRAIT.
  • ftsW_flippase enables lipid_ii — a flippase flips its substrate. That's transports.

The one new term

METPO:2007900 powers, 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. That is not a new observation here — it's the same gap METPO:2007804 (exports) and METPO: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:

"Ion motive force powers rotation of the flagellar motor."
"Ion flux through stator complexes powers torque generation."
"Proton motive force can power gliding motility motors."

object_types is gated to GENE_OR_PROTEIN on purpose: an energetic driver feeding a process satisfies enables' range already and must stay on RO: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_PROTEIN added to transports' subjects (a flippase is a protein, not a pathway), and TRAIT added to confers' (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:

  1. 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.
  2. I filled the new row's columns 4–6 from their names. They are the SSSOM mapping predicate, source ontology and match confidence — not the causal predicate.

Redone with QUOTE_MINIMAL and the columns copied off a real row. The diff is now exactly 2 changed rows + 1 added.

Measured, not predicted

ENABLES_RANGE_VIOLATION        16 -> 0
MICROBE_DOMAIN_ON_NONORGANISM   0 -> 0
audit-graphs                 1533 -> 1533   (218 FRAGMENTED, 1303 UNREACHABLE)

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_vesicle UNREACHABLE details that now name both TRAIT anchors because buoyancy became one. Same node set, same counts, verified by diffing them rather than trusting the totals.

just qc green · 502 tests pass · ruff clean · just verify-proposal metpo_traitmech_v10 PASS.

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

culturebot-reviewer Bot commented Aug 7, 2026

Copy link
Copy Markdown

Claude finished @realmarcin's task in 5m 36s —— View job


Review — PR #355

Checked 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 mappings/predicate_grounding.tsv, the retypes' knock-on edges, and the baseline diffs. The gating all holds — powers (BP|STATE → GENE_OR_PROTEIN) matches all five subjects/objects, transports matches FtsW/lipid II, confers matches both retyped TRAIT objects, has output matches the PATHWAY subject, and the two repointed edges land on nodes that exist and are range-correct. METPO:2007900 is free in data/raw/metpo.owl and doesn't collide with the v8/v9 blocks. CI is green.

Two things to fix.


🟡 The reversed cytochrome bd edge kept evidence that no longer supports it

data/traits/environment/facultative_oxygen_preference.yaml:133-143

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.

Fix this →


🟡 powers grounds seven edges, not five

mappings/predicate_grounding.tsv:111 · proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3 · proposals/metpo_traitmech_v10/proposal.md

The new row also matches two pre-existing corpus edges that were never part of #334, and your own reports show it: reports/predicate_grounding_residual.tsv goes from powers 6 unmapped to powers 4 blocked_by_node_type, and reports/biolink_coverage.tsv moves powers from residual …6 to applied … METPO:2007900 plus residual …4. The two that got picked up are

  • data/traits/environment/ph_optimum_mid2.yamlproton_motive_force (STATE) → f0f1_atp_synthase (GENE_OR_PROTEIN)
  • data/traits/physiology/carboxydotrophic.yamlproton_motive_force (STATE) → atp_synthase (GENE_OR_PROTEIN)

Both read correctly under the new definition, so this isn't a wrong grounding — but three statements are now understated: traits_addressed says "covers 5 causal-graph edges across 4 trait records", the mapping note says "the five edges", and the round-trip plan says the placeholder is "confined to those two places" and "the five edges' predicate_id". When METPO mints the real ID, whoever follows that plan will miss the two edges a grounding pass will have stamped in the meantime.

Correcting the counts and naming the two extra records is enough; nothing needs regrounding.


🔵 The new gate is load-bearing on an inconsistency it didn't create

proton_motive_force is typed four different ways across the corpus — STATE in gliding.yaml/carboxydotrophic.yaml, BIOLOGICAL_PROCESS in electron_transfer.yaml/ph_delta_mid1.yaml, CHEMICAL in metabolism.yaml/oxygenic_photosynthesis.yaml, CAPACITY in phototrophic.yaml. That's why phototrophic.yaml's proton_motive_force powers atp_synthase stays blocked_by_node_type while the byte-identical assertion in carboxydotrophic.yaml grounds. Pre-existing, #352's shape, and out of scope here — but this PR is what makes it decide groundings, so it's worth an issue reference rather than leaving it to be rediscovered.

🔵 hfq -promotes-> rpos still describes itself as enabling translation

data/traits/physiology/stress_response.yaml:148-157. The rationale for the change is that the object is the sigma factor and not the translation — but the description still reads "Hfq enables sRNA-dependent translation of RpoS", which is the assertion the migration argued this edge doesn't make. True sentence, wrong edge.

Advisory

No history/records/ entry for a change touching 15 trait records. CI's curation-history check is warn-only and the inline curation_history blocks are thorough, so this is a note, not a defect.


The rest is solid work — in particular, retyping even_pg_distribution and treatment_resistance_persistence rather than rewriting their predicates is the right call and is the harder one to make, and parsing/re-emitting through emit_trait_yaml instead of splicing lines is visibly the correct instinct given what regex migrations did to this corpus before.

Requesting changes on the two 🟡 items.
· fix/334-enables-range

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

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>
@realmarcin

Copy link
Copy Markdown
Contributor Author

All four correct. The second one I could have caught by reading my own reports.

🟡 The reversed cytochrome bd edge

You'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:

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

🟡 powers grounds seven edges, not five

Verified rather than taken on faith — exactly seven ground, and the two extra are the ones you named:

GROUNDS (7):   ph_optimum_mid2   proton_motive_force(BIOLOGICAL_PROCESS) -> f0f1_atp_synthase
               carboxydotrophic  proton_motive_force(STATE)              -> atp_synthase
               + the 5 from #334
BLOCKED (4):   aerobic, anoxygenic_photosynthesis, photosynthesis  <- process objects, correct
               phototrophic  proton_motive_force(CAPACITY)         <- typing, not design

Both read correctly under the definition, so nothing is regrounded. But you've identified the one that would have cost someone real time: the round-trip plan said the placeholder lives in "the five edges' predicate_id", so whoever swaps in the minted METPO ID would have missed two that a grounding pass had stamped in the meantime. Counts corrected in the mapping note, the ROBOT template and the proposal, with both records named.

Counting this also confirmed the gate does what it was designed to: three of the four blocked edges are blocked correctly — their objects are processes, which satisfy enables' range and belong on RO:0002327. That's the object_types gate earning its keep.

🔵 The gate is load-bearing on an inconsistency it didn't create

Agreed, and filed as #356 with the four typings laid out, referenced from the proposal. Your framing is the right one — a gate is the correct mechanism; what's wrong is that the corpus doesn't agree with itself about what a proton motive force is. I noted there that a check for one node_id carrying multiple node_types across records would catch this whole family and doesn't exist yet.

🔵 hfq

Caught cleanly: the migration's own rationale was that this edge asserts nothing about translation, and then I left the description saying it did. True sentence, wrong edge. Now states what promotes asserts.

Advisory on history/records/

Noted, and it's the open policy question in #325 rather than an oversight — #351, the previous half of this same issue, also added thorough inline curation_history and no history/records/ entry. If #325 lands as "enforce it", this change is one of the ones that should get a retrospective record.

just qc green · 512 tests pass · just verify-proposal metpo_traitmech_v10 PASS.

@culturebot-reviewer

culturebot-reviewer Bot commented Aug 7, 2026

Copy link
Copy Markdown

Claude finished @realmarcin's task in 3m 6s —— View job


Re-review — PR #355

Checked commit c8b30ab2 against the four items from the last round. Both 🟡 are resolved; both 🔵 too.


🟡 Cytochrome bd — resolved

The 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 DOI:10.1128/jb.00389-22 actually says; the first is the node's own definition, not a sourced property. The unsourced high-affinity/low-O2 claim is gone, and the notes clause now describes the surviving edge rather than the deleted one. Writing to the quote instead of hunting a citation for the stronger claim was the right call.

🟡 powers counts — resolved, and I re-counted independently

Eleven powers edges in the corpus; four residual per reports/predicate_grounding_residual.tsv, so seven ground. Records: motility, gliding, flagellated, motile (×2), plus ph_optimum_mid2 and carboxydotrophic — 7 edges, 6 records, matching what the proposal now says. The four blocked are as described: aerobic (atp_synthesis, BIOLOGICAL_PROCESS), photosynthesis (electron_transport, BIOLOGICAL_PROCESS), anoxygenic_photosynthesis (photosynthetic_etc, PATHWAY) — all three correctly blocked, their objects satisfy enables' range — and phototrophic, blocked by its CAPACITY subject, which is #356.

(phototrophic.yaml not appearing in that report row is the example_files cap of 3 in scripts/ground_causal_predicates.py:224, not a miscount.)

The round-trip plan — the part that would actually have cost someone — now reads "seven edges' predicate_id (the five from #334 plus the two named above)".

🔵 Two residuals in the ROBOT template's observations

proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3. The traits_addressed column is corrected, but the free-text column beside it wasn't fully brought along:

  1. "RO models energetics at the process level while all five corpus objects are protein complexes" — contradicts covers 7 causal-graph edges two columns to its left in the same row. All seven objects are protein complexes, so it's just the stale number.
  2. The example quote got detached from its clause: "…every one of the five 33 enables edges point at non-activity objects, violating RO:0002327's range #334 edge descriptions already used the verb 'power'; the gate also picks up two pre-existing proton_motive_force -> ATP synthase edges ('Ion motive force powers rotation of the flagellar motor')." That quote is motile.yaml's, one of the five — it now reads as an example of the two pre-existing edges, which it isn't.

proposal.md got both of these right; only the ROBOT row is stale. It's the artifact that goes upstream, which is the only reason it's worth naming.

Verification

Key moves only in the baselines, confirmed by reading them: molecular_oxygen->terminal_oxidase_cytochrome_bdterminal_oxidase_cytochrome_bd->aerobic_respiration and cytoplasmic_na->mrp_antiporter_complexcytoplasmic_na->extreme_alkaliphile_antiport in conf/evidence_snippet_baseline.tsv, no findings added or dropped. Both changed YAMLs have regenerated pages in the same commit. CI green across qc, pytest, validate-strict, pr-sanity, label-correspondence, curation-history.


Approving. The 🔵 is a two-word correction in a free-text column, not a reason to hold this. Sixteen edges closed with the object type explicitly rejected as the classifier, every decision carrying the sentence from the record that justifies it, and a new term whose gate is narrow enough that the violation it fixes stays visible — that last part is the thing most easily gotten wrong, and it wasn't.
· fix/334-enables-range

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

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

culturebot-reviewer Bot commented Aug 8, 2026

Copy link
Copy Markdown

Re-review — PR #355 (new commit)

  • Read the new commit c43d1a85 (history record + history/README.md)
  • Check the history record against the schema / existing records
  • Verify the record's factual claims against the diff it describes
  • Re-check the outstanding 🔵 on the ROBOT template row
  • Confirm nothing else regressed

View job run

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

culturebot-reviewer Bot commented Aug 8, 2026

Copy link
Copy Markdown

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


Re-review — PR #355 (commits c43d1a85, 57e2321a)

  • Read the two new commits (history record; history/README.md revert)
  • Check the history record against schema / existing records
  • Verify the record's factual claims against the diff it describes
  • Confirm history/README.md is byte-identical to main
  • Re-check the outstanding 🔵 on the ROBOT template row
  • Confirm nothing else regressed

The README revert is clean — git diff origin/main...HEAD -- history/README.md is empty, so the net effect of the two commits is the record and nothing else. The record validates (just validate-history → no issues), its layout matches history/<kind-dir>/<slug>/<ts>-<actor>-<shortid>.yaml, and its shape matches history/records/sulfur_globule/. The advisory presence check counts added history/**/*.yaml, so it's satisfied; Curation history is green along with the rest of CI.

Targeting the script rather than the 15 trait records is the right call, and the commit message makes the argument for it well.

One thing, and it's in the one place that can't be fixed later.


🟡 The 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:26-28

Six idioms: 4 repointed at a process already in the graph, 1 reversed …, 3 retyped …, 3 moved to produces/has output/transports, 2 to part of/promotes, and 5 onto a new term METPO:2007900 powers.

4+1+3+3+2+5 = 18, two lines below Repaired 16 edges across 15 trait records. Counting the 16 entries in DECISIONS:

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_inclusionMETPO: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.)

Fix this →


Everything else the record claims checks out

  • "16 edges across 15 trait records" — 16 DECISIONS entries over 15 distinct files. ✔
  • "Selection rule: every edge reported … as ENABLES_RANGE_VIOLATION"conf/predicate_domain_audit_baseline.tsv drops exactly 16 rows, all ENABLES_RANGE_VIOLATION, and their file/graph pairs are the DECISIONS keys with motile.yaml appearing twice as it should. ✔
  • "audit-graphs unchanged at 1533" — baseline is 1533 lines, 11 changed / 0 net. ✔
  • Vocabulary (EDIT/changed, kind: infrastructure, explicit --path for a non-record kind) follows the README's own rules. ✔

🔵 Still open from last round

proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3observations 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.

· fix/334-enables-range ·

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

🟡 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.yaml4 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>
@realmarcin

Copy link
Copy Markdown
Contributor Author

Correct, and thank you for catching it in the one artifact where it couldn't be fixed later.

Recounted from DECISIONS, which is the source of truth:

n idiom records
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 piezophilic, chemolithoheterotrophic, sphere_shaped
2 part of / promotes filament_shaped, stress_response
1 manifests as intracellular_inclusion
5 powers the five
16

Your diagnosis was exact — two errors that nearly cancelled: the repoint count was 1, not 4, and the manifests as edge was missing from the enumeration entirely. Fixed in the record now rather than after merge, for the reason you gave: history/README.md says a record is written once and never edited, so a correction afterwards costs a whole second record.

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 docs/CURATION_PLAYBOOK.md, which asserted "four of #334's sixteen needed only repointing" — as guidance for future curators, in the section telling them what to reach for. A wrong count in a record is a wrong record; a wrong count in the playbook actively misdirects. Now reads: one needed only that, and a second was fixed by reversing it onto a node already present.

just qc green.

@culturebot-reviewer

culturebot-reviewer Bot commented Aug 8, 2026

Copy link
Copy Markdown

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


Re-review — PR #355 (commit ef340806)

  • Read the new commit
  • Verify the corrected tally against DECISIONS
  • Check the playbook correction
  • Re-check the outstanding 🔵 on the ROBOT template row
  • Confirm just validate-history and nothing else regressed

The 🟡 from last round is fixed. The record now reads "Seven idioms: 1 repointed … 1 reversed … 3 retyped … 3 moved to produces/has output/transports, 2 to part of/promotes, 1 to manifests as, and 5 onto … powers" — 1+1+3+3+2+1+5 = 16, and I re-derived each number from DECISIONS rather than from the commit message: 16 entries over 15 files, one object-only repoint (ph_range_high), one subject rewrite (facultative_oxygen_preference), three retype blocks, five powers. just validate-history → no issues. CI green on all seven runs. No data artifact moved in this commit.

But the playbook paragraph is where I should have looked harder last round, because two more counts in it are wrong — and they are wrong in the direction that matters most, since you correctly identified this file as the one that misdirects rather than merely misrecords.


🟡 Two more #334 counts in the playbook, both new in this PR

docs/CURATION_PLAYBOOK.md:177-186

Both sentences were added by aeec2f3a, both sit in the paragraph ef340806 just edited, and both disagree with DECISIONS.

1. "In #334 five of the sixteen edges had the right predicate and the wrong node type" (line 177). Three edges had the wrong node type — treatment_resistance_persistence, buoyancy, even_pg_distribution — and of those exactly one had the right predicate: cell_width_large, which keeps enables. The other two moved to confers as well as being retyped. The sentence's own second example undercuts it: "tolerance to antimicrobial treatment" is treatment_resistance_persistence, whose predicate did change, so it is not an instance of "right predicate, wrong node type."

The distinction is the whole point of the heading — "Check the node type before you change the predicate" — so the number a curator reads there should be the one that supports it: three had the wrong node type, and one of those needed nothing else.

2. "Twice in #334 the edge was simply backwards" (line 185). Once. facultative_oxygen_preference is the only entry in DECISIONS that rewrites subject, and it is the one the sentence then quotes. Your own corrected line 194 says as much nine lines below: "a second was fixed by reversing it onto a node already present" — one reversal, described from the other side. As written the two sentences claim three reversal-or-repoint edges where there are two.

Fix this →


🔵 Still open (third round, unchanged)

proposals/metpo_traitmech_v10/metpo_proposal_properties_robot.tsv:3observations still reads "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 (lines 46, 76, 133). Only the row that goes upstream is stale.

🔵 "Seven idioms" vs the script's "Six"

The record now says seven; scripts/migrate_enables_range_edges.py:10 still says "Six distinct idioms are mixed together" and its comments number them 1–6, folding manifests as into idiom 6 with the retypes. The record's finer split is the more accurate one and it's the record that's immutable — but the script is what it cites as the source of truth, so one of the two words should move. Genuinely optional.


Requesting changes on the 🟡 only. The record — the part that can't be fixed after merge — is correct now, and its tally is verifiable line-by-line against DECISIONS, which is what a provenance record should be.

· fix/334-enables-range ·

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

🟡 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>
@realmarcin

Copy link
Copy Markdown
Contributor Author

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 — cell_width_large, which keeps enables. The other two moved to confers as well.

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

2. "Twice in #334 the edge was simply backwards." Once. Only facultative_oxygen_preference rewrites a subject, and it's the one the sentence then quotes — and line 194, which I'd corrected nine lines below, already said so 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

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.

just qc green.

@culturebot-reviewer

culturebot-reviewer Bot commented Aug 8, 2026

Copy link
Copy Markdown

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


Re-review — PR #355 (commit 80d8da92)

  • Read the new commit
  • Re-derive every #334 count in the playbook from DECISIONS
  • Re-check the outstanding 🔵 items
  • Confirm nothing else regressed

Both 🟡 from last round are fixed, and I re-derived the corrected numbers from DECISIONS rather than from the commit message:

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:3observations 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

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

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:

  1. 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 is target.path in an immutable history record and is the cited source of truth for what the migration decided.
  2. 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.
  3. scripts/migrate_enables_range_edges.py:10 still 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.

@realmarcin
realmarcin merged commit 4e492e5 into main Aug 8, 2026
8 checks passed
@realmarcin
realmarcin deleted the fix/334-enables-range branch August 8, 2026 01:30
realmarcin added a commit that referenced this pull request Aug 8, 2026
… (#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>
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.

33 enables edges point at non-activity objects, violating RO:0002327's range

1 participant