Skip to content

Add velocity-dropwizard: the service tier (first increment)#11

Merged
wolpert merged 2 commits into
mainfrom
velocity-dropwizard
Jul 19, 2026
Merged

Add velocity-dropwizard: the service tier (first increment)#11
wolpert merged 2 commits into
mainfrom
velocity-dropwizard

Conversation

@wolpert

@wolpert wolpert commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

velocity-dropwizard — the Dropwizard 5 service tier, serving the velocity-api HTTP contract
over the velocity-core engine, wired with Dagger 2, behind namespace-scoped API-key auth.
First increment: record + query + capabilities, proven end-to-end over real HTTP.

What's in it

  • VelocityApplication builds a Dagger graph (VelocityModule/VelocityComponent provide the
    VelocityEngine = BackendRegistry + MutableFeatureDefinitionProvider + DimensionHasher +
    CapabilityValidator) and registers the JAX-RS resource behind the auth filter + a problem
    exception mapper. The backend is supplied via createBackend() (overridable) so a deployment wires
    a velocity-backend-*; tests wire the in-memory one.
  • VelocityResource: POST /v1/{ns}/record, POST /v1/{ns}/query, GET /v1/{ns}/capabilities
    (AR-2). Money and every value are JSON strings of a decimal integer (OQ-D) — BigDecimal cents
    String, no float.
  • ApiKeyFilter (P9 / NFR-21): X-API-Key → allowed-namespace set; missing/blank/unknown → 401,
    a key outside the path {namespace}403 (fail-closed — a credential cannot touch another
    tenant). RFC 9457 problem responses (AR-5).
  • Wire DTOs faithful to the OpenAPI (FeatureResultSuccess | Failure with a kind discriminator,
    ADR 0009 — never a silent success).

Tests

15 tests, 0 skipped, coverage 96.9% line / 86.4% branch (gate 80/70):

  • a DropwizardAppExtension HTTP integration test that boots the real app and proves record→query
    round-trip, fan-out (one record updates card.count AND card.sum), read-your-write, and the
    401/403 auth paths — money round-trips as "14950";
  • unit tests for WireMapper (incl. the UNSUPPORTED_WINDOW failure mapping), ExceptionMapper
    status branches, and all ApiKeyFilter branches.

Review

Hand-implemented (the implementer subagents stalled again — a delegation-tooling issue). Reviewed by
the change-validator agent: VERDICT APPROVE — verified the end-to-end HTTP round-trip, that
namespace-scoped auth is fail-closed and blocks cross-tenant access, the faithful money-as-string
encoding, real Dagger wiring, and meaningful (non-padding) tests.

⚠️ Follow-up to reconcile (flagged by review)

The query shape diverges between velocity-api (yaml: {tuples:[{subject,aggregation,window}]}
→ positional results) and velocity-core (engine: query(ns, subject, [featureNames]) → map). This
module faithfully wraps the core engine, so the OpenAPI contract and the core query model need to
be reconciled
before the openapi-generator cutover (AR-3) — either update the yaml to feature-name
query, or add tuple-based query to the engine. Worth an architect decision.

Other deferrals (scoped-out, not defects)

features + purge resources; openapi-generator server DTOs (replacing the hand-written ones); real
backend selection/config; finer AR-5 status mapping (unknown-namespace 404 / unsupported-window 422);
per-namespace backend selection in /capabilities.

./gradlew :velocity-dropwizard:buildBUILD SUCCESSFUL (in-JVM Jetty).

🤖 Generated with Claude Code

wolpert and others added 2 commits July 19, 2026 07:25
The Dropwizard 5 service serving the velocity-api HTTP contract over the velocity-core
engine, wired with Dagger 2, behind namespace-scoped API-key auth. First increment:
record + query + capabilities endpoints, proven end-to-end over real HTTP.

- VelocityApplication (Dropwizard 5) builds a Dagger graph (VelocityModule/VelocityComponent
  provide the VelocityEngine = BackendRegistry + MutableFeatureDefinitionProvider +
  DimensionHasher + CapabilityValidator) and registers the JAX-RS resource behind the
  auth filter + a problem exception mapper. The backend is supplied via createBackend()
  (overridable) so a deployment wires a velocity-backend-*; tests wire the in-memory one.
- VelocityResource: POST /v1/{ns}/record, POST /v1/{ns}/query, GET /v1/{ns}/capabilities
  (AR-2). Money and every value are JSON STRINGS of a decimal integer (OQ-D) — BigDecimal
  cents <-> String, no float.
- ApiKeyFilter (P9/NFR-21): X-API-Key -> allowed-namespace set; missing/blank/unknown -> 401,
  a key outside the path {namespace} -> 403 (fail-closed, cross-tenant access blocked).
  RFC 9457 problem responses (AR-5).
- Hand-written wire DTOs faithful to the OpenAPI (FeatureResult -> Success|Failure with a
  "kind" discriminator, ADR 0009 — never a silent success).
- No Jackson/Dagger leak concerns: Jackson is legitimate here (JSON service); Dagger-generated
  code excluded from the coverage gate.

15 tests (a DropwizardAppExtension HTTP integration test proving record->query round-trip,
fan-out, read-your-write, and the 401/403 auth paths; plus WireMapper/ExceptionMapper/
ApiKeyFilter unit tests), 0 skipped; coverage 96.9% line / 86.4% branch (gate 80/70).

Hand-implemented (implementer subagents stalled). Validated by the change-validator agent:
VERDICT APPROVE.

Deferred (follow-ups): features + purge resources; openapi-generator server DTOs (replacing
the hand-written ones); real backend selection/config; finer AR-5 status mapping (unknown-
namespace 404 / unsupported-window 422).

FOLLOW-UP TO RECONCILE (flagged by review): the query request/response shape DIVERGES between
velocity-api (tuple-based: {tuples:[{subject,aggregation,window}]} -> positional results) and
velocity-core (feature-name-based: query(ns, subject, [featureNames]) -> map). The module
faithfully wraps the core engine; the velocity-api yaml and velocity-core query model need to
be reconciled before the openapi-generator cutover (AR-3).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The velocity-api OpenAPI query was tuple-based ({tuples:[{subject,aggregation,
window}]} -> positional results) while velocity-core (and the velocity-dropwizard
service that wraps it) is feature-name-based. Align the contract to the engine:

- QueryRequest is now {subject, features:[name]}; QueryResponse maps each feature
  name to its per-window FeatureResult list — matching VelocityEngine.query and the
  service's wire DTOs (which were already feature-name-based).
- Remove the now-unused QueryTuple schema; update the OpenApiSpecTest schema list.

Resolves the query request/response divergence flagged in this PR's review, unblocking
velocity-client-java generation from a contract that agrees with the engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wolpert

wolpert commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Update: the query-contract divergence flagged in review is now resolved in this PR. Aligned the velocity-api OpenAPI query to the feature-name model — QueryRequest {subject, features:[name]}, QueryResponse maps each feature name to its per-window FeatureResult list — matching VelocityEngine.query and the service's wire DTOs (which were already feature-name-based). Removed the unused QueryTuple schema. velocity-api + velocity-dropwizard now agree, unblocking velocity-client-java generation. Both modules green.

@wolpert
wolpert merged commit 9d35e24 into main Jul 19, 2026
3 checks passed
@wolpert
wolpert deleted the velocity-dropwizard branch July 19, 2026 14: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.

1 participant