Skip to content

feat: implement HKP op=index (#137) - #152

Merged
bmarwell merged 5 commits into
mainfrom
issue-137-hkp-index
May 27, 2026
Merged

feat: implement HKP op=index (#137)#152
bmarwell merged 5 commits into
mainfrom
issue-137-hkp-index

Conversation

@bmarwell

Copy link
Copy Markdown
Owner

Summary

Implements GET /pks/lookup?op=index as described in #137.

What changed

Architecture:

  • KeyIndexResult and UidIndexEntry are top-level records in application-api — the web layer only depends on primary ports, no secondary port types cross the boundary
  • Added @NullMarked package-info.java to the application.api root package
  • application-port-repository pom gains a dependency on application-api (one-way, no cycle)

Port layer (KeyRepository):

  • Added findManyBySearch(String, boolean) returning List<KeyIndexResult>

Service layer:

  • KeyRepositoryService.searchForIndex() delegates to the new port method
  • PersistentKeyRepositoryService implements it

Repository adapter (JpaKeyRepository):

  • findManyBySearch() with the same routing (fingerprint → email → UID substring)
  • Extracted queryEntitiesByEmail() and queryEntitiesByUidSubstring() helpers that both the single-result and multi-result paths share — eliminates duplicated JPQL
  • toIndexResult(KeyEntity) maps to the new record; only verified=true UIDs included

Web layer:

  • HkpIndexRenderer (ApplicationScoped): renderMachineReadable() produces the HKP info:/pub:/uid: format; renderHtml() a simple table
  • LookupEndpoint routes op=index: options=mrtext/plain, else text/html; returns 404 when no results

Tests (LookupEndpointIndexTest, 7 cases):

  • 404 on empty results
  • info:1: header with correct count
  • pub: line format (fingerprint, algorithm, keylen, epoch seconds)
  • UID percent-encoding
  • r flag for revoked keys
  • e flag for expired keys
  • HTML content-type and fingerprint presence for non-mr requests

Closes #137

- Add KeyIndexResult and UidIndexEntry as top-level records in
  application-api so the web layer only depends on primary ports
- Add package-info.java (@NullMarked) to application-api root package
- Extend KeyRepository secondary port with findManyBySearch()
- Add keyserver-application-api dependency to application-port-repository
- Add searchForIndex() to KeyRepositoryService and implement in
  PersistentKeyRepositoryService
- Implement findManyBySearch() in JpaKeyRepository; refactor private
  search helpers (queryEntitiesByEmail, queryEntitiesByUidSubstring)
  to eliminate duplicated JPQL strings; both single- and multi-result
  paths share one query definition
- Add HkpIndexRenderer (ApplicationScoped): renderMachineReadable()
  produces HKP info:/pub:/uid: format; renderHtml() a simple table
- Wire op=index routing in LookupEndpoint (options=mr -> text/plain,
  else HTML; 404 when no results)
- Add LookupEndpointIndexTest (7 tests): 404 on empty, info: header,
  pub: line format, UID percent-encoding, revoked/expired flags, HTML

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.40230% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.86%. Comparing base (4b1ecb4) to head (85716c3).

Files with missing lines Patch % Lines
...hub/bmarwell/keyserver/web/pks/LookupEndpoint.java 88.23% 0 Missing and 2 partials ⚠️
...plication/core/PersistentKeyRepositoryService.java 0.00% 1 Missing ⚠️
...b/bmarwell/keyserver/web/pks/HkpIndexRenderer.java 98.55% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #152      +/-   ##
============================================
+ Coverage     66.83%   72.86%   +6.02%     
- Complexity      100      122      +22     
============================================
  Files            27       28       +1     
  Lines           392      479      +87     
  Branches         40       52      +12     
============================================
+ Hits            262      349      +87     
+ Misses          111      104       -7     
- Partials         19       26       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Implements HKP op=index end-to-end (closes #137). Adds a new application-API DTO pair (KeyIndexResult, UidIndexEntry), extends the outbound KeyRepository port and KeyRepositoryService with multi-result search, plumbs it through the JPA adapter (extracting shared JPQL helpers), and adds a new HkpIndexRenderer plus routing in LookupEndpoint for machine-readable (options=mr) and HTML output, with unit-test coverage.

Changes:

  • New application-api records KeyIndexResult/UidIndexEntry (with @NullMarked package-info) and a new searchForIndex service method, mirrored by findManyBySearch on the secondary repository port.
  • JpaKeyRepository gains findManyBySearch with the same routing as findBySearch, sharing extracted email/UID query helpers, and maps verified UIDs into the new DTO.
  • LookupEndpoint routes op=index through the new HkpIndexRenderer, which emits the info:/pub:/uid: machine-readable format (flags r,e) or a minimal HTML table.

Reviewed changes

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

Show a summary per file
File Description
application/application-api/.../KeyIndexResult.java New DTO for a key's index entry.
application/application-api/.../UidIndexEntry.java New DTO for a verified UID.
application/application-api/.../package-info.java Adds @NullMarked to root api package.
application/application-api/.../KeyRepositoryService.java Adds searchForIndex.
application/application-ports/.../KeyRepository.java Adds findManyBySearch and updates KeySearchResult javadoc.
application/application-ports/application-port-repository/pom.xml Depends on application-api for the new DTO.
application/application-core/.../PersistentKeyRepositoryService.java Implements searchForIndex by delegation.
application/application-core/.../VerifyUidCommandHandlerTest.java Updates fake repo to satisfy new port method.
repository/.../JpaKeyRepository.java Adds multi-result search paths, extracts shared JPQL helpers, maps KeyIndexResult.
web/.../LookupEndpoint.java Routes op=index, selects mr vs HTML, returns 404 on empty.
web/.../HkpIndexRenderer.java New renderer for machine-readable and HTML index output.
web/.../LookupEndpointIndexTest.java Seven unit tests covering routing, formatting, flags, and HTML.

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

- Cap multi-result index queries at 5_000 rows (INDEX_RESULT_LIMIT
  constant) to prevent unbounded memory use on broad search terms;
  applies to both email and UID-substring paths
- Replace '+' with '%20' in UID percent-encoding: URLEncoder uses
  form-encoding (space→'+') but '+' is a legal literal in UID strings
  and would be mis-decoded by strict RFC 3986 clients
- Strengthen expired-key test: assert the full pub: line including the
  expiration epoch and ':e' flag position, not just ':e\n', so the
  test targets the key-level flag specifically rather than the uid: line

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

- Add DB migration V010: 'disabled' BOOLEAN column on keys table (default FALSE)
- Add 'disabled' field to KeyEntity with getter/setter
- Add 'disabled' component to KeyIndexResult record
- HkpIndexRenderer.computeFlags now emits 'd' between 'r' and 'e' per HKP spec
  - Key-level pub: line passes key.disabled(); uid: lines always pass false
- Fix N+1 query for multi-result index paths (findManyByEmail, findManyByUidSubstring):
  - Two-query pattern: first query selects fingerprints only with setMaxResults()
    so the SQL LIMIT applies cleanly on a scalar query without pagination/JOIN FETCH conflict
  - Second query does JOIN FETCH k.uids WHERE fingerprint IN :fps for correct eager loading
- Fix findManyByKeyIdOrFingerprint: use JOIN FETCH k.uids with DISTINCT on all paths
  (fingerprint, long key ID, short key ID) to avoid N+1 per key
- Add JPA provider read-only hints (Hibernate, EclipseLink, Apache OpenJPA) on all
  multi-result index queries to skip dirty tracking and lock acquisition
- Add test setsFlagDForDisabledKey() in LookupEndpointIndexTest
- Update all existing KeyIndexResult constructions for new 'disabled' component

Closes #137 review comments (N+1 query, 'd' flag)
Related: #153 (search result ranking, filed separately)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

…lter, HTML label

- Fix V010 migration comment: disabled keys are shown with 'd' flag in op=index,
  not hidden from results (HKP spec behaviour)
- Filter keys with no verified UIDs from all findManyBy* result paths:
  toIndexResults() now drops KeyIndexResult entries with empty verifiedUids,
  making fingerprint/keyid lookups consistent with email/UID-substring paths
  which already gate matching on u.verified = true
- HTML renderer: rename 'Algorithm' column to 'Algorithm (OpenPGP code)' so
  users understand the integer value (e.g. 22 = EdDSA, 1 = RSA)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

…d HTML DOCTYPE

- AGENTS.adoc: document exception to 'no Optional in fields' rule — immutable
  transient DTO records (never persisted, never serialized) may use Optional
  components when optionality is intrinsic to the domain; KeyIndexResult and
  UidIndexEntry are the canonical examples
- LookupEndpoint: fix options=mr detection — split on ',' and compare tokens
  case-insensitively via Arrays.stream().anyMatch() instead of String.contains(),
  preventing false matches on tokens like 'nomr' or 'mrtg'
- HkpIndexRenderer.renderHtml: add DOCTYPE and <meta charset=utf-8> so browsers
  render in standards mode; fixes potential mis-rendering of UTF-8 UIDs via
  'save as' workflows and legacy intermediaries
- LookupEndpointIndexTest: add assertions for DOCTYPE/charset in HTML test and
  new recognisesMrTokenAmongCommaDelimitedOptions test (nm,mr -> machine-readable)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

@bmarwell
bmarwell merged commit ce07b74 into main May 27, 2026
7 checks passed
@bmarwell
bmarwell deleted the issue-137-hkp-index branch May 27, 2026 19:38
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.

feat: implement HKP op=index in LookupEndpoint

2 participants