Skip to content

Test auth-error, claims, and keyring contracts against the code - #26

Merged
szweibel merged 1 commit into
mainfrom
test-audit/contract-conformance
Sep 24, 2026
Merged

szweibel merged 1 commit into
mainfrom
test-audit/contract-conformance

Conversation

@szweibel

Copy link
Copy Markdown
Contributor

Gap

The package publishes five contract files under contract/. Only principal-v1.json and subject-derivation-v2.json had tests tying them to the code. Nothing compared auth-error-envelope-v1.json, identity-jwt-claims-v1.json or identity-keyring-v1.json with the implementation, so the code could drift from what downstream services (including non-TypeScript ones) read from those files.

What each test proves

Each test reads the contract file and derives its expectations from it. The same pattern as principal-contract.test.ts and subject.test.ts.

test/auth-error-contract.test.ts (auth-error-envelope-v1.json)

  • CAIL_AUTH_ERROR_CODES equals the contract's code.enum, and the parser accepts every enum value.
  • Every code serializes through createCailAuthError/serializeCailAuthError into the contract shape (root required, nested required, only declared nested fields, message and launch satisfy the contract patterns).
  • Each contract example parses and re-serializes unchanged.
  • Dropping any nested required field, or adding an undeclared field at either level (additionalProperties: false), is rejected.
  • Differential: for a probe set of messages and launch values (root-relative and canonical-origin paths, dot segments, query/fragment, backslash, percent-encoding, other origins/ports/userinfo), the parser and isCailAuthLaunch accept a value exactly when the contract's minLength/pattern/oneOf patterns do.

test/identity-jwt-claims-contract.test.ts (identity-jwt-claims-v1.json)

  • Each contract example, minted as a real RS256 token, verifies through verifyIdentityJwt; sub and every optional claim (log_sub → operationalSubject) are mapped.
  • A token missing any required claim is rejected.
  • A token missing any optional claim is accepted and the field is left unmapped.
  • For each declared claim, values that violate its type/minLength/pattern are rejected.
  • An undeclared claim is accepted (additionalProperties: true).

test/identity-keyring-contract.test.ts (identity-keyring-v1.json)

  • Header names come from each leg's $comment; the contract examples read back through readIdentityKeyring.
  • Only app_jwt is required: gateway-only headers yield null, app-only yields a one-leg keyring.
  • Differential: for each leg, readIdentityKeyring accepts a header value exactly when the contract's minLength/maxLength/pattern do (including the 8192/8193 boundary and comma-joined duplicates).
  • The gateway audience in the contract's $comment equals CAIL_GATEWAY_AUDIENCE; verifyKeyringGatewayJwt accepts a same-subject leg for that audience and rejects a different subject, an app-audience leg, and a non-gateway config.

test/auth-error.test.ts no longer pins CAIL_AUTH_ERROR_CODES with a literal list; the contract comparison replaces it.

Mutation results

Each mutation was applied to src/, the relevant contract test run, then reverted (git diff src/ clean afterwards).

Mutation Result
Drop identity_verification_misconfigured from CAIL_AUTH_ERROR_CODES fails: code set
Allow leading ./- in launch segments fails: launch differential (/.hidden)
Stop treating DEL (0x7f) as a control character fails: message differential
Remove ? check in isSafeLaunchPath survives: equivalent mutant, the segment pattern already rejects ?
Make exp optional (both checks) fails: missing required claim
Stop mapping log_sub to operationalSubject fails: example mapping
Skip the log_sub pattern check fails: claim schema violation
Accept any string sub fails: claim schema violation
KEYRING_JWT_MAX_LENGTH 8192 → 16384 fails: leg differential
CAIL_GATEWAY_AUDIENCE renamed fails: gateway audience
Drop gateway-leg subject binding fails: gateway audience/subject
Rename the gateway header fails: 3 keyring tests

Contract/code mismatches

None between the three contracts and the code. One packaging gap, left for a separate change: contract/identity-keyring-v1.json ships in the tarball but has no exports entry in package.json, unlike the other four contracts, so consumers cannot import it by subpath. package.json is also touched by #23, so this PR does not change it.

Validation

🤖 Generated with Claude Code

The package publishes five contract JSON files, but only principal-v1
and subject-derivation-v2 were tied to the implementation. Add
conformance tests that read auth-error-envelope-v1, identity-jwt-claims-v1
and identity-keyring-v1 and exercise the real parser, verifier and
keyring reader against them.

The auth-error code set is now compared with the contract enum, so the
literal code list in auth-error.test.ts is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@szweibel
szweibel merged commit 6b57677 into main Sep 24, 2026
1 check passed
@szweibel
szweibel deleted the test-audit/contract-conformance branch September 24, 2026 17:48
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