Skip to content

feat: schedule status checks from expected_confirmation_at - #1809

Merged
bokelley merged 1 commit into
nextfrom
claude/issue-1640-expected-confirmation
Nov 2, 2026
Merged

bokelley merged 1 commit into
nextfrom
claude/issue-1640-expected-confirmation

Conversation

@bokelley

@bokelley bokelley commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR implements AdCP 3.3 expected_confirmation_at against the pinned 3.3.0-beta.3 bundle. The field is still present in that bundle. It is the seller's estimate of when a submitted task will confirm. Buyers should check status at that time instead of polling tightly. A passed timestamp is never a failure.

Buyer side

  • TaskResult.expected_confirmation_at returns the estimate from the response as an aware datetime. It reads typed data or wire dicts. adcp.expected_confirmation_at(...) does the same for a model, a get_media_buys buy or a mapping.
  • adcp.next_status_check_delay(estimate, poll_interval=, max_interval=) returns the wait before the next check:
    • with no estimate, or once it has passed: poll_interval (the floor);
    • with a future estimate: the time until it, capped at max_interval so an early confirmation is still seen.
  • wait_for_refinement_verified now schedules its polls this way. A new max_poll_interval parameter (default 60s) sets the cap. Callers that don't pass it get the old behavior unless the seller sends an estimate.
  • CompatibilityPurchaseOperation adds expected_confirmation_at and next_refresh_delay(...), so callers driving refresh_pending_legacy_purchase can schedule the next refresh. The continuation does not poll on its own.

Seller side

  • Setting the estimate: use adcp.server.submitted_response(task_id, expected_confirmation_at=...), or pass ctx.handoff_to_task(fn, expected_confirmation_at=...) / ctx.handoff_to_workflow(...) on a decisioning platform. The value is normalized to RFC 3339 UTC. A naive datetime is refused, and a past one is logged.
    • The framework puts it on the submitted envelope only for create_media_buy, buy_products, accept_proposal and refine_proposals. On any other task it is left off with a warning.
  • Downgrade rules (compat/versions.py):
    • Dropped for a caller below 3.3 on buy_products, accept_proposal and refine_proposals, on every arm, with a recorded field_unavailable loss. The generic projection did not do this before for the first two: their async submitted schemas don't declare the field and are open, so it passed through to 3.2 callers whose main response arms are closed.
    • Kept for 3.0, 3.1 and 3.2 callers on create_media_buy's submitted envelope and on get_media_buys buys. Those objects are open and the spec makes the field safe to ignore there. It used to be dropped.
    • Tests check each converted payload against the target release's schema.
  • The handler's auto-emit gate now recognizes framework submitted projections that carry extra keys, so a handoff with an estimate still skips the inline completion path.

Notes

  • There is no generic wait_for_task helper. Callers polling get_task_status themselves use next_status_check_delay. Task status polls don't repeat the estimate, so it comes from the submitted response.
  • Writes outside the stated scope:
    • src/adcp/types/core.py: a read-only property on TaskResult.
    • src/adcp/status_checks.py: a new module.
    • src/adcp/server/responses.py: the helper.
  • Edits to shared decisioning files are small: handoff kwargs in context.py / types.py, _submitted_projection in dispatch.py, and one condition in handler.py.
  • Rebased onto next after feat!: wire the list_proposals and list_collections tasks #1803 merged. The MIGRATION and inventory conflicts were resolved by keeping both sides' rows.

Test plan

  • tests/test_expected_confirmation_at.py (new), covering:
    • conversion for each task and release;
    • a server dropping the field for a 3.2 buy_products caller and recording the loss;
    • a decisioning handoff carrying it;
    • formatting;
    • delay bounds;
    • the refinement waiter's sleep schedule;
    • the purchase-continuation refresh delay.
  • make lint, make typecheck-all, public API snapshot regenerated.
  • Full suite at -n 3 after the rebase: 21314 passed, 0 failed.

