Skip to content

feat(licensing): verification runtime — minting deploy + prod public-key wiring (PR C)#510

Merged
blove merged 6 commits into
mainfrom
claude/licensing-verification-runtime
May 21, 2026
Merged

feat(licensing): verification runtime — minting deploy + prod public-key wiring (PR C)#510
blove merged 6 commits into
mainfrom
claude/licensing-verification-runtime

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 21, 2026

Summary

Closes the loop on the @ngaf/chat relicense. Mostly operational/CI work plus one library bug fix and one example wiring. Enforcement stays advisory only (console.warn, no UI nag) per the locked brainstorm decision. Origin allowlist deferred.

What this PR delivers:

  1. runLicenseCheck idempotency bug fixed. Repeat calls with the same (package, token) tuple now return the cached actual LicenseStatus, not the literal 'licensed'. Adds a regression test for the no-token path.
  2. CACHEPLANE_LICENSE_PUBLIC_KEY wired into publish.yml. The GH secret existed since 2026-04-30 but was never referenced; published @ngaf/chat therefore baked in the dev fixture key. One-line env injection makes the prebuild script (libs/licensing/scripts/generate-public-key.mjs) pick up the prod hex.
  3. apps/minting-service deployed via CI. New minting-deploy job in ci.yml, parallel to existing website/cockpit/demo deploys, gated on push to main. Hits https://minting.threadplane.ai/api/health after deploy for a sanity check.
  4. libs/chat/README.md gains a "Using a commercial license" section showing the provideChat({ license: '…' }) snippet customers paste after purchase, plus the build-time-define variant.
  5. examples/chat/angular/ wired to read environment.license. Default is undefined so the demo stays unlicensed in main; a smoke-test session can drop a real token into environment.ts to exercise the verify path.

Operational tasks (post-merge, not in code)

  • Add GH secret VERCEL_MINTING_PROJECT_ID = prj_3x6SBua2bmAk374uFrp0MdqZSe9u
  • In Vercel UI: assign domain minting.threadplane.ai to the threadplane-minting-service project
  • In Stripe Dashboard: point the live-mode webhook at https://minting.threadplane.ai/api/stripe-webhook
  • On the first @ngaf/chat publish after this lands: confirm dist/libs/licensing/fesm2022/*.mjs contains the prod public-key hex (not the dev fixture 793132582f3d…)
  • Execute the end-to-end smoke test runbook from docs/superpowers/specs/2026-05-20-licensing-verification-runtime-design.md

Test plan

  • npx nx run licensing:test → success (regression test passes; no regressions in existing tests)
  • npx nx run chat:test → success (no library code changed; baseline preserved)
  • npx nx run examples-chat-angular:build → success
  • YAML validation of both workflow files (yaml.safe_load) → ok
  • Scope check: only libs/licensing/, libs/chat/README.md, .github/workflows/{publish,ci}.yml, examples/chat/angular/, and docs/superpowers/ touched.

Out of scope (deliberately)

  • No nag UI. Enforcement is advisory only, same as today.
  • No claims schema changes. LicenseClaims stays { sub, tier, iat, exp, seats }. Origin allowlist is a future PR.
  • No new env var conventions. Token source is ChatConfig.license only. Customer pastes the token into their app config.
  • No nag-UI / banner component / license signal exposed to consumers. Just console.warn from runLicenseCheck.

Risks

  • Public-key / private-key mismatch. The GH secret was set 2026-04-30; the Vercel private key was set in the same window. Likely match but unverifiable from secrets alone. The smoke-test runbook in the spec includes an explicit derive-from-private check via vercel env pull + @noble/ed25519.
  • First minting deploy could fail if VERCEL_MINTING_PROJECT_ID secret hasn't been added yet. The CI job's if: gate means it only runs on push to main; a pre-merge dry-run isn't possible. Mitigation: the operational checklist above is the first thing to do post-merge.
  • No example wiring runs in CI today because environment.license stays undefined by default. The verify path is only exercised during manual smoke testing. Acceptable for an advisory-only enforcement model.

🤖 Generated with Claude Code

blove and others added 6 commits May 21, 2026 10:30
6 tasks: runLicenseCheck idempotency bug fix + regression test;
inject CACHEPLANE_LICENSE_PUBLIC_KEY into publish.yml; add
minting-deploy job to ci.yml mirroring existing Vercel deploy
patterns; document provideChat({license}) in libs/chat/README;
wire examples/chat/angular to read optional license token; final
verification + operational checklist for PR description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, repeat calls with the same (package, token) tuple short-
circuited to the literal 'licensed' regardless of what was actually
computed on the first call. That hid 'missing' / 'expired' / 'tampered'
statuses from any caller that invoked the check twice.

Switches the dedup Set<string> to a Map<string, LicenseStatus> and
returns the cached actual status. Adds a regression test for the
no-token path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The GH secret existed since 2026-04-30 but was never referenced by any
workflow. As a result, the published @ngaf/chat bundle baked in the
dev-fixture public key from libs/licensing/fixtures/dev-public-key.hex
— meaning real license tokens signed by the minting service would not
verify in consumer apps.

This single-line addition wires the secret into the env block of the
build step that runs `nx ... build ... licensing`. The existing
prebuild script (libs/licensing/scripts/generate-public-key.mjs)
already reads the env var and emits the prod hex into
license-public-key.generated.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Vercel project (prj_3x6SBua2bmAk374uFrp0MdqZSe9u) exists with all
runtime env vars (LICENSE_SIGNING_PRIVATE_KEY_HEX, RESEND_API_KEY,
Neon Postgres set) but was never deployed via CI. Adds a parallel
deploy job mirroring the existing patterns, gated on push to main.

Requires GH secret VERCEL_MINTING_PROJECT_ID (operational; not in
this PR). Hits /api/health after deploy to confirm the service
returned 200 within 30s of going live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shows the provideChat({ license: '…' }) snippet customers paste after
purchase, plus the build-time-define variant for public repos. Notes
that verification is offline and advisory (console.warn, no render
block) — matches PR C's enforcement policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds provideChat({ license: environment.license }) so a smoke-test
session can drop a real token into environment.ts and exercise the
verify path end-to-end. When license is undefined (the default in
main), the demo behaves identically to today: runLicenseCheck fires
once advisorily, status is 'noncommercial' under dev NODE_ENV, no
blocking. The token is intentionally absent from environment.ts so
the demo stays unlicensed in main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment May 21, 2026 5:40pm

Request Review

@blove blove enabled auto-merge (squash) May 21, 2026 17:38
@blove blove merged commit 5fd23e2 into main May 21, 2026
3 checks passed
blove added a commit that referenced this pull request May 21, 2026
…lane.ai (#512)

The Vercel project threadplane-minting-service has the domain
mint.threadplane.ai assigned (not minting.threadplane.ai which was
assumed when PR #510 landed). Updates the post-deploy health check
URL to match. Also the domain customers see for Stripe webhooks.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant