Repository navigation
feat(reporting): serve reporting subscriptions from ReliableReportingService - #1725
Merged
Merged
Conversation
…ng ledger A principal's reporting subscription is now executable by the ledger. The engine's frozen owed sets become ordinary obligations of one configuration generation per account, owning principal, subscription_id, version and resolved offering_id, aligned with the TypeScript SDK. - ReportingConfigurationGenerationKey gains an optional offering_id, set only for subscription generations. ledger_asdict omits it when unset, so every digest, work key and persisted scope of an account generation is byte-identical to before; a guard test forbids bare asdict in reporting. - SubscriptionObligationPlanner writes sparse, immutable obligations for periods ending by the engine's horizon, then advances the owed set's completeness horizon. Stores opt in through ReportingSubscriptionLedger; the in-memory store implements it, and the planner refuses other stores. - The producer never grid-fills an owed set and reads an owed obligation only once its period has ended. Owed sets are not leased; the service runs them through run_configuration. - get_reporting_status reports offering_id per period, keeps one wire generation per id, version and purpose, and leaves scope_closed false while the scope ends after the owed set's horizon. - sync_reporting_status resolves a statement through the caller's owed obligation for its period and report definition; a period the owed set does not hold fails with INVALID_STATUS_PERIOD. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pshot The closed-payload allowlist in reporting_reconciliation_validate now names offering_id, so the required-schema manifest carries the function's new fingerprint, computed from a clean migration chain. The public API snapshot records ReportingSubscriptionGeneration and ReportingSubscriptionLedger. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The engine now persists its state through a compare-and-swap store, so frozen_owed_sets reads one snapshot of the principal's state, as owed_periods and subscription_coverage do, instead of the removed process-local lock. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…Service ReliableReportingService(reporting_subscriptions=...) now owes and serves a principal's reporting subscriptions. Without the option, capabilities, responses and worker turns are unchanged. - Each worker turn plans every listed principal's frozen owed set, then executes it with the account configurations. A subscription generation is installed through the same trusted configure path: account_context receives it with configuration.subscription set, and the service validates the source route, currency and advertised offering as for an account configuration. The horizon is the earlier of the engine's and the turn clock. - A subscription that cannot be planned is isolated, recorded in turn.subscription_errors and reported to planning_error_handler or the worker error handler; it never stops another subscription or an account configuration. - Capabilities gain configuration_tasks, the subscriptions block, standard_dimensions, the feature id and the principal section at the served release; callers below 3.3 see the pre-3.3 block, and adcp#8585 offering fields reach only 3.3.0-beta.2 callers. - get_reporting_status summary and periods views gain subscription_coverage. Ledger reads stay scoped to the caller, and resolve_principal is checked against the caller resolver. - The planner takes an install hook and resolves currency per generation; the engine exposes its offerings and status_retention_days. Production options, managed or receipt-reconciled subscription offerings and PostgreSQL owed sets are refused or not yet served. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean, additive, heavily tested reporting-ledger / subscriptions change.
What I checked:
- No critical/high findings (row 1 N/A). gated_paths is false (row 2 N/A). No deletions in high-risk reasons (row 3 N/A) — the only high-risk match is
src/adcp/decisioning/handler.py (modified), but the reviewer surfaced no medium-or-higher finding on it, so the modification is presumed safe (row 5 N/A). No findings at all, so rows 4/8 N/A. No prior decision (row 6 N/A). No no-auto-approve team match (row 7 N/A). Falls through to row 9 → approve. - Byte-stability:
ledger_asdictspecial-cases the 5-field generation key droppingoffering_idNone so pre-existing digests/scopes stay byte-identical; reconciliation allowlist+fingerprint repinned and guard test forbids bareasdict. NewReportingObligationRecord.offering_iddestabilizes no persisted digest. - Isolation fail-closes when
resolve_principaldisagrees; pg refuses subscription lookups so the planner rejects it. - Semver: new names additive; keyword-only
adcp_versionadded; snapshot updated;feat:is correct.
Non-blocking note (not a finding): planning adds host I/O at turn start with no per-call timeout and stopping() is checked only between items, so a hung adopter hook could block the turn. This mirrors the existing cooperative config path and the reviewer did not raise it to Medium — worth a follow-up but not a gate.
The high_risk flag is a heuristic LOOK signal only; the modified decisioning file carries no actionable concern. Approving per row 9.
This was referenced Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1695, the second of the remaining stacked PRs: service wiring. It stacks on #1723, so it contains that PR's commits. Review only
feat(reporting): serve reporting subscriptions from ReliableReportingService. It follows the design of adcontextprotocol/adcp-client#3518.What ships
ReliableReportingService(reporting_subscriptions=ReportingSubscriptionServiceOptions(engine=..., list_principals=..., resolve_principal=..., definitions=...)). Without the option, capabilities, responses and worker turns are unchanged; there is a test for this.Planning in each worker turn
SubscriptionObligationPlanner), then executes it with the account configurations. New obligations therefore run in the same turn.configure()'s path.account_contextreceives the generation withconfiguration.subscriptionset and returns the source route, currency and advertised offering. The service validates them exactly as for an account configuration: offering contract, required finality and the registered public offering. The only owed-set exception is the account-timezone equality check, because an owed set never derives periods from its schedule; each obligation carries its own period and source timezone.min(engine through, turn clock). Each source slice carries exactly that period's frozen media buys (tested, including a period with two buys).turn.subscription_errorsand reported toplanning_error_handler, or to the worker error handler if none is set.Capabilities, at the served release
configuration_tasksand the deprecatedconfiguration_task;subscriptionsblock;standard_dimensionson every offering;media_buy.reporting_subscriptionsfeature;resolved_adcp_versiontoinject_capabilities.validate()refuses:standard_dimensionson every offering);status_retention_daysdiffers from the service's.Status
get_reporting_statussummary and periods views gainsubscription_coverage[]for callers served 3.3 or later.sync_reporting_statusstay scoped to the caller'sconsumer_id, and a subscription generation'sconsumer_idis its principal'ssubject, so visibility is per principal:subscription_ideach see only their own periods;resolve_principalmust agree with the caller resolver; otherwise the read is refused.Deviations from the TypeScript service
runCyclewith anAggregateErrorwhen there is noonPlanningError. The Python service already isolates configuration and extension errors onReliableReportingTurnand reports them toworker_error_handler, so subscription failures follow that convention.resolveSubscriptionConfigurationhook. Python'saccount_contextresolver already receives theReportingConfiguration, so it returns the subscription generation's source settings through the existing trusted hook.definitionssupplies each offering's content-addressed report definition, which includes monetary units that are not on the wire offering.principal.changed, authorization changes).Not yet served
productionoptions (refused at construction).render_path_template, per-account destinations. These are the managed-delivery PR.This does not adopt beta.2.
Tests
tests/test_reporting_subscription_service.pycovers:All reporting suites pass locally. Locally, the wheel-building packaging tests could not run because the sandbox disk is full; CI runs them.
🤖 Generated with Claude Code