Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
131 commits
Select commit Hold shift + click to select a range
73c073e
docs: phase 9b entry review
Mujhtech Jul 28, 2026
3951615
docs: phase 9b stage 1 plan pending owner decisions
Mujhtech Jul 28, 2026
638baaa
docs: refine 9b plan decisions with verified evidence
Mujhtech Jul 28, 2026
2231b83
docs: add customer creation model and token deviation note to 9b plan
Mujhtech Jul 28, 2026
693815b
docs: installation alias as evidence-only with analytics attribution …
Mujhtech Jul 28, 2026
07f3945
docs: make fact-shape pass an explicit blocking prerequisite
Mujhtech Jul 28, 2026
d5da4bb
docs: owner approved all phase 9b stage 1 decisions
Mujhtech Jul 28, 2026
a126500
fix: correct 9A defect B1 - keep state-derived Google fact kind under…
Mujhtech Jul 28, 2026
0daf5bb
fix: correct 9A defect B2 - voided Google one-time purchases produce …
Mujhtech Jul 28, 2026
bc1faf0
fix: correct 9A defect B7 - never date a fact with worker wall-clock
Mujhtech Jul 28, 2026
cb190b7
feat: fact-shape pass - persist provider fields under validator versi…
Mujhtech Jul 28, 2026
e3926e9
feat: phase 9b schema - customers, lineages, projection, grants, enti…
Mujhtech Jul 28, 2026
9d13c4f
feat: pure subscription and entitlement projection engines
Mujhtech Jul 28, 2026
40db045
feat: billing customer identity, association resolver, and lineage se…
Mujhtech Jul 28, 2026
40d9b0d
protocol: add Authoritative Entitlement Contract v1 as a draft
Mujhtech Jul 28, 2026
360618f
protocol: add Customer Access Token Contract v1 as a draft
Mujhtech Jul 28, 2026
0010ad2
protocol: add Billing State Webhook Contract v1 as a draft
Mujhtech Jul 28, 2026
33a80e0
protocol: register the Phase 9B draft contracts in validation and policy
Mujhtech Jul 28, 2026
6bf546a
feat: authoritative projection transaction, job scheduling, and replay
Mujhtech Jul 28, 2026
e57b084
docs: ADR-0024 webhook signing and destination SSRF policy
Mujhtech Jul 28, 2026
8b1e267
protocol: apply orchestrator ratifications to the Phase 9B drafts
Mujhtech Jul 28, 2026
0d35ad2
feat: projection persistence, worker job family, and webhook secret r…
Mujhtech Jul 28, 2026
1a191fd
feat: restore sync jobs and access-surface schema alignment
Mujhtech Jul 28, 2026
ff083c5
docs: amend 9b plan pause placement and as-landed migration inventory
Mujhtech Jul 28, 2026
2129c31
fix: correct the blocking projection defects from the batch-1 review
Mujhtech Jul 28, 2026
dd068a2
fix: checkpoint prefix detection, position encoding, timeline identit…
Mujhtech Jul 28, 2026
9bf5e47
feat: customer access tokens, SDK entitlement sync, and trusted entit…
Mujhtech Jul 28, 2026
129b450
feat(billing): emit contract-shaped billing state webhook events from…
Mujhtech Jul 28, 2026
645d75f
feat(billing): add projection health surface, 9B table row metrics, a…
Mujhtech Jul 28, 2026
5a5ece0
test(billing): pin the planned webhook event shape and the no-change …
Mujhtech Jul 28, 2026
0b76b86
docs(backend): document the projection health operation in the OpenAP…
Mujhtech Jul 28, 2026
f2569a6
docs(privacy): state what a billing webhook delivery may and may not …
Mujhtech Jul 28, 2026
94c75cf
fix: correct review finding I-10 and add the trusted billing-identity…
Mujhtech Jul 28, 2026
22432fe
docs(adr): correct ADR-0024's signed-payload literal to the frozen co…
Mujhtech Jul 28, 2026
8904bd8
docs(backend): document the billing identity operations in the OpenAP…
Mujhtech Jul 28, 2026
dbc6cf1
docs(backend): document the billing identity APIs and the conflict re…
Mujhtech Jul 28, 2026
a831dcc
fix(billing): correct 9B review finding I-14.1 - a Google quantity_pa…
Mujhtech Jul 28, 2026
c3314aa
fix(billing): correct 9B review finding I-4 - an unattributable Googl…
Mujhtech Jul 28, 2026
3df4457
fix(billing): correct 9B review finding I-12 - honour the selected re…
Mujhtech Jul 28, 2026
6d9ee81
docs(billing): state 9B review finding I-13 - the FactDigest v2 dupli…
Mujhtech Jul 28, 2026
5e98f68
fix(billing): correct 9B review findings I-14.2 and I-14.3 - pairwise…
Mujhtech Jul 28, 2026
51a0fd3
feat: phase 9b WP18 restore and sync service, worker, and status endp…
Mujhtech Jul 28, 2026
c4231b9
feat(billing): wire the restore and sync surfaces into the API, route…
Mujhtech Jul 28, 2026
f307c9f
feat: application webhook destinations, signing, SSRF policy, and del…
Mujhtech Jul 28, 2026
2bc60ff
docs(backend): document the restore and sync operations in the OpenAP…
Mujhtech Jul 28, 2026
61976c2
feat: PostgreSQL persistence for Phase 9B billing identity
Mujhtech Jul 28, 2026
205ac03
feat(billing): expose bounded projection replay and wire the identity…
Mujhtech Jul 28, 2026
3f026ee
docs(backend): document the webhook and projection replay operations …
Mujhtech Jul 28, 2026
73c51f0
docs(backend): document webhooks, restore, projection replay, and pro…
Mujhtech Jul 28, 2026
74b19d5
fix: correct 9A cursor defect - encode keyset cursors at microsecond …
Mujhtech Jul 28, 2026
76dd04b
chore: reserve migration version 41 as an explicit no-op
Mujhtech Jul 28, 2026
5073f8f
fix: align one-time refund scope with the ratified partial-refund dec…
Mujhtech Jul 28, 2026
c768a2d
test: cover webhook delivery crash recovery, worker races, and replay…
Mujhtech Jul 28, 2026
993922f
feat: add Product-to-Entitlement Grant Version management endpoints
Mujhtech Jul 28, 2026
6c1f8cd
chore(dashboard): regenerate the API client against the phase 9b surface
Mujhtech Jul 28, 2026
10fa9bd
feat(dashboard): frozen authoritative entitlement vocabulary
Mujhtech Jul 28, 2026
fc9fda9
feat(dashboard): versioned entitlement grants, retiring the mutable c…
Mujhtech Jul 28, 2026
4dff585
feat(dashboard): projection health and bounded replay, and revise the…
Mujhtech Jul 28, 2026
130db27
feat(api): add session-authenticated Phase 9B billing operator surface
Mujhtech Jul 28, 2026
0a57db0
docs(api): document the Phase 9B billing operator surface
Mujhtech Jul 28, 2026
45043a8
fix(api): prefix the operator billing schemas so they stop re-declari…
Mujhtech Jul 28, 2026
1489d43
fix(api): correct spec-vs-code drift on the trusted billing surface
Mujhtech Jul 28, 2026
8b1375a
chore(dashboard): regenerate the API client against the 9B operator s…
Mujhtech Jul 28, 2026
1c8b472
feat(dashboard): billing customer search, list, and entitlement expla…
Mujhtech Jul 29, 2026
e987be5
feat(dashboard): billing customer detail and the five subscription st…
Mujhtech Jul 29, 2026
6253e96
feat(dashboard): subscription detail with its immutable timeline
Mujhtech Jul 29, 2026
3752116
feat(dashboard): identity conflicts and the multi-step resolution form
Mujhtech Jul 29, 2026
beea52c
feat(dashboard): restore status, customer routes, nav, and the bounda…
Mujhtech Jul 29, 2026
2d4beda
feat(ios): authoritative entitlement models with shared vector confor…
Mujhtech Jul 29, 2026
f26402d
feat(android): authoritative entitlement models and the vector-pinned…
Mujhtech Jul 29, 2026
8176126
feat(flutter): read Authoritative Entitlement v1 records with vector …
Mujhtech Jul 29, 2026
567996f
feat(ios): closed decoder and canonical serialization for entitlement…
Mujhtech Jul 29, 2026
df099e3
feat(android): strict authoritative entitlement codec and canonical d…
Mujhtech Jul 29, 2026
7896123
feat(flutter): hold Customer Access Tokens in memory with single-flig…
Mujhtech Jul 29, 2026
509b2cb
feat(android): customer access token provider and single-flight token…
Mujhtech Jul 29, 2026
fa17f9f
feat(ios): customer token provider and memory-only token store
Mujhtech Jul 29, 2026
c0eb5cd
feat(flutter): cache accepted entitlement snapshots per customer, bac…
Mujhtech Jul 29, 2026
3543f7b
feat(android): backup-excluded per-customer entitlement snapshot store
Mujhtech Jul 29, 2026
5f3ba9a
feat(ios): backup-excluded per-customer entitlement cache store
Mujhtech Jul 29, 2026
f13ca7c
feat(android): customer entitlement sync transport with conditional r…
Mujhtech Jul 29, 2026
475e2df
feat(flutter): sync authoritative entitlements with an atomic accepta…
Mujhtech Jul 29, 2026
af579f0
chore(flutter): drop redundant test imports
Mujhtech Jul 29, 2026
69db64b
feat(ios): entitlement sync client, transport, and update fan-out
Mujhtech Jul 29, 2026
09a149f
feat(android): authoritative entitlement runtime, acceptance gate, an…
Mujhtech Jul 29, 2026
323d6f4
fix(ios): emit transaction observations on the StoreKit restore path
Mujhtech Jul 29, 2026
357b638
feat(flutter): stage restore and authoritative sync into one honest r…
Mujhtech Jul 29, 2026
9c625ff
feat(android): restore-and-sync composition and debounced purchase re…
Mujhtech Jul 29, 2026
82c2f2c
feat(ios): multi-stage restore and authoritative sync
Mujhtech Jul 29, 2026
aef6fcd
feat(flutter): wire authoritative entitlements into the Mosaic client
Mujhtech Jul 29, 2026
bd7928e
feat(ios): wire authoritative entitlements into Mosaic.configure
Mujhtech Jul 29, 2026
cc6e898
feat(android): wire authoritative entitlements into configuration and…
Mujhtech Jul 29, 2026
293242f
docs(flutter): document authoritative entitlements and the backend re…
Mujhtech Jul 29, 2026
e5b47ae
feat(flutter-example): add the authoritative entitlements customer tab
Mujhtech Jul 29, 2026
d8f5ffc
docs(android): document authoritative entitlements and wire the examp…
Mujhtech Jul 29, 2026
c9b9ef8
feat(ios): entitlement lifecycle, example panel, and documentation
Mujhtech Jul 29, 2026
25d9d19
docs(protocol): pin the SDK sync form and the absent-key reader rule
Mujhtech Jul 29, 2026
5ad6821
feat(ios): POST the sync request envelope and align cross-SDK entitle…
Mujhtech Jul 29, 2026
1d2178a
fix(flutter): slide entitlement freshness only from the canonical unc…
Mujhtech Jul 29, 2026
a169de7
fix(android): apply ratified sync flow, unknown for absent keys, and …
Mujhtech Jul 29, 2026
2d5455e
fix(api): negotiated entitlement sync answers the unchanged record, n…
Mujhtech Jul 29, 2026
1849580
test(api): phase 9b demonstration fixtures, tenant, and destination stub
Mujhtech Jul 29, 2026
fa036ff
test(api): drive the fourteen phase 9b demonstrations from the billin…
Mujhtech Jul 29, 2026
890bbfa
docs: record phase 9b stage 4 demonstration evidence and five defects
Mujhtech Jul 29, 2026
79cf69c
fix(api): D-3 — mount the billing and billing-operator surfaces witho…
Mujhtech Jul 29, 2026
5d46f98
fix(api): D-5 — remove the unreachable conditional-GET 304 branch
Mujhtech Jul 29, 2026
b2c127c
fix(api): D-2 — join restore facts to lineages by chain digest and st…
Mujhtech Jul 29, 2026
fda704f
fix(api): D-4 — a customer snapshot is only ever computed from all of…
Mujhtech Jul 29, 2026
b011ec8
fix(api): D-1 (part) — key Purchase Lineages in the fact digest domain
Mujhtech Jul 29, 2026
52e694a
test(api): re-run the 9B demonstration driver against the fixed wiring
Mujhtech Jul 29, 2026
134a47e
docs: record the Stage 4 defect-pass verdicts and the D-0 operational…
Mujhtech Jul 29, 2026
fcb35aa
feat(api): D-1 — wire the Phase 9A to 9B seam
Mujhtech Jul 29, 2026
2846297
test(api): prove the 9A to 9B seam end to end, and the purchase-ancho…
Mujhtech Jul 29, 2026
dae33a1
docs: document the 9A to 9B seam and record D-1 as fixed
Mujhtech Jul 29, 2026
157c3c1
docs(protocol): correct the sync read shape and document the customer…
Mujhtech Jul 29, 2026
78775d5
feat(flutter): bind transaction observations to the customer token at…
Mujhtech Jul 29, 2026
4454792
feat(ios): bind transaction observations to the customer at send time
Mujhtech Jul 29, 2026
bdeb0ec
feat(android): bind transaction observations to the identified Billin…
Mujhtech Jul 29, 2026
1ca5d90
fix(android): read only an already-held customer token on the observa…
Mujhtech Jul 29, 2026
46c1ca1
chore: untrack accidentally committed worker binary
Mujhtech Jul 29, 2026
c309a9a
docs: record the 9b/9c webhook split in the roadmap per OD-1(b)
Mujhtech Jul 29, 2026
07ef19a
fix(billing): number the never-projected placeholder snapshot version 0
Mujhtech Jul 29, 2026
6de995f
docs: correct stage 5 protocol review findings L1-L5, product finding…
Mujhtech Jul 29, 2026
7424ea0
feat: narrow webhook stateSummary.accessState to active, inactive, un…
Mujhtech Jul 29, 2026
15adbf0
fix(protocol): define the never-projected entitlement snapshot
Mujhtech Jul 29, 2026
00df7b3
fix(api): harden billing customer association and adoption
Mujhtech Jul 29, 2026
1c2dea4
fix(dashboard): complete billing customer recovery workflows
Mujhtech Jul 29, 2026
662ad93
fix(flutter): support never-projected entitlement snapshots
Mujhtech Jul 29, 2026
9c574fe
fix(ios): support never-projected entitlement snapshots
Mujhtech Jul 29, 2026
689810b
fix(android): support never-projected entitlement snapshots
Mujhtech Jul 29, 2026
fc7a2cd
test(api): exercise trusted identity conflict authority
Mujhtech Jul 29, 2026
3315cb3
docs: close phase 9b with tracked follow-ups
Mujhtech Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .claude/agents/mosaic-backend.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: mosaic-backend
description: Owns Mosaic's Go modular-monolith backend, REST APIs, persistence, publishing, telemetry, and workers. Use for any change under apps/api, apps/worker, or migrations.
model: claude-fable-5
model: claude-opus-5
---

