ogar-obo: migrate the row key from the V1 u24 tail to the V3 family:identity rail - #248
Conversation
…dentity rail `pack_key` wrote the V1 tail (identity's low 3 bytes at 13..16, family zeroed); `lance-graph-contract` has defaulted to `guid-v3-tail` since the 2026-07-04 ruling (ISS-V1-TAIL-RESIDUE), so the baker was minting a shape the declared loader no longer reads as canonical. There are no pre-flip rows of this bake to preserve, so this is a straight migration rather than a gated dual-read. V3 tail, per `NodeGuid::new_v2`: leaf u16 @10..12, family u16 @12..14, identity u16 @14..16. The CURIE numeric does NOT fit `identity` alone. OBO ids run past 65_535 (MONDO:0700092 = 700_092) and `mint_for`'s V2/V3 arm asserts `identity <= 0xFFFF` rather than truncating, so the 24-bit numeric is carried by the family:identity RAIL -- family = num >> 16, identity = num & 0xFFFF. That is the point of V3 over a flat u24: a u24 has no axis and cannot carry an X:Y rail; the pair can, losslessly, with 8 bits of headroom left in `family`. Sort order is unchanged. `family` holds the high bits, so ordering by (classid, family, identity) is ordering by (classid, num) -- the binary-search-by-key invariant over the sorted artifact survives the migration. New test `v3_tail_carries_oversize_curie_numerics_on_the_family_identity_rail` is the falsifier the existing round-trip could not be: that one uses id 9, which fits either layout and so cannot tell them apart. The new one uses 700_092, pins the byte positions against new_v2, asserts a V1 u24 read of the same bytes is observably WRONG (proving the tail moved rather than coincided), and asserts tail-byte ordering still tracks the numeric. ogar-obo 13/13, ogar-ro 6/6, clippy clean on both. CONSEQUENCE, not handled here: the published `obo-core.soa` (release ogar-obo-v0.1.0, 68_797 rows) was baked V1-tailed and is now STALE against this code -- a reader would decode the numeric from the wrong bytes. It needs a re-bake and re-publish. The manifest also carries no tail-variant field; adding one, and refusing a bake whose declared tail does not match the reader, is what I-LEGACY-API-FEATURE-GATED requires of a layout reclaim ("a v1 binary blob under a v2 reader MUST refuse to decode"). Both are operator actions -- flagged, not silently left. Pre-existing rustfmt drift in `examples/bake_obo.rs` is untouched (verified pre-existing by stashing this diff); not widening this change to cover it.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_4cf97f37-a1ec-4946-ad75-bec4f72ef0bd) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0ffe801d-f84f-4d27-9a70-6888e933f64d) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82be74d293
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let f = ((identity >> 16) as u16).to_le_bytes(); | ||
| let i = ((identity & 0xFFFF) as u16).to_le_bytes(); |
There was a problem hiding this comment.
Encode the numeric using the actual V3 facet layout
When these rows are loaded under the advertised guid-v3-tail default, this writes the deprecated NodeGuid::new_v2 carving (leaf:u16 / family:u16 / identity:u16) into bytes that V3 defines as six independent (u8, u8) axes (CLAUDE.md:9-22 and docs/NODEGUID-CANON-AUDIT.md:80-94). For OBO IDs above 65,535, canonical V3 readers therefore see unrelated rail components and cannot reconstruct values such as 700_092; only this crate's custom decode_key understands the invented concatenation, breaking the stated cross-repository byte contract. Encode through the sanctioned V3 facet/ClassView layout, or explicitly select a registered legacy/wide carving instead of splitting the numeric across V2 fields.
Useful? React with 👍 / 👎.
What this does
ogar-obo'spack_keywrote the V1 u24 tail (identity's low 3 bytes at13..16, family zeroed).lance-graph-contracthas defaulted toguid-v3-tailsince the 2026-07-04 ruling (ISS-V1-TAIL-RESIDUE), so thebaker was minting a shape the manifest's own declared loader
(
canonical_node::node_rows_from_le_bytes) no longer reads as canonical.There are no pre-flip rows of this bake to preserve, so this is a straight
migration, not a gated dual-read.
V3 tail per
NodeGuid::new_v2: leafu16@10..12, familyu16@12..14,identity
u16@14..16.The CURIE numeric does not fit
identityOBO ids run past
u16— MONDO:0700092 = 700,092 — andmint_for's V2/V3 armasserts
identity <= 0xFFFFrather than truncating. So the 24-bit numericis carried on the family:identity rail:
family = num >> 16,identity = num & 0xFFFF. Lossless for the full 24 bits, 8 bits of headroomleft in
family.Sort order is unchanged —
familyholds the high bits, so ordering by(classid, family, identity)is ordering by(classid, num). Thebinary-search-by-key invariant over the sorted artifact survives.
The test is the point
v3_tail_carries_oversize_curie_numerics_on_the_family_identity_railis thefalsifier the existing round-trip could not be: that one uses id
9, whichfits either layout and therefore cannot tell them apart. The new one uses
700,092, pins the byte positions against
new_v2, asserts a V1 u24 read ofthe same bytes is observably wrong (proving the tail moved rather than
coincided), and asserts tail-byte ordering still tracks the numeric.
ogar-obo13/13 ·ogar-ro6/6 · clippy clean on both.Open, and deliberately not decided here
1. A canon question the reviewer should rule on. OGAR's canon says the
12-byte register is
6×(u8:u8)and that "u8:u8stays two separate bytes —never widened to u16 or u24", because a flat wide integer has no axis. Using
family:identity as a 32-bit split is arguably that same widening, spread over
two fields. The canon names a sanctioned home for exactly this shape — the
grace carvings (
D-V1-GRACE-CARVINGS,legacy_outliers), which state outrightthat "the V1
family:identityu24 fragment is the degenerate G1/G2 case" —while preferring migration to a real axis. Three options: keep this; register
it as a declared grace carving; or give
familya genuine bucket meaning(cheap now, expensive after a re-bake ships).
2. The published artifact is now stale.
obo-core.soa(releaseogar-obo-v0.1.0, 68,797 rows) was baked V1-tailed; a reader on this codewould decode the numeric from the wrong bytes. It needs a re-bake + re-publish.
3. The manifest has no tail-variant field — which is what allowed the
ambiguity.
I-LEGACY-API-FEATURE-GATEDrequires a layout reclaim to be pairedwith a version gate: "a v1 binary blob under a v2 reader MUST refuse to
decode." Adding that field, and making the loader fail closed on a mismatch,
is the durable fix.
Pre-existing rustfmt drift in
examples/bake_obo.rsis untouched (verifiedpre-existing by stashing this diff).