Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .claude/agents/change-validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
name: change-validator
description: >-
Validates a code change before it is declared done: confirms the tests are meaningful (not
coverage-padding) and that the code actually satisfies the requirement/ADR the change targets.
Invoke on every non-trivial code change in this repo, per CLAUDE.md and CONTRIBUTING.md's
Definition of Done. Read-only — it reviews and reports; it does not edit code.
tools: Read, Grep, Glob, Bash
---

You are the **change validator** for the Velocity Engine. You are a skeptical staff engineer
reviewing a change before it is allowed to be called "done." You do not fix code and you do not
rubber-stamp. Your job is to catch (a) tests that don't actually test, and (b) code that doesn't
actually meet its requirement.

You will be told **what the change is** and **which requirement/ADR it implements** (e.g. an
`FR-*`/`NFR-*` ID, an ADR number, or a §11 acceptance item). If that context is missing, ask for it
or infer it from the diff and the docs — but say which you did.

## Ground truth to check against

- `docs/requirements.md` — the spec (locked decisions §2/§2.1, FR/NFR, §11 acceptance, §15).
- `docs/adr/` — the architecture decisions. **The `velocity-spi` contract is frozen and
additive-only (NFR-17, ADR 0002/0003/0009).** A change that alters a published SPI type
non-additively is an automatic CHANGES-REQUESTED unless a new ADR justifies it.
- `CONTRIBUTING.md` → Definition of Done. `CLAUDE.md` → working model.

## What you verify

1. **The tests are real.** For each new/changed test: does it assert on actual behavior and outputs,
not just "did not throw"? Would it **fail if the implementation were wrong** (mentally mutate the
code — flip a condition, drop a write — would a test catch it)? Reject tautologies, assertions on
mocks instead of results, over-mocking that tests the mock, snapshot tests with no meaningful
oracle, and tests written only to move the coverage number.
2. **Edge and failure paths are covered.** Not just the happy path — boundaries, empty/one/many,
concurrency where the requirement claims atomicity/read-your-write, and the **failure results**
the contract requires (e.g. ADR 0009: a stalled backend returns a distinguishable
`UNAVAILABLE`/`DEADLINE_EXCEEDED`, never a silent `0` — is that actually tested?).
3. **The code meets its stated requirement.** Compare the implementation to the FR/NFR/ADR it
claims to satisfy. Flag partial implementations, silent deviations, and anything that violates a
locked decision (namespaces, server-clock, BigDecimal-cents, Dropwizard-only, HLL-tumbling-only,
etc.) or the frozen SPI shape.
4. **The Definition of Done is honestly met.** Tests exist for non-trivial code. The trivial-code
exemption is applied correctly: it is fine to have no tests for pure records/DTOs/getters — but
verify real logic was **not parked in an excluded package** (`**/dto/**`, `**/model/**`) to dodge
the coverage gate. If the change is a *feature*, verify an end-to-end/integration test exists (a
backend → the `velocity-testkit` TCK `*Scenarios`; the service → an HTTP/OpenAPI integration
test), not only unit tests.
5. **It builds and the gate passes.** Run the relevant `./gradlew` target (e.g.
`./gradlew :velocity-core:build` or `clean build test`) and confirm success **and** that
`jacocoTestCoverageVerification` is actually satisfied (≥80% line / ≥70% branch on the affected
module), not skipped. Report the real output tail. If the sandbox blocks the build, say so.

## Output

End with a single verdict line: **`VERDICT: APPROVE`** or **`VERDICT: CHANGES REQUESTED`**.

Above it, a prioritized, specific list of findings — each with `file:line`, what's wrong, and the
requirement/ADR it violates or the missing test. No generic advice, no praise padding. If you would
approve, still list any non-blocking suggestions separately. If you cannot verify something
(missing context, build blocked), say so explicitly rather than assuming it passed.
24 changes: 16 additions & 8 deletions .github/workflows/auto-dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,30 @@ jobs:
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

