go-cart-actor's hand-rolled SMTP stack (SASL loginAuth/plainAuth, dial/
STARTTLS/auth/MAIL/RCPT/DATA wire code, MIME builders) is replaced by an
adapter over platform/email, which gains the multipart/mixed attachment
envelope ported from it. One SMTP implementation now serves both the
order/ledger senders and go-auth's AUTH_EMAIL_* flows; public surfaces
(SMTPEmailSenderConfig, NewSMTPEmailSender, EmailSender, EmailMessage)
are unchanged so all callers compile untouched. Bumps the go-cart-actor
pin to 7cad997.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Give go-auth mail delivery on the shared platform/email sender (same
transport contract as the order service's ORDER_EMAIL_*): AUTH_EMAIL_SENDER=
smtp|log with the AUTH_EMAIL_SMTP_* knobs. /forgot emails a single-use
AOF-backed reset link (no enumeration, throttled) and /reset sets a new
password, revoking sessions, refresh tokens and remaining reset links;
admin invite + password-reset email credentials, and the create_user /
set_user_password MCP tools match (revoking outstanding reset links too).
Compose/k8s default to the log sender so the flows are exercisable without
SMTP credentials. Committed together with the Auth MCP server, its client-
registry admin UI, and the mcp-servers doc row they build on.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
The remaining P3 tail is done:
- §13.5: three TOTP codes for the shared RFC secret computed by an
independent implementation (Python stdlib, anchored to RFC 4226 App D
counter 0 = 755224) at recorded wall-clock Unix seconds, pinned in
platform/auth TestInteropCapturePythonTOTP — the Go code must produce
identical codes at the same instants and VerifyWindow(0) must accept
each at its capture time (cross-implementation agreement).
- §14.4: a full WebAuthn ceremony through real Chrome (navigator.credentials
with a CDP virtual authenticator: ctap2/internal, resident key, UV,
ES256) against a live go-auth instance — registration + passwordless
login both completed via the RP handlers. The raw browser-produced
blobs (clientDataJSON, attestationObject, authenticatorData, signature)
are stored verbatim as a golden fixture and replayed by
TestInteropCaptureChromeRegistration/Assertion: the parser must decode
the real CBOR/COSE/authenticator-data, and the real ECDSA signature
must verify over SHA-256(authData ‖ clientDataJSON) with the parsed key.
Plan doc §13.5/§14.4 updated with the recorded values; P3 row marked
complete. Both new suites green.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The authenticator-app second factor (D4, plan §5.7): shared RFC 4226/6238
primitive in platform/auth/totp.go (±1 window, monotonic replay floor, §13
RFC vectors), per-user AES-GCM-wrapped secrets under AUTH_TOTP_WRAP_KEY,
hashed one-shot recovery codes, and the password → /totp challenge (or
recovery) → session browser flow with first-login enrollment (otpauth QR via
vendored qrcode-generator, two-code confirm, once-only recovery reveal).
AUTH_REQUIRE_TOTP defaults on; per-challenge throttling (R5). Covered E2E by
internal/oidc/totp_flow_test.go.
Passkeys (D6, plan §5.8): go-auth is its own WebAuthn RP — hand-rolled
CBOR/COSE subset parser (stdlib only), register/login ceremonies with
attestation:none, per-request RP ID + origin derived from the issuer host,
AOF-backed cred:<user>:<credID> store. The /admin UsersManager (invite,
disable, reset password, list/rename/delete passkeys, enroll in-browser) is
session + admin-role gated. WebAuthn vectors + ceremony negatives in
internal/webauthn + webauthn_flow_test.go.
Also: bare-host GET / now redirects to /login (the issuer root otherwise
404s in a browser).
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Add `auth api-key {set|clear}` and prefer a stored key over the device
token, so scripts authenticate with a permanent minted API key
(X-API-Key) instead of re-running the 12h device flow. Explicit flags
and env still win over persisted credentials. Also treat a zero-byte
config file as defaults (previously every command failed to parse), and
correct AGENTS.md, which claimed device tokens auto-refresh.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Deploys cmd/scheduling as a 3-replica actor cluster (shared RWX commerce-data
event logs, gRPC mesh via POD_IP + actor-pool label, edge /api/scheduling
pass-through, fail-closed mcpauth on admin/MCP). cart/checkout get
SCHEDULING_SERVICE_URL so line removal releases holds; backoffice gets
SCHEDULING_URL for the admin proxy. Platform seam: order.Line.Properties
carries booking_* fields through order.created; scheduling.booking_confirmed/
cancelled event types. Compose + prep-dev-data wire the schedules dir.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Implements steps 1-2 and the RBAC half of step 4 of
docs/plan-storefront-edge-config.md (storefront auth gating, generalised
to dynamic grain routes).
- platform/edgeconfig: Fragment/Reloader interfaces, atomic Writer
(all-or-nothing flush), HTTPReloader (dev), NullReloader,
KubeReloader (injected PodLister keeps platform stdlib-only).
go vet clean; tests pass under -race.
- deploy/reloader-lib.sh + nginx-reloader.sh: reload handler
(cksum -> nginx -t -> graceful reload) + CGI endpoint on :8082
(pod IP only) + 60s fallback poll. Include-only risk model: main
config never changes at runtime; broken fragments fail validation
and the previous config keeps serving.
- k8s: reloader wired into the nginx Deployment, writable cms-output
mount, fragment include line, SA/Role/RoleBinding for backoffice
pod listing; backoffice uses the new ServiceAccount.
- compose/dev: same handler on stock nginx:alpine, EDGE_RELOAD_PROVIDER=http
targeting nginx:8082 from backoffice.
- docs: plan-storefront-edge-config.md (supersedes earlier gating plans)
and the fixed-zones variant kept for drift analysis.
Error/Info helpers emit a structured log record AND touch the active span
(status + stack trace), so failures show up as red markers in Jaeger/Tempo
with correlated log lines. No-op safe when no span is active. First consumer:
go-cart-actor cmd/order.
Bumps go-cart-actor (profile-link recovery-contact backfill, capture
email sender, /carts/abandoned read API) and backoffice (Abandoned
carts panel, storefront recovery-contact wiring). Compose shares
CAMPAIGN_DIR between cart service and backoffice and sets
CART_EMAIL_SENDER=capture for dev. Plan doc:
docs/plan-abandoned-carts-visibility.md.
The key store holds only SHA-256 hashes + metadata (no plaintext keys) and is
read by other services in their own containers running as different non-root
uids. saveLocked wrote it 0600, so when the backoffice pod (running as root)
minted or revoked a key the file became root-owned 0600 — unreadable to the
ledger/cart services, freezing their mtime-reload snapshots: new keys 401'd
and revocations never took effect. Write 0644 so every reader reloads.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
platform/order + platform/event gain the sale.paid and refund.issued wire
contracts (per-rate VatBuckets, PSP provider, timestamps) that drive the
ledger's SHOP-/REFUND- vouchers (go-cart-actor 2b819bd). docs/status.md
records the shop-sales + PSP settlement stack (tickets 17 + 18); the
wayfinder ledger tracker gains the two closed tickets and their Decisions
entries. Bumps go-cart-actor + backoffice (PSP float stat).
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
The backoffice commerce build now merges the ledger service's full MCP
tool surface (/ledger-mcp) into /mcp via MountLedgerMCP — a JSON-RPC
bridge that forwards tools/call to the ledger service (single-writer
AOF preserved), gated by the shared MCP_API_KEY. Compose backoffice now
sets LEDGER_URL=http://ledger:8080.
Submodule bumps:
- backoffice: ledger MCP bridge + device approve-page sign-in gating
- slask-finder: safe returnTo after OIDC login + dep tidy
- go-cart-actor / go-image-gen / go-redirector / slask-tracking: dep updates
Infra: k8s order CHROME_CDP_URL pinned to the headless-shell ClusterIP
(DNS-rebinding guard) + shared invoice-issuer-config ConfigMap for the
invoice PDF seller block; platform go.mod refreshed (otel direct, x/sys).
docs/status.md notes the ledger tools on /mcp.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
The approve UI needs to show what a user code asks for and how long it
is valid; GrantInfo returns (scopes, secondsLeft, ok) without consuming
the grant (consumption stays one-shot in Token).
platform/auth gains the LocalJWT (HS256 mint/verify, AUTH_TOKEN_SECRET,
disabled when unset), the RFC 8628 DeviceFlow (start/approve/poll with
requested-scope gating) and KeyStore.ReloadIfChanged (mtime-based, so a
freshly minted key works across processes without a restart). One shared
store at /commerce/apikeys.json (AUTH_APIKEYS_FILE) now backs the
backoffice admin gate, the CMS API and every MCP endpoint; AUTH_TOKEN_SECRET
lets the companion app's device flow mint bearer tokens the same surfaces
accept. Submodule bumps: backoffice (AuthGate + device endpoints + mcp
scope), go-cart-actor (mcpauth sources), go-graph-cms-v2 (shared store +
token chain).
Also lands the still-pending receipt-ocr sidecar (previous pass): the
PP-OCR ONNX inference service + download-ocr-models.sh + k8s
deployment/initContainer, wired into compose and the ledger RECEIPT_OCR_URL,
with the extractor pairing fix riding the go-cart-actor bump.
Deploy: AUTH_APIKEYS_FILE/AUTH_TOKEN_SECRET/AUTH_DEVICE_BASE_URL on
backoffice + cart/profile/order/ledger in compose and k8s, /device ingress
route, stack-secrets AUTH_TOKEN_SECRET optional ref, docs updated.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Restore the per-batch visibility lost in the cart migration: applyDelta now logs
'delta applied epoch=N (+U/-D, overlay=X)', and the snapshot swap line includes
'snapshot' so it's findable. Deltas were applying silently, which read as 'nothing
happened' on a bulk catalog update (only the debounced snapshot swap was logged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Generic SnapshotWriter[T]/SnapshotReader[T] over a value-agnostic raw layer;
SnapshotHolder[T] now RWMutex-guards munmap vs in-flight Get (race-free swap,
TestSnapshotHolder_SwapRace) — the drain grace is now just close-batching.
- ProjectionStore[T]: snapshot begin|chunk|end assembly → atomic swap, epoch-gated
delta overlay, tombstones, atomic epoch sidecar (ordered before the swap),
cold-start reopen, mandatory ProjectionTransform[T]. Future-epoch deltas are
dropped by contract (recover via next snapshot), not buffered.
- Shared bus-framing constants so producer + consumer agree.
- Bump slask-finder / go-cart-actor / backoffice pointers; refresh status.md +
catalog-projection.md to the finished state.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>