Skip to content

feat(pl-client): LicensePayload type + decodeLicenseToken + license test - #1776

Open
DenKoren wants to merge 6 commits into
mainfrom
feat/license-payload-type-and-test
Open

feat(pl-client): LicensePayload type + decodeLicenseToken + license test#1776
DenKoren wants to merge 6 commits into
mainfrom
feat/license-payload-type-and-test

Conversation

@DenKoren

@DenKoren DenKoren commented Aug 6, 2026

Copy link
Copy Markdown
Member

What

Adds, in @milaboratories/pl-client, next to the Maintenance API license() call that returns the raw license token:

  • LicensePayload type — the decoded body of a Platforma license token (v, e = expiration, u, m required; w/wt/et, monitoring config s, etc. optional). Mirrors the desktop app's local copy and the backend License struct (core/pl/cmd/platforma/license.go).
  • decodeLicenseToken(token) — splits the I.<base64Payload>.<watermark>.<signature> envelope, base64-decodes the payload, and validates required fields (no signature verification — that stays in the desktop's LicenseManager).
  • assertLicensePayload(value) — runtime guard for the required fields.
  • license.test.ts — fetches the license from a live backend (client.license()), decodes it, and asserts the required fields are always present. Also logs the full payload + human-readable expiration, so it doubles as an investigation surface for any backend (point it via PL_ADDRESS / PL_TEST_USER / PL_TEST_PASSWORD).

Why

Puts the license-payload contract next to the code that controls the license communication, and gives us a patchable, backend-agnostic way to inspect a customer's actual license expiration. Supports informing clients about license expiration in advance.

Scope / follow-up

  • pl-client only. The desktop app keeps its own identical LicensePayload copy for now; migrating it to import from pl-client is a separate follow-up (desktop consumes pl-client as a published package, so it can only switch after this ships and the catalog pin is bumped).
  • No backend change; no zod dependency added (validation is plain runtime assertions).

Test note

license.test.ts requires a licensed backend to run (it calls client.license()), same as the existing client.test.ts suite — it runs in the backend-integration harness, not as a pure unit test.

Greptile Summary

Adds public Platforma-license payload types, runtime payload validation, token decoding, and a live-backend contract test to @milaboratories/pl-client.

  • LicensePayload — the decoded license body; this PR defines required validity, customer, and metric fields plus optional warning, fallback, monitoring, and issuer-specific metadata.
  • LicenseMonitoringMode — the telemetry-channel mode; this PR defines the supported with_id, no_id, and none values.
  • decodeLicenseToken — the raw-token decoder; this PR exports envelope parsing, base64/JSON decoding, and required-field validation.
  • assertLicensePayload — the runtime type guard; this PR checks that v, e, u, and m have their declared primitive types.
  • License integration test — the live-backend contract check; this PR fetches and inspects a license but currently exposes the complete payload in retained output.

Confidence Score: 3/5

The PR should not merge until the live-backend test stops exposing complete license payloads in retained test output.

The new decoder and types are generally coherent, but the integration test emits customer and fallback-license data without redaction, and the envelope validator also accepts malformed segment layouts.

Files Needing Attention: lib/node/pl-client/src/core/license.test.ts; lib/node/pl-client/src/core/license.ts

Security Review

The new integration test logs the full decoded license payload, potentially exposing customer identifiers, fallback license credentials, hardware identifiers, and opaque issuer metadata in CI output. How this was verified: The logged object includes every field, while the new type identifies u as a customer UID and l as a fallback license code.

Important Files Changed

Filename Overview
lib/node/pl-client/src/core/license.ts Introduces exported license types and decoding, but the envelope check accepts empty signatures and silently ignores extra segments.
lib/node/pl-client/src/core/license.test.ts Adds a useful live-backend contract test, but logs the complete potentially sensitive license payload.
lib/node/pl-client/src/index.ts Re-exports the new public license API from the package entry point.
.changeset/license-payload-type.md Records the new public API as a minor package release.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Licensed backend] -->|raw token| B[PlClient.license]
  B --> C[decodeLicenseToken]
  C --> D[Validate required field types]
  D --> E[LicensePayload]
  E --> F[Consumer]
  E -->|current integration test| G[Unredacted CI log]
Loading

Fix All in Claude Code

Prompt To Fix All With AI
### Issue 1
lib/node/pl-client/src/core/license.test.ts:37
**Full license payload enters CI logs**

When this integration test runs against a licensed backend, it serializes every payload field, including the customer UID, fallback license code, hardware identifier, and opaque issuer metadata, into retained test output. Remove or explicitly redact the sensitive fields before logging. **How this was verified:** The test passes the complete `payload` object to `JSON.stringify`, while `LicensePayload` defines `u` as a customer UID and `l` as a fallback license code.

```suggestion

```

### Issue 2
lib/node/pl-client/src/core/license.ts:94-102
**Envelope validation accepts malformed segments**

Array destructuring silently discards segments after the fourth, and the type checks accept an empty signature. Consequently, `decodeLicenseToken` can return a payload for tokens that do not satisfy its documented four-part `I.<payload>.<watermark>.<signature>` envelope, preventing callers from relying on successful decoding as structural validation.

```suggestion
  const segments = token.split(".");
  const [prefix, base64Payload, watermark, signature] = segments;
  if (
    segments.length !== 4 ||
    prefix !== LICENSE_TOKEN_PREFIX ||
    watermark !== LICENSE_TOKEN_WATERMARK ||
    typeof base64Payload !== "string" ||
    base64Payload.length === 0 ||
    typeof signature !== "string" ||
    signature.length === 0
  ) {
    throw new Error("invalid license token: unexpected envelope format");
  }
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat: add LicensePayload type and decode..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Context used:

  • Context used - Terms is a types in codebase. Provide the list of ... (source)
  • Knowledge Base — pl-client

Define the decoded license-token payload type next to the Maintenance API
license() call that returns the raw token, plus a decode helper that validates
the required fields. Add a test that pulls the license from a live backend and
asserts those fields are always present.
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 17a7e15

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@milaboratories/pl-client Minor
@milaboratories/pl-model-backend Patch
@milaboratories/pl-errors Patch
@milaboratories/pl-tree Patch
@milaboratories/pl-drivers Patch
@milaboratories/pl-middle-layer Patch
@platforma-sdk/pl-cli Patch
@platforma-sdk/test Patch
@platforma-sdk/tengo-builder Patch
@platforma-sdk/block-tools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread lib/node/pl-client/src/core/license.test.ts
Comment thread lib/node/pl-client/src/core/license.ts Outdated
It runs at decode time inside decodeLicenseToken, so callers get validation
for free — no need to expose it from the package.
'e' (and 'v') describe the short-lived license token, not the license's own
validity window: a license valid for a year issued with a 24h token TTL still
yields e = issued-at + 86400.
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
577 1 576 7
View the top 1 failed test(s) by shortest run time
src/local/pl.test.ts > should restart platforma if restart option was provided
Stack Traces | 10.9s run time
AssertionError: expected 4 to deeply equal 3

- Expected
+ Received

- 3
+ 4

 ❯ src/local/pl.test.ts:101:34

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

DenKoren and others added 2 commits August 6, 2026 14:35
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@DenKoren
DenKoren enabled auto-merge August 6, 2026 12:53
…ePayload

le is the license's own expiration date, independent of e (the short-lived
token TTL). Add it to the type, log it in the license test when present.
@DenKoren
DenKoren force-pushed the feat/license-payload-type-and-test branch from 3ab13a6 to 17a7e15 Compare August 7, 2026 12:38
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