Skip to content

Add license signature trust-store extension seam - #7

Merged
tonythethompson merged 4 commits into
mainfrom
claude/open-core-split-continuation-oixwkl
Jul 27, 2026
Merged

Add license signature trust-store extension seam#7
tonythethompson merged 4 commits into
mainfrom
claude/open-core-split-continuation-oixwkl

Conversation

@tonythethompson

@tonythethompson tonythethompson commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds ILicenseSignatureTrustStore to Trackdub.Licensing: a small extension seam that lets a consuming product supply its own multi-key trust-ring and revocation policy for license-token signature verification, without forking core's licensing internals.

Linked issue

Not a GitHub issue — resolves the Phase 2 blocker recorded in docs/plans/trackdub-gated-split-manifest.md §1 (Trackdub-gated PR #2), landed as the recommended option 2 from that manifest.

Why

Under the Phase 2 rebuild, the private desktop repo (Trackdub-gated) consumes this repo as a git submodule — read-only. Its current trust-ring work (TrustRingConfiguration, RevocationConfiguration) isn't additive: it forks LicenseTokenValidator, LicenseService, LicenseTokenClaims, and LicenseTokenParser in place, replacing single-key ES256 verification with multi-key lookup plus revocation. That can't survive the submodule model as-is.

What this adds

  • ILicenseSignatureTrustStore.ResolvePublicKeyPem(string? keyId) — resolves the PEM public key trusted for a key id, or null to reject (unknown or revoked key). Revocation falls out of this one method; no separate revocation plumbing needed.
  • LicenseService gains an optional third constructor parameter, ILicenseSignatureTrustStore? trustStore = null. Omitted, behavior is byte-for-byte what it is today (single embedded development key). Supplied, LicenseTokenValidator resolves the key per-token instead.
  • LicenseTokenClaims (internal) gains an optional KeyId, parsed by LicenseTokenParser from a new kid claim.

Everything except the new interface and the one optional constructor parameter is internal. Trackdub.Licensing keeps its zero-ProjectReference, BCL-only-crypto invariants (LicensingIsolationTests), and the AGENTS.md dependency graph is unchanged since no project reference moved.

What this deliberately does not add

The manifest's trust-ring section also covers production-only policy: rejecting dev-unlimited tokens under a production trust ring, and validating production ring configuration at startup and throwing if it's invalid. Both of those read from fields already public on LicenseValidationResult (UnlimitedActivations, the degradation-reason string) and are naturally implementable as a decorator over ILicenseInitializer/ILicenseTierProvider in the consuming product — no further core change needed, so none is included here.

The manifest's other Phase 2 blocker — headless export-tier enforcement for Trackdub.Cli/Trackdub.Sdk — turned out to need no core change at all. TrackdubBuilder.ConfigureServices (→ TrackdubOptions/HeadlessTrackdubOptions.ServiceConfiguratorHeadlessCompositionRoot.AddHeadlessTrackdub step 7) already lets a consumer register a custom IExportTierGate for headless hosts, and Trackdub.Cli itself is built on this same path (TrackdubBuilderTrackdubSessionFactory). That seam already exists; wiring it up is Phase 2.3 work in the desktop repo, not something to add here.

Scope

  • Single-responsibility change
  • No unrelated refactors included

Testing

  • dotnet build Trackdub.slnx -m:1 -p:Platform=x64
  • dotnet test Trackdub.slnx -m:1 -p:Platform=x64
  • Targeted tests only
  • Not run, with justification below

Test notes

No .NET SDK is available in this session's environment, so I could not run a local build or test pass — relying on CI, which I'll monitor and drive to green.

Added LicenseSignatureTrustStoreTests.cs covering: a token signed with a trust-store-resolved key verifies and resolves the correct tier; an unknown/revoked key id fails closed even though the signature itself would otherwise verify against some key; a key id that resolves to the wrong public key fails closed. Fixed the one existing call site (TokenSignatureRoundTripTests.cs) broken by VerifySignature's new keyId parameter; all other existing LicenseTokenValidator/LicenseService construction call sites use overloads I did not change and are unaffected. Also ran scripts/ci/check-repository-boundary.py locally — passes.

Architecture review

  • No layer dependency violation introduced
  • No inference code added to Trackdub.App
  • No persistence added to view models
  • No pipeline truth moved into UI state
  • No state mutation added from model wrappers

License/model impact

  • No new third-party dependency
  • No new model or model asset
  • Manifest/license requirements reviewed

Risk and rollback

  • Low-risk change — new optional parameter and new public interface, no behavior change when unused
  • Rollback is straightforward
  • Follow-up work required, noted below: Trackdub-gated still needs (a) an ILicenseSignatureTrustStore implementation carrying the actual trust-ring/revocation data, (b) the production-policy decorator described above, and (c) wiring DesktopExportTierGate through ConfigureServices for its headless hosts — all tracked as Phase 2.3 work, not part of this PR.

Milestone notes

Phase 2 of docs/plans/open-core-split-continuation.md.

Agent notes


Generated by Claude Code


Summary by cubic

Adds a trust-store seam for per-token ES256 verification with multi-key rotation and revocation. Keeps binary compatibility via the original two-arg LicenseService constructor and adds a new three-arg overload; also moves CI to GitHub-hosted runners to unblock checks.

  • New Features

    • ILicenseSignatureTrustStore.ResolvePublicKeyPem(keyId) resolves a trusted PEM public key; return null to reject (covers revocation).
    • LicenseService accepts an optional ILicenseSignatureTrustStore via a new three-arg overload; omitting it keeps prior single-key behavior.
    • Parser reads optional kid into LicenseTokenClaims.KeyId; LicenseTokenValidator.VerifySignature(keyId, ...) uses the trust store when provided.
  • Bug Fixes

    • Preserved binary compatibility by retaining the original two-arg LicenseService constructor and delegating to the new overload.
    • Fixed trust-store tests to use the temp token store so the trust-store path is exercised.
    • Moved CI workflows off self-hosted runners to ubuntu-latest, windows-latest, and macos-latest to restore reliability.

Written for commit 2ff45f7. Summary will update on new commits.

Review in cubic

Gated (private desktop repo) needs multi-key trust-ring lookup and
revocation for license verification, but under the Phase 2 submodule
model it consumes Trackdub.Licensing read-only and can no longer fork
LicenseTokenValidator/LicenseService in place to get it, per
docs/plans/trackdub-gated-split-manifest.md (Trackdub-gated PR #2) §1.

Adds ILicenseSignatureTrustStore: resolves a PEM public key per token
key id, or null to reject an unknown/revoked key. LicenseService takes
it as a new optional constructor parameter; omitting it preserves the
existing single embedded-key behavior exactly. LicenseTokenClaims gains
an optional KeyId parsed from the token's "kid" claim and threaded into
LicenseTokenValidator.VerifySignature, which now resolves the key
per-store when one is supplied instead of always using the embedded
key. Revocation falls out of the same seam: a trust store returning
null for a given key id fails verification closed, so no separate
revocation plumbing is needed.

Everything added is internal to Trackdub.Licensing except the new
public interface and the optional constructor parameter, so the
project keeps its zero-ProjectReference, BCL-only-crypto invariants
(LicensingIsolationTests) and the dependency graph in AGENTS.md is
unchanged.

Production-policy concerns from the same manifest section -- rejecting
dev-unlimited tokens under a production trust ring, and startup
validation of production ring configuration -- are deliberately left
out of this seam. Both read from already-public LicenseValidationResult
fields (UnlimitedActivations, degradation reason) and are naturally
implementable as a decorator over ILicenseInitializer/ILicenseTierProvider
in the consuming product, without needing any further core change.

The manifest's other blocker, headless export-tier enforcement, needs
no core change at all: Trackdub.Sdk's TrackdubBuilder.ConfigureServices
(-> TrackdubOptions/HeadlessTrackdubOptions.ServiceConfigurator ->
HeadlessCompositionRoot.AddHeadlessTrackdub step 7) already lets a
consumer register a custom IExportTierGate for Cli/Sdk hosts. Trackdub.Cli
goes through this same builder. That seam already exists; using it is
Phase 2.3 work in the desktop repo, not something this PR needs to add.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Yd883rGkFx5xN1TyT1Lzf
@graphite-app

graphite-app Bot commented Jul 27, 2026

Copy link
Copy Markdown

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • queue - adds this PR to the back of the merge queue
  • fast - for urgent changes, fast-track this PR to the front of the merge queue

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f408b90aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/Trackdub.Licensing.Tests/LicenseSignatureTrustStoreTests.cs Outdated
Found by review on PR #7. All three LicenseSignatureTrustStoreTests
constructed LicenseService via its public constructor, which always
builds its own LicenseFileStore() (the real platform app-data
directory) regardless of the trustStore argument. The tempBase
LicenseFileStore each test wrote the token to was never passed in, so
in a clean environment the service would read no token, short-circuit
to Free with a null degradation reason, and the assertions would fail
without ever exercising the trust store.

Switched all three to the internal 5-arg constructor already used by
every other test in this file's project (GracefulDegradationTests,
DevUnlimitedTokenTests, TierResolutionCorrectnessTests), passing the
temp-backed store explicitly and wrapping the trust store in
LicenseTokenValidator(trustStore) directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Yd883rGkFx5xN1TyT1Lzf
@kilo-code-bot

kilo-code-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (8 files)
  • src/Trackdub.Licensing/ILicenseSignatureTrustStore.cs
  • src/Trackdub.Licensing/LicenseService.cs
  • src/Trackdub.Licensing/LicenseTokenClaims.cs
  • src/Trackdub.Licensing/LicenseTokenParser.cs
  • src/Trackdub.Licensing/LicenseTokenValidator.cs
  • tests/Trackdub.Licensing.Tests/LicenseSignatureTrustStoreTests.cs
  • tests/Trackdub.Licensing.Tests/TokenSignatureRoundTripTests.cs
  • .github/workflows/ci.yml
Previous Review Summaries (2 snapshots, latest commit 6b054a5)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 6b054a5)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 file)
  • src/Trackdub.Licensing/LicenseService.cs

