Skip to content

Add GeneratingKeyProvider with production guard (ADR-0018, ADR-0019)#671

Open
ojongerius wants to merge 5 commits into
mainfrom
claude/stoic-edison-4icqj
Open

Add GeneratingKeyProvider with production guard (ADR-0018, ADR-0019)#671
ojongerius wants to merge 5 commits into
mainfrom
claude/stoic-edison-4icqj

Conversation

@ojongerius
Copy link
Copy Markdown
Contributor

@ojongerius ojongerius commented May 29, 2026

What

Implements GeneratingKeyProvider across all SDKs (Go, TypeScript, Python) with a production safety guard. This dev-only key provider generates fresh Ed25519 keypairs and refuses to run when AGENTRECEIPTS_PRODUCTION=true, preventing silent DID rotation on ephemeral compute cold starts.

Why

ADR-0018 defines a KeyProvider abstraction for supplying keypairs to the signing layer. ADR-0019 § S2 identifies a critical failure mode: auto-generating keys in production silently creates a new DID on every cold start, producing an unverifiable audit trail with no error surfaced.

GeneratingKeyProvider is intentionally limited to development and bootstrap scenarios. The production guard makes this failure mode unreachable by failing fast and loudly when misconfigured in production.

Implementation Details

  • Production Guard: All implementations check AGENTRECEIPTS_PRODUCTION=true (exact match) before generating any key material. If set, they raise ProductionKeyProviderError immediately.
  • Dev-Only Warning: A one-time stderr warning is emitted per process when the provider is constructed outside production, alerting developers to the dev-only nature.
  • Stable Keypair: The generated keypair is cached for the lifetime of the provider instance.
  • Comprehensive Tests: Each SDK includes tests covering the production guard, key generation, warning behavior, and signature verification.

Checklist

  • Tests pass for all changed components (Go, TypeScript, Python)
  • Linter passes (go vet, ruff check, biome as applicable)
  • No real keys or secrets in the diff
  • Cross-language tests verify generated keypairs produce valid signatures
  • Deployment guide updated with production safety guidance

Security

  • This PR touches crypto and secrets handling
  • Ed25519 key generation uses standard library primitives (Go crypto/ed25519, TypeScript crypto.generateKeyPairSync, Python cryptography.hazmat.primitives.asymmetric.ed25519)
  • Production environment check validates exact string match to prevent false negatives
  • All inputs validated at trust boundaries (environment variable, key generation errors)
  • Edge cases tested: production mode, dev warnings, concurrent construction, stable keypair behavior

