Skip to content

Commit 76aaef1

Browse files
authored
Merge pull request #16 from djarecka/ref/linkml_ingestion
Ref/linkml ingestion
2 parents 8d5958c + 5f037b5 commit 76aaef1

2 files changed

Lines changed: 34 additions & 9 deletions

File tree

docs/ingestion.md

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,8 @@ units, multivalued, required, pattern}}}`.
7979
Converts the dict into real, hash-identified objects:
8080

8181
- **Properties are built first.** Each one's `hash_id` is computed from
82-
`name`/`description`/`range`/`units` only — `slot_uri` and
83-
`registry_version` are stored on the object but excluded from the hash
84-
(they're provenance/operational metadata, not identity).
82+
`name`/`description`/`range`/`units` only — `slot_uri` is stored on the
83+
object but excluded from the hash (it's origin metadata, not identity).
8584
- **Classes reference their properties by hash_id**, sorted. This is why a
8685
class's own hash depends on its full induced property set — two classes
8786
with the same properties (regardless of declaration order) hash the same.
@@ -91,8 +90,8 @@ Converts the dict into real, hash-identified objects:
9190
have every `is_a` target resolvable within its own import closure (that's
9291
`SchemaView`'s job), so this recursion always terminates cleanly.
9392
- **Every entity gets one `ProvenanceEntry`** for this ingestion — `source`,
94-
`attributed_to` (agent), `generated_at`, `activity` — entirely separate
95-
from the hash computation.
93+
`attributed_to` (agent), `generated_at`, `activity`, `registry_version`
94+
entirely separate from the hash computation.
9695

9796
### 3. `write_registry_entities()` + `write_structural_edges()` (`neuro_ghost/db.py`)
9897

@@ -116,9 +115,8 @@ and `seed.py` — schema.org is ingested through the exact same path, just with
116115
| `range`, `units` | `RegistryProperty` | Identity-defining. |
117116
| `properties`, `is_a`, `mixins` | `RegistryClass` | Identity-defining — all stored as hash_id references. |
118117
| `class_uri` / `slot_uri` | `RegistryClass` / `RegistryProperty` | Ontology IRI preserved from the source. **Not** part of the hash — two schemas using different IRIs (or none) for the same content still collapse to one node. |
119-
| `registry_version` | both | Operational stamp. Not part of the hash. |
120118
| `provenance` | both | List of `ProvenanceEntry`. Accumulates, never affects `hash_id`. |
121-
| `source`, `attributed_to`, `generated_at`, `activity`, `derived_from` | `ProvenanceEntry` | PROV-O–grounded (`slot_uri: prov:wasAttributedTo` etc.) |
119+
| `source`, `attributed_to`, `generated_at`, `activity`, `derived_from`, `registry_version` | `ProvenanceEntry` | PROV-O–grounded (`slot_uri: prov:wasAttributedTo` etc.). `registry_version` lives here, not on the entity — the same entity can be attested by different sources at different times, each under a different registry version, so a single scalar on the entity doesn't fit (same reasoning as dropping `source_label`). |
122120

123121
Field names were deliberately aligned with LinkML's own metamodel
124122
(`description`, `range`, `class_uri`/`slot_uri`, `is_a`, `abstract`) rather
@@ -167,6 +165,33 @@ Saying "parse_linkml extracts X" and "the registry stores X" are different
167165
claims — test each layer for what it actually is, not for what you assume
168166
the other layer does with it.
169167

168+
`tests/test_ingest_registry.py::test_required_does_not_affect_property_identity`
169+
takes this one step further, end to end: two schemas declare the exact same
170+
`age` slot except one marks it `required: true`. Ingesting both must produce
171+
exactly one `RegistryProperty` node, not two — proving `required` doesn't
172+
leak into identity, in the real graph, not just in an isolated object.
173+
174+
## Open question: when should ProvenanceEntry.registry_version be set?
175+
176+
The whole-registry version (the semver in `data/registry.json`, e.g. `1.7.0`)
177+
only gets computed once, at the very end of a submission, when
178+
`export_json.py` reads the last entry in `data/provenance.json` and bumps it.
179+
`seed.py`/`ingest_linkml.py`/`align.py` all run *before* that — so the version
180+
an entity is actually going to belong to doesn't exist yet at the moment it's
181+
ingested.
182+
183+
That rules out the obvious fix ("pass the current, pre-bump version to
184+
ingest_linkml.py/align.py") — it would record the version this submission is
185+
replacing, not the one it's part of. The consistent fix would be computing
186+
the bump once, up front (alongside where the Action already determines
187+
`bump` type in "Parse issue metadata"), and threading that single value
188+
through every step including `export_json.py` — instead of `export_json.py`
189+
computing it independently after the fact.
190+
191+
**Not implemented.** `schema_submission.yml` does not pass `--registry-version`
192+
to `ingest_linkml.py` or `align.py` — those entities' `ProvenanceEntry.
193+
registry_version` stays `None` for now, pending a decision on the above.
194+
170195
## Known gaps (as of this writing)
171196

172197
- **`index.html`** (frontend) still expects the pre-rework JSON shape

neuro_ghost/export_json.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ def export_snapshot(conn, registry_version: str) -> dict:
9696
"hash_id": hash_id,
9797
"iri": class_uri or "",
9898
"name": name or "",
99-
"definition": desc or "",
100-
"is_abstract": bool(is_abstract),
99+
"definition": desc or "",
100+
"is_abstract": bool(is_abstract),
101101
"sources": _attesting_sources(conn, "RegistryClass", "HAS_PROVENANCE", hash_id),
102102
"properties": [
103103
{

0 commit comments

Comments
 (0)