Skip to content

Rejustifies the duration subset rule - #67

Merged
johnnyt merged 1 commit into
mainfrom
sui-cw0-rejustify-duration-shape
Sep 2, 2026
Merged

Rejustifies the duration subset rule#67
johnnyt merged 1 commit into
mainfrom
sui-cw0-rejustify-duration-shape

Conversation

@johnnyt

@johnnyt johnnyt commented Sep 1, 2026

Copy link
Copy Markdown
Member

Why

sui-cw0 (mirrors px-69c) was filed while predicator's evaluated durations
had an expression-dependent key set: seven units normally, eight when the
expression mentioned ms, against a declared type of eight. px-69c closed on
the eight-key branch (predicator PR #176, shipped in predicator 9.0.0), so the
justification this repo wrote against the seven-key output is now false, even
though the code it justifies is still right.

What

StatifierUI.Shape.duration?/1 still accepts any non-empty atom-keyed subset
of the eight units. The @doc no longer explains that by citing a parser bug;
it explains it as the viewer's own tolerance - statifier-ui renders a value
stream it did not produce (a hand-written fixture, a decoded wire message, a
datamodel from an older engine or another interpreter), and a duration short a
unit should read as a duration rather than collapse into a seven-field map.

The pinned test "durations from real predicator expressions" was passing
incidentally and now pins the rule explicitly. See the sabotage evidence below.

Contract re-read, with citations

Resolved dependency: mix.lock:27 pins predicator at 9.0.0 (>= 9.0.0, as
required).

  • deps/predicator/lib/predicator/types.ex:37-46 - @type duration declares
    all eight units, milliseconds: non_neg_integer() among them, and the
    moduledoc at types.ex:24-29 states "Every unit the expression did not name
    is present and 0, so the map is always this wide".

  • deps/predicator/lib/predicator/duration.ex:36-48 - new/1 builds all eight
    via Keyword.get(opts, :milliseconds, 0).

  • Live probe against the resolved dependency, all eight keys every time:

    "3d"       -> 8 keys
    "2w"       -> 8 keys
    "1h30m"    -> 8 keys
    "3d8h"     -> 8 keys
    "500ms"    -> 8 keys
    "1s500ms"  -> 8 keys
    

Sabotage evidence

Temporarily tightening duration?/1 from MapSet.subset?/2 to
MapSet.equal?/2 (require exactly eight keys), then reverting:

  • Before this branch's test change: 3 failures - the module doctest, the
    seven-key test, the partial-duration test. "durations from real predicator expressions" stayed green. It was passing incidentally, which is the
    outcome this bead was filed to prevent.
  • After this branch's test change: 4 failures - the same three plus
    "durations from real predicator expressions", failing on
    Shape.infer(Map.delete(value, :milliseconds)) == :duration.

The rule is reverted to MapSet.subset?/2; the sabotage was an experiment, not
a change. The test now asserts both halves: that real evaluator output carries
all eight keys (so a regression upstream fails at the dependency), and that
dropping a unit from that real output still infers as a duration (the tolerance
the subset rule exists for).

Value.encode/1 is correct as it stands - unchanged here

lib/statifier_ui/value.ex:191-196 maps over all eight @duration_units with
Map.get(duration, unit, 0), so encode/1 fills an absent :milliseconds
with 0. Under the eight-key contract that writes a unit the value domain
declares (types.ex:45) and that Duration.new/1 itself produces - it is
canonicalization, not invention. The bead's encode-side worry applied only to
the seven-key branch, which predicator did not take. No change was needed and
none was made; the file is untouched on this branch.

Residual, deliberately not fixed here: value.ex:24-29's moduledoc carries
the same stale parenthetical ("predicator's parser emits seven, omitting
:milliseconds"). It is prose only and outside this branch's declared file
set, so it is reported rather than edited. Worth a follow-up bead.

Sibling bead

st-4epq in statifier-ex (also mirrors: px-69c) is CLOSED - "Acceptance
criteria satisfied via st-793d (predicator 9.0 consumption bump, merged PR
#175); no engine-side work remained". Its notes record a seven-versus-eight
audit of that repo finding no key-set assumptions. Nothing is owed there.

Notes

  • Full mix quality green on the rebased HEAD (attested, not scoped): format,
    compile, dependencies, doctor, credo, 726 tests / 92.9% coverage, dialyzer.
    Gettext and Sobelow are the two permanent not-applicable skips.
  • No changelog fragment: doc and test only, no public API or observable
    behavior change, per changelog.d/README.md.
  • Rebased onto main at 4ad5a9d; no commits replayed, no conflicts.
  • Mirrored with px-69c - not closed here. No Closes line by design:
    this half stays open for the operator.

Refs: sui-cw0

Predicator 9.0 settled the eight-key duration contract (px-69c), so
Shape.duration?/1's @doc no longer explains its any-non-empty-subset
rule by citing a seven-key parser. The rule is unchanged; the reason
is now the viewer's own: statifier-ui renders a value stream it did
not produce, and a duration short a unit should still read as a
duration.

The pinned "durations from real predicator expressions" test was
passing incidentally - under 9.0 every expression yields all eight
keys, so tightening the rule to require exactly eight left it green.
It now asserts the eight-key contract explicitly and asserts that
dropping a unit from real evaluator output still infers as a
duration, which is the tolerance the rule exists for.

Value.encode/1 is unchanged: filling :milliseconds with 0 writes a
unit the contract declares, not one it lacks.

Refs: sui-cw0
@johnnyt
johnnyt merged commit e08ac60 into main Sep 2, 2026
1 check passed
@johnnyt
johnnyt deleted the sui-cw0-rejustify-duration-shape branch September 2, 2026 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant