Repository navigation
Conversation
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — cross-SDK parity slice with one non-blocking medium.
What I checked:
- Public-surface semver: removing
ReportingSubscriptionServiceOptionsfrom the facade is non-breaking — it was added inadaba82a4within this same unreleased stack (no tag contains it) and remains importable viaadcp.reporting.advancedand.service_subscriptions, so thefeat:prefix holds. - The
update_subscription_status→record_subscription_readbackrename ships with a deprecated delegating alias, preserving backward compat. ReportingSubscriptionStateCorruptErrorstays aValueError, preserving existing catch semantics.- The 10k-page fail-closed roster cap replaces an unbounded
while True— correct direction. - high_risk is true only due to a
(modified)src/adcp/server/principal.py; no medium-or-higher finding lands on that file, so the modification is presumed safe (row 5 does not fire).
Medium findings:
- src/adcp/reporting/subscriptions/engine.py:1178 —
_account_buyswrong-shape guard raises a raw TypeError on a None/non-iterable host return instead of the typed SUBSCRIPTION_HOST_FACT_INVALID, and the branch is untested.
Decision: Single medium finding, category is not data-loss/schema/infra, high_risk is (modified)-only with no medium on the modified file, no gated paths, no team gate. Rows 1–8 do not fire; falls through to row 9 → approve. Worth tightening the typed error on engine.py:1178 in a follow-up, but it does not block.
Medium findings
- src/adcp/reporting/subscriptions/engine.py:1178 — _account_buys wrong-shape guard misses None/non-iterable returns
0f89d2a to
105089f
Compare
6bc162d to
5b116d5
Compare
105089f to
268789b
Compare
…rors and readback name Parity with adcp-client#3542, #3544 and #3546: - adcp.reporting.advanced is the documented home of the low-level building blocks, as @adcp/sdk/reporting/advanced is: the engine and its stores, the coverage function, calendar and selection, the capability builder, principal-section validation and the service planner hooks. adcp.reporting.subscriptions and service_subscriptions keep defining and exporting them. ReportingSubscriptionServiceOptions leaves the adcp.reporting facade (it was added there earlier in this stack, never released); the facade keeps SubscriptionOptions and PrincipalRef. - ReportingSubscriptionEngineError carries code and recovery for the engine's runtime failures, as TypeScript names them: - SUBSCRIPTION_STATE_CONFLICT: ReportingSubscriptionStateConflictError is now a subclass. - SUBSCRIPTION_STATE_CORRUPT: an unreadable state document; still a ValueError. - SUBSCRIPTION_ROSTER_INCOMPLETE: the roster walk is capped at 10,000 pages, and an unfinished one is refused, never used partially. - SUBSCRIPTION_HOST_FACT_INVALID: a host hook returned the wrong shape. - ReliableReportingConfigurationError gains config_field (TypeScript's configField), set on the seller configuration codes the SDK raises. - PrincipalService.update_subscription_status is renamed record_subscription_readback (TypeScript's recordSubscriptionReadback); the old name warns and delegates. Refs #1695 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5b116d5 to
f5c119d
Compare
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — TS-parity slice for the reporting subscriptions surface.
What I checked:
- Semver signal: clean. The removed facade symbol was never released, and the
record_subscription_readbackrename keeps a deprecated delegator, so no breaking public-API change ships under a non-breaking prefix. - High-risk flag (
src/adcp/server/principal.pymodified) — reviewer surfaced no medium-or-higher concern on that file, so the modification is presumed contract-preserving. gated_paths: falseandreview_decision: APPROVED— no gate obstacles.- No no-auto-approve team match.
Medium findings (1 — non-blocking):
- src/adcp/reporting/subscriptions/engine.py:1178 —
_account_buyswrong-shape guard misses None/non-iterable host returns; raises rawTypeErrorinstead of typedSUBSCRIPTION_HOST_FACT_INVALID(inconsistent with_list_accountssibling).
This is a single medium finding carried over from the prior approve. Decision table: no critical/high (row 1 n/a), gated_paths false (row 2 n/a), no (deleted) high-risk reason (row 3 n/a), the lone medium is not data-loss/schema/infra category (row 4 n/a), high-risk (modified) + medium would trigger row 5 — however this medium is on engine.py, not on the high-risk-matched principal.py file, so row 5 does not fire. Prior decision was approve, not escalate (row 6 n/a). No team gate (row 7 n/a). Fewer than three mediums (row 8 n/a). Falls through to row 9 → approve.
Medium findings
- src/adcp/reporting/subscriptions/engine.py:1178 — _account_buys wrong-shape guard misses None/non-iterable host returns
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — cross-SDK parity slice with 1 medium finding, no blocking issues.
What I checked:
- ReportingSubscriptionServiceOptions removal from the adcp.reporting facade confirmed non-breaking (never released earlier in this stack per PR body), so
feat:prefix is correct — no semver signal violation. - ReportingSubscriptionStateCorruptError's (EngineError, ValueError) MRO is valid and preserves ValueError catch compatibility.
record_subscription_readbackrename ships with a deprecating shim.- New public export
advancedre-export module added.
Medium findings:
- src/adcp/reporting/subscriptions/engine.py:1178 — _account_buys wrong-shape guard misses None/non-iterable returns (raises bare TypeError instead of typed SUBSCRIPTION_HOST_FACT_INVALID; path untested).
Decision path: No critical/high findings (row 1 n/a). gated_paths is false (row 2 n/a). high_risk is true but the only high-risk match (src/adcp/server/principal.py) is (modified) with no medium finding on it, and the sole medium is on engine.py — not a high-risk-path file, so row 5 does not fire. No deletions (row 3). The single medium is category code-quality/edge-case, not data-loss/schema/infra (row 4 n/a). Prior decision was approve, not escalate (row 6 n/a). No no-auto-approve team match (row 7 n/a). Only 1 medium finding (row 8 requires ≥3). Falls through to row 9 → approve.
Note: the engine.py:1178 medium carried over from the prior approve run and remains unresolved, but a single medium is below the blocking/comment threshold.
Medium findings
- src/adcp/reporting/subscriptions/engine.py:1178 — _account_buys wrong-shape guard misses None/non-iterable returns
…f the wrong shape Review follow-ups: - An authorized_at regression is counted and logged after the engine-state write, not inside the change, which reruns on a compare-and-swap conflict. Under contention one regression used to count and log once per attempt. - account_buys returning None, a non-sequence or a string now fails with SUBSCRIPTION_HOST_FACT_INVALID instead of a raw TypeError. - The managed path's page-cap comment covers buys only; the engine caps the roster walk. Refs #1695 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — follow-up pass clean.
This is a subsequent review on a prior approve decision. The one prior medium finding (_account_buys wrong-shape guard at engine.py:1178) has been resolved: engine.py:1193 now rejects non-Sequence returns before iterating, with test coverage for None/int/str/wrong-element. The regression-counting refactor additionally fixes a real double-count bug by moving counting/logging out of the _mutate CAS-retry closure into _report_regressions (called once post-write), validated by test_a_regression_is_counted_once_under_write_conflicts.
No current blocking or medium findings. high_risk is true only because src/adcp/server/principal.py is modified, but with no medium-or-higher concern on that file, the modification is presumed safe. gated_paths is false and review_decision is APPROVED. No no-auto-approve team match.
Decision table: rows 1–8 do not fire (no critical/high, gated_paths false, no deletions, no medium findings, no team gate, prior decision was approve not escalate). Falls through to row 9 → approve.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean pass, no blocking or medium findings.
Checked: this PR mirrors the TS SDK parity slice — adds adcp.reporting.advanced, the ReportingSubscriptionEngineError code/recovery hierarchy, config_field on seller configuration errors, and renames update_subscription_status to record_subscription_readback. The rename keeps a delegating DeprecationWarning wrapper, so there is no public-surface break; ReportingSubscriptionServiceOptions leaving the facade is an unreleased-stack change tracked by the public_api_snapshot.json fixture — feat: is the correct semver signal (no feat!:/BREAKING CHANGE: required). Operational readiness is sound: the roster walk is bounded (10k pages) and fail-closes on an unfinished walk, host facts are shape-validated (SUBSCRIPTION_HOST_FACT_INVALID), and the regression-counter refactor counts once under write conflicts (covered by the _ConflictingStore test).
Decision table: high_risk is true, but the only matching reason is src/adcp/server/principal.py (modified) with no medium-or-higher finding on it, so row 5 does not fire. gated_paths is false and review_decision is APPROVED. Prior decision was approve. No findings at any tier → falls through to row 9.
Summary
The cross-SDK parity slice, stacked on #1746. It mirrors what the TypeScript DX slices adcp-client#3542–#3546 add, in snake_case. Review only
feat(reporting): mirror the TypeScript SDK's advanced path, engine errors and readback name.windDownGrant(current?),REPORTING_WIND_DOWN_TASKSwind_down_grant(current=None),REPORTING_WIND_DOWN_TASKS(keepauthorized_at; same shared vectors)ReportingSubscriptionErrorwith the 7 vector codes; the 5 refusals carrydispatched: falseCoverageInputErrorwithdetail, includingCOVERAGE_BUDGET_EXCEEDEDPrincipalRef { tenantId, principalId }PrincipalRef(tenant_id, principal_id)owedSubscriptionPeriodsV1,Owed*typesowed_subscription_periods,OwedSubscriptionPeriod(deprecated aliases kept)engine.listSubscriptionPrincipalsengine.list_subscription_principals(deprecated alias kept)subscriptionswithSubscriptionOptionsReliableReportingService(subscriptions=SubscriptionOptions(...)); passing it withreporting_subscriptions=is refused@adcp/sdk/reporting/advancedadcp.reporting.advancedReportingSubscriptionEngineError(SUBSCRIPTION_STATE_CONFLICT,_STATE_CORRUPT,_ROSTER_INCOMPLETE,_HOST_FACT_INVALID)ReportingSubscriptionEngineErrorwithcodeandrecoveryPlatformConfigError.configFieldReliableReportingConfigurationError.config_fieldrecordSubscriptionReadbackPrincipalService.record_subscription_readback(update_subscription_statuswarns and delegates)This PR
adcp.reporting.advancedre-exports the building blocks, grouped as in #3542:ReportingSubscriptionServiceOptionsleaves theadcp.reportingfacade. It was added there earlier in this stack and never released. The facade keepsSubscriptionOptionsandPrincipalRef.ReportingSubscriptionEngineError(code, message)carriesrecovery(transient for conflict and roster, terminal for corrupt and host fact) anddispatched=None.ReportingSubscriptionStateConflictErrorbecomes its subclass.ReportingSubscriptionStateCorruptError, which is still aValueError.SUBSCRIPTION_HOST_FACT_INVALID.config_fieldis set on every seller configuration code the SDK raises:SubscriptionOptions.accounts;capability_offerings;storage;python -m adcp migratecommand.Where Python does not match, and why
PrincipalLifecycleError(code,recovery,dispatched,applied→appliedResponse). Python has no counterpart to rename. TS throws it from its principal lifecycle helper, and #3544 itself notes "Python has no principal lifecycle helper". Python's sellerPrincipalServicereturns failed responses instead of raising. The buyer'sPrincipalConfigurationError(inadcp.principal) is a different helper's error, with noappliedfield.PlatformConfigError's default codePLATFORM_CONFIG_INVALID. It is TS-only and not inerror-codes.json. Python keepscode=Nonefor uncoded configuration errors and sets a code only for the four spec codes.groupOwedSubscriptionPeriodsV1andtoExpectedReportingPeriodsV1. They have no Python equivalent because none is needed.owed_subscription_periodsalready returns one row per (version, offering, period), with its media buys grouped, andOwedSubscriptionPeriodalready is anExpectedReportingPeriod, so no conversion exists to rename.adcp.reporting.subscriptions. TS madereporting/advancedtheir only home. In Python,adcp.reporting.subscriptionsandadcp.reporting.service_subscriptionsare the defining modules, and the earlier PRs in this stack and the docs import from them. Deprecating every name there would emit warnings across the suite for no behavioral gain.adcp.reporting.advancedis the documented home; the old paths can be deprecated in a later beta if wanted.ReportingSubscriptionServiceOptions.list_principalskeeps its field name (see feat(reporting): use owed and list_subscription_principals, as the TypeScript SDK does #1746).Tests
tests/test_reporting_subscription_ts_parity.pycovers:config_field.Refs #1695
🤖 Generated with Claude Code