diff --git a/src/communitymech/datamodel/communitymech.py b/src/communitymech/datamodel/communitymech.py index d31b3c55..b3d41f49 100644 --- a/src/communitymech/datamodel/communitymech.py +++ b/src/communitymech/datamodel/communitymech.py @@ -1,5 +1,5 @@ # Auto generated from communitymech.yaml by pythongen.py version: 0.0.1 -# Generation date: 2026-08-08T00:48:13 +# Generation date: 2026-08-08T01:28:55 # Schema: communitymech # # id: https://w3id.org/communitymech diff --git a/src/communitymech/schema/communitymech.yaml b/src/communitymech/schema/communitymech.yaml index c63b367d..6524ea73 100644 --- a/src/communitymech/schema/communitymech.yaml +++ b/src/communitymech/schema/communitymech.yaml @@ -1640,7 +1640,34 @@ classes: required: false inlined_as_list: true taxonomy: - description: Organisms present in the community + description: >- + Organisms present in the community — its membership, not everything it + interacts with. + + A host and an antagonist are **not** members (#319). The community + lives on or in its host and is applied against its antagonist; both + appear as `ecological_interactions` participants without appearing + here, which is why the network auditor reports them as + `UNKNOWN_SOURCE`/`UNKNOWN_TARGET` at warning severity rather than + gating on them (#326). + + Of the 25 interaction participants currently outside this slot, 14 are + aggregate names for members that *are* present — `Variovorax` where + the record lists `Variovorax sp. CF313`, `YR634` and the rest — so + they are a naming coarseness rather than a membership question. The + remaining 11 are 6 hosts, 3 antagonists and 2 abiotic + (`anode`), and none of them belongs here. + + The mirror-image question is #307: candidates deliberately screened + *out* are likewise not members, and live in + `engineering_design.counter_selection`. Both answers are the same, for + the same reason — this slot records what the community is made of, and + an edge to something outside it is not evidence that it should be + inside it. + + `tests/test_interaction_participants_outside_taxonomy.py` holds the + population to those four groups, so a new host or antagonist has to be + classified rather than absorbed. range: TaxonomicComposition multivalued: true ecological_interactions: diff --git a/tests/test_interaction_participants_outside_taxonomy.py b/tests/test_interaction_participants_outside_taxonomy.py index eacebd83..3accd42a 100644 --- a/tests/test_interaction_participants_outside_taxonomy.py +++ b/tests/test_interaction_participants_outside_taxonomy.py @@ -210,3 +210,32 @@ def test_the_group_sizes_are_what_the_decision_was_sized_against(group: str, exp which is a much smaller commitment than the headline count suggests. """ assert len(globals()[group]) == expected + + +def test_the_schema_records_the_membership_decision(): + """#319's question, answered in the slot rather than in an issue thread. + + "Should hosts and antagonists be taxonomy members?" was re-derived at least + twice — once when the auditor was written and once when this file was — and + each time from the same evidence. Recording it on `taxonomy` means the next + reader finds the answer where they are already looking. + + Asserted positively: the description must *say* hosts and antagonists are + not members. A check that some phrase is absent would pass on a rewrite + that dropped the reasoning entirely. + """ + import yaml + + schema = yaml.safe_load( + (REPO / "src/communitymech/schema/communitymech.yaml").read_text(encoding="utf-8") + ) + description = schema["classes"]["MicrobialCommunity"]["attributes"]["taxonomy"]["description"] + + assert "#319" in description + lowered = description.lower() + assert "host" in lowered and "antagonist" in lowered, ( + "the taxonomy slot no longer states whether hosts and antagonists are " + "members, which is the question #319 asked and this file pins the " + "population for" + ) + assert "not" in lowered, "the decision was 'no'; the description must say so"