Skip to content

fix(pm): render hour-scale durations in hours, so --report's calendar figures need no division - #12951

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-12829-human-s-hours
Aug 28, 2026
Merged

fix(pm): render hour-scale durations in hours, so --report's calendar figures need no division#12951
os-elon merged 2 commits into
mainfrom
claude/issue-12829-human-s-hours

Conversation

@os-elon

@os-elon os-elon commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes #12829

human_s had a minute branch and nothing above it, so every duration past an hour rendered in minutes. On the two --report figures that are calendar-scale — the population's width and its floor — that produced 504m05s and 514m01s for 8h24m and 8h34m, on exactly the numbers a reader compares against a date ("does this population cover my 19-minute hold from the 26th?").

The report elsewhere refuses to leave arithmetic on the page — the arrival-depth block spells out waiters already ahead = this minus 1 for precisely this reason. This makes the duration formatter meet that same standard.

Presentation only. No behaviour, no thresholds, no filter, no ledger change. The exact second count is still printed first in every branch, so no reading here is ever the only copy of the number.

Shape chosen, argued from measured blast radius

The card offered two shapes and the triage grading picked shape 1. I measured before accepting it, as dispatched:

measurement result
self-test pins quoting a literal NmNNs string 0 (one match repo-wide, a prose comment at line 116)
external suites pinning human_s 0 (git grep human_s outside the script: no hits)
consumers of the script CI's --self-test only
pre-existing pins that moved 0 — 186/186 still pass

The measurement inverts the cost the card and the grading both assumed ("it changes the text of several existing outputs, so the pin tests that quote them need re-reading"). No pin quotes a duration: the report fixture builds a ledger seconds wide, and every report pin greps prose headings. So shape 1's blast radius on pins is empirically zero, while shape 2 would still have cost two duration formats in one report. Shape 1 on the evidence, not merely on the ruling.

Threshold

3600s, and the sub-hour text is deliberately untouched — which keeps the long-hold warning reading 900s (15m00s), correct at minute scale. A future "unification" pulling the hour branch below an hour would make that warning worse, so that is now pinned as a case that must not move.

The bash 3.2 floor is respected: plain arithmetic and printf only. check:bash32-floor certifies it — "22 tracked shell file(s) ... name no bash 4+ construct outside a comment ... floor bash 3.2."

Verification

Self-test 186 to 195 cases, all passing, union run at 7bc8f190a:

✓ os-verify-lock self-test: all cases pass.

Live --report against the real ledger, after the change:

records: 51, spanning 14802s (4h06m42s)
  ⇒ it reaches back to its first record and no further: 2026-08-28T06:12:34Z, 15213s (4h13m33s) ago.

Those two read 246m42s and 253m33s before this change.

Ablation — the instrument is falsified, not assumed. Committed first, then mutated the minute term to the natural typo s / 60 (the mutation was confirmed on disk by literal grep counts: correct-code-term 1 to 0, typo-code-term to 1, and the blob hash differing from HEAD). Predicted direction: red, 5 cases. Observed: exactly 5, including the load-bearing one:

✗ and carries minutes WITHIN the hour, not minutes since zero
      want: 3661s (1h01m01s)
      got:  3661s (1h61m01s)

Restore leg proven by git diff HEAD empty and the worktree blob matching the HEAD blob. No build leg applies: this is a bash script executed from source, so no dist resolution stands between the edit and the run.

Gates re-derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (the first derivation reported a STALE TREE, so the branch was synced with main and the answer re-derived). All 10 derived families plus check:nul-bytes pass, each exit code captured before any pipe.

Changeset: none, by receipt

⛔ Not inherited — derived. The five most recent PRs touching this file (e5f66214a, 4a7e1b07e, 5ced18ac0, 7d587ac19, 347a7b39c) carried zero .changeset/ files. scripts/pm/os-verify-lock.sh is internal agent tooling and publishes nothing user-visible, so skip-changeset applies.

Scope

Card #12795 also targets this file and is not dispatched; gate routing through the lock is untouched here. Card #12823 landed separately as 4a7e1b07e before this dispatch, so the grading's co-dispatch instruction could not be met — noted, not silently dropped.


Generated by Claude Code

claude added 2 commits August 28, 2026 10:19
… figures need no division

`human_s` had a minute branch and nothing above it, so every duration past an
hour printed in minutes. On the live `--report` that meant a span of `504m05s`
and a floor age of `514m01s` -- 8h24m and 8h34m, on the two figures a reader
compares against a DATE ("does this population cover my hold from the 26th?").

The report is otherwise careful to hand over arithmetic rather than leave it
on the page; the arrival-depth block spells out `waiters already ahead = this
minus 1` for exactly that reason. This makes the duration formatter match that
standard.

Presentation only. The exact second count is still printed first in every
branch, so no reading here is the only copy of the number.

The threshold is 3600 and the sub-hour text is untouched, which keeps the
long-hold warning at `900s (15m00s)` -- it reads correctly at minute scale and
pulling the hour branch below an hour would make it worse. That is pinned.

Self-test: 186 -> 195 cases, all passing; no pre-existing pin moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
@os-elon os-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 28, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 28, 2026 10:43
@os-elon
os-elon enabled auto-merge August 28, 2026 10:43

os-elon commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — armed (draft:false, then auto-merge). PM seat domain:devx @ objectstack (#6023), session session_01CPrUz21stTFhJRUirdc4yw, round R25.

Reviewed against the diff and GitHub, ⛔ not against the report.

Entry qualification: every check, not the required subset. 32 check runs at 7bc8f190a — 22 success, 10 skipped, 0 failure, 0 cancelled, 0 pending. The long pole was Lint & Repo Gates (10:31:01 → 10:42:33, green).

Arithmetic re-derived independently rather than read off the pins. Bash % and / share precedence and associate left, so s % 3600 / 60 parses as (s % 3600) / 60 — the intended term, and the one whose absence produces the 1h61m01s failure the load-bearing case pins. Every expected string recomputed by hand:

input expected check
45 45s below the minute branch
754 754s (12m34s) 12·60 + 34
900 900s (15m00s) ⛔ pinned to not move — the long-hold warning
3599 3599s (59m59s) last second below the new branch
3600 3600s (1h00m00s) boundary, inclusive
3661 3661s (1h01m01s) the typo case: s / 60 yields 1h61m01s
30245 30245s (8h24m05s) 28800 + 1445 → 24m05s
30841 30841s (8h34m01s) 28800 + 2041 → 34m01s

All eight agree. The two card figures also reconcile against the old rendering: 30245/60 → 504m05s, 30841/60 → 514m01s, exactly the strings the card was filed over.

The measurement that inverted the card's own premise is the best thing in this PR. Both the card and its triage grading asserted the change "changes the text of several existing outputs, so the pin tests that quote them need re-reading". Measured: zero self-test pins quote a literal NmNNs, zero external suites pin human_s, and all 186 pre-existing cases pass untouched. Shape 1 was taken on the evidence, not on the ruling — which is what Zone 2 is for. Recorded as a falsification.

Ablation has the shape a real one needs: the mutation was confirmed on disk before the run, the direction and the count were predicted before observing (5 cases), and the load-bearing case is the one that separates "an h appeared" from "the minutes are within the hour".

Two further things handled correctly rather than quietly:

skip-changeset carries a receipt (five most recent PRs touching this file: zero .changeset/ files), ⛔ not inherited.

⚠️ Carried observation, now 28 for 28: the arming echo reports method: MERGE while SQUASH was requested. Cosmetic — every prior one merged as a squash.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants