Skip to content

feat(protocol): the principal layer — unified webhooks, declarations, principal.changed - #7020

Open
bokelley wants to merge 2 commits into
feat/agent-configurationfrom
feat/connection-layer
Open

feat(protocol): the principal layer — unified webhooks, declarations, principal.changed#7020
bokelley wants to merge 2 commits into
feat/agent-configurationfrom
feat/connection-layer

Conversation

@bokelley

@bokelley bokelley commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Implements RFC #7015. Stacked on #6966 — the base branch is feat/agent-configuration; retarget to main after #6966 merges. Draft until the WG weighs in on the RFC's questions.

Naming update: following design review, the layer is named after its entity — the principal, the durable party (buyer agent or operator identity) behind an authenticated caller. Tasks are sync_principal / get_principal (renamed on the base PR), the capability block is adcp.principal, the feature gate is protocol.principal, and this PR's webhook is principal.changed.

The model

Three-tier hierarchy: principal (who you are, resolved from transport; buyer_agent or operator) → the principal's standing configuration with one seller (webhooks, destinations, declarations) → accounts (principal × advertiser authority). Operators an agent serves are agent-scoped labels inside that agent's principal space — each agent maps its operators its own way, so matching labels across principals confer nothing. The model composes per hop: buyer agent → Interchange → seller is two independent principal relationships, and a person in Claude buying through Interchange is one operator principal there.

What this adds

1. Principal-level webhook subscriptions. Caller-level notification_configs subscribers may carry account-anchored event types (creative.status_changed, account.change_recorded, product.*, …) with fire-time authorization scoping: each fire covers only accounts the caller is authorized for at that moment. No wildcard — enumerate event_types or set the explicit include_future_event_types: true opt-in. Caller-level and account-level subscriptions are independent; receivers dedupe by notification_id. A subscriber's active: false halts all fires across every account within the advertised suspension_interval_seconds. This flips the caller-level rejection rule #6966 ships — which is why it should be decided while the surface is experimental.

2. declarations section. Webhook fires have no request context to negotiate payload versions against; the durable declaration is that channel. The caller declares bounded consumption facts (async_adcp_versions, webhook_signing_algorithms, experimental_features); the seller returns the accepted intersection governing asynchronous interactions. Unknown experimental identifiers are excluded, not rejected; an empty signing intersection with an active subscriber fails with UNSUPPORTED_FEATURE; {} clears.

3. principal.changed webhook. Caller-anchored invalidation for seller-driven principal-state transitions (destination validatingready, setup expiring, proof invalidated, accepted intersection changed), repaired by re-reading get_principal — closing the poll loop for destination setup. Payload: principal-changed-webhook.json.

4. Resolved principal_kind + assertion fence. Every applied/current readback carries the seller-resolved party kind (buyer_agent | operator), resolved solely from the authorization system so per-party policy (e.g. billing gates) can rely on it. Requests may carry expected_principal_kind — an assertion fence rejected with CONFLICT on mismatch, never identity input.

5. Agent-scoped operator_id destination label. Per-operator isolation, audit, and one-call offboarding under an agent principal, with a normative cross-principal non-alignment rule: matching operator labels or domains across principals MUST NOT be linked, deduped, or granted shared authority.

6. Two-sided event offering. Capabilities advertise caller_event_types so buyers select from the offering instead of probing by rejection.

Also in this PR

  • Storyboard fix: destination phases gate on supported_sections (a notifications-only seller previously failed them), plus gated declarations_lifecycle and principal_kind grading.
  • Fire-time-scoped delivery grading and principal.changed emission grading are fixture-dependent → Fixture-dependent conformance for agent configuration lifecycle #7010.

Verification

16/16 principal contract tests; 28/28 MCP schema projection/analysis; schema build; compliance symbol check; storyboard lints; JSON-schema docs validation; full precommit suite; pre-push storyboard matrix with all tenants above floors.

🤖 Generated with Claude Code

