Skip to content

HF-307 PR 4/4: describe only the functions the license grants - #1731

Open
marcin-kordas-hoc wants to merge 1 commit into
hf-307-entitlement-gating-pr3from
hf-307-entitlement-gating-pr4
Open

HF-307 PR 4/4: describe only the functions the license grants#1731
marcin-kordas-hoc wants to merge 1 commit into
hf-307-entitlement-gating-pr3from
hf-307-entitlement-gating-pr4

Conversation

@marcin-kordas-hoc

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

Copy link
Copy Markdown
Collaborator

Last of four. Stacks on hf-307-entitlement-gating-pr3 (#1730) — merge that first. Tests: handsontable/hyperformula-tests#33.

Context

HF-307, decision D2.

getAvailableFunctions() and getFunctionDetails() read straight from the function registry with no license filter, while the interpreter gates the same functions per call. A restricted key therefore advertised functions that return #LIC! when called — precisely the failure #1724 (HF-349) removed the static variants to prevent:

the static one invites a function picker to advertise functions that then fail on evaluation, and nothing errors at integration time

That PR removed the static methods on the rationale that "an instance knows its license key, so it can answer for the engine the caller actually holds". The instance methods never learned to read it. This finishes the job.

The change

Both methods now filter through licenseListsFunction, which shares one licenseAllowsFunction rule with the interpreter rather than spelling the same condition out twice, and canonicalises aliases the same way. Extracting that rule is the point of the PR as much as the filter is: two copies would drift, and the drift is invisible until a customer's function picker offers something that fails.

The one decision worth reviewing

The filter reads gate B only — never the key's validity state.

A missing, invalid or expired key resolves to an unrestricted entitlement (the invariant PR 3 documents), so it reaches the filter with unrestricted set and keeps the whole catalogue. That falls out of the invariant rather than being a second decision — but it is a deliberate one, and the alternative is defensible, so it is worth an explicit look.

I chose it because narrowing on gate A would return two functions (the protected built-ins) to anyone who calls the API before configuring a key — an empty function picker with no clue why, for the exact integrator still wiring things up. A key problem is already reported on the console and by #LIC! in cells. The list narrows only for a valid key that genuinely excludes a function, which is when the answer is useful.

Pinned by tests in both directions; folding gate A into the filter fails 3 of them.

Also

  • types-of-errors.md described #LIC! as only ever meaning a key problem. It now also means "not in your package".
  • CHANGELOG — the feature's first user-visible entry. PRs 1–3 were internals by design and deliberately carry none.
  • The guide documents the mechanism, not the package contents. HF-306 is still in review with six open questions, so publishing the function lists now would put moving targets in the public docs.

How did you test your changes?

  • full suite with the private tests attached: 512 suites, 6323 tests passing, 3 skipped, 0 failures
  • npx tsc --noEmit: clean. npm run lint: 0 errors
  • npm run docs:generate-function-docs: succeeds, and the generated reference still documents BITAND / VLOOKUP / XIRR — the generator builds with gpl-v3, so the published docs do not narrow to a tier
  • mutation-tested three ways: removing the list filter fails 4 tests, dropping the custom-function exemption fails 2, folding gate A into the filter fails 3

Not run here: npm run test:browser (Karma needs Firefox, unavailable in this environment). No Jest-only matcher forms were used.

Types of changes

  • Breaking change
  • New feature or improvement
  • Bug fix
  • Additional language file
  • Change to the documentation

Not marked breaking: the narrowing only happens for a valid restricted key, and no such key can exist for a released version yet.

Related issues

  1. HF-307 — Implement feature packages and add-ons in HF (decision D2)
  2. Remove the static function metadata API (HF-349) #1724 / HF-349 — removed the static metadata API on the rationale this PR delivers
  3. Companion test PR: handsontable/hyperformula-tests#33

Checklist

  • I have reviewed the guidelines about Contributing to HyperFormula
  • I have signed the Contributor License Agreement
  • My change is compliant with the OpenDocument standard
  • My change is compatible with Microsoft Excel
  • My change is compatible with Google Sheets
  • I described my changes in the CHANGELOG.md file
  • My changes require a documentation update

The three compatibility boxes are left unticked as not applicable: this change touches no formula semantics.

Found while working on this, NOT fixed here

=OFFSET() with no arguments throws a TypeError out of the parser instead of returning an error value — handleOffsetHeuristic (src/parser/FormulaParser.ts:764) reads args[0].type with no arity check. Pre-existing, unrelated to licensing, and out of scope; flagging it for a separate issue.


Note

Medium Risk
Changes public metadata API behaviour for restricted proprietary keys and must stay in sync with interpreter licensing; scope is narrow and gate-A vs gate-B split is explicit and tested.

Overview
Function pickers no longer advertise built-ins that would return #LIC! on a valid restricted key. getAvailableFunctions() and getFunctionDetails() filter through licenseListsFunction, which uses the same gate-B rule and alias canonicalisation as the interpreter via new shared helper licenseAllowsFunction in CapabilityRegistry. The interpreter’s FUNCTION_CALL path is updated to call that helper instead of duplicating the condition.

Listing behaviour for key problems is intentionally unchanged. The filter consults entitlement only, not LicenseKeyValidityState: a missing, invalid, or expired key still yields the full catalogue (cells still get #LIC!), so integrators can build a picker before a key is wired. Custom registered functions stay listed regardless of the key.

User-facing docs: license-key.md adds “feature packages and add-ons”; #LIC! in types-of-errors.md also covers “not in your package”; CHANGELOG records the metadata API change under Unreleased.

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

@qunabu

qunabu commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 12, 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 8d05428 Commit Preview URL

Branch Preview URL
Aug 13 2026, 01:59 PM

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Performance comparison of head (8d05428) vs base (7c25bbc)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |  489.14 |  493.03 | +0.80%
                                      Sheet B |  158.68 |  157.51 | -0.74%
                                      Sheet T |  141.53 |  140.47 | -0.75%
                                Column ranges |   471.9 |  472.62 | +0.15%
                                Sorted lookup | 15337.3 | 14328.9 | -6.57%
Sheet A:  change value, add/remove row/column |   14.83 |   15.32 | +3.30%
 Sheet B: change value, add/remove row/column |  132.43 |  132.27 | -0.12%
                   Column ranges - add column |   143.7 |  146.61 | +2.03%
                Column ranges - without batch |  446.92 |  454.13 | +1.61%
                        Column ranges - batch |  113.48 |  111.55 | -1.70%

@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the hf-307-entitlement-gating-pr4 branch from 7dd0eb2 to bb874d8 Compare August 12, 2026 17:13
`getAvailableFunctions` and `getFunctionDetails` read straight from the function
registry, with no license filter, while the interpreter gates the same functions
per call. A restricted key therefore advertised functions that return `#LIC!`
when called - the exact failure removing the static metadata methods (HF-349)
was meant to prevent, left half-delivered because the instance methods never
learned to read the key their rationale said they could.

Both now filter through `licenseListsFunction`, which shares one
`licenseAllowsFunction` rule with the interpreter rather than spelling the same
condition out twice, and canonicalises aliases the same way. Extracting that
rule is the point: two copies would drift, and the drift is invisible until a
customer's picker offers a function that fails.

Gate B only, deliberately - never the validity state. A missing, invalid or
expired key resolves to an unrestricted entitlement (the invariant), so it
reaches the filter with `unrestricted` set and keeps the whole catalogue. That
falls out of the invariant rather than being a second decision, and it is the
useful answer: narrowing to the two protected built-ins would hand an integrator
who has not wired up their key yet an empty function picker and no clue why.
The list narrows only for a *valid* key that genuinely excludes a function.

Also documents `#LIC!` in types-of-errors.md, which listed only key problems and
not "function not in your package", and adds the CHANGELOG entry the feature has
not carried so far - PRs 1-3 were internals by design.

The guide deliberately documents the mechanism, not the package contents: HF-306
is still in review with six open questions, so publishing the lists now would
put moving targets in the public docs.

Tests: handsontable/hyperformula-tests#33

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GuUdq242TtFaepKRNdEkj9
@marcin-kordas-hoc
marcin-kordas-hoc force-pushed the hf-307-entitlement-gating-pr4 branch from bb874d8 to 8d05428 Compare August 13, 2026 13:55
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.38%. Comparing base (7c25bbc) to head (8d05428).

Additional details and impacted files

Impacted file tree graph

@@                      Coverage Diff                       @@
##           hf-307-entitlement-gating-pr3    #1731   +/-   ##
==============================================================
  Coverage                          97.38%   97.38%           
==============================================================
  Files                                204      204           
  Lines                              16194    16208   +14     
  Branches                            3557     3486   -71     
==============================================================
+ Hits                               15770    15784   +14     
- Misses                               416      424    +8     
+ Partials                               8        0    -8     
Files with missing lines Coverage Δ
src/HyperFormula.ts 99.76% <100.00%> (+<0.01%) ⬆️
src/interpreter/Interpreter.ts 95.49% <100.00%> (-0.02%) ⬇️
src/license/CapabilityRegistry.ts 100.00% <100.00%> (ø)

... and 5 files with indirect coverage changes

🚀 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