Repository navigation
chore: release package (beta) - #3650
Merged
Merged
Conversation
|
Ladon does not review bot-authored PRs. |
aao-ipr-bot
Bot
force-pushed
the
changeset-release/main
branch
16 times, most recently
from
November 9, 2026 20:44
4fd6241 to
47f9313
Compare
aao-ipr-bot
Bot
force-pushed
the
changeset-release/main
branch
from
November 9, 2026 21:23
47f9313 to
572e767
Compare
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.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
mainis currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, runchangeset pre exitonmain.Releases
@adcp/sdk@17.0.0-beta.22
Minor Changes
7a812a1: Enforce the AdCP 3.3 creative start policy on
control_media_buy(Server: refuse start_requires_all_creatives_approved: true on control_media_buy when the seller doesn't declare creative_start_policy #3649). A seller whose capabilities do not declaremedia_buy.features.creative_start_policy: truenow refusespackages[].start_requires_all_creatives_approved: truebefore the handler runs and before the idempotency lookup:UNSUPPORTED_FEATURE,recovery: 'correctable',fieldnaming the first opted-in package,details: { feature, fields }listing every one, andmutation_outcome: 'not_applied'. The gate applies when the server's release is 3.3 or later;falseand omission always reach the handler. This breaks sellers that honoredtruewithout declaring the feature: declare it withcapabilities.features.creativeStartPolicy: true(new, also oncreateAdcpServerFromPlatform) orcapabilities.overrides.media_buy.features.creative_start_policy.The client refuses the same request locally (
ProtocolFeatureUnsupportedError,code: 'UNSUPPORTED_FEATURE', samefieldanddetails, nothing sent) when its discovered capabilities for a 3.3 call lack the feature, and sends it when capabilities are synthetic or unknown.MediaBuyFeatures.creativeStartPolicyandclient.supports('creative_start_policy')read the declaration;adcpError()acceptsmutation_outcome.create_media_buyandupdate_media_buyare not gated. Withdrawing the field from their packages is pending fix(schema): keep 3.3 additions off the deprecated create_media_buy and update_media_buy (#8501) adcp#8767; the pinned 3.3.0-beta.5 package schemas still declare it, so until that lands an opted-in create or update reaches the handler unenforced (a known gap the Python SDK shares). Matches adcp-client-python#1872 and docs(development): 7.x dual-mode interim before the 8.0 V2-only break #1878.mediaBuyActionResolver.resolve()offersupdate_creative_start_policyonly when the newcreativeStartPolicy: trueoption says the seller declares the feature; otherwise it reports the action unavailable (not_supported_on_buy), so the SDK never advertises an action its own gate refuses. PasscreativeStartPolicy: truealongsidemetadata.update_creative_start_policyto keep offering it.57ffd9f: Tools with a zod input schema now validate it at the AdCP layer, so a rejected call returns an AdCP error that echoes
context(customTools input-schema errors drop context (MCP -32602 before the AdCP layer) #3666). This covers:customToolsoncreateAdcpServer, and sotasks_getoncreateAdcpServerFromPlatformcomply_test_controllerfromregisterTestController,createComplyController().register()andcreateAdcpServerFromPlatform({ complyTest })Before, the MCP SDK checked the arguments before any AdCP code ran and answered a failure with a bare
-32602text result. That result had nostructuredContent, noadcp_errorand nocontext, which breaks the context-sessions rule that failures echocontext. See Custom tool input validation in the 16 → 17 migration guide.Error shape (breaking for buyers that matched the old text). A call that fails the declared schema now gets
adcp_errorwith:INVALID_REQUESTfield, naming the first failing path; a missing property is reported at its own path (field: 'query')issues, also atdetails.issuescontext,status: 'failed'and the servedadcp_versionThis is the envelope framework tools return. Buyers that matched the
-32602text, theInput validation error:prefix, orisErrorwithoutstructuredContentmust readadcp_errorinstead. Framework tools still emitVALIDATION_ERRORfor request-schema failures; Framework request-schema failures emit VALIDATION_ERROR; the spec code for schema violations is INVALID_REQUEST #3674 tracks converging them onINVALID_REQUEST.Handlers get the parsed value on every path (breaking for handlers that read undeclared keys outside MCP). On MCP a valid call reaches the handler with the same parsed value as before: zod coercion, defaults and transforms applied, undeclared keys stripped.
AdcpServer.invoke(),dispatchTestRequest()and A2A used to skip the schema and hand custom-tool handlers the caller's raw arguments. They now validate too, and pass the same parsed, stripped value. On those paths an invalid call no longer reaches the handler, and the handler no longer sees keys its schema does not declare. A handler that reads such keys must declare them, or use a loose object (z.looseObject({...}), orz.object({...}).passthrough()on zod 3).One parsed value decides everything. The arguments are parsed once.
identity33authorization, release selection (adcp_version,adcp_major_version) and the handler all read that parsed value, on every transport. A declared default, coercion or transform therefore applies before anything is authorized, exactly as when the MCP SDK validated. Oninvoke(),dispatchTestRequest()and A2A, authorization and release selection used to read the raw arguments. Behavior change on those paths: a custom tool whose schema does not declareadcp_versionused to let the caller's rawadcp_versionselect the release. The schema now strips it, so the default release applies, the same as on MCP. A2A negotiates no AdCP version at the envelope level (A2A-Versionselects only the A2A wire), and framework tools are unchanged. Declareadcp_versionto let buyers pick the release. The one pre-parse gate is thecomply_test_controllervisibility check fromcreateAdcpServerFromPlatform. It can only hide the controller, and is checked again on the parsed value.Every rejection echoes
context. Version, authorization and validation rejections,comply_test_controller's account rejections, and failures a custom handler returns or throws all echo the caller'scontext, even when the declared schema strips it.Discovery. MCP
tools/liststill advertises the declared schema, on the legacy SDK and on the 2026-07-28 adapter. Internally the SDK now registers a passthrough schema for these tools.c3790c8: Legacy format projection now says when it guessed a seller id's meaning, lets a caller turn the guessing off, and no longer lets a guess contradict a seller's own
format_options[](toAdditiveCanonicalProduct reads display_<W>x<H> at any agent URL as image, guessing from the id #3634).projectV1ProductToV2,toAdditiveCanonicalProductand the other v1 → v2 helpers read a legacy id under any agent URL by the AAO naming conventions, so a seller's owndisplay_300x250came back asimageeven when the seller sells it as a third-party tag. Sellers whose legacy ids look likedisplay_WxHorvideo_WxHwere reported as contradicting themselves by consumers that compared that projection with the seller's declared options.inferred_from_id: true. The routes are JSON-safe, so the marker persists. Declarations and default results are otherwise unchanged.V1ToV2ProjectionOptions.authoritativeOnly: trueresolves refs only from catalog snapshots, exact AAO catalog entries andlegacyFormatConverter. A ref only a guess could resolve gets aFORMAT_PROJECTION_FAILEDdiagnostic instead of a declaration.isAaoAgentUrlis exported from@adcp/sdk/v2/projection.format_ids[]andformat_options[], a guess from an id's name no longer vetoes the index pairing of that id with the seller's own option. Only the AAO's own mapping of an id at the AAO reference creative agent can;legacyFormatConverteris not consulted for this check. Adisplay_tagoption paired with a seller'sdisplay_300x250id now gets its route instead of aLEGACY_FORMAT_ID_DROPPED_UNMAPPEDdiagnostic.36f34f6: Reliable Reporting:
issueObservertells the seller when a reporting issue opens, escalates or resolves (feat(reporting): issueObserver — notify the seller when a reporting issue opens, escalates or resolves (Python parity) #3648). This is the TypeScript side of the Python SDK'sissue_observer(adcp-client-python#1874, fix(mcp): connectMCP 401s carry auth-scheme context #1875).A
CONSUMER_STATUS_MISMATCHis caller-scoped, so only the buyer whosesync_reporting_statusstatement caused it sees it. A seller had no supported way to learn that one opened, escalated pastconsumer_mismatch_escalation_seconds, or resolved.createReliableReportingService({ issueObserver }). The observer is called once per transition with aReportingIssueEventV1:eventId,kind,occurredAt,accountId,consumerId,principal(subscription obligations),issueId,reportingObligationId,reportingStatusId,attempt, andissue, rendered asget_reporting_statusshowed it atoccurredAt(nullforresolved).openedandresolvedin the same transaction as thesync_reporting_statuswrite or revision commit that caused them.runCyclesweepsescalatedand then delivers committed events.runCycle, events are recorded but the observer is never called andescalatedis never recorded.eventId:"rpie_" + sha256(canonical_json_utf8_v1(["reporting-issue-event-v1", issueId, consumerId, kind]))[:40]. That is Python's scheme with the consumer added, because the publishedissueIddoes not name the consumer and two consumers may reuse onereporting_status_id. An open occurrence's events are never pruned; a closed one's (resolved, with its statement superseded or its obligation gone) are, seven days after its last delivery.onErroras aReportingIssueObserverError, andrunCycle(...).issueEvents.errors.ReportingIssueEventStoreV1(issueEventsSupported,issueEventsReady,sweepIssueEscalations,claimIssueEvents,completeIssueEvent,releaseIssueEvent,pruneIssueEvents). The same experimental surface adds the pure helpers stores share:reportingIssueEventIdV1,reportingConsumerMismatchOccurrenceV1,planReportingIssueEventsV1,reportingIssueEscalationV1andrenderReportingIssueEventV1.memoryStorage()implements it, andcreateReportingIssueEventDispatcherV1runs delivery without the service. The service refusesissueObserveragainst a store without the capability.acknowledgedandwaivedare in the kind union for parity, but no TypeScript store emits them, because the TypeScript ledger has no operator issue-state API.Pair with
@adcp/reporting-storagefrom this release, which records the events on PostgreSQL after migration0012.8e7fdd8: Add runtime subpaths so client-only code can stop importing
@adcp/sdk, whose root also loads the server and testing modules (Root entry and ./client load the SDK's server and testing modules #3664, step 1). The new entry points are@adcp/sdk/errors(typed errors,adcpErrorToTypedError,isErrorOfType,STANDARD_ERROR_CODES,getErrorRecovery,extractAdcpErrorInfo,isTerminalAdcpError,decideRetry),@adcp/sdk/version(ADCP_VERSION,LIBRARY_VERSION,COMPATIBLE_ADCP_VERSIONS,parseAdcpMajorVersion,resolveAdcpVersion),@adcp/sdk/registry(RegistryClient,sanitizeStreamTextand the rest of the registry exports),@adcp/sdk/utils(canonicalize,canonicalJsonSha256, media-buy status helpers,unwrapProtocolResponse,parseCapabilitiesResponse, the*_TOOLSlists,brandManifestToBrandReference),@adcp/sdk/discovery(validateAdAgents,resolveAgentProperties,canonicalizeAgentUrl, the publisher property selector helpers),@adcp/sdk/media-buy(preflightUpdateMediaBuy, thecan*checks,getAvailableActions,MediaBuyLifecycleCoordinator),@adcp/sdk/client/account-changes(streamAccountChanges,restoreAccountChangeCursor,buildAccountChangeSubscriptionRequest) and@adcp/sdk/client/webhook-stores(pgWebhookRegistrationStore,cleanupExpiredWebhookRegistrations, the Redis store).@adcp/sdk/client/corealso exportscreateInMemoryAgentDiscoveryCache,ADCP_STATUS,InputRequiredError,DeferredSettlementOwnershipErrorandDirectContinuationRecoveryError.Each name is the same export as on the root, so
instanceofagrees whichever path a caller imports, and no subpath reuses a root type name for a different shape (the root'sCreateMediaBuyRequest,ProductandPackageRequeststay the canonical aliases from@adcp/sdk/v2/projection). Nothing is removed from the root, and@adcp/sdk/clientis unchanged.@adcp/sdk/client/coreno longer loads any module underdist/lib/server/(it used to load the property-list resolver and the pinned-DNS fetch from there), and@adcp/sdk/media-buy/actionsno longer loadsserver/media-buy-helpers. The shared helpers moved out ofsrc/lib/server/(targeting-helperstocore/,pin-and-bind-fetchtonet/,media-buy-helperstomedia-buy/valid-actions, anderrors,envelope-allowlistandpick-safe-detailstoutils/); the olddist/lib/server/*files re-export them, so deep imports keep working. A new check,npm run check:client-entry-graph(also run as a unit test), bundles each client entry point with esbuild and fails if its module graph reachesdist/lib/server/ordist/lib/testing/.307c004: A seller built with
createAdcpServerFromPlatformcan now say it sells a wholesale catalogue inget_adcp_capabilities.media_buy.buying_modes(Server: a seller cannot declare wholesale in media_buy.buying_modes (framework overwrites it) #3629). Before, the framework wrote['brief']or['brief', 'refine']from proposal support alone and overwrote anything the seller put incapabilities.overrides.media_buy.buying_modes, so a wholesale-only seller was unrepresentable and the SDK's own buyer client refused to send it a wholesale request (UnsupportedBuyingModeError).mediaBuyLifecycle.listProductsaddswholesale. A platform that registers it and has no brief path (nosales.getProducts,proposalManager,requestProposalsorrefineProposals) advertises['wholesale']alone. A platform without a catalogue handler advertises exactly what it did before.capabilities.buyingModesstates the list explicitly, for asales.getProductshandler that answersbuying_mode: 'wholesale'(the handlers cannot say so) or to leave out a mode they imply. It is checked against the registered handlers, andcreateAdcpServerFromPlatformthrows aPlatformConfigError(configField: 'capabilities.buyingModes') for an empty list, a value that is not a buying mode, or a mode with no handler to serve it:briefandrefineneedsales.getProductsor a proposal path,wholesaleneedssales.getProductsormediaBuyLifecycle.listProducts.overrides.media_buy.buying_modesis unchanged: the framework value still overwrites it.capabilities.overrides(andcreateAdcpServer'scapabilities.overrides) now acceptwholesale_feed_versioningandwholesale_feed_webhooks. The SDK does not derive them.Who is affected. A platform that registers
mediaBuyLifecycle.listProductsnow gainswholesale; if it has no brief path it also stops advertisingbrief, which AdCP treats as implicit and the SDK buyer client treats as always supported. Declarecapabilities.buyingModesto keep a different list.Patch Changes
f52e1ae:
createAdcpServerandcreateAdcpServerFromPlatformnow projectget_adcp_capabilitiesdown to the release they serve an older caller, as DR-0024 requires (Server: project get_adcp_capabilities down to the served release (drop 3.3-only fields and experimental feature ids for 3.1/3.2 callers) #3646). Before, those callers received 3.3-only capability fields because the older capabilities schemas are open, and the server removed only fields where an older schema is closed.business_calendars,media_buy.issue_reporting,media_buy.open_opportunities,media_buy.supported_match_keysandaccount.requests. Keys that no release declares are kept, for examplelibrary_versionor a seller's own extension key.experimental_featuresdrops an id that the server's capabilities schema names and the served release's schema never mentions, such asmedia_buy.match_keysfor a 3.2 caller. AdCP has no structured registry of experimental feature ids yet (Publish a machine-readable registry of experimental feature ids per release adcp#8812), so ids that neither schema mentions are kept.onVersionTranslationasversion_downgrade_fields_droppeddrift.Responses to 3.3 callers are unchanged. 3.0 callers keep the existing allow-list projection of properties to the 3.0 schema, and now also lose experimental feature ids that 3.0 does not define, such as
media_buy.match_keys.29c9af4: The Zod schema generator now follows each source schema's
additionalPropertiesinstead of assuming the schema is closed. This prepares the SDK for the AdCP 3.3.0-rc.0 bundle, which opens the schemas that were closed by accident (fix(schemas): open the 3.3 schemas that were meant to be open adcp#8866).Before, the generator required
additionalProperties: falseon theget_reporting_statusscope,scope.delivery_config_generations[],obligation_countsandpaginationobjects, and failed generation when the source opened them. Several other post-processors closed objects without reading the source:ReportingCoverage,ReportingReceiptandReportingVerificationReportingConsumerStatusand itsperiod, and thesync_reporting_statusrequestTMPXChunk,TMPProviderRegistrationandPublisherTMPXMacroMappingschemas, and the routertmpx_providersentriesTargetingKvs, which was closed only because the context-match request pass also rewrote the export that follows itCompatibilityPurchaseCoordinatorInputEach of these is now strict only while its source closes it, and passes unknown keys through once the source opens it. Objects the source still closes keep their strict behaviour, including the Trusted Match request privacy boundaries, the placement presentation document and the canonical metric qualifier. Generated output for the current 3.3.0-beta.5 bundle is unchanged.
cae11b7: A
legacyFormatConverternow outranks the SDK's guess about a seller's own legacy format id.For a ref at an agent URL other than the AAO reference creative agent, the v1 → v2 projector consults
legacyFormatConverterbefore it guesses the id's meaning from its name. Before, a registry id glob guess ran first, so a converter could not say that the seller'sdisplay_300x250is a third-party tag rather than an image. A converter that returnsundefinedstill accepts the guess, which its route marks withinferred_from_id: true. A converter that throws or returns an invalid declaration still fails closed. Refs at the AAO agent are unchanged: the AAO's own mappings run first and the converter runs only after they fail.727c947: Honor explicit storyboard
requires_toolgates before stateful failure cascades in single-agent runs, matching routed execution. Missing prerequisites remain benign skips, including the controller-specific skip reason, while genuine failures continue to fail the storyboard.daa32d9: fix(compliance): a
fixture_unavailableskip no longer makes its trackpartial(computeTrackStatus marks a track partial for a fixture_unavailable skip, so a runner-owned coverage gap shows the agent as degraded #3596). The skip means the runner's test kit could not build a creative asset the seller's format requires, for example a requiredalt_textslot. The runner-output contract grades that storyboardnot_applicableand says it is not a grading signal about the agent.comply()still graded the trackpartial, which madeoverall_statuspartialand showed agents as degraded with zero failing steps. The track now grades from the steps that ran, the same wayfixture_unsatisfiedandfixture_seed_unsupportedalready did. A real failure next to the gap still gradespartialorfail. Unverified request-signing coverage still holds its trackpartial. The gap stays visible in the step'sskip_reasonand detail, insummary.skipped_by_reason, and as a coverage gap in the specialism bundle result.8947543:
createMediaBuyandupdateMediaBuynow send a package'sformat_option_refsto an AdCP 3.1 seller when the selected options have no legacyformat_idsrepresentation (On an AdCP 3.1 session, create_media_buy throws for canonical-only format_option_refs instead of sending them #3665). Before, the client threwCreativeFormatProjectionError("canonical format_option_refs have no complete legacy representation") and never sent the buy, so a buyer could not select a canonical-only format option from a 3.1 seller. Leaving the selector off was no workaround: AdCP 3.1 activates every product format when a package has no selector, and the seller held the buy for creatives it would never get.AdCP 3.1 defines
format_option_refson the package request (media-buy/package-request.json), so the seller resolves the selection itself. The client sends the refs when the seller'ssupported_versionsprove a mutually served 3.1 release. Selections with a complete legacy mapping are still rewritten toformat_ids, as before. AdCP 3.0 sellers, and sellers whose release cannot be proven, still get the error: 3.0 does not define the field, and dropping it would activate every format. AdCP 3.2+ sellers are unchanged.projectMediaBuyCreativesForDeliverytakes an optional trailingformatOptionRefsOnWireargument for the same behavior in pure projection, andreleaseCarriesPackageFormatOptionRefs(capabilities, version)from@adcp/sdk/v2/projectioncomputes it.3971edf: Refresh the registry OpenAPI snapshot and generated types for the published compliance coverage fields:
coverage_incompleteon the agent compliance summary and on compliance runs. It is true when any track had a coverage-gap skip, and it is reported besidestatus, not folded into it.track_details[].skipped_by_reason, which counts a track's skipped steps by skip reason.not_applicable_universal_bundle_ids, listing universal bundles that are partial only because the agent advertises no tools for a surface it does not declare.All the new fields are optional.
28694b0: Reporting storage (experimental): period retirement resolves the period's open issue occurrences (feat(reporting): issueObserver — notify the seller when a reporting issue opens, escalates or resolves (Python parity) #3648 follow-up).
retireExpiredPeriodsdeleted a period's obligation and consumer statements but left itsCONSUMER_STATUS_MISMATCHoccurrences open in the issue-event outbox. Nothing could resolve or escalate them afterwards. An occurrence that had already escalated stayed open forever: the issue observer never received itsresolvedevent, and retention never pruned it.Retirement now records
resolvedfor each of the obligation's open occurrences in the same transaction that deletes the obligation. Pending events stay deliverable: theresolvedevent carriesissue: nulland noprincipal. The whole-occurrence prune collects the occurrence once it is delivered. Outbox rows are never deleted at retirement, so no undelivered event is lost.No migration and no API change.
ReportingIssueEventStoreV1(experimental) now states the contract for custom stores: a store that deletes an obligation resolves the obligation's open occurrences in the same transaction.8e0ec72: Request-signing grading now honors a vector's
applies_whencondition (adcp#8064). The storyboard probe evaluates it against the agent's discoveredget_adcp_capabilities, so vector 027 skips ascapability_profile_mismatchwhenwebhook_signing.legacy_hmac_fallbackis false or absent instead of failing (signed_requests: honor applies_when on request-signing vectors (027, adcp#8064) #3410).gradeOneVectorandgradeRequestSigningacceptagentCapabilitiesfor the same check. Supported form: a dotted capability path compared with==or!=to a JSON scalar; any other expression, or a run without capabilities, still grades the vector.@adcp/reporting-storage@0.3.0-beta.6
Minor Changes
f6a6a76: Allow shared row-storage tables to hold rows written by another SDK without requiring its obligations in the JavaScript core ledger. The row-set obligation foreign key is removed; row-set ownership is maintained by each SDK's ledger transactions and retirement process.
Migration: run
npx adcp-reporting-storage migrateto apply migration 0011 to existing databases. Deployments applying the deprecatedREPORTING_ROW_STORAGE_MIGRATIONdirectly should reapply it. The migration drops the defaultadcp_reporting_row_sets_obligation_id_fkeyconstraint; remove any custom-named foreign key to the JavaScript obligations table separately. Row-set and chunk foreign keys within the shared tables remain in place.36f34f6: Reporting storage (experimental):
PostgresReportingLedgerStorerecords issue-lifecycle events for the SDK'sissueObserver(feat(reporting): issueObserver — notify the seller when a reporting issue opens, escalates or resolves (Python parity) #3648). This mirrors the Python storage outbox (adcp-client-python#1874).syncConsumerStatusBatchrecordsopenedwhen the new leaf conflicts with the seller's record, andresolvedfor the occurrence of the leaf it superseded.commitRevisionrecordsopenedwhen a restatement makes a leaf conflict or anchors one filed before any revision. It also recordsresolvedwhen the read projection stops conflicting.(issue_id, consumer_id), since two consumers may reuse onereporting_status_id.UNIQUE (issue_id, consumer_id, kind)keeps every record exactly once across replicas, and theevent_idis derived from that triple.sweepIssueEscalationssettles each candidate obligation first, then recordsescalatedonce.claimIssueEventshands outSKIP LOCKEDleases, at most one per occurrence, in recording order.completeIssueEvent,releaseIssueEventandpruneIssueEventssettle and clean up, andissueEventsReady()reports whether the table exists. Pruning removes only whole closed occurrences (resolved, fully delivered past the retention, and with the causing statement superseded or the obligation gone), never an open one, because the table is also the occurrence ledger and no later write may record a deliveredevent_idagain.createPostgresReliableReportingProductionServiceacceptsissueObserver(passed to the core service it drives) and addsREPORTING_ISSUE_EVENTS_MIGRATIONtosetup.migrationswhen it is set.Why minor: this ships a new versioned migration that
postgresStorage().checkSchema()requires (REPORTING_SCHEMA_BEHINDuntil applied), and per the stability policy a schema change goes in a minor of@adcp/reporting-storage.Migration note:
npx adcp-reporting-storage migrate. This adds version0012,reporting_issue_events; the SQL is exported asREPORTING_ISSUE_EVENTS_MIGRATION. It is additive: one new table (adcp_reporting_issue_events) and its indexes, with no change to any existing table, key, function or trigger. It is safe to apply while workers of the previous release still run; they record no events for their own writes.issueEventsReady()isfalse, andissueObserverrefuses to start. Nothing else changes.0008, keeps its storage contract at2, and uses the sameevent_idderivation.See
docs/migration-reporting-storage-package.mdanddocs/guides/REPORTING-LEDGER.md.Patch Changes
28694b0: Reporting storage (experimental): period retirement resolves the period's open issue occurrences (feat(reporting): issueObserver — notify the seller when a reporting issue opens, escalates or resolves (Python parity) #3648 follow-up).
retireExpiredPeriodsdeleted a period's obligation and consumer statements but left itsCONSUMER_STATUS_MISMATCHoccurrences open in the issue-event outbox. Nothing could resolve or escalate them afterwards. An occurrence that had already escalated stayed open forever: the issue observer never received itsresolvedevent, and retention never pruned it.Retirement now records
resolvedfor each of the obligation's open occurrences in the same transaction that deletes the obligation. Pending events stay deliverable: theresolvedevent carriesissue: nulland noprincipal. The whole-occurrence prune collects the occurrence once it is delivered. Outbox rows are never deleted at retirement, so no undelivered event is lost.No migration and no API change.
ReportingIssueEventStoreV1(experimental) now states the contract for custom stores: a store that deletes an obligation resolves the obligation's open occurrences in the same transaction.Updated dependencies [f52e1ae]
Updated dependencies [29c9af4]
Updated dependencies [cae11b7]
Updated dependencies [7a812a1]
Updated dependencies [57ffd9f]
Updated dependencies [727c947]
Updated dependencies [daa32d9]
Updated dependencies [c3790c8]
Updated dependencies [8947543]
Updated dependencies [3971edf]
Updated dependencies [36f34f6]
Updated dependencies [28694b0]
Updated dependencies [8e0ec72]
Updated dependencies [8e7fdd8]
Updated dependencies [307c004]