Closes #1640

🤖 Generated with Claude Code

"preview_creative_response",
"products_response",
"signals_response",
"submitted_response",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: New public response builder submitted_response lands without a matching row in AGENTS.md's Response Builders table (L75-94), where every other *_response builder is documented. Per repo policy a new public export without a handler-table entry is a medium finding — adopters and coding agents driving the seller surface won't discover it. Add | submitted_response(task_id, ...) | Submitted task dict | create/buy/accept/refine |.

@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 — additive AdCP 3.3 expected_confirmation_at support with correct semver signal and version-translation pathway.

What I checked:

  • Downgrade keep/drop logic in versions.py (open-object _OLDER_PEER_SAFE_FIELDS keep + explicit _LATEST_LINE_ONLY_FIELDS drop with recorded field_unavailable loss, gated by minor == 3) — satisfies the DR-0032/0033 pathway-back requirement.
  • Delay floor/ceiling math, dispatch projection task gating + warning, handler auto-emit gate fix for extra-key projections.
  • Per-release schema-validated tests covering the version-translation pathway.
  • All changes are additive; feat: is the correct conventional-commit prefix — no public-surface removals/renames/required-flips, so no feat!:/migration requirement triggered.
  • High-risk flag is true but all matching files are (modified) with only a single Medium finding (docs-only), so no escalation trigger (rows 3–5 not met).

Medium findings (1):

  • src/adcp/server/init.py:382 — submitted_response missing from AGENTS.md Response Builders table

Decision table: no critical/high (row 1 n/a); gated_paths false (row 2 n/a); no deleted high-risk file (row 3 n/a); the single medium is docs/scope, not data-loss/schema/infra (row 4 n/a); only one medium finding so row 5 n/a; no prior decision (row 6 n/a); no team gate (row 7 n/a); fewer than three mediums (row 8 n/a) → row 9 approve. Recommend addressing the AGENTS.md table entry as a follow-up nit.

Medium findings

  • src/adcp/server/init.py:382 — submitted_response missing from AGENTS.md Response Builders table

@bokelley
bokelley force-pushed the claude/issue-1640-expected-confirmation branch from fd1b3b3 to bb11115 Compare November 2, 2026 02:33
"preview_creative_response",
"products_response",
"signals_response",
"submitted_response",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: submitted_response lands as a new public export from adcp.server / adcp.server.responses but is still absent from the Response Builders table in AGENTS.md:75. Per repo convention a new public export without a matching handler-table entry is a Medium. Add a row — submitted_response(task_id, *, expected_confirmation_at=..., message=...) → Submitted envelope → create_media_buy/buy_products/accept_proposal/refine_proposals. Flagged on the prior pass; unaddressed.

@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 — AdCP 3.3 expected_confirmation_at support added end-to-end, all additive.

What I checked:

  • Version-conversion drop rules: downgrade drop fires before _project so exactly one field_unavailable loss is recorded (test asserts single-entry list). create_media_buy/get_media_buys retain the field via _OLDER_PEER_SAFE_FIELDS on open targets. DR-0032/0033 pathway satisfied (droppable-with-recorded-loss for compact tasks, safe-keep for open ones) with tests.
  • next_status_check_delay floor/ceiling math validated (test_expected_confirmation_at.py:924); naive datetimes refused at the handoff site.
  • _is_framework_submitted_projection guard correctly re-identifies the submitted envelope now carrying a third key.
  • All changes additive → feat: is the correct semver signal; no public-surface break.
  • high_risk is true but all reasons are (modified) files with no medium-or-higher concern on them; gated_paths false; review_decision APPROVED.

Medium findings (1):

  • src/adcp/server/init.py:382 — submitted_response export missing from AGENTS.md Response Builders table.

One medium finding, no blocking issues, no escalation trigger, no team gate → falls through to row 9 (approve). This medium re-surfaced from the prior run; worth addressing but non-blocking.

