Skip to content

HF-307: warn once when a typed key enters its notice window (5/8) - #1736

Open
marcin-kordas-hoc wants to merge 4 commits into
hf-307-entitlement-gating-pr4from
spike/hf307-notice-window
Open

HF-307: warn once when a typed key enters its notice window (5/8)#1736
marcin-kordas-hoc wants to merge 4 commits into
hf-307-entitlement-gating-pr4from
spike/hf307-notice-window

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Consumes the license key's notice field: 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 (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=0 and notice>0 whose 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 the release_until axis), window ends exactly where the soft-stop phase would begin, blind to graceDays by design.
  • licenseKeyValidator.ts: notifyLicenseKeyNotice() with per-key warn-once accounting (_noticedKeys keyed 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.
  • Blocking behaviour at/after expiry is byte-identical to before; the key's silent flags suppress the warning; CHANGELOG entry included.

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 --noEmit clean, eslint --quiet clean 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_until expiry falls inside the key’s configured notice window. The message names the last covered day (“valid until … (UTC)”), respects the key’s silent flags, and never runs for release_until keys or after hard expiry—blocking at/after expiry is unchanged.

licenseResolution introduces expiryWithinNoticeWindow() (usage axis only, window ends at the usage deadline, ignores grace) and only applies noticeDays for rev-5 license shapes. resolveLicense accepts notifyConsole so transient config resolution can skip console output.

licenseKeyValidator adds notifyLicenseKeyNotice() with per-key warn-once tracking (checksum-based identity), separate from the existing once-per-page invalid/missing/expired messages.

Config / mergeConfig pass notifyLicenseMessages into resolution; rebuildWithConfig silences 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.

@qunabu

qunabu commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 18, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the spike/hf307-notice-window branch from c1cce95 to cbe23f0 Compare August 18, 2026 06:17
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Performance comparison of head (db92bcd) vs base (cd79b06)

                                     testName |    base |   head | change
-------------------------------------------------------------------------
                                      Sheet A |   495.5 | 496.87 | +0.28%
                                      Sheet B |  162.91 | 164.61 | +1.04%
                                      Sheet T |  144.44 | 144.88 | +0.30%
                                Column ranges |  526.85 | 525.57 | -0.24%
                                Sorted lookup | 15010.7 |  14891 | -0.80%
Sheet A:  change value, add/remove row/column |   18.02 |  17.07 | -5.27%
 Sheet B: change value, add/remove row/column |  148.54 | 150.26 | +1.16%
                   Column ranges - add column |  163.96 |  161.4 | -1.56%
                Column ranges - without batch |  508.77 | 494.92 | -2.72%
                        Column ranges - batch |   126.9 | 126.63 | -0.21%

@marcin-kordas-hoc
marcin-kordas-hoc marked this pull request as ready for review August 18, 2026 07:32
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the hf-307-entitlement-gating-pr4 branch from 5d67b5d to 46ac32d Compare August 18, 2026 08:57
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the spike/hf307-notice-window branch from cbe23f0 to 7b5398d Compare August 19, 2026 09:55
@marcin-kordas-hoc

Copy link
Copy Markdown
Collaborator Author

Paired tests PR: handsontable/hyperformula-tests#37 — merge it BEFORE this one (fetch-tests pairs by branch name).

@marcin-kordas-hoc marcin-kordas-hoc changed the title HF-307: warn once when a typed key enters its notice window (5/6) HF-307: warn once when a typed key enters its notice window (5/8) Aug 20, 2026

@Tobiadefami Tobiadefami left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/license/licenseResolution.ts Outdated
// 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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

marcin-kordas-hoc and others added 4 commits August 26, 2026 03:17
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>
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the spike/hf307-notice-window branch from aa835bf to db92bcd Compare August 26, 2026 03:34
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the hf-307-entitlement-gating-pr4 branch from e9863f2 to cd79b06 Compare August 26, 2026 03:34
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (hf-307-entitlement-gating-pr4@cd79b06). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                       Coverage Diff                        @@
##             hf-307-entitlement-gating-pr4    #1736   +/-   ##
================================================================
  Coverage                                 ?   97.38%           
================================================================
  Files                                    ?      204           
  Lines                                    ?    16236           
  Branches                                 ?     3493           
================================================================
  Hits                                     ?    15812           
  Misses                                   ?      424           
  Partials                                 ?        0           
Files with missing lines Coverage Δ
src/Config.ts 94.69% <100.00%> (ø)
src/HyperFormula.ts 99.76% <100.00%> (ø)
src/helpers/licenseKeyValidator.ts 95.74% <100.00%> (ø)
src/license/licenseResolution.ts 97.43% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

3 participants