feat(cli): codespar mandate verify — offline V3 dual-signature verification#114
Merged
Conversation
Port the V3 canonical signing-string format from the private mandate package into the public CLI, dependency-free (node:crypto + stdlib only): - decodeToken: split the base64url JSON presentation envelope (signature / agent_sig / issuer_sig / kid) from the signed fields. - reconstructSigningString: the 14-field V3 canonical string (V2's 12 + principal_kyc_ref + agent_kid), byte-locked against the copied canonical.v3.fixture.json freeze. - verifyEd25519: SPKI-wrap a raw 32-byte key (RFC 8410) and verify with node:crypto; returns false, never throws, on malformed input. - did.ts: did:web resolution (standard URL first, api.codespar.dev encoded fallback) extracting Ed25519 keys from JsonWebKey2020 docs. Tests (offline only): byte-lock, agent+issuer verify pass, one-byte tamper fails, wrong key fails, did:web URL mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the offline/network V3 verifier command under the mandate group: - Offline mode (--agent-pubkey / --issuer-pubkey hex): verify the named signatures against supplied raw Ed25519 keys, zero network calls. - Network mode (default): resolve agent + issuer public keys via did:web (issuer DID derived from the agent DID host) and verify. - Prints per-signature status (verified / failed / skipped), the decoded governed fields (purposes, caps, agent_did, kid, expiry), and redacts principal_kyc_ref to a boolean. --json for machine output. - Non-zero exit code on any verification failure. Requires no API key (offline verification is fully local). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bring the V3 mandate codec to the @codespar/sdk public surface as a new
./mandate subpath export, keeping node:crypto out of the main (edge-safe)
graph:
- decodeMandateToken: split the base64url presentation envelope from the
signed fields.
- reconstructSigningString: the 14-field V3 canonical string (byte-locked
against the shared canonical.v3.fixture.json freeze).
- verifyEd25519: SPKI-wrap a raw 32-byte key (RFC 8410), verify via
node:crypto; false-never-throws on malformed input.
- verifyMandateToken: high-level offline verify from hex/byte pubkeys →
{ verified, mandate, agent/issuer SignatureCheck, agentDid, kid }.
node:crypto is a Node builtin, not an npm dependency — the zero-runtime-dep
guarantee holds. Fixture is byte-identical to the enterprise freeze and the
CLI copy (cross-impl parity anchor). 16 tests; subpath import driven E2E.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… crypto)
Port the V3 mandate codec to the Python SDK as a submodule (mirrors the TS
./mandate subpath — imported explicitly, kept out of the base import path):
- decode_mandate_token / reconstruct_signing_string: pure stdlib, zero new
deps. The 14-field canonical string is byte-locked against the same shared
canonical.v3.fixture.json the enterprise codec, TS SDK, and CLI pin.
- verify_ed25519 / verify_mandate_token: Ed25519 offline verify (hex or bytes
keys) → { verified, mandate, agent/issuer SignatureCheck, agent_did, kid }.
Ed25519 isn't in the stdlib, so it lazy-imports 'cryptography' and raises a
clear install hint if absent — the base package keeps its single runtime
dependency (httpx). Opt in via the new [verify] extra.
Tests byte-lock decode + reconstruction (always run); the verify cases are
guarded on the optional extra (installed in the dev extra, so they run in CI).
Verified locally against real cryptography 49.0.0 (agent+issuer verify, tamper
fails, wrong/short key fails, partial-key skip). Note: this env is Python 3.9
and the package requires 3.10+ (match statements), so the pytest suite itself
runs in CI; behavior here was exercised via standalone module load.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
Scope extended to the full SDK surface (same branch, +2 commits):
Publish plan (post-merge, OIDC trusted publishing only): |
fabianocruz
added a commit
that referenced
this pull request
Jul 5, 2026
…date verify (#115) Version bumps + changelogs for the #114 surface (already merged): `@codespar/sdk/mandate` subpath, `codespar mandate verify`, `codespar.mandate` Python submodule with the `[verify]` extra. Release plan after merge: 1. Tag `v0.11.0` on the merge commit → `publish.yml` (npm OIDC trusted publishing) ships sdk 0.11.0 + cli 0.6.0; every other package soft-passes at its already-published version. 2. `gh release create python-v0.11.0` → `publish-python.yml` (PyPI trusted publishing) ships `codespar` 0.11.0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fabianocruz
added a commit
that referenced
this pull request
Jul 5, 2026
…PI 0.11.0 (#116) The python-v0.11.0 release run failed at the ruff gate: `__all__` in the new `codespar.mandate` module wasn't isort-sorted (the authoring env was Python 3.9, so ruff couldn't run locally — CI is the gate, as flagged in #114). Sort-only change. After merge: re-run `publish-python.yml` via workflow_dispatch to ship `codespar` 0.11.0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Third-party verification of V3 dual-signed mandates from the CLI, with zero new runtime deps (node:crypto only):
packages/cli/src/mandate-codec.ts: token decode, canonical signing-string reconstruction (V3 14-field; also handles V2 12-field), Ed25519 verify with a raw 32-byte pubkey (RFC 8410 SPKI wrap). Byte-locked against the enterprise canonical fixture (copied into tests) so any drift fails loud.packages/cli/src/did.ts: did:web resolver — standard URL mapping first, api.codespar.dev encoded-DID fallback; extracts Ed25519 keys from JsonWebKey2020 documents.codespar mandate verify <token>: offline mode (--agent-pubkey/--issuer-pubkey, no network, no API key) and network mode (resolves pubkeys via did:web, still no API key). Exit code tracks signature verification: verified = at least one Ed25519 signature verified and none failed; in network mode an unresolvable key is a failure.--jsonsupported.expires_atshown with an [expired] flag, informational only.Tests: 45 passed (22 new) — byte-lock, tamper detection, wrong-key, bad-kid, decode failures; CLI driven end-to-end for real exit codes. Typecheck clean.
🤖 Generated with Claude Code