feat(training-agent): implement BILLING_NOT_SUPPORTED scope: "account" sub-gate#4027
Merged
Merged
Conversation
…" sub-gate Closes the third remaining billing-gate follow-up: per-(operator, billing) restriction tested end-to-end. Distinct from the seller-wide capability gate (already implemented) and the per-buyer-agent gate (BILLING_NOT_PERMITTED_FOR_AGENT, also already implemented). Per the spec at error-details/billing-not-supported.json: "the seller's capability accepts the value generally but not for the specific operator on this account." New account-billing-relationships.ts — single helper isPerAccountBillingRestricted(operator, billing) keyed on operator domain. Documents the convention: operators whose domain ends in -no-direct-billing.example have no direct billing relationship for billing: 'operator'. Other billing models (agent, advertiser) don't depend on per-operator onboarding state. handleSyncAccounts wires the new gate between the capability gate (scope: "capability") and the per-buyer-agent gate. Returns BILLING_NOT_SUPPORTED with error.details.scope: "account" and the supported_billing echo. 4 new unit tests in account-handlers.test.ts: - operator with no-direct-billing suffix → BILLING_NOT_SUPPORTED scope: "account". - same operator with billing: agent passes (gate is operator-billing- specific — scope discipline regression-pin). - operator without the suffix passes (over-broadening regression-pin). - composition: passthrough principal still gets per-agent rejection on billing: agent (gate-ordering regression-pin — account-scope gate doesn't apply because billing is `agent`, not `operator`). Scope discipline: the per-account gate is operator-scoped, not per- buyer-agent. It fires regardless of who's calling — mirrors real- world data where operator billing eligibility is a property of the seller's onboarding state with the operator, not of the agent representing the buyer. 26/26 unit tests pass; full server suite clean (3159 passed, 42 skipped) excluding two pre-existing flaky tests (illustration-c2pa-wiring, addie-router LLM confidence tiers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the third remaining billing-gate follow-up. Distinct from the seller-wide capability gate (already implemented in #3851) and the per-buyer-agent gate (
BILLING_NOT_PERMITTED_FOR_AGENT, also in #3851). Per the spec aterror-details/billing-not-supported.json:What landed
New
account-billing-relationships.ts— single helperisPerAccountBillingRestricted(operator, billing)keyed on operator domain. Convention: operators whose domain ends in-no-direct-billing.examplehave no direct billing relationship forbilling: 'operator'. Other billing models (agent,advertiser) don't depend on per-operator onboarding state.handleSyncAccountswires the new gate between the capability gate (scope: "capability") and the per-buyer-agent gate. ReturnsBILLING_NOT_SUPPORTEDwitherror.details.scope: "account"+ thesupported_billingecho.4 new unit tests in
account-handlers.test.ts:-no-direct-billing.examplesuffix →BILLING_NOT_SUPPORTED scope: "account"billing: agentpasses (scope-discipline regression-pin: gate is operator-billing-specific)billing: agentagainst a no-direct-billing operator still gets the per-agent rejection (gate-ordering regression-pin — account-scope gate doesn't apply because billing isagent, notoperator)Scope discipline
The per-account gate is operator-scoped, not per-buyer-agent. It fires regardless of who's calling — even an agent-billable buyer agent submitting
billing: operatorfor a no-direct-billing operator gets the samescope: "account"rejection. Mirrors real-world data: the operator's billing eligibility is a property of the seller's onboarding state with the operator, not of the agent representing the buyer.Phase 2 outlook
When SDK Phase 2 ships framework-level enforcement, both billing gates (
BILLING_NOT_PERMITTED_FOR_AGENTper-agent andBILLING_NOT_SUPPORTED scope: "account"per-operator) become candidates for framework-side machinery — the agent-side gate viactx.agent.billing_capabilities(already populated since #4026), the account-side via a parallelctx.account.billing_relationshipssurface that doesn't yet exist. Until then, both stay adopter-side with the data sources colocated incommercial-relationships.ts(per-agent) andaccount-billing-relationships.ts(per-operator).Tests
account-handlers.test.tspass (was 22 before this PR; +4 new).illustration-c2pa-wiringandaddie-router LLM confidence tiers).tsc --noEmitclean.🤖 Generated with Claude Code