Skip to content

HF-329: re-port the license key reader to the entitlement key format (7/8) - #1740

Open
marcin-kordas-hoc wants to merge 1 commit into
spike/hf307-addon-grantsfrom
hf-329-entitlement-key-envelope
Open

HF-329: re-port the license key reader to the entitlement key format (7/8)#1740
marcin-kordas-hoc wants to merge 1 commit into
spike/hf307-addon-grantsfrom
hf-329-entitlement-key-envelope

Conversation

@marcin-kordas-hoc

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

Copy link
Copy Markdown
Collaborator

7/8 of the HF-307 stack (#1728#1729#1730#1731#1736#1737 → this). Implements HF-329 ("Import license key validator shared code to HF"). Pairs with hyperformula-tests#41merge the tests PR first (CI clones it by branch name).

Why now

Upstream handsontable/license-key 4.0.0 (18.08, DEV-2512) deleted src/typed-key/ — the exact directory our vendored reader was ported from — and replaced the tagged key format with the entitlement key format:

<prose>\n\n[<base64url-payload><sha512-checksum>]

my.handsontable.com already pins 4.0.0 and mints entitlement keys for trials, so the vendored reader was reading a format that no longer exists anywhere. The byte-level rules are specified in the key spec's "Technical implementation" addendum (T1–T14).

What changed

Only the envelope. The entitlement layer — token resolution, the capability table, gate A/B, ensureCapability — is untouched by design.

  • Re-vendored from src/entitlement-key/ at tag 4.0.0: detectFormat.ts and extractKeyData.ts are new ports; sha512.ts/utils.ts are byte-identical upstream (same hashes) and carry over. PROVENANCE.md records the new tag, commit, and sha256 table.
  • The reader is schema-free by upstream design (strict on shape, lenient on vocabulary — T7), so defaultSchema.ts is no longer vendored; the only name the engine reads is its own hyperformula product entry.
  • resolveLicense routes on the vendored detectLicenseKeyFormat (normative T12 order: literals → bracketed block → legacy shape). Literal keys and legacy 25-character keys reach checkLicenseKeyValidity completely unchanged.
  • The invariant stands: only a VALID entitlement key may restrict the entitlement; MISSING/INVALID/EXPIRED resolve to unrestrictedEntitlement() (mutation-tested in the suite).

Decisions to confirm in review

  1. The tagged format is removed outright, not kept alongside. It was never issued to anyone (its 3.5.0 carrier is unreleased, and my.handsontable.com's own PR says "the typed keys never reached production"), upstream deleted it, and PROVENANCE mirrors upstream. With it goes TIER_TO_CAPABILITY_TOKEN — the entitlement format has no tier field.
  2. Deliberate behaviour deltas, each spec-mandated and pinned in the paired tests:
    • a prose edit keeps a key VALID (T8, stated by the addendum as a reversal of the typed-key behaviour);
    • a key granting another product only never expires for HyperFormula (each entry carries its own terms; the licensed-product rule is gone);
    • both-dates, numeric-date, and null-entry payloads are INVALID (T7 strict shape; the old adapter tolerated each).
  3. Dates are carried as the payload's own YYYY-MM-DD strings, never routed through Date formatting (fixture J11).

Testing

Fixtures re-minted with upstream's real generator at 4.0.0 and round-tripped through upstream's extractor; the addendum's T14 conformance vectors pinned 1:1, baselined on its T13 worked example verbatim. Full private suite: 516 suites / 6430 passed, 3 pre-existing skips; tsc --noEmit and ESLint clean.

Found while working on this, NOT fixed here

CHANGELOG.md's [Unreleased] has no Removed entry for the static function metadata API removed by HF-349 (#1724) — a breaking change currently shipping into 3.5.0 unannounced. Raised separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o


Note

High Risk
This is the license-key parser: checksums, expiry, and capability grants all flow through the new reader. A parse or routing bug can unlock or lock features, or reject real customer keys.

Overview
Switches proprietary-key parsing from the unused tagged format to the entitlement envelope (prose plus a trailing [payload+sha512] block), matching handsontable/license-key 4.0.0.

resolveLicense now routes with detectLicenseKeyFormat (literals → bracketed block → 25-character classic). Only a valid entitlement key can restrict capabilities; missing/invalid/expired still resolve unrestricted so a bad key does not start throwing from the CRUD API. Classic keys and gpl-v3 still go through checkLicenseKeyValidity unchanged.

The dual-shape adapter (tier/addons vs capabilities) is gone. Terms come only from the hyperformula product entry (usage_until / release_until, notice, grace, flags). Feature tokens stay opt-in, but the fallback now requires a recognized feat:* token so an unknown feat:… does not wipe the gated API. Docs describe both key spellings and the two expiry axes.

Reviewed by Cursor Bugbot for commit 523316e. Bugbot is set up for automated code reviews on this repo. Configure here.

Spec-to-ship review (2026-08-20): 3 findings fixed here

A six-dimension adversarial review ran over this PR, #1741 and #1743 (spec conformance, decision
conformance, port fidelity, hostile payloads, dialect table, API surface). What it found against
this commit, all fixed above and each one verified by running it:

  1. A hostile key could crash engine construction. capabilityTokens.push(...grant.capabilities)
    spread an attacker-sized array into a call: measured, a checksum-valid key carrying 125 000
    tokens threw RangeError: Maximum call stack size exceeded out of buildFromArray instead of
    resolving to a verdict — the one thing this module's own doc comment promises never happens. Now
    appended element by element; 200 000 tokens (a 3 MB key) resolves in 141 ms. Pinned by a test.
  2. The T14 payload-tamper vector was vacuous. rejects one character changed in the payload segment [catches: the checksum doing nothing] passed with the checksum comparison deleted
    outright, because the flipped character corrupted the payload's products key and the T7 shape
    check rejected it first. Mutation-verified in both directions: with the comparison removed only
    the checksum-segment vector failed. Replaced with a well-formed payload (the T13 record with
    notice: 61) carrying T13's original checksum, plus a positive control asserting the same
    payload with its own checksum is accepted — so the rejection can only come from the checksum.
  3. Two inaccuracies in what this PR wrote. PROVENANCE.md's divergence DATE type is not supported by arithmetic and comparison operators #3 claimed the typed
    entry's "fields carry their verified types", but the date field is verified only by matching
    String(value) against YYYY-MM-DD, so an array holding one date string passes and the declared
    string is wider than the value (faithful to upstream — the manifest was the thing overstating).
    And isNonNegativeInteger had drifted to the global isFinite where upstream uses
    Number.isFinite: behaviour-identical behind the typeof guard, but an undocumented edit inside
    a hash-pinned mirror. Both corrected.

Also corrected in the guide: the classic-key example showed 20 characters next to the sentence
"25 characters", and the entitlement-key paragraph said the text "may be re-wrapped or re-formatted
… without affecting the key" — true of the prose, false of the block, which has to arrive character
for character.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 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 523316e Commit Preview URL

Branch Preview URL
Aug 21 2026, 01:36 AM

@qunabu

qunabu commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

Performance comparison of head (523316e) vs base (4ab3cc6)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |  502.01 |  489.03 | -2.59%
                                      Sheet B |  159.01 |  156.59 | -1.52%
                                      Sheet T |  139.79 |  137.26 | -1.81%
                                Column ranges |  471.96 |  467.29 | -0.99%
                                Sorted lookup | 13999.4 | 14502.5 | +3.59%
Sheet A:  change value, add/remove row/column |   14.65 |   14.83 | +1.23%
 Sheet B: change value, add/remove row/column |  131.46 |  132.03 | +0.43%
                   Column ranges - add column |  147.42 |  145.55 | -1.27%
                Column ranges - without batch |  456.24 |  451.43 | -1.05%
                        Column ranges - batch |  113.77 |  111.03 | -2.41%

@marcin-kordas-hoc
marcin-kordas-hoc marked this pull request as ready for review August 20, 2026 09:10
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the spike/hf307-addon-grants branch from 9933ba3 to 4ab3cc6 Compare August 20, 2026 12:54
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the hf-329-entitlement-key-envelope branch from 864ee26 to 3ad0a5b Compare August 20, 2026 13:09
Upstream handsontable/license-key 4.0.0 (DEV-2512) deleted src/typed-key/
and replaced the tagged key format with the entitlement key format:
<prose>, blank line, [<base64url-payload><sha512-checksum>]. The tagged
format was never issued to anyone (its 3.5.0 carrier was never released),
so the old reader is removed rather than kept alongside.

Re-vendored from src/entitlement-key/ at tag 4.0.0: detect-format and
extract-key-data are new ports; sha512 and utils are byte-identical
upstream and carry over. The reader is schema-free by upstream design,
so default-schema is no longer vendored and TIER_TO_CAPABILITY_TOKEN
(the tagged format's tier adapter) is gone with the format that fed it.

Resolution reads HyperFormula's own product entry only: capabilities
verbatim, exactly one of usage_until/release_until (the reader enforces
the shape), notice/grace, flags (trial + the three silent spellings).
Legacy 25-character keys and the literals are untouched; the invariant
stands - only a VALID entitlement key may restrict the entitlement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the hf-329-entitlement-key-envelope branch from 3ad0a5b to 523316e Compare August 21, 2026 01:31
@codecov

codecov Bot commented Aug 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.38%. Comparing base (4ab3cc6) to head (523316e).

Additional details and impacted files

Impacted file tree graph

@@                     Coverage Diff                      @@
##           spike/hf307-addon-grants    #1740      +/-   ##
============================================================
- Coverage                     97.38%   97.38%   -0.01%     
============================================================
  Files                           204      204              
  Lines                         16236    16231       -5     
  Branches                       3493     3481      -12     
============================================================
- Hits                          15812    15807       -5     
  Misses                          424      424              
Files with missing lines Coverage Δ
src/Config.ts 94.69% <ø> (ø)
src/helpers/licenseKeyValidator.ts 95.83% <100.00%> (+0.08%) ⬆️
src/license/LicenseEntitlement.ts 100.00% <ø> (ø)
src/license/capabilities.ts 100.00% <ø> (ø)
src/license/licenseResolution.ts 98.63% <100.00%> (+1.19%) ⬆️
src/license/vendor/constants.ts 100.00% <100.00%> (ø)
src/license/vendor/detectFormat.ts 100.00% <100.00%> (ø)
src/license/vendor/extractKeyData.ts 97.50% <100.00%> (-2.50%) ⬇️
src/license/vendor/sha512.ts 100.00% <ø> (ø)
src/license/vendor/utils.ts 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.

2 participants