Skip to content

feat: decompose core into focused microcrates and add new core infrastructure#21

Merged
EffortlessSteven merged 18 commits into
mainfrom
feat/core-keypair-and-cleanup
Feb 24, 2026
Merged

feat: decompose core into focused microcrates and add new core infrastructure#21
EffortlessSteven merged 18 commits into
mainfrom
feat/core-keypair-and-cleanup

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Member

Summary

Major architectural refactoring that decomposes the monolithic core fixture system into highly focused microcrates, improving modularity, compile times, and publish granularity. Also adds critical new capabilities for deterministic seed handling, typed caching, and comprehensive adapter tests.

New core infrastructure crates

  • uselesskey-core-seed — Seed parsing and redaction primitives. Parses hex or free-form string inputs into 32-byte seeds, with secure Debug output that redacts seed material to prevent log leakage.
  • uselesskey-core-cache — Identity-keyed typed cache. Process-local DashMap (or spin::Mutex<BTreeMap> under no_std) keyed by ArtifactId, stores fixtures as Arc<dyn Any + Send + Sync> with explicit panic on type mismatch.

New domain microcrates

  • uselesskey-core-jwk — Typed JWK/JWKS models (PublicJwk, PrivateJwk, AnyJwk) and JwksBuilder with stable kid-ordered serialization. Shape/serialization only — decoupled from key generation.
  • uselesskey-core-token — Deterministic token-shape generation helpers (API keys, bearer tokens, OAuth access tokens) with realistic formatting, no signing logic.
  • uselesskey-core-x509-spec — X.509 fixture spec models (X509Spec, ChainSpec, KeyUsage, NotBeforeOffset) with stable byte encodings for cache keys.
  • uselesskey-core-x509-derive — Deterministic X.509 derivation: base times from identity, positive serial numbers, length-prefixed hashing to avoid input-boundary collisions.
  • uselesskey-core-x509 — Aggregator for X.509 negative-policy types (X509Negative, ChainNegative) plus re-exports from spec/derive crates.

Refactoring

  • Cache extraction — Moved cache implementation from uselesskey-core into uselesskey-core-cache; removed direct dashmap/spin deps from core.
  • JWK extraction — Moved 728 lines of JWK types from uselesskey-jwk into uselesskey-core-jwk; uselesskey-jwk is now a thin facade re-exporting core types.
  • Token extraction — Extracted token formatting and base62 logic to uselesskey-core-token; uselesskey-token delegates via generate_token().
  • X.509 architecture split — Separated X.509 concerns across three crates (spec → derive → negative aggregator); uselesskey-x509 now depends on unified core types and drops util.rs.

Testing

  • AWS LC-RS comprehensive tests — RSA/ECDSA/Ed25519 key conversions, multi-size RSA (2048/3072/4096), cross-key validation failures, deterministic behavior validation.
  • Property-based tests — Seed parsing edge cases (seed_prop.rs), JWK serialization properties (jwk_prop.rs).
  • BDD coverage — New core-seed.feature with step implementations for hex parsing, error handling, and debug redaction.
  • Integration tests — Seed integration tests, JWK re-export tests.

Build/CI

  • Workspace members and [workspace.dependencies] expanded for all new crates.
  • PUBLISH_CRATES order updated in xtask to respect new dependency graph.
  • xtask/src/plan.rs dependency map updated so PR-scoped CI correctly triggers downstream rebuilds (e.g., seed change → core-id → core-cache → core → all).

Test plan

  • cargo check --workspace --all-features passes
  • cargo xtask test passes (all workspace tests)
  • cargo xtask clippy clean
  • cargo xtask bdd passes
  • cargo xtask feature-matrix passes
  • cargo xtask publish-check passes

- Introduced `uselesskey-core-keypair` crate for reusable key material helpers.
- Implemented `Pkcs8SpkiKeyMaterial` struct with methods for accessing and manipulating PKCS#8 and SPKI key formats.
- Added tests for key material accessors and corruption methods.

feat: add deterministic key-id helpers

- Created `uselesskey-core-kid` crate for generating deterministic key IDs.
- Implemented `kid_from_bytes` function for deriving key IDs from byte arrays.
- Added tests to ensure deterministic behavior of key ID generation.

feat: introduce negative fixture builders

- Added `uselesskey-core-negative` crate for generating corrupted PEM/DER data.
- Implemented various corruption strategies for PEM and DER formats.
- Added tests for corruption functions to ensure stability and uniqueness.

feat: implement tempfile-backed artifact sinks

- Created `uselesskey-core-sink` crate for managing temporary file artifacts.
- Implemented `TempArtifact` struct for creating and managing temporary files.
- Added tests to verify file creation, reading, and cleanup behavior.

feat: define canonical feature and matrix definitions

- Introduced `uselesskey-feature-grid` crate for feature flag and matrix definitions.
- Defined core and BDD feature matrices for automation consistency.
- Added tests to ensure uniqueness and correctness of feature definitions.

feat: add compatibility façade for feature grid

- Created `uselesskey-test-grid` crate as a compatibility layer for `uselesskey-feature-grid`.
- Re-exported feature matrices and constants for backwards compatibility.
Add default-features = false on microcrate dependency edges and forward
std feature to uselesskey-core-id and uselesskey-core-negative. Remove
invalid module-level #![no_std] attributes from id.rs and negative/mod.rs
(only valid at crate root).
Add content assertion to truncation test to catch argument-swap
mutations. All 25 mutants now caught or unviable.
- Implement `ChainSpec` for generating a three-level X.509 certificate chain (root CA, intermediate CA, leaf).
- Introduce methods for setting various properties of the chain specification, including Common Names, RSA key size, and validity periods.
- Add `X509Negative` and `ChainNegative` enums for representing invalid X.509 certificates and chains for negative testing.
- Create `X509Spec` for defining X.509 certificate specifications, including key usage flags and subject alternative names.
- Implement stable byte representations for deterministic derivation and cache keys.
- Add comprehensive tests for all new functionalities, ensuring correctness and stability of the specifications.
- Moved `KeyUsage`, `NotBeforeOffset`, and `X509Spec` definitions to a new crate `uselesskey-core-x509-spec`.
- Added `ChainSpec` struct for generating a three-level X.509 certificate chain.
- Updated `uselesskey-core-x509` to re-export types from the new spec crate.
- Removed the old `spec.rs` file from `uselesskey-core-x509`.
- Introduced stable byte encoding methods for deterministic derivation in both crates.
- Updated documentation and tests to reflect the new structure and functionality.
- Introduced `uselesskey-core-jwk` crate with core typed JWK/JWKS models.
- Implemented `JwksBuilder` for composing JWKS documents with stable key ordering.
- Added support for various JWK types including RSA, EC, OKP, and Oct.
- Updated fuzzing targets to include JWK builder tests.
- Enhanced xtask to publish the new crate and updated dependency management.
- Added property tests to ensure correct behavior of JWK builder.
- Created integration tests to verify compatibility between facade and core types.
Copilot AI review requested due to automatic review settings February 23, 2026 20:16
@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@EffortlessSteven has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 19 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 672f661 and e9a5c43.

📒 Files selected for processing (1)
  • crates/uselesskey-core-x509-spec/src/spec.rs

Walkthrough

Modularizes core functionality: adds multiple core crates (jwk, x509-spec/derive, x509, seed, cache, token), turns jwk/x509 into facades re-exporting core crates, centralizes artifact caching with ArtifactCache, and updates workspace members, features, tests, fuzz targets, and xtask propagation logic.

Changes

Cohort / File(s) Summary
Workspace & Manifests
Cargo.toml, crates/*/Cargo.toml, xtask/src/*
Adds new workspace members (core-jwk, core-x509-spec, core-x509, core-x509-derive, core-seed, core-cache, core-token), updates workspace dependencies, publish list, and xtask impact graph and mutants behavior.
JWK Core + Facade
crates/uselesskey-core-jwk/..., crates/uselesskey-jwk/src/lib.rs, crates/uselesskey-jwk/tests/*, fuzz/fuzz_targets/jwk_builder.rs
New typed JWK/JWKS crate with Public/Private/Any JWK types and JwksBuilder (stable kid ordering). uselesskey-jwk replaced with a compatibility facade that re-exports core types; adds integration and fuzz tests.
X.509 Core / Spec / Derive
crates/uselesskey-core-x509-spec/..., crates/uselesskey-core-x509-derive/..., crates/uselesskey-core-x509/..., crates/uselesskey-x509/src/*
Introduces spec models, deterministic derivation helpers, and negative-policy types in core crates; x509 crate now re-exports these core types and delegates base-time/serial logic to core. Local util/negative implementations removed or replaced by re-exports.
Seed Core & Re-exports
crates/uselesskey-core-seed/src/lib.rs, crates/uselesskey-core-id/src/lib.rs, crates/uselesskey-bdd-steps/*, fuzz/fuzz_targets/seed_parse.rs
Adds Seed type with parsing (hex/blake3), redacted Debug, tests and proptests. core-id re-exports Seed from core-seed. BDD steps and feature gated by uk-core-seed. Fuzz target added for seed parsing.
Artifact Cache & Factory
crates/uselesskey-core-cache/src/lib.rs, crates/uselesskey-core/src/factory.rs
New ArtifactCache (std: DashMap, no_std: spin::Mutex+BTreeMap) with typed get/insert and downcast helper. Factory switched to use ArtifactCache, removing previous conditional cache logic.
Token Core + Adapter
crates/uselesskey-core-token/src/lib.rs, crates/uselesskey-token/src/token.rs
New token-generation core crate (TokenKind, generators). Token adapter delegates generation to core-token and maps TokenSpec→TokenKind internally.
X.509 Negative Policy Module
crates/uselesskey-core-x509/src/negative.rs, crates/uselesskey-x509/src/chain_negative.rs
Core crate exposes X509Negative/ChainNegative and apply_to_spec logic; downstream chain helpers refactored to use core ChainNegative/X509Chain helpers.
Cache & Feature Grid
crates/uselesskey-core/Cargo.toml, crates/uselesskey-feature-grid/src/lib.rs
core crate features updated to depend on core-cache/std; feature grid adds uk-core-seed.
Tests & Fuzz
various tests under crates/*/tests, crates/*/src/*_tests.rs, fuzz/*
Adds comprehensive tests: jwk prop tests, seed proptests, x509 negative and derive tests, aws-lc-rs conversion tests, and fuzz targets for seed/jwk builder.
Documentation / READMEs
crates/*/README.md
Adds READMEs for new core crates and updates existing READMEs to describe facade/core relationships.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
Hop, I split the crates with care,
Seven cores now live out there,
Facades hide the inner art,
Cache and Seed each play their part,
I nibble tests and ship a patch — a carrot for every heart!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title concisely summarizes the main change: a major architectural refactoring that decomposes core into focused microcrates with new core infrastructure.
Description check ✅ Passed Description is comprehensive and directly related to the changeset, covering new crates, refactoring details, testing additions, and build/CI updates that all align with the file changes.
Docstring Coverage ✅ Passed Docstring coverage is 96.83% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/core-keypair-and-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @EffortlessSteven, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural refactoring by breaking down the core fixture system into smaller, more specialized microcrates. This change aims to improve modularity, reduce compile times, and enhance the granularity of published components. It also adds new capabilities for deterministic seed management, typed caching, and structured handling of JWK and X.509 artifacts, alongside expanded testing and updated build configurations to support the new structure.

