Skip to content

Port the post-v0.228.0 development train - #70

Merged
kapoorankush merged 3 commits into
masterfrom
port/v0.229.0
Sep 21, 2026
Merged

kapoorankush merged 3 commits into
masterfrom
port/v0.229.0

Conversation

@kapoorankush

Copy link
Copy Markdown
Owner

Ports the litclock-dev train merged 2026-09-19/20 (litclock-dev#872-#878), boundary afa2d6b4..fde50fc6.

Gate

Bench QA pass of 2026-09-20 on a fresh flash of dev-20260920-512c291 (Pi Zero 2 W): 13 checks, no blocking findings, every member of this train exercised on real hardware — the setup-key rotation arms, the shell-history wipe on the PWA factory reset, the registry corpus resolution, and the Stage A self-test.

What owners get

One line in the CHANGELOG, because it is the only thing visible from outside:

  • Gift mode and the app's Factory reset now wipe and lock the two default shell history files, as clone prep already did (litclock-dev#868). history -c reaches only the script's own shell; the console or SSH shell the operator ran it from writes its history back on exit, during the power-off. The lock is a root-owned empty directory at the path, which fails that write with EISDIR; first-boot.sh removes both on the not-yet-set-up path, so the recipient's first login gets an ordinary history file.

Shipping silently, by design

  • Per-language corpus resolution (litclock-dev#870). The quote CSV is resolved from the languages.json registry for the active language; LITCLOCK_CORPUS_CSV still overrides, and the PNG lookup reads image_corpus_path() on purpose — the corpus the images were baked from. Latent while English is the sole active language: the pre-rendered images stay English, so a second language needs on-device text rendering before it can be activated.
  • The runtime-render self-test, shipped INERT (litclock-dev#871 Stage A). On a marker-bearing device an applied release now asks whether the device can actually paint a quote from text, and records the verdict in /var/lib/litclock/runtime-render-selftest.json. It changes no setting, it renders into a throwaway directory so the live frame is untouched, weather is forced off, and a failed self-test is not an update failure. First field measurement: 3.8s on a Pi Zero 2 W during an update, 3.4-3.7s idle — whole-process wall time (interpreter start + PIL import + render), not render time, and not comparable to frame settle.

Plus: the LKG recorder stops claiming bootcheck/revert is unshipped (litclock-dev#877), the WiFi retry tests stub an IP-geo resolver that was making a live network call (litclock-dev#876), and CLAUDE.md gains the 2026-09-20 bench results and the rewritten litclock-dev#867 raw-journal check (the old one passed vacuously).

Port mechanics

  • Boundary established by content, not by the previous port commit's stated range.
  • 48 bare #NNN and 14 dev#NNN requalified to litclock-dev#NNN, on ported lines only, validated by this repo's own tests/test_issue_ref_namespace.py.
  • Hand-merged where this repo has diverged: README (its own structure), docs/script-reference.md (the #resetting anchor), scripts/lib/state.sh (the already-qualified writer-lock header), and two comment conflicts.
  • CONTRIBUTING.md edited in place to preserve CRLF.
  • Scrubbed: one maintainer-local harness path.

Review findings, fixed on dev and ported in

/review on this branch found two defects the train carried over from dev. Both were fixed on dev first (litclock-dev#879, #880, #882) and ported here, which is why this branch has three commits.

  • The IP-geo stub litclock-dev#876 added covered one test class of three. Measured on this checkout with a getaddrinfo probe: 24 DNS lookups of ip-api.com across 8 tests. The resolver's success path calls set_system_timezone(), which shells out to sudo, and it is not sandboxed by anything these tests patch — so on a machine where the installer has run, pytest tests/ could change the system timezone. The fixture is module-scoped now. Probe after the fix: 0.
  • ${EPOCHREALTIME/./} recorded a silently wrong duration_s under a non-dot LC_NUMERIC. Now ${EPOCHREALTIME//[^0-9]/} — every non-digit, because the separator is not limited to a dot and a comma (glibc gives fa_IR and ps_AF U+066B). Production always got C from systemd; the exposure is a maintainer running update.sh by hand from a non-English desktop, which the README tells owners they may do.

Four review rounds each broke the previous round's test with a shape it did not model, so the test that holds this now executes the shipped function with stubs and a known-duration painter and asserts on the value the record writer receives, rather than asserting anything about source text.

Two findings belong to this repo alone and are fixed here: a doubled litclock-dev litclock-dev#876 the requalifier created, and PUBLIC_NUMBER_CEILING raised 67 → 70 (two ports behind), with the 68–70 audit recorded.

Hardware QA

The train was gated by the full bench pass of 2026-09-20 (fresh flash of dev-20260920-512c291, 13 checks, no blocking findings). The only device-facing code added after that pass is the two timing expansions above, so they got their own targeted check on the bench today — the Pi's bash is 5.2.15 against 5.2.21 on the dev box, which is why it needed hardware:

  • real function, real painter, real clock: recorded 3.4s, against an independently measured 3497ms — agreeing to the tenth, across two runs that moved together. Consistent with the 2026-09-20 baseline (3.8s under update load, 3.4–3.7s idle), so the widening did not move the figure.
  • all three separators handled on 5.2.15; the old pattern handles only the dot.
  • control: with the strip replaced by a no-op, the duration comes back empty against a real 3558ms — so the check can fail.
  • live frame untouched, LITCLOCK_RUNTIME_RENDER still false, record file untouched.

No OTA run: the self-test is inert this release, update.sh has no set -e, and a failed self-test is explicitly not an update failure, so a broken expansion can mis-record a number but cannot stop an update.

Verification

ruff check . clean, shellcheck (exact CI invocation) clean, pytest 4648 passed / 66 skipped, vitest 209 passed.

Ports litclock-dev afa2d6b4..fde50fc6 — the seven PRs merged 2026-09-19/20
(litclock-dev#872-#878). The boundary was established by CONTENT, not by the
previous port commit's stated range: public carries litclock-dev#865's
blocked-sha and none of litclock-dev#868/#870/#871, and the file sizes agree
(update.sh 2542 vs 2739, state.sh 317 vs 416, quote_corpus.py 249 vs 368).

Gated on the bench QA pass of 2026-09-20 (`dev-20260920-512c291`, a fresh
flash on a Pi Zero 2 W): 13 checks, no blocking findings, every member of this
train exercised on hardware.

What owners get (one line in the CHANGELOG, because that is all that is
visible from outside):
- gift mode and the app's Factory reset now wipe AND LOCK the default shell
  history files, as clone prep already did (litclock-dev#868). The lock is a
  root-owned empty directory at the path: `history -c` reaches only the
  script's own shell, and the console or SSH shell the operator ran it from
  writes its history back on exit, during the power-off. first-boot.sh removes
  both directories on the not-yet-set-up path, so the recipient's first login
  gets an ordinary history file.

Shipping silently, by design:
- the quote corpus is resolved per active language from the registry
  (litclock-dev#870). Latent while English is the sole active language: the
  pre-rendered PNGs stay English, so a second language needs on-device text
  rendering before it can be activated.
- the updater's runtime-render self-test, shipped INERT (litclock-dev#871
  Stage A). On a marker-bearing device an applied release now asks whether the
  device can actually paint a quote from text and records the verdict in
  /var/lib/litclock/runtime-render-selftest.json; it changes no setting, and a
  failure is not an update failure. First field measurement: 3.8s on a Pi
  Zero 2 W during an update, 3.4-3.7s idle — whole-process wall time, not
  render time.

Also: the LKG recorder no longer claims bootcheck/revert is unshipped
(litclock-dev#877), the WiFi retry tests stub the IP-geo resolver that was
making a live network call (litclock-dev#876), and CLAUDE.md gains the
2026-09-20 bench results plus the rewritten litclock-dev#867 raw-journal check
(the old one passed vacuously).

Port mechanics:
- 48 bare `#NNN` refs and 14 `dev#NNN` shorthands requalified to
  `litclock-dev#NNN` on ported lines only, validated by this repo's own
  tests/test_issue_ref_namespace.py rather than by a hand regex.
- Hand-merged where public has diverged: README (its own structure and
  headings), docs/script-reference.md (public's `#resetting` anchor),
  scripts/lib/state.sh (public's already-qualified env.sh writer-lock header),
  and the two comment conflicts in update.sh and status.py.
- CONTRIBUTING.md edited in place to preserve its CRLF line endings.
- Scrubbed on the way over: the `~/archives/litclock-qa` harness path, which
  does not exist for a reader of this repo.

ruff clean, shellcheck clean, pytest 4638 passed / 66 skipped, vitest 209
passed.
Ports litclock-dev 7c2063ea..9c8ddc81. Both defects were carried over by the
train in the parent commit, not introduced by it; both were found by the
gstack /review of this branch.

- tests/test_wifi_retry_flow.py: the IP-geo stub litclock-dev#876 added covered
  ONE class of three. Measured with a getaddrinfo probe on this checkout: 24
  DNS lookups of ip-api.com across 8 tests. The resolver's success path calls
  set_system_timezone(), which shells out to sudo, and ENV_FILE is
  monkeypatched by these tests while that call is not sandboxed by anything —
  so on a machine where the installer has run, `pytest tests/` could change the
  system timezone. The fixture is module-scoped and autouse now, so a class
  inherits it rather than appearing on a list of the classes somebody
  remembered. Probe on this branch after the port: 0 attempts, 70 passed.

- scripts/update.sh: ${EPOCHREALTIME/[.,]/}, not a literal dot. The separator
  follows LC_NUMERIC, so under a comma locale the strip matched nothing and the
  comma parsed as bash arithmetic's comma operator. Three measured outcomes,
  all wrong; the dangerous one is silent (rc 0, nothing on stderr) and IS
  recorded, beside "result":"passed", in the file litclock-dev#871 Stage B is
  meant to gate on. Production always got C from systemd; the exposure is a
  maintainer running the script by hand from a non-English desktop, which
  README's manual-update section tells owners they may do.

The seven tests lift the shipped t0=/dur_ms=/duration= assignments out of
_runtime_render_selftest and execute them verbatim with EPOCHREALTIME injected,
rather than a copy — a copy proves nothing about the script, and neither did
counting occurrences near it (an indirect-expansion mutant satisfied the count
and reintroduced the bug). Five mutants verified red against THIS checkout's
update.sh, including the full revert.

No CHANGELOG entry: nothing here is visible to an owner.

ruff clean, shellcheck clean, pytest 4645 passed / 66 skipped, vitest 209.
…dings of this repo's own

Ports litclock-dev 9c8ddc81..7438f611, plus two fixes that exist only here.

From dev:
- `${EPOCHREALTIME//[^0-9]/}` — strip every non-digit, not just `[.,]`. glibc
  gives fa_IR and ps_AF U+066B (٫), and measured, `[.,]` leaves it in place for
  rc 0, duration=0.0, `invalid arithmetic operator` on stderr, and a wrong
  value jq accepts and records. Same hazard class, rarer locale family.
- The test that holds it stopped asserting source text. Four review rounds each
  broke the previous round's rule with a shape it did not model (a decoy in a
  comment, an indirect expansion, a `local`-prefixed assignment, and finally
  `printf -v duration '0.0'`, which is not an assignment at all). So a second
  class EXECUTES the shipped `_runtime_render_selftest` with its helpers
  stubbed and a painter that sleeps a known time, and asserts on the value
  `_runtime_selftest_record_write` is handed, with a second painter as the
  control. Verified against THIS checkout: printf -v and eval red, the full
  revert red, and narrowing back to `[.,]` red.
- The hazard test now uses a fraction whose wrong output `jq tonumber`
  ACCEPTS, so it exercises the silent-and-recorded shape the fix exists for;
  the earlier default produced `0.-3`, which jq refuses, i.e. the loud band.

Found in this repo, by the review of this branch at its final HEAD:
- `tests/test_wifi_retry_flow.py:161` carried `litclock-dev litclock-dev#876` —
  the requalifier prefixed a `#876` on a line that already said `litclock-dev`
  with a space. The only occurrence in the tree; this is the dev#767 doubled-
  qualifier class, so it is worth catching rather than leaving.
- `PUBLIC_NUMBER_CEILING` was still 67, two ports behind: #68 and #69 landed
  the v0.227.0 and v0.228.0 trains and this lands as #70. Audited 68..70 across
  all tracked files — ZERO bare `#N` in that range, so nothing in the tree
  changes meaning — and raised to 70 with that audit recorded, which is the
  deliberate act the module docstring asks for.

Also removed a stray empty `0755/` directory in the working tree, a `mkdir -m`
typo from 2026-09-13.

ruff clean, shellcheck clean, pytest 4648 passed / 66 skipped, vitest 209.
@kapoorankush
kapoorankush merged commit 4daba5e into master Sep 21, 2026
6 checks passed
@kapoorankush
kapoorankush deleted the port/v0.229.0 branch September 21, 2026 14:10
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