Skip to content

feat(decisioning): align AccountStore.resolution literal with JS + Tier 1 docs#330

Merged
bokelley merged 2 commits intomainfrom
bokelley/decisioning-tier1-docs
Apr 30, 2026
Merged

feat(decisioning): align AccountStore.resolution literal with JS + Tier 1 docs#330
bokelley merged 2 commits intomainfrom
bokelley/decisioning-tier1-docs

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Summary

Round-5 design feedback applied:

  • Cross-language parity: AccountStore.resolution literal renamed ('singleton''derived', 'from_auth''implicit') to match JS reference at src/lib/server/decisioning/account.ts. Class names (SingletonAccounts, FromAuthAccounts) kept; they're Python-ergonomic with no JS counterpart.
  • Tier 1 docs (3 of 4): spec-agent vs auth-principal section in accounts.py module docstring, RequestContext "when to use which identifier" table, TaskHandoff "what it's for / not for" caveat steering adopters away from human-driven HITL.
  • Tier 1 chore(main): release 1.0.0 #4 (StateReader UserWarning) was already done in round-4 hardening — _NotYetWiredStateReader._warn_once fires per method on first call, with deterministic test coverage.

Why now

PR #316 (foundation) merged hours ago. The held release-please PR #328 is the only thing accumulating these changes — adopters are on editable installs (salesagent) or none. This is the cheapest moment to align before the literal locks in.

NOT a feat!: breaking change

The held #328 hasn't shipped to PyPI. External adopters never see the old literal values; the rename is intra-pre-release alignment. Post-merge release notes will document the literal values for adopters reading store.resolution at runtime.

Tier 2 deferred

hello_publisher.py (multi-tenant), AdagentsAccountStore, SignedRequestAuth/BearerAuth/MtlsAuth adapter wrappers — punt to 4.5.0 with RFCs. Better design data once salesagent's actual port exists as the reference.

Test plan

  • pytest tests/test_decisioning_*.py tests/test_hello_seller*.py — 155 passed
  • pytest tests/ — 2183 passed
  • ruff check clean
  • mypy src/adcp/decisioning/ clean

🤖 Generated with Claude Code

bokelley and others added 2 commits April 30, 2026 19:23
…er 1 docs

Round-5 design feedback (salesagent + cross-language parity audit):
the Python decisioning module's adopter surface drifts from the JS
reference at ``src/lib/server/decisioning/account.ts``. Aligning now,
hours post-foundation merge, before any adopter locks in.

Pre-release alignment (4.4.0 release PR is held; not on PyPI yet):

* ``AccountStore.resolution`` literal: ``'singleton' → 'derived'``,
  ``'from_auth' → 'implicit'``. ``'explicit'`` unchanged. Class names
  (``SingletonAccounts``, ``FromAuthAccounts``) kept — they're Python-
  ergonomic with no JS counterpart to drift from.

The literal is what the framework reads at server boot for
``validate_platform`` deployment checks; cross-language parity
matters because both SDKs read the same wire-shape concept. NOT a
``feat!:`` because the foundation hasn't shipped to PyPI in 4.4.0
form — the held release PR #328 carries this rename alongside the
foundation, so external adopters never see the old literal values.

Tier 1 documentation additions (3 of 4 from salesagent's
``adcp-decisioning-design-feedback.md``; the 4th — StateReader
UserWarning — was already implemented in round-4 via
``_NotYetWiredStateReader._warn_once``):

* ``accounts.py`` module docstring: "Spec-agent vs auth-layer
  principal" section. Resolves the load-bearing ambiguity adopters
  hit — what string AuthInfo.principal carries varies by auth shape
  (agent_url for AdCP v3 signed-request, OAuth subject for bearer,
  mTLS subject for client-cert). Adopters wiring FromAuthAccounts
  decide what their auth middleware projects.

* ``RequestContext`` docstring: "Identifier disambiguation — when
  to use which" table. ``account.id`` (data ownership),
  ``auth_principal`` (caller identity), ``caller_identity``
  (framework-managed cache scope key, never read directly),
  ``tenant_id`` (transport routing). Salesagent flagged this as
  cognitively heavy.

* ``TaskHandoff`` docstring: "What it's for / not for" section.
  Steers adopters away from human-driven HITL workflows where the
  background fn would block on a person's clicks. v6.1 may add a
  ``ctx.handoff_to_human()`` primitive; for v6.0, the recommended
  pattern for queued approvals is ``input-required`` + adopter-
  owned webhook emission, not TaskHandoff.

Tier 2 items (multi-tenant ``hello_publisher.py``,
``AdagentsAccountStore``, built-in auth adapter wrappers like
``SignedRequestAuth``/``BearerAuth``/``MtlsAuth``) deferred to 4.5.0
with proper RFCs — better design data once salesagent's actual port
exists as the reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…le render, doc citations

Code-reviewer + docs-expert findings on the round-5 alignment PR:

- 3 stale literal references missed by the rename pass:
  * accounts.py AccountStore.resolve docstring (``'singleton'`` /
    ``'from_auth'`` references)
  * context.py auth_info field docstring
  * handler.py _extract_auth_info comment