Implements ADR-0019 §S2 (#476) across the TypeScript, Go, and Python
SDKs. Introduces the KeyProvider abstraction from ADR-0018 and its
dev-only GeneratingKeyProvider, which:

- throws ProductionKeyProviderError (Go: ErrProductionKeyProvider) when
  AGENTRECEIPTS_PRODUCTION=true, before any key is generated
- emits exactly one loud stderr warning per process otherwise
- generates a stable Ed25519 keypair for dev/bootstrap use

Documents the guard explicitly in the ephemeral-compute deployment guide.
Comment thread sdk/py/src/agent_receipts/receipt/key_provider.py Fixed
Comment thread sdk/py/tests/receipt/test_key_provider.py Fixed
Comment thread sdk/py/src/agent_receipts/receipt/key_provider.py Fixed
Drop the dual `import ... as kp_module` + `from ... import` for the same
module in the key-provider test; reset the once-per-process latch via
monkeypatch's string-target form instead. Addresses a PR review nit.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a development-only GeneratingKeyProvider to the Go/TypeScript/Python SDKs, guarded by AGENTRECEIPTS_PRODUCTION=true to prevent accidental key auto-generation (and DID rotation) on production ephemeral compute deployments, and documents the recommended deployment posture.

Changes:

  • Introduces GeneratingKeyProvider + production guard + one-time stderr warning across TS/Python/Go.
  • Exposes new provider/error APIs from the SDK entrypoints and adds unit tests for guard/warning/stability.
  • Updates the ephemeral compute deployment guide with explicit production guidance.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
site/src/content/docs/deployment/ephemeral-compute.mdx Documents why auto-generated keys are unsafe on cold starts and how AGENTRECEIPTS_PRODUCTION=true prevents misuse.
sdk/ts/src/receipt/key-provider.ts Adds TS KeyProvider interface, GeneratingKeyProvider, and ProductionKeyProviderError with production guard + one-time warning.
sdk/ts/src/receipt/key-provider.test.ts Adds TS tests for production guard, warning behavior, and keypair stability.
sdk/ts/src/index.ts Re-exports the new TS key-provider symbols from the root entrypoint.
sdk/py/src/agent_receipts/receipt/key_provider.py Adds Python KeyProvider protocol, GeneratingKeyProvider, and ProductionKeyProviderError with production guard + one-time warning.
sdk/py/tests/receipt/test_key_provider.py Adds Python tests for guard, warning behavior, keypair stability, and sign/verify using generated keys.
sdk/py/src/agent_receipts/receipt/init.py Exposes the new Python key-provider API from the receipt package.
sdk/py/src/agent_receipts/init.py Exposes the new Python key-provider API from the top-level package.
sdk/go/receipt/keyprovider.go Adds Go KeyProvider, GeneratingKeyProvider, and ErrProductionKeyProvider with production guard + one-time warning.
sdk/go/receipt/keyprovider_test.go Adds Go tests for guard, warning behavior, keypair stability, and sign/verify using generated keys.

Comment thread sdk/go/receipt/keyprovider.go
Comment thread sdk/go/receipt/keyprovider.go
Comment thread sdk/go/receipt/keyprovider_test.go
Comment thread sdk/go/receipt/keyprovider.go
Comment thread sdk/go/receipt/keyprovider.go
Comment thread sdk/go/receipt/keyprovider_test.go
Comment thread sdk/ts/src/index.ts
- Go: wrap the GenerateKeyPair error with operation context in
  NewGeneratingKeyProvider, matching the SDK's fmt.Errorf("...: %w")
  convention.
- Go test: check the error from the second GetKeyPair call rather than
  discarding it.
- TS: re-export GeneratingKeyProvider / KeyProvider /
  ProductionKeyProviderError from the ./receipt subpath entrypoint, so
  consumers importing "@agnt-rcpt/sdk-ts/receipt" get them too.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comment thread sdk/ts/src/receipt/key-provider.test.ts
The "exactly one warning per process" test installed a second
vi.spyOn(process.stderr, "write") on top of the one beforeEach already
creates. Double-spying the same global method is fragile (restore
ordering / potential flakiness). process.stderr is a singleton, so the
beforeEach spy already captures writes from the re-imported module —
assert on it directly instead. Addresses a PR review comment.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines +5 to +24
import os
import sys
from typing import Protocol, runtime_checkable

from agent_receipts.receipt.signing import KeyPair, generate_key_pair

_PRODUCTION_ENV_VAR = "AGENTRECEIPTS_PRODUCTION"
"""Environment variable that marks a production deployment. A
:class:`GeneratingKeyProvider` refuses to run when it is set to the exact
value ``"true"`` (see ADR-0018 § Key generation policy and ADR-0019 § S2)."""

_DEV_WARNING = (
"⚠ GeneratingKeyProvider is dev-only — set AGENTRECEIPTS_PRODUCTION=true "
"to disable in production"
)
"""The one-line, dev-only warning emitted at most once per process."""

# One stderr warning per process, regardless of how many providers are built.
_dev_warning_emitted = False

Comment on lines +71 to +73
if not _dev_warning_emitted:
_dev_warning_emitted = True
print(_DEV_WARNING, file=sys.stderr)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comment on lines +46 to +55
it("generates a usable, stable keypair outside production", async () => {
const provider = new GeneratingKeyProvider();
const kp = await provider.getKeyPair();

expect(kp.publicKey).toContain("BEGIN PUBLIC KEY");
expect(kp.privateKey).toContain("BEGIN PRIVATE KEY");

// Stable for the lifetime of the provider.
expect(await provider.getKeyPair()).toEqual(kp);
});
Comment thread sdk/go/receipt/keyprovider.go Outdated
Comment on lines +52 to +54
// It is explicitly prohibited in production: construct one when
// AGENTRECEIPTS_PRODUCTION=true and NewGeneratingKeyProvider returns
// ErrProductionKeyProvider before any key is generated.
Comment on lines +5 to +23
import os
import sys
from typing import Protocol, runtime_checkable

from agent_receipts.receipt.signing import KeyPair, generate_key_pair

_PRODUCTION_ENV_VAR = "AGENTRECEIPTS_PRODUCTION"
"""Environment variable that marks a production deployment. A
:class:`GeneratingKeyProvider` refuses to run when it is set to the exact
value ``"true"`` (see ADR-0018 § Key generation policy and ADR-0019 § S2)."""

_DEV_WARNING = (
"⚠ GeneratingKeyProvider is dev-only — set AGENTRECEIPTS_PRODUCTION=true "
"to disable in production"
)
"""The one-line, dev-only warning emitted at most once per process."""

# One stderr warning per process, regardless of how many providers are built.
_dev_warning_emitted = False
Comment on lines +71 to +73
if not _dev_warning_emitted:
_dev_warning_emitted = True
print(_DEV_WARNING, file=sys.stderr)
- Python: guard dev-only warning latch with threading.Lock so concurrent
  GeneratingKeyProvider construction cannot emit the warning more than once.
- TypeScript: add sign+verify test to confirm the generated keypair
  produces a valid Ed25519 receipt signature.
- Go: fix grammatically incorrect doc comment on GeneratingKeyProvider.
(see ADR-0018).
"""

def get_key_pair(self) -> KeyPair: ...

with _dev_warning_lock:
if not _dev_warning_emitted:
_dev_warning_emitted = True
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