HF-306/HF-307: resolve both capability-token dialects from one group registry (8/8) - #1741
Open
marcin-kordas-hoc wants to merge 1 commit into
Open
HF-306/HF-307: resolve both capability-token dialects from one group registry (8/8)#1741marcin-kordas-hoc wants to merge 1 commit into
marcin-kordas-hoc wants to merge 1 commit into
Conversation
Contributor
|
Task linked: HOT-15413 Pick function sets for each HF product package |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | 4c8258e | Commit Preview URL Branch Preview URL |
Aug 21 2026, 01:36 AM |
Performance comparison of head (4c8258e) vs base (523316e) |
marcin-kordas-hoc
marked this pull request as ready for review
August 20, 2026 09:10
marcin-kordas-hoc
force-pushed
the
hf-329-entitlement-key-envelope
branch
from
August 20, 2026 13:09
864ee26 to
3ad0a5b
Compare
marcin-kordas-hoc
force-pushed
the
hf-329-token-dialects
branch
from
August 20, 2026 13:09
02e3434 to
e7b250b
Compare
The engine now resolves BOTH capability-token vocabularies in circulation, from one source of truth: the 21 function groups of the packaging doc (transcribed 1:1 from its §6, drift-checked by the published per-group counts) now live in capabilities.ts, and the functions_1..4 package slices are DERIVED from them as the doc's own cumulative group unions - so a function moved between groups moves in both dialects at once. New recognized tokens: fun:all (= the functions_4 grant), the 21 fun:<family>.<A|B|C> group tokens, and a fun:<CANONICAL_NAME> single- function token for every catalog entry. fun:info.a / fun:lookup.a / fun:offset / fun:version resolve to EMPTY grants on purpose - their members are the protected built-ins, which must never become table-covered. Token matching is now case-insensitive (the packaging doc states it outright for fun:*; the other tokens tolerate it for free). Accepting the superset is spec-clean (T7: an unrecognized token is a grant this version does not implement, so implementing more breaks nothing) and makes the engine robust to the still-open business choice between the dialects. The 18.08 'fun:* grants zero functions' pin is inverted BY DECISION (owner, 20.08), not by accident - see the test. 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
marcin-kordas-hoc
force-pushed
the
hf-329-token-dialects
branch
from
August 21, 2026 01:31
e7b250b to
4c8258e
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## hf-329-entitlement-key-envelope #1741 +/- ##
================================================================
Coverage 97.38% 97.39%
================================================================
Files 204 204
Lines 16231 16253 +22
Branches 3481 3483 +2
================================================================
+ Hits 15807 15829 +22
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.
8/8 of the HF-307 stack, stacked on #1740. Pairs with
hyperformula-tests#42— merge the tests PR first.Why
Two capability-token vocabularies exist for HyperFormula and the business choice between them is still open (D8, asked on HF-307 on 13.08, unanswered):
functions_1..4package slices plus thespreadsheet/import_exportadd-ons. This is what upstreamlicense-key@4.0.0's own schema mints today;fun:all, 21 group tokensfun:<family>.<A|B|C>, and a single-function tokenfun:<CANONICAL_NAME>for every catalog entry.Rather than bet on one, the engine now resolves both (owner's decision, 20.08). This is spec-clean: T7 defines an unrecognized token as "a grant this version does not implement", so implementing a superset breaks nothing — and whichever dialect keys end up minted in, they work without another engine release.
Design: one source of truth, two dialects reading from it
FUNCTION_GROUPS(members verbatim, operators and protected built-ins included, so a drift check can compare against the doc's published counts).functions_1..3package slices are now DERIVED from the groups as the doc's own cumulative unions (.A/.A+.B/.A+.B+.C), minus the operators (granted bycore) and the protected built-ins. The derived memberships are pinned by name by the pre-existingcapability-table.spec.tslists, which is the consistency proof: derivation reproduces the previously authored lists exactly.fun:allgrants byte-for-byte whatfunctions_4grants; the excel-simulator tail stays an authored list (the doc does not itemize it, and deriving it from the registry would gate user-registered functions — D1).fun:info.A/fun:lookup.A/fun:OFFSET/fun:VERSIONresolve to empty grants: their members are the protected built-ins, which are always available and must never become table-covered. Recognized bookkeeping, exactly as the doc frames them ("technical limitation").fun:*; the other tokens tolerate it for free — nothing collides under lowercasing). Normalization happens at lookup; entitlements keep the key's own spellings as diagnostics.fun:STDEV.Saccepts=STDEV(...)).capabilityOf's first-wins reverse index: the package slices stay ahead of the new tokens, so its answers are unchanged.The 18.08 test "a key in the published
fun:*vocabulary grants zero functions, silently" pinned a measured outcome pending D9. This PR resolves it the way the pin's own comment anticipated ("translate the vocabulary, warn, or reject — it has to change this test"): the vocabulary is now implemented. The inversion is by the owner's 20.08 decision, not a regression, and the replacement tests say so in place.Risks, stated
Testing
unit/license+unit/helpers/licenseKeyValidator: 16 suites / 266 tests green; dialect-equivalence pinned per package (functions_N≡fun:*union,fun:all≡functions_1..4);tsc --noEmitand ESLint clean.🤖 Generated with Claude Code
https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o
Note
High Risk
Changes license capability resolution, which controls which functions and API areas a key grants. A transcription or lookup bug could silently grant or deny paid features.
Overview
License keys can now grant functions using either token vocabulary: the existing package slices (
functions_1–functions_4plus add-ons) or the packaging-docfun:*groups (fun:all,fun:<family>.<A|B|C>, and per-functionfun:<NAME>). Both dialects resolve from a singleFUNCTION_GROUPSmap so membership cannot drift.Tokens are matched case-insensitively (trim + lowercase at lookup). Package slices are derived from the group unions instead of hand-maintained lists. Protected built-ins (
OFFSET,VERSION) stay ungated: their group/function tokens are recognized but grant nothing.Also replaces O(n²)
queue.shift()expansion with a cursor walk so a key with a huge token list cannot stall construction. Feature-opt-in and unrecognized-token checks now use the same normalized spelling, so paddedfeat:*tokens no longer flip the all-features fallback.Reviewed by Cursor Bugbot for commit 4c8258e. Bugbot is set up for automated code reviews on this repo. Configure here.
Spec-to-ship review (2026-08-20): 3 findings fixed here
An unrecognized
feat:*token revoked the whole gated API instead of being inert. Thefeature opt-in trigger was a purely syntactic prefix test, so a token this version does not
implement suppressed the "this key does not talk about features" fallback and left the key with
zero of the five areas. Measured:
functions_1functions_1,feat:crudfunctions_1,feat:teleport(unknown)functions_1,feat:cruds(typo at issuing time)That inverts the additive-safety rule for the two cases that matter — an older build meeting a
key from a newer generator, and a one-character typo when a key is minted — and it contradicts
D3, which makes an unrecognized token silently ignored. The trigger now requires a feature
token this version recognizes. Four tests pin both directions.
Scope note for the reviewer: the defect predates this PR (it came in with the opt-in rule in
PR 3/9) — the adversarial verifier correctly flagged that. It is fixed here because this PR
rewrites that exact predicate to add case-insensitivity, and because leaving a known revocation
bug in place while touching the line would be the wrong call.
Group membership was pinned only by COUNT.
FUNCTION_GROUPSis the single source of truthfor both dialects, but only the doc's per-group sizes were checked — so moving a function
between two groups of the same tier letter changed what a
fun:<family>.<letter>key grantswhile leaving every count, every derived package union and every other assertion identical.
Demonstrated live: with
EXPandMEDIANswapped betweenfun:math.Candfun:stat.C, thewhole license suite stayed green at 272/272. The 21 memberships are now checked by name
against §6 of the packaging doc (the expected literal was generated by parsing the doc itself),
and that check catches the swap.
Token expansion was quadratic, and whitespace split a token's meaning in two.
CapabilityRegistry.resolvedrained its queue withArray.prototype.shift— O(n) per call, soO(n²) overall: 1.4 s inside the constructor for a key with 100 000 tokens. Now a read cursor.
Separately,
normalizeCapabilityTokenlowercased but did not trim, and the two rules that read atoken disagreed for a padded one:
' feat:crud'failed the prefix test and was granted all fiveareas,
'feat:crud 'passed it and was granted none — neither was "crud". Normalization nowtrims, pinned by a test.