Skip to content

small fixes 2026-08-18: spec 4.5 temporal-companion parity (issue #483) - #484

Merged
espg merged 3 commits into
mainfrom
claude/small-fixes-2026-08-18
Aug 19, 2026
Merged

small fixes 2026-08-18: spec 4.5 temporal-companion parity (issue #483)#484
espg merged 3 commits into
mainfrom
claude/small-fixes-2026-08-18

Conversation

@espg

@espg espg commented Aug 18, 2026

Copy link
Copy Markdown
Member

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

  • spec §4.5: temporal-companion sentence contradicts the per-centroid amendment; 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 stays class: "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}_times over the same centroid partition, class is approximate, and the entry carries temporal — the companion shape, keyed only when set, load-bearing for template reconstruction exactly as location is. The wording follows the writer's own behavior — the manifest pyramid entry's temporal key is written only when set by pyramid.py#L212-L213, which is the entry the overview writer reconstructs a level from; config.py#L2981-L2985 applies the same keyed-only-when-set discipline in a different record, output_field_signature's shard-map field signature — and the committed temporal/ fixture (h_tdigest class: "approximate", temporal: "per-centroid").
    • Review fold, 7e03e885: scoped the "does not exclude a field from the pyramid" claim to the "per-centroid" shape and stated the per-cell rule — a temporal: "per-cell" field is classed none whatever its reducer (semantics.py::field_composability), pinned by the fixture's "observed": {"class": "none"}.
    • Review fold, 3324dbe8: §4.6's Structure bullet enumerated only the location-gated {field}_locations sibling for a column's resolution groups; extended it to both channel siblings, matching §7 and the committed golden tests/data/spec/temporal/1/1/2/1/3/all.pyramid.zarr/4/ (which holds h_tdigest_times) — column.py folds via the channel-generic field_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-cell observed array 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 spec green (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

  • None — the design was ruled 2026-08-17 (the (c) per-centroid-everywhere amendment); this is the documentation catch-up moczarr#44's sweep flagged (question 2 there).

@espg espg added the implement label Aug 18, 2026

@espg espg left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.py L205-212 does if meta.get("temporal") is not None: fields[name]["temporal"] = str(meta["temporal"]), and _validate_temporal_shape (src/zagg/config.py L1851-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 the approximate branch only, so on a digest field the value can only ever be "per-centroid" (per-centroid requires kind: ragged).
  • The committed fixture agrees: tests/data/spec/temporal/morton_hive.jsonpyramid.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_config L2046-2052 stamps variables[name]["temporal"] on the reconstructed template exactly as it does location, and COMPANION_CHANNELS / field_companions (L287-306) make the sibling {field}_times and its declaration channel-generic. check_companion_match (L246-281) additionally pins the fold's shape to per-centroid, which is the amendment restated in code.
  • "the payload's §8.3 times binding" is the right citation, not §1.2: §8.3's Binding paragraph defines times as a sibling of the ragged block, and §1.2 L156-158 itself points outward ("The §2.0 weights key and §8.3's times binding are likewise spec-owned"). The asymmetry with locations/§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 in docs/ 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.md carry 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; none fields are absent),
plus — for a field whose §4.5 entry carries location — that field's
{field}_locations sibling, 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.

Comment thread docs/specification.md Outdated
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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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-cell field — the §8.2 dense shape, GEDI's leaf companion (ruling 2) — is a different object and stays none. 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.jsondeclarations.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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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 classed none whatever 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."

Comment thread docs/specification.md
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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

@espg

espg commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

Acknowledging the review-body (out-of-diff) finding — §4.6 Structure bullet enumerates only the location sibling. Folded in 3324dbe8.

Verified before editing: src/zagg/column.py builds and folds the column's resolution groups through the channel-generic field_companions (L209 staging, L280 folding — src/zagg/sweep_overview.py L298-311 iterates COMPANION_CHANNELS, so it returns {field}_times for a temporal entry just as it returns {field}_locations for a location one), and the committed golden tests/data/spec/temporal/1/1/2/1/3/all.pyramid.zarr/4/ holds h_tdigest, h_tdigest_locations, and h_tdigest_times. §7 already stated it; §4.6 had not caught up.

The bullet now reads "plus every channel sibling that field's §4.5 entry declares — the {field}_locations sibling for a location entry (carrying the §9 declaration), the {field}_times sibling for a temporal entry (carrying the §8.3 declaration) — each row-aligned with its payload", with the written-together-or-not-at-all invariant generalized to "Each pair" and cross-referenced to §9.1 + §8.3.

Checks on the new head 3324dbe8: uv run pytest tests/ -q -k spec → 191 passed; uvx codespell docs/specification.md → only the known pre-existing re-declares hit (now line 1770), untouched by this diff.

@espg
espg marked this pull request as ready for review August 18, 2026 21:23
@espg espg added the waiting label Aug 18, 2026
@espg
espg merged commit 745ed73 into main Aug 19, 2026
11 checks passed
@espg
espg deleted the claude/small-fixes-2026-08-18 branch August 19, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec §4.5: temporal-companion sentence contradicts the per-centroid amendment; temporal: field-entry key undocumented

1 participant