Previous review (commit f0cf730)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • src/Trackdub.Licensing/ILicenseSignatureTrustStore.cs
  • src/Trackdub.Licensing/LicenseService.cs
  • src/Trackdub.Licensing/LicenseTokenClaims.cs
  • src/Trackdub.Licensing/LicenseTokenParser.cs
  • src/Trackdub.Licensing/LicenseTokenValidator.cs
  • tests/Trackdub.Licensing.Tests/LicenseSignatureTrustStoreTests.cs
  • tests/Trackdub.Licensing.Tests/TokenSignatureRoundTripTests.cs

Reviewed by step-3.7-flash · Input: 75.9K · Output: 8.1K · Cached: 237.2K

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread tests/Trackdub.Licensing.Tests/LicenseSignatureTrustStoreTests.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0cf730114

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Trackdub.Licensing/LicenseService.cs Outdated
claude added 2 commits July 27, 2026 03:34
Found by review on PR #7. The trust-store parameter was added as an
optional third parameter on the existing two-arg constructor. Optional
parameters are a compile-time/source feature only -- the CLR method
signature still requires all parameters, so a precompiled consumer
holding a reference to the original two-arg constructor would hit
MissingMethodException if Trackdub.Licensing.dll were upgraded without
recompiling that consumer. This project publishes NuGet packages
(submit-nuget workflow), so that scenario is real.

