Skip to content

feat(reporting): prototype reliable ledger reconciliation - #1095

Draft
bokelley wants to merge 12 commits into
mainfrom
feat/reporting-reconciliation
Draft

feat(reporting): prototype reliable ledger reconciliation#1095
bokelley wants to merge 12 commits into
mainfrom
feat/reporting-reconciliation

Conversation

@bokelley

@bokelley bokelley commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Prototype the reliable reporting reconciliation loop in the Python SDK against the current protocol work:

  • add typed client methods for get_reporting_status and sync_reporting_receipts over MCP and A2A
  • add an SDK-managed reconciler that exhausts a stable ledger snapshot, restarts on snapshot drift, deduplicates immutable records, validates history counts, selects the current revision, verifies finality and producer evidence, inspects the destination, submits an authenticated receipt, and confirms receipt readback
  • require an independent expected-period denominator keyed by configuration generation, report definition, feed, profile, exact campaign set, and period before returning a definitive result
  • enforce full partition coverage when a configuration requires it
  • deduplicate totals by canonical revision so one revision delivered to several destinations is counted once
  • expose the reporting and reusable agent-configuration wire types through the supported adcp.types surface

Why this is a draft

This intentionally validates two pending protocol changes together:

The active beta.9 schema cache is regenerated from a local integration of both protocol branches. This proves the combined contract and fixes source-style schema-reference handling in the generator and runtime loader, but the SDK must not merge or release until both protocol changes are accepted and published as one authoritative bundle.

The generation also updates the SDK's expected schema-collision and generated-contract inventories. Follow-up release-engineering gaps are tracked in adcontextprotocol/adcp#6984 and the linked SDK issues.

What the prototype proved

A buyer can only call a period definitive when the seller returns a closed, retained denominator and the SDK independently finds every expected period, complete associated history, full required coverage, one current revision at the requested finality, matching materialization evidence, and an accepted consumer receipt when required.

The seller learns that a buyer or governance consumer agrees by reading the authenticated receipt back from the same ledger. Availability alone does not prove consumer agreement or downstream ingestion.

Validation

  • full schema preparation, code generation, collision gate, and 2,800-export consolidation
  • ruff check on the reporting implementation, loader, generated surface, and tests
  • mypy on the reporting implementation and schema loader
  • 445 relevant tests passed after regeneration
  • 67 focused reporting, loader, and generated-contract tests passed after coverage enforcement

Comment thread src/adcp/reporting.py
class ReportingReconciliationClient(Protocol):
async def get_reporting_status(
self, request: GetReportingStatusRequest
) -> TaskResult[GetReportingStatusResponse]: ...
Comment thread src/adcp/reporting.py

async def sync_reporting_receipts(
self, request: SyncReportingReceiptsRequest
) -> TaskResult[SyncReportingReceiptsResponse]: ...
Comment thread src/adcp/reporting.py


class ReportingCheckpointStore(Protocol):
async def get(self, reporting_materialization_id: str) -> ReportingReceipt | None: ...
Comment thread src/adcp/reporting.py
class ReportingCheckpointStore(Protocol):
async def get(self, reporting_materialization_id: str) -> ReportingReceipt | None: ...

async def put(self, receipt: ReportingReceipt) -> None: ...
ctx: RequestContext[TMeta],
) -> MaybeAsync[GetReportingStatusResponse]:
"""Reconcile the caller's durable reporting ledger."""
...
ctx: RequestContext[TMeta],
) -> MaybeAsync[SyncReportingReceiptsResponse]:
"""Record caller verification receipts for materialized reports."""
...
@bokelley

Copy link
Copy Markdown
Contributor Author

SDK validation surfaced five upstream follow-ups beyond the reporting feature itself:

The cross-repository release gate is tracked in adcontextprotocol/adcp#6984. It should let a schema PR produce a deterministic bundle, run both SDK generators before merge, and record the exact protocol commit/bundle digest consumed by generated output.

I would treat #1099, #1100, #1101, and #1102 as release-safety work; #1103 is the next implementation/DX slice after the wire contract stabilizes.

@bokelley

Copy link
Copy Markdown
Contributor Author

Issue #1103 proposes completing the manifest/file inspection surface that this PR's reconciler currently delegates to the adopter via inspect(context). The proposed scope includes a pluggable ReportingResourceReader, reuse of the SDK's hardened canonical-reference fetch policy, typed retry/reject failure codes, and a conformance fixture that covers the full retrieve-verify-receipt path.

Since this PR is still a draft, it may be worth folding that scope here before merge rather than shipping a separate follow-up PR that needs its own rebase against the protocol bundle. I've deferred #1103 as a fold candidate; closing this PR will resurface it automatically.


Generated by Claude Code

@bokelley

Copy link
Copy Markdown
Contributor Author

Packaging fix scope note (from issue triage of #1104):

#1104 tracks the bug where VCS-built wheels omit adcp/_schemas because scripts/bundle_schemas.py is never called during a PEP 517 build. The build_py-hook approach being validated on this branch is the proposed fix.

Two paths forward before this PR merges:

  1. Fold it here — commit the build_py hook change and a CI smoke (clean checkout → _schemas absent → build → verify schema resolution) to this branch, so the packaging fix ships with the reporting reconciliation work.
  2. Extract it first — if the protocol-PR block (adcontextprotocol/adcp#6953 / adcontextprotocol/adcp#6966) will take a while, consider extracting the packaging fix to a standalone fix(packaging) PR that closes fix(packaging): make VCS-built wheels include runtime schemas #1104 independently. That unblocks adopters doing Git-SHA pins without waiting on protocol acceptance.

Flagging here so the packaging fix doesn't get lost when this PR eventually merges. No action required from the triage routine — this is just the audit trail.


Generated by Claude Code

@bokelley

Copy link
Copy Markdown
Contributor Author

The first adopter is now pinned to this PR's exact commit in scope3data/embedded-sales-agent#456. That exposed and fixed two SDK boundary issues here: agent-configuration types were generated but not exported through the supported public surface, and a clean VCS-built wheel omitted all runtime schemas. The latter is tracked in #1104. A clean wheel now bundles 3,054 schemas and resolves legacy 3.1, managed-reporting, and agent-configuration contracts; the adopter's full typecheck and 6,750-test suite pass against the installed wheel.

@bokelley

Copy link
Copy Markdown
Contributor Author

Adopter proof from scope3data/embedded-sales-agent#456 is now green against exact commit 98402079f4b995f9d8f4a0a6b698f870c93bfca4 (17 passed checks, 2 optional skips, no failures).

That run builds the Git wheel in Docker and exercises delivery-webhook serialization under freezegun, which originally exposed a late deferred serializer rebuild against the patched datetime.date. The fallback now uses the already-built declared parent schema only when runtime subclass serialization cannot safely complete that nested rebuild. The SDK's full local suite also passes: 7,489 passed, 41 skipped, 1 xfailed.

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