- RequestContext identifier table:
  * Composite cache-key format was incorrect (said ``store.qualname:
    account.id``; actual is
    ``<store_module>.<store_qualname>:<account_id>`` per
    dispatch.py:457). The ``__module__`` prefix is load-bearing for
    cross-package collision isolation.
  * "NEVER read directly in adopter code" claim was overstated —
    audit logging and rate-limiting code legitimately reads
    ``ctx.caller_identity``. Tightened to "treat as opaque; don't
    parse, compare, or rewrite" so the contract matches reality.
  * Replaced the reST grid table with a definition-list format —
    the original had a 1-char width mismatch on the
    ``caller_identity`` row that would have failed pdoc3's grid-table
    parser and fallen back to literal-block rendering. Definition
    lists render correctly in pdoc3 + ``help()`` + IDE hover without
    width-counting.

- TaskHandoff "what it's NOT for" section:
  * Added concrete pointers for the human-driven HITL pattern —
    ``input-required`` status from
    ``schemas/cache/enums/task-status.json`` + per-tool
    ``*_async_response_input_required`` envelopes; webhook
    primitives in ``adcp.webhooks`` (payload builders) +
    ``adcp.webhook_sender`` (HMAC-SHA256 signing + IP-pinned
    delivery). Worked example deferred to ``hello_publisher.py`` in
    4.5.0.

- accounts.py spec-agent vs auth-principal section:
  * Tightened the signed-request claim — the SDK's ``adcp.signing``
    primitives verify the signature, but the wiring that writes
    ``AuthInfo.principal = agent_url`` lives in adopter middleware
    today (or the built-in ``SignedRequestAuth`` adapter wrapper that
    lands in 4.5.0). Adopters wiring this manually before then should
    follow the convention so ``adcp.adagents`` reads the right key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 6490948 into main Apr 30, 2026
12 checks passed
bokelley added a commit that referenced this pull request May 1, 2026
…ionLists Protocols (breadth sprint Batch 4 — FINAL) (#335)

Final batch of the breadth-sprint per the parity audit. Ports the
remaining four specialism Protocols from JS reference. With this
PR, every spec specialism slug except ``governance-aware-seller``
has a Protocol class + REQUIRED_METHODS coverage.

New Protocols:

* ``BrandRightsPlatform`` — covers ``brand-rights``. Identity
  discovery + licensing. Required (3, sync): ``get_brand_identity``,
  ``get_rights``, ``acquire_rights``. ``acquire_rights`` returns a
  3-arm discriminated success union (acquired / pending / rejected)
  — rejection-as-data for spec-defined GRANT rejection, AdcpError
  only for buyer-fixable REQUEST rejection. Async outcomes for the
  ``pending`` arm flow via ``push_notification_config`` webhook
  (NOT a polling tool).

* ``ContentStandardsPlatform`` — covers ``content-standards``.
  Brand safety policies, content adjacency rules, per-creative
  compliance verification. Required (6): list/get/create/update,
  ``calibrate_content``, ``validate_content_delivery``. Optional
  (2, analyzer reads): ``get_media_buy_artifacts``,
  ``get_creative_features``.

* ``PropertyListsPlatform`` + ``CollectionListsPlatform``
  (specialisms/lists.py) — covers ``property-lists`` and
  ``collection-lists``. Parallel CRUD shapes (5 methods each, all
  required) with token-issuance semantics: ``create_*`` returns a
  per-seller-scoped ``fetch_token``, ``delete_*`` revokes it.
  Compromise-driven revocation MUST trigger the delete path.

Required-method coverage in ``REQUIRED_METHODS_PER_SPECIALISM``:
``brand-rights`` (3), ``content-standards`` (6),
``property-lists`` (5), ``collection-lists`` (5).

Public re-exports added at ``adcp.decisioning.__all__``:
``BrandRightsPlatform``, ``ContentStandardsPlatform``,
``PropertyListsPlatform``, ``CollectionListsPlatform``.

Test coverage in ``tests/test_decisioning_specialisms.py`` (13 new
tests, 43 total in the file):

* ``runtime_checkable`` conformance per Protocol.
* ``validate_platform`` enforcement per slug — including a
  security-relevant test that ``property-lists`` REQUIRES
  ``delete_property_list`` (revocation path) so adopters can't ship
  list-publishing without revocation primitives.
* Contract pins per slug.
* **Breadth-sprint completeness pin**:
  ``test_every_spec_slug_except_governance_aware_seller_is_enforced``
  asserts that ``SPEC_SPECIALISM_ENUM - REQUIRED_METHODS.keys()``
  yields exactly ``{governance-aware-seller, signed-requests}`` —
  the two slugs unenforced by design (composition claim and
  deprecated-moved-to-universal respectively).

One existing dispatch test updated:
``test_validate_platform_warns_on_unenforced_spec_specialism``
swapped its canonical "spec-recognized but unenforced" example
from ``brand-rights`` (now enforced) to ``governance-aware-seller``
(the only remaining unenforced spec slug — by design).

**Breadth sprint COMPLETE.** All 8 missing specialism Protocols
from the parity audit are now ported. 9 PRs total accumulating in
the held release PR #328:

* #316 foundation
* #329 codemod ergonomics
* #330 parity rename + Tier 1 docs
* #331 F12 auto-emit
* #332 Signals + Audience (Batch 1)
* #333 Creative Builder + AdServer (Batch 2)
* #334 Campaign Governance (Batch 3)
* #335 Brand + Content + Lists (Batch 4 — this PR)

Ready for salesagent validation against editable install before
tagging 4.4.0.

2252 tests pass (up from 2239).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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