Repository navigation
fix(reporting): bound each seller read in reconcile; scrub account_requests[] - #1820
Merged
Merged
Conversation
reconcile(), explain(), poll()/events() and multi.reporting.reconcile() take read_timeout (timedelta, default 30 seconds, None for no bound; a non-positive or non-timedelta value is refused as INVALID_REQUEST before anything is read). It bounds each read the run makes (get_principal, get_adcp_capabilities, list_accounts, get_media_buys, get_reporting_status) and the receipts policy's sync_reporting_receipts write, mirroring the TS SDK's withReadTimeout and readTimeoutMilliseconds. A read that does not answer in time fails as TIMEOUT, naming read_timeout, and degrades the same way any other failed read does: a hung get_principal is SUBSCRIPTION_PRINCIPAL_UNAVAILABLE (a fleet issue for that seller), a hung account read fails that account. The read is cancelled and abandoned at the deadline, so a hung seller frees its fleet concurrency slot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… credential scrubber list_accounts scrubbed its accounts[] rows and then attached account_requests[] unscrubbed, and request_account's response skipped the scrubber entirely. A resolved AccountRequest carries a full Account, so its billing_entity.bank, governance_agents[].authentication and notification_configs[].authentication.credentials now get the same defense-in-depth strip as the rows beside them. request_account joins CREDENTIAL_BEARING_METHODS so the task registry and webhook boundaries scrub it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean two-commit follow-up with no blocking or Medium findings.
Checked:
fix(reporting): adds optionalread_timeout(default 30s,Nonedisables, non-positive/non-timedelta refused as INVALID_REQUEST) bounding per-seller reads via_BoundedClient; cancels-and-abandons at the deadline so a hung seller frees its fleet concurrency slot, degrades through existing failed-account/fleet-issue paths, and passes through a client's own TimeoutError unrelabelled. Covered by fleet, single-seller, explain, None-disable, bad-value, and cancellation-ignoring tests.fix(accounts): routesaccount_requests[]andrequest_accountresponses throughstrip_credentials_from_wire_result, closing a real credential-leak gap (bank coords, governance, webhook auth) via the generic recursive scrubber; addsrequest_accountto CREDENTIAL_BEARING_METHODS.- Additions are backward-compatible so
fix:is the correct semver signal; no wire/enum/task change so DR-0032/0033 pathway requirements do not apply; no import-layering or generated-code violations.
high_risk is true only because two src/adcp/decisioning/** files were modified; with no Medium-or-higher finding on those modified files, row 5 does not fire. gated_paths is false; no no-auto-approve team match. No rows 1–8 triggered → row 9 approve.
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.
Two small reviewer follow-ups for the 3.3 line. Two commits, both
fix:.1.
fix(reporting): per-seller read timeout for reconcile and fleet reconcileNothing bounded a reconcile run, so a hung seller held its fleet concurrency slot indefinitely, and single-seller reconcile could block forever.
read_timeout: timedelta | Noneonclient.reporting.reconcile(),explain(),poll(),events(), andmulti.reporting.reconcile(). It also covers the module functionsreconcile_subscriptions,explain_buyer_coverageandreconcile_fleet, andReportingEvents.poll/events. The default isDEFAULT_READ_TIMEOUT = timedelta(seconds=30).Noneturns the bound off. A zero, negative or non-timedeltavalue is refused up front asReportingSubscriptionError(code="INVALID_REQUEST"). The fleet checks it once before fanning out, so a bad value never turns into one issue per seller. The name and the default followSubscriptionOptions.read_timeoutinmanaged.py. The exception type follows the siblingconcurrencycheck and the TS SDK.withReadTimeoutinsrc/lib/principal/reporting-reconcile.tswraps the client's task methods (defaultreadTimeoutMilliseconds30 000), andreconcileReportingFleetpasses the option through to every seller. Here_BoundedClientwraps the same set:get_principal,get_adcp_capabilities(fetch_capabilities),list_accounts,get_media_buys,get_reporting_status, and the receipts policy'ssync_reporting_receiptswrite.ReportingSubscriptionError(code="TIMEOUT", recovery="transient"). Its message is<task> did not answer within read_timeout (Ns).... It degrades through the same path as any other failed read:get_principalgivesSUBSCRIPTION_PRINCIPAL_UNAVAILABLEwith a"*"failed entry (error.code == "TIMEOUT"). In the fleet, it becomes that seller'sFleetReconcileIssue, the same shape as other per-seller failures, and the fleet carries on.failedwitherror.taskset to the task anderror.code == "TIMEOUT".explain()raises theTIMEOUTerror.asyncio.waitplus cancel, notwait_for). A seller that ignores cancellation still frees the run and its fleet slot. ATimeoutErrorraised by the client itself, such as a transport timeout, stays the client's own error and is not relabelled asread_timeout.concurrency=1and the hung seller first: the other sellers complete and the hung one is reported as timed out.get_principal, plusexplain.Noneturns the bound off; non-positive and non-timedeltavalues are refused for reconcile, fleet and explain.TimeoutErrorpasses through unchanged.reporting-subscriptions-buyer.mdandreporting-subscriptions-errors.md.2.
fix(accounts): runaccount_requests[]through the credential scrubberPlatformHandler.list_accountsscrubbed theaccounts[]envelope and only then attachedaccount_requests[], so those items skippedstrip_credentials_from_wire_result.request_accountnever went through the scrubber at all.This is more than theoretical: a resolved
AccountRequestcarries a fullaccount: Account, which can holdbilling_entity.bank,governance_agents[].authenticationandnotification_configs[].authentication.credentials.Fix:
account_requestspage is scrubbed underlist_accountsbefore it is attached.request_account's response is scrubbed.request_accountjoinsCREDENTIAL_BEARING_METHODS, so the task-registry and webhook boundaries scrub it too.The test puts all three credential shapes into an
account_requests[]item and into arequest_accountresponse. Both come back stripped, and the hook's own record is left untouched. The test fails without the handler change.TS parity: the TS SDK has the same gap.
from-platform.tsservesaccount_requests: requests.itemsraw next toprojectAccountList(...), so it needs a matching follow-up there.Checks
ruff check src/ packages/adcp-reporting-storage/src/andruff format --checkon the changed files pass.mypy src/adcp/ packages/adcp-reporting-storage/src/adcp_reporting_storage/passes.CHANGELOG.next.mdis generated by release-please.🤖 Generated with Claude Code