Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cc11643
fix(server): compact a pinned seller's tools/list so an SDK 10 buyer …
bokelley Oct 29, 2026
684914f
chore(main): release 9.3.1 (#1703)
aao-ipr-bot[bot] Oct 29, 2026
aac52ca
fix(types): defer the import-time model rebuilds that cost adcp.clien…
bokelley Oct 29, 2026
787cfb2
feat(signing): accept any well-formed adcp_use purpose in keygen (#1710)
bokelley Oct 29, 2026
7088ebf
feat(server): seller helper for wholesale feed versions (#1718)
bokelley Oct 30, 2026
0c0ef92
feat(adagents): attribute and verify publisher property claims (#1719)
bokelley Oct 30, 2026
8635115
ci: pin pypa/gh-action-pypi-publish to v1.14.3 for GHSA-5xjx-3w67-w9x…
bokelley Oct 31, 2026
bf6d530
chore(main): release 9.4.0 (#1714)
aao-ipr-bot[bot] Oct 31, 2026
1fcc331
test(reporting): run the row-upload lease test at the wall clock (#1813)
bokelley Nov 2, 2026
126f905
fix(server): return task and workflow handoffs uncached from Idempote…
bokelley Nov 2, 2026
7447d00
fix(types): make the domain package roots lazy so a types package imp…
bokelley Nov 2, 2026
272fced
feat(decisioning): let a raised AdcpError reject a request with every…
bokelley Nov 2, 2026
6714203
fix(canonical-formats): read versions from typed legacy formats (#1757)
bokelley Nov 2, 2026
617762e
fix(canonical-formats): check narrowing on seller-asserted references…
bokelley Nov 2, 2026
f49ffc5
ci(release): gate publishes on a companion the built distribution wor…
bokelley Nov 2, 2026
e45e1b8
chore(main): release 9.5.0 (#1815)
aao-ipr-bot[bot] Nov 2, 2026
495593e
fix(ci): check PR titles without stripping a trailing issue reference…
bokelley Nov 3, 2026
ce20905
chore(main): release 9.5.1 (#1824)
aao-ipr-bot[bot] Nov 3, 2026
6bb34df
feat(reporting-storage): accept 255 character row binding IDs and cre…
bokelley Nov 3, 2026
4449bc9
feat(reporting-storage): order the account change feed by xid and seq…
bokelley Nov 3, 2026
e004f1b
feat(reporting-storage): collect orphaned row objects by listing with…
bokelley Nov 3, 2026
536b8f8
perf(reporting-storage): drop the per-object advisory lock where noth…
bokelley Nov 3, 2026
2bd7751
fix(server): preserve valid creatives in lenient legacy batches (#1841)
bokelley Nov 3, 2026
679af6e
chore(main): release 9.6.0 (#1833)
aao-ipr-bot[bot] Nov 3, 2026
86302d0
fix(reporting): isolate handler registrations for each installation (…
bokelley Nov 3, 2026
8da31cc
chore(main): release 9.6.1 (#1848)
aao-ipr-bot[bot] Nov 3, 2026
5beaaed
chore: add Traycer environment file (#1858)
bokelley Nov 7, 2026
5eebb53
fix(reporting-storage): refuse a ledger upgraded to storage contract …
bokelley Nov 8, 2026
4f4c2bf
chore: merge main into next through 9.6.1
bokelley Nov 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
".": "9.3.0",
".": "9.6.1",
"packages/adcp-reporting-storage": "0.1.0-beta.4"
}
15 changes: 15 additions & 0 deletions .traycer/environment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"setup": {
"default": "ROOT=\"$(cd \"$(git rev-parse --git-common-dir)/..\" && pwd)\" || exit 1\nfor f in \"$ROOT\"/.env*; do n=\"${f##*/}\"; [ -f \"$f\" ] && [ ! -e \"$n\" ] && git check-ignore -q \"$n\" && cp \"$f\" \"$n\"; done; true || exit 1\nuv sync --all-extras || exit 1\nmake bootstrap || exit 1",
"macos": null,
"linux": null,
"windows": null
},
"teardown": {
"default": "",
"macos": null,
"linux": null,
"windows": null
},
"updatedAt": 1793934266991
}
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,19 @@ from adcp.testing import build_asgi_app, build_test_client, make_request_context

## Property Lists and Identifier Resolution

For publisher manifest sync, resolve the agent's selectors with
`get_properties_by_agent` or `resolve_properties_for_agent`, then call
`properties_for_publisher_domain(properties, publisher_domain)` to attribute
inventory. It keeps publisher subdomains and wildcard domain inventory, and
excludes domainless inventory (bundle-only apps, venues) unless
`include_domainless=True`. Permissive resolution requires `publisher_domain=`
from the trusted fetch origin (`allow_any_domain=True` skips the gate for
diagnostics). Use `await verify_cross_domain_properties(properties,
source_domain=..., agent_url=...)` before trusting claims about another
publisher's domain; its `CrossDomainVerification` result separates
`verified`, `unverified` and `domainless` properties. All three helpers are
exported from `adcp`. See `docs/adagents-discovery.md`.

Import `MCPPropertyListFetcher`, `ProductPropertyPolicy`, and
`check_property_list_reference` from `adcp`. The default seller fetcher resolves
lists with MCP `get_property_list`, follows pagination, and caches complete
Expand Down
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,75 @@
change public consumer defaults. See
[format-kind migration](docs/canonical-format-kinds-migration.md).

## [9.6.1](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.6.0...v9.6.1) (2026-11-03)


### Bug Fixes

* **reporting:** isolate handler registrations for each installation ([#1847](https://github.com/adcontextprotocol/adcp-client-python/issues/1847)) ([86302d0](https://github.com/adcontextprotocol/adcp-client-python/commit/86302d09404067410a60d3f9d0f850790038a36f))

## [9.6.0](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.5.1...v9.6.0) (2026-11-03)


### Features

* **reporting-storage:** accept 255 character row binding IDs and credential refs ([#1828](https://github.com/adcontextprotocol/adcp-client-python/issues/1828)) ([6bb34df](https://github.com/adcontextprotocol/adcp-client-python/commit/6bb34df00821e11f03aed2a018a0c5a6ccab3cdf))
* **reporting-storage:** collect orphaned row objects by listing with a clone fence ([#1835](https://github.com/adcontextprotocol/adcp-client-python/issues/1835)) ([e004f1b](https://github.com/adcontextprotocol/adcp-client-python/commit/e004f1bc55eff3bc32065a9c785f451af3813f79))
* **reporting-storage:** order the account change feed by xid and seq ([#1827](https://github.com/adcontextprotocol/adcp-client-python/issues/1827)) ([4449bc9](https://github.com/adcontextprotocol/adcp-client-python/commit/4449bc94cdd47900f39e15f5e99a35a9699d0c30))


### Bug Fixes

* **server:** preserve valid creatives in lenient legacy batches ([#1841](https://github.com/adcontextprotocol/adcp-client-python/issues/1841)) ([2bd7751](https://github.com/adcontextprotocol/adcp-client-python/commit/2bd7751cb1312efda5aefc4a11fecdfab0933c9e))


### Performance Improvements

* **reporting-storage:** drop the per-object advisory lock where nothing pairs on it ([#1836](https://github.com/adcontextprotocol/adcp-client-python/issues/1836)) ([536b8f8](https://github.com/adcontextprotocol/adcp-client-python/commit/536b8f870f518e7ceab3c7a1d64aa0b229b75e8e))

## [9.5.1](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.5.0...v9.5.1) (2026-11-03)


### Bug Fixes

* **ci:** check PR titles without stripping a trailing issue reference ([#1819](https://github.com/adcontextprotocol/adcp-client-python/issues/1819)) ([495593e](https://github.com/adcontextprotocol/adcp-client-python/commit/495593ec319bca75b22174c582fa5417f9730860))

## [9.5.0](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.4.0...v9.5.0) (2026-11-02)


### Features

* **decisioning:** let a raised AdcpError reject a request with every error ([#1780](https://github.com/adcontextprotocol/adcp-client-python/issues/1780)) ([#1810](https://github.com/adcontextprotocol/adcp-client-python/issues/1810)) ([272fced](https://github.com/adcontextprotocol/adcp-client-python/commit/272fced52d57632dedf32f2c175cf294100bff63))


### Bug Fixes

* **canonical-formats:** check narrowing on seller-asserted references ([#1758](https://github.com/adcontextprotocol/adcp-client-python/issues/1758)) ([617762e](https://github.com/adcontextprotocol/adcp-client-python/commit/617762e94371b5817f400481bbbc866c760a6a1a))
* **canonical-formats:** read versions from typed legacy formats ([#1757](https://github.com/adcontextprotocol/adcp-client-python/issues/1757)) ([6714203](https://github.com/adcontextprotocol/adcp-client-python/commit/67142036f40e6a68fd9ca3d5c51acaa7bfe402fb))
* **server:** return task and workflow handoffs uncached from IdempotencyStore.wrap ([#1795](https://github.com/adcontextprotocol/adcp-client-python/issues/1795)) ([126f905](https://github.com/adcontextprotocol/adcp-client-python/commit/126f9052ab79399f1e7bab8a53f34e24999ef95e)), closes [#1781](https://github.com/adcontextprotocol/adcp-client-python/issues/1781)
* **types:** make the domain package roots lazy so a types package imports first in any order ([#1798](https://github.com/adcontextprotocol/adcp-client-python/issues/1798)) ([7447d00](https://github.com/adcontextprotocol/adcp-client-python/commit/7447d00ceba97490109c46f0cf2f0ca9d6f8e5f3))

## [9.4.0](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.3.1...v9.4.0) (2026-10-31)


### Features

* **adagents:** attribute and verify publisher property claims ([#1719](https://github.com/adcontextprotocol/adcp-client-python/issues/1719)) ([0c0ef92](https://github.com/adcontextprotocol/adcp-client-python/commit/0c0ef92515ec5dfe9d1f045b976194d3d245d4e5))
* **server:** seller helper for wholesale feed versions ([#1718](https://github.com/adcontextprotocol/adcp-client-python/issues/1718)) ([7088ebf](https://github.com/adcontextprotocol/adcp-client-python/commit/7088ebf909852134521ba0ed4796b6f2896a4ad7)), closes [#1676](https://github.com/adcontextprotocol/adcp-client-python/issues/1676)
* **signing:** accept any well-formed adcp_use purpose in keygen ([#1710](https://github.com/adcontextprotocol/adcp-client-python/issues/1710)) ([787cfb2](https://github.com/adcontextprotocol/adcp-client-python/commit/787cfb215cf9207bfef8c28cc34ff0b737cea39b))


### Bug Fixes

* **types:** defer the import-time model rebuilds that cost adcp.client 560 MiB RSS ([#1704](https://github.com/adcontextprotocol/adcp-client-python/issues/1704)) ([aac52ca](https://github.com/adcontextprotocol/adcp-client-python/commit/aac52cac66ecae2d1c2c4e11be23294335a3edd8)), closes [#1694](https://github.com/adcontextprotocol/adcp-client-python/issues/1694)

## [9.3.1](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.3.0...v9.3.1) (2026-10-29)


### Bug Fixes

* **server:** compact a pinned seller's tools/list so an SDK 10 buyer can call it ([#1702](https://github.com/adcontextprotocol/adcp-client-python/issues/1702)) ([cc11643](https://github.com/adcontextprotocol/adcp-client-python/commit/cc11643352a078b977449f44dcd2d5687ad29bf6)), closes [#1692](https://github.com/adcontextprotocol/adcp-client-python/issues/1692)

## [9.3.0](https://github.com/adcontextprotocol/adcp-client-python/compare/v9.2.0...v9.3.0) (2026-10-27)


Expand Down
32 changes: 32 additions & 0 deletions docs/reporting-ledger-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,35 @@ seeds ledgers with the actual published wheel pairs (`adcp==9.3.1` with
`adcp-reporting-storage==0.1.0b4`, `adcp==10.0.0b2` with `0.2.0b0`) and proves
the retained evidence reads back unchanged, a restart rebinds the stored
generations, and the upgraded catalog is identical to a fresh migration.

## Upgrading a ledger that adcp-reporting-storage 0.1.0b5 wrote

0.1.0b5 is the SDK 9 storage release that orders the account change feed by
`(xid, seq)`, accepts 255-character row binding IDs and credential refs, and
adds the opt-in orphaned-object collector. Its ledger upgrades to this release
through the same two entry points and the same numbered migrations as a
0.1.0b4 ledger (above): this release adds no migration for them. Their DDL is
in the files `create_schema()` re-applies on every start when the option is
on (`reporting_changes.sql` with `change_feed=True`,
`reporting_row_storage.sql` with `row_storage`), as on the SDK 9 line, and
`python -m adcp migrate` never creates those tables. A ledger created before
0.1.0b5 gains the account horizon's xid columns, the named feed-consumer and
row-binding constraints, the `(account_id, xid, seq)` index and the orphan
collector fence table the first time this release starts with those options;
see [host change feed and consumers](reporting-row-storage.md#host-change-feed-and-consumers)
for creating that index concurrently on a large changes table.

adcp-reporting-storage 0.2.0b0 to 0.2.0b2 predate the `(xid, seq)` account
feed. On a 0.1.0b5 ledger they refuse an account cursor that carries an
`xid`, refuse a row binding ID over 128 characters, and prune the change feed
without advancing the account horizon's xid columns. A cursor 0.1.0b5 handed
out could then pass the account horizon check and skip what 0.2.x pruned. So
the first time this release starts with `change_feed=True` on a 0.1.0b5 ledger
that a 0.2 release has already upgraded, it raises each account horizon to the
deployment horizon, or to just below the account's oldest retained change when
that is lower. Those cursors then fail closed with `CURSOR_EXPIRED`; a
consumer starting over still reads every retained change. It runs once (a
comment on `adcp_reporting_change_account_horizon` records it) and changes
nothing on a ledger upgraded straight from 0.1.0b5. As for the `(xid, seq)`
upgrade on SDK 9, stop every older host's change maintenance before this
release starts.
67 changes: 67 additions & 0 deletions docs/reporting-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ restart across the upgrade does not turn a replay into a conflict. This is part
the shared layout in adcontextprotocol/adcp#7996 (Stage 2, tracked in
[#1457](https://github.com/adcontextprotocol/adcp-client-python/issues/1457)).

## Account change feed orders by `(xid, seq)`

The account change feed (`changes_after(account_id=...)`) now orders by `(xid, seq)` behind
`xid < pg_snapshot_xmin(pg_current_snapshot())`, as the deployment-wide feed already did, matching the JavaScript SDK
([adcp-client#3289](https://github.com/adcontextprotocol/adcp-client/pull/3289)). It replaces the page truncation at
the first in-flight change from #1483, and no longer depends on the account lock for its order: a lower `seq` that
commits late is returned on a later page, never skipped. Account cursors now carry `xid`. A seq-only account cursor
saved by an earlier release is still accepted and mapped to a position that skips nothing, at the cost of redelivering
a few changes, and it still expires exactly (`pruned_through_seq` is kept). `reporting_changes.sql` is re-applied by
`create_schema` and is idempotent: it adds the account horizon's xid columns, replaces the consumer check constraint,
and creates `adcp_reporting_changes_account_xid`. Create that index with `CREATE INDEX CONCURRENTLY` first on a large
changes table, stop older hosts' change maintenance before the new reader runs, and do not roll back after a consumer
saved a new-format account cursor. Not affected: deployment-wide cursors, the buyer-facing `changes_after` projection and
anyone who never enabled `change_feed`. See
[host change feed and consumers](reporting-row-storage.md#host-change-feed-and-consumers)
([#1825](https://github.com/adcontextprotocol/adcp-client-python/issues/1825)).

## Feed consumer checkpoints are forward-only

`save_feed_consumer_cursor` used to replace the saved position unconditionally. It now ignores a cursor behind the
Expand All @@ -94,6 +111,56 @@ Reads handle both forms. `migrate_inline_rows` moves legacy rows with verificati
rows do not reproduce its binding, and `retire_legacy_inline_rows` then renames the legacy table so older binaries fail
closed. See [row storage](reporting-row-storage.md).

## The per-object lock is kept only where it pairs

Retirement, the intent sweep and a commit that adopts an object its own intent recorded no longer take the per-object
advisory lock, and no longer hold a transaction or pooled connection across the provider delete. The lock stays in the
two places that pair with each other: the orphan collector's delete, which locks every binding's key for the object, and
a commit that adopts an object its intent did not record. The lock text is unchanged, so a Python deleter and a
JavaScript adopter still agree, and rolling upgrades between a version that locks everywhere and this one are safe (every
pair that needs exclusion keeps the lock on both sides). A commit now closes its intent with `RETURNING created_objects`
to know which adopted objects it recorded. No schema or wire change. See
[the object lock contract](reporting-row-storage.md#the-object-lock-contract)
([#1492](https://github.com/adcontextprotocol/adcp-client-python/issues/1492), adcp-client#3606).

## Opt-in orphaned-object collector, fenced against clones

`PgReportingLedgerStore(orphan_collection=ReportingOrphanCollectionOptionsV1(...))` and
`collect_orphaned_row_objects` list each object binding under its owned prefix and delete the objects no ledger record
or open upload names, which the write-intent sweep cannot see (an upload that succeeded before its intent recorded it,
intents for a binding no host configures, intents lost to a restore). It mirrors the JavaScript SDK
([adcp-client#3291](https://github.com/adcontextprotocol/adcp-client/pull/3291) and the clone fence in
[#3593](https://github.com/adcontextprotocol/adcp-client/pull/3593)). Nothing changes unless you opt in. An object is
deleted only when it is a well-formed row key under `{prefix}/{namespace_key}/`, older than `min_age_seconds` (default
7 days, minimum 1 day), named by no record or upload at any version under any binding, and unreferenced under the
per-object lock of every binding. A real pass first claims the namespace in a create-only fence in the bucket and
refuses to delete while another database holds a newer claim, so a clone or restore of the ledger database that shares
its namespace key and its opt-in cannot delete the live database's newer objects; `take_over_orphan_collector_fence`
is the deliberate step after a restore.

Providers gain an optional `list`. The provider conformance suite checks it when present, so its result now ends with a
`list` entry for providers that implement it; compare accordingly. A provider without `list` keeps working and is
reported `unsupported`. `create_schema` adds the shared `adcp_reporting_orphan_collector_fences` table, additively and
outside the required-schema manifest. The production worker reports `fenced` bindings as stalled work and `unsupported`
bindings as pending work. **Roll out:** start with `dry_run=True`, claim from the live database first, and never enable
the collector on a copy of the deployment. See [the listing collector](reporting-row-storage.md#orphaned-objects-the-opt-in-listing-collector)
([#1829](https://github.com/adcontextprotocol/adcp-client-python/issues/1829)).

## Row binding IDs and credential references accept 255 characters

`row_binding_id` and a binding's `credential_ref` were capped at 128 characters. Every comparable AdCP identifier
(`reporting_revision_id`, `resource_ref`, `destination_ref`) allows 255, so an ID derived from an AdCP destination
reference could be refused for no protocol reason. Both now accept 1 to 255 characters of `A-Za-z0-9_.:-`, matching the
JavaScript SDK ([adcp-client#3595](https://github.com/adcontextprotocol/adcp-client/pull/3595)). The configuration
check, the `adcp_reporting_row_bindings` `CHECK` and the provider `clients` map share the bound, which is exported as
`REPORTING_ROW_BINDING_ID_MAX_LENGTH` and `REPORTING_ROW_CREDENTIAL_REF_MAX_LENGTH`. A malformed value that the old
check let through (a trailing newline) is now refused too. `create_schema` replaces the original unnamed 128-character
`CHECK` with `adcp_reporting_row_bindings_row_binding_id_bounds` in place, idempotently, with no data migration; stop
older writers first, because an older binary requires the original constraint and fails closed against an upgraded
database. Nothing that was valid before is refused. See
[stored names follow AdCP](reporting-row-storage.md#stored-names-follow-adcp)
([#1498](https://github.com/adcontextprotocol/adcp-client-python/issues/1498)).

## Row storage field alignment (breaking for row storage adopters)

Row storage stores every value in the format AdCP defines for it, matching the JavaScript SDK byte for byte:
Expand Down
Loading
Loading