Highlights

  • Core Infrastructure Decomposition: The monolithic core fixture system has been refactored into several focused microcrates, including uselesskey-core-seed for deterministic seed handling, and uselesskey-core-cache for identity-keyed typed caching.
  • New Domain Microcrates: New crates like uselesskey-core-jwk, uselesskey-core-token, uselesskey-core-x509-spec, uselesskey-core-x509-derive, and uselesskey-core-x509 have been introduced to provide specialized functionalities for JWK, token generation, and X.509 certificate handling.
  • Refactoring and Extraction: Existing functionalities for caching, JWK types, token formatting, and X.509 concerns have been extracted into their respective new microcrates, making the original uselesskey-core, uselesskey-jwk, uselesskey-token, and uselesskey-x509 crates thinner facades or aggregators.
  • Enhanced Testing: Comprehensive AWS LC-RS tests for key conversions and deterministic behavior, property-based tests for seed parsing and JWK serialization, and new BDD coverage for core-seed functionality have been added.
  • Build and CI Updates: The workspace members and [workspace.dependencies] in Cargo.toml have been expanded, PUBLISH_CRATES order updated, and the xtask/src/plan.rs dependency map adjusted to correctly trigger downstream rebuilds in CI.
Changelog
  • AGENTS.md
    • Updated crate list to reflect new core-jwk and core-x509 crates.
  • CLAUDE.md
    • Updated crate list to reflect new core-jwk and core-x509 crates.
  • Cargo.lock
    • Added new core microcrates as dependencies.
    • Updated dependencies for uselesskey-core to reflect cache extraction.
    • Added uselesskey-core-jwk as a new package.
    • Added uselesskey-core-seed as a new package.
    • Added uselesskey-core-token, uselesskey-core-x509, uselesskey-core-x509-derive, and uselesskey-core-x509-spec as new packages.
    • Updated uselesskey-jwk dependencies to use uselesskey-core-jwk.
    • Updated uselesskey-token dependencies to use uselesskey-core-token.
    • Updated uselesskey-x509 dependencies to use uselesskey-core-x509 and removed direct blake3, rand_core dependencies.
  • Cargo.toml
    • Added new core microcrates to the workspace members list.
    • Added new core microcrates to the workspace dependencies list.
  • crates/uselesskey-aws-lc-rs/tests/aws_lc_rs_comprehensive.rs
    • Added tests for RSA, ECDSA, and Ed25519 key conversion to match fixture material.
  • crates/uselesskey-bdd-steps/Cargo.toml
    • Added uselesskey-core-seed as an optional dependency and feature.
  • crates/uselesskey-bdd-steps/src/lib.rs
    • Added core_seed_steps module and core_seed_seed/core_seed_error fields to UselessWorld for BDD testing.
  • crates/uselesskey-bdd-steps/src/steps/core_seed_steps.rs
    • Added new BDD step definitions for core seed parsing, error handling, and debug redaction.
  • crates/uselesskey-bdd/Cargo.toml
    • Added uk-core-seed feature to enable BDD steps for core seed.
  • crates/uselesskey-bdd/features/core-seed.feature
    • Added a new BDD feature file to test core seed parsing primitives.
  • crates/uselesskey-core-cache/Cargo.toml
    • Added new crate uselesskey-core-cache with its package metadata and dependencies.
  • crates/uselesskey-core-cache/README.md
    • Added README for uselesskey-core-cache describing its purpose and features.
  • crates/uselesskey-core-cache/src/lib.rs
    • Added ArtifactCache implementation for identity-keyed typed caching, supporting std and no_std.
  • crates/uselesskey-core-id/Cargo.toml
    • Added uselesskey-core-seed as a dependency.
  • crates/uselesskey-core-id/README.md
    • Updated purpose to reflect seed parsing being moved to uselesskey-core-seed and re-exporting it.
  • crates/uselesskey-core-id/src/lib.rs
    • Removed local Seed implementation and re-exported it from uselesskey-core-seed.
    • Updated derive_seed to use the new Seed::new constructor.
  • crates/uselesskey-core-id/tests/seed_integration.rs
    • Added new integration tests to verify seed derivation and type compatibility.
  • crates/uselesskey-core-jwk/Cargo.toml
    • Added new crate uselesskey-core-jwk with its package metadata and dependencies.
  • crates/uselesskey-core-jwk/README.md
    • Added README for uselesskey-core-jwk describing its purpose and features.
  • crates/uselesskey-core-jwk/src/lib.rs
    • Added typed JWK/JWKS models and JwksBuilder for stable key ordering.
  • crates/uselesskey-core-jwk/tests/jwk_prop.rs
    • Added property-based tests for JwksBuilder to ensure correct sorting and duplicate handling.
  • crates/uselesskey-core-negative/src/lib.rs
    • Added new test helper functions for finding PEM/DER variants and specific corruption scenarios.
    • Added comprehensive tests for derived_truncate_len, derived_offset, flip_byte, inject_bad_base64_line, inject_blank_line, and deterministic PEM/DER corruption arms.
  • crates/uselesskey-core-seed/Cargo.toml
    • Added new crate uselesskey-core-seed with its package metadata and dependencies.
  • crates/uselesskey-core-seed/README.md
    • Added README for uselesskey-core-seed describing its purpose and features.
  • crates/uselesskey-core-seed/src/lib.rs
    • Added Seed structure with methods for parsing hex or string values and redacting debug output.
  • crates/uselesskey-core-seed/tests/seed_prop.rs
    • Added property-based tests for Seed::from_env_value to ensure robust parsing of various inputs.
  • crates/uselesskey-core-token/Cargo.toml
    • Added new crate uselesskey-core-token with its package metadata and dependencies.
  • crates/uselesskey-core-token/README.md
    • Added README for uselesskey-core-token describing its purpose and features.
  • crates/uselesskey-core-token/src/lib.rs
    • Added functions for generating API key, bearer, and OAuth access token shapes, along with authorization scheme helpers.
  • crates/uselesskey-core-x509-derive/Cargo.toml
    • Added new crate uselesskey-core-x509-derive with its package metadata and dependencies.
  • crates/uselesskey-core-x509-derive/README.md
    • Added README for uselesskey-core-x509-derive describing its purpose and features.
  • crates/uselesskey-core-x509-derive/src/lib.rs
    • Added deterministic X.509 derivation helpers for base times, serial numbers, and length-prefixed hashing.
  • crates/uselesskey-core-x509-spec/Cargo.toml
    • Added new crate uselesskey-core-x509-spec with its package metadata and dependencies.
  • crates/uselesskey-core-x509-spec/README.md
    • Added README for uselesskey-core-x509-spec describing its purpose and features.
  • crates/uselesskey-core-x509-spec/src/chain_spec.rs
    • Renamed from crates/uselesskey-x509/src/chain_spec.rs and updated comments.
  • crates/uselesskey-core-x509-spec/src/lib.rs
    • Added new crate uselesskey-core-x509-spec to aggregate X.509 spec models.
  • crates/uselesskey-core-x509-spec/src/spec.rs
    • Renamed from crates/uselesskey-x509/src/spec.rs.
  • crates/uselesskey-core-x509/Cargo.toml
    • Added new crate uselesskey-core-x509 with its package metadata and dependencies.
  • crates/uselesskey-core-x509/README.md
    • Added README for uselesskey-core-x509 describing its purpose and features.
  • crates/uselesskey-core-x509/src/lib.rs
    • Added new crate uselesskey-core-x509 to aggregate X.509 negative policies and re-export spec/derive types.
  • crates/uselesskey-core-x509/src/negative.rs
    • Added X.509 negative-fixture policy types and their application logic.
  • crates/uselesskey-core/Cargo.toml
    • Removed direct dashmap and spin dependencies.
    • Added uselesskey-core-cache as a dependency.
  • crates/uselesskey-core/README.md
    • Updated description of per-process cache to mention uselesskey-core-cache.
  • crates/uselesskey-core/src/factory.rs
    • Refactored Factory to use uselesskey-core-cache::ArtifactCache for caching logic.
    • Removed internal cache implementation details.
  • crates/uselesskey-core/src/factory_tests.rs
    • Removed downcast_or_panic tests, as this functionality is now handled by uselesskey-core-cache.
  • crates/uselesskey-feature-grid/src/lib.rs
    • Added UK_FEATURE_CORE_SEED to the list of feature constants and sets.
  • crates/uselesskey-jwk/Cargo.toml
    • Updated description to reflect its role as a compatibility facade.
    • Removed serde dependency and added uselesskey-core-jwk dependency.
  • crates/uselesskey-jwk/README.md
    • Updated README to clarify its role as a compatibility facade for uselesskey-core-jwk.
  • crates/uselesskey-jwk/src/lib.rs
    • Refactored to re-export all public items from uselesskey-core-jwk.
  • crates/uselesskey-jwk/tests/reexport_integration.rs
    • Added new integration tests to verify compatibility and functionality of the re-exported types.
  • crates/uselesskey-token/Cargo.toml
    • Removed direct base64, rand_core, serde_json dependencies.
    • Added uselesskey-core-token as a dependency.
  • crates/uselesskey-token/src/token.rs
    • Refactored token generation and authorization scheme logic to use uselesskey-core-token.
  • crates/uselesskey-x509/Cargo.toml
    • Removed direct blake3 and rand_core dependencies.
    • Added uselesskey-core-x509 as a dependency.
  • crates/uselesskey-x509/src/cert.rs
    • Refactored to use uselesskey_core_x509 for ChainSpec, NotBeforeOffset, X509Negative, X509Spec, and deterministic derivation helpers.
  • crates/uselesskey-x509/src/chain.rs
    • Refactored to use uselesskey_core_x509 for ChainSpec and deterministic derivation helpers.
  • crates/uselesskey-x509/src/chain_negative.rs
    • Refactored to re-export ChainNegative from uselesskey_core_x509.
  • crates/uselesskey-x509/src/lib.rs
    • Removed local chain_spec, spec, and util modules.
    • Re-exported ChainNegative, ChainSpec, KeyUsage, NotBeforeOffset, X509Negative, and X509Spec from uselesskey-core-x509.
  • crates/uselesskey-x509/src/negative.rs
    • Refactored to re-export X509Negative from uselesskey_core_x509.
  • crates/uselesskey-x509/src/util.rs
    • Removed file, as its functionality was moved to uselesskey-core-x509-derive.
  • fuzz/Cargo.toml
    • Added uselesskey-jwk and serde_json dependencies.
    • Removed pkcs8 feature from rsa dependency.
    • Added new fuzz targets for seed_parse and jwk_builder.
  • fuzz/fuzz_targets/jwk_builder.rs
    • Added new fuzz target for testing the JwksBuilder.
  • fuzz/fuzz_targets/seed_parse.rs
    • Added new fuzz target for testing Seed::from_env_value.
  • xtask/src/main.rs
    • Updated PUBLISH_CRATES order to reflect new crate dependencies.
    • Adjusted cargo mutants command to conditionally use --all-features for aws-lc-rs on Windows.
    • Updated cargo fuzz command to use +nightly toolchain.
  • xtask/src/plan.rs
    • Updated the dependency map to include the new core microcrates and their relationships.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a65b4f877c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread xtask/src/main.rs
