diff --git a/.template-version b/.template-version index 9075be4..dc1e644 100644 --- a/.template-version +++ b/.template-version @@ -1 +1 @@ -1.5.5 +1.6.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d00c3d..3bb768d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Versioning: [SemVer](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [1.6.0] — 2026-08-23 + ### Changed - `app_core` is now a STATIC library (was INTERFACE) and the billing module is de-inlined into it — non-template bodies of `billing/Wallet`, @@ -66,6 +68,9 @@ Versioning: [SemVer](https://semver.org/spec/v2.0.0.html). shared helpers, dead includes dropped, hot-path copies removed (JWT claims moved not copied, request bodies handled as views). 49 files, behavior bit-for-bit. +- Dead template surface resolved: undocumented unused Auth/Messaging helpers removed; `CrudBase` owned-templates, `Database::execute_transaction`/`execute_write_idempotent` and friends are now instantiated by tests (which immediately exposed that the owned-templates required the templated `from_row` convention); `Validation::uuid` reuses the single UUID implementation. +- The app_core de-inline is complete: 22 more header/body pairs (all controllers, email, security, storage, observability, cache, Migrations), headers down 62%; the email↔jobs include cycle is gone from the header plane. +- CI sanitizers (ASan+UBSan and TSan) now cover the integration+api buckets against real Postgres/Redis, not just unit — the first instrumented run caught and fixed a latent heap-use-after-free (range-for over a temporary json). ### Added - Billing SPA pages — `/billing` (wallet, packages, top-up), `/billing/return`, @@ -108,6 +113,21 @@ Versioning: [SemVer](https://semver.org/spec/v2.0.0.html). adjustment notice) routed through the generic `email.send` job — delivery can never affect the money path. Full OpenAPI coverage and api/integration test suites included. +- `Billing::spend` — idempotent credit spending with a partial unique reference index (migration 009) and typed `InsufficientCredits`; the wallet test suite gains a randomized 300-op invariant check (`SUM(ledger) == cached balance` after every step). +- Changelog fragments: a PR now records its changelog entry as its own file + `changelog.d/..md` (type ∈ added|changed|fixed|removed|security, + format: `changelog.d/README.md`) instead of competing for the same + `[Unreleased]` lines — `scripts/release.sh` folds the fragments in and + retitles `[Unreleased]` itself, `./scripts/assemble-changelog.sh --check` + gates fragment format in CI (fragments stay optional, direct `[Unreleased]` + edits remain legal), and the gate selftest grows to 20 planted breakages. +- `.devcontainer` runs on the prebuilt `builder:cache` image — the native `make test-local` inner loop works minutes after opening (warm re-run ~7 s). `Makefile` build jobs are now RAM-capped (`JOBS`), fixing OOM kills on 8 GiB Docker VMs. +- The e2e suite validates every real JSON response against the OpenAPI schemas (dependency-free subset validator + committed spec JSON with a freshness hash); the first pass fixed the spec's missing `status` field, three long-standing YAML flow-map errors, four undocumented statuses and the absent `Post` schema. +- Nightly libFuzzer runs over the four byte-facing parsers (traceparent, decimal-cents, config placeholder expansion, path matching) with round-trip oracles and a growing cached corpus; harnesses are std-only by design — no vcpkg needed. +- `init-project.sh --with-orgs` (installs the multi-tenancy kit post-rename) and `--minimal` (strips the content module via `remove-content-module.sh` with the full gate battery green on the cut tree); self-verification fixed on pristine trees (#57). +- Opt-in transactional outbox (`Outbox::enqueue` in the caller's transaction + `SKIP LOCKED` drain into the jobs queue, migration 010, `outbox.drain_interval_sec` default off) for events whose loss after commit is unacceptable; decision guide in CONVENTIONS gotcha 20. +- Releases ship SPDX SBOMs and cosign keyless signatures/attestations for all three images; a nightly guard re-hashes the pinned Swagger UI assets against the CDN, and Renovate watches the pin without automerging it. +- `scripts/sync-upstream.sh` + a `.template-version` stamp: degit forks with no shared git history can pull template releases as three-way patches with honest conflict markers (`.template-sync-ignore` excludes renamed paths). ## [1.5.5] — 2026-08-21 diff --git a/CMakeLists.txt b/CMakeLists.txt index aa87dfd..6ea8f0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.21) -project(cpp_api_template VERSION 1.5.5 LANGUAGES CXX) +project(cpp_api_template VERSION 1.6.0 LANGUAGES CXX) # Release builds stamp the git tag in instead of the baked value above: # docker/Dockerfile declares ARG APP_VERSION and forwards it as diff --git a/changelog.d/billing-spend.added.md b/changelog.d/billing-spend.added.md deleted file mode 100644 index 02e81a0..0000000 --- a/changelog.d/billing-spend.added.md +++ /dev/null @@ -1 +0,0 @@ -`Billing::spend` — idempotent credit spending with a partial unique reference index (migration 009) and typed `InsufficientCredits`; the wallet test suite gains a randomized 300-op invariant check (`SUM(ledger) == cached balance` after every step). diff --git a/changelog.d/changelog-fragments.added.md b/changelog.d/changelog-fragments.added.md deleted file mode 100644 index 3b1e8d9..0000000 --- a/changelog.d/changelog-fragments.added.md +++ /dev/null @@ -1,7 +0,0 @@ -Changelog fragments: a PR now records its changelog entry as its own file -`changelog.d/..md` (type ∈ added|changed|fixed|removed|security, -format: `changelog.d/README.md`) instead of competing for the same -`[Unreleased]` lines — `scripts/release.sh` folds the fragments in and -retitles `[Unreleased]` itself, `./scripts/assemble-changelog.sh --check` -gates fragment format in CI (fragments stay optional, direct `[Unreleased]` -edits remain legal), and the gate selftest grows to 20 planted breakages. diff --git a/changelog.d/dead-surface.changed.md b/changelog.d/dead-surface.changed.md deleted file mode 100644 index efa3915..0000000 --- a/changelog.d/dead-surface.changed.md +++ /dev/null @@ -1 +0,0 @@ -Dead template surface resolved: undocumented unused Auth/Messaging helpers removed; `CrudBase` owned-templates, `Database::execute_transaction`/`execute_write_idempotent` and friends are now instantiated by tests (which immediately exposed that the owned-templates required the templated `from_row` convention); `Validation::uuid` reuses the single UUID implementation. diff --git a/changelog.d/deinline-rest.changed.md b/changelog.d/deinline-rest.changed.md deleted file mode 100644 index 0b72ff7..0000000 --- a/changelog.d/deinline-rest.changed.md +++ /dev/null @@ -1 +0,0 @@ -The app_core de-inline is complete: 22 more header/body pairs (all controllers, email, security, storage, observability, cache, Migrations), headers down 62%; the email↔jobs include cycle is gone from the header plane. diff --git a/changelog.d/devcontainer.added.md b/changelog.d/devcontainer.added.md deleted file mode 100644 index 217ec1b..0000000 --- a/changelog.d/devcontainer.added.md +++ /dev/null @@ -1 +0,0 @@ -`.devcontainer` runs on the prebuilt `builder:cache` image — the native `make test-local` inner loop works minutes after opening (warm re-run ~7 s). `Makefile` build jobs are now RAM-capped (`JOBS`), fixing OOM kills on 8 GiB Docker VMs. diff --git a/changelog.d/e2e-openapi.added.md b/changelog.d/e2e-openapi.added.md deleted file mode 100644 index 64de2aa..0000000 --- a/changelog.d/e2e-openapi.added.md +++ /dev/null @@ -1 +0,0 @@ -The e2e suite validates every real JSON response against the OpenAPI schemas (dependency-free subset validator + committed spec JSON with a freshness hash); the first pass fixed the spec's missing `status` field, three long-standing YAML flow-map errors, four undocumented statuses and the absent `Post` schema. diff --git a/changelog.d/fuzzing.added.md b/changelog.d/fuzzing.added.md deleted file mode 100644 index 3e2069c..0000000 --- a/changelog.d/fuzzing.added.md +++ /dev/null @@ -1 +0,0 @@ -Nightly libFuzzer runs over the four byte-facing parsers (traceparent, decimal-cents, config placeholder expansion, path matching) with round-trip oracles and a growing cached corpus; harnesses are std-only by design — no vcpkg needed. diff --git a/changelog.d/init-options.added.md b/changelog.d/init-options.added.md deleted file mode 100644 index b74cfa9..0000000 --- a/changelog.d/init-options.added.md +++ /dev/null @@ -1 +0,0 @@ -`init-project.sh --with-orgs` (installs the multi-tenancy kit post-rename) and `--minimal` (strips the content module via `remove-content-module.sh` with the full gate battery green on the cut tree); self-verification fixed on pristine trees (#57). diff --git a/changelog.d/outbox.added.md b/changelog.d/outbox.added.md deleted file mode 100644 index 3ed8969..0000000 --- a/changelog.d/outbox.added.md +++ /dev/null @@ -1 +0,0 @@ -Opt-in transactional outbox (`Outbox::enqueue` in the caller's transaction + `SKIP LOCKED` drain into the jobs queue, migration 010, `outbox.drain_interval_sec` default off) for events whose loss after commit is unacceptable; decision guide in CONVENTIONS gotcha 20. diff --git a/changelog.d/sanitizers-integration.changed.md b/changelog.d/sanitizers-integration.changed.md deleted file mode 100644 index 3fc126c..0000000 --- a/changelog.d/sanitizers-integration.changed.md +++ /dev/null @@ -1 +0,0 @@ -CI sanitizers (ASan+UBSan and TSan) now cover the integration+api buckets against real Postgres/Redis, not just unit — the first instrumented run caught and fixed a latent heap-use-after-free (range-for over a temporary json). diff --git a/changelog.d/supply-chain.added.md b/changelog.d/supply-chain.added.md deleted file mode 100644 index e5bcc4a..0000000 --- a/changelog.d/supply-chain.added.md +++ /dev/null @@ -1 +0,0 @@ -Releases ship SPDX SBOMs and cosign keyless signatures/attestations for all three images; a nightly guard re-hashes the pinned Swagger UI assets against the CDN, and Renovate watches the pin without automerging it. diff --git a/changelog.d/sync-upstream.added.md b/changelog.d/sync-upstream.added.md deleted file mode 100644 index eaa1b58..0000000 --- a/changelog.d/sync-upstream.added.md +++ /dev/null @@ -1 +0,0 @@ -`scripts/sync-upstream.sh` + a `.template-version` stamp: degit forks with no shared git history can pull template releases as three-way patches with honest conflict markers (`.template-sync-ignore` excludes renamed paths). diff --git a/helm/cpp-api/Chart.yaml b/helm/cpp-api/Chart.yaml index 02e5bdd..ecfcb6f 100644 --- a/helm/cpp-api/Chart.yaml +++ b/helm/cpp-api/Chart.yaml @@ -3,6 +3,6 @@ name: cpp-api description: A Helm chart for deploying the C++ REST API application type: application version: 0.1.0 -appVersion: "1.5.5" +appVersion: "1.6.0" maintainers: - name: cpp-api-team diff --git a/helm/cpp-env/Chart.yaml b/helm/cpp-env/Chart.yaml index 18f540b..7bb1154 100644 --- a/helm/cpp-env/Chart.yaml +++ b/helm/cpp-env/Chart.yaml @@ -6,7 +6,7 @@ description: >- and Jaeger — auto-configured, with *..example.com ingress. type: application version: 0.1.0 -appVersion: "1.5.5" +appVersion: "1.6.0" maintainers: - name: cpp-api-team # Subcharts are wired entirely from this chart's values (fixed in-namespace diff --git a/helm/cpp-env/values-demo.yaml b/helm/cpp-env/values-demo.yaml index 4827644..e51d15b 100644 --- a/helm/cpp-env/values-demo.yaml +++ b/helm/cpp-env/values-demo.yaml @@ -70,7 +70,7 @@ demoReset: cpp-api: image: - tag: "1.5.5" # pinned known-good amd64 build (unprefixed — release.yml strips the v); bump when a newer one ships + tag: "1.6.0" # pinned known-good amd64 build (unprefixed — release.yml strips the v); bump when a newer one ships externalKafka: enabled: false externalDatabase: @@ -114,7 +114,7 @@ cpp-api: cpp-worker: image: - tag: "1.5.5" + tag: "1.6.0" externalDatabase: host: postgresql-rw.db.svc.cluster.local name: cpp-api-demo @@ -143,7 +143,7 @@ cpp-worker: cpp-frontend: image: - tag: "1.5.5" + tag: "1.6.0" # Admin "Open trace" deep-links into the cluster Grafana (Tempo datasource # uid `tempo`) — traces are owner-visible behind Grafana auth, not public. traceUi: "https://grafana.tarassov.me/explore?schemaVersion=1&panes=%7B%22a%22%3A%7B%22datasource%22%3A%22tempo%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22query%22%3A%22{traceId}%22%2C%22queryType%22%3A%22traceql%22%7D%5D%7D%7D&orgId=1" diff --git a/helm/cpp-env/values-stage.yaml b/helm/cpp-env/values-stage.yaml index 8ba016e..65f7495 100644 --- a/helm/cpp-env/values-stage.yaml +++ b/helm/cpp-env/values-stage.yaml @@ -31,15 +31,15 @@ baseDomain: tarassov.me cpp-api: image: - tag: "1.5.5" + tag: "1.6.0" cpp-frontend: image: - tag: "1.5.5" + tag: "1.6.0" cpp-worker: image: - tag: "1.5.5" + tag: "1.6.0" app: baseUrl: https://app.stage.tarassov.me worker: diff --git a/helm/cpp-env/values.yaml b/helm/cpp-env/values.yaml index 7e7565f..2d33b5d 100644 --- a/helm/cpp-env/values.yaml +++ b/helm/cpp-env/values.yaml @@ -102,7 +102,7 @@ cpp-api: # NOTE: release.yml strips the tag's leading v — GHCR carries 1.5.3, not # v1.5.3. A v-prefixed pin here pulls a tag that does not exist # (downstream bump: cyber-accountant 80996a7). - tag: "1.5.5" + tag: "1.6.0" pullPolicy: Always nodeSelector: kubernetes.io/arch: amd64 @@ -162,7 +162,7 @@ cpp-worker: replicaCount: 1 image: repository: ghcr.io/moveeeax/cpp-rapid-rest-template-worker - tag: "1.5.5" # unprefixed — see the cpp-api image note + tag: "1.6.0" # unprefixed — see the cpp-api image note pullPolicy: Always nodeSelector: kubernetes.io/arch: amd64 @@ -217,7 +217,7 @@ cpp-frontend: replicaCount: 1 image: repository: ghcr.io/moveeeax/cpp-rapid-rest-template-frontend - tag: "1.5.5" # unprefixed — see the cpp-api image note + tag: "1.6.0" # unprefixed — see the cpp-api image note pullPolicy: Always nodeSelector: kubernetes.io/arch: amd64 diff --git a/helm/cpp-frontend/Chart.yaml b/helm/cpp-frontend/Chart.yaml index b659d52..9208210 100644 --- a/helm/cpp-frontend/Chart.yaml +++ b/helm/cpp-frontend/Chart.yaml @@ -3,6 +3,6 @@ name: cpp-frontend description: A Helm chart for the C++ REST template React SPA (nginx + /api proxy) type: application version: 0.1.0 -appVersion: "1.5.5" +appVersion: "1.6.0" maintainers: - name: cpp-api-team diff --git a/helm/cpp-worker/Chart.yaml b/helm/cpp-worker/Chart.yaml index 2452391..97d54c4 100644 --- a/helm/cpp-worker/Chart.yaml +++ b/helm/cpp-worker/Chart.yaml @@ -3,6 +3,6 @@ name: cpp-worker description: A Helm chart for deploying the C++ background job worker type: application version: 0.1.0 -appVersion: "1.5.5" +appVersion: "1.6.0" maintainers: - name: cpp-api-team