Implement velocity-core: the embeddable engine (first increment) - #7
Merged
Conversation
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
enabled auto-merge
July 19, 2026 00:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
velocity-core— the embeddable engine, and the first consumer of both the frozenvelocity-spicontract and the
velocity-testkitin-memory backend. This is a complete, end-to-end firstincrement: 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
FeatureDefinition+SubjectSource(PRIMARY|FromDimension)for dimension-derived fan-out (D5/FR-18);
FeatureDefinitionssnapshot with a deterministicversionHash(FR-40). YAML round-trip import/export (FeatureDefinitionYaml, FR-28).CapabilityValidator(FR-29) — validates a definition againstBackendCapabilities(aggregation, every window,
maxRetention≥ largest window), collecting all violations.FanOutResolver(FR-18) — onerecord()fans out across multiple subjects (primary +dimension-derived, e.g. per-IP) and aggregations;
SUMwithout a value /DISTINCTwithout itsdimension are
SKIPPEDwith a reason, not silently dropped.DimensionHasher(FR-38/R11) —HMAC-SHA256(namespace-salt, value)→ opaque 32-byteDistinctMember; raw dimension values are never stored.NamespaceSaltProvideris an interface(a KMS-backed impl is a follow-up, per R11).
VelocityEngine—record()/query()/capabilities()/purge()/reload().recordgroups intents by backend and dispatches to the right mix-in (a missing mix-in →
FAILEDPerFeature, never a throw), stampsdefinitionVersionHash, and returnsApplyResult(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-validatoragent (CLAUDE.md working model): VERDICT APPROVE — it confirmedfan-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:build→ BUILD SUCCESSFUL.🤖 Generated with Claude Code