small fixes 2026-08-18: spec 4.5 temporal-companion parity (issue #483) - #484
Conversation
espg
left a comment
There was a problem hiding this comment.
🤖 from Claude (review)
Adversarial review of the §4.5 temporal-companion bullet, run against the code, the committed fixtures, and the rest of docs/specification.md.
Verified clean (no finding):
- The writer really records the shape:
src/zagg/pyramid.pyL205-212 doesif meta.get("temporal") is not None: fields[name]["temporal"] = str(meta["temporal"]), and_validate_temporal_shape(src/zagg/config.pyL1851-1938) proves the declared value is a §8 shape, not a column — so "the companion shape ("per-centroid")" and "keyed only when set" both hold. The keying sits inside theapproximatebranch only, so on a digest field the value can only ever be"per-centroid"(per-centroidrequireskind: ragged). - The committed fixture agrees:
tests/data/spec/temporal/morton_hive.json→pyramid.overview.fields.h_tdigest={"class": "approximate", …, "location": "leaf_id", "temporal": "per-centroid"}. No fixture regeneration is owed by this diff. - The overview-level claim is backed by the writer:
sweep_overview._overview_configL2046-2052 stampsvariables[name]["temporal"]on the reconstructed template exactly as it doeslocation, andCOMPANION_CHANNELS/field_companions(L287-306) make the sibling{field}_timesand its declaration channel-generic.check_companion_match(L246-281) additionally pins the fold's shape toper-centroid, which is the amendment restated in code. - "the payload's §8.3
timesbinding" is the right citation, not §1.2: §8.3's Binding paragraph definestimesas a sibling of theraggedblock, and §1.2 L156-158 itself points outward ("The §2.0weightskey and §8.3'stimesbinding are likewise spec-owned"). The asymmetry withlocations/§1.2 in the located bullet is therefore correct, not a copy-paste slip. - §8.4's amendment record is quoted accurately and not contradicted: L2037-2041 reads "That ruling was amended on 2026-08-17 … the reduction below is licensed but unused by this writer", which is what the new bullet cross-references.
- Markdown structure is sound: L946 opens a top-level
-item with every continuation at 2 spaces (L947-966), the located bullet still closes on its own sentence at L945 ("with the words at the heterogeneous orders §9.1 makes normative"), and nothing is orphaned. The bold opener and the[ruling 3 on\nissue #410](…)link both wrap inside a single block, which renders. - No stale pre-amendment claim survives elsewhere: the only
temporal+ pyramid/fold text indocs/is this bullet and §7 L1607-1615, which already says the column carries the folded companions "at every level".docs/hive_layout.md,docs/ragged_layout.md,docs/temporal_events.md,docs/design/sparse_coverage.mdcarry nothing on the subject.
Finding (2) — §4.6's Structure bullet is the sibling contradiction this fix leaves standing (out of the diff, so it is here rather than inline). docs/specification.md L1108-1111 still enumerates the located companion alone:
array per composable field (§4.5 classes;
nonefields are absent),
plus — for a field whose §4.5 entry carrieslocation— that field's
{field}_locationssibling, row-aligned with its payload and carrying the
§9 declaration, exactly as an overview level does.
That reads as exhaustive, and it is now contradicted from two directions. §7 L1607-1613 says of the same artifact: "Its §4.6 leaf column carries the folded companions: every resolution group holds h_tdigest with both siblings, each declaring per-centroid". And the committed golden proves it — tests/data/spec/temporal/1/1/2/1/3/all.pyramid.zarr/4/ holds h_tdigest, h_tdigest_locations, h_tdigest_times, with the payload attrs carrying "times": "h_tdigest_times". The writer is channel-generic (column.py L209, L280 both call field_companions), so the temporal sibling is not an accident of that fixture. Since #483's whole point is that §4.5 no longer says temporal skips the pyramid, §4.6 is now the one place a reader can still infer a located-only companion contract; suggest widening L1109-1111 to the companion channels generally (e.g. "for a field whose §4.5 entry carries location or temporal — that field's {field}_locations / {field}_times sibling … carrying the §9 / §8.3 declaration"), and the "written together or not at all" clause reads unchanged for either channel.
| fields exist at native resolution only. | ||
| words at the **heterogeneous orders** §9.1 makes normative. | ||
| - **A temporal ragged field composes the same way, and its entry carries | ||
| `temporal`.** A `temporal:` declaration (§8.3) does not exclude a field |
There was a problem hiding this comment.
🤖 from Claude (review)
Finding (1) — "A temporal: declaration (§8.3) does not exclude a field from the pyramid either" is true of one of the two shapes, and the sentence reads generically.
The located sentence this mirrors is safe because location: has exactly one shape. temporal: has two, and the §8.2 one does exclude the field. src/zagg/semantics.py::field_composability L401-407 is explicit:
A
temporal: per-cellfield — the §8.2 dense shape, GEDI's leaf companion (ruling 2) — is a different object and staysnone. Its fold law is the word grammar's join over a cell group, not its own reducer …
and it forces that class regardless of the field's function (an exact max/sum law does not rescue it). The committed fixture pins the same thing: tests/data/spec/temporal/morton_hive.json declares "observed": {"class": "none"}, and observed is precisely the shape: "per-cell" companion (temporal.expected.json → declarations.observed = {"spec": "zagg-toc/1", "shape": "per-cell", "grammar": "mortie-toc/1"}).
The bullet's lead ("A temporal ragged field") and the (§8.3) citation do scope it, but the body sentence is the one an implementer quotes, and §4.5 — the section that owns the fields map's classes — never states anywhere that a per-cell temporal companion is none-class. §7 L1613-1615 states it only for the column groups ("its fold law is the grammar's join over a cell group rather than the field's own reducer, so it exists at native resolution only"), which is the artifact contract, not the manifest declaration.
Suggest a half-sentence pinning the shape, e.g. "A temporal: declaration of the §8.3 "per-centroid" shape does not exclude a field from the pyramid either (the §8.2 dense "per-cell" companion is a different object and stays none — its fold law is the grammar's join over a cell group, not the field's reducer)". That also removes the only way to read the new bullet as reversing §7's observed line.
There was a problem hiding this comment.
🤖 from Claude
Folded in 7e03e885. Verified the behavior first: field_composability returns "none" on sig.get("temporal") == TOC_SHAPE_PER_CELL (src/zagg/semantics.py L415-416), ahead of any kind/function classification, and the fixture pins it (tests/data/spec/temporal/morton_hive.json → "observed": {"class": "none"}, generated from a temporal: "per-cell" declaration in tools/generate_spec_fixtures.py L1004-1010).
Two changes to the §4.5 temporal bullet:
- Scoped the opening claim — now "A
"per-centroid"temporal:declaration (§8.3) does not exclude a field from the pyramid either". - Added the per-cell rule as one closing sentence: "The §8.2 dense
"per-cell"shape is the exception: a field declaring it is classednonewhatever its reducer (zagg.semantics.field_composability), that shape's fold law being the word grammar's join over a cell group rather than the field's own reducer, so it exists at native resolution only."
| level and the class is `approximate` like any other digest field. Its | ||
| entry carries `temporal` — the companion **shape** (`"per-centroid"`), | ||
| which is all a level above the leaf can act on, the leaf's ingest column | ||
| never being re-read by any fold — keyed **only when set**, so a field |
There was a problem hiding this comment.
🤖 from Claude (review)
Finding (3), minor — the prose is right, but the PR body's evidence link for it points at the wrong function.
"keyed only when set" for the manifest pyramid entry is implemented in src/zagg/pyramid.py L211-212 (if meta.get("temporal") is not None: fields[name]["temporal"] = str(meta["temporal"])), inside declared_fields. The PR description cites config.py#L2981-2985 instead, which is the shard-map field signature builder (the get_output_signature-driven {"name", "kind", "trailing_shape", "inner_shape", "dtype", …} entries) — a different artifact with its own keyed-only-when-set discipline, and not the pyramid.overview.fields entry §4.5 is specifying.
No spec text changes; it is the PR body's citation that should point at pyramid.py L205-212 (or name both, since config.py backs the parallel shard-map claim), so a reviewer following the link lands on the code the sentence is describing.
There was a problem hiding this comment.
🤖 from Claude
Folded — PR body edited (no commit; body-only fix). You are right that config.py#L2981-2985 sits in output_field_signature, the shard-map field-signature builder, not the manifest pyramid entry. The manifest entry's keying is src/zagg/pyramid.py L212-213 (if meta.get("temporal") is not None: fields[name]["temporal"] = str(meta["temporal"])), which is what the §4.5 "keyed only when set" sentence describes and what the overview writer reconstructs a level from.
The body now leads with pyramid.py#L206-L213 as the manifest-entry evidence and keeps config.py#L2981-L2985 as an explicitly-labelled secondary cite for the same discipline in the shard-map field signature.
|
🤖 from Claude Acknowledging the review-body (out-of-diff) finding — §4.6 Structure bullet enumerates only the Verified before editing: The bullet now reads "plus every channel sibling that field's §4.5 entry declares — the Checks on the new head |
Closes #483.
What this does
Small-fixes bundle for 2026-08-18 (one entry). Prose-only spec fix: brings §4.5's temporal-companion sentence to parity with the 2026-08-17 amendment to ruling 3 on issue #410 and documents the
temporal:field-entry key the manifest actually carries.The fix
temporal:field-entry key undocumented #483 — spec §4.5 temporal-companion sentence +temporal:key (de702861). Replaced the stale closing sentence of the located-field bullet ("A temporal companion (§8.3) is a different case and staysclass: "none"… those fields exist at native resolution only") with its own bullet mirroring the located one: the companion stays"per-centroid"at every level, the temporal k-way merge reduces{field}_timesover the same centroid partition, class isapproximate, and the entry carriestemporal— the companion shape, keyed only when set, load-bearing for template reconstruction exactly aslocationis. The wording follows the writer's own behavior — the manifest pyramid entry'stemporalkey is written only when set bypyramid.py#L212-L213, which is the entry the overview writer reconstructs a level from;config.py#L2981-L2985applies the same keyed-only-when-set discipline in a different record,output_field_signature's shard-map field signature — and the committedtemporal/fixture (h_tdigestclass: "approximate",temporal: "per-centroid").7e03e885: scoped the "does not exclude a field from the pyramid" claim to the"per-centroid"shape and stated the per-cell rule — atemporal: "per-cell"field is classednonewhatever its reducer (semantics.py::field_composability), pinned by the fixture's"observed": {"class": "none"}.3324dbe8: §4.6's Structure bullet enumerated only thelocation-gated{field}_locationssibling for a column's resolution groups; extended it to both channel siblings, matching §7 and the committed goldentests/data/spec/temporal/1/1/2/1/3/all.pyramid.zarr/4/(which holdsh_tdigest_times) —column.pyfolds via the channel-genericfield_companions.No wire-format, attrs-grammar, or
spec-marker change — §4's fixture rule is not triggered; the fixtures already reflect the truth and are byte-untouched. §8.4's "licensed but unused by this writer" posture for the per-cell reduction is cross-referenced, not altered. The §7 line about the dense per-cellobservedarray existing at native resolution only is correct (its fold law is the grammar's join, not the field's reducer) and untouched.How it was tested
Docs-only diff (
docs/specification.md, +35/−10).pytest -k specgreen (191 passed — includes the conformance-fixture suite, unaffected as expected). codespell clean on the touched lines (the one hit at line 1770,re-declares, is pre-existing and outside this diff).Questions for review