Medium findings

  • src/adcp/server/init.py:382 — submitted_response missing from AGENTS.md Response Builders table

AdCP 3.3 lets a seller estimate when a submitted buy or proposal
confirms. Buyers should check status then instead of polling tightly; it is
never a failure deadline.

Buyer side:
- TaskResult.expected_confirmation_at reads the estimate from the response.
- adcp.next_status_check_delay schedules the next check from it, floored at
  the polling interval (also used once the estimate passes) and capped by a
  ceiling so an early confirmation is still seen.
- wait_for_refinement_verified uses it, with a new max_poll_interval.
- CompatibilityPurchaseOperation gains expected_confirmation_at and
  next_refresh_delay for pending legacy purchases.

Seller side:
- adcp.server.submitted_response(task_id, expected_confirmation_at=...) and
  ctx.handoff_to_task / handoff_to_workflow(..., expected_confirmation_at=...)
  put an RFC 3339 UTC estimate on the submitted envelope. Naive datetimes are
  refused; a task that does not define the field omits it with a warning.
- Version conversion drops it below 3.3 on buy_products, accept_proposal and
  refine_proposals with a recorded loss. The async submitted schemas of the
  first two let it pass through before. It is now kept on create_media_buy
  and get_media_buys for 3.0 to 3.2 callers, whose objects are open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bokelley
bokelley force-pushed the claude/issue-1640-expected-confirmation branch from bb11115 to 50f83a2 Compare November 2, 2026 04:05
"preview_creative_response",
"products_response",
"signals_response",
"submitted_response",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium: submitted_response lands as a new public export (adcp.server / adcp.server.responses) but is absent from AGENTS.md's Response Builders table (AGENTS.md:75-94). Per repo rule a new public export without a matching handler-table entry is a medium finding — add a row (submitted_response(task_id, *, expected_confirmation_at=..., message=...) → submitted envelope → create_media_buy / buy_products / accept_proposal / refine_proposals). Unchanged from the prior pass.

@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 — AdCP 3.3 expected_confirmation_at surface lands cleanly with correct version-translation pathways.

What I checked:

  • Version-translation pathways (DR-0032/DR-0033): _LATEST_LINE_ONLY_FIELDS correctly drops expected_confirmation_at below 3.3 on buy_products/accept_proposal/refine_proposals with a field_unavailable loss; _OLDER_PEER_SAFE_FIELDS keeps it on create_media_buy/get_media_buys where the object is open. Conversion direction/open-object logic is correct and reported.
  • Naive datetimes refused; _FrameworkSubmittedProjection isinstance guard correctly re-identifies the envelope with a third key.
  • 345-line test file exercises downgrade, seller, and buyer-scheduling paths with floor/ceiling bounds verified — pathway test present as required.
  • No public-API breakage (additive surface), no credential-in-metadata, no generated-code hand-edits, no import-layering violations.

Decision path: No critical/high findings (row 1 no). gated_paths false (row 2 no). high_risk true but all reasons are (modified); the single finding is a documentation medium, and no (deleted) entries (rows 3,5 no). The one medium is category docs, not data-loss/schema/infra (row 4 no). Prior decision was approve, not escalate (row 6 no). No no-auto-approve team (row 7 no). Only one medium finding, fewer than three (row 8 no). Falls through to row 9 = approve.

Medium findings:

  • src/adcp/server/init.py:382 — submitted_response is a new public export missing from the AGENTS.md Response Builders table. Standing nit re-surfaced from the prior run; non-blocking.

Medium findings

  • src/adcp/server/init.py:382 — submitted_response missing from AGENTS.md Response Builders table

@bokelley
bokelley merged commit 45a3f89 into next Nov 2, 2026
41 checks passed
@bokelley
bokelley deleted the claude/issue-1640-expected-confirmation branch November 2, 2026 05:35
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