Skip to content

Implement velocity-core: the embeddable engine (first increment) - #7

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

Implement velocity-core: the embeddable engine (first increment)#7
wolpert merged 2 commits into
mainfrom
velocity-core

Conversation

@wolpert

@wolpert wolpert commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

velocity-core — the embeddable engine, and the first consumer of both the frozen velocity-spi
contract and the velocity-testkit in-memory backend. This is a complete, end-to-end first
increment
: it loads feature definitions, validates them against a backend's capabilities, fans an
event out to intents across backends, and returns per-feature velocities.

What's in it

  • Feature-definition modelFeatureDefinition + SubjectSource (PRIMARY | FromDimension)
    for dimension-derived fan-out (D5/FR-18); FeatureDefinitions snapshot with a deterministic
    versionHash (FR-40). YAML round-trip import/export (FeatureDefinitionYaml, FR-28).
  • CapabilityValidator (FR-29) — validates a definition against BackendCapabilities
    (aggregation, every window, maxRetention ≥ largest window), collecting all violations.
  • FanOutResolver (FR-18) — one record() fans out across multiple subjects (primary +
    dimension-derived, e.g. per-IP) and aggregations; SUM without a value / DISTINCT without its
    dimension are SKIPPED with a reason, not silently dropped.
  • DimensionHasher (FR-38/R11) — HMAC-SHA256(namespace-salt, value) → opaque 32-byte
    DistinctMember; raw dimension values are never stored. NamespaceSaltProvider is an interface
    (a KMS-backed impl is a follow-up, per R11).
  • VelocityEnginerecord() / query() / capabilities() / purge() / reload(). record
    groups intents by backend and dispatches to the right mix-in (a missing mix-in → FAILED
    PerFeature, never a throw
    ), stamps definitionVersionHash, and returns ApplyResult
    (read-your-write). reload() validates then atomically swaps a namespace's definitions
    (FR-17/29); no matching definition → empty result (FR-4). No Dagger/Dropwizard — DI belongs to
    the service tier.

Deferred (noted as follow-ups)

Micrometer metrics (NFR-11), a KMS-backed salt provider, and non-primary hot-reload sources (OQ-C).

Tests

39 tests driving the real InMemoryVelocityBackend (not SPI mocks). Coverage 91.9% line /
88.1% branch
(gate 80/70; core pinned to 80 line). End-to-end fan-out (card count/sum/distinct + a
separate IP-derived subject), keyed-hash de-dup, atomic capability-validated reload, missing-mixin →
FAILED, read-your-write, namespace isolation, no-match → empty.

Review

Reviewed by the change-validator agent (CLAUDE.md working model): VERDICT APPROVE — it confirmed
fan-out correctness, opaque keyed-hashing (raw value never stored), all-violations atomic reload,
ADR-0009 result fidelity, and that tests drive the real backend. I added a symmetric
DISTINCT-dimension-absent skip test per its one non-blocking note.

./gradlew :velocity-core:buildBUILD SUCCESSFUL.

🤖 Generated with Claude Code

wolpert and others added 2 commits July 18, 2026 17:51
The engine that turns a record() into backend-neutral intents, routes them to
backends, and returns per-feature velocities — built on the frozen velocity-spi,
tested end-to-end against velocity-testkit's in-memory backend.

- FeatureDefinition model + SubjectSource (PRIMARY | FromDimension) for
  dimension-derived fan-out (D5/FR-18); FeatureDefinitions snapshot with a
  deterministic versionHash (FR-40).
- FeatureDefinitionYaml: round-trippable YAML import/export of definitions (FR-28).
- CapabilityValidator (FR-29): validates a definition against a backend's
  BackendCapabilities (aggregation, every window, maxRetention >= largest window),
  collecting ALL violations.
- FanOutResolver (FR-18): one event fans out across multiple subjects (primary +
  dimension-derived) and aggregations; SUM without value / DISTINCT without its
  dimension are SKIPPED with a reason, not silently dropped.
- DimensionHasher (FR-38/R11): HMAC-SHA256(namespace salt, value) -> opaque 32-byte
  DistinctMember; raw dimension values never stored. NamespaceSaltProvider is an
  interface (KMS-backed impl is a follow-up).
- VelocityEngine: record()/query()/capabilities()/purge()/reload(). record groups
  intents by backend, dispatches to the right mix-in (missing mix-in -> FAILED
  PerFeature, never a throw), stamps definitionVersionHash, and returns ApplyResult
  (read-your-write). reload() validates then ATOMICALLY swaps a namespace's
  definitions (FR-17/29); no matching definition -> empty result (FR-4).
- No Dagger/Dropwizard (DI belongs to the service tier). Money is BigDecimal cents
  end-to-end (P3).

Deferred (noted as follow-ups): Micrometer metrics (NFR-11), a KMS-backed salt
provider, non-primary hot-reload sources (OQ-C).

39 tests driving the real InMemoryVelocityBackend; coverage 91.9% line / 88.1%
branch (gate 80/70, core pinned 80 line). Validated by the change-validator agent:
VERDICT APPROVE. Added a symmetric DISTINCT-dimension-absent skip test per its one
non-blocking note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wolpert
wolpert enabled auto-merge July 19, 2026 00:54
@wolpert
wolpert merged commit 50d0901 into main Jul 19, 2026
3 checks passed
@wolpert
wolpert deleted the velocity-core branch July 19, 2026 00:56
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