Comment on lines +251 to +252
"uselesskey-core-x509-derive",
"uselesskey-core-x509",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include core-x509-spec in publish crate list

PUBLISH_CRATES drives both publish_check() and publish preflight iteration, but the newly introduced uselesskey-core-x509-spec crate is not listed here even though uselesskey-core-x509 depends on it (crates/uselesskey-core-x509/Cargo.toml). In release runs this means we never dry-run/publish the spec crate, which can block publishing downstream X.509 crates that now require that dependency version to exist in the registry.

Useful? React with 👍 / 👎.

Comment thread xtask/src/plan.rs
Comment on lines +160 to +161
"uselesskey-core-x509-derive" => &["uselesskey-core-x509"],
"uselesskey-core-x509" => &["uselesskey-x509"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate core-x509-spec changes to dependent crates

The PR impact graph adds entries for uselesskey-core-x509-derive and uselesskey-core-x509 but none for uselesskey-core-x509-spec, so a change under crates/uselesskey-core-x509-spec/** will not fan out to uselesskey-core-x509/uselesskey-x509 test targets in cargo xtask pr. That under-tests changes to shared spec/stable-byte logic and can let downstream X.509 regressions slip through CI.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request significantly improves the modularity of the uselesskey workspace by decomposing the monolithic core into focused microcrates. The introduction of uselesskey-core-seed, uselesskey-core-cache, and domain-specific microcrates for JWK, tokens, and X.509 derivation enhances compile times and publish granularity. The refactoring of the cache system into a standalone crate with std/no_std support is a great architectural move. I have provided feedback regarding the efficiency and statistical bias of the random token generation helper, which can be improved to better align with the project's goal of providing realistic fixtures.

Comment on lines +96 to +107
pub fn random_base62(rng: &mut impl RngCore, len: usize) -> String {
let mut out = String::with_capacity(len);
let mut byte = [0u8; 1];

while out.len() < len {
rng.fill_bytes(&mut byte);
let idx = byte[0] as usize % BASE62.len();
out.push(BASE62[idx] as char);
}

out
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation of random_base62 is inefficient and introduces a slight statistical bias. Calling rng.fill_bytes for a single byte in a loop is expensive due to the overhead of the RNG call. Additionally, using byte[0] % 62 is biased because 256 is not a multiple of 62, making the first 8 characters of the alphabet (A-H) approximately 25% more likely to appear than the others (5/256 vs 4/256). For more realistic and efficient token generation, consider filling a larger buffer and using a rejection sampling approach or at least reducing the number of RNG calls.

Suggested change
pub fn random_base62(rng: &mut impl RngCore, len: usize) -> String {
let mut out = String::with_capacity(len);
let mut byte = [0u8; 1];
while out.len() < len {
rng.fill_bytes(&mut byte);
let idx = byte[0] as usize % BASE62.len();
out.push(BASE62[idx] as char);
}
out
}
pub fn random_base62(rng: &mut impl RngCore, len: usize) -> String {
let mut out = String::with_capacity(len);
let mut buffer = [0u8; 64];
while out.len() < len {
let to_fill = (len - out.len()).min(buffer.len());
rng.fill_bytes(&mut buffer[..to_fill]);
for &b in &buffer[..to_fill] {
let idx = b as usize % BASE62.len();
out.push(BASE62[idx] as char);
}
}
out
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR performs a major workspace refactor by extracting seed parsing, caching, token/JWK helpers, and X.509 policy/derivation/spec types into focused “core-*” microcrates, while updating facades, adapters, and repo automation (xtask, fuzz, BDD) to match the new dependency graph.

Changes:

  • Added new microcrates for core seed parsing/redaction, typed caching, token/JWK helpers, and split X.509 policy/spec/derive.
  • Updated existing crates to delegate/re-export to the new core microcrates (e.g., uselesskey-jwk, uselesskey-token, uselesskey-x509, uselesskey-core).
  • Extended CI/automation: updated xtask dependency expansion + publish ordering, added fuzz targets and BDD steps for core-seed.

Reviewed changes

Copilot reviewed 62 out of 64 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
xtask/src/plan.rs Updates PR-scoped impacted-crate expansion map and adds tests for new crate relationships.
xtask/src/main.rs Updates publish ordering and CI tooling behavior (mutants/fuzz) to account for new crates.
fuzz/fuzz_targets/seed_parse.rs Adds a fuzz target for seed parsing + debug redaction invariants.
fuzz/fuzz_targets/jwk_builder.rs Adds a fuzz target exercising JWKS builder sorting/serialization.
fuzz/Cargo.toml Adds dependencies/binaries for new fuzz targets and adjusts existing fuzz dependencies.
crates/uselesskey-x509/src/util.rs Removes X.509 deterministic time/serial helpers (moved to core microcrates).
crates/uselesskey-x509/src/negative.rs Replaces local negative-policy enums with re-exports from uselesskey-core-x509.
crates/uselesskey-x509/src/lib.rs Re-exports X.509 spec/negative types from uselesskey-core-x509.
crates/uselesskey-x509/src/chain_negative.rs Switches chain-negative policy to uselesskey-core-x509 while keeping fixture helpers.
crates/uselesskey-x509/src/chain.rs Uses deterministic_base_time_from_parts / serial helpers from uselesskey-core-x509.
crates/uselesskey-x509/src/cert.rs Uses uselesskey-core-x509 spec/policy + derivation helpers instead of local modules.
crates/uselesskey-x509/Cargo.toml Drops direct blake3/rand_core deps and depends on uselesskey-core-x509.
crates/uselesskey-token/src/token.rs Delegates token generation/scheme logic to uselesskey-core-token.
crates/uselesskey-token/Cargo.toml Replaces direct deps with uselesskey-core-token and shifts test-only deps to dev-deps.
crates/uselesskey-jwk/tests/reexport_integration.rs Adds integration test ensuring facade/core JWK type compatibility and builder behavior.
crates/uselesskey-jwk/src/lib.rs Converts uselesskey-jwk into a thin re-export facade over uselesskey-core-jwk.
crates/uselesskey-jwk/README.md Updates documentation to reflect facade/re-export role.
crates/uselesskey-jwk/Cargo.toml Changes dependencies to uselesskey-core-jwk (serde stays in core crate).
crates/uselesskey-feature-grid/src/lib.rs Adds a new feature constant for core-seed.
crates/uselesskey-core/src/factory_tests.rs Removes cache downcast tests from core (moved to core-cache).
crates/uselesskey-core/src/factory.rs Replaces embedded cache impl with uselesskey-core-cache::ArtifactCache.
crates/uselesskey-core/README.md Documents that core cache is now powered by uselesskey-core-cache.
crates/uselesskey-core/Cargo.toml Drops dashmap/spin deps and adds uselesskey-core-cache with std feature wiring.
crates/uselesskey-core-x509/src/negative.rs Introduces shared X.509 negative-policy enums and spec mutations.
crates/uselesskey-core-x509/src/lib.rs Adds core X.509 policy crate that re-exports spec + derive helpers.
crates/uselesskey-core-x509/README.md Documents scope/purpose of core X.509 policy crate.
crates/uselesskey-core-x509/Cargo.toml Defines new core X.509 policy crate packaging.
crates/uselesskey-core-x509-spec/src/spec.rs Introduces X.509 spec models + stable encoding helpers.
crates/uselesskey-core-x509-spec/src/lib.rs Adds core X.509 spec crate module exports.
crates/uselesskey-core-x509-spec/src/chain_spec.rs Updates/specifies chain spec modeling and stable encoding details.
crates/uselesskey-core-x509-spec/README.md Documents scope/purpose of core X.509 spec crate.
crates/uselesskey-core-x509-spec/Cargo.toml Defines new core X.509 spec crate packaging.
crates/uselesskey-core-x509-derive/src/lib.rs Introduces deterministic base-time/serial derivation + length-prefixed hashing helpers.
crates/uselesskey-core-x509-derive/README.md Documents scope/purpose of core X.509 derive crate.
crates/uselesskey-core-x509-derive/Cargo.toml Defines new core X.509 derive crate packaging.
crates/uselesskey-core-token/src/lib.rs Adds deterministic token-shape generation helpers (API key/bearer/OAuth JWT-shape).
crates/uselesskey-core-token/README.md Documents scope/purpose of core token crate.
crates/uselesskey-core-token/Cargo.toml Defines new core token crate packaging.
crates/uselesskey-core-seed/tests/seed_prop.rs Adds property tests for seed parsing behavior and robustness.
crates/uselesskey-core-seed/src/lib.rs Adds seed parsing/redaction primitive with hex parsing + blake3 hashing.
crates/uselesskey-core-seed/README.md Documents scope/purpose of core seed crate.
crates/uselesskey-core-seed/Cargo.toml Defines new core seed crate packaging + std feature wiring.
crates/uselesskey-core-negative/src/lib.rs Expands deterministic corruption tests and adds variant-discovery helpers.
crates/uselesskey-core-jwk/tests/jwk_prop.rs Adds property tests for JWKS builder ordering stability.
crates/uselesskey-core-jwk/src/lib.rs Adds core typed JWK/JWKS models and stable-ordering builder implementation.
crates/uselesskey-core-jwk/README.md Documents scope/purpose of core JWK crate.
crates/uselesskey-core-jwk/Cargo.toml Defines new core JWK crate packaging.
crates/uselesskey-core-id/tests/seed_integration.rs Adds integration test to confirm core-id seed derivation works with core-seed type.
crates/uselesskey-core-id/src/lib.rs Re-exports Seed from uselesskey-core-seed and updates derivation accordingly.
crates/uselesskey-core-id/README.md Updates docs to reflect seed re-export rather than seed parsing living here.
crates/uselesskey-core-id/Cargo.toml Adds dependency/feature wiring for uselesskey-core-seed.
crates/uselesskey-core-cache/src/lib.rs Introduces typed cache keyed by ArtifactId with std/no_std backends.
crates/uselesskey-core-cache/README.md Documents scope/purpose of core cache crate.
crates/uselesskey-core-cache/Cargo.toml Defines new core cache crate packaging + std feature wiring.
crates/uselesskey-bdd/features/core-seed.feature Adds BDD scenarios covering seed parsing error handling and debug redaction.
crates/uselesskey-bdd/Cargo.toml Extends BDD feature sets to include core-seed steps.
crates/uselesskey-bdd-steps/src/steps/core_seed_steps.rs Implements cucumber steps for core-seed feature.
crates/uselesskey-bdd-steps/src/lib.rs Wires core-seed steps and world state fields behind the new feature flag.
crates/uselesskey-bdd-steps/Cargo.toml Adds optional dependency/feature flag for uselesskey-core-seed.
crates/uselesskey-aws-lc-rs/tests/aws_lc_rs_comprehensive.rs Adds conversion tests ensuring adapter conversions match fixture key material.
Cargo.toml Adds new microcrates to workspace members and workspace dependency table.
Cargo.lock Updates lockfile for new crates and revised dependency graph.
CLAUDE.md Updates repository crate map documentation for new microcrates/facades.
AGENTS.md Updates repository crate map documentation for new microcrates/facades.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread xtask/src/plan.rs
Comment on lines 150 to 162
match crate_name {
"uselesskey-core-id" => &["uselesskey-core"],
"uselesskey-core-seed" => &["uselesskey-core-id"],
"uselesskey-core-id" => &["uselesskey-core-cache", "uselesskey-core"],
"uselesskey-core-kid" => &["uselesskey-core-keypair", "uselesskey-hmac"],
"uselesskey-core-keypair" => &["uselesskey-rsa", "uselesskey-ecdsa", "uselesskey-ed25519"],
"uselesskey-core-cache" => &["uselesskey-core"],
"uselesskey-core-negative" => &["uselesskey-core"],
"uselesskey-core-sink" => &["uselesskey-core"],
"uselesskey-core-token" => &["uselesskey-token"],
"uselesskey-core-jwk" => &["uselesskey-jwk"],
"uselesskey-core-x509-derive" => &["uselesskey-core-x509"],
"uselesskey-core-x509" => &["uselesskey-x509"],
"uselesskey-core" => &[

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

dependents() is missing an entry for uselesskey-core-x509-spec. Since uselesskey-core-x509 depends on it, changes to the spec crate won't expand to rebuild uselesskey-core-x509, uselesskey-x509, and downstream adapter crates in PR-scoped CI. Add a mapping like "uselesskey-core-x509-spec" => &["uselesskey-core-x509"] (and consider adding a corresponding unit test).

Copilot uses AI. Check for mistakes.
Comment thread xtask/src/main.rs
Comment on lines 242 to 253
const PUBLISH_CRATES: &[&str] = &[
"uselesskey-core-seed",
"uselesskey-core-id",
"uselesskey-core-cache",
"uselesskey-core-kid",
"uselesskey-core-negative",
"uselesskey-core-sink",
"uselesskey-core-token",
"uselesskey-core-jwk",
"uselesskey-core-x509-derive",
"uselesskey-core-x509",
"uselesskey-core",

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

PUBLISH_CRATES is missing uselesskey-core-x509-spec, even though it's a workspace member and is depended on (via uselesskey-core-x509). This will cause cargo xtask publish-check / publish-preflight to skip validating/packaging that crate and can break publish ordering. Add uselesskey-core-x509-spec to the list (before uselesskey-core-x509).

Copilot uses AI. Check for mistakes.
Comment thread fuzz/Cargo.toml
Comment on lines 14 to 17
uselesskey = { path = "../crates/uselesskey", features = ["ecdsa", "token"] }
rsa = { version = "0.9", features = ["pem", "pkcs8"] }
uselesskey-jwk = { path = "../crates/uselesskey-jwk" }
rsa = { version = "0.9", features = ["pem"] }
p256 = { version = "0.13", features = ["ecdsa", "pkcs8", "pem"] }

Copilot AI Feb 23, 2026

Copy link

Choose a reason for hiding this comment

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

The rsa dependency no longer enables the pkcs8 feature, but fuzz_targets/rsa_pkcs8_pem_parse.rs imports rsa::pkcs8::DecodePrivateKey and calls RsaPrivateKey::from_pkcs8_pem, which is gated on that feature. Re-enable rsa's pkcs8 feature (in addition to pem) so the fuzz target builds.

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
xtask/src/plan.rs (1)

149-222: ⚠️ Potential issue | 🔴 Critical

Add missing uselesskey-core-x509-spec entry to the dependency propagation graph.

uselesskey-core-x509-spec is a workspace crate depended upon by uselesskey-core-x509, but it has no entry in the dependents() function. Changes to it will not trigger downstream rebuilds.

Fix
         "uselesskey-core-jwk" => &["uselesskey-jwk"],
+        "uselesskey-core-x509-spec" => &["uselesskey-core-x509"],
         "uselesskey-core-x509-derive" => &["uselesskey-core-x509"],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@xtask/src/plan.rs` around lines 149 - 222, The dependents() graph is missing
an entry for the workspace crate "uselesskey-core-x509-spec", so changes there
won't propagate to dependents; update the match in the dependents() function to
include a new arm for "uselesskey-core-x509-spec" that lists its downstream
crates (at minimum "uselesskey-core-x509" and any other crates that directly
depend on the spec), ensuring the propagation includes this crate alongside the
existing "uselesskey-core-x509" entry.
crates/uselesskey-core-negative/src/lib.rs (4)

40-43: 🧹 Nitpick | 🔵 Trivial

Shadowing bytes: &[u8; 32] with bytes: usize in the same scope

The _ arm binds a new bytes: usize that shadows the outer bytes: &[u8; 32] digest slice. Renaming the inner binding (e.g., truncate_chars) removes the ambiguity and aligns with the suggested rename above.

♻️ Proposed refactor
         _ => {
-            let bytes = derived_truncate_len(pem, bytes);
-            corrupt_pem(pem, CorruptPem::Truncate { bytes })
+            let chars = derived_truncate_len(pem, bytes);
+            corrupt_pem(pem, CorruptPem::Truncate { chars })
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-negative/src/lib.rs` around lines 40 - 43, The match
arm currently shadows the outer `bytes: &[u8; 32]` by binding a new `bytes:
usize`; update the binding name in the `_` arm (for example `truncate_chars` or
`truncate_len`) and use that new name in the call to `derived_truncate_len(pem,
...)` and when constructing `CorruptPem::Truncate { bytes: ... }` so the slice
`bytes` and the usize length are not ambiguous; specifically edit the `_` arm
around the calls to `derived_truncate_len` and `corrupt_pem` to use the new
identifier instead of `bytes`.

17-17: ⚠️ Potential issue | 🟡 Minor

CorruptPem::Truncate { bytes } field semantics mismatch: char count vs. byte count

corrupt_pem consumes this value as a Unicode character count (pem.chars().take(bytes)), not a byte offset. PEM is always ASCII in practice so chars == bytes, but the public field name bytes documents the wrong unit and will mislead callers who pass an actual byte count for non-ASCII input.

🔧 Proposed rename
-    Truncate { bytes: usize },
+    Truncate { chars: usize },
-        CorruptPem::Truncate { bytes } => pem.chars().take(bytes).collect(),
+        CorruptPem::Truncate { chars } => pem.chars().take(chars).collect(),

And in corrupt_pem_deterministic:

-            let bytes = derived_truncate_len(pem, bytes);
-            corrupt_pem(pem, CorruptPem::Truncate { bytes })
+            let chars = derived_truncate_len(pem, bytes);
+            corrupt_pem(pem, CorruptPem::Truncate { chars })

Also applies to: 26-26

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-negative/src/lib.rs` at line 17, The Truncate
variant's field is misnamed: CorruptPem::Truncate { bytes: usize } is
interpreted as a character count by corrupt_pem and corrupt_pem_deterministic
(they use pem.chars().take(bytes)), so rename the field to "chars"
(CorruptPem::Truncate { chars: usize }) and update all uses in corrupt_pem and
corrupt_pem_deterministic to read the new field name; alternatively, if you
intended a byte count, change the logic to slice the PEM by bytes using
pem.as_bytes().take(bytes) and convert back safely—pick one approach and make
the variant, pattern matches, and consumers consistent.

166-172: ⚠️ Potential issue | 🟡 Minor

derived_truncate_len_bytes can produce 0 (including for len == 2) and silently yields empty DER

Two distinct problems:

  1. Zero output: The result range is [0, len-2]digest[2] % span can be 0, so corrupt_der_deterministic arms 0 and 2 can return an empty Vec<u8>. The companion derived_truncate_len (line 54) avoids this by adding 1: 1 + (u16 % span) → range [1, len-1]. DER corruption should likely guarantee at least one byte remains.

  2. Entropy collapse at len == 2: When len == 2, span == 1digest[2] % 1 == 0 unconditionally, making the truncation length always 0 regardless of the variant digest. Any 2-byte input is always completely erased.

🔧 Proposed fix — mirror the PEM formula
 fn derived_truncate_len_bytes(len: usize, digest: &[u8; 32]) -> usize {
     if len <= 1 {
         return 0;
     }
     let span = len - 1;
-    digest[2] as usize % span
+    1 + (digest[2] as usize % span)   // guarantees [1, len-1], never empty
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-negative/src/lib.rs` around lines 166 - 172, The
truncation can return 0 and collapse for len==2; update
derived_truncate_len_bytes to mirror the PEM/derived_truncate_len behavior by
computing a value in [1, len-1] instead of [0, len-2]: keep the early return for
len <= 1, compute span = len - 1, and return 1 + (digest[2] as usize % span) so
at least one byte remains and len==2 yields 1; adjust any comments if present in
derived_truncate_len_bytes to reflect the new range.

174-183: 🧹 Nitpick | 🔵 Trivial

Missing proptest coverage for helper-function invariants (per coding guidelines)

The deterministic helpers (flip_byte, truncate_der, derived_offset, derived_truncate_len_bytes) all have clear, universal algebraic invariants that are only tested with hard-coded inputs. Property-based tests would cover arbitrary byte slices and digests.

Example invariants suitable for proptest:

  • flip_byte: for any der, offset < der.len() → exactly one byte differs and its value is input[offset] ^ 0x01.
  • truncate_der: for any len < der.len()output == &der[..len].
  • derived_offset: for any len > 0, selectorresult < len.
  • derived_truncate_len_bytes: for any len > 1result < len.

As per coding guidelines, "Use proptest for property-based tests."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-negative/src/lib.rs` around lines 174 - 183, Add
property-based tests using proptest to cover the deterministic helpers'
invariants: create a #[cfg(test)] proptest module and add proptest cases that
generate arbitrary byte slices and lengths to assert the invariants for
flip_byte (for any der and offset < der.len() assert exactly one byte differs
and new byte == der[offset] ^ 0x01), truncate_der (for any len < der.len()
assert output equals &der[..len]), derived_offset (for any len > 0 and arbitrary
selector assert result < len), and derived_truncate_len_bytes (for any len > 1
assert result < len); reference the existing helper names flip_byte,
truncate_der, derived_offset, derived_truncate_len_bytes and use proptest
strategies for Vec<u8> and valid offsets/lengths to reproduce the conditions
described in the comment.
AGENTS.md (2)

77-90: ⚠️ Potential issue | 🟡 Minor

Workspace structure section is missing many newly introduced crates.

The PR adds at least these crates that are absent from the Architecture → Workspace Structure list:
uselesskey-core-seed, uselesskey-core-cache, uselesskey-core-token, uselesskey-core-x509-derive, uselesskey-core-keypair, uselesskey-core-kid, uselesskey-core-negative, uselesskey-core-sink, uselesskey-feature-grid, uselesskey-test-grid.

Without these entries, the section is materially incomplete for contributors using this file as orientation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 77 - 90, The Workspace Structure list is missing
several newly added crate entries; update the Architecture → Workspace Structure
list to include the new crates by adding lines for uselesskey-core-seed,
uselesskey-core-cache, uselesskey-core-token, uselesskey-core-x509-derive,
uselesskey-core-keypair, uselesskey-core-kid, uselesskey-core-negative,
uselesskey-core-sink, uselesskey-feature-grid, and uselesskey-test-grid
alongside the existing crate entries (keep the same bullet style and ordering
convention used in the list so they are discoverable and consistent with entries
like crates/uselesskey-core and crates/uselesskey-x509).

1-1: ⚠️ Potential issue | 🟡 Minor

Fix the document title: this file is AGENTS.md, not CLAUDE.md.

Line 1 reads # CLAUDE.md, but the file is AGENTS.md. These appear to be two distinct agent-guidance files, so AGENTS.md should have its own heading.

📝 Proposed fix
-# CLAUDE.md
+# AGENTS.md
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 1, The document header is incorrect: change the first-line
heading from "# CLAUDE.md" to a correct title for this file (for example "#
AGENTS.md" or another accurate heading that reflects the content) so the file
AGENTS.md has its own proper top-level heading; update only the heading line and
keep the rest of the content unchanged.
crates/uselesskey-x509/src/chain.rs (1)

64-87: 🧹 Nitpick | 🔵 Trivial

new duplicates with_variant — consider delegating.

new is identical to with_variant(…, "good") except for the hard-coded variant string. Delegating removes the duplication.

♻️ Proposed refactor
 pub(crate) fn new(factory: Factory, label: &str, spec: ChainSpec) -> Self {
-    let inner = load_chain_inner(&factory, label, &spec, "good");
-    Self {
-        factory,
-        label: label.to_string(),
-        spec,
-        inner,
-    }
+    Self::with_variant(factory, label, spec, "good")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-x509/src/chain.rs` around lines 64 - 87, The new()
constructor duplicates with_variant(...) by hardcoding the "good" variant;
refactor new to delegate to with_variant to remove duplication: replace new's
body so it calls Self::with_variant(factory, label, spec, "good") (ensuring
ownership/borrowing matches) rather than calling load_chain_inner itself; update
any signature/ownership adjustments as needed so Factory, label (&str), and spec
(ChainSpec) are passed correctly to with_variant.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/uselesskey-bdd/Cargo.toml`:
- Line 19: The uk-all feature currently declares both
"uselesskey-bdd-steps/uk-all" and every individual sub-feature (e.g.,
"uselesskey-bdd-steps/uk-rsa", "uselesskey-bdd-steps/uk-ecdsa",
"uselesskey-bdd-steps/uk-ed25519", etc.), which is redundant; change the uk-all
feature definition to only include "uselesskey-bdd-steps/uk-all" so the umbrella
feature in the uselesskey-bdd-steps crate controls the sub-features (update the
uk-all array to contain only "uselesskey-bdd-steps/uk-all").

In `@crates/uselesskey-core-cache/Cargo.toml`:
- Line 18: The spin dependency in Cargo.toml includes an unused feature
"spin_mutex"; remove that feature so the dependency only enables the used
"mutex" feature. Edit the spin entry in Cargo.toml (the dependency declaration
for spin) to drop "spin_mutex" from the features array, leaving features =
["mutex"] to match the usage of spin::Mutex in lib.rs.

In `@crates/uselesskey-core-jwk/src/lib.rs`:
- Around line 23-28: The Display impl for Jwks currently calls
serde_json::to_string(...).expect(...), which can panic; change it to handle
serialization errors by mapping them into a fmt::Error and returning
Err(fmt::Error) (e.g., use serde_json::to_string(self).map_err(|_|
fmt::Error).and_then(|s| f.write_str(&s).map_err(|_| fmt::Error)) or match on
the Result and return Err(fmt::Error) on failure). Apply the same non-panicking
pattern to the other Display implementations mentioned (the other impl
fmt::Display blocks in this file) so none call expect() and all convert
serde_json errors to fmt::Error instead.
- Around line 23-28: The Display impls for Jwks, AnyJwk, and PrivateJwk
currently serialize full key material; add a clear rustdoc WARNING above each
Display implementation (impl fmt::Display for Jwks, impl fmt::Display for
AnyJwk, impl fmt::Display for PrivateJwk) stating that Display emits
secret/unredacted material and should not be used for logging or diagnostics,
and then provide a safe alternative by adding a public helper that returns a
redacted JSON (e.g., pub fn to_redacted_json(&self) -> String) which reuses the
redaction logic from the existing Debug impls; update the rustdoc to recommend
using Debug or to_redacted_json() for inspection.

In `@crates/uselesskey-core-negative/src/lib.rs`:
- Around line 271-292: The unbounded loops in find_pem_variant and
find_der_variant can hang; add an iteration cap (e.g., for i in 0..1_000) and
after the loop panic with a clear message indicating exhaustion (include target
and function name) so tests fail fast instead of looping forever; update both
functions (find_pem_variant and find_der_variant) to use the capped range and a
panic! if no matching variant is found.

In `@crates/uselesskey-core-seed/src/lib.rs`:
- Around line 28-37: Update the documentation for from_env_value to explicitly
state the 64-char edge case: when the trimmed input (after optional "0x" prefix)
is exactly 64 characters the function will attempt parse_hex_32 and return that
Err on invalid hex rather than falling back to blake3::hash; otherwise any other
string is hashed with blake3::hash. Reference from_env_value and parse_hex_32 in
the doc comment so callers know this behavior.

In `@crates/uselesskey-core-seed/tests/seed_prop.rs`:
- Around line 14-21: The test comment for
seed_from_env_value_non_64_lengths_are_ok is misleading: update the doc comment
to state the real invariant — that after trimming and stripping an optional "0x"
prefix (using trimmed and after_prefix) any input whose effective length
(after_prefix.len()) is not 64 should cause Seed::from_env_value(&s) to succeed;
mention that the strategy plus prop_assume! excludes 64-length effective inputs
and that non-hex characters in a 64-char string are not covered by this test.

In `@crates/uselesskey-core-x509-derive/Cargo.toml`:
- Around line 19-20: The rcgen = "0.13" and time = "0.3" dependency entries
should be removed from the uselesskey-core-x509-derive Cargo.toml and instead
added to the workspace Cargo.toml under [workspace.dependencies] so both crates
reuse the same pinned versions; locate the rcgen and time lines in the
uselesskey-core-x509-derive manifest (and the duplicate entries in
uselesskey-x509) and move them to the workspace dependencies section alongside
existing workspace = true entries like blake3 and rand_core, preserving the
exact version pins (rcgen = "0.13", time = "0.3") to avoid the rcgen 0.14
breaking changes.

In `@crates/uselesskey-core-x509-derive/src/lib.rs`:
- Around line 34-37: The doc comment for deterministic_base_time is off by one
day: with BASE_TIME_WINDOW_DAYS = 365 and using day_offset % 365 the computed
range is 2025-01-01 through 2025-12-31, not 2026-01-01; update the comment to
reflect the actual window or if you intended to include 2026-01-01 change
BASE_TIME_WINDOW_DAYS to 366 (and keep day_offset % BASE_TIME_WINDOW_DAYS) so
the range becomes 2025-01-01 through 2026-01-01; adjust the text near the
deterministic_base_time function accordingly and ensure references to
BASE_TIME_WINDOW_DAYS and the day_offset modulo logic match the chosen intent.

In `@crates/uselesskey-core-x509/src/negative.rs`:
- Around line 45-66: The X509Negative::SelfSignedButClaimsCA variant is
described as conflicting but the builder sets spec.is_ca = true and
spec.key_usage = KeyUsage::ca(), producing a consistent CA; change the fixture
to actually conflict or make naming/description accurate: either (A) modify the
SelfSignedButClaimsCA branch to introduce a conflict (e.g., keep spec.is_ca =
true but remove or clear key usage like not setting KeyUsage::ca(), or set an
explicit conflicting key usage), or (B) rename the variant/description in
description() to reflect that it produces a valid self-signed CA (update
X509Negative::SelfSignedButClaimsCA string) so behavior and description match;
update references accordingly (look for X509Negative::SelfSignedButClaimsCA,
spec.is_ca, KeyUsage::ca(), and the description() match arm).

In `@crates/uselesskey-jwk/Cargo.toml`:
- Around line 17-18: Replace the inline path+version dependency for
uselesskey-core-jwk with a workspace-managed dependency: add an entry for
uselesskey-core-jwk to the workspace's [workspace.dependencies] in the root
Cargo.toml (the same key and version that was used here) and then in the crate's
Cargo.toml (where the current line "uselesskey-core-jwk = { path = ..., version
= ... }" exists) change that dependency to use workspace = true (remove path and
version). This ensures the crate uses the central workspace version and avoids
silent divergence; update the dependency declaration in the crate that
references uselesskey-core-jwk to "uselesskey-core-jwk = { workspace = true }".

In `@crates/uselesskey-x509/Cargo.toml`:
- Around line 25-26: Remove the unused workspace dependencies by deleting the
rsa.workspace and base64.workspace entries from the crate's Cargo.toml;
specifically remove the lines declaring rsa.workspace = true and
base64.workspace = true so the crate no longer redundantly lists rsa (provided
transitively via uselesskey-rsa) and the unused base64 dependency.

In `@crates/uselesskey-x509/src/chain.rs`:
- Around line 311-317: Replace the silent cast of spec.rsa_bits to u32 with an
explicit try_from conversion to avoid silent truncation: call
u32::try_from(spec.rsa_bits).expect("rsa_bits fits u32") and then .to_be_bytes()
when building rsa_bits; update the rsa_bits binding used in
deterministic_base_time_from_parts and add the necessary TryFrom import if
required to resolve the trait.

In `@xtask/src/main.rs`:
- Around line 242-252: The PUBLISH_CRATES array (const PUBLISH_CRATES) is
missing the new crate "uselesskey-core-x509-spec"; update the PUBLISH_CRATES
constant to include "uselesskey-core-x509-spec" in the list so
publish-check/preflight and mutants include the new crate during release checks,
ensuring the string is added alongside the other "uselesskey-core-*" entries.

---

Outside diff comments:
In `@AGENTS.md`:
- Around line 77-90: The Workspace Structure list is missing several newly added
crate entries; update the Architecture → Workspace Structure list to include the
new crates by adding lines for uselesskey-core-seed, uselesskey-core-cache,
uselesskey-core-token, uselesskey-core-x509-derive, uselesskey-core-keypair,
uselesskey-core-kid, uselesskey-core-negative, uselesskey-core-sink,
uselesskey-feature-grid, and uselesskey-test-grid alongside the existing crate
entries (keep the same bullet style and ordering convention used in the list so
they are discoverable and consistent with entries like crates/uselesskey-core
and crates/uselesskey-x509).
- Line 1: The document header is incorrect: change the first-line heading from
"# CLAUDE.md" to a correct title for this file (for example "# AGENTS.md" or
another accurate heading that reflects the content) so the file AGENTS.md has
its own proper top-level heading; update only the heading line and keep the rest
of the content unchanged.

In `@crates/uselesskey-core-negative/src/lib.rs`:
- Around line 40-43: The match arm currently shadows the outer `bytes: &[u8;
32]` by binding a new `bytes: usize`; update the binding name in the `_` arm
(for example `truncate_chars` or `truncate_len`) and use that new name in the
call to `derived_truncate_len(pem, ...)` and when constructing
`CorruptPem::Truncate { bytes: ... }` so the slice `bytes` and the usize length
are not ambiguous; specifically edit the `_` arm around the calls to
`derived_truncate_len` and `corrupt_pem` to use the new identifier instead of
`bytes`.
- Line 17: The Truncate variant's field is misnamed: CorruptPem::Truncate {
bytes: usize } is interpreted as a character count by corrupt_pem and
corrupt_pem_deterministic (they use pem.chars().take(bytes)), so rename the
field to "chars" (CorruptPem::Truncate { chars: usize }) and update all uses in
corrupt_pem and corrupt_pem_deterministic to read the new field name;
alternatively, if you intended a byte count, change the logic to slice the PEM
by bytes using pem.as_bytes().take(bytes) and convert back safely—pick one
approach and make the variant, pattern matches, and consumers consistent.
- Around line 166-172: The truncation can return 0 and collapse for len==2;
update derived_truncate_len_bytes to mirror the PEM/derived_truncate_len
behavior by computing a value in [1, len-1] instead of [0, len-2]: keep the
early return for len <= 1, compute span = len - 1, and return 1 + (digest[2] as
usize % span) so at least one byte remains and len==2 yields 1; adjust any
comments if present in derived_truncate_len_bytes to reflect the new range.
- Around line 174-183: Add property-based tests using proptest to cover the
deterministic helpers' invariants: create a #[cfg(test)] proptest module and add
proptest cases that generate arbitrary byte slices and lengths to assert the
invariants for flip_byte (for any der and offset < der.len() assert exactly one
byte differs and new byte == der[offset] ^ 0x01), truncate_der (for any len <
der.len() assert output equals &der[..len]), derived_offset (for any len > 0 and
arbitrary selector assert result < len), and derived_truncate_len_bytes (for any
len > 1 assert result < len); reference the existing helper names flip_byte,
truncate_der, derived_offset, derived_truncate_len_bytes and use proptest
strategies for Vec<u8> and valid offsets/lengths to reproduce the conditions
described in the comment.

In `@crates/uselesskey-x509/src/chain.rs`:
- Around line 64-87: The new() constructor duplicates with_variant(...) by
hardcoding the "good" variant; refactor new to delegate to with_variant to
remove duplication: replace new's body so it calls Self::with_variant(factory,
label, spec, "good") (ensuring ownership/borrowing matches) rather than calling
load_chain_inner itself; update any signature/ownership adjustments as needed so
Factory, label (&str), and spec (ChainSpec) are passed correctly to
with_variant.

In `@xtask/src/plan.rs`:
- Around line 149-222: The dependents() graph is missing an entry for the
workspace crate "uselesskey-core-x509-spec", so changes there won't propagate to
dependents; update the match in the dependents() function to include a new arm
for "uselesskey-core-x509-spec" that lists its downstream crates (at minimum
"uselesskey-core-x509" and any other crates that directly depend on the spec),
ensuring the propagation includes this crate alongside the existing
"uselesskey-core-x509" entry.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2c77ab and a65b4f8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (63)
  • AGENTS.md
  • CLAUDE.md
  • Cargo.toml
  • crates/uselesskey-aws-lc-rs/tests/aws_lc_rs_comprehensive.rs
  • crates/uselesskey-bdd-steps/Cargo.toml
  • crates/uselesskey-bdd-steps/src/lib.rs
  • crates/uselesskey-bdd-steps/src/steps/core_seed_steps.rs
  • crates/uselesskey-bdd/Cargo.toml
  • crates/uselesskey-bdd/features/core-seed.feature
  • crates/uselesskey-core-cache/Cargo.toml
  • crates/uselesskey-core-cache/README.md
  • crates/uselesskey-core-cache/src/lib.rs
  • crates/uselesskey-core-id/Cargo.toml
  • crates/uselesskey-core-id/README.md
  • crates/uselesskey-core-id/src/lib.rs
  • crates/uselesskey-core-id/tests/seed_integration.rs
  • crates/uselesskey-core-jwk/Cargo.toml
  • crates/uselesskey-core-jwk/README.md
  • crates/uselesskey-core-jwk/src/lib.rs
  • crates/uselesskey-core-jwk/tests/jwk_prop.rs
  • crates/uselesskey-core-negative/src/lib.rs
  • crates/uselesskey-core-seed/Cargo.toml
  • crates/uselesskey-core-seed/README.md
  • crates/uselesskey-core-seed/src/lib.rs
  • crates/uselesskey-core-seed/tests/seed_prop.rs
  • crates/uselesskey-core-token/Cargo.toml
  • crates/uselesskey-core-token/README.md
  • crates/uselesskey-core-token/src/lib.rs
  • crates/uselesskey-core-x509-derive/Cargo.toml
  • crates/uselesskey-core-x509-derive/README.md
  • crates/uselesskey-core-x509-derive/src/lib.rs
  • crates/uselesskey-core-x509-spec/Cargo.toml
  • crates/uselesskey-core-x509-spec/README.md
  • crates/uselesskey-core-x509-spec/src/chain_spec.rs
  • crates/uselesskey-core-x509-spec/src/lib.rs
  • crates/uselesskey-core-x509-spec/src/spec.rs
  • crates/uselesskey-core-x509/Cargo.toml
  • crates/uselesskey-core-x509/README.md
  • crates/uselesskey-core-x509/src/lib.rs
  • crates/uselesskey-core-x509/src/negative.rs
  • crates/uselesskey-core/Cargo.toml
  • crates/uselesskey-core/README.md
  • crates/uselesskey-core/src/factory.rs
  • crates/uselesskey-core/src/factory_tests.rs
  • crates/uselesskey-feature-grid/src/lib.rs
  • crates/uselesskey-jwk/Cargo.toml
  • crates/uselesskey-jwk/README.md
  • crates/uselesskey-jwk/src/lib.rs
  • crates/uselesskey-jwk/tests/reexport_integration.rs
  • crates/uselesskey-token/Cargo.toml
  • crates/uselesskey-token/src/token.rs
  • crates/uselesskey-x509/Cargo.toml
  • crates/uselesskey-x509/src/cert.rs
  • crates/uselesskey-x509/src/chain.rs
  • crates/uselesskey-x509/src/chain_negative.rs
  • crates/uselesskey-x509/src/lib.rs
  • crates/uselesskey-x509/src/negative.rs
  • crates/uselesskey-x509/src/util.rs
  • fuzz/Cargo.toml
  • fuzz/fuzz_targets/jwk_builder.rs
  • fuzz/fuzz_targets/seed_parse.rs
  • xtask/src/main.rs
  • xtask/src/plan.rs
💤 Files with no reviewable changes (1)
  • crates/uselesskey-x509/src/util.rs

default = ["uk-default"]
uk-default = ["uselesskey-bdd-steps/uk-default"]
uk-all = ["uselesskey-bdd-steps/uk-all", "uselesskey-bdd-steps/uk-rsa", "uselesskey-bdd-steps/uk-ecdsa", "uselesskey-bdd-steps/uk-ed25519", "uselesskey-bdd-steps/uk-hmac", "uselesskey-bdd-steps/uk-pgp", "uselesskey-bdd-steps/uk-x509", "uselesskey-bdd-steps/uk-jwk", "uselesskey-bdd-steps/uk-token", "uselesskey-bdd-steps/uk-jwt", "uselesskey-bdd-steps/uk-core-id", "uselesskey-bdd-steps/uk-core-kid", "uselesskey-bdd-steps/uk-core-keypair", "uselesskey-bdd-steps/uk-core-negative", "uselesskey-bdd-steps/uk-core-sink", "uselesskey-bdd-steps/uk-aws-lc-rs", "uselesskey-bdd-steps/uk-rustls", "uselesskey-bdd-steps/uk-tonic", "uselesskey-bdd-steps/uk-ring", "uselesskey-bdd-steps/uk-rustcrypto"]
uk-all = ["uselesskey-bdd-steps/uk-all", "uselesskey-bdd-steps/uk-rsa", "uselesskey-bdd-steps/uk-ecdsa", "uselesskey-bdd-steps/uk-ed25519", "uselesskey-bdd-steps/uk-hmac", "uselesskey-bdd-steps/uk-pgp", "uselesskey-bdd-steps/uk-x509", "uselesskey-bdd-steps/uk-jwk", "uselesskey-bdd-steps/uk-token", "uselesskey-bdd-steps/uk-jwt", "uselesskey-bdd-steps/uk-core-id", "uselesskey-bdd-steps/uk-core-seed", "uselesskey-bdd-steps/uk-core-kid", "uselesskey-bdd-steps/uk-core-keypair", "uselesskey-bdd-steps/uk-core-negative", "uselesskey-bdd-steps/uk-core-sink", "uselesskey-bdd-steps/uk-aws-lc-rs", "uselesskey-bdd-steps/uk-rustls", "uselesskey-bdd-steps/uk-tonic", "uselesskey-bdd-steps/uk-ring", "uselesskey-bdd-steps/uk-rustcrypto"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

uk-all lists uselesskey-bdd-steps/uk-all plus all individual sub-features explicitly — likely redundant.

If uselesskey-bdd-steps/uk-all already activates every individual feature in that crate (e.g., uk-rsa, uk-core-seed, etc.), the 20 explicit uselesskey-bdd-steps/<feature> entries are redundant and will accumulate maintenance burden each time a new micro-crate is added (requiring edits in both uselesskey-bdd-steps/uk-all and here).

Consider simplifying to just:

uk-all = ["uselesskey-bdd-steps/uk-all"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-bdd/Cargo.toml` at line 19, The uk-all feature currently
declares both "uselesskey-bdd-steps/uk-all" and every individual sub-feature
(e.g., "uselesskey-bdd-steps/uk-rsa", "uselesskey-bdd-steps/uk-ecdsa",
"uselesskey-bdd-steps/uk-ed25519", etc.), which is redundant; change the uk-all
feature definition to only include "uselesskey-bdd-steps/uk-all" so the umbrella
feature in the uselesskey-bdd-steps crate controls the sub-features (update the
uk-all array to contain only "uselesskey-bdd-steps/uk-all").


[dependencies]
dashmap = { workspace = true, optional = true }
spin = { version = "0.9.8", default-features = false, features = ["mutex", "spin_mutex"] }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n --type=rust 'SpinMutex' crates/uselesskey-core-cache/src/

Repository: EffortlessMetrics/uselesskey

Length of output: 54


🏁 Script executed:

#!/bin/bash
rg -n --type=rust 'spin::' crates/uselesskey-core-cache/src/

Repository: EffortlessMetrics/uselesskey

Length of output: 131


Remove the unused spin_mutex feature from the spin dependency.

The codebase only uses spin::Mutex (line 15 of lib.rs), which is provided by the mutex feature. The spin_mutex feature is not used anywhere and should be removed. The dependency should be:

spin = { version = "0.9.8", default-features = false, features = ["mutex"] }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-cache/Cargo.toml` at line 18, The spin dependency in
Cargo.toml includes an unused feature "spin_mutex"; remove that feature so the
dependency only enables the used "mutex" feature. Edit the spin entry in
Cargo.toml (the dependency declaration for spin) to drop "spin_mutex" from the
features array, leaving features = ["mutex"] to match the usage of spin::Mutex
in lib.rs.

Comment on lines +23 to +28
impl fmt::Display for Jwks {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_json::to_string(self).expect("serialize JWKS");
f.write_str(&s)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Avoid expect() panics inside Display implementations.

fmt::Display is commonly hit in error/logging paths; panicking here (Line 25, Line 277, Line 308, Line 346) can take down callers unexpectedly. Prefer mapping serialization errors to fmt::Error.

Proposed diff (non-panicking Display)
 impl fmt::Display for Jwks {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let s = serde_json::to_string(self).expect("serialize JWKS");
+        let s = serde_json::to_string(self).map_err(|_| fmt::Error)?;
         f.write_str(&s)
     }
 }
@@
 impl fmt::Display for PublicJwk {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let s = serde_json::to_string(self).expect("serialize JWK");
+        let s = serde_json::to_string(self).map_err(|_| fmt::Error)?;
         f.write_str(&s)
     }
 }
@@
 impl fmt::Display for PrivateJwk {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let s = serde_json::to_string(self).expect("serialize JWK");
+        let s = serde_json::to_string(self).map_err(|_| fmt::Error)?;
         f.write_str(&s)
     }
 }
@@
 impl fmt::Display for AnyJwk {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let s = serde_json::to_string(self).expect("serialize JWK");
+        let s = serde_json::to_string(self).map_err(|_| fmt::Error)?;
         f.write_str(&s)
     }
 }

Also applies to: 275-280, 306-311, 344-349

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-jwk/src/lib.rs` around lines 23 - 28, The Display impl
for Jwks currently calls serde_json::to_string(...).expect(...), which can
panic; change it to handle serialization errors by mapping them into a
fmt::Error and returning Err(fmt::Error) (e.g., use
serde_json::to_string(self).map_err(|_| fmt::Error).and_then(|s|
f.write_str(&s).map_err(|_| fmt::Error)) or match on the Result and return
Err(fmt::Error) on failure). Apply the same non-panicking pattern to the other
Display implementations mentioned (the other impl fmt::Display blocks in this
file) so none call expect() and all convert serde_json errors to fmt::Error
instead.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file structure and size
wc -l crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 113


🏁 Script executed:

# Read the flagged lines to see the Display implementations
sed -n '23,28p' crates/uselesskey-core-jwk/src/lib.rs
echo "---"
sed -n '306,311p' crates/uselesskey-core-jwk/src/lib.rs
echo "---"
sed -n '344,349p' crates/uselesskey-core-jwk/src/lib.rs
echo "---"
sed -n '498,508p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 1119


🏁 Script executed:

# Look for Debug trait implementations to see if they redact
rg -n --type=rust 'impl.*Debug' crates/uselesskey-core-jwk/src/lib.rs -A 5

Repository: EffortlessMetrics/uselesskey

Length of output: 1447


🏁 Script executed:

# Check what serde_json::to_string includes for these structures
rg -n --type=rust 'derive.*Serialize' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 421


🏁 Script executed:

# Look for all Display implementations in the file
rg -n --type=rust 'impl.*Display' crates/uselesskey-core-jwk/src/lib.rs -A 5

Repository: EffortlessMetrics/uselesskey

Length of output: 956


🏁 Script executed:

# Look at the structure definitions for the private key types
sed -n '85,105p' crates/uselesskey-core-jwk/src/lib.rs
echo "---"
sed -n '154,172p' crates/uselesskey-core-jwk/src/lib.rs
echo "---"
sed -n '253,273p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 1287


🏁 Script executed:

# Check for any doc comments or warnings about Display
rg -n --type=rust '/// .*secret|/// .*private|/// .*leak' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 54


🏁 Script executed:

# Look at PrivateJwk enum definition
sed -n '282,305p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 611


🏁 Script executed:

# Check for any serde skip attributes that might hide fields
rg -n --type=rust '#\[serde.*skip' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 54


🏁 Script executed:

# Get full RsaPrivateJwk structure
sed -n '136,155p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 456


🏁 Script executed:

# Get full OctJwk structure
sed -n '228,252p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 591


🏁 Script executed:

# Check if there's any documentation about this behavior
sed -n '1,25p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 754


🏁 Script executed:

# Look at the test more carefully to see what's being tested
sed -n '498,520p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 851


🏁 Script executed:

# Check RsaPrivateJwk for private key fields
sed -n '136,152p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 401


🏁 Script executed:

# Check OkpPrivateJwk for private key fields  
sed -n '200,217p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 406


🏁 Script executed:

# Check what this crate is used for - look at Cargo.toml and any README
cat crates/uselesskey-core-jwk/Cargo.toml | head -20

Repository: EffortlessMetrics/uselesskey

Length of output: 681


🏁 Script executed:

# Check if there are any integration tests that use these Display impls in real scenarios
rg -n --type=rust 'to_string\(\)|format!\(' crates/uselesskey-core-jwk/ -B 3 -A 3 | head -60

Repository: EffortlessMetrics/uselesskey

Length of output: 3844


🏁 Script executed:

# Look at test helper functions to understand context
rg -n --type=rust 'fn sample_oct_private|fn sample_rsa' crates/uselesskey-core-jwk/src/lib.rs -A 5

Repository: EffortlessMetrics/uselesskey

Length of output: 758


🏁 Script executed:

# Check if there's any documentation or comments about the intended use of Display
sed -n '306,325p' crates/uselesskey-core-jwk/src/lib.rs

Repository: EffortlessMetrics/uselesskey

Length of output: 645


🏁 Script executed:

# Look at the README to understand the library's purpose
cat crates/uselesskey-core-jwk/README.md

Repository: EffortlessMetrics/uselesskey

Length of output: 489


Display trait serializes all private key material in cleartext, while Debug redacts it.

Display for PrivateJwk, AnyJwk, and Jwks (lines 306–311, 344–349, 23–28) serialize all fields via serde_json::to_string(), including private exponents (d, p, q, dp, dq, qi for RSA; d for EC/OKP) and symmetric keys (k for oct). This differs from their redacted Debug implementations and creates a footgun: accidental {} formatting will leak secrets into CI logs or console output. The test display_outputs_json reinforces that this behavior is intentional.

While serializing full keys makes sense for this fixture library, the Display/Debug inconsistency is risky. Add rustdoc warnings to Display impls that they output secret material unredacted, and consider providing a safe Debug path for aggregate types (AnyJwk, Jwks) to discourage callers from reaching for Display when inspecting values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-jwk/src/lib.rs` around lines 23 - 28, The Display
impls for Jwks, AnyJwk, and PrivateJwk currently serialize full key material;
add a clear rustdoc WARNING above each Display implementation (impl fmt::Display
for Jwks, impl fmt::Display for AnyJwk, impl fmt::Display for PrivateJwk)
stating that Display emits secret/unredacted material and should not be used for
logging or diagnostics, and then provide a safe alternative by adding a public
helper that returns a redacted JSON (e.g., pub fn to_redacted_json(&self) ->
String) which reuses the redaction logic from the existing Debug impls; update
the rustdoc to recommend using Debug or to_redacted_json() for inspection.

Comment on lines +271 to +292
fn find_pem_variant(target: u8) -> String {
for i in 0u64.. {
let v = format!("v{i}");
let digest = hash32(v.as_bytes());
if digest.as_bytes()[0] % 5 == target {
return v;
}
}
unreachable!()
}

/// Find a variant string whose `hash32` first byte % 3 == target.
fn find_der_variant(target: u8) -> String {
for i in 0u64.. {
let v = format!("v{i}");
let digest = hash32(v.as_bytes());
if digest.as_bytes()[0] % 3 == target {
return v;
}
}
unreachable!()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Unbounded search loops in test helpers

find_pem_variant and find_der_variant loop over 0u64.. without an iteration cap. Blake3 distributes uniformly so they terminate in a few iterations in practice, but a hard limit (e.g., 1_000) with a panic! on exhaustion avoids infinite-loop surprises in unusual environments.

♻️ Proposed guard
 fn find_pem_variant(target: u8) -> String {
-    for i in 0u64.. {
+    for i in 0u64..1_000 {
         let v = format!("v{i}");
         let digest = hash32(v.as_bytes());
         if digest.as_bytes()[0] % 5 == target {
             return v;
         }
     }
-    unreachable!()
+    panic!("find_pem_variant: no variant found for target {target} within search limit")
 }

(Identical change for find_der_variant.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-negative/src/lib.rs` around lines 271 - 292, The
unbounded loops in find_pem_variant and find_der_variant can hang; add an
iteration cap (e.g., for i in 0..1_000) and after the loop panic with a clear
message indicating exhaustion (include target and function name) so tests fail
fast instead of looping forever; update both functions (find_pem_variant and
find_der_variant) to use the capped range and a panic! if no matching variant is
found.

Comment on lines +28 to +37
pub fn from_env_value(value: &str) -> Result<Self, String> {
let v = value.trim();
let hex = v.strip_prefix("0x").unwrap_or(v);

if hex.len() == 64 {
return parse_hex_32(hex).map(Self);
}

Ok(Self(*blake3::hash(v.as_bytes()).as_bytes()))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Document the 64-char non-hex edge case.

A trimmed input of exactly 64 characters that is not valid hex (e.g. contains !, g, etc.) hits the hex.len() == 64 branch and returns Err rather than falling through to the BLAKE3 hash path. The current doc comment says "any other string (hashed with BLAKE3)" which implies a clean fallback for all non-64-char-hex inputs, but this edge case silently fails instead of hashing. Callers who happen to pass a 64-char non-hex string will be surprised.

📝 Suggested doc clarification
     /// Accepted formats:
     /// - 64-char hex (with optional `0x` prefix)
-    /// - any other string (hashed with BLAKE3)
+    /// - any other string (hashed with BLAKE3)
+    ///
+    /// Note: a string whose effective length (after trimming and stripping `0x`) is
+    /// exactly 64 characters but contains non-hex digits returns `Err`; it is NOT
+    /// hashed as a fallback.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-seed/src/lib.rs` around lines 28 - 37, Update the
documentation for from_env_value to explicitly state the 64-char edge case: when
the trimmed input (after optional "0x" prefix) is exactly 64 characters the
function will attempt parse_hex_32 and return that Err on invalid hex rather
than falling back to blake3::hash; otherwise any other string is hashed with
blake3::hash. Reference from_env_value and parse_hex_32 in the doc comment so
callers know this behavior.

Comment on lines +45 to +66
X509Negative::SelfSignedButClaimsCA => {
// Self-signed but claims to be CA with no real chain context.
spec.is_ca = true;
spec.key_usage = KeyUsage::ca();
}
}

spec
}

/// Human-readable description of this negative fixture.
pub fn description(&self) -> &'static str {
match self {
X509Negative::Expired => "Certificate with not_after in the past (expired)",
X509Negative::NotYetValid => {
"Certificate with not_before in the future (not yet valid)"
}
X509Negative::WrongKeyUsage => {
"Certificate marked as CA but without keyCertSign key usage"
}
X509Negative::SelfSignedButClaimsCA => "Self-signed certificate that claims to be a CA",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

SelfSignedButClaimsCA is described as conflicting but currently produces a valid CA

This branch sets is_ca = true and KeyUsage::ca(), which is internally consistent. If you need an always-invalid fixture, introduce an actual conflict or adjust the description/variant name to match the behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-x509/src/negative.rs` around lines 45 - 66, The
X509Negative::SelfSignedButClaimsCA variant is described as conflicting but the
builder sets spec.is_ca = true and spec.key_usage = KeyUsage::ca(), producing a
consistent CA; change the fixture to actually conflict or make
naming/description accurate: either (A) modify the SelfSignedButClaimsCA branch
to introduce a conflict (e.g., keep spec.is_ca = true but remove or clear key
usage like not setting KeyUsage::ca(), or set an explicit conflicting key
usage), or (B) rename the variant/description in description() to reflect that
it produces a valid self-signed CA (update X509Negative::SelfSignedButClaimsCA
string) so behavior and description match; update references accordingly (look
for X509Negative::SelfSignedButClaimsCA, spec.is_ca, KeyUsage::ca(), and the
description() match arm).

Comment on lines +17 to +18
uselesskey-core-jwk = { path = "../uselesskey-core-jwk", version = "0.3.0" }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding uselesskey-core-jwk to [workspace.dependencies] and using workspace = true here.

All other internal crates (blake3, rand_core, etc.) are managed via workspace inheritance. Keeping uselesskey-core-jwk as an inline path = … version = … entry will diverge silently if the workspace-level version is ever bumped centrally.

♻️ Suggested change

In the root Cargo.toml:

+ uselesskey-core-jwk = { path = "crates/uselesskey-core-jwk", version = "0.3.0" }

In this file:

-uselesskey-core-jwk = { path = "../uselesskey-core-jwk", version = "0.3.0" }
+uselesskey-core-jwk.workspace = true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-jwk/Cargo.toml` around lines 17 - 18, Replace the inline
path+version dependency for uselesskey-core-jwk with a workspace-managed
dependency: add an entry for uselesskey-core-jwk to the workspace's
[workspace.dependencies] in the root Cargo.toml (the same key and version that
was used here) and then in the crate's Cargo.toml (where the current line
"uselesskey-core-jwk = { path = ..., version = ... }" exists) change that
dependency to use workspace = true (remove path and version). This ensures the
crate uses the central workspace version and avoids silent divergence; update
the dependency declaration in the crate that references uselesskey-core-jwk to
"uselesskey-core-jwk = { workspace = true }".

Comment on lines 25 to 26
rsa.workspace = true
base64.workspace = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if rsa or base64 are referenced directly in uselesskey-x509 source
rg -n --type=rust 'rsa::|base64::' crates/uselesskey-x509/src/

Repository: EffortlessMetrics/uselesskey

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Check uselesskey-x509 Cargo.toml to confirm rsa/base64 are listed
echo "=== uselesskey-x509 Cargo.toml lines 20-30 ==="
sed -n '20,30p' crates/uselesskey-x509/Cargo.toml

# Check if uselesskey_rsa depends on rsa and/or base64
echo ""
echo "=== uselesskey_rsa/Cargo.toml dependencies ==="
if [ -f crates/uselesskey_rsa/Cargo.toml ]; then
  cat crates/uselesskey_rsa/Cargo.toml
else
  echo "File not found"
fi

Repository: EffortlessMetrics/uselesskey

Length of output: 430


🏁 Script executed:

#!/bin/bash
# Search for RsaFactoryExt and RsaSpec definitions
echo "=== Searching for RsaFactoryExt and RsaSpec ==="
rg -l 'RsaFactoryExt|RsaSpec' crates/ --type=rust

echo ""
echo "=== Check if uselesskey_rsa is a module or re-export ==="
rg -n 'mod uselesskey_rsa|pub use.*uselesskey_rsa' crates/ --type=rust

echo ""
echo "=== List all crates in repo ==="
fd -type d -mindepth 2 -maxdepth 2 'Cargo.toml' crates/ | xargs dirname

Repository: EffortlessMetrics/uselesskey

Length of output: 1525


🏁 Script executed:

#!/bin/bash
# Check full dependencies section of uselesskey-x509
echo "=== uselesskey-x509/Cargo.toml [dependencies] section ==="
sed -n '/^\[dependencies\]/,/^\[/p' crates/uselesskey-x509/Cargo.toml | head -20

echo ""
echo "=== uselesskey-rsa/Cargo.toml dependencies ==="
sed -n '/^\[dependencies\]/,/^\[/p' crates/uselesskey-rsa/Cargo.toml | head -30

Repository: EffortlessMetrics/uselesskey

Length of output: 1005


Remove unused workspace dependencies rsa and base64 from uselesskey-x509.

These dependencies are declared but never used directly in the codebase. The rsa crate is available transitively via uselesskey-rsa, and base64 is not referenced at all. Both should be removed from lines 25–26 of the Cargo.toml.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-x509/Cargo.toml` around lines 25 - 26, Remove the unused
workspace dependencies by deleting the rsa.workspace and base64.workspace
entries from the crate's Cargo.toml; specifically remove the lines declaring
rsa.workspace = true and base64.workspace = true so the crate no longer
redundantly lists rsa (provided transitively via uselesskey-rsa) and the unused
base64 dependency.

Comment on lines +311 to +317
let rsa_bits = (spec.rsa_bits as u32).to_be_bytes();
let base_time = deterministic_base_time_from_parts(&[
label.as_bytes(),
spec.leaf_cn.as_bytes(),
spec.root_cn.as_bytes(),
&rsa_bits,
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ChainSpec struct definition with rsa_bits field
ast-grep --pattern 'struct ChainSpec {
  $$$
  rsa_bits: $_,
  $$$
}'

Repository: EffortlessMetrics/uselesskey

Length of output: 54


🏁 Script executed:

# Fallback: search for ChainSpec definition and rsa_bits type
rg -n "struct ChainSpec" --type rust -A 20

Repository: EffortlessMetrics/uselesskey

Length of output: 2068


🏁 Script executed:

# Also check the exact location in chain.rs
cd crates/uselesskey-x509/src && cat -n chain.rs | sed -n '280,320p'

Repository: EffortlessMetrics/uselesskey

Length of output: 1951


Consider using try_from to make the RSA key size conversion explicit and safe from silent truncation.

spec.rsa_bits is usize, and the silent cast to u32 could truncate values > u32::MAX. While RSA key sizes (typically 2048–4096 bits) will never exceed this in practice, using u32::try_from(spec.rsa_bits).expect("rsa_bits fits u32") makes the intent clearer and guards against bad values at the call site.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-x509/src/chain.rs` around lines 311 - 317, Replace the
silent cast of spec.rsa_bits to u32 with an explicit try_from conversion to
avoid silent truncation: call u32::try_from(spec.rsa_bits).expect("rsa_bits fits
u32") and then .to_be_bytes() when building rsa_bits; update the rsa_bits
binding used in deterministic_base_time_from_parts and add the necessary TryFrom
import if required to resolve the trait.

Comment thread xtask/src/main.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/uselesskey-core-cache/Cargo.toml (1)

18-18: Remove unused spin_mutex feature (already flagged previously).

#!/bin/bash
# Verify whether SpinMutex or related APIs are used anywhere.
rg -n --type=rust -C3 '\bSpinMutex\b|\bspin::SpinMutex\b'
rg -n --type=rust -C3 '\bspin::Mutex\b'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/uselesskey-core-cache/Cargo.toml` at line 18, The Cargo.toml currently
enables an unused feature "spin_mutex" on the spin dependency (spin = { version
= "0.9.8", default-features = false, features = ["mutex", "spin_mutex"] });
remove "spin_mutex" from the features array, run the provided verification
commands (searching for SpinMutex, spin::SpinMutex, and spin::Mutex) to ensure
no code references remain, then run cargo build and cargo test (and cargo update
if needed) to regenerate Cargo.lock and confirm the change is safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/uselesskey-core-cache/Cargo.toml`:
- Line 18: The Cargo.toml currently enables an unused feature "spin_mutex" on
the spin dependency (spin = { version = "0.9.8", default-features = false,
features = ["mutex", "spin_mutex"] }); remove "spin_mutex" from the features
array, run the provided verification commands (searching for SpinMutex,
spin::SpinMutex, and spin::Mutex) to ensure no code references remain, then run
cargo build and cargo test (and cargo update if needed) to regenerate Cargo.lock
and confirm the change is safe.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a65b4f8 and 672f661.

📒 Files selected for processing (6)
  • crates/uselesskey-core-cache/Cargo.toml
  • crates/uselesskey-core-id/Cargo.toml
  • crates/uselesskey-core-x509/Cargo.toml
  • crates/uselesskey-core/Cargo.toml
  • xtask/src/main.rs
  • xtask/src/plan.rs

@EffortlessSteven EffortlessSteven merged commit d146300 into main Feb 24, 2026
3 of 4 checks passed
@EffortlessSteven EffortlessSteven deleted the feat/core-keypair-and-cleanup branch February 24, 2026 18:39
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