HF-307: warn once when a typed key enters its notice window (5/8) - #1736
HF-307: warn once when a typed key enters its notice window (5/8)#1736marcin-kordas-hoc wants to merge 4 commits into
Conversation
|
Task linked: HF-307 Implement feature packages and add-ons in HF |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | db92bcd | Commit Preview URL Branch Preview URL |
Aug 26 2026, 03:39 AM |
c1cce95 to
cbe23f0
Compare
Performance comparison of head (db92bcd) vs base (cd79b06) |
5d67b5d to
46ac32d
Compare
cbe23f0 to
7b5398d
Compare
|
Paired tests PR: handsontable/hyperformula-tests#37 — merge it BEFORE this one (fetch-tests pairs by branch name). |
Tobiadefami
left a comment
There was a problem hiding this comment.
Reviewed at aa835bf together with the paired tests at c832fff7. I checked the notice-window boundaries, usage/release axes, silence and warn-once behavior, config rebuild path, current automated findings, and later stacked branches. The 12 focused license suites pass (215 tests), and the current engine checks are green. I found one material boundary issue: the notice period opens one day late, as noted inline.
| // The first instant no longer on the usage_until day — the same boundary `validityOf` uses | ||
| // before adding its grace term. | ||
| const usageAxisDeadline = terms.expiryTimestamp + MILLISECONDS_PER_DAY | ||
| const noticeWindowStart = usageAxisDeadline - (terms.expiry.noticeDays * MILLISECONDS_PER_DAY) |
There was a problem hiding this comment.
Using usageAxisDeadline to derive the start shifts the window one day late. For usage_until: 2027-08-12 and notice: 60, 60 days before the governing date is 2027-06-13T00:00:00Z, but this computes June 14; I reproduced zero warnings at the former instant. The same off-by-one means a 45-day trial with notice: 45 misses its intended first warning day. It would help to subtract noticeDays from terms.expiryTimestamp while keeping usageAxisDeadline as the exclusive end, and add an exact-boundary regression.
The key's `notice` field was parsed into LicenseExpiry and read by nothing. Now a VALID typed key whose usage_until lies within `notice` days of the current UTC instant prints a single console warning naming the expiry date (UTC marker included). The warn-once identity is the key string, not the process — two engines built with two different keys each get their own warning. release_until-axis keys never warn (rev 5: notice/grace have no effect there), the key's silent flags suppress it, and blocking at/after expiry is byte-identical to before (Kuba's D5-A: hard stop stays in 3.5.0, the full rev 5 §4.1 window model is a follow-up). Trials made this concrete: a trial is just a key with grace=0 and notice>0 whose warnings must surface in the console (packages meeting 12.08). Implemented by a prep-ship lane (task HF-307-notice-window); verified here: license suite 165/165 under Jest, tsc --noEmit clean, eslint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PdHPZAjciZFWqGa19Yf7it
Four findings from the spec-to-ship re-review (each cross-confirmed by at least two independent review angles): 1. Message wording: the notice now uses rev 5 section 3.2's own subscription clause - "is valid until <last covered day> (UTC)" - instead of "will expire on". The pre-existing expired message names the first day NOT covered (+1 day, frozen convention), so "expires on Aug 25" followed by "expired on Aug 26" printed two different days for one boundary. 2. The notice read is gated on the key SHAPE (rev 5), not on the field's presence: on the shipped shape the terms come off the LICENSED product's entry - for a dual-product key, Handsontable's - so a stray `notice` field there must not switch HyperFormula's console output on. The expiryWithinNoticeWindow doc also no longer claims kind='usage' implies the date came from usage_until (the envelope-exp fallback is real and documented as accepted standalone; the entitlement re-port removes it). 3. rebuildWithConfig's transient serialization-only Config no longer prints license messages: replacing keyA with keyB used to print keyA's notice in the very call that discards keyA. Config gains an internal-defaulted notifyLicenseMessages parameter, same pattern as showDeprecatedWarns. 4. The warn-once identity is now trim + the trailing 128 chars (the key's own checksum): extractTypedKeyData trims, so 'KEY' and 'KEY\n' are one license and must be one identity; truncation bounds a long-lived process's memory to 128 chars per distinct warned key. CHANGELOG entry gains its PR link. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PdHPZAjciZFWqGa19Yf7it
… not before its end The window start was derived from `usageAxisDeadline` (`usage_until + 1 day`), which makes the window a day shorter than the specification's and opens it a day late. The date-semantics fixtures are explicit: for `usage_until` 2027-08-12 with `notice` 60 the warning must appear from 2027-06-13T00:00:00Z, and 2027-08-12 minus 60 days is exactly that day. Section 4.1 counts the window "before `usage_until`", not before the boundary that ends it. Measured before the change, clean processes with a fixed clock: silent at 2027-06-13T00:00:00Z and still silent through 2027-06-13T23:59:59Z, first warning at 2027-06-14T00:00:00Z. After it, the warning appears exactly at 2027-06-13T00:00:00Z and the instant before it stays silent. The same off-by-one cost a trial its first warning day: with `notice` equal to the whole term (45/45), the window is meant to open on the day the key is issued, and it opened the day after. Measured on a 2026-09-26 trial: silent on 2026-08-11, warning from 2026-08-12. The end of the window is unchanged, so a key already past `usage_until` still gets no notice. Reported by Tobiadefami on this PR, including the trial case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same cleanup as on the branches below: a colleague's first name attached to a business decision, removed from a comment this branch introduces. The substance of the comment is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aa835bf to
db92bcd
Compare
e9863f2 to
cd79b06
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## hf-307-entitlement-gating-pr4 #1736 +/- ##
================================================================
Coverage ? 97.38%
================================================================
Files ? 204
Lines ? 16236
Branches ? 3493
================================================================
Hits ? 15812
Misses ? 424
Partials ? 0
🚀 New features to boost your workflow:
|
Consumes the license key's
noticefield: a VALID typed key whoseusage_untillies withinnoticedays of the current UTC instant prints a single console warning naming the expiry date (with a(UTC)marker). Stacks on #1731; rebased onto its current head on 19.08 (the base moved during PR3/PR4's review passes, which had left this PR conflicting).Why now
Per Kuba's D5-A (ClickUp, 12.08): hard blocking at/after expiry stays in 3.5.0 and the full rev 5 §4.1 window model is a follow-up — but trials made the notice warning concrete for this release: a trial is technically just a key with
grace=0andnotice>0whose warnings must surface in the console (packages meeting 12.08), and the trial mechanism lands in August.What changed
licenseResolution.ts:expiryWithinNoticeWindow()— usage-axis only (rev 5: notice/grace have no effect on therelease_untilaxis), window ends exactly where the soft-stop phase would begin, blind tograceDaysby design.licenseKeyValidator.ts:notifyLicenseKeyNotice()with per-key warn-once accounting (_noticedKeyskeyed by the raw key string) — deliberately not the process-lifetime boolean the state messages use: two engines built with two different keys each get their own warning.Verification
Paired tests:
hyperformula-tests@spike/hf307-notice-window(authored RED-first, 8 assertions: inside/outside window, release_until never warns, expired still hard-blocks, silent suppresses, per-key warn-once across two engines). Full license suite 212/212 under Jest (12 suites,unit/license+unit/helpers/licenseKeyValidator), re-measured after the 19.08 rebase onto the current #1731 (e9863f27) — the earlier 165/165 predated PR3's and PR4's review fixes,tsc --noEmitclean,eslint --quietclean on changed files.🤖 Generated with Claude Code
Note
Medium Risk
Touches license validation and console messaging on every engine build, but changes are additive warnings with explicit silencing for transient configs and no change to expiry blocking or entitlements.
Overview
Adds a one-time console warning for valid typed license keys whose
usage_untilexpiry falls inside the key’s configurednoticewindow. The message names the last covered day (“valid until … (UTC)”), respects the key’s silent flags, and never runs forrelease_untilkeys or after hard expiry—blocking at/after expiry is unchanged.licenseResolutionintroducesexpiryWithinNoticeWindow()(usage axis only, window ends at the usage deadline, ignores grace) and only appliesnoticeDaysfor rev-5 license shapes.resolveLicenseacceptsnotifyConsoleso transient config resolution can skip console output.licenseKeyValidatoraddsnotifyLicenseKeyNotice()with per-key warn-once tracking (checksum-based identity), separate from the existing once-per-page invalid/missing/expired messages.Config/mergeConfigpassnotifyLicenseMessagesinto resolution;rebuildWithConfigsilences notices for the serialization-only config built from the outgoing key while the caller may be replacing it.Reviewed by Cursor Bugbot for commit db92bcd. Bugbot is set up for automated code reviews on this repo. Configure here.