Allow caller-level webhook subscribers to carry account-anchored event
types with fire-time authorization scoping — each fire covers only
accounts the caller is authorized for at fire time — plus an explicit
include_future_event_types opt-in instead of a wildcard. Add the
declarations section: buyer-declared consumption facts (async payload
versions, verifiable webhook signing algorithms, experimental opt-ins)
with a seller-computed accepted intersection that gives webhook fires
the version-negotiation channel synchronous requests get from the
envelope. Add the caller-anchored principal.changed invalidation
webhook for seller-driven principal-state transitions, repaired through
get_principal. Report the seller-resolved principal_kind (buyer_agent
or operator) on every readback with an expected_principal_kind
assertion fence, and add the agent-scoped operator_id destination label
with a normative cross-principal non-alignment rule. Advertise
caller_event_types in capabilities so buyers select from the offering
instead of probing by rejection, and gate the destination storyboard
phases on section support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bokelley bokelley changed the title feat(protocol): agent connection layer — unified webhooks, declarations, connection.changed feat(protocol): the principal layer — unified webhooks, declarations, principal.changed Aug 29, 2026
@bokelley
bokelley force-pushed the feat/connection-layer branch from 8f0ea08 to c9f2f43 Compare August 29, 2026 22:20
@bokelley
bokelley marked this pull request as ready for review August 30, 2026 03:58
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/core/principal-changed-webhook.json",
"title": "Connection Changed Webhook",

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.

(non-blocking) Title is "Connection Changed Webhook" — the old entity name in the PR that renames connection→principal. Sibling schemas already landed as "Principal State" / "Sync Principal Request"; this new schema (and principal-declarations.json"Agent Declarations", principal-declarations-state.json"Agent Declarations State", plus the new index.json "connection configuration" / "connection-state" descriptions) ships under the pre-rename name. Published title is a spec-facing symbol; worth aligning before the layer leaves experimental.

@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 schema paths require human/CODEOWNERS approval.

