Repository navigation
Conversation
Add the pure expectedSubscriptionPeriods rule for AdCP 3.3 principal-level reporting subscriptions (experimental media_buy.reporting_subscriptions), the normative period_generation calendar it uses, selector resolution (exact, finest, all with byte-order ties), scope membership, and a builder for the 3.3 registration capabilities (configuration_tasks, the deprecated configuration_task, and the subscriptions block). createAdcpServerFromPlatform adds media_buy.reporting_subscriptions to experimental_features only beside a subscriptions block. installConfiguration now throws a typed ReportingConfigurationImmutableError with the unchanged message. The cross-SDK parity table is test/fixtures/reporting-subscriptions/coverage-cases.json. Refs #3504 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…al ships Advertising media_buy.reporting_subscriptions also requires adcp.principal.supported_sections to list reporting_subscriptions, which the SDK does not emit yet. Keep the capability builder pure in this slice and add the feature id together with the principal block in the runtime slice, so an adopter can never produce a 3.3-invalid capabilities response. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adopt the rules adcontextprotocol/adcp#8585 settled: - Selection reads each offering's published standard_dimensions and unmapped_dimensions, and fails closed on an offering without them. - period_timezone: a utc offering matches only UTC or Etc/UTC; a source_timezone offering only its own published zone (never an account-resolved one). - The coverage function takes the readback's inactive_intervals in wire shape, per-offering timezone_cutovers with previous_timezone (account.timezone is now the current zone), and per-offering gaps. - The parity table fixes the billing-cycle case's scope (both keys, which maxProperties 1 forbids), adds cases for the settled rules, and its subscriptions are now schema-validated. The new wire fields are hand-modeled in subscriptions/wire.ts until 3.3.0-beta.2 ships. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- An empty flight (stop at or before start) overlaps nothing. - A cutover carries the outgoing stretch's own owing start into E, so two cutovers inside one period never owe a period twice. - Sub-daily periods under a local alignment restart at each local midnight on wall-clock multiples (adcp#8588); calendars gain next() and the walk iterates by instant with an iteration budget and skip-ahead. - A billing anchor in a DST fold is interval zero itself. - A wire authorized_at equal to the chain start continues the chain. - The start rule lives in one function for the pending owner decision. - Schema validation accepts unknown reporting-dimension values. - The parity table grows to 79 cases with obligations on every case and non-wire inputs labelled. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The start bound is the first boundary at or after the later of the version's accepted_at and the account's authorized_at; a buy is owed every period that ends after its confirmed_at and overlaps its flight, so the period in which the buy is confirmed is owed in full. The rule stays in firstOwedBoundary. Parity table: the mid-period confirmed_at and hourly cases change, plus every case whose first period contains confirmed_at; new cases for an ASAP buy confirmed mid-day and a monthly billing buy confirmed on the 12th. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…time
Opt-in support for the AdCP 3.3 sync_principal reporting_subscriptions
section (experimental media_buy.reporting_subscriptions):
- createPrincipalLifecycle({ reportingSubscriptions }) validates, versions
and reads back subscriptions (core/reporting-subscription-state.json),
honours dry_run and expected_configuration_version, applies
replace_account_configs once per version through a host hook, derives
action_required from the destination, and emits principal.changed with
reason reporting_subscription_changed. It also exposes the adcp.principal
capability block.
- createReportingSubscriptionEngineV1 turns three host hooks into grant
history, max_accounts slots, precedence, exceptions at cutover instants,
timezone cutovers, re-grant gaps, accounts_unauthorized, the receipt
roster and the post-loss receipt window, and computes owed periods with
the same expectedSubscriptionPeriods rule buyers run.
- createReliableReportingService({ reportingSubscriptions }) advertises
configuration_tasks and the subscriptions block and adds
subscription_coverage to summary and periods views.
- createAdcpServerFromPlatform passes capabilities.principal and
capability_changes through, adds protocol.principal and
media_buy.reporting_subscriptions to experimental_features, refuses
subscriptions without the reporting_subscriptions section, and strips
3.3-only fields for callers pinned below 3.3.
Without the options every response is byte-for-byte unchanged; a golden
transcript captured from the pre-change runtime proves it.
Refs #3504
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… by release - readFacts refuses a roster that does not finish within the page cap, as it already did for a repeating cursor: a partial roster would read as revocations and fabricate accounts_unauthorized entries. - Callers pinned below 3.2 no longer see protocol.principal once the adcp.principal block is removed for them. - subscription_coverage is added only for callers served 3.3 or later; earlier get_reporting_status response shapes are closed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The placeholder regex backtracked polynomially on runs of '{' (CodeQL).
Tokenize braces in a single pass instead, which also rejects nested and
unbalanced braces explicitly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… test Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The principal readback lists inactive_intervals; the engine feeds them to the coverage function in wire shape. - Timezone cutovers carry previous_timezone and gaps are recorded per offering_id when covered offerings' boundaries differ. - authorized_at is the start of the authorization chain: a re-grant inside retention keeps it, so the engine stamps the re-grant when authority is observed again. - The service requires standard_dimensions on every offering when subscriptions are advertised. - Responses served at 3.3.0-beta.1 omit the fields its closed schemas do not define; they reach callers served 3.3.0-beta.2 or later. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ts, sharded engine state Addresses the expert review of the subscription runtime: - Owed periods are frozen as they elapse and never recomputed; pruning past status_retention_days never re-owes or drops an elapsed period. A product, package or offering change applies from the E of its observation, and periods of that buy starting earlier keep resolving from the buy as it was. - Change instants come from source data (granted_at, timezone_changed_at, resolveRevocationInstant, the overriding configuration's active_until). - The engine store is sharded per principal and per account; the receipt roster is tenant-scoped; an unobserved revocation followed by a new chain opens a gap. - replace_account_configs runs after commit and is retried on failure; a destination transport change needs a version; inactive_intervals over 64 is rejected; shared path_templates that can expand to the same path are rejected; the delivery-config clash check is required unless opted out; coverage recording rejects oversized lists and no longer bumps configuration_version. - Post-beta.1 fields are gated on the served bundle's schema, and an unknown served version emits none of them. prior_versions[] carries configuration (adcp#8588). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…counts A small max_accounts against a large roster gives every account past the cap a capacity exception, which can exceed the readback's 1000-entry limit. The reconcile used to throw for the whole principal on every pass. Now that subscription keeps its last published lists, every other subscription still updates, and the pass reports it in readback_overflow and through onReadbackOverflow. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…criptions - syncReportingSubscriptionsV1 writes and reads back the reporting_subscriptions section through the guarded sync_principal lifecycle. - reportingSubscriptionVersionRecordsV1 rebuilds coverage version records from the readback plus the buyer's own per-version configurations, and subscriptionExpectedReportingPeriodsV1 maps owed obligations onto the consumer runtime's ExpectedReportingPeriod. - The consumer runtime takes a per-account subscriptionExpectations hook fed with the summary view's subscription_coverage, and refreshes on an authenticated principal.changed through handleAuthenticatedPrincipalChanged. - createReportingSubscriptionEngineStateStoreV1 persists engine state as one CAS document per principal on any AdcpStateStore; PostgresStateStore makes it durable with no further migration. A PostgreSQL restart test joins the CI backend-contracts step. Refs #3504 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ords Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he buyer side reportingSubscriptionVersionRecordsV1 no longer takes buyer-recorded inactive intervals: the readback's inactive_intervals (adcp#8585) pass to expectedSubscriptionPeriods unchanged. Test offerings publish standard_dimensions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
listPrincipals feeds the receipt roster, so a list cut short at the page cap, or a repeating cursor, now throws instead of returning a partial list, matching the authorized-account walk. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The engine state store follows the engine's sharded interface: one CAS document per principal and per (principal, account), with listKeys and a tenant-scoped listPrincipals. - Subscription expectations get the summary's ledger_as_of (minus an optional grace) as now, and the hook is documented as requiring a seller that materializes subscription obligations. - Expectation templates must supply coverage; the old default could never match an obligation reporting packages or partial coverage. - principal.changed redeliveries are dropped by idempotency_key, and a burst during a run joins one trailing run. - syncReportingSubscriptionsV1 refuses a deactivation by omission unless allowDeactivation, checked against every configuration read it replaces, and verifies the stored configuration field by field. - reportingSubscriptionVersionRecordsV1 falls back to the readback's configuration and prior_versions[].configuration (adcp#8588). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owed-set generations for AdCP 3.3 principal-level reporting subscriptions (#3504): one generation per (account, owning principal, subscription_id, subscription_version, resolved offering), with an immutable `subscription` block and a mutable `owedSet` horizon outside the fingerprint. - producer.planSubscriptionObligations installs each generation once, writes every frozen owed obligation with its own period, media buys and constituents (periodOrdinal = start second), and then advances owedSet.through on every generation the subscription owns. - Coverage treats an owed set as complete by construction: gaps are not owed, so there is no continuity error, and the scope stays open until owedSet.through reaches its end. - get_reporting_status, get_media_buy_delivery and sync_reporting_status scope subscription generations to the caller's principal (resolveSubscriptionPrincipal, snapshot `subscription_principal`). - Status ingest accepts a status without reporting_obligation_id against an owed set only for an existing owed obligation. - PostgreSQL: additive `subscription_key` column and a unique index on (account_id, delivery_config_id, delivery_config_version, subscription_key) replacing the three-column constraint, found and dropped by its columns. Mirrored in the memory test store. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…vice
createReliableReportingService({ reportingSubscriptions }) now writes the
engine's frozen owed sets into the ledger every cycle, before account
configurations, and the worker executes them (#3504).
- engine.frozenOwedSets(principal, { accountId? }) returns each
subscription's frozen obligations, horizon and version configurations;
engine.listPrincipals(tenantId) lists principals from the engine store.
- Generations go through the same trusted path as installConfiguration
(resolveSource, resolveCurrency, resolveCoverage), with seller policy from
the new resolveSubscriptionConfiguration hook. Each obligation freezes the
authorized constituents of exactly the media buys it owes.
- New options: listSubscriptionPrincipals (required),
resolveSubscriptionConfiguration (required), resolveSubscriptionAccount and
onPlanningError. Planning failures are isolated per subscription.
- resolvePrincipal now also scopes get_reporting_status,
get_media_buy_delivery and sync_reporting_status to the caller's own
subscription generations.
- Without reportingSubscriptions the service is byte-for-byte unchanged,
proven by a golden transcript captured from the service before the change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ntime timezone data A source-calendar P1D generation freezes the runtime's tzdb and ICU versions and the install day into its identity, so the golden differed on CI runners. The transcript now uses a UTC-aligned offering; the golden is recaptured from #3509's head (800adfc) and still matches byte for byte. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Deliver principal-level reporting subscription obligations (#3504) to the owner's destination through managed delivery. - Destination resolution: the Core service calls a new reportingSubscriptions.bindManagedGeneration hook (through the planner's bindGeneration) before a managed generation's first obligation. The production service supplies createReportingSubscriptionManagedBindingV1: version destination_id -> the owner's current destination generation (delivery-eligible, pattern and transport matching the frozen destination_method) -> a per-account authorization of that destination_ref (ensureDestinationAuthorization) -> an immutable binding that freezes path_template. - Bindings are keyed by their Core configuration: the managed migration drops the published (account, delivery_config_id, delivery_config_version) key, found by its columns. - path_template is expanded per obligation and passed to adapters as delivery_path; object-store names place it inside the collision-free generation prefix. Subscription revision rows carry account_id inside the committed digest. - The managed worker re-checks the owner's account authority before delivering and before settling; a lost grant holds the materialization without spending an attempt, so the obligation stays owed. - Status subscribers and notification activity reach only the owner of a subscription obligation. Production reads are scoped to the caller's principal and carry subscription_coverage. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # scripts/check-package-size.mjs
…ley/reporting-subscription-ledger-service
…to bokelley/reporting-subscription-delivery
…bscription-coverage # Conflicts: # scripts/check-package-size.mjs
…dules after merging main Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…/reporting-subscription-runtime # Conflicts: # scripts/check-package-size.mjs
…er merging main Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reporting-subscription-consumer
…ey/reporting-subscription-dx-naming
When the offerings a condition covers have different period boundaries, the engine now writes one excluded[] entry per offering with offering_id and that offering's cutover instants (capacity, excluded_by_buyer, action_required, overridden, and offering_unsupported after a resolution change). Before, it wrote one account-wide entry cut at the latest end. A per-offering entry closes at its own offering's cutover. A release whose excluded[] has no offering_id gets them folded back into one entry. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… Reporting service
createReliableReportingService({ subscriptions: SubscriptionOptions }) now
builds the coverage engine, installs the reporting_subscriptions section on
the principal runtime, fills in capabilities.principal on install, reconciles
a principal after each sync_principal write that submits subscriptions, and
sweeps every subscribed principal at the start of runCycle.
The roster and buys are derived from the platform's accounts.list and
sales.getMediaBuys under the principal's identity, with overrides named as
in the Python SDK's SubscriptionOptions. An unreadable roster is coded
SUBSCRIPTION_ROSTER_UNAVAILABLE, and reconcile and planning failures are
returned in runCycle's subscriptionPlanningFailures instead of an
AggregateError. The production service accepts the managed block.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the managed subscriptions block the service serves the journal's authorization.authorized_at on list_accounts and sync_accounts to callers holding subscriptions, and the derived roster reads the same value. A host value is adopted on first observation, an omitted one is stamped, a value that moves an open chain earlier is refused and logged as authorized_at_regressed, and the chain rule decides whether a re-grant continues the chain. service.grants.changed pushes a grant change for an exact instant, and resolveRevocationInstant becomes optional. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
client.reporting (ReportingClient) adds subscribe, unsubscribe, replaceSubscriptions, listSubscriptions and preview: patch-shaped writes over the replace-shaped sync_principal wire, with auto-versioning, a destination merge, pre-dispatch checks (scope, destination, path_template, SUBSCRIPTION_ID_CLASH, SUBSCRIPTION_OPERATOR_SCOPE_REQUIRED, SUBSCRIPTION_SELECTOR_UNMATCHED), CONFLICT retries with fresh keys and a verified readback. A raw client.syncPrincipal that would deactivate a subscription or revoke a destination by omission is refused before sending unless allowDeactivation or allowRevocation is passed. Names follow the Python SDK's ReportingClient. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ager graph The facade loads offering selection on demand and carries its own readback diff, so client/core gains only the facade module itself. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…entity in the auth snapshot - The derived roster and buy reads take the engine's signal and check it before each read and page, so a cancelled cycle stops promptly. - The authInfo snapshot kept for service-context reads is an allowlist (clientId, scopes, resource, expiresAt), so no adopter field carrying a credential reaches the durable engine store. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ey/reporting-subscription-dx-grants Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… chain rule after retention - A projection whose journal write fails still serves the platform's values, but now warns and reports the failure to planningErrorHandler instead of failing silently. - New engine test: a pushed re-grant inside retention continues the chain, one after retention starts a new chain, and a listing observed after retention does too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…y/reporting-subscription-dx-buyer-facade
A seller that rewrites a submitted field, or drops the subscription from its readback, fails with SUBSCRIPTION_READBACK_MISMATCH. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
memoryStorage() (new @adcp/sdk/storage subpath) and postgresStorage({ pool })
(@adcp/reporting-storage) return one ReportingStorage covering the reporting
ledger, principal records, reporting-subscription engine state and the buyer
consumer's checkpoints and leases. Process-local storage is refused outside
NODE_ENV=test|development with REPORTING_STORAGE_NOT_DURABLE.
The storage package ships versioned migrations recorded in
adcp_schema_migrations and the `npx adcp-reporting-storage migrate
[--status | --dry-run]` command; checkSchema() refuses a schema that is behind
with REPORTING_SCHEMA_BEHIND, and the service runs it before its first cycle.
The migration constants stay as deprecated re-exports.
The service, the production service and the buyer consumer take storage.
subscriptions.principals is optional with storage: subscriptions.principal
builds the runtime over storage.principals (service.principals). Durability is
declared by the state store; the durability options are deprecated, accepted
when they agree and refused when they contradict the store. The in-memory
ledger the tests used now ships behind memoryStorage().
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… to request_account client.accounts.ensure now resolves to a discriminated AccountOutcome (ready | pending | ambiguous | rejected) instead of the registry's ProvisionedAccount; pending is a value, not an exception. It calls request_account when the seller advertises account.requests and sync_accounts otherwise, and is idempotent per seller, caller and natural key: a repeat call returns the open request's state and never opens a duplicate. accounts.refresh() re-reads open requests and accounts pending approval and returns what moved; accounts.pending() lists what is waiting. nextStep() and formatPendingAction() follow the Python SDK. Only a failure to process the call throws (AccountRequestError on request_account). BREAKING CHANGE: accounts.ensure resolves to AccountOutcome. The previous call and its ProvisionedAccount result are now accounts.provision(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…de suite Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ey/reporting-subscription-dx-accounts
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ey/reporting-subscription-dx-accounts
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — scoped review of commit 201e6dc (accounts.ensure → AccountOutcome, routes to request_account).
Verified clean: changeset correctly typed major for the breaking return-type change; capability-gated routing gives 3.2 sellers a fallback to sync_accounts (DR-0033 version pathway satisfied); official clients used throughout; no fabrication beyond documented SDK outcome shapes (witness-not-translator upheld). Idempotency-key derivation, rejection-retry key rotation, read() pagination bound + required accounts guard, knownOutcome/recordRequest alias publishing, and formatPendingAction untrusted-text stripping all confirmed sound.
Medium findings
- src/lib/core/buyer-account-registry.ts:894 — refresh() discards already-persisted request moves on partial failure (sync-pending repairStatus loop throwing leaves persisted moves un-re-reported). Worth a follow-up but non-blocking.
Only a single medium finding, not in data-loss/schema/infra category. No critical/high findings, high_risk is false, gated_paths is false, no author team gate. Falls through decision table rows 1–8 to row 9.
Medium findings
- src/lib/core/buyer-account-registry.ts:894 — refresh() discards already-persisted request moves on partial failure
…roduction Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ey/reporting-subscription-dx-accounts
… re-read Also counts the account-outcomes module in the focused client's eager-graph budget. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — delta review, prior blocking issue resolved, 1 medium finding remaining (below comment threshold).
Prior finding resolved: The earlier Medium (src/lib/core/buyer-account-registry.ts:894 — refresh() discarding already-persisted request moves on partial failure) is fixed correctly. repairStatus now runs in try/catch, re-throws on abort and otherwise continues, leaving the failed account pending, and is covered by a new test. Net improvement over the prior pass.
Medium findings (1):
src/lib/reporting/consumer-runtime.ts:346— Consumer durability guard is fail-open (refuses onlydurability === 'process-local') vs the seller's fail-closed check (requires=== 'durable'); a storage wrapper omitting the optional durability field would run non-durable in production. Worth a follow-up but non-blocking.
Changesets are present for the src/lib changes. No critical/high findings and no blockers in the delta.
Decision path: No critical/high (row 1 n/a). gated_paths false (row 2 n/a). high_risk false, no deletions/modifications triggers (rows 3–5 n/a). Prior decision was approve, not escalate (row 6 n/a). No no-auto-approve team match (row 7 n/a). Only 1 medium finding, below the 3-medium threshold (row 8 n/a). Falls through to row 9 → approve.
Medium findings
- src/lib/reporting/consumer-runtime.ts:346 — Consumer durability guard is fail-open vs the seller's fail-closed check
…le, as the seller does Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ey/reporting-subscription-dx-accounts
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — prior Medium resolved, no new blocking or Medium findings.
This is a subsequent review following a prior approve. The developer pushed a 52-line delta across 2 files that resolves the prior Medium finding (src/lib/reporting/consumer-runtime.ts:346 — consumer durability guard was fail-open vs the seller's fail-closed check). The fix makes storage.durability required and rewrites the guard to storage !== undefined && durability !== 'durable', now fail-closed and matching the seller's pattern (runtime.ts:134, webhook-emitter.ts:540). A test asserting undefined-durability storage is refused was added. The breaking type change is covered by the PR's declared major changeset on an @experimental reporting surface.
Decision table: no critical/high findings (row 1 n/a); gated_paths false (row 2 n/a); high_risk false (rows 3/5 n/a); no Medium findings at all (rows 4/8 n/a); no no-auto-approve team match (row 7 n/a); prior decision was approve not escalate (row 6 n/a). Falls through to row 9 → approve.
Note: review_decision is REVIEW_REQUIRED, but gated_paths is false, so the hard gate in row 2 does not apply. The deprecated protected-branch rule no longer forces comment/escalate.
No blocking findings. Clean delta that improves durability safety.
T9 for #3504 (approved DX plan §2.11 and §6.1 T9): the
accounts.ensurereshape. It stacks on #3555 (T8) → #3554 (T7) → … and the rest of the #3504 stack. Review onlyfeat(accounts)!: accounts.ensure returns an AccountOutcome and routes to request_account. It targetsmainbecause CI runs only for PRs intomain.This is a deliberate breaking return-type change for this release (major changeset, within the SDK 17 beta line).
API
accounts.ensure(account, options)→ProvisionedAccountaccounts.ensure(account, options)→AccountOutcomeaccounts.provision(account, options)→ProvisionedAccount(unchanged behavior;resolveAccountuses it)accounts.refresh(),accounts.pending(),nextStep(),formatPendingAction(),AccountRequestErrorAccountCapabilities.requestsSupported,EnsureAccountOptions.currency/.timezone,BuyerAccountStorage.list?(scope)AccountOutcomeisAccountReady | AccountPending | AccountAmbiguous | AccountRejected, discriminated bykind. Wire-style keys (account_id,request_id) match plan §2.11 and Python's field names.pending.actionis the generated wirePendingAction, and its TSDoc says to showurlto a person and never fetch it.account.requests.supported,ensurecallsrequest_account. Otherwise it callssync_accountsthrough the existing provisioning engine.pending_approvalwithsetupmaps topending, and a failed or rejected row maps torejected.ensurere-reads the open request throughlist_accountsrequest_idsinstead of opening another.request_accountuses a deterministic key,adcp-ensure-<sha256(scope, body, previous)>, as Python does, so registries that share storage replay one request. A new request after a rejection gets a new key.account_idalias for account-scoped tasks.request_accountpath that isAccountRequestError, which carries the wirecode,recoveryanderrors.Deviations from the plan and from Python
sync_accountspath keeps throwing the existing typed errors (AccountSetupRequiredError,AccountNotFoundError, …) when the call itself fails. They already carry the wire code, and changing them would breakresolveAccountcallers. Python raisesAccountRequestErrorthere too.outcome.wait?.({ timeout })is not built. It is optional in the plan's sample, and Python has no equivalent. Userefresh().change()/use()are T16.provision()is the old method under a new name, kept as the migration path for callers that need the registry record.require_operator_authsellers beforesync_accounts. TS keeps its existing billing-capability checks.Tests
test/lib/account-outcomes.test.js(12) covers:nextStepacross every kind;formatPendingActionstripping control and bidi characters, quoting and capping;in_review→pending, then a repeatensurethat re-reads without a duplicate, thenrefresh()→readywith the alias published;ambiguous+choose, including a refused non-candidate;rejectedas a value, with a new key on retry;AccountRequestError;pending()from a fresh registry;ready,pendingwith a human URL followed byrefresh()→ready, and a failed row asrejected;The existing provisioning suites (
buyer-account-registry*,buyer-stable-account-id,sync-accounts-version-guards,version-translation) now callprovision(), mechanically.docs/guides/FIRST-CALL-TO-A-SELLER.mdanddocs/development/BUYER-STORAGE.mdshow the new outcome.File budget +4 (one dual-format module). Packed SDK: 26,604,124 bytes, under the inherited 26,610,000 cap.
Checks
npm run typecheck,lint:boundaries,check:package-size,ci:docs-check, Prettier, and 1,059 tests across the account, client, reporting-facade and version-translation suites.Refs #3504
🤖 Generated with Claude Code