Skip to content

fix(cli): align generated samples with local verification#829

Merged
jithinraj merged 1 commit into
mainfrom
build/samples-local-verify-alignment
Jun 7, 2026
Merged

fix(cli): align generated samples with local verification#829
jithinraj merged 1 commit into
mainfrom
build/samples-local-verify-alignment

Conversation

@jithinraj

@jithinraj jithinraj commented Jun 7, 2026

Copy link
Copy Markdown
Member

Summary

Align the CLI-generated valid samples with the current PEAC signed interaction record path so that generated valid samples actually pass local verification. This updates public sample/demo semantics only.

Scope

@peac/cli sample generation + the specs/conformance/samples/ demo namespace + sample docs. It does not change normative fixtures (specs/conformance/fixtures/), the record format, schema validators, signing semantics, registries, or conformance requirements. The sample loader reads only specs/conformance/samples/, which is separate from the normative fixtures/ and specs/wire/ namespaces; those are untouched.

Why generated samples needed alignment

The generated valid/*.jws files were legacy receipt-shaped claims signed directly via the low-level sign path. Their signatures were valid but their claims did not pass strict local verification (verifyLocal), so "valid" samples were not actually locally verifiable.

What "valid" means after this PR

Valid samples are now stored as issue() input recipes (format: "issue-options") and are issued through the current issue path at generation time. Every generated valid record passes verifyLocal() against the generated single-key sandbox JWKS (bundles/sandbox-jwks.json). Record-oriented IDs: basic-record, full-record, mcp-tool-run, payment-event, event-time-record. The previous long-expiry sample is removed (current records are permanent evidence and do not model expiry); event-time-record replaces it to demonstrate event-time semantics. jti is a fresh UUIDv7 assigned by issue() (no fixed literal).

What --now means after this PR

  • --now <unix-seconds> sets each valid sample's event time (occurred_at), not its issuance time. iat remains the actual issuance time, so generated valid record JWS bytes are not identical across runs.
  • A future --now is rejected at preflight (before any sample files are written) when valid samples are selected, because a future occurred_at fails local verification. A non-integer --now is rejected; --now 0 is honored (epoch occurred_at).
  • The default key id is derived from generation time, not the event time; --kid is reflected in the generated JWS header and the sandbox JWKS.

Input validation (preflight, before filesystem side effects)

samples generate validates all inputs before creating directories or writing files: unsupported --format (only jws/json) is rejected, unknown --category (only valid/invalid/edge) is rejected, and an empty selection fails.

What remains intentionally invalid/edge

Invalid samples (expired, future-iat, missing-iss) remain raw legacy claims signed directly, so they keep their intentionally invalid shapes as rejection fixtures. The sample loader uses a discriminated SampleDefinition union (issue-options for valid, legacy-claims for invalid/edge) and skips malformed samples with a surfaced message rather than degrading silently.

Confirmations

  • verifyLocal() is not relaxed and no lenient mode was added.
  • No change to schema validators, signing semantics, registries, normative conformance fixtures, or the record format.
  • No dependency or lockfile change.
  • The offline peac verify --public-key work is out of scope for this PR and remains a follow-up.

Validation

  • pnpm --filter '@peac/cli' build and pnpm --filter '@peac/cli' test (259 tests) pass. New tests assert: every generated valid sample verifies locally and has a UUIDv7 jti; invalid samples are rejected; --now maps to occurred_at (not iat); --now 0 sets the epoch; a future --now is rejected before any valid files are written; non-integer --now is rejected; unsupported --format and unknown --category are rejected; a malformed valid sample is skipped; --kid appears in the header and JWKS; a payload-tampered record fails.
  • pnpm conformance:test (676 tests) passes, confirming no conformance impact.
  • pnpm format:check, scripts/ci/forbid-strings.sh, eslint, git diff --check, and the extension sentinel test all pass.

Changed public sample semantics

Generated valid samples are now current PEAC signed interaction records that pass local verification (previously legacy receipt-shaped, signature-only). Sample IDs changed to record-oriented names; long-expiry removed. CLI help and sample docs now describe records and the occurred_at vs iat distinction.

@jithinraj jithinraj force-pushed the build/samples-local-verify-alignment branch 4 times, most recently from 9ae9c85 to 5a76898 Compare June 7, 2026 07:21
Generated valid CLI samples were legacy receipt-shaped claims signed
directly, so they failed strict local verification. This aligns them with
the current PEAC signed interaction record path.

- Valid samples are now issue() input recipes (format: issue-options) and
  are issued via issue() at generation time, so every generated valid
  record passes verifyLocal() against the generated sandbox JWKS.
- Sample IDs are now record-oriented: basic-record, full-record,
  mcp-tool-run, payment-event, event-time-record. The misleading
  long-expiry sample is removed (current records do not model expiry).
- --now sets each valid sample's event time (occurred_at), not its
  issuance time (iat); iat remains the actual issuance time, so generated
  record bytes are not identical across runs. --kid is reflected in the
  generated JWS header and the sandbox JWKS.
- Invalid samples remain raw legacy claims (intentionally invalid
  rejection fixtures), signed directly.
- The sample loader uses a discriminated SampleDefinition union
  (issue-options for valid, legacy-claims for invalid/edge).
- Sample docs use PEAC record / signed interaction record language.
- Adds a test that every generated valid sample verifies locally, invalid
  samples are rejected, --now maps to occurred_at, --kid appears in the
  header, and a payload-tampered record fails.

Updates public sample/demo semantics only; does not change normative
fixtures, schema validators, signing semantics, registries, or conformance
requirements.
@jithinraj jithinraj force-pushed the build/samples-local-verify-alignment branch from 5a76898 to 72245a3 Compare June 7, 2026 07:37
@jithinraj jithinraj merged commit 8f7f62a into main Jun 7, 2026
27 checks passed
@jithinraj jithinraj deleted the build/samples-local-verify-alignment branch June 7, 2026 07:45
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