# Auto-merge EVERY Dependabot update unconditionally — patch, minor, and major bumps, across
# both the gradle and github-actions ecosystems. The job-level `if:` above already restricts
# this workflow to Dependabot's own PRs; `--auto` still waits for the required CI checks, so a
# PR only lands once the build is green and branch protection is satisfied. The `metadata` step
# is retained for its logging/annotations even though no step gates on its output.
# Auto-merge only low-risk bumps: patch and minor version updates. Major version bumps — and
# ANY github-actions update (a privileged, secret-adjacent supply-chain surface; these actions
# run with `contents: write` and would sit next to publish/signing creds once a release
# workflow lands) — require a human, because a green CI run executes the dependency's build-time
# code but does not establish that a newly published release is trustworthy. For a grouped PR,
# `update-type` reflects the highest bump in the group, so a group containing a major is held.
#
# NOTE: this merges major version bumps and GitHub Actions updates (a privileged supply-chain
# surface) on a green CI alone, without human review — an explicit, deliberate choice for this
# repo. Your required status checks (CI) are the only gate.
# `--auto` still waits for the required CI checks, so an eligible PR lands only once the build
# is green and branch protection is satisfied.
- name: Enable auto-merge
if: |
(steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor') &&
steps.metadata.outputs.package-ecosystem != 'github-actions'
run: gh pr merge --auto --squash --delete-branch "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Approve PR
if: |
(steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor') &&
steps.metadata.outputs.package-ecosystem != 'github-actions'
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Changelog

All notable changes to the Velocity Engine are recorded here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

The project is pre-1.0. The `0.x` line is a single pre-stable development series:
the `velocity-spi` interfaces + DTOs and the OpenAPI contract are compatibility
surfaces (requirements §15/NFR-17), but they are **not yet frozen** and may
change before 1.0. No versions have been released to Maven Central yet.

## [Unreleased]

The project is in its **initial design and scaffolding** phase. The
specification and the Tier-1 architecture are settled; functional implementation
is just beginning. The modules are compiling skeletons — there is no working
counting code, no benchmarks, and no published artifacts yet.

### Added

- **Requirements specification** ([`docs/requirements.md`](./docs/requirements.md),
Draft v0.2) — functional and non-functional requirements, the locked scope
decisions (D1–D8) and resolved parameters (P1–P18), the architecture and SPI
design (§6), and the review-driven revisions that shape the SPI (§15).
- **Tier-1 Architecture Decision Records** ([`docs/adr/0001`–`0008`](./docs/adr/)) —
the decisions that gate freezing the published `velocity-spi` contract: record
ADRs, `velocity-spi` as a standalone contract module, SPI capability mix-ins,
the mandatory conformance TCK, HLL restricted to tumbling windows, the
distinct exact→HLL threshold and merge rule, read-your-write as a declared
capability, and the seed/backfill contract.
- **Gradle multi-module skeleton** — Phase 1 modules `velocity-spi`,
`velocity-core`, `velocity-api`, and `velocity-testkit` as compiling stubs,
with `build-logic` convention plugins, the `gradle/libs.versions.toml` version
catalog, and nmcp → Sonatype Central Portal publishing wiring, mirroring the
sibling `../pk-auth` project.
- **Project governance documentation** — `README.md`, `GOVERNANCE.md`,
`SECURITY.md`, `CONTRIBUTING.md`, and this changelog (requirements GR-2/GR-3).

### Notes

- Nothing is published to Maven Central yet; the current version is
`0.1.0-SNAPSHOT`.
- v1 will ship two production backends — Postgres (JDBI, tumbling reference) and
Redis (sliding, hot-path reference); see the roadmap in
[`GOVERNANCE.md`](./GOVERNANCE.md) and the v1 acceptance criteria in
requirements §11.

[Unreleased]: https://github.com/codeheadsystems/velocity-engine/commits/main
42 changes: 35 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ Dependency versions are centralized in `gradle/libs.versions.toml` (add new libs
the header `// SPDX-License-Identifier: BSD-3-Clause` (spotless adds it via `spotlessApply`).
Library modules compile with `-Xlint:all -Werror` + Error Prone, so warnings fail the build.
- **Coverage gates** (`jacocoTestCoverageVerification`, part of `check`): library modules floor at
LINE ≥70% / BRANCH ≥55%; `velocity-core` raises LINE to ≥80%. Raise floors as coverage improves; a
module only restates limits it *raises*.
**LINE ≥80% / BRANCH ≥70%**; a module may restate a *higher* limit but never lower. Trivial/generated
code (records/DTOs, `**/dto/**`, `**/model/**`, generated DI) is excluded from the denominator, so
the floor applies to logic. See the working model below and `CONTRIBUTING.md` → Definition of Done.
- **Spotless must run fresh** — it is marked incompatible with the configuration cache (a
google-java-format/Guava class-loading bug). Seeing "Configuration cache entry discarded … spotless…"
is expected, not an error. The build cache is also disabled globally in `gradle.properties` for the
Expand All @@ -94,12 +95,39 @@ Dependency versions are centralized in `gradle/libs.versions.toml` (add new libs
for stub modules; `isFailOnError = false` in `velocity.java-conventions` keeps the build green. This
self-resolves once a module gains public types — do not "fix" it by weakening anything else.

## Working model — how to write code here (required)

`CONTRIBUTING.md` → **Definition of Done** is binding for every code change. In short: tests ship
with the code; **≥80% line / ≥70% branch** on the affected module (a hard build gate); **no tests for
trivial code** (records/DTOs/getters and generated code are excluded from coverage — do not pad, and
do not hide real logic in an excluded package); and **a feature is not done until it has an
end-to-end/integration test** (a backend → the `velocity-testkit` conformance TCK; the service → an
HTTP/OpenAPI integration test).

For any **non-trivial** change, follow this loop before reporting the work complete:

1. Write the implementation **and** its tests together (unit tests for logic; the e2e/TCK test for a
feature).
2. Run `./gradlew clean build test` (or the affected module's `build`) and confirm it is green,
including `jacocoTestCoverageVerification`. **Never report a change done on a red or skipped gate.**
3. **Invoke the `change-validator` sub-agent** (`.claude/agents/change-validator.md`) via the Agent
tool, telling it what the change is and which requirement/ADR (`FR-*`/`NFR-*`/ADR-N/§11 item) it
implements. It independently checks that the tests are *meaningful* (would fail if the code were
broken) and that the code actually satisfies that requirement and the frozen `velocity-spi`
contract (NFR-17, additive-only).
4. If it returns `CHANGES REQUESTED`, address the findings and re-validate. Only call the change done
after it returns `APPROVE` (or you have explicitly resolved each finding with the user).

Trivial, no-runtime-surface changes (docs, comments, a pure DTO/record addition, build-comment tweaks)
do not require the sub-agent — but a DTO addition still goes through the build. When in doubt, validate.

## CI / automation (`.github/`)

`ci.yml`'s **`build` job** (`./gradlew clean build test` on JDK 21) is the required status check.
Dependabot (`dependabot.yml`) runs **weekly on Tuesday** for the `gradle` and `github-actions`
ecosystems, grouped into one PR each. `auto-dependabot.yml` **auto-approves and auto-merges every
Dependabot PR** (all update types, both ecosystems) once CI is green — so CI is the *only* gate
protecting `main`; keep it meaningful. Auto-merge also needs three repo settings enabled (allow
auto-merge, allow Actions to approve PRs, and branch protection requiring the `build` check) — see the
header comment in `auto-dependabot.yml`.
ecosystems, grouped into one PR each. `auto-dependabot.yml` auto-approves and auto-merges only
**patch/minor** Dependabot PRs once CI is green; **major bumps and all github-actions updates are
held for human review** (a green build runs a dependency's build-time code but doesn't prove a new
release is trustworthy). Auto-merge also needs three repo settings enabled (allow auto-merge, allow
Actions to approve PRs, and branch protection requiring the `build` check) — see the header comment
in `auto-dependabot.yml`.
Loading
Loading