You are the Mosaic backend owner.
Expand Down
2 changes: 1 addition & 1 deletion .claude/agents/mosaic-dashboard.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: mosaic-dashboard
description: Owns Mosaic Studio and dashboard using TanStack Start, Tailwind CSS, shadcn/ui, and Base UI. Use for any change under apps/dashboard or frontend documentation.
model: claude-fable-5
model: claude-opus-5
---

You are the Mosaic dashboard owner.
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
.env
xcuserdata/
*.xcuserstate

# compiled binaries
apps/api/worker
apps/api/api
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Added

- **iOS SDK: authoritative customer entitlements** (Authoritative Entitlement
Contract v1, draft). `Mosaic.configure` gains an optional
`customerTokenProvider`; a new `MosaicCustomer…` surface reports what Mosaic
has validated for a Billing Customer, alongside — never replacing — the
existing provider-observed entitlement surface, which is unchanged.
`checkCustomerEntitlement(_:)`, `customerEntitlementSnapshot()`,
`customerEntitlementUpdates()`, `refreshCustomerEntitlements()`,
`customerEntitlementDiagnostics()`, `restoreAndSyncCustomerEntitlements()`,
and `clearCustomerState()`. Requires an application backend to mint Customer
Access Tokens; there is no anonymous mode. Tokens are memory-only, never
logged, never parsed. Bounded-grace offline policy with server-issued windows,
a per-customer backup-excluded cache, and the normative rule that any
rejection yields `unknown` and never `inactive`.

