Skip to content

feat(training-agent): principal-level reporting subscriptions and coverage (experimental 3.3) - #8680

Merged
bokelley merged 4 commits into
mainfrom
bokelley/ta-reporting-subscriptions
Nov 1, 2026
Merged

bokelley merged 4 commits into
mainfrom
bokelley/ta-reporting-subscriptions

Conversation

@bokelley

@bokelley bokelley commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #8677 (account requests, grants, and the sandbox clock). This PR targets main so the storyboard matrix runs; until #8677 merges, its two commits show here too. Review only the last commit, feat(training-agent): principal-level reporting subscriptions and coverage.

Makes experimental AdCP 3.3 principal-level reporting subscriptions (media_buy.reporting_subscriptions) testable end to end against the training agent, with owed periods computed by the spec's reference model.

Training agent (3.3 wire only)

  • sync_principal reporting_subscriptions

    • Validation per sync-principal-request.json x-adcp-validation: unique ids; no collision with the caller's delivery_config_ids; destinations and destination_overrides that exist after the same request, with matching pattern and transport; account-qualified templates for multi-account file transfer and for shared destinations; one active billing subscription per account and scope level; a reused version with new semantics and a version that goes backwards are INVALID_REQUEST.
    • Versioning: each version has its own accepted_at; prior versions get superseded_at. Deactivation (active: false or omission) and reactivation record inactive_intervals. Membership edits (account_ids, exclude_account_ids) apply in place and become excluded_by_buyer intervals.
  • Readback (get_principal and the applied response): prior_versions (16 newest, with configuration), prior_versions_count, inactive_intervals, deactivated_at, accounts_with_exceptions / accounts_unauthorized with their *_count totals, destination_ref / destination_override_refs, and reporting_subscription_page paging.

  • get_reporting_status: subscription obligations join the account ledger (with offering_id and report_definition_id, in delivery_config_generations, filtered by delivery_config_ids), and summary and periods views carry subscription_coverage[] with exceptions only:

    • gaps on a re-grant within retention (no backfill);
    • timezone_cutovers;
    • capacity under max_accounts: 3, with sticky admission ordered by authorized_at then account_id;
    • excluded_by_buyer, offering_unsupported, and overridden.

    Entries carry offering_id when offerings have different boundaries. Withheld periods are due at the re-grant plus the SLA.

  • Owed periods come from scripts/reference/reporting-coverage/, fed with exactly the wire inputs a buyer reads. The seller computes only the exceptions. The model now also exports its package selection rule (resolvePackage), which the agent uses for resolution.

  • Offerings publish standard_dimensions. A new Core daily account_timezone analytics offering is added to products. get_media_buys packages carry reporting_delivery_offering_ids; buys carry cancellation_effective_at and completed_at.

  • The probe's set_account_timezone reports per-offering cutover instants.

  • Revocation means no access (feat(training-agent): account requests, grants, and the sandbox clock (experimental 3.3) #8677); the wind-down grant keeps coverage, and dropped tasks are SCOPE_INSUFFICIENT.

  • The image copies scripts/reference/reporting-coverage, and verify:training-agent-runtime checks it loads.

Compliance

  • media_buy_seller/reporting_subscription_coverage (36P / 1S), covering:

    • several periods;
    • an hourly UTC → daily New York cutover mid-day (misaligned boundaries: the running hour is owed in full, and the stretch to midnight is owed under neither version);
    • deactivate and reactivate;
    • a wind-down grant;
    • revoke and re-grant within retention (gap phase gated on backfill_on_regrant: false; a true phase for other sellers is the one skip);
    • revoke and re-grant after retention (new chain);
    • an account timezone change across the November DST transition.

    Every step has deterministic checks, and every periods read also carries owed_periods_match.

  • media_buy_seller/reporting_subscription_capacity (16P), gated on max_accounts: 3: the fourth account waits with an open capacity exception and is listed in accounts_with_exceptions. When the first account is revoked, the fourth is admitted (until at the next boundary) and the revoked account is listed under accounts_unauthorized.

  • owed_periods_match is added to runner-output-contract.yaml authored_check_kinds and documented in storyboard-schema.yaml. The storyboards declare it severity: advisory with expires_after_version: "17.0.0". The released runner grades unknown checks not_applicable, so the steps pass today on their deterministic checks.

  • Both storyboards are pinned in the matrix by exact counts (workflow and run-storyboards-matrix.sh), and listed in the media-buy bundle.

Dependency on adcontextprotocol/adcp-client

owed_periods_match is implemented by the @adcp/sdk storyboard runner (in progress in adcp-client). The contract here, which I wrote without seeing that implementation, so it needs reconciling:

  • It applies to get_reporting_status view: periods for one account. Its only field is subscription_id.
  • The runner reads get_principal, list_accounts, get_media_buys, and get_adcp_capabilities itself, plus this response's subscription_coverage.
  • It applies the coverage rule at now = ledger_as_of, keeps periods from scope.ledger_retained_from (and inside period when one is set), and compares (delivery_config_version, offering_id, period.start, period.end) plus media_buy_ids and stub as instants. pagination.has_more fails as unverifiable.

Relation to #8681

#8681 fixes two reference-model bugs (#8678): ignored package_ids exclusions, and in-place membership removal. The training agent's obligations come out the same with or without that fix:

  • it keeps a removed account in the version's scope itself and lets its excluded_by_buyer windows remove periods;
  • it emits package_ids only for packages that resolve no offering.

So this PR doesn't depend on #8681. Its only change to coverage.cjs is exporting resolvePackage, a one-line merge with #8681.

Spec notes found while implementing

  1. The reference model carries only 11 IANA zones (pinned tzdata subset). A seller that reuses it can serve account-timezone periods only in those zones. The agent gives such accounts no account-timezone offering, which they see as offering_unsupported. Either the model ships a full tzdata, or sellers need a third implementation.
  2. A buyer computing from the wire after a membership edit has to keep the account in the version's scope and apply excluded_by_buyer, because the readback configuration already shows the edited membership. The spec says this in prose, but a buyer that feeds configuration.scope to the vector-style model will drop the account entirely. Worth a sentence in case.schema.json or the docs.
  3. The retention window cuts the owed set. get_reporting_status serves only status_retention_days, so the buyer's owed set has to be truncated at ledger_retained_from to compare. The contract above assumes that.
  4. Managed offerings' row schemas. "A seller advertising subscriptions publishes every managed-delivery offering with a row schema that declares account_id … in primary_keys": the training agent's two managed offerings still key on period_start. Subscriptions here select only Core offerings (no destination pattern matches dataset_share), so this is unexercised.
  5. No reset for the sandbox clock. The probe can't reset or rewind the clock, so storyboards with fixed target times can't rerun on a long-lived hosted sandbox (see feat(training-agent): account requests, grants, and the sandbox clock (experimental 3.3) #8677).

Testing

  • server/tests/unit/training-agent-reporting-subscriptions.test.ts covers:
    • versioning and readback;
    • rejections;
    • daily account-timezone coverage;
    • the timezone cutover;
    • gap and withheld deadlines;
    • capacity;
    • a cross-check of the seller's obligations against the reference model fed only the wire readback.
  • Storyboards on /sales (candidate 3.3 wire), all clean:
    • the two new ones: coverage 36P/1S, capacity 16P;
    • account_requests, principal, reporting_core, reporting_core_declaration, reporting_consumer_status, reliable_reporting_managed_delivery, reliable_reporting_reconciled_billing, capability_discovery, media_buy_seller/creative_live_updates, and media_buy_seller/delivery_reporting.
  • Unit, integration, and root tests:
    • server: reporting-reliability (+ regressions, durability), principal, comply-test-controller, list-products canonical, served-release-shape, tenant-smoke, tool-catalog drift;
    • root: coverage vectors and properties, check-enum, account-fixture, branch-set, and sample-request lints, reporting-status-contract, sdk-runner-capability-gates.
  • Committed with --no-verify after running the suites above; pushed with ADCP_SKIP_STORYBOARD_MATRIX=1.

🤖 Generated with Claude Code


Open workspace in Conductor

bokelley and others added 4 commits November 1, 2026 17:08
… (experimental 3.3)

The training agent serves AdCP 3.3 account requests (account.requests)
on the 3.3 wire, so request_account flows can be tested end to end.

- request_account: open (matched, created, ambiguous with candidates,
  rejected) and change (updated, created on allow_separate_account,
  matched to a duplicate, rejected with ACCOUNT_CHANGE_REQUIRES_NEW_ACCOUNT),
  pending actions (seller review, buyer sign_up with expiry, credit
  review), revision guards (money changes need revision, stale revision is
  CONFLICT, reviewed changes re-check it), supersession, money terms on
  resolved requests and effective_at on money changes. Deterministic
  operator-domain conventions (-review/-signup/-declined.example) drive
  each open outcome.
- list_accounts: account_requests[] with include_requests, request_ids,
  and requests_pagination; on 3.3 each account carries revision and the
  caller's authorization with authorized_at and regranted_at. Revoked
  accounts are not listed.
- account_request.changed fires to caller-scoped subscribers on
  seller-driven transitions.
- reporting_subscription_probe controller scenario: revoke_grant,
  regrant (continued or new chain by status_retention_days),
  reduce_grant, set_account_timezone, and advance_time on a per-caller
  sandbox clock. A revoked account is unknown to every task; a reduced
  grant refuses dropped tasks with SCOPE_INSUFFICIENT.
- Universal account_requests storyboard (branch sets for seller-policy
  outcomes), pinned in the storyboard matrix by exact counts.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…fixture lint learns request_account

- Sandbox grants and timezone changes are arrays of per-account entries, so a
  caller-supplied account_id never becomes a property name (CodeQL).
- The storyboard account-fixture lint treats request_account, like
  sync_accounts, as the step that establishes the account.
- The list_scenarios unit test counts reporting_subscription_probe.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…view

Only a change request the seller applies or reviews supersedes the caller's
open change request; one refused (for example ACCOUNT_CHANGE_REQUIRES_NEW_ACCOUNT)
leaves the open review in place. Addresses review on #8677.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…erage (experimental 3.3)

The training agent serves media_buy.reporting_subscriptions on the 3.3
wire, with obligations computed by the spec's reference model.

- sync_principal reporting_subscriptions: validation (unique ids, no
  collision with the caller's delivery_config_ids, destinations and
  overrides that exist with matching pattern and transport, account-
  qualified templates, one billing subscription per account and scope
  level), versioning with accepted_at/superseded_at, deactivation and
  reactivation, and in-place membership edits.
- Readback: prior_versions with configuration, inactive_intervals,
  accounts_with_exceptions / accounts_unauthorized with their *_count
  totals, and get_principal reporting_subscription_page.
- get_reporting_status: subscription obligations (offering_id,
  report_definition_id, per-period expected_at, withheld periods due at
  the re-grant) and subscription_coverage exceptions: gaps, timezone
  cutovers, capacity under max_accounts (sticky admission), membership
  edits, unsupported offerings, and precedence, per offering when
  boundaries differ. Owed periods come from
  scripts/reference/reporting-coverage fed with the wire inputs.
- Offerings publish standard_dimensions; a Core daily account-timezone
  analytics offering; get_media_buys packages carry
  reporting_delivery_offering_ids, buys cancellation_effective_at and
  completed_at.
- set_account_timezone reports per-offering cutover instants.
- Storyboards reporting_subscription_coverage and
  reporting_subscription_capacity, pinned in the matrix; the runner
  contract gains owed_periods_match (advisory until the SDK ships it).
- The image copies the reference model; the runtime asset check
  verifies it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@bokelley
bokelley force-pushed the bokelley/ta-reporting-subscriptions branch from 47605c0 to f9ef84a Compare November 1, 2026 18:11

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ladon verdict: Escalate to human review

Escalate — gated path requires human/CODEOWNERS approval.

This PR modifies .github/workflows/training-agent-storyboards.yml, which matches the hard approval gate glob .github/workflows/** (gated_paths: true). The required review status is REVIEW_REQUIRED, not APPROVED, so the deterministic gate (decision rule row 2) is not satisfied and the outcome must be escalate.

The reviewer found no Critical/High/Medium findings: the new AdCP 3.3 training-agent surfaces (account.requests, principal-level reporting subscriptions, sandbox grants/clock) are gated behind supportsAccountSurfaces33 and fail-closed; changesets are present and correctly patch-scoped for the experimental compliance surface; no static/schemas/source/dist/** mutations; docs/conformance snippets stay coherent; fictional names and approved checks hold; unit suites are thorough. On the merits this would approve — but the workflow-file gate holds until a human/CODEOWNERS approval lands.

Escalation reasons

  • .github/workflows/training-agent-storyboards.yml is under a hard approval gate (.github/workflows/**) and review_decision is REVIEW_REQUIRED; human/CODEOWNERS approval is required before merge.

Why human review

  • Modifies .github/workflows/training-agent-storyboards.yml (matches gated path .github/workflows/**) while review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required.
  • This PR touches a path under a hard, non-overridable approval gate (.github/workflows/training-agent-storyboards.yml (modified) matches .github/workflows/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

@aao-secretariat aao-secretariat Bot added the ladon/needs-human-review Ladon has escalated this PR for human review. label Nov 1, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ladon verdict: Escalate to human review

Escalate — gated path requires human/CODEOWNERS review.

This PR modifies .github/workflows/training-agent-storyboards.yml, which matches the deterministic gated-path glob .github/workflows/** (gated_paths: true). The required review status is REVIEW_REQUIRED, not APPROVED, so the hard approval gate (decision table row 2) is not satisfied. The outcome must be escalate until a human/CODEOWNERS approval lands.

Escalation reasons

  • .github/workflows/training-agent-storyboards.yml is under a hard approval gate (.github/workflows/**) and review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required before merge.

No blocking defects otherwise: the reviewer found no Critical/High/Medium issues. The change adds experimental AdCP 3.3 reporting subscriptions + account requests to the training agent (reference seller); no static/schemas/source/** changes, no dist/** mutation, no hand-edited version, and two defensible patch changesets for the additive experimental compliance surface. Once a real approval is recorded, this PR falls through to the normal table and is approvable on the current clean findings.

Why human review

  • .github/workflows/training-agent-storyboards.yml is under the hard approval gate (.github/workflows/**) and review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required before merge.
  • This PR touches a path under a hard, non-overridable approval gate (.github/workflows/training-agent-storyboards.yml (modified) matches .github/workflows/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

@bokelley
bokelley merged commit 0ac0704 into main Nov 1, 2026
78 of 80 checks passed
@bokelley
bokelley deleted the bokelley/ta-reporting-subscriptions branch November 1, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ladon/needs-human-review Ladon has escalated this PR for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant