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
Open
HF-329: re-port the license key reader to the entitlement key format (7/8)#1740marcin-kordas-hoc wants to merge 1 commit into
marcin-kordas-hoc wants to merge 1 commit into
Conversation
Deploying with
|
| 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 |
Contributor
Performance comparison of head (523316e) vs base (4ab3cc6) |
marcin-kordas-hoc
marked this pull request as ready for review
August 20, 2026 09:10
marcin-kordas-hoc
force-pushed
the
spike/hf307-addon-grants
branch
from
August 20, 2026 12:54
9933ba3 to
4ab3cc6
Compare
marcin-kordas-hoc
force-pushed
the
hf-329-entitlement-key-envelope
branch
from
August 20, 2026 13:09
864ee26 to
3ad0a5b
Compare
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
force-pushed
the
hf-329-entitlement-key-envelope
branch
from
August 21, 2026 01:31
3ad0a5b to
523316e
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ 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
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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#41— merge the tests PR first (CI clones it by branch name).Why now
Upstream
handsontable/license-key4.0.0 (18.08, DEV-2512) deletedsrc/typed-key/— the exact directory our vendored reader was ported from — and replaced the tagged key format with the entitlement key format: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.src/entitlement-key/at tag 4.0.0:detectFormat.tsandextractKeyData.tsare new ports;sha512.ts/utils.tsare byte-identical upstream (same hashes) and carry over.PROVENANCE.mdrecords the new tag, commit, and sha256 table.defaultSchema.tsis no longer vendored; the only name the engine reads is its ownhyperformulaproduct entry.resolveLicenseroutes on the vendoreddetectLicenseKeyFormat(normative T12 order: literals → bracketed block → legacy shape). Literal keys and legacy 25-character keys reachcheckLicenseKeyValiditycompletely unchanged.unrestrictedEntitlement()(mutation-tested in the suite).Decisions to confirm in review
TIER_TO_CAPABILITY_TOKEN— the entitlement format has notierfield.YYYY-MM-DDstrings, never routed throughDateformatting (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 --noEmitand ESLint clean.Found while working on this, NOT fixed here
CHANGELOG.md's[Unreleased]has noRemovedentry 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), matchinghandsontable/license-key4.0.0.resolveLicensenow routes withdetectLicenseKeyFormat(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 andgpl-v3still go throughcheckLicenseKeyValidityunchanged.The dual-shape adapter (
tier/addonsvscapabilities) is gone. Terms come only from thehyperformulaproduct entry (usage_until/release_until, notice, grace, flags). Feature tokens stay opt-in, but the fallback now requires a recognizedfeat:*token so an unknownfeat:…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:
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 exceededout ofbuildFromArrayinstead ofresolving 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.
rejects one character changed in the payload segment [catches: the checksum doing nothing]passed with the checksum comparison deletedoutright, because the flipped character corrupted the payload's
productskey and the T7 shapecheck 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 samepayload with its own checksum is accepted — so the rejection can only come from the checksum.
PROVENANCE.md's divergence DATE type is not supported by arithmetic and comparison operators #3 claimed the typedentry's "fields carry their verified types", but the date field is verified only by matching
String(value)againstYYYY-MM-DD, so an array holding one date string passes and the declaredstringis wider than the value (faithful to upstream — the manifest was the thing overstating).And
isNonNegativeIntegerhad drifted to the globalisFinitewhere upstream usesNumber.isFinite: behaviour-identical behind thetypeofguard, but an undocumented edit insidea 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.