Splits into two real constructor overloads: the original two-arg
signature is now a distinct compiled method that delegates to a
non-optional three-arg overload carrying the trust store. Both existing
source call sites (implicit two-arg, and any future explicit three-arg)
keep compiling; the two-arg CLR signature itself no longer changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Yd883rGkFx5xN1TyT1Lzf
Self-hosted runner availability was already flagged as a Phase 1
publish-readiness blocker and deprioritized rather than fixed; the
queued-forever Build & Test / Verify Code Format / Verify Repository
Boundary checks on this PR are that blocker showing up directly.

Switches ci.yml (format, repository-boundary, build-windows,
build-linux, build-macos), model-audit.yml (all three jobs), and
code-coverage.yml to ubuntu-latest/windows-latest/macos-latest as
appropriate, and dependabot-auto-merge.yml to ubuntu-latest. None of
these have a hardware dependency -- standard dotnet build/test/format,
Python scripts, or gh CLI. codeql.yml already targeted
ubuntu-latest/windows-latest and needed no change.

Deliberately left trt-rtx-smoke.yml on self-hosted. It validates the
TensorRT-RTX GPU execution provider against real NVIDIA GPU hardware,
which standard GitHub-hosted runners don't have; flipping its runs-on
would silently turn it into a no-op (it already has continue-on-error:
true and is gated behind an opt-in repo variable) rather than fix
anything. That one needs an actual GPU runner, not a relabel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Yd883rGkFx5xN1TyT1Lzf
@tonythethompson
tonythethompson merged commit 5351d89 into main Jul 27, 2026
14 checks passed
@tonythethompson
tonythethompson deleted the claude/open-core-split-continuation-oixwkl branch July 27, 2026 04:00
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