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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ Skeleton (copy what you need):
reaching a Service past the gateway, a host without `/etc/hosts`) are
one-line `kubectl port-forward` snippets under setup.md § Prerequisites.

#### Services
- **The ADR-017 expand-phase aliases are gone** (payment-service v2.8.0,
shipping-service v1.10.0). These now answer 404: payment's
`/protected/attempts/open`, `/protected/reconciliations/runs[/:id]`,
`/internal/reconciliation/runs[/:id]` and `/public/webhooks/mockpay`, and
shipping's `/public/track` and `/public/estimate`. Both edges drop the
webhook alias match; e2e-audit A7 and the k6 smoke suite now expect the
shipping alias to be 404.

### Feature

#### GitOps
Expand Down Expand Up @@ -333,6 +342,10 @@ Skeleton (copy what you need):
and compose pins, one grouped PR per plugin.

#### Services
- **payment v2.8.0 (and mockpay) and shipping v1.10.0 on Kind** — the
ADR-017 contract releases that remove the expand-phase aliases. Both passed
the full local-stack release audit (A/B/C + C22) from scratch, with A7
asserting the shipping alias is 404.
- **payment v2.7.0 (and mockpay) and admin-service v0.4.3 on Kind.** payment
serves the canonical protected paths beside their deprecated aliases;
the Backoffice calls the canonical ones and its runtime image carries
Expand Down
6 changes: 3 additions & 3 deletions docs/api/microservices.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ availability vocabulary used by service At-a-glance tables.
| Promo contention, asymmetric upstream errors, and parked confirm recovery | checkout | Accepted operational trade-offs | [checkout known gaps](./checkout.md#known-gaps) |
| Committed-stock cancellation and workflow-start terminal failures | order | Accepted or alerted trade-offs | [order known gaps](./order.md#known-gaps) |
| Real provider delivery, send idempotency, and unused SMS/HTTP twins | notification | Current limitations and no-caller surfaces | [notification known gaps](./notification.md#known-gaps) |
| Deprecated aliases, unpersisted destination, and demo quote math | shipping | Migration debt and accepted limitations | [shipping known gaps](./shipping.md#known-gaps) |
| Deprecated aliases, direct DB connection, reconciliation limits, and single-replica constraint | payment | Migration and scaling constraints | [payment known gaps](./payments.md#known-gaps) |
| Unpersisted destination and demo quote math | shipping | Migration debt and accepted limitations | [shipping known gaps](./shipping.md#known-gaps) |
| Direct DB connection, reconciliation limits, and single-replica constraint | payment | Migration and scaling constraints | [payment known gaps](./payments.md#known-gaps) |
| Bounded review feed and write-once reviews | review | Accepted design limits | [review known gaps](./review.md#known-gaps) |
| Backoffice availability and static-delivery hardening | Backoffice | Current platform gaps | [Backoffice known gaps](../frontend/admin-portal/README.md#known-gaps) |

Expand All @@ -101,4 +101,4 @@ contracts; they are not ongoing-work rows here.
- [Workflow registry](./workflows.md)
- [Repository index](../README.md#repositories)

_Last updated: 2026-08-26 — removes duplicated deployment, route, RPC, and technique inventories; restores the catalog to feature ownership and current known-gap rollup._
_Last updated: 2026-10-02 — deprecated aliases are no longer a known gap for shipping or payment (ADR-017 contract). Previously 2026-08-26 — removes duplicated deployment, route, RPC, and technique inventories; restores the catalog to feature ownership and current known-gap rollup._
22 changes: 8 additions & 14 deletions docs/api/payments.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ reconciliation loop that proves the books match the provider.
| **Temporal** | Money-step gRPC participant | Implemented | [Temporal participation](#temporal-participation) |
| **Events** | Transactional outbox + signed inbound mockpay webhooks | Implemented | [Ledger + outbox](#ledger--outbox-settle-once-tell-everyone-at-least-once) · [Webhook HMAC](#mockpay--webhook-hmac) |

Known gaps: [aliases, pooler, and reconciliation limits](#known-gaps).
Known gaps: [pooler and reconciliation limits](#known-gaps).

| Attribute | Value |
|-----------|-------|
Expand Down Expand Up @@ -116,9 +116,9 @@ Private responses are owner-scoped by the JWT `user_id`. Internal routes are
never given an `HTTPRoute` at the edge; NetworkPolicy is the cluster boundary. The
public webhook is not anonymous in practice: its HMAC signature is the
credential (the edge's rate/size limits still apply — `BackendTrafficPolicy`).
The deprecated pre-v3 alias
`/payment/v1/public/webhooks/mockpay` stays mounted during the ADR-017 window
([Known gaps](#known-gaps)); shared conventions live in [api.md](./api.md).
Shared conventions live in [api.md](./api.md). The pre-v3 aliases
`/payment/v1/public/webhooks/mockpay` and `/payment/v1/internal/reconciliation/runs`
were removed in v2.8.0 (ADR-017 contract) and now answer 404.

## gRPC API

Expand Down Expand Up @@ -398,11 +398,9 @@ Read-only: refunds and recon triggers stay `internal`.
| `GET` | `/payment/v1/protected/payments/reconciliation/runs` | Run headers, newest first — the detect-only recon records' first reader |
| `GET` | `/payment/v1/protected/payments/reconciliation/runs/:id` | Run + its discrepancies (`{run, discrepancies}`) — the triage view |

**Deprecated aliases (ADR-017 expand phase, payment-service v2.7.0).** The
pre-canonical `GET /payment/v1/protected/attempts/open` and
`GET /payment/v1/protected/reconciliations/runs[/:id]` still answer on the same
handlers. They are removed in the contract release, after the Backoffice has
shipped on the canonical paths.
The pre-canonical `GET /payment/v1/protected/attempts/open` and
`GET /payment/v1/protected/reconciliations/runs[/:id]` served as aliases in v2.7.0
while the Backoffice moved over, and were removed in v2.8.0 (ADR-017 contract).

### mockpay provider API

Expand Down Expand Up @@ -436,10 +434,6 @@ mockpay posts its events back to payment's signed webhook,

## Known gaps

- **Deprecated webhook alias** `/payment/v1/public/webhooks/mockpay` — pre-v3
path kept at both edges during the ADR-017 window so in-flight mockpay
retries keep landing; remove at contract end. A matching deprecated internal
alias `/payment/v1/internal/reconciliation/runs` remains mounted in-service.
- **No pooler for payment DB** — direct CNPG connection with `sslmode=require`
because PgDog does not terminate TLS yet (RFC-0020 research).
- **Reconciliation limits (deliberate, tracked):** refund *amounts* aren't
Expand Down Expand Up @@ -545,4 +539,4 @@ Paths in [`duynhlab/payment-service`](https://github.com/duynhlab/payment-servic
- [workflows.md](./workflows.md) · [Service contracts](./README.md#service-contracts)
- [RFC-0010](../proposals/rfc/RFC-0010/) — full design; ADRs [007](../proposals/adr/ADR-007-double-entry-payment-ledger/) ledger · [008](../proposals/adr/ADR-008-mockpay-standalone-provider/) mockpay · [009](../proposals/adr/ADR-009-saga-authorize-early-capture-late/) auth-early/capture-late · [010](../proposals/adr/ADR-010-shared-idempotency-library/) idempotency · [011](../proposals/adr/ADR-011-detect-only-reconciliation/) detect-only · [012](../proposals/adr/ADR-012-reconciliation-auto-heal/) auto-heal

_Last updated: 2026-10-02 — protected attempt and reconciliation reads move under `payments/` (ADR-017 expand; old paths are deprecated aliases); mockpay provider API table added. Previously 2026-08-26 — adds evidence-backed capability and ownership summaries. Previously 2026-08-14 — RFC-0023 Train 3 shipped the protected Backoffice reads._
_Last updated: 2026-10-02 — the ADR-017 aliases (protected, internal reconciliation, webhook) are removed in v2.8.0. Previously 2026-10-02 — protected attempt and reconciliation reads move under `payments/` (ADR-017 expand; old paths are deprecated aliases); mockpay provider API table added. Previously 2026-08-26 — adds evidence-backed capability and ownership summaries. Previously 2026-08-14 — RFC-0023 Train 3 shipped the protected Backoffice reads._
10 changes: 4 additions & 6 deletions docs/api/shipping.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Shipping turns "an order that must move" into a tracked shipment — and turns "
| **Temporal** | Shipment-step gRPC participant | Implemented | [Temporal participation](#temporal-participation) |
| **Events** | None | None | — |

Known gaps: [no-caller route, aliases, and unpersisted address](#known-gaps).
Known gaps: [no-caller route and unpersisted address](#known-gaps).

| Attribute | Value |
|-----------|-------|
Expand Down Expand Up @@ -166,8 +166,8 @@ accepted for compatibility.
| estimate | `400` | `VALIDATION_ERROR` | Missing params, or weight not a positive finite number |
| both | `500` | `INTERNAL_ERROR` | Repository/infrastructure failure |

The deprecated pre-v3 paths `/shipping/v1/public/{track,estimate}` remain
temporary aliases during the ADR-017 expand phase (see [Known gaps](#known-gaps)).
The pre-v3 paths `/shipping/v1/public/{track,estimate}` were removed in v1.10.0
(ADR-017 contract) and answer 404.

## gRPC API

Expand Down Expand Up @@ -247,8 +247,6 @@ east-west gRPC surface is unauthenticated by design — the policy is the fence.
- **Internal HTTP twin — No caller.** `GET /shipping/v1/internal/shipments/orders/:orderId`
mirrors the gRPC lookup; order migrated to gRPC, so the route is kept documented
but has no live consumer.
- **Pre-v3 aliases.** `/shipping/v1/public/track` and `/shipping/v1/public/estimate`
are deprecated ADR-017 expand-phase aliases; removal lands with the contract phase.
- **`CreateShipmentRequest.address` not persisted.** Accepted for forward
compatibility; the shipment row stores no destination yet.
- **Estimate is demo math.** The public estimate is a deterministic formula, not a
Expand Down Expand Up @@ -302,4 +300,4 @@ Paths in [`duynhlab/shipping-service`](https://github.com/duynhlab/shipping-serv
- [checkout.md](./checkout.md) · [order.md](./order.md) — quote and enrichment callers
- [Service contracts](./README.md#service-contracts)

_Last updated: 2026-08-26 — adds evidence-backed capability and ownership summaries. Previously 2026-08-14 — RFC-0023 Train 3 shipped the protected Backoffice reads._
_Last updated: 2026-10-02 — the pre-v3 `/public/track` and `/public/estimate` aliases are removed in v1.10.0. Previously 2026-08-26 — adds evidence-backed capability and ownership summaries. Previously 2026-08-14 — RFC-0023 Train 3 shipped the protected Backoffice reads._
2 changes: 1 addition & 1 deletion kubernetes/apps/mockpay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ spec:
# still. Re-aligned as an ADR-053-train ride-along BEFORE the first Kind
# bring-up, because mockpay is on that path (the saga charges through it,
# and the F1 GameDay finding was precisely an image skew here).
tag: "2.7.0" # {"$imagepolicy": "flux-system:payment:tag"}
tag: "2.8.0" # {"$imagepolicy": "flux-system:payment:tag"}
pullPolicy: IfNotPresent
args: ["mockpay"]
service:
Expand Down
2 changes: 1 addition & 1 deletion kubernetes/apps/services/payment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ spec:
# registered is traced and logged as an ordinary 404 rather than vanishing.
# Compose E2E audit passed on this code before the tag; measured 0 -> 15 spans
# for /metrics and 0 for /health and /ready, unchanged.
image_tag: "2.7.0" # {"$imagepolicy": "flux-system:payment:tag"}
image_tag: "2.8.0" # {"$imagepolicy": "flux-system:payment:tag"}
namespace: payment
# RFC-0024 P3: authmw consumer — the domain template injects the explicit
# OIDC_ISSUER/OIDC_JWKS_URL pair (Keycloak realm; pkg v0.37.0 contract).
Expand Down
2 changes: 1 addition & 1 deletion kubernetes/apps/services/shipping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ spec:
# registered is traced and logged as an ordinary 404 rather than vanishing.
# Compose E2E audit passed on this code before the tag; measured 0 -> 15 spans
# for /metrics and 0 for /health and /ready, unchanged.
image_tag: "1.9.1" # {"$imagepolicy": "flux-system:shipping:tag"}
image_tag: "1.10.0" # {"$imagepolicy": "flux-system:shipping:tag"}
namespace: shipping
# Runs pkg/authmw (cmd/main.go) and serves /protected/, so both realm
# pairs belong in the manifest rather than in compiled defaults.
Expand Down
7 changes: 1 addition & 6 deletions kubernetes/infra/configs/envoy-gateway/routes/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ spec:
---
# Payment webhooks — public, anonymous: the HMAC signature over the raw body
# is the credential (mockpay signs, payment verifies). Rate/size limits still
# apply (btp-api.yaml). Both webhook paths are matched by one route.
# apply (btp-api.yaml).
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
Expand All @@ -639,11 +639,6 @@ spec:
- path:
type: PathPrefix
value: /payment/v1/public/payments/webhooks
# Deprecated pre-v3 path — kept during the v3 rollout so in-flight
# mockpay retries keep landing. Remove at contract (ADR-017).
- path:
type: PathPrefix
value: /payment/v1/public/webhooks
filters:
- type: ResponseHeaderModifier
responseHeaderModifier:
Expand Down
12 changes: 6 additions & 6 deletions local-stack/docs/e2e-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,17 +385,17 @@ docker compose exec -T postgres psql -U postgres -lqt </dev/null \
&& echo "A6 FAIL: the auth database still exists" \
|| echo "A6 OK: no auth database"

# A7. v3 collection-noun paths (ADR-017): new canonical 200 + deprecated
# aliases still answering during the expand phase (removed at contract).
# Shipping only. The `POST /auth/v1/public/login` alias that used to be
# A7. v3 collection-noun paths (ADR-017): the canonical paths answer 200 and
# the pre-v3 alias answers 404 — the contract step removed it, and a 200
# here would mean a forgotten alias is still serving. Shipping only. The `POST /auth/v1/public/login` alias that used to be
# checked here is not "deprecated but serving" — it certified the retired
# token layer, and with auth-service gone from local-stack there is no
# backend and no route behind it. Nothing to expand-phase.
audit_curl -s -o /dev/null -w "A7 shipments/track: %{http_code} (want 200)\n" \
"$BASE/shipping/v1/public/shipments/track?tracking_number=1Z999AA10123456784"
audit_curl -s -o /dev/null -w "A7 shipments/estimate: %{http_code} (want 200)\n" \
"$BASE/shipping/v1/public/shipments/estimate?origin=HN&destination=SG&weight=1"
audit_curl -s -o /dev/null -w "A7 alias track: %{http_code} (want 200 — deprecated)\n" \
audit_curl -s -o /dev/null -w "A7 alias track: %{http_code} (want 404 — removed at contract)\n" \
"$BASE/shipping/v1/public/track?tracking_number=1Z999AA10123456784"

# A8. Renamed zero-caller internal paths are gone (no aliases kept):
Expand Down Expand Up @@ -2189,7 +2189,7 @@ make -C .. e2e-conformance # from homelab/: stops Weaver, saves the rep
| A4 | Refresh reuse (realm) | refresh rotates; replaying the consumed token 400 `invalid_grant` / `Maximum allowed refresh token reuse exceeded`; the replay revokes the family, so the rotated token also 400s (`Session doesn't have required client`) |
| A5 | Logout (realm) | end-session 204, replay **also 204** (idempotent); refresh afterwards 400 `Session not active` |
| A6 | Removed surfaces | `/auth/v1/private/*` 404 (no HTTPRoute matches) **and the `auth` database does not exist** — auth-service is removed from local-stack, and RFC-0024 P5 retired its cluster surface |
| A7 | v3 paths (ADR-017) | new `shipments/*` paths 200 and the deprecated `shipping/v1/public/track` alias still 200 (expand phase). The old `auth/v1/public/login` alias is **not** checked — it certified the retired token layer and has no backend |
| A7 | v3 paths (ADR-017) | new `shipments/*` paths 200 and the removed `shipping/v1/public/track` alias 404 (ADR-017 contract). The old `auth/v1/public/login` alias is **not** checked — it certified the retired token layer and has no backend |
| A8 | Internal audience sealed | renamed `notify/*` + `internal/orders/*` 404 in-container (no aliases); and the two `/internal/` paths that DO exist — product create, cart clear — 404 **at the edge** because every HTTPRoute is audience-scoped, so no audience leaks |
| A9 | Checkout sessions (RFC-0015) | lifecycle **201**→200→200→200 through edge-JWT, with the create's 201 asserted (not just used for its id); no-token 401; `/api/v1/checkout` 404; price bump flags `price_changed` |
| A10 | Confirm + abandonment (RFC-0015 P2–P4) | fee/tax/promo composition asserted; `Idempotency-Key` required; replay = same order; order reaches `confirmed` or `completed`; order total == session total; lazy-410 past `expires_at` |
Expand All @@ -2200,7 +2200,7 @@ make -C .. e2e-conformance # from homelab/: stops Weaver, saves the rep
| A16 | String subject persisted (ADR-042) | a cart write made with a realm token lands in `cart.cart_items.user_id` as the caller's realm UUID — the edge, `pkg/authmw`, the handler, and the column all agree |
| A15 | Versioning drill (conditional) | deployment registers, workflow reports `Pinned` on the current build, the superseded version reports `draining`, and the teardown leaves no `Running` order-fulfillment workflow behind (A10's lazy-410 abandonment watch runs until its TTL) |
| A17 | Protected surface (RFC-0023 + ADR-050) | tokenless 401 **at the edge**; bare `/inventory/v1/private/*` 404 (only `/protected` is routed); a valid **customer-realm** token 401 **wrong-issuer at the edge** (the ADR-050 fence — stronger than the old in-service 403); staff operator `duyne` (realm `duynhlab-staff`) lists real balances with derived `atp`; receipt 201 `applied:true`, exact replay 200 `applied:false`, invariant-violating adjustment 409 `STOCK_UNAVAILABLE`; the movement row's `actor` is duyne's staff-realm `sub` (`d0e00000-…-001`) |
| A18 | Protected read fan-out (Train 3) | order/payment/shipping/user each answer the staff operator's list 200 **and** reject a customer-realm token 401 at the edge; payment's `reconciliations/runs` pages 200 |
| A18 | Protected read fan-out (Train 3) | order/payment/shipping/user each answer the staff operator's list 200 **and** reject a customer-realm token 401 at the edge; payment's `payments/reconciliation/runs` pages 200 |
| A19 | Protected catalog writes (slice B) | staff list 200 / customer token 401 at the edge; create lands **DRAFT** (v1) and 404s publicly; duplicate name 409; publish makes it public and a second publish is **409 `INVALID_TRANSITION`**; an edit at v2 succeeds and the same version again is **409 `VERSION_CONFLICT`**; archive 404s the page; the audit trail's newest action is `ARCHIVE` and every row's `actor_sub` is duyne's staff subject — a body-supplied actor is ignored; categories page 200 |
| A20 | Operator resolve (train 7 / ADR-051) | a real declined refund (total's cents `07`) parks the order in **`manual_review`** through the cancellation compensation, not through SQL; the case view carries `version`, the payment/reservation/shipment truths and the transition history, with `degraded` listing only what actually failed; a customer token is **401 wrong-issuer at the edge** on the command; an empty note and a reason from another command's vocabulary are both **400**; an illegal target is **409 `INVALID_TRANSITION`**; a version the order is not at is **409 `VERSION_CONFLICT`**; the decision itself is **201 `applied:true`**, an identical retry **200 `applied:false`** with no second history row, and a further resolve **409** (no longer parked); the `OPERATOR` history row carries `WRITTEN_OFF`, the note, and duyne's staff subject **even though the body named another actor** |
| A21 | Untracked SKU is a conflict, not an outage (ADR-053) | a published product with NO balance row carts fine, and session create answers **flat `409 ITEM_NOT_ORDERABLE`** with **no `Retry-After`** and an opaque body (the SKU ids stay in the log/span); after an operator receipt the SAME basket creates a session — the operator fix, not a retry, is what clears the state. The confirm arm's 409-with-requoted-session envelope is pinned by checkout-service's own contract tests on the same commit |
Expand Down
Loading
Loading