@@ -108,33 +108,104 @@ asserted only in the request body.
108108
109109## PostgreSQL production wiring
110110
111+ The PostgreSQL factory installs ledger, source staging, and replay-seal schemas
112+ in the caller's pool. Ordinary adapter registrations use those durable stores;
113+ restart replay reads the sealed bytes without refetching the source. Open the
114+ pool before starting the service and close it after service shutdown settles.
115+ This is a change for existing ` postgres() ` callers, including those that do not
116+ enable ` production ` : ` initialize() ` now bootstraps ` reporting_inline_objects `
117+ and ` reporting_inline_seals ` in addition to the ledger schema. Before upgrading,
118+ ensure the pool's startup role can run that source-schema DDL. A DDL-restricted
119+ runtime role cannot use this factory's schema bootstrap until its deployment
120+ grants or startup sequence are updated.
121+
122+ Pass ` ReportingProductionOptions ` to compose the managed materializer, status
123+ projection, exact reads, consumer/receipt handlers, and optional signed
124+ notification workers. Adopters supply domain declarations and their actual
125+ destination, verifier registry, trusted account task and authorization callbacks.
126+ The SDK constructs the component graph:
127+
111128``` python
112129from psycopg_pool import AsyncConnectionPool
113- from adcp.reporting import ReliableReportingService
130+ from adcp.reporting import (
131+ ReliableReportingService,
132+ ReportingProductionOptions,
133+ ReportingServiceOffering,
134+ )
114135
115136pool = AsyncConnectionPool(DATABASE_URL , open = False )
116137await pool.open()
117138
118139reporting = ReliableReportingService.postgres(
119140 pool = pool,
120141 account_context = resolve_reporting_context,
121- caller_resolver = resolve_authenticated_caller,
122- worker_interval = timedelta(minutes = 1 ),
123- materialization_worker = managed_delivery_worker, # optional tier
124- notification_worker = notification_worker, # optional extension
125- notification_attempt_store = notification_attempts,
126- receipt_handler = receipt_handler, # optional tier
127- reconciled_billing = True ,
128- worker_error_handler = report_reporting_worker_error,
142+ consumer_status_enabled = True ,
143+ production = ReportingProductionOptions(
144+ offerings = (
145+ ReportingServiceOffering(
146+ adapter = " gam" ,
147+ offering = gam_public_offering, # ReportingDeliveryOffering
148+ profile = gam_execution_profile, # ProducerOfferings
149+ source_offering_id = gam_source_offering_id,
150+ verification_key = gam_verifier.key,
151+ ),
152+ ),
153+ destination = warehouse_provider, # ReportingProductionDestination
154+ registry = verifier_registry, # ReportingRevisionVerifierRegistry
155+ configuration_task = account_task, # ReportingProductionConfigurationTask
156+ resolve_account = authorize_reporting_account,
157+ ),
129158)
159+ reporting.sources.register(" gam" , gam_adapter)
160+ platform = reporting.install(platform)
161+ # Mount the returned handler on MCP/A2A before reporting.start().
162+ # Use reporting.start / reporting.close as the application's lifespan hooks.
130163```
131164
132- The PostgreSQL factory makes the ledger durable; it does not make the default
133- adapter staging or replay-seal stores durable. Production adapters should pass
134- durable ` staging= ` and ` seals= ` implementations to ` sources.register ` , or use
135- ` sources.register_executor ` for a custom executor and object reader. Committed revision rows are retained in the ledger for exact reads. Durable
136- staging and seals are needed to recover interrupted acquisitions and replay
137- previously sealed source results across a restart.
165+ Each registered production adapter needs at least one public offering. Multiple
166+ public offerings for an adapter share its fixed execution profile and verifier;
167+ different adapters may reuse a local source offering ID. Profiles include the
168+ source scope, currency, metric/dimension sets, and snapshot/official selections.
169+ Admission checks trusted account context against the selected profile and
170+ persists it once per generation. Restart recovery uses the stored route.
171+
172+ Managed adapters also implement the existing
173+ ` ReportingProductionSource.configuration_binding(configuration) ` method. Return
174+ the current source binding, including the exact media-buy/product mapping, or
175+ ` None ` when unauthorized. The wrapper forwards this callback before dispatch and
176+ again under the account lock before seal/publication. Revocation or remapping
177+ discards in-flight results; restoring the admitted mapping allows the next turn
178+ to retry. ** Revocation takes effect at the next dispatch or publish.** Adopters
179+ own any caching and latency inside the callback. A fetch already in progress is
180+ allowed to return. Buyer-facing feed and destination authorization still run on
181+ each request and session.
182+
183+ To enable signed push, set ` notifications=True ` and provide ` subscriptions ` ,
184+ ` cipher ` (` ReportingEnvelopeCipher ` ), and ` signing ` (` ReportingProductionSigning ` )
185+ on the options. All three are required together. The factory owns the outboxes,
186+ attempt storage and workers for source, materialization and status events.
187+ With only ` notifications=True ` , events are retained for polling without claiming
188+ push delivery. Consumer status is controlled by the service's
189+ ` consumer_status_enabled ` argument. Reconciled Billing follows the validated
190+ official offering, receipt method, and destination contracts; a flag cannot
191+ promote an unsupported provider. The memory factory accepts the same options
192+ for conformance, but never advertises durable Managed/Reconciled guarantees.
193+
194+ Use the typed ` sync_accounts ` admission callback for this composition, rather
195+ than ` configure() ` . Production workers belong to ` start() ` /` close() ` ; the Core
196+ ` run_worker() ` entry point is not used. See the
197+ [ production guide] ( reporting-production.md ) for account admission and provider
198+ contracts, and [ the wiring example] ( ../examples/reporting_service_production.py ) .
199+
200+ ## Advanced composition
201+
202+ ` postgres() ` without production options remains the Core factory. Advanced
203+ adopters can still inject workers and receipt handlers, pass explicit
204+ ` staging= ` /` seals= ` to ` sources.register ` , use ` constituent_of= ` for a custom row
205+ identity, or register a complete executor and object reader. Explicit stores are
206+ borrowed; initialize their schemas yourself. ` from_production() ` continues to
207+ own an already composed production graph. Keep injected components separate
208+ from ` production= ` options, which own that graph themselves.
138209
139210Managed delivery, notification, and receipt components are replaceable
140211extensions. Startup rejects combinations that cannot be advertised honestly,
0 commit comments