This PR modifies and adds files under static/schemas/source/**, which is a hard, deterministic approval gate (gated_paths: true). The required review_decision is not APPROVED (currently unknown/none), so the gate is not satisfied and the outcome cannot be approve or plain comment per decision-table row 2.

The reviewer found the change coherent: a connection→principal rename plus additive experimental extensions (principal.changed webhook, declarations negotiation, principal_kind + assertion fence, operator_id label, caller_event_types offering, account-anchored subscriptions). Schema↔docs coherence holds, the minor changeset is correctly scoped, oneOf disjointness is preserved, and the released-surface event_types widening is an additive loosening with the notification-type enum updated in lockstep. No Critical/High/Medium findings — only one non-blocking Low naming-drift straggler (new schemas titled under old "Connection"/"Agent" names) posted inline.

No blocking findings exist, but the schema-source hard gate stands until a human/CODEOWNERS approval is recorded. A human should confirm the wire-shape changes and that these additive extensions carry the appropriate Editorial/Normative (non-breaking) classification before merge.

Why human review

  • Touches gated schema-source paths under static/schemas/source/** (modified: agent-notification-config-state.json, agent-notification-config.json, agent-reporting-destination.json, principal-state.json, notification-type.json, index.json, get-adcp-capabilities-response.json, get-principal-request.json, get-principal-response.json, sync-principal-request.json, sync-principal-response.json; added: principal-changed-webhook.json, principal-declarations-state.json, principal-declarations.json) with review_decision not APPROVED — human/CODEOWNERS approval required (decision-table row 2).
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/agent-notification-config-state.json (modified) matches static/schemas/source/**; static/schemas/source/core/agent-notification-config.json (modified) matches static/schemas/source/**; static/schemas/source/core/agent-reporting-destination.json (modified) matches static/schemas/source/**; static/schemas/source/core/principal-changed-webhook.json (added) matches static/schemas/source/**; static/schemas/source/core/principal-declarations-state.json (added) matches static/schemas/source/**; static/schemas/source/core/principal-declarations.json (added) matches static/schemas/source/**; static/schemas/source/core/principal-state.json (modified) matches static/schemas/source/**; static/schemas/source/enums/notification-type.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/get-adcp-capabilities-response.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/get-principal-request.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/get-principal-response.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/sync-principal-request.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/sync-principal-response.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'unknown', 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 Aug 30, 2026
…ontract

State explicitly that the layer begins after authentication — AdCP
defines no credential-registration protocol — and add the normative
"Establishing a connection" lifecycle joining authentication, principal
configuration, account binding, and readiness. Require the explicit
all_authorized_accounts acknowledgment for account-anchored event types
on caller-level subscribers, with authorization evaluated at each
delivery attempt so revocation suppresses queued retries carrying that
account's data. Restrict include_future_event_types to types classified
invalidation-only, so the flag can never silently opt a caller into
payload-bearing events. Persist the full declarations negotiation
record: declared set, accepted intersection, the seller's
selected_async_adcp_version, and per-value exclusion reasons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bokelley

Copy link
Copy Markdown
Contributor Author

Commit 183d7ba2b1 applies the BYOBA connection-contract review to the principal layer:

  • Begins after authentication, stated normatively — AdCP defines no credential-registration protocol — plus a new "Establishing a connection" lifecycle section joining authentication → sync_principal → account binding → readiness, with the four deliberately separate objects (credential / principal record / account authorization / delivery configuration) spelled out. No register_buyer_agent task exists or is needed.
  • Explicit all_authorized_accounts: true required (schema-enforced) whenever a caller-level subscriber lists account-anchored event types — subscribing an endpoint to every account is never implicit.
  • Authorization at each delivery attempt, not enqueue time: revocation suppresses queued retries carrying that account's data.
  • include_future_event_types restricted to invalidation-only classified types — it can never silently opt a caller into payload-bearing events; the notification-type enum now requires new types to declare their classification.
  • Full declarations negotiation record persisted: declared set, accepted intersection, the seller's selected_async_adcp_version, and per-value exclusions with reasons — the durable answer to "what can this buyer implementation consume, and what did the seller decide?"

🤖 Generated with Claude Code

@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 schema-source paths require human/CODEOWNERS approval.

This PR modifies and adds files under static/schemas/source/**, which are under a hard, non-overridable approval gate (gated_paths: true). The current review_decision is unknown/none, not APPROVED, so decision-table row 2 fires: Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

The reviewer's delta pass found no Critical/High/Medium findings — the new all_authorized_accounts if/then gate fails closed over exactly the 15 account-anchored types, the added principal-declarations-state fields are schema↔docs coherent with the sync_principal.mdx updates, the changeset is appropriate for the additive experimental surface, and tests exercise the gate both directions. The diff itself is mechanically clean, but the gate is deterministic and path-based, not diff-quality-based.

Escalation reasons

  • Modifies/adds gated schema-source paths under static/schemas/source/** (modified: agent-notification-config-state.json, agent-notification-config.json, agent-reporting-destination.json, principal-state.json, notification-type.json, index.json, get-adcp-capabilities-response.json, get-principal-request.json, get-principal-response.json, sync-principal-request.json, sync-principal-response.json; added: principal-changed-webhook.json, principal-declarations-state.json, principal-declarations.json) with review_decision not APPROVED — human/CODEOWNERS review required (row 2).

The gate lifts the moment review_decision becomes APPROVED; a human approval on the thread will let a re-run fall through to a normal approve.

Why human review

  • Touches gated schema-source paths under static/schemas/source/** with review_decision not APPROVED — human/CODEOWNERS approval required (decision-table row 2). Modified: agent-notification-config-state.json, agent-notification-config.json, agent-reporting-destination.json, principal-state.json, notification-type.json, index.json, get-adcp-capabilities-response.json, get-principal-request.json, get-principal-response.json, sync-principal-request.json, sync-principal-response.json. Added: principal-changed-webhook.json, principal-declarations-state.json, principal-declarations.json.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/agent-notification-config-state.json (modified) matches static/schemas/source/**; static/schemas/source/core/agent-notification-config.json (modified) matches static/schemas/source/**; static/schemas/source/core/agent-reporting-destination.json (modified) matches static/schemas/source/**; static/schemas/source/core/principal-changed-webhook.json (added) matches static/schemas/source/**; static/schemas/source/core/principal-declarations-state.json (added) matches static/schemas/source/**; static/schemas/source/core/principal-declarations.json (added) matches static/schemas/source/**; static/schemas/source/core/principal-state.json (modified) matches static/schemas/source/**; static/schemas/source/enums/notification-type.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/get-adcp-capabilities-response.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/get-principal-request.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/get-principal-response.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/sync-principal-request.json (modified) matches static/schemas/source/**; static/schemas/source/protocol/sync-principal-response.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve

…(truncated)…

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