TS morton core + hive arithmetic (phase 6a/6b of issue #1)#7
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N32Mx4rWhbTK8xXm7Mt2ax
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N32Mx4rWhbTK8xXm7Mt2ax
| } | ||
| const [, base, tail] = m; | ||
| const components = [base]; | ||
| for (let i = 0; i < tail.length; i += pathGrouping) { |
There was a problem hiding this comment.
🤖 from Claude (review)
Top finding — the path_grouping remainder rule is spec-silent; freeze it before it can drift. This loop implements "leading components full-width, last carries the remainder." I derived the rule from spec §6.1 and it is not stated there: §6.1 says only "declares how many digits each component chunks; readers chunk the digit string per the manifest, never by assumption" — it never says which end absorbs the remainder when order % path_grouping != 0. §8 (frozen list) doesn't pin it either.
Cross-impl status (verified, not asserted): moczarr PR-A (d70-t#17) convention.group_digits chose the same rule — [digits[i:i+pg] for i in range(0, len, pg)] — and the zagg-written serc_hive_pg3 fixture (path_grouping: 3) confirms it: 433142241 → 4/331/422/41/…, -433412214 → -4/334/122/14/…. I ran gridlook coveredLeafPaths against moczarr leaf_path on both SERC fixtures (pg1 and pg3): exact MATCH. So all three implementations agree today.
The risk is that the agreement is undocumented. Because nothing normative pins the remainder end, a future writer/reader is free to pick "last full, first remainder" and silently mis-shard every non-evenly-divisible id. Recommend filing a spec-gap issue on mortie (the d70-t#62 successor) to add the remainder rule to §6.1 and citing it in this docstring. No code change — the rule here is correct and matches moczarr and the fixture.
There was a problem hiding this comment.
🤖 from Claude
The normative fix is filed as espg/mortie#124 (freeze "leading components full-width, last carries the remainder" into §6.1, plus the base-component-stands-alone note); gridlook, moczarr PR-A, and the serc_hive_pg3 fixture all already implement it, so the code stands pending the spec amendment — no code change here.
| const usable = | ||
| raw["spec"] === COVERAGE_SPEC && | ||
| raw["encoding"] === "ranges" && | ||
| typeof order === "number" && |
There was a problem hiding this comment.
🤖 from Claude (review)
Coverage parse-tolerance boundary diverges from moczarr — the "mirroring moczarr" claim in the header is imprecise. moczarr's parse_root_coverage gates the tolerant (→None) bucket on spec + encoding only; everything else (the order shape, the ranges shape, endpoint validity) is loud at ranges_words. This usable check additionally folds the order-is-integer and ranges-is-array checks into the tolerant bucket, moving the split point.
Confirmed by running the same envelopes (valid spec+encoding) through both readers:
| envelope | gridlook parseRootCoverage |
moczarr |
|---|---|---|
order: "6" (string) |
null — whole MOC dropped | dict → ranges_words reads it, returns the shard |
order missing |
null | dict → loud KeyError |
ranges missing / non-array |
null | dict → loud error |
Two concerns: (a) for order:"6" the two readers produce different coverage from the same recoverable envelope — moczarr reads the shard, gridlook discards the entire MOC; (b) this reader is MOC-first with no LIST fallback, so a structurally-broken root MOC (right spec/encoding, mangled ranges) reads as null → no coverage → the store renders empty, whereas moczarr surfaces a loud error. Silent-empty is the more dangerous failure mode for a corrupt cache.
Recommend either matching moczarr's boundary (gate only on spec+encoding; let a bad order/ranges throw at expansion), or — if the stricter parse is deliberate — dropping the "mirroring moczarr" wording and having espg confirm the silent-empty posture is intended given there is no walk fallback. The endpoint-validation loud bucket (base-crossing / reversed / wrong-order / non-string) is exact parity; only this parse boundary differs.
There was a problem hiding this comment.
🤖 from Claude
Reconciled the boundary to moczarr's parse_root_coverage vs ranges_words split: the tolerant-null bucket now gates on spec+encoding only, a numeric-string order (e.g. "6") is read like a JSON number, and a structurally-corrupt order/ranges (missing, non-numeric, non-list, or a mangled range entry) now throws loud instead of reading silent-empty (cb4adf9).
| */ | ||
| function pathGroupingOf(raw: Record<string, unknown>): number { | ||
| const value = raw["path_grouping"]; | ||
| if (value === undefined || value === null) { |
There was a problem hiding this comment.
🤖 from Claude (review)
path_grouping: null parity divergence with moczarr. Here an explicit JSON null is normalized to 1 (docstring: "Absent/null reads as 1"). moczarr's parse_manifest treats only an absent key as 1 (payload.get("path_grouping", 1)); an explicit null falls through to isinstance(None, int) → ValueError. Confirmed: {"spec":"morton-hive/1","cell_order":8,"shard_order":6,"path_grouping":null} is accepted by gridlook (pg=1) and rejected by moczarr. Low severity (writers emit the integer or omit the key), but the two readers disagree on the same document — worth reconciling in one direction so a serializer-nulled or hand-edited manifest can't read differently in the browser vs Python. Flagging for espg.
There was a problem hiding this comment.
🤖 from Claude
An explicit path_grouping: null now rejects (loud, via asInt) — only an ABSENT key defaults to 1, matching moczarr's payload.get("path_grouping", 1), so the two readers agree on the same document (3c6f7b2).
|
🤖 from Claude (review) Fresh-context adversarial review of phase 6a+6b (BigInt morton codec + hive/manifest/coverage arithmetic). The catastrophic class here is cross-language drift against the three other implementations (mortie rust, moczarr python, the normative spec), so the review was weighted toward differential testing, not reading. Verdict: the §1/§2/§4 codec is soundI derived the 28..=47 suffix→order band table by hand from the spec formula (
Then cross-language differential tests (moczarr
No correctness bug found in the codec — the bullseye holds. Findings posted inline (3)
Noted, not posted
Gates green locally: |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N32Mx4rWhbTK8xXm7Mt2ax
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N32Mx4rWhbTK8xXm7Mt2ax
Refs #1 (phase 6a+6b)
Pure-TS morton core (
src/lib/morton/, no three.js, no fetch/UI wiring) implementing the mortie v1.0 spec, per the ratified phase-6 plan and itspath_groupingamendment. 6c (detector + data path) and 6d (hive virtual store) are out of scope here and untouched.What's in
6a — word codec + decimal grammar (
word.ts,decimal.ts)base_id + 1), 54-bit body (27 × 2-bit tuples), 6-bit suffix with the full band table (0..=27order==suffix;28..=47order-28/29 area preorderr = t28*5 + (t29 present ? t29+1 : 0);48..=63order-29 pointr2 = t28*4 + t29). Order extraction, kind predicate (suffix range only, per §4 — no metadata), base-cell decode with loud rejection of the empty sentinel / invalid prefixes.pkind suffix: legal solely on full order-29 strings, selects the POINT word; unmarked order-29 strings always parse AREA (the §4 tie-break); paths never carryp. Round-trip is lossless for both kinds.wordToNested),clipToOrder(mortieclip2ordersemantics: clip at-or-above native order is identity, a real clip always yields an area word), andviewNestedId— the viewer-side float64 cast: POINT clips 29→24 first, AREA never clips (order > 24 throws toward the phase-6d proxy/virtual-store path), results ≤ order 24 are exact JS Numbers (12·4²⁴ < 2⁵³).6b — manifest + store arithmetic (
manifest.ts,hive.ts,coverage.ts)morton_hive.jsonparse for/1–/3, discriminated byspeconly;path_groupingabsent→1, loud on anything but a positive integer.path_groupingchunking as the only construction code path (leading components full-width, last carries the remainder),path_grouping: 3fixture cases included per the amendment;/2windowed and/3window-only (all.zarr) basenames; windowed-name split on the first_;shardIdFromPathrecovering ids from digit components (any grouping — how/3leaves get their id back).coverage.mocranges parse (§7.3, box/ranges tier only — the zstd leaf bitmap stays server-side): tolerant envelope parse (unusable → null, regenerable-cache posture), loud range validation (string endpoints only, same base, same order, non-reversed), exact expansion, O(ranges) containment, andcoveredLeafPaths= manifest + root MOC → every leaf path with zero requests (MOC-first; deliberately no LIST fallback).classifyStoreRoot(objects, prefixes)— manifest at the root ⇒ bare store; product-grammar children ([a-z0-9_-]{1,192}minus the-?[1-6]base-component exclusion) ⇒ sorted product entries.Golden provenance (TS↔Python↔Rust pinned to one convention)
433142211↔ word5347180132572332040↔ NESTED238416(order 8) — moczarrtests/test_fabricate.pyGOLDEN_WORD/GOLDEN_NESTED.11570383905173274643↔ NESTED2483716583387— moczarrGOLDEN_WORD_O19_SOUTH/GOLDEN_NESTED_O19_SOUTH; decimal-41132132113423444234derived once with mortie 0.9.0.-5112333family (words, leaf paths-5/1/1/2/3/3/3/-5112333.zarretc., windowed split cases incl. the malformed_20_19reject) — moczarrtests/conftest.py/tests/test_convention.py.SOUTH_WORDSliterals and the cross-base shard set (3444444,6111111,6444444,-1111111,-4444444, northern-before-southern raw-sort order) — moczarrtests/test_ranges.pyTestSouthernAndMultiBaseGoldens(cross-base words derived once with mortie 0.9.0).clip2order.coverage.mocenvelope and the six covered leaf paths — moczarrtests/data/serc_hive(zagg-written, post zagg#314).Judgment calls
pparse/emission (mortie PR Feature: Hide min bound d70-t/gridlook#121) and itsgeo2mort(order=29)emits order-29 area words, so the point twin (48 + t28*4 + t29) is pinned from the frozen §1/§4 formulas rather than a mortie output; its clip-to-24 result is cross-checked against the mortie-derived area clip (same path ⇒ same word)./3temporal posture: moczarr doesn't parse/3yet, so this reader accepts a/3manifest with an absent temporal block asschedule: none(matching §6.4'sall.zarrconstruct);/2still requirestemporal.scheduleand/1still refuses a temporal block (moczarr parity).path_grouping: 0is malformed (throws) rather than normalized to 1 — zagg'sor 1normalization is its resume-compare projection, not read validation; the manifest is loud-on-malformed by posture.viewNestedIdthrows on area words above order 24 instead of returning a BigInt — per the amendment ("genuinely-finer data takes the proxy path"); the raw BigInt remains available viawordToNested.classifyStoreRootthrows when a root has neither a manifest nor product-shaped children (not a morton-hive root), rather than guessing.Testing
58 new vitest cases (CPU-only) in
tests/unit/lib/morton/; full suite 137 passing.npm run test,npm run typecheck,npm run lint-ci, andnpm run buildall green locally (baseline verified green before the change).🤖 Generated with Claude Code
https://claude.ai/code/session_01N32Mx4rWhbTK8xXm7Mt2ax