Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
154 commits
Select commit Hold shift + click to select a range
5657f80
fix: A2A top-level failures return failed Task with AdCP envelope, no…
numarasSigmaSoftware Jul 6, 2026
8d78cb7
fix: Cover A2A failed task routing
numarasSigmaSoftware Jul 9, 2026
8fca519
Merge branch 'prebid:main' into pr/a2a-error-routing
numarasSigmaSoftware Jul 9, 2026
e6ef756
test: address non-blocking review follow-ups on A2A error routing
numarasSigmaSoftware Jul 9, 2026
7d540d0
refactor: share the failed-Task envelope reader across test altitudes
numarasSigmaSoftware Jul 10, 2026
a5248fe
test: harden the shared failed-Task reader pins
numarasSigmaSoftware Jul 10, 2026
c51e76c
test: address round-3 review — BDD merge durability, helper DRY, read…
numarasSigmaSoftware Jul 13, 2026
51d7b33
Merge remote-tracking branch 'upstream/main' into pr/a2a-error-routing
numarasSigmaSoftware Jul 13, 2026
4a5ee33
test: fold in round-4 review polish (naming, guard hardening, spec co…
numarasSigmaSoftware Jul 13, 2026
1bc4185
fix: route A2A skill failures to failed Tasks; drop redundant failure…
numarasSigmaSoftware Jul 13, 2026
acfe455
fix: no webhook for immediate terminal A2A tasks; preserve mixed-batc…
numarasSigmaSoftware Jul 13, 2026
8ae774c
fix: preserve duplicate-named artifacts in A2A webhook payload
numarasSigmaSoftware Jul 13, 2026
a5132c9
fix: reject multi-skill batches; sanitize untyped crashes; harden A2A…
numarasSigmaSoftware Jul 13, 2026
fe4c23d
merge: upstream/main into pr/a2a-error-routing
numarasSigmaSoftware Jul 14, 2026
5a23a65
fix: close explicit-skill secret leak + orphan Task; de-advertise stu…
numarasSigmaSoftware Jul 14, 2026
176a5d4
fix: sanitize typed internal-error leaks + correlate A2A task id to w…
numarasSigmaSoftware Jul 14, 2026
0c10422
fix: durable tasks/cancel + one webhook per workflow status change
numarasSigmaSoftware Jul 14, 2026
d037cc0
merge: upstream/main into pr/a2a-error-routing (adcp 6.6.0, #1417/#1430)
numarasSigmaSoftware Jul 14, 2026
ee13a6d
fix: close round-12 blockers — JSON-RPC message scrub, principal-scop…
numarasSigmaSoftware Jul 15, 2026
ea44fe7
fix: close round-13 blockers — in-memory principal isolation + two-si…
numarasSigmaSoftware Jul 15, 2026
e9c2514
fix: round-14 B1+B2 — atomic update_workflow_step + callers honor ref…
numarasSigmaSoftware Jul 15, 2026
2428449
fix: round-15 — admin writers atomic, cancellable-state policy (B3), …
numarasSigmaSoftware Jul 15, 2026
daacade
docs: correct _durable_cancel_step docstring to cancel_if_cancellable…
numarasSigmaSoftware Jul 15, 2026
c806db2
fix: exclude in_progress from cancellable workflow-step statuses
numarasSigmaSoftware Jul 15, 2026
87b5141
fix: source-state-guarded compare-and-set for workflow approve/reject
numarasSigmaSoftware Jul 15, 2026
a764510
fix: media-buy detail route selects approvable steps via the canonica…
numarasSigmaSoftware Jul 16, 2026
f300986
test: render-grade the media-buy detail approval UI for legacy approv…
numarasSigmaSoftware Jul 16, 2026
4400d1b
fix: harden task auth and approval selection
numarasSigmaSoftware Jul 16, 2026
b466601
fix: scrub internal-error messages on the webhook path via one shared…
numarasSigmaSoftware Jul 16, 2026
0f75f1f
refactor: drop dead cancel_if_nonterminal; make legacy approval cance…
numarasSigmaSoftware Jul 16, 2026
34fe71f
test: break auth-boundary tautology + add merge-durability self-test
numarasSigmaSoftware Jul 16, 2026
cd7c087
refactor: fold in review minors (DRY durable preamble, type hints, as…
numarasSigmaSoftware Jul 16, 2026
75fdf24
fix: sanitized suggestion must match recovery — terminal errors must …
numarasSigmaSoftware Jul 16, 2026
6c07d8b
fix: normalize sanitized internal-error recovery to canonical + 3-way…
numarasSigmaSoftware Jul 16, 2026
5b5d799
fix: scrub untyped ValueError/PermissionError on the webhook audit path
numarasSigmaSoftware Jul 16, 2026
dc59b0f
fix: separate exception provenance from semantic code in the shared s…
numarasSigmaSoftware Jul 16, 2026
8e3f84c
fix: category-specific sanitized message + suggestion so text matches…
numarasSigmaSoftware Jul 16, 2026
75fc3d0
fix: enforce sanitized-category registry completeness vs built-in nor…
numarasSigmaSoftware Jul 17, 2026
1b75743
Merge remote-tracking branch 'upstream/main' into pr/a2a-error-routing
numarasSigmaSoftware Jul 17, 2026
17d774c
fix: unify every raw-exception normalizer into one introspectable reg…
numarasSigmaSoftware Jul 17, 2026
6f287d3
fix: derive normalizer target from the factory, not a separate declar…
numarasSigmaSoftware Jul 17, 2026
2c37087
fix: pin exception normalizer semantics
numarasSigmaSoftware Jul 17, 2026
e3bf24e
fix: correlate admin webhooks to buyer task IDs
numarasSigmaSoftware Jul 18, 2026
e29a490
chore: retrigger CI
numarasSigmaSoftware Jul 19, 2026
5e99d68
fix: review remediation — pollable failed A2A Task + scrub-family con…
numarasSigmaSoftware Jul 21, 2026
5bf1821
Merge remote-tracking branch 'upstream/main' into pr/a2a-error-routing
numarasSigmaSoftware Jul 21, 2026
832aaba
test: drive on_get_task in the failed-Task pollability regression; st…
numarasSigmaSoftware Jul 21, 2026
41cdfae
refactor: route the last stragglers through their shared handles (A2A…
numarasSigmaSoftware Jul 22, 2026
bd0545f
Merge remote-tracking branch 'upstream/main' into pr/a2a-error-routing
numarasSigmaSoftware Jul 22, 2026
b1b2076
fix: sanitize untyped failures at the tasks/get and tasks/cancel boun…
numarasSigmaSoftware Jul 22, 2026
c8ca35a
test: one strict failed-Task reader; recovery required on the shared …
numarasSigmaSoftware Jul 22, 2026
78ba8c4
test: route every hand-rolled secret check through the shared leak or…
numarasSigmaSoftware Jul 22, 2026
5d266a8
fix: ground sanitized suggestions in the pinned spec enum
numarasSigmaSoftware Jul 22, 2026
6e08548
refactor: one sanitize composition, no dead machinery in on_message_send
numarasSigmaSoftware Jul 22, 2026
34d3c3a
refactor: one name for the terminal-guarded workflow transition
numarasSigmaSoftware Jul 22, 2026
c55c904
fix: policy-route tenant gate + step-type filter; unknown-action not …
numarasSigmaSoftware Jul 22, 2026
8284a90
test: enforce the A2A external_task_id forwarding (guard + behavioral)
numarasSigmaSoftware Jul 22, 2026
c005712
test: merge-durability guard catches empty/unmodeled refs; tighten co…
numarasSigmaSoftware Jul 22, 2026
269ea52
refactor: strip cross-PR back-references; harden two A2A wire nits
numarasSigmaSoftware Jul 22, 2026
4395a92
chore: formatting, lint, and durable-test docstring accuracy
numarasSigmaSoftware Jul 22, 2026
32e7aa9
test: read the C3 forwarding assertion via the repository, not a raw …
numarasSigmaSoftware Jul 22, 2026
806e52f
test: close the last oracle bypasses and pin the readers that grade them
numarasSigmaSoftware Jul 23, 2026
cfdeb1e
fix: one error framing for a failed Task, polled or synchronous
numarasSigmaSoftware Jul 23, 2026
99f4ff8
refactor: one reading of the acting user; canonical approvable set on…
numarasSigmaSoftware Jul 23, 2026
c0daf64
docs: correct claims that outran the code
numarasSigmaSoftware Jul 23, 2026
98ace2d
refactor: route the admin policy read and the refused-claim redirect …
numarasSigmaSoftware Jul 23, 2026
090bbe6
test: one home for the failed-Task client-facing scrub surface
numarasSigmaSoftware Jul 23, 2026
b13146e
docs: restore whitespace in prose comments; drop unresolvable local t…
numarasSigmaSoftware Jul 23, 2026
48989f7
fix: every A2A auth failure carries the AUTH_REQUIRED envelope, from …
numarasSigmaSoftware Jul 23, 2026
86edccc
test: the secret-leak oracle now defends itself
numarasSigmaSoftware Jul 23, 2026
d7abf17
refactor: shared failed-artifact locator, hoisted import, positive co…
numarasSigmaSoftware Jul 23, 2026
19bb1a8
fix: one enveloped source for every A2A rejection, graded arm by arm
numarasSigmaSoftware Jul 23, 2026
0a6c467
test: the secret-oracle self-test no longer grades itself
numarasSigmaSoftware Jul 23, 2026
e0e40f4
test: dedupe the approval setup; widen and anchor the repository guard
numarasSigmaSoftware Jul 23, 2026
916688e
test: pin the auth envelope's message/suggestion; dedupe the last app…
numarasSigmaSoftware Jul 24, 2026
8350ba3
test: pin the AUTH_REQUIRED envelope on the real JSON-RPC wire, not j…
numarasSigmaSoftware Jul 24, 2026
33bb1f2
fix: dedupe the A2A boundary-error arm; tighten CAS guard typing; fix…
numarasSigmaSoftware Jul 24, 2026
47d6c76
Merge upstream/main into pr/a2a-error-routing
numarasSigmaSoftware Jul 24, 2026
c945a39
test: update isolation tests for the merged TaskNotFoundError behavior
numarasSigmaSoftware Jul 24, 2026
b031a17
test: make webhook dedup oracle deterministic
numarasSigmaSoftware Jul 24, 2026
ee8743b
fix: record outer A2A task failures
numarasSigmaSoftware Jul 25, 2026
63cbf33
test: close PR 1547 review gaps
numarasSigmaSoftware Jul 25, 2026
22c1c72
fix: distinguish A2A authentication failures
numarasSigmaSoftware Jul 25, 2026
95d6065
fix: classify malformed A2A credentials
numarasSigmaSoftware Jul 25, 2026
33b8fea
fix: close A2A auth review gaps
numarasSigmaSoftware Jul 25, 2026
e771533
fix: harden cross-transport error boundaries
numarasSigmaSoftware Jul 25, 2026
8f11cdc
fix: close validation and approval consistency gaps
numarasSigmaSoftware Jul 25, 2026
5b41082
fix: close validation and approval consistency gaps
numarasSigmaSoftware Jul 27, 2026
61093b1
fix: repair PR 1547 CI contracts
numarasSigmaSoftware Jul 27, 2026
dc1dc97
fix: close remaining PR 1547 review gaps
numarasSigmaSoftware Jul 28, 2026
a2bdd62
fix: align approval webhook fixture with catalog
numarasSigmaSoftware Jul 28, 2026
3e52582
test: align creative format mocks with protocol types
numarasSigmaSoftware Jul 28, 2026
27803bf
fix: preserve sanitized date validation contract
numarasSigmaSoftware Jul 28, 2026
a01544f
refactor: single-source the sanitized scrub text selection
numarasSigmaSoftware Jul 28, 2026
5af3f4f
fix: one adapter-agnostic message for the waiting-for-creatives outcome
numarasSigmaSoftware Jul 28, 2026
540dd0a
refactor: type the two new signatures this branch left bare
numarasSigmaSoftware Jul 28, 2026
7d7e8ca
fix: log the original exception at the REST boundary, not its sanitiz…
numarasSigmaSoftware Jul 28, 2026
335fe38
test: grade the untyped-crash arm the top-level-failure locks were na…
numarasSigmaSoftware Jul 28, 2026
f047bd6
test: expose the NL A2A wire envelope as a TransportResult
numarasSigmaSoftware Jul 28, 2026
685ed0c
fix: frame NL client-correctable failures as failed Tasks, not Intern…
numarasSigmaSoftware Jul 28, 2026
d91fc38
refactor: route the push-config handlers through the shared boundary arm
numarasSigmaSoftware Jul 28, 2026
a0a6562
fix: make the pinned-schema provenance a checkable claim, not a label
numarasSigmaSoftware Jul 28, 2026
ce45f9a
docs: name the internal-bucket suggestion deviation and why it stands
numarasSigmaSoftware Jul 28, 2026
e7ec1cb
test: stop the wire-message containment step from also checking absence
numarasSigmaSoftware Jul 29, 2026
bf48d5c
test: prove rejected credentials never reach natural-key resolution
numarasSigmaSoftware Jul 29, 2026
c108982
docs: ground the buyer-value-echo opt-in in the AdCP spec, not just p…
numarasSigmaSoftware Jul 29, 2026
c6c92b8
fix: break the circular oracle in assert_sanitized_wire_error
numarasSigmaSoftware Jul 29, 2026
2985cf6
test: exercise the buyer-value-echo opt-in through the real wire, clo…
numarasSigmaSoftware Jul 29, 2026
fe19e95
test: assert no system secret rides alongside the echoed buyer value
numarasSigmaSoftware Jul 29, 2026
d209eb0
fix: skip the natural-key lookup spy on e2e transports, not just repo…
numarasSigmaSoftware Jul 29, 2026
5e53f0c
fix: single-source the PENDING_RECONCILIATION operator message
numarasSigmaSoftware Jul 29, 2026
841ced9
refactor: type four helpers to the concrete type already used a few l…
numarasSigmaSoftware Jul 29, 2026
21001c5
docs: ground list_accounts auth-required in the spec, not a rule this…
numarasSigmaSoftware Jul 29, 2026
c4e2506
merge: pull in datamodel-code-generator CVE fix from upstream/main
numarasSigmaSoftware Jul 29, 2026
1dd5280
merge: resolve #1697 SSRF collision, keeping this branch's webhook-sc…
numarasSigmaSoftware Jul 30, 2026
9deda75
fix: honor the dev callback seam at webhook registration, not only de…
numarasSigmaSoftware Jul 30, 2026
4889c1a
test: stop patching the SSRF gate the comment says not to patch; drop…
numarasSigmaSoftware Jul 30, 2026
13483b2
fix: let audited static validation messages reach the buyer
numarasSigmaSoftware Jul 30, 2026
399bb3c
fix: register and deliver webhooks under one HTTPS policy
numarasSigmaSoftware Jul 30, 2026
97a6976
fix: tell the operator to assign creatives instead of "waiting for 0"
numarasSigmaSoftware Jul 30, 2026
6756c3e
test: give _atomic_transition's tenant predicate a failing oracle
numarasSigmaSoftware Jul 30, 2026
5abd6af
test: dispatch the NL A2A step through the shared harness seam
numarasSigmaSoftware Jul 30, 2026
a436d04
docs: correct the AUTH_OPTIONAL_SKILLS grounding to what the spec says
numarasSigmaSoftware Jul 30, 2026
ee70d23
fix: put the callback HTTPS rule where a testing flag cannot reach it
numarasSigmaSoftware Jul 30, 2026
edb898b
fix: keep the buyer's suggestion when a raise site opts its message in
numarasSigmaSoftware Jul 30, 2026
2dfe3d7
fix: say where the two-layer envelope actually reaches the buyer
numarasSigmaSoftware Jul 30, 2026
562364e
test: grade two behaviours that were prose-only
numarasSigmaSoftware Jul 30, 2026
2b59b0d
test: make the auth non-disclosure check see the leak it names
numarasSigmaSoftware Jul 30, 2026
cf05bb4
test: stop the SSRF host tests short-circuiting on scheme
numarasSigmaSoftware Jul 30, 2026
3ab463a
fix: keep the non-disclosure step — it was tested, not dead
numarasSigmaSoftware Jul 30, 2026
c3b6a10
fix: require HTTPS for callbacks unless development is declared expli…
numarasSigmaSoftware Jul 31, 2026
e08193d
fix: one scheme policy across all three webhook delivery sinks
numarasSigmaSoftware Jul 31, 2026
97ec54f
test: restore the discriminator the suggestion-parity suite grades
numarasSigmaSoftware Jul 31, 2026
649ed61
test: make the opt-in guard check the direction that can leak
numarasSigmaSoftware Jul 31, 2026
8cceed3
test: match the re-inline guard on the clause that survives a drift
numarasSigmaSoftware Jul 31, 2026
7e3c9e1
test: make the cancel/approval TOCTOU ordering actually race
numarasSigmaSoftware Jul 31, 2026
c68dc41
Merge upstream/main: adopt the SDK's own schema tree as the single pin
numarasSigmaSoftware Aug 12, 2026
c0d7d2a
fix(test): forward the REST auth headers the capabilities harness dro…
numarasSigmaSoftware Aug 12, 2026
9eaa7a1
fix: stop four failures that were invisible to the buyer or the operator
numarasSigmaSoftware Aug 12, 2026
17a0c29
fix: update_media_buy was the third async skill with no persisted tas…
numarasSigmaSoftware Aug 12, 2026
c456e75
fix: one home for approval-execution eligibility; revert the webhook …
numarasSigmaSoftware Aug 12, 2026
dea3df8
fix: grade the creative gate on real creative statuses only [order R1-4]
numarasSigmaSoftware Aug 13, 2026
80cde69
fix: validate update_media_buy push-notification callbacks [order R1-5]
numarasSigmaSoftware Aug 13, 2026
5b9f3a2
test: add known-bad self-tests for the two unguarded scrub oracles [o…
numarasSigmaSoftware Aug 13, 2026
e4293de
fix: derive complete_task pre-check from the canonical terminal set […
numarasSigmaSoftware Aug 13, 2026
1bf9718
fix: forward idempotency_key, reporting_webhook and ext from the A2A …
numarasSigmaSoftware Aug 13, 2026
00ca307
docs: correct two stale BDD conftest comments [order R1-13]
numarasSigmaSoftware Aug 13, 2026
475f87b
fix: log every creative-approval identifier through log_safe [order R…
numarasSigmaSoftware Aug 13, 2026
6b1a47e
refactor: apply the mechanical review nits [order R1-NITS]
numarasSigmaSoftware Aug 13, 2026
0067454
fix: forward the flight and economics parameters the A2A update skill…
numarasSigmaSoftware Aug 13, 2026
a52f1ac
fix: stop the approval service logging a refused transition as a comp…
numarasSigmaSoftware Aug 13, 2026
fec0a3b
fix: principal-scope the MCP task tools [order R3-1]
numarasSigmaSoftware Aug 13, 2026
d82b3a3
fix: update_media_buy rejected every callback, not just unsafe ones
numarasSigmaSoftware Aug 13, 2026
e36919d
fix: stop threading the outer task id into sync_creatives [order R4-1]
numarasSigmaSoftware Aug 14, 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 .mypy-untyped-defs-baseline
Original file line number Diff line number Diff line change
@@ -1 +1 @@
227
212
6 changes: 3 additions & 3 deletions .ruff-complexity-baseline
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"C901": 183,
"PLR0912": 134,
"PLR0915": 108
"C901": 181,
"PLR0912": 133,
"PLR0915": 106
}
7 changes: 7 additions & 0 deletions docker-compose.e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ services:
SUPER_ADMIN_EMAILS: ${SUPER_ADMIN_EMAILS:-}
ADCP_TESTING: ${ADCP_TESTING:-true}
ADCP_AUTH_TEST_MODE: ${ADCP_AUTH_TEST_MODE:-true}
# Exact callback host admitted by the development-only protocol webhook
# seam. Standalone host tests override this with host.docker.internal;
# the in-network runner uses its stable "tests" alias.
ADCP_WEBHOOK_TEST_HOST: ${ADCP_WEBHOOK_TEST_HOST:-tests}
# The SERVER must know about the pinned in-network creative agent: sync/
# create flows resolve format specs server-side, and references carry the
# PUBLIC canonical agent_url — without this, the registry's connection
Expand Down Expand Up @@ -180,6 +184,9 @@ services:
# network alias (requires `docker compose run --use-aliases`). 'tests' is
# not 'localhost', so the server does NOT rewrite it to host.docker.internal.
ADCP_WEBHOOK_HOST: tests
# Exact in-network callback host allowed only by the development-mode
# protocol-webhook validator. Production has no private-host override.
ADCP_WEBHOOK_TEST_HOST: tests
# Pinned reference creative agent, reachable in-network by service name
# (no host :9999). Clears the 18 test_creative_agent_live integration tests.
CREATIVE_AGENT_URL: http://creative-agent:8080/api/creative-agent
Expand Down
6 changes: 6 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ services:
ADCP_SALES_PORT: "8080"
# Enable test login mode for development
ADCP_AUTH_TEST_MODE: "true"
# Declare development EXPLICITLY. Security gates that relax for development fail
# closed on an absent or unrecognised value, so the relaxation has to be stated
# rather than inferred from omission — see
# WebhookURLValidator._require_https_for_callback, which requires HTTPS for AdCP
# callbacks unless this reads "development". docker-compose.e2e.yml already sets it.
ENVIRONMENT: development
# Development settings (venv at /opt/venv in image — see Dockerfile UV_PROJECT_ENVIRONMENT)
PYTHONPATH: "/app"
PYTHONUNBUFFERED: "1"
Expand Down
93 changes: 74 additions & 19 deletions docs/adcp-spec-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,26 @@ uv run python -c "import adcp; print(adcp.get_adcp_spec_version())"
across `pyproject.toml`, the test's `EXPECTED_SPEC_VERSION` constant, and
this document.

## Behavior target vs SDK pin

The SDK **pin** (3.1.0-beta.3) fixes the request/response *type shapes* we
build against. It does **not** always fix the graded *behavior*. One field
diverges deliberately: the `media_buy_status` dual-emit on
create-/update-media-buy responses.

- **beta.3 storyboard** (`dist/compliance/3.1.0-beta.3/.../pending_creatives_to_start.yaml`,
~L131-134) grades the body `status` as `field_value_or_absent` that MUST equal
`media_buy_status` — the deprecated "both identical" model (#4908).
- **Target GA** — graded by the published **3.1.0** compliance
(`dist/compliance/3.1.0/.../pending_creatives_to_start.yaml`, ~L146-153;
`3.1.1` is byte-identical for this storyboard) — grades `media_buy_status`
as `field_value` (the DOMAIN status) and the top-level `status` as
`field_value` `'completed'` (the PROTOCOL `TaskStatus`, protocol envelope).
The two are DIFFERENT namespaces and are NOT identical.

Our wire already implements the divergent (target GA) model:
## Behavior sources vs SDK pin

The pin and the behavior target are the same version (3.1.1). What differs is
the ROLE of each artifact: the SDK **pin** fixes the request/response *type
shapes* we build against, and it does not replace the authoritative prose and
compliance storyboards for protocol behavior. For example, the published
**3.1.1** `pending_creatives_to_start.yaml` storyboard grades `media_buy_status`
as `field_value` (the DOMAIN status) and the top-level `status` as
`field_value` `'completed'` (the PROTOCOL `TaskStatus`, protocol envelope).
The two are DIFFERENT namespaces and are NOT identical.

Our wire implements that two-namespace model:
`TaskResultEnvelope._serialize` sets the top-level `status` to the protocol
`TaskStatus`, while the domain status survives under `media_buy_status`
(`src/core/schemas/_base.py` `_mirror_media_buy_status`). The dual-emit
validator only backfills the deprecated **body** `status` from the domain
`media_buy_status` for the deprecation window; it does not touch the wire
top-level `status`.

**Known SDK type defect (SDK not authoritative):** adcp 5.7 types the response
**Historical SDK type defect (SDK not authoritative):** adcp 5.7 typed the response
`status` as `MediaBuyStatus | None`, but the wire top-level `status` carries a
protocol `TaskStatus` (`submitted` / `completed`). This is fine because that
protocol value lives on `TaskResultEnvelope.status` (typed `str`), never on the
Expand All @@ -77,6 +71,67 @@ SDK-typed body field. Grounding for the divergent behavior is the value-pinned
`tests/bdd/steps/domain/uc002_create_media_buy.py` (see PR #1417).
`tests/unit/test_adcp_spec_version.py` only guards the SDK pin, not this behavior.

## Authentication error classification

The immutable AdCP **v3.1.1** source is tag `v3.1.1`, commit
`467fd93d77112baf9e094e18980119edcd3a4d07`. Its
`static/schemas/source/enums/error-code.json` metadata requires sellers to
return:

- `AUTH_MISSING` when no standard `Authorization` header was included
(`correctable`: provide credentials and retry).
- `AUTH_INVALID` when an `Authorization` header was present but rejected
(`terminal`, except the spec's one-time OAuth refresh allowance).

Prebid Sales Agent applies that split at every A2A, MCP, and REST wire
boundary. The legacy `x-adcp-auth` extension remains accepted as an input
channel, but it does not change the standard header-presence classifier.
Direct `_impl` helpers retain deprecated `AUTH_REQUIRED` only where no wire
credential-presence information exists.

This behavior is **ungraded** by the official conformance storyboards:
`dist/compliance/3.1.1/universal/error-compliance.yaml` contains no
`AUTH_*` scenario. The two codes are covered by the repository's cross-transport
wire tests, and by the pinned fixture at
`tests/fixtures/adcp_schemas_pinned/enums/error-code.json`.

### What the pinned fixture does and does not vendor

The fixture tree is vendored at commit `04f59d2d5` (the v3.1 cut, 2026-05-13), **not**
at `v3.1.1`. The two AUTH entries above are transplanted from `467fd93d` and declared as
a supplement in `tests/fixtures/adcp_schemas_pinned/_refresh.py`, because production
emits both codes and the recovery-conformance guard resolves them against this file.

So the fixture is **not** a complete v3.1.1 vocabulary: it carries the base commit's
codes plus exactly that declared supplement, and it lags published 3.1.1 by a number of
codes and several `enumMetadata` suggestion texts. That gap is real and tracked
separately — the point of declaring it is that the tree no longer claims a fidelity it
does not have.

`tests/unit/test_pinned_schema_provenance.py` enforces the claim against the bytes:
`PINNED_SHA` must match the generated `_manifest.py`, every vendored file must match its
recorded digest, and the error-code enum must equal the base vocabulary plus exactly the
declared supplement. Advancing the pin therefore requires actually re-running
`_refresh.py` — editing the constant alone fails.

## Protocol callback URL transport policy

The pinned AdCP **v3.1.1** source defines
`static/schemas/source/core/push-notification-config.json#properties/url` as a
generic URI. It does not mandate acceptance of plaintext HTTP callback URLs.
The authoritative
`dist/compliance/3.1.1/universal/webhook-emission.yaml` storyboard (version
1.3.0) uses an operator-supplied **HTTPS** receiver in `proxy_url` mode and
grades payload idempotency, operation correlation, retry behavior, and RFC 9421
signing. It does not grade whether an implementation accepts HTTP callback
registration.

Prebid Sales Agent therefore treats HTTPS-only production callbacks as an
implementation security policy: production rejects HTTP before persisting or
sending a callback, while the exact-host development seam remains available
for the Docker E2E receiver. This restriction is **ungraded** by the 3.1.1
storyboard and does not replace its signing or idempotency requirements.

## Wire negotiation

AdCP wire values for `adcp_version` are release-precision (`"3.0"`,
Expand Down
14 changes: 7 additions & 7 deletions docs/test-obligations/BR-UC-011-manage-accounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ High impact. Account management is a new protocol domain in adcp 3.x. The schema
**Layer** behavioral
**Given** no authentication
**When** the buyer sends `sync_accounts`
**Then** the request is rejected with AUTH_REQUIRED
**Then** every wire transport rejects the request with AUTH_MISSING
**Business Rule:** BR-12
**Priority:** P0

Expand Down Expand Up @@ -209,14 +209,14 @@ High impact. Account management is a new protocol domain in adcp 3.x. The schema
**Business Rule:** BR-10
**Priority:** P0

### Extension A: AUTH_REQUIRED
### Extension A: Authentication errors

#### Scenario: Missing auth token on sync_accounts
**Obligation ID** UC-011-EXT-A-01
**Layer** behavioral
**Given** no Bearer token in request
**When** the buyer sends `sync_accounts`
**Then** the response is error variant with `AUTH_REQUIRED`
**Then** every wire transport returns an error variant with `AUTH_MISSING`
**And** no accounts are modified
**And** context is echoed
**Business Rule:** BR-12, POST-F1
Expand All @@ -227,15 +227,15 @@ High impact. Account management is a new protocol domain in adcp 3.x. The schema
**Layer** behavioral
**Given** an expired Bearer token
**When** the buyer sends `sync_accounts`
**Then** the response is error variant with `AUTH_REQUIRED`
**Then** every wire transport returns an error variant with `AUTH_INVALID`
**Priority:** P1

#### Scenario: Malformed auth token on sync_accounts
**Obligation ID** UC-011-EXT-A-03
**Layer** behavioral
**Given** a malformed Bearer token
**When** the buyer sends `sync_accounts`
**Then** the response is error variant with `AUTH_REQUIRED`
**Then** every wire transport returns an error variant with `AUTH_INVALID`
**Priority:** P1

### Extension B: SYNC_PARTIAL_FAILURE
Expand Down Expand Up @@ -394,7 +394,7 @@ High impact. Account management is a new protocol domain in adcp 3.x. The schema
**Obligation ID** UC-011-EXT-G-03
**Layer** schema
**Given** an unauthenticated buyer sending sync_accounts with `context: {"trace": "t1"}`
**When** the AUTH_REQUIRED error is returned
**When** the wire transport returns AUTH_MISSING
**Then** the error response includes `context: {"trace": "t1"}`
**Business Rule:** POST-F3
**Priority:** P1
Expand Down Expand Up @@ -430,7 +430,7 @@ High impact. Account management is a new protocol domain in adcp 3.x. The schema
#### Scenario: sync-accounts-response error variant
**Obligation ID** UC-011-SCHEMA-03
**Layer** schema
**Given** an operation-level error response (e.g., AUTH_REQUIRED)
**Given** an operation-level error response (e.g., AUTH_MISSING, AUTH_INVALID, or AUTH_REQUIRED)
**When** serialized
**Then** it validates against `sync-accounts-response.json` (error oneOf variant)
**And** has errors array
Expand Down
6 changes: 4 additions & 2 deletions docs/test-obligations/UC-002-create-media-buy.md
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,8 @@ Source: UC-002-ext-c.md, BR-RULE-013
**Layer** schema
**Given** `start_time` is an ISO 8601 datetime that is in the past
**When** the system validates timing
**Then** it returns error: "Invalid start time: {value}. Start time cannot be in the past."
**Then** it returns `INVALID_REQUEST` with static message "Start time cannot be in the past."
**And** the error identifies `field: start_time`, includes a corrective suggestion, and does not echo request timestamps
**Business Rule:** BR-RULE-013 INV-2
**Priority:** P0

Expand All @@ -777,7 +778,8 @@ Source: UC-002-ext-c.md, BR-RULE-013
**Layer** schema
**Given** `end_time` is before or equal to `start_time`
**When** the system validates timing
**Then** it returns error: "Invalid time range: end time ({end}) must be after start time ({start})."
**Then** it returns `INVALID_REQUEST` with static message "End time must be after start time."
**And** the error identifies `field: end_time`, includes a corrective suggestion, and does not echo request timestamps
**Business Rule:** BR-RULE-013 INV-3
**Priority:** P0

Expand Down
6 changes: 6 additions & 0 deletions docs/test-obligations/bdd-traceability.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,12 @@ mappings:
upstream_refs: ["BR-UC-002-ext-d"]
business_rules: []
status: new
- adcp_scenario_id: "T-UC-002-ext-nl-unsupported"
adcp_feature: "BR-UC-002-create-media-buy.feature"
obligation_id: null
upstream_refs: ["transport-errors.mdx#Layer-Separation"]
business_rules: []
status: new
- adcp_scenario_id: "T-UC-002-ext-e"
adcp_feature: "BR-UC-002-create-media-buy.feature"
obligation_id: null
Expand Down
15 changes: 8 additions & 7 deletions docs/test-obligations/business-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -931,16 +931,17 @@ Then an empty accounts array is returned (not an error)
### BR-RULE-055: Account Operation Authentication Policy
**Obligation ID** BR-RULE-055-01
**Layer** behavioral
**Invariant:** sync_accounts requires valid auth. list_accounts works without auth but scopes results. Unauthenticated list returns empty array.
**Invariant:** sync_accounts and list_accounts require valid auth. list_accounts scopes results to accounts visible to the authenticated agent.
**Grounded in:** dist/docs/3.1.1/accounts/tasks/list_accounts.mdx:8 ("Returns all accounts the authenticated agent can operate..."); dist/docs/3.1.1/protocol/required-tasks.mdx:118 (list_accounts discovers "seller-assigned accounts" for a resolved credential, unlike the plain "Required" no-auth-caveat discovery tasks in the same table). Not a single explicit "MUST require authentication" sentence — inferred consistently across both passages from a task that is meaningless without a resolved identity to scope against.
**Scenario:**
```gherkin
Given no valid authentication
When sync_accounts is called
Then AUTH_REQUIRED error is returned

Given no authentication
When list_accounts is called
Then an empty accounts array is returned (not an error)
When sync_accounts or list_accounts is called
Then every wire transport returns AUTH_MISSING

Given rejected authentication credentials
When sync_accounts or list_accounts is called
Then every wire transport returns AUTH_INVALID
```
**Priority:** P0
**Affected by 3.6:** Yes -- accounts domain is new in v3
Expand Down
9 changes: 6 additions & 3 deletions docs/test-obligations/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -1635,11 +1635,14 @@ Then status=pending_approval with setup.message
### account_auth_policy: Account Authentication Policy
**Obligation ID** CONSTR-ACCOUNT-AUTH-POLICY-01
**Layer** behavioral
**Requirement:** sync_accounts requires valid auth. list_accounts allows anonymous (empty results).
**Requirement:** sync_accounts and list_accounts require valid authentication.
**Scenario:**
```gherkin
Given no auth on sync_accounts
Then AUTH_REQUIRED error
Given no auth on sync_accounts or list_accounts
Then every wire transport returns AUTH_MISSING

Given rejected auth on sync_accounts or list_accounts
Then every wire transport returns AUTH_INVALID
```
**Priority:** P0
**Affected by 3.6:** Yes -- accounts domain is new in v3
Expand Down
Loading
Loading