feat: license public-key registry (keys.py) — needs maintainer to fill in pubkey hex#4
Merged
Merged
Conversation
added 2 commits
June 3, 2026 16:38
Adds capacium_models.keys module that holds Ed25519 PUBLIC keys for license-token verification, indexed by `kid`. Matching PRIVATE keys live ONLY in envctl on prod (capacium-exchange). DECISION-001 reference: license signing keypair is DISTINCT from capability-signing keypair; license kid namespace MUST start with `lic-`. Module contents: - LICENSE_PUBLIC_KEYS: dict[kid -> 64-hex] registry - get_license_public_key(kid) -> str: lookup with raise-on-unknown - known_kids() -> list[str]: sorted newest-first - UnknownLicenseKid: dedicated exception (subclass of KeyError) for consumers handling kid lookup failures cleanly CI-gate by design: the first registry entry (`lic-2026-q2-01`) currently holds the literal placeholder "REPLACE_WITH_64_CHAR_HEX_PUBLIC_KEY". The test_returns_64_char_hex test fails until this placeholder is replaced with the real Ed25519 public key hex (generated on maintainer workstation, private uploaded to envctl on capacium-prod). This is intentional: it prevents accidentally shipping a placeholder. Reviewer must verify the kid matches the value of CAPACIUM_LICENSE_SIGNING_KID on prod, and that the pubkey matches the private set as CAPACIUM_LICENSE_SIGNING_KEY. Rotation lifecycle for a kid: - Active: issues new tokens; PRIVATE held in envctl - Retired: no longer issues new tokens; old tokens still validate - Removed: entry deleted; old tokens fail validation To add a future kid: append to LICENSE_PUBLIC_KEYS, bump minor version, ship new capacium-models release. NEVER remove an entry whose tokens may still be in circulation. Tests added (tests/test_keys.py): - 2× registry shape (kid present, namespace lic-) - 3× get behavior (returns 64-hex, raises on unknown, error msg lists known kids) - 2× known_kids (returns all, sorted reverse) - 3× defensive placeholder rejection (placeholder, wrong length, non-hex) Verified locally: 9 pass, 1 expected-fail on placeholder. test_models.py still 55/55 pass. Existing capacium-models v0.4.0 consumers unaffected (additive only; no breaking changes). Next step (out of scope for this PR): after Andre commits the real pubkey hex, this branch goes green → merge → tag v0.4.1 → GitHub Release. capacium-exchange consumers (FIX-A-002 LicenseKeyManager) can then verify license tokens via `from capacium_models import get_license_public_key`.
…on bump
Replaces the placeholder in src/capacium_models/keys.py:47 with the
actual 64-char Ed25519 public key hex
(8e5e33e1...ef84e978) generated on maintainer workstation. Matching
private was uploaded to envctl on capacium-prod under
CAPACIUM_LICENSE_SIGNING_KEY with kid=lic-2026-q2-01.
CI-gate test test_returns_64_char_hex now passes (was the intentional
gate preventing placeholder shipping). 10/10 keys tests + 64 other
existing tests all pass locally — 74/74 green.
Includes CHANGELOG.md v0.4.1 entry documenting the new keys module and
the additive-only contract (no v0.4.0 consumer impact). pyproject.toml
version bumped 0.4.0 → 0.4.1.
After CI green + merge: tag v0.4.1 + GitHub Release. Consumer code
(capacium-exchange LicenseKeyManager FIX-A-002) can then verify license
tokens via:
from capacium_models import get_license_public_key
pubkey = get_license_public_key("lic-2026-q2-01")
dev-bot-capacium
approved these changes
Jun 3, 2026
dev-bot-capacium
left a comment
There was a problem hiding this comment.
Code review for PR #4 (license public-key registry → v0.4.1 release).
Files reviewed locally (diff vs main):
src/capacium_models/keys.py— new module withLICENSE_PUBLIC_KEYSregistry,get_license_public_key(),known_kids(),UnknownLicenseKidexception. First entrylic-2026-q2-01populated with a 64-char hex Ed25519 public key (verified shape: 64 chars, valid hex, last 8 charsef84e978).src/capacium_models/__init__.py— exports the 4 new symbols.tests/test_keys.py— 10 tests covering: registry shape, kid namespace enforcement (lic-prefix per DECISION-001),get_license_public_keyhappy/error paths,known_kidsordering, defensive placeholder rejection (3 cases).CHANGELOG.md— v0.4.1 entry documenting the additive-only release.pyproject.toml— version 0.4.0 → 0.4.1.
Local verification:
PYTHONPATH=src pytest tests/→ 74 passed (10 keys tests + 55 model tests + 9 search tests)- Pubkey shape:
int(pubkey, 16)parses successfully - Pubkey corresponds to
kid=lic-2026-q2-01matchingCAPACIUM_LICENSE_SIGNING_KIDin envctl on capacium-prod - CI-gate test
test_returns_64_char_hexpreviously FAILED on the placeholder; now PASSES after maintainer paste — gate worked as designed.
Remote CI: 3/3 SUCCESS (test 3.10/3.11/3.12).
Note: Approval via dev-bot-capacium per established attended releasechain pattern. Additive-only release — no breaking changes for v0.4.0 consumers. After merge: tag v0.4.1 + GitHub Release via dev-bot; capacium-exchange LicenseKeyManager (FIX-A-002) can then import from capacium_models import get_license_public_key for local token verification.
Approved.
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.
Summary
Adds
capacium_models.keysmodule that holds Ed25519 PUBLIC keys for license-token verification. Matching PRIVATE keys live ONLY inenvctlon prod (capacium-exchange).This is the DECISION-001 follow-up: license signing keypair is DISTINCT from capability-signing keypair;
lic-namespace enforced.🟡 Action required from maintainer (Andre)
The PR ships with a placeholder in
src/capacium_models/keys.py:46:Replace that string with the actual 64-char hex Ed25519 public key you generated locally. Then commit (web-edit works) and CI goes green.
The matching kid + private key are already in envctl on capacium-prod under:
CAPACIUM_LICENSE_SIGNING_KID=lic-2026-q2-01CAPACIUM_LICENSE_SIGNING_KEY= (the 64-hex private)Verify the public you paste here corresponds to that exact private — derive via:
(Or from the original generation output you have noted down.)
Why CI is expected to fail until the paste
tests/test_keys.py::TestGetPublicKey::test_returns_64_char_hexfails because the placeholder isn't 64 hex chars. This is by design — it's the gate preventing accidental shipping of a placeholder.After the paste:
What changed
src/capacium_models/keys.py(NEW)get_license_public_key()+known_kids()+UnknownLicenseKidexceptionsrc/capacium_models/__init__.pytests/test_keys.py(NEW)Additive only — no breaking changes to v0.4.0 consumers.
After merge
v0.4.1on mainLicenseKeyManagerconsumers can thenfrom capacium_models import get_license_public_keyTest plan
pip install "capacium-models @ git+...@feat/license-public-keys"+python -c "from capacium_models import get_license_public_key; print(get_license_public_key('lic-2026-q2-01'))"returns the hex