- Protocol schemas are embedded in the API binary (`go:embed`), so a released
image can no longer be built without them. Filesystem overrides still work for
operators pinning a schema; a drift test fails the build if an embedded copy
Expand All @@ -32,6 +46,25 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
(including the 304 path) and analytics ingestion.
- `healthcheck` probe binary, so the distroless image can answer container
healthchecks.
- iOS: transaction-observation submissions now carry the current Customer Access
Token in a `Mosaic-Customer-Token` header when one is held, binding an
identified user's purchase to their Billing Customer server-side. The token is
read at send time, never persisted with the queue, and never logged; without
one a submission still succeeds and anchors anonymously. The 9A observation
record is unchanged — this is transport-level only.
- iOS: `MosaicDiagnosticStage` gains `entitlementTransport`,
`entitlementValidation`, `entitlementCache`, `entitlementAuthentication`, and
`entitlementRestore`. This is source-breaking for a host that switches
exhaustively over the enumeration, which is accepted at `0.1.0-dev`.
- iOS: the StoreKit adapter now emits transaction observations on the **restore**
path from `Transaction.currentEntitlements`, not only on purchase. A
fresh-device restore previously submitted nothing, so a restored purchase was
never associated with a Billing Customer. Idempotent through the existing
acceptance store and submission-identifier de-duplication.
- iOS: the StoreKit acceptance store and the identity store are now excluded from
backup. Restoring either onto a second device corrupted behaviour that is
supposed to be per-install: duplicate-delivery suppression and installation
identity.
- Trusted-proxy middleware: `X-Forwarded-For`/`X-Real-IP` are honoured only from
a peer inside `MOSAIC_TRUSTED_PROXY_CIDRS` (default: none).
- Baseline rate limits for authenticated dashboard APIs and for Placement and
Expand Down
130 changes: 105 additions & 25 deletions apps/api/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ import (

"github.com/Mujhtech/mosaic/apps/api/internal/analytics"
"github.com/Mujhtech/mosaic/apps/api/internal/billing"
"github.com/Mujhtech/mosaic/apps/api/internal/billingaccess"
"github.com/Mujhtech/mosaic/apps/api/internal/billingcustomer"
"github.com/Mujhtech/mosaic/apps/api/internal/billingdiagnostics"
"github.com/Mujhtech/mosaic/apps/api/internal/billinggrant"
"github.com/Mujhtech/mosaic/apps/api/internal/billingoperator"
"github.com/Mujhtech/mosaic/apps/api/internal/billingprojection"
"github.com/Mujhtech/mosaic/apps/api/internal/billingrestore"
"github.com/Mujhtech/mosaic/apps/api/internal/billingwebhook"
"github.com/Mujhtech/mosaic/apps/api/internal/browserauth"
"github.com/Mujhtech/mosaic/apps/api/internal/cloudworkspace"
"github.com/Mujhtech/mosaic/apps/api/internal/experiment"
Expand All @@ -25,7 +33,17 @@ import (
"github.com/Mujhtech/mosaic/apps/api/internal/platform/appstorejws"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/appstoreserver"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/authn"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingaccesspostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingcustomerpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingdiagnosticspostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billinggrantpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingkeys"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingoperatorpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingprojectionpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingrestorepostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingseam"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/billingwebhookpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/browserauthpostgres"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/buildinfo"
"github.com/Mujhtech/mosaic/apps/api/internal/platform/cloudworkspacepostgres"
Expand Down Expand Up @@ -255,7 +273,15 @@ func run() (runErr error) {
analyticsEventLimiter := ratelimit.New(cfg.Analytics.KeyEventsPerMinute, cfg.Analytics.KeyEventBurst, cfg.Analytics.LimiterEntries)

var billingService *billing.Service
var billingIPLimiter, billingKeyLimiter *ratelimit.Limiter
var billingAccessService *billingaccess.Service
var billingDiagnosticsService *billingdiagnostics.Service
var billingGrantService *billinggrant.Service
var billingRestoreService *billingrestore.Service
var billingCustomerService *billingcustomer.Service
var billingOperatorService *billingoperator.Service
var billingProjectionService *billingprojection.Service
var billingWebhookService *billingwebhook.Service
var billingIPLimiter, billingKeyLimiter, entitlementSyncLimiter *ratelimit.Limiter
if cfg.Billing.Enabled {
billingCipher, err := providercredential.NewAESGCMCipher(cfg.Providers.CredentialKeyring, rand.Reader)
if err != nil {
Expand Down Expand Up @@ -287,12 +313,58 @@ func run() (runErr error) {
if err != nil {
return fmt.Errorf("configure Google Play client: %w", err)
}
billingIPLimiter = ratelimit.New(cfg.Billing.ObservationsPerMinute, cfg.Billing.ObservationBurst, cfg.Billing.LimiterEntries)
billingKeyLimiter = ratelimit.New(cfg.Billing.ObservationsPerMinute, cfg.Billing.ObservationBurst, cfg.Billing.LimiterEntries)
billingAccessService = billingaccess.NewService(
billingaccesspostgres.New(databasePool),
billingaccesspostgres.NewKeyAuthenticator(billingpostgres.New(databasePool)),
billingaccess.WithIssuer(cfg.Telemetry.ServiceName),
billingaccess.WithFreshness(billingaccess.Freshness{
RefreshAfter: cfg.Billing.EntitlementRefreshAfter,
ValidFor: cfg.Billing.EntitlementValidFor,
StaleGrace: cfg.Billing.EntitlementStaleGrace(),
}))
entitlementSyncLimiter = ratelimit.New(cfg.Billing.EntitlementSyncPerMinute,
cfg.Billing.EntitlementSyncBurst, cfg.Billing.LimiterEntries)
// The API process runs no projection jobs; it constructs the projection
// service only to enqueue triggers (identity movements) and to run
// bounded operator replays. Both go through the same command the worker
// runs, so there is no second write path.
projectionRepository := billingprojectionpostgres.New(databasePool)
billingProjectionService = billingprojection.NewService(projectionRepository)
billingDiagnosticsService = billingdiagnostics.NewService(
billingdiagnosticspostgres.New(databasePool),
billingdiagnostics.WithReplay(billingProjectionService, projectionRepository))
billingKeys := billingkeys.New(billingpostgres.New(databasePool))
billingRestoreService = billingrestore.NewService(
billingrestorepostgres.New(databasePool), billingKeys.Restore())
billingCustomerService = billingcustomer.NewService(
billingcustomerpostgres.New(databasePool), billingKeys.Identity(), billingProjectionService)
// The Phase 9A→9B seam. The ingestion service is constructed last
// because it depends on it: an observation submitted with a Customer
// Access Token records the association that lets a first purchase reach
// an identified customer, and a committed fact hands its lineage to the
// identity service. Without this the 9B read model is unreachable from a
// purchase, which was defect D-1.
billingService = billing.NewService(billingpostgres.New(databasePool), billingCipher, verifier,
billing.WithProviders(appleClient, googleClient),
billing.WithRetention(cfg.Billing.RawRetention()),
billing.WithNotificationBaseURL(cfg.Billing.NotificationBaseURL))
billingIPLimiter = ratelimit.New(cfg.Billing.ObservationsPerMinute, cfg.Billing.ObservationBurst, cfg.Billing.LimiterEntries)
billingKeyLimiter = ratelimit.New(cfg.Billing.ObservationsPerMinute, cfg.Billing.ObservationBurst, cfg.Billing.LimiterEntries)
billing.WithNotificationBaseURL(cfg.Billing.NotificationBaseURL),
billing.WithSeam(billingseam.New(billingCustomerService, billingAccessService),
billingseam.New(billingCustomerService, billingAccessService)))
billingGrantService = billinggrant.NewService(billinggrantpostgres.New(databasePool))
// The operator surface reads through the same repositories the trusted
// APIs read through, so the dashboard and an application backend see one
// answer derived once. Its own repository holds only the read model the
// dashboard needs and no writer at all.
billingOperatorService = billingoperator.NewService(
billingoperatorpostgres.New(databasePool),
billingaccesspostgres.New(databasePool),
billingCustomerService)
billingWebhookService = billingwebhook.NewService(
billingwebhookpostgres.New(databasePool), billingCipher,
billingwebhook.NewPolicy(billingwebhook.WithSelfHostedAllowlist(
cfg.Billing.WebhookAllowPrivateDestinations)))
}

readiness := health.NewReadiness(
Expand Down Expand Up @@ -320,27 +392,35 @@ func run() (runErr error) {
TrustedProxyCIDRs: cfg.HTTP.TrustedProxyCIDRs,
EnableHSTS: cfg.ProductionLike(),
}, logger, httpserver.Dependencies{
BrowserAuth: browserAuthService,
BrowserAuthConfig: browserauthhttp.Config{CookieSecure: cfg.BrowserAuth.CookieSecure, CookieDomain: cfg.BrowserAuth.CookieDomain, AllowedOrigins: cfg.HTTP.CORSAllowedOrigins, RateLimiter: authenticationLimiter},
CloudWorkspace: workspaceService,
HostedPublishing: publishingService,
PlacementDecision: placementDecisionService,
PrincipalResolver: authn.NewBrowserSessionResolver(browserAuthService),
DeliveryLimiter: deliveryLimiter,
Analytics: analyticsService,
AnalyticsIPLimiter: analyticsIPLimiter,
AnalyticsKeyLimiter: analyticsKeyLimiter,
AnalyticsEventLimiter: analyticsEventLimiter,
Experiment: experimentService,
Billing: billingService,
BillingIPLimiter: billingIPLimiter,
BillingKeyLimiter: billingKeyLimiter,
APILimiter: apiLimiter,
DecisionLimiter: decisionLimiter,
UploadLimiter: uploadLimiter,
ExportLimiter: exportLimiter,
Readiness: readiness,
ReadinessChecker: database.HealthChecker{Pinger: databasePool},
BrowserAuth: browserAuthService,
BrowserAuthConfig: browserauthhttp.Config{CookieSecure: cfg.BrowserAuth.CookieSecure, CookieDomain: cfg.BrowserAuth.CookieDomain, AllowedOrigins: cfg.HTTP.CORSAllowedOrigins, RateLimiter: authenticationLimiter},
CloudWorkspace: workspaceService,
HostedPublishing: publishingService,
PlacementDecision: placementDecisionService,
PrincipalResolver: authn.NewBrowserSessionResolver(browserAuthService),
DeliveryLimiter: deliveryLimiter,
Analytics: analyticsService,
AnalyticsIPLimiter: analyticsIPLimiter,
AnalyticsKeyLimiter: analyticsKeyLimiter,
AnalyticsEventLimiter: analyticsEventLimiter,
Experiment: experimentService,
Billing: billingService,
BillingAccess: billingAccessService,
BillingDiagnostics: billingDiagnosticsService,
BillingGrant: billingGrantService,
BillingRestore: billingRestoreService,
BillingCustomer: billingCustomerService,
BillingOperator: billingOperatorService,
BillingWebhook: billingWebhookService,
BillingIPLimiter: billingIPLimiter,
BillingKeyLimiter: billingKeyLimiter,
EntitlementSyncLimiter: entitlementSyncLimiter,
APILimiter: apiLimiter,
DecisionLimiter: decisionLimiter,
UploadLimiter: uploadLimiter,
ExportLimiter: exportLimiter,
Readiness: readiness,
ReadinessChecker: database.HealthChecker{Pinger: databasePool},
})

server := &http.Server{
Expand Down
Loading
Loading