Skip to content

fix(reporting): compare set-valued subscription arrays without order on readback - #1764

Merged
bokelley merged 2 commits into
nextfrom
claude/issue-1695-readback-sets
Nov 1, 2026
Merged

bokelley merged 2 commits into
nextfrom
claude/issue-1695-readback-sets

Conversation

@bokelley

@bokelley bokelley commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Loose end from the reporting-subscriptions DX plan: the open Ladon finding on #1737.

Problem

client.reporting.subscribe() verifies the seller's readback with differing_path, which compared every array element for element. The subscription's scope.account_ids, exclude_account_ids, operator_domains and selector.dimensions are uniqueItems sets. A conformant seller that returns them in another order, such as sorted, tripped a terminal SUBSCRIPTION_READBACK_MISMATCH ("The seller altered a submitted field; contact the seller") on a write that succeeded.

Fix

  • Those four arrays now compare as sets, so only membership counts.
  • destination_overrides entries select disjoint accounts, so their order carries no meaning either. Each sent entry now matches any stored entry.
  • Any other array still compares element for element.

The registry's own versioning (registry._semantic) already treats these arrays as sets, so the buyer check now agrees with it.

Tests

  • A seller that sorts every set-valued array passes subscribe(). Before the fix this test fails.
  • Unit cases for differing_path: reordered sets pass, changed members fail, reordered destination_overrides pass, a changed override names its index, and other arrays stay ordered.

pytest tests/test_reporting_subscription_facade.py: 15 passed. ruff and mypy are clean.

🤖 Generated with Claude Code

…on readback

The buyer facade's readback check compared every array element for element,
but account_ids, exclude_account_ids, operator_domains and selector dimensions
are uniqueItems sets. A conformant seller returning them in another order,
such as sorted, tripped a terminal SUBSCRIPTION_READBACK_MISMATCH on a write
that succeeded. Those arrays now compare as sets, and destination_overrides,
whose entries select disjoint accounts, compare as an unordered collection.
Any other array still compares element for element.

Addresses the Ladon finding on adcp-client-python#1737.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean, correct fix with no blocking or medium findings.

This PR fixes differing_path in the reporting subscription facade so that the four uniqueItems subscription arrays (account_ids, exclude_account_ids, operator_domains, dimensions) are compared as sets and destination_overrides as an unordered membership match. This resolves a spurious terminal SUBSCRIPTION_READBACK_MISMATCH triggered by a conformant seller that reorders these arrays. The buyer check now agrees with registry._semantic (registry.py:86–110), which already canonicalizes these arrays.

Checked:

  • No public-surface or wire-shape change; no semver signal required; fix: prefix is correct.
  • No forward-compat / discriminated-union, import-layering, generated-code, or credential concerns (changes limited to the facade + tests).
  • Tests cover reordered sets passing, changed members failing, reordered vs. altered destination_overrides, and other arrays staying order-sensitive. The only theoretical edge (greedy override matching) does not bite given disjoint, destination_id-keyed entries.

Gates: high_risk false, gated_paths false, no author team gate. No critical/high/medium findings. Falls through to row 9 → approve.

The producer row-upload test stamps its worker lease with the settling
harness clock, fixed at 2026-11-01T02:10Z, while the row store checks the
lease against the database's clock_timestamp. Once the wall clock passed that
instant plus the lease, every run failed with STATE_UNAVAILABLE: no live
worker lease covers this row upload, on next and on every pull request. The
test now runs its turn at the wall clock, so the lease lies in the real
future.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit ae15bab)

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — test-only delta since prior approve, no blocking or medium findings.

This subsequent review covers a single test-only change in tests/conformance/reporting/test_reporting_row_storage_object_pg.py: it captures the harness clock and sets clock[0] = datetime.now(timezone.utc) before run_worker(). This is a correct fix for a time-dependency — the previously fixed 2026-11-01 02:10 UTC instant would go stale as real time passes, and the PG row-upload path compares the worker lease against the database's clock_timestamp(), so a past-stamped lease would read as expired. The clock is a mutable list read lazily by both store and producer, so the mutation propagates correctly.

No production surface touched, no public-API/wire-shape changes, no new findings. Prior run had no blockers and none re-surface.

Decision table: no critical/high findings (row 1 no), gated_paths false (row 2 no), high_risk false (rows 3/5 no), no medium findings (rows 4/8 no), prior decision was approve (row 6 no), no author team gate (row 7 no). Falls through to row 9 → approve.

@bokelley
bokelley merged commit 99abea8 into next Nov 1, 2026
41 checks passed
@bokelley
bokelley deleted the claude/issue-1695-readback-sets branch November 1, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant