Follow-up from the #325 PR, which itemises 9 tools that edit kb/communities records in place and append no curation event:
scripts/apply_strain_designations.py scripts/fix_reference_formats.py
scripts/apply_taxonomy_corrections.py scripts/suggest_related_media.py
scripts/backfill_metals.py scripts/term_fix_apply.py
scripts/chebi_fix_apply.py scripts/term_remap.py
scripts/drop_obsolete_go_bp.py
They are gated (a new one has to be classified) but not fixed, and the reason they were not fixed in that PR is structural rather than effort.
Why record_curation_event does not reach them
communitymech.curate.curation_event.record_curation_event appends to a parsed dict and expects the caller to dump it. These nine are line editors: they operate on path.read_text().splitlines(), apply regex substitutions, and write_text the result. That is deliberate — a YAML round-trip would reflow every record it touches, which is why term_remap describes itself as "text-only edits".
So appending an event means inserting YAML text at the right place, with the right indentation, without disturbing anything else.
That code already exists, privately
scripts/gtdb_ground.py has _append_curation_event, written for #395, and it already handles the parts that are easy to get wrong:
- the insertion point — an earlier version scanned for the first column-0 key and put the new event above the existing history, because
- timestamp: is itself column-0. The fix was anchoring on ^[A-Za-z_].
- idempotence — an unconditional append meant every re-run grew the history, so it short-circuits when the edit is a no-op.
- the write guard —
_assert_only_grounding_changed takes a curation_event parameter and permits at-most-one appended entry, so the event cannot smuggle in other changes.
Every one of those is a bug someone else will re-hit if nine scripts each grow their own version.
Proposed
Lift it to communitymech.curate.curation_event beside record_curation_event — something like append_curation_event_text(lines, event) -> list[str] — keep gtdb_ground.py calling it so it stays exercised by the existing tests/test_gtdb_curation_history.py, then wire the nine. Each fixed writer removes a line from _OWED in tests/test_writers_leave_a_trace.py, and test_neither_list_has_rotted already fails if one gains a trace and stays listed.
Worth doing in more than one PR: the lift plus one writer first, to confirm the shared helper works outside its original caller before nine call sites depend on it.
Follow-up from the #325 PR, which itemises 9 tools that edit
kb/communitiesrecords in place and append no curation event:They are gated (a new one has to be classified) but not fixed, and the reason they were not fixed in that PR is structural rather than effort.
Why
record_curation_eventdoes not reach themcommunitymech.curate.curation_event.record_curation_eventappends to a parsed dict and expects the caller to dump it. These nine are line editors: they operate onpath.read_text().splitlines(), apply regex substitutions, andwrite_textthe result. That is deliberate — a YAML round-trip would reflow every record it touches, which is whyterm_remapdescribes itself as "text-only edits".So appending an event means inserting YAML text at the right place, with the right indentation, without disturbing anything else.
That code already exists, privately
scripts/gtdb_ground.pyhas_append_curation_event, written for #395, and it already handles the parts that are easy to get wrong:- timestamp:is itself column-0. The fix was anchoring on^[A-Za-z_]._assert_only_grounding_changedtakes acuration_eventparameter and permits at-most-one appended entry, so the event cannot smuggle in other changes.Every one of those is a bug someone else will re-hit if nine scripts each grow their own version.
Proposed
Lift it to
communitymech.curate.curation_eventbesiderecord_curation_event— something likeappend_curation_event_text(lines, event) -> list[str]— keepgtdb_ground.pycalling it so it stays exercised by the existingtests/test_gtdb_curation_history.py, then wire the nine. Each fixed writer removes a line from_OWEDintests/test_writers_leave_a_trace.py, andtest_neither_list_has_rottedalready fails if one gains a trace and stays listed.Worth doing in more than one PR: the lift plus one writer first, to confirm the shared helper works outside its original caller before nine call sites depend on it.