@adcp/sdk 14.0.0-rc.38 → 14.0.0-rc.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/adcp-storyboard-summary.js +286 -0
- package/bin/adcp.js +221 -10
- package/dist/lib/conformance/schemaLoader.d.mts +1 -1
- package/dist/lib/conformance/schemaLoader.d.ts +1 -1
- package/dist/lib/core/AgentClient.d.mts +6 -1
- package/dist/lib/core/AgentClient.d.ts +6 -1
- package/dist/lib/core/AgentClient.js +9 -0
- package/dist/lib/core/AgentClient.mjs +9 -0
- package/dist/lib/core/ConversationTypes.d.mts +29 -0
- package/dist/lib/core/ConversationTypes.d.ts +29 -0
- package/dist/lib/core/SingleAgentClient.d.mts +3 -1
- package/dist/lib/core/SingleAgentClient.d.ts +3 -1
- package/dist/lib/core/SingleAgentClient.js +9 -1
- package/dist/lib/core/SingleAgentClient.mjs +9 -1
- package/dist/lib/core/TaskExecutor.d.mts +13 -1
- package/dist/lib/core/TaskExecutor.d.ts +13 -1
- package/dist/lib/core/TaskExecutor.js +216 -18
- package/dist/lib/core/TaskExecutor.mjs +216 -19
- package/dist/lib/index.d.mts +5 -2
- package/dist/lib/index.d.ts +5 -2
- package/dist/lib/index.js +10 -1
- package/dist/lib/index.mjs +10 -1
- package/dist/lib/media-buy/action-assessment.d.mts +77 -0
- package/dist/lib/media-buy/action-assessment.d.ts +77 -0
- package/dist/lib/media-buy/action-assessment.js +300 -0
- package/dist/lib/media-buy/action-assessment.mjs +284 -0
- package/dist/lib/media-buy/action-constraints.d.mts +9 -0
- package/dist/lib/media-buy/action-constraints.d.ts +9 -0
- package/dist/lib/media-buy/action-constraints.js +170 -0
- package/dist/lib/media-buy/action-constraints.mjs +146 -0
- package/dist/lib/media-buy/action-contracts.d.mts +40 -0
- package/dist/lib/media-buy/action-contracts.d.ts +40 -0
- package/dist/lib/media-buy/action-contracts.js +304 -0
- package/dist/lib/media-buy/action-contracts.mjs +267 -0
- package/dist/lib/media-buy/action-metadata.generated.d.mts +37 -0
- package/dist/lib/media-buy/action-metadata.generated.d.ts +37 -0
- package/dist/lib/media-buy/action-metadata.generated.js +128 -0
- package/dist/lib/media-buy/action-metadata.generated.mjs +101 -0
- package/dist/lib/media-buy/action-types.d.mts +106 -0
- package/dist/lib/media-buy/action-types.d.ts +106 -0
- package/dist/lib/{types/v3-1-beta/tools.generated.js → media-buy/action-types.js} +2 -2
- package/dist/lib/media-buy/actions.d.mts +6 -0
- package/dist/lib/media-buy/actions.d.ts +6 -0
- package/dist/lib/media-buy/actions.js +38 -0
- package/dist/lib/media-buy/actions.mjs +9 -0
- package/dist/lib/media-buy/available-actions.d.mts +2 -1
- package/dist/lib/media-buy/available-actions.d.ts +2 -1
- package/dist/lib/media-buy/available-actions.js +4 -3
- package/dist/lib/media-buy/available-actions.mjs +4 -3
- package/dist/lib/media-buy/compatibility.js +3 -0
- package/dist/lib/media-buy/compatibility.mjs +3 -0
- package/dist/lib/media-buy/index.d.mts +4 -0
- package/dist/lib/media-buy/index.d.ts +4 -0
- package/dist/lib/media-buy/index.js +3 -1
- package/dist/lib/media-buy/index.mjs +1 -0
- package/dist/lib/media-buy/legacy-action-ids.d.mts +1 -0
- package/dist/lib/media-buy/legacy-action-ids.d.ts +1 -0
- package/dist/lib/media-buy/legacy-action-ids.js +50 -0
- package/dist/lib/media-buy/legacy-action-ids.mjs +26 -0
- package/dist/lib/media-buy/mutations.d.mts +70 -0
- package/dist/lib/media-buy/mutations.d.ts +70 -0
- package/dist/lib/media-buy/mutations.js +525 -0
- package/dist/lib/media-buy/mutations.mjs +498 -0
- package/dist/lib/media-buy/preflight.d.mts +20 -69
- package/dist/lib/media-buy/preflight.d.ts +20 -69
- package/dist/lib/media-buy/preflight.js +100 -336
- package/dist/lib/media-buy/preflight.mjs +111 -333
- package/dist/lib/media-buy/targeting-input.d.mts +7 -4
- package/dist/lib/media-buy/targeting-input.d.ts +7 -4
- package/dist/lib/media-buy/types.d.mts +55 -7
- package/dist/lib/media-buy/types.d.ts +55 -7
- package/dist/lib/protocols/rawResponseCapture.d.mts +12 -0
- package/dist/lib/protocols/rawResponseCapture.d.ts +12 -0
- package/dist/lib/protocols/rawResponseCapture.js +33 -3
- package/dist/lib/protocols/rawResponseCapture.mjs +32 -3
- package/dist/lib/registry/types.generated.d.mts +377 -9
- package/dist/lib/registry/types.generated.d.ts +377 -9
- package/dist/lib/reporting/evidence.d.mts +1 -0
- package/dist/lib/reporting/evidence.d.ts +1 -0
- package/dist/lib/reporting/evidence.js +32 -3
- package/dist/lib/reporting/evidence.mjs +31 -3
- package/dist/lib/reporting/ledger/handler.d.mts +51 -1
- package/dist/lib/reporting/ledger/handler.d.ts +51 -1
- package/dist/lib/reporting/ledger/handler.js +306 -38
- package/dist/lib/reporting/ledger/handler.mjs +304 -37
- package/dist/lib/reporting/ledger/health.d.mts +12 -0
- package/dist/lib/reporting/ledger/health.d.ts +12 -0
- package/dist/lib/reporting/ledger/health.js +15 -2
- package/dist/lib/reporting/ledger/health.mjs +13 -1
- package/dist/lib/reporting/ledger/index.d.mts +3 -0
- package/dist/lib/reporting/ledger/index.d.ts +3 -0
- package/dist/lib/reporting/ledger/index.js +7 -1
- package/dist/lib/reporting/ledger/index.mjs +3 -0
- package/dist/lib/reporting/ledger/lifecycle.d.mts +31 -4
- package/dist/lib/reporting/ledger/lifecycle.d.ts +31 -4
- package/dist/lib/reporting/ledger/lifecycle.js +244 -37
- package/dist/lib/reporting/ledger/lifecycle.mjs +244 -37
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +231 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +231 -0
- package/dist/lib/reporting/ledger/managed-postgres.js +1797 -0
- package/dist/lib/reporting/ledger/managed-postgres.mjs +1776 -0
- package/dist/lib/reporting/ledger/managed.d.mts +355 -0
- package/dist/lib/reporting/ledger/managed.d.ts +355 -0
- package/dist/lib/reporting/ledger/managed.js +661 -0
- package/dist/lib/reporting/ledger/managed.mjs +633 -0
- package/dist/lib/reporting/ledger/notification-activity.d.mts +222 -0
- package/dist/lib/reporting/ledger/notification-activity.d.ts +222 -0
- package/dist/lib/reporting/ledger/notification-activity.js +1139 -0
- package/dist/lib/reporting/ledger/notification-activity.mjs +1110 -0
- package/dist/lib/reporting/ledger/postgres.d.mts +161 -1
- package/dist/lib/reporting/ledger/postgres.d.ts +161 -1
- package/dist/lib/reporting/ledger/postgres.js +1153 -21
- package/dist/lib/reporting/ledger/postgres.mjs +1152 -21
- package/dist/lib/reporting/ledger/producer.d.mts +8 -0
- package/dist/lib/reporting/ledger/producer.d.ts +8 -0
- package/dist/lib/reporting/ledger/producer.js +146 -52
- package/dist/lib/reporting/ledger/producer.mjs +146 -51
- package/dist/lib/reporting/ledger/types.d.mts +358 -1
- package/dist/lib/reporting/ledger/types.d.ts +358 -1
- package/dist/lib/reporting/ledger/types.js +3 -0
- package/dist/lib/reporting/ledger/types.mjs +2 -0
- package/dist/lib/reporting/service/conformance.d.mts +35 -0
- package/dist/lib/reporting/service/conformance.d.ts +35 -0
- package/dist/lib/reporting/service/conformance.js +89 -0
- package/dist/lib/reporting/service/conformance.mjs +68 -0
- package/dist/lib/reporting/service/index.d.mts +182 -0
- package/dist/lib/reporting/service/index.d.ts +182 -0
- package/dist/lib/reporting/service/index.js +793 -0
- package/dist/lib/reporting/service/index.mjs +787 -0
- package/dist/lib/reporting/source/index.d.mts +2 -1
- package/dist/lib/reporting/source/index.d.ts +2 -1
- package/dist/lib/reporting/source/index.js +16 -3
- package/dist/lib/reporting/source/index.mjs +12 -1
- package/dist/lib/reporting/source/inline.d.mts +88 -1
- package/dist/lib/reporting/source/inline.d.ts +88 -1
- package/dist/lib/reporting/source/inline.js +1161 -163
- package/dist/lib/reporting/source/inline.mjs +1166 -163
- package/dist/lib/reporting/source/manifest.js +12 -7
- package/dist/lib/reporting/source/manifest.mjs +12 -7
- package/dist/lib/reporting/source/source.d.mts +26 -0
- package/dist/lib/reporting/source/source.d.ts +26 -0
- package/dist/lib/reporting/source/source.js +71 -2
- package/dist/lib/reporting/source/source.mjs +65 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +18 -0
- package/dist/lib/server/create-adcp-server.d.ts +18 -0
- package/dist/lib/server/create-adcp-server.js +52 -2
- package/dist/lib/server/create-adcp-server.mjs +52 -2
- package/dist/lib/server/decisioning/account.d.mts +1 -1
- package/dist/lib/server/decisioning/account.d.ts +1 -1
- package/dist/lib/server/decisioning/index.d.mts +1 -0
- package/dist/lib/server/decisioning/index.d.ts +1 -0
- package/dist/lib/server/decisioning/platform.d.mts +3 -0
- package/dist/lib/server/decisioning/platform.d.ts +3 -0
- package/dist/lib/server/decisioning/runtime/from-platform.js +88 -10
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +88 -10
- package/dist/lib/server/decisioning/specialisms/reporting.d.mts +38 -0
- package/dist/lib/server/decisioning/specialisms/reporting.d.ts +38 -0
- package/dist/lib/{types/v3-1-beta/index.js → server/decisioning/specialisms/reporting.js} +2 -8
- package/dist/lib/server/decisioning/specialisms/reporting.mjs +0 -0
- package/dist/lib/server/index.d.mts +4 -2
- package/dist/lib/server/index.d.ts +4 -2
- package/dist/lib/server/index.js +5 -0
- package/dist/lib/server/index.mjs +4 -0
- package/dist/lib/server/media-buy-action-resolver.d.mts +63 -0
- package/dist/lib/server/media-buy-action-resolver.d.ts +63 -0
- package/dist/lib/server/media-buy-action-resolver.js +297 -0
- package/dist/lib/server/media-buy-action-resolver.mjs +285 -0
- package/dist/lib/server/media-buy-actions.d.mts +7 -8
- package/dist/lib/server/media-buy-actions.d.ts +7 -8
- package/dist/lib/server/media-buy-actions.js +57 -14
- package/dist/lib/server/media-buy-actions.mjs +63 -14
- package/dist/lib/server/notification-subscriptions/index.d.mts +2 -2
- package/dist/lib/server/notification-subscriptions/index.d.ts +2 -2
- package/dist/lib/server/notification-subscriptions/index.js +2 -0
- package/dist/lib/server/notification-subscriptions/index.mjs +2 -0
- package/dist/lib/server/notification-subscriptions/postgres-runtime.d.mts +3 -1
- package/dist/lib/server/notification-subscriptions/postgres-runtime.d.ts +3 -1
- package/dist/lib/server/notification-subscriptions/postgres-runtime.js +1 -0
- package/dist/lib/server/notification-subscriptions/postgres-runtime.mjs +1 -0
- package/dist/lib/server/notification-subscriptions/runtime.d.mts +12 -1
- package/dist/lib/server/notification-subscriptions/runtime.d.ts +12 -1
- package/dist/lib/server/notification-subscriptions/runtime.js +102 -16
- package/dist/lib/server/notification-subscriptions/runtime.mjs +101 -16
- package/dist/lib/server/notification-subscriptions/types.d.mts +104 -1
- package/dist/lib/server/notification-subscriptions/types.d.ts +104 -1
- package/dist/lib/server/webhook-emitter.d.mts +40 -5
- package/dist/lib/server/webhook-emitter.d.ts +40 -5
- package/dist/lib/server/webhook-emitter.js +23 -12
- package/dist/lib/server/webhook-emitter.mjs +23 -12
- package/dist/lib/storage/interfaces.d.mts +14 -2
- package/dist/lib/storage/interfaces.d.ts +14 -2
- package/dist/lib/testing/compliance/comply.d.mts +9 -3
- package/dist/lib/testing/compliance/comply.d.ts +9 -3
- package/dist/lib/testing/compliance/comply.js +158 -3
- package/dist/lib/testing/compliance/comply.mjs +159 -4
- package/dist/lib/testing/compliance/storyboard-tracks.js +7 -5
- package/dist/lib/testing/compliance/storyboard-tracks.mjs +7 -5
- package/dist/lib/testing/compliance/summary.js +10 -1
- package/dist/lib/testing/compliance/summary.mjs +10 -1
- package/dist/lib/testing/index.d.mts +1 -1
- package/dist/lib/testing/index.d.ts +1 -1
- package/dist/lib/testing/index.js +2 -0
- package/dist/lib/testing/index.mjs +3 -1
- package/dist/lib/testing/storyboard/agent-routing.d.mts +12 -43
- package/dist/lib/testing/storyboard/agent-routing.d.ts +12 -43
- package/dist/lib/testing/storyboard/agent-routing.js +75 -13
- package/dist/lib/testing/storyboard/agent-routing.mjs +70 -12
- package/dist/lib/testing/storyboard/index.d.mts +1 -0
- package/dist/lib/testing/storyboard/index.d.ts +1 -0
- package/dist/lib/testing/storyboard/index.js +3 -0
- package/dist/lib/testing/storyboard/index.mjs +2 -0
- package/dist/lib/testing/storyboard/junit.d.mts +1 -1
- package/dist/lib/testing/storyboard/junit.d.ts +1 -1
- package/dist/lib/testing/storyboard/junit.js +21 -2
- package/dist/lib/testing/storyboard/junit.mjs +21 -2
- package/dist/lib/testing/storyboard/probes.d.mts +130 -0
- package/dist/lib/testing/storyboard/probes.d.ts +130 -0
- package/dist/lib/testing/storyboard/probes.js +682 -10
- package/dist/lib/testing/storyboard/probes.mjs +683 -3
- package/dist/lib/testing/storyboard/request-signing/builder.d.mts +16 -5
- package/dist/lib/testing/storyboard/request-signing/builder.d.ts +16 -5
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +44 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +44 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +46 -22
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +43 -21
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.mts +86 -11
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.ts +86 -11
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.js +119 -25
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +117 -26
- package/dist/lib/testing/storyboard/request-signing/synthesize.js +22 -2
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +22 -2
- package/dist/lib/testing/storyboard/runner.d.mts +103 -0
- package/dist/lib/testing/storyboard/runner.d.ts +103 -0
- package/dist/lib/testing/storyboard/runner.js +1090 -268
- package/dist/lib/testing/storyboard/runner.mjs +1096 -261
- package/dist/lib/testing/storyboard/seeding.d.mts +4 -1
- package/dist/lib/testing/storyboard/seeding.d.ts +4 -1
- package/dist/lib/testing/storyboard/seeding.js +55 -24
- package/dist/lib/testing/storyboard/seeding.mjs +55 -24
- package/dist/lib/testing/storyboard/test-kit.d.mts +37 -3
- package/dist/lib/testing/storyboard/test-kit.d.ts +37 -3
- package/dist/lib/testing/storyboard/test-kit.js +8 -2
- package/dist/lib/testing/storyboard/test-kit.mjs +7 -2
- package/dist/lib/testing/storyboard/types.d.mts +132 -10
- package/dist/lib/testing/storyboard/types.d.ts +132 -10
- package/dist/lib/testing/storyboard/types.js +17 -0
- package/dist/lib/testing/storyboard/types.mjs +16 -0
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/storyboard/validations.js +16 -0
- package/dist/lib/testing/storyboard/validations.mjs +16 -0
- package/dist/lib/testing/test-controller.d.mts +1 -1
- package/dist/lib/testing/test-controller.d.ts +1 -1
- package/dist/lib/types/buy-products.d.ts +71 -6
- package/dist/lib/types/control-media-buy.d.ts +25 -11
- package/dist/lib/types/core.generated.d.mts +69 -10
- package/dist/lib/types/core.generated.d.ts +69 -10
- package/dist/lib/types/create-media-buy.d.ts +71 -6
- package/dist/lib/types/forward-compat-error-codes.d.mts +7 -6
- package/dist/lib/types/forward-compat-error-codes.d.ts +7 -6
- package/dist/lib/types/index.d.mts +2 -2
- package/dist/lib/types/index.d.ts +2 -2
- package/dist/lib/types/schemas.generated.d.ts +8040 -8116
- package/dist/lib/types/schemas.generated.js +1074 -309
- package/dist/lib/types/schemas.generated.mjs +1068 -309
- package/dist/lib/types/tools.generated.d.mts +65 -6
- package/dist/lib/types/tools.generated.d.ts +65 -6
- package/dist/lib/types/update-media-buy.d.ts +71 -6
- package/dist/lib/utils/redact-secrets.d.mts +1 -1
- package/dist/lib/utils/redact-secrets.d.ts +1 -1
- package/dist/lib/utils/redact-secrets.js +1 -1
- package/dist/lib/utils/redact-secrets.mjs +1 -1
- package/dist/lib/v2/projection/cache-versions.d.mts +0 -1
- package/dist/lib/v2/projection/cache-versions.d.ts +0 -1
- package/dist/lib/v2/projection/cache-versions.js +1 -15
- package/dist/lib/v2/projection/cache-versions.mjs +1 -14
- package/dist/lib/v2/projection/canonical-properties.js +2 -2
- package/dist/lib/v2/projection/canonical-properties.mjs +3 -3
- package/dist/lib/v2/projection/registry.d.mts +1 -1
- package/dist/lib/v2/projection/registry.d.ts +1 -1
- package/dist/lib/v2/projection/registry.js +2 -2
- package/dist/lib/v2/projection/registry.mjs +3 -3
- package/dist/lib/version.d.mts +5 -5
- package/dist/lib/version.d.ts +5 -5
- package/dist/lib/version.js +3 -8
- package/dist/lib/version.mjs +3 -8
- package/dist/lib/wholesale-feed-sync/index.d.mts +1 -0
- package/dist/lib/wholesale-feed-sync/index.d.ts +1 -0
- package/dist/lib/wholesale-feed-sync/protocol-types.d.mts +42 -0
- package/dist/lib/wholesale-feed-sync/protocol-types.d.ts +42 -0
- package/dist/lib/wholesale-feed-sync/protocol-types.js +16 -0
- package/dist/lib/wholesale-feed-sync/protocol-types.mjs +0 -0
- package/dist/lib/wholesale-feed-sync/sync.d.mts +6 -6
- package/dist/lib/wholesale-feed-sync/sync.d.ts +6 -6
- package/dist/lib/wholesale-feed-sync/sync.js +3 -1
- package/dist/lib/wholesale-feed-sync/sync.mjs +3 -1
- package/dist/lib/wholesale-feed-sync/types.d.mts +15 -14
- package/dist/lib/wholesale-feed-sync/types.d.ts +15 -14
- package/dist/lib/wholesale-feed-sync/webhook-notification.d.mts +7 -5
- package/dist/lib/wholesale-feed-sync/webhook-notification.d.ts +7 -5
- package/dist/lib/wholesale-feed-sync/webhook-notification.js +45 -0
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +44 -0
- package/docs/CLI.md +1 -1
- package/docs/TYPE-SUMMARY.md +118 -4
- package/docs/guides/ASYNC-API-REFERENCE.md +62 -1
- package/docs/guides/ASYNC-DEVELOPER-GUIDE.md +25 -0
- package/docs/guides/ASYNC-DOCUMENTATION-INDEX.md +3 -0
- package/docs/guides/BUYER-QUICKSTART-3.2.md +5 -0
- package/docs/guides/DURABLE-BUYER-WRITES.md +337 -0
- package/docs/guides/EXISTING-PLATFORM.md +35 -0
- package/docs/guides/MEDIA-BUY-3.2-COMPATIBILITY.md +7 -3
- package/docs/guides/MEDIA-BUY-ACTION-ASSESSMENT.md +300 -0
- package/docs/guides/REPORTING-LEDGER.md +654 -3
- package/docs/guides/REPORTING-SOURCE-EXECUTOR.md +63 -1
- package/docs/guides/SELLER-QUICKSTART-3.2.md +2 -0
- package/docs/guides/VALIDATE-YOUR-AGENT.md +35 -0
- package/docs/guides/idempotency-crash-recovery.md +4 -0
- package/docs/llms.txt +4 -2
- package/docs/migration-14.x-rc-worksheet.md +5 -5
- package/docs/migration-7.9-to-7.10.md +7 -5
- package/examples/README.md +17 -0
- package/examples/durable-buyer-writes/caller.ts +745 -0
- package/examples/durable-buyer-writes/worker.ts +240 -0
- package/examples/reliable-reporting-service/README.md +100 -0
- package/examples/seller-3.2-starter.ts +7 -8
- package/examples/targeting-input-existing-platform.ts +153 -0
- package/package.json +41 -29
- package/skills/build-seller-agent/specialisms/signed-requests.md +11 -1
- package/skills/cross-cutting.md +28 -11
- package/compliance/cache/3.1.0-beta.7/domains/brand/index.yaml +0 -160
- package/compliance/cache/3.1.0-beta.7/domains/brand/scenarios/distributed_brand_resolution.yaml +0 -415
- package/compliance/cache/3.1.0-beta.7/domains/brand/scenarios/single_side_trust_extension.yaml +0 -454
- package/compliance/cache/3.1.0-beta.7/domains/creative/index.yaml +0 -339
- package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/billing_out_of_band.yaml +0 -153
- package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/creative_lifecycle_webhooks.yaml +0 -389
- package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/native_in_feed.yaml +0 -543
- package/compliance/cache/3.1.0-beta.7/domains/governance/index.yaml +0 -682
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/index.yaml +0 -781
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/audience_buy_flow.yaml +0 -380
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/available_actions.yaml +0 -565
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/billing_finality_delivery.yaml +0 -354
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/canonical_formats.yaml +0 -711
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/clicks_buy_flow.yaml +0 -264
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/completed_views_buy_flow.yaml +0 -344
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/create_media_buy_async.yaml +0 -234
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml +0 -419
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/creative_reception.yaml +0 -247
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/delivery_reporting.yaml +0 -357
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/dependency_impairment.yaml +0 -633
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/dependency_impairment_cardinality.yaml +0 -800
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/event_dedup_flow.yaml +0 -399
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/frequency_cap_enforcement.yaml +0 -309
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_approved.yaml +0 -214
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_conditions.yaml +0 -199
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_denied.yaml +0 -204
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_denied_recovery.yaml +0 -252
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/invalid_transitions.yaml +0 -289
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/inventory_list_no_match.yaml +0 -148
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/inventory_list_targeting.yaml +0 -276
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/measurement_accountability.yaml +0 -244
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/measurement_terms_rejected.yaml +0 -203
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/pending_creatives_to_start.yaml +0 -274
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/per_creative_conversion_attribution.yaml +0 -500
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/performance_buy_flow.yaml +0 -428
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/performance_buy_flow_roas.yaml +0 -470
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/product_signal_targeting.yaml +0 -373
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_finalize.yaml +0 -399
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_finalize_asap_timing.yaml +0 -264
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_not_found_errors.yaml +0 -257
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_audit_observation.yaml +0 -333
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_enforcement.yaml +0 -517
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_truth_of_claim.yaml +0 -294
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/reach_buy_flow.yaml +0 -823
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/refine_finalize_exclusivity.yaml +0 -360
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/refine_products.yaml +0 -148
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_accountability.yaml +0 -293
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +0 -307
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_optimization_flow.yaml +0 -576
- package/compliance/cache/3.1.0-beta.7/domains/media-buy/state-machine.yaml +0 -442
- package/compliance/cache/3.1.0-beta.7/domains/signals/index.yaml +0 -266
- package/compliance/cache/3.1.0-beta.7/domains/sponsored-intelligence/index.yaml +0 -256
- package/compliance/cache/3.1.0-beta.7/index.json +0 -356
- package/compliance/cache/3.1.0-beta.7/protocols/brand/index.yaml +0 -160
- package/compliance/cache/3.1.0-beta.7/protocols/brand/scenarios/distributed_brand_resolution.yaml +0 -415
- package/compliance/cache/3.1.0-beta.7/protocols/brand/scenarios/single_side_trust_extension.yaml +0 -454
- package/compliance/cache/3.1.0-beta.7/protocols/creative/index.yaml +0 -339
- package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/billing_out_of_band.yaml +0 -153
- package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/creative_lifecycle_webhooks.yaml +0 -389
- package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/native_in_feed.yaml +0 -543
- package/compliance/cache/3.1.0-beta.7/protocols/governance/index.yaml +0 -682
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/index.yaml +0 -781
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/audience_buy_flow.yaml +0 -380
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/available_actions.yaml +0 -565
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/billing_finality_delivery.yaml +0 -354
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/canonical_formats.yaml +0 -711
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/clicks_buy_flow.yaml +0 -264
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/completed_views_buy_flow.yaml +0 -344
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/create_media_buy_async.yaml +0 -234
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml +0 -419
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/creative_reception.yaml +0 -247
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/delivery_reporting.yaml +0 -357
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/dependency_impairment.yaml +0 -633
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/dependency_impairment_cardinality.yaml +0 -800
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/event_dedup_flow.yaml +0 -399
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/frequency_cap_enforcement.yaml +0 -309
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_approved.yaml +0 -214
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_conditions.yaml +0 -199
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_denied.yaml +0 -204
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_denied_recovery.yaml +0 -252
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/invalid_transitions.yaml +0 -289
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/inventory_list_no_match.yaml +0 -148
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/inventory_list_targeting.yaml +0 -276
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/measurement_accountability.yaml +0 -244
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/measurement_terms_rejected.yaml +0 -203
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/pending_creatives_to_start.yaml +0 -274
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/per_creative_conversion_attribution.yaml +0 -500
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/performance_buy_flow.yaml +0 -428
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/performance_buy_flow_roas.yaml +0 -470
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/product_signal_targeting.yaml +0 -373
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_finalize.yaml +0 -399
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_finalize_asap_timing.yaml +0 -264
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_not_found_errors.yaml +0 -257
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_audit_observation.yaml +0 -333
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_enforcement.yaml +0 -517
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_truth_of_claim.yaml +0 -294
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/reach_buy_flow.yaml +0 -823
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/refine_finalize_exclusivity.yaml +0 -360
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/refine_products.yaml +0 -148
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_accountability.yaml +0 -293
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +0 -307
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml +0 -576
- package/compliance/cache/3.1.0-beta.7/protocols/media-buy/state-machine.yaml +0 -442
- package/compliance/cache/3.1.0-beta.7/protocols/signals/index.yaml +0 -266
- package/compliance/cache/3.1.0-beta.7/protocols/sponsored-intelligence/index.yaml +0 -256
- package/compliance/cache/3.1.0-beta.7/specialisms/audience-sync/index.yaml +0 -313
- package/compliance/cache/3.1.0-beta.7/specialisms/brand-rights/index.yaml +0 -350
- package/compliance/cache/3.1.0-beta.7/specialisms/brand-rights/scenarios/governance_denied.yaml +0 -226
- package/compliance/cache/3.1.0-beta.7/specialisms/collection-lists/index.yaml +0 -359
- package/compliance/cache/3.1.0-beta.7/specialisms/content-standards/index.yaml +0 -572
- package/compliance/cache/3.1.0-beta.7/specialisms/creative-ad-server/index.yaml +0 -409
- package/compliance/cache/3.1.0-beta.7/specialisms/creative-generative/generative-seller.yaml +0 -807
- package/compliance/cache/3.1.0-beta.7/specialisms/creative-generative/index.yaml +0 -758
- package/compliance/cache/3.1.0-beta.7/specialisms/creative-template/index.yaml +0 -510
- package/compliance/cache/3.1.0-beta.7/specialisms/governance-aware-seller/index.yaml +0 -143
- package/compliance/cache/3.1.0-beta.7/specialisms/governance-aware-seller/scenarios/governance_multi_agent_rejected.yaml +0 -117
- package/compliance/cache/3.1.0-beta.7/specialisms/governance-delivery-monitor/index.yaml +0 -441
- package/compliance/cache/3.1.0-beta.7/specialisms/governance-spend-authority/denied.yaml +0 -221
- package/compliance/cache/3.1.0-beta.7/specialisms/governance-spend-authority/index.yaml +0 -330
- package/compliance/cache/3.1.0-beta.7/specialisms/property-lists/index.yaml +0 -482
- package/compliance/cache/3.1.0-beta.7/specialisms/sales-broadcast-tv/index.yaml +0 -738
- package/compliance/cache/3.1.0-beta.7/specialisms/sales-catalog-driven/index.yaml +0 -840
- package/compliance/cache/3.1.0-beta.7/specialisms/sales-guaranteed/index.yaml +0 -601
- package/compliance/cache/3.1.0-beta.7/specialisms/sales-non-guaranteed/index.yaml +0 -546
- package/compliance/cache/3.1.0-beta.7/specialisms/sales-proposal-mode/index.yaml +0 -586
- package/compliance/cache/3.1.0-beta.7/specialisms/sales-social/index.yaml +0 -919
- package/compliance/cache/3.1.0-beta.7/specialisms/signal-marketplace/index.yaml +0 -424
- package/compliance/cache/3.1.0-beta.7/specialisms/signal-marketplace/scenarios/governance_denied.yaml +0 -211
- package/compliance/cache/3.1.0-beta.7/specialisms/signal-owned/index.yaml +0 -317
- package/compliance/cache/3.1.0-beta.7/specialisms/sponsored-intelligence/index.yaml +0 -59
- package/compliance/cache/3.1.0-beta.7/test-kits/acme-outdoor-live.yaml +0 -78
- package/compliance/cache/3.1.0-beta.7/test-kits/acme-outdoor.yaml +0 -223
- package/compliance/cache/3.1.0-beta.7/test-kits/billing-gate-runner.yaml +0 -115
- package/compliance/cache/3.1.0-beta.7/test-kits/bistro-oranje.yaml +0 -126
- package/compliance/cache/3.1.0-beta.7/test-kits/distributed-brand-runner.yaml +0 -281
- package/compliance/cache/3.1.0-beta.7/test-kits/nova-motors.yaml +0 -262
- package/compliance/cache/3.1.0-beta.7/test-kits/osei-natural.yaml +0 -126
- package/compliance/cache/3.1.0-beta.7/test-kits/parallel-dispatch-runner.yaml +0 -196
- package/compliance/cache/3.1.0-beta.7/test-kits/rate-limit-trip-runner.yaml +0 -172
- package/compliance/cache/3.1.0-beta.7/test-kits/signed-requests-runner.yaml +0 -155
- package/compliance/cache/3.1.0-beta.7/test-kits/single-side-trust-runner.yaml +0 -294
- package/compliance/cache/3.1.0-beta.7/test-kits/substitution-observer-runner.yaml +0 -688
- package/compliance/cache/3.1.0-beta.7/test-kits/summit-foods.yaml +0 -125
- package/compliance/cache/3.1.0-beta.7/test-kits/webhook-receiver-runner.yaml +0 -265
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/001-minimal-plan.json +0 -43
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/002-full-plan.json +0 -217
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/003-bookkeeping-stripped.json +0 -60
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/004a-human-review-omitted.json +0 -43
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/004b-human-review-explicit-null.json +0 -49
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/005a-policy-categories-order-1.json +0 -53
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/005b-policy-categories-order-2.json +0 -57
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/006a-ext-trace-v1.json +0 -49
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/006b-ext-trace-v2.json +0 -53
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/007-unicode-objectives.json +0 -43
- package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/008-numeric-canonicalization.json +0 -65
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/README.md +0 -220
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/canonicalization.json +0 -241
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/keys.json +0 -60
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/001-no-signature-header.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/002-wrong-tag.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/003-expired-signature.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/004-window-too-long.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/005-alg-not-allowed.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/006-missing-covered-component.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/007-missing-content-digest.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/008-unknown-keyid.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/009-key-ops-missing-verify.json +0 -27
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/010-content-digest-mismatch.json +0 -33
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/011-malformed-header.json +0 -27
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/012-missing-expires-param.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/013-expires-le-created.json +0 -27
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/014-missing-nonce-param.json +0 -27
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/015-signature-invalid.json +0 -28
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/016-replayed-nonce.json +0 -35
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/017-key-revoked.json +0 -38
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/018-digest-covered-when-forbidden.json +0 -28
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/019-signature-without-signature-input.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/020-rate-abuse.json +0 -34
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/021-duplicate-signature-input-label.json +0 -31
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/022-multi-valued-content-type.json +0 -31
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/023-multi-valued-content-digest.json +0 -32
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/024-unquoted-string-param.json +0 -31
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/025-jwk-alg-crv-mismatch.json +0 -43
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/026-non-ascii-host.json +0 -31
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/027-webhook-registration-authentication-unsigned.json +0 -25
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/028-unsigned-protocol-method-required.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/001-basic-post.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/002-post-with-content-digest.json +0 -31
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/003-es256-post.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/004-multiple-signature-labels.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/005-default-port-stripped.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/006-dot-segment-path.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/007-query-byte-preserved.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/008-percent-encoded-path.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/009-percent-encoded-unreserved-decoded.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/010-percent-encoded-slash-preserved.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/011-ipv6-authority.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/012-ipv6-authority-default-port-stripped.json +0 -30
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/README.md +0 -211
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/keys.json +0 -61
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/001-wrong-tag.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/002-expired-signature.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/003-window-too-long.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/004-alg-not-allowed.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/005-missing-authority-component.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/006-missing-content-digest.json +0 -25
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/007-unknown-keyid.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/008-wrong-adcp-use.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/009-content-digest-mismatch.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/010-malformed-signature-input.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/011-signature-without-input.json +0 -25
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/012-missing-expires-param.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/013-expires-le-created.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/014-missing-nonce-param.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/015-signature-invalid.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/016-replayed-nonce.json +0 -37
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/017-key-revoked.json +0 -32
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/018-rate-abuse.json +0 -33
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/019-revocation-stale.json +0 -32
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/020-key-ops-missing-verify.json +0 -41
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/021-base64-alphabet-mixing.json +0 -26
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/001-basic-post.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/002-es256-post.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/003-multiple-signature-labels.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/004-default-port-stripped.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/005-percent-encoded-path.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/006-query-byte-preserved.json +0 -24
- package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/007-body-without-idempotency-key.json +0 -25
- package/compliance/cache/3.1.0-beta.7/universal/billing-gate-dispatch.yaml +0 -450
- package/compliance/cache/3.1.0-beta.7/universal/canonical-format-validate-input.yaml +0 -640
- package/compliance/cache/3.1.0-beta.7/universal/capability-discovery.yaml +0 -125
- package/compliance/cache/3.1.0-beta.7/universal/collection-lists-pagination-integrity.yaml +0 -306
- package/compliance/cache/3.1.0-beta.7/universal/comply-controller-mode-gate.yaml +0 -141
- package/compliance/cache/3.1.0-beta.7/universal/content-standards-pagination-integrity.yaml +0 -326
- package/compliance/cache/3.1.0-beta.7/universal/deterministic-testing.yaml +0 -1430
- package/compliance/cache/3.1.0-beta.7/universal/error-compliance-signals.yaml +0 -377
- package/compliance/cache/3.1.0-beta.7/universal/error-compliance.yaml +0 -528
- package/compliance/cache/3.1.0-beta.7/universal/fictional-entities.yaml +0 -307
- package/compliance/cache/3.1.0-beta.7/universal/get-media-buys-pagination-integrity.yaml +0 -160
- package/compliance/cache/3.1.0-beta.7/universal/get-signals-pagination-integrity.yaml +0 -211
- package/compliance/cache/3.1.0-beta.7/universal/idempotency.yaml +0 -861
- package/compliance/cache/3.1.0-beta.7/universal/notification-config-event-scope.yaml +0 -119
- package/compliance/cache/3.1.0-beta.7/universal/notification-config-lifecycle.yaml +0 -337
- package/compliance/cache/3.1.0-beta.7/universal/notification-config-rejections.yaml +0 -107
- package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity-creative-formats.yaml +0 -265
- package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity-list-accounts.yaml +0 -245
- package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity.yaml +0 -263
- package/compliance/cache/3.1.0-beta.7/universal/property-lists-pagination-integrity.yaml +0 -307
- package/compliance/cache/3.1.0-beta.7/universal/read-tool-idempotency.yaml +0 -405
- package/compliance/cache/3.1.0-beta.7/universal/runner-output-contract.yaml +0 -1266
- package/compliance/cache/3.1.0-beta.7/universal/schema-validation-signals.yaml +0 -181
- package/compliance/cache/3.1.0-beta.7/universal/schema-validation.yaml +0 -548
- package/compliance/cache/3.1.0-beta.7/universal/security.yaml +0 -539
- package/compliance/cache/3.1.0-beta.7/universal/signed-requests.yaml +0 -217
- package/compliance/cache/3.1.0-beta.7/universal/stale-response-advisory.yaml +0 -295
- package/compliance/cache/3.1.0-beta.7/universal/storyboard-schema.yaml +0 -2136
- package/compliance/cache/3.1.0-beta.7/universal/v3-envelope-integrity.yaml +0 -117
- package/compliance/cache/3.1.0-beta.7/universal/version-negotiation.yaml +0 -130
- package/compliance/cache/3.1.0-beta.7/universal/webhook-emission.yaml +0 -411
- package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-bulk-webhooks.yaml +0 -82
- package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-product-webhooks.yaml +0 -83
- package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-products.yaml +0 -151
- package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-signal-webhooks.yaml +0 -83
- package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-signals.yaml +0 -149
- package/dist/lib/types/v3-1-beta/index.d.mts +0 -1
- package/dist/lib/types/v3-1-beta/index.d.ts +0 -1
- package/dist/lib/types/v3-1-beta/index.mjs +0 -1
- package/dist/lib/types/v3-1-beta/tools.generated.d.mts +0 -26788
- package/dist/lib/types/v3-1-beta/tools.generated.d.ts +0 -26788
- /package/dist/lib/{types/v3-1-beta/tools.generated.mjs → media-buy/action-types.mjs} +0 -0
|
@@ -19,9 +19,12 @@ import {
|
|
|
19
19
|
REPORTING_CONSUMER_STATUS_ERROR_FIELD_MAX_BYTES,
|
|
20
20
|
REPORTING_CONSUMER_STATUS_ERROR_MESSAGE_MAX_BYTES,
|
|
21
21
|
REPORTING_CONSUMER_STATUS_MAX_BYTES,
|
|
22
|
+
REPORTING_LEDGER_AUTHORITY,
|
|
22
23
|
ReportingLedgerSnapshotUnavailableError
|
|
23
24
|
} from "./types.mjs";
|
|
24
25
|
import { ReportingConsumerStatusConflictError } from "./types.mjs";
|
|
26
|
+
import { moreSevereReportingHealthV1, projectManagedDelivery } from "./handler.mjs";
|
|
27
|
+
import { reportingCanonicalAdjustmentSha256V1 } from "./producer.mjs";
|
|
25
28
|
import {
|
|
26
29
|
normalizeReportingConsumerStatusIdsV1,
|
|
27
30
|
reportingConsumerStatusChainKeyFromIdentityV1,
|
|
@@ -174,6 +177,12 @@ CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_scope
|
|
|
174
177
|
ON adcp_reporting_consumer_statuses (account_id, consumer_id, created_at, consumer_status_id);
|
|
175
178
|
CREATE UNIQUE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_current
|
|
176
179
|
ON adcp_reporting_consumer_statuses (account_id, consumer_id, chain_key) WHERE is_current;
|
|
180
|
+
-- The managed state digest is read inside the apply transaction, under the
|
|
181
|
+
-- account lock. Filtering this table by obligation alone matched no index, so
|
|
182
|
+
-- every apply scanned every consumer status in the deployment while holding
|
|
183
|
+
-- that lock and pushed concurrent Core writes past their lock timeout.
|
|
184
|
+
CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_obligation
|
|
185
|
+
ON adcp_reporting_consumer_statuses (obligation_id, created_at, consumer_status_id);
|
|
177
186
|
|
|
178
187
|
CREATE TABLE IF NOT EXISTS adcp_reporting_consumer_status_batches (
|
|
179
188
|
account_id TEXT NOT NULL,
|
|
@@ -198,6 +207,29 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_issues (
|
|
|
198
207
|
CREATE INDEX IF NOT EXISTS adcp_reporting_issues_obligation
|
|
199
208
|
ON adcp_reporting_issues (obligation_id, observed_at, issue_id);
|
|
200
209
|
|
|
210
|
+
-- Per-obligation reconciliation watermark. A reconcile that changes nothing
|
|
211
|
+
-- still has to record that it looked, or a change with no health effect stays
|
|
212
|
+
-- a candidate forever and starves newer work behind it. Core, not managed:
|
|
213
|
+
-- the lifecycle sweep is a Core concern and must not depend on the add-on.
|
|
214
|
+
CREATE TABLE IF NOT EXISTS adcp_reporting_lifecycle_state (
|
|
215
|
+
obligation_id TEXT PRIMARY KEY REFERENCES adcp_reporting_obligations(obligation_id),
|
|
216
|
+
processed_state_version TEXT,
|
|
217
|
+
processed_roster_version TEXT,
|
|
218
|
+
processed_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
219
|
+
);
|
|
220
|
+
CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_processed
|
|
221
|
+
ON adcp_reporting_lifecycle_state (processed_at, obligation_id);
|
|
222
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
223
|
+
ADD COLUMN IF NOT EXISTS current_roster_version TEXT;
|
|
224
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
225
|
+
ADD COLUMN IF NOT EXISTS failure_count INTEGER NOT NULL DEFAULT 0;
|
|
226
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
227
|
+
ADD COLUMN IF NOT EXISTS next_attempt_at TIMESTAMPTZ;
|
|
228
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
229
|
+
ADD COLUMN IF NOT EXISTS roster_refreshed_at TIMESTAMPTZ;
|
|
230
|
+
CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_roster_refreshed
|
|
231
|
+
ON adcp_reporting_lifecycle_state (roster_refreshed_at, obligation_id);
|
|
232
|
+
|
|
201
233
|
CREATE TABLE IF NOT EXISTS adcp_reporting_transitions (
|
|
202
234
|
transition_id TEXT PRIMARY KEY,
|
|
203
235
|
transition_sequence BIGSERIAL NOT NULL UNIQUE,
|
|
@@ -237,7 +269,23 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_checkpoints (
|
|
|
237
269
|
CREATE INDEX IF NOT EXISTS adcp_reporting_checkpoints_expiry
|
|
238
270
|
ON adcp_reporting_checkpoints (expires_at);
|
|
239
271
|
`.trim();
|
|
272
|
+
const REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT = "adcp_reporting_transitions_finality_recorded";
|
|
273
|
+
const REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION = `
|
|
274
|
+
DO $$
|
|
275
|
+
BEGIN
|
|
276
|
+
IF NOT EXISTS (
|
|
277
|
+
SELECT 1 FROM pg_constraint
|
|
278
|
+
WHERE conrelid = 'adcp_reporting_transitions'::regclass
|
|
279
|
+
AND conname = '${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}'
|
|
280
|
+
) THEN
|
|
281
|
+
ALTER TABLE adcp_reporting_transitions
|
|
282
|
+
ADD CONSTRAINT ${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}
|
|
283
|
+
CHECK (data ? 'finality') NOT VALID;
|
|
284
|
+
END IF;
|
|
285
|
+
END $$;
|
|
286
|
+
`.trim();
|
|
240
287
|
const MAX_SNAPSHOT_ITEMS = 1e4;
|
|
288
|
+
const MAX_LIFECYCLE_RECEIPT_LEAVES = 1e5;
|
|
241
289
|
const MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
|
|
242
290
|
const MAX_ACTIVE_SNAPSHOTS_PER_ACCOUNT = 32;
|
|
243
291
|
const MAX_ACTIVE_SNAPSHOT_BYTES_PER_ACCOUNT = 128 * 1024 * 1024;
|
|
@@ -292,6 +340,9 @@ async function sweepExpiredReportingLedgerState(pool, limit = 1e3) {
|
|
|
292
340
|
class PostgresReportingLedgerStore {
|
|
293
341
|
constructor(pool, options = {}) {
|
|
294
342
|
this.pool = pool;
|
|
343
|
+
this.managedDelivery = options.managedDelivery === true;
|
|
344
|
+
this.obligatedConsumers = options.obligatedConsumers;
|
|
345
|
+
this[REPORTING_LEDGER_AUTHORITY] = { substrate: pool, managedDelivery: this.managedDelivery };
|
|
295
346
|
const development = process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development";
|
|
296
347
|
if (!development && !options.acknowledgeIsolatedDatabase) {
|
|
297
348
|
throw new Error(
|
|
@@ -299,6 +350,8 @@ class PostgresReportingLedgerStore {
|
|
|
299
350
|
);
|
|
300
351
|
}
|
|
301
352
|
this.consumerMismatchEscalation = assertReportingConsumerMismatchEscalation(options.consumerMismatchEscalation);
|
|
353
|
+
this.notificationActivityPort = options.notificationActivityPort;
|
|
354
|
+
this.transactionalNotificationActivity = options.notificationActivityPort !== void 0;
|
|
302
355
|
}
|
|
303
356
|
pool;
|
|
304
357
|
/**
|
|
@@ -309,6 +362,11 @@ class PostgresReportingLedgerStore {
|
|
|
309
362
|
* contradict the summary.
|
|
310
363
|
*/
|
|
311
364
|
consumerMismatchEscalation;
|
|
365
|
+
[REPORTING_LEDGER_AUTHORITY];
|
|
366
|
+
managedDelivery;
|
|
367
|
+
obligatedConsumers;
|
|
368
|
+
transactionalNotificationActivity;
|
|
369
|
+
notificationActivityPort;
|
|
312
370
|
async putConfiguration(configuration) {
|
|
313
371
|
return this.transaction(
|
|
314
372
|
async (client) => {
|
|
@@ -409,14 +467,26 @@ class PostgresReportingLedgerStore {
|
|
|
409
467
|
positiveInteger(input.limit, "limit");
|
|
410
468
|
if (input.limit > 1e3) throw new RangeError("limit must not exceed 1000");
|
|
411
469
|
if (!Number.isFinite(Date.parse(input.ledgerAsOf))) throw new TypeError("ledgerAsOf must be an RFC 3339 instant");
|
|
470
|
+
const managedDueArm = await this.managedDueTablesReady() ? MANAGED_DUE_ARM : "";
|
|
412
471
|
const result = await this.query(
|
|
413
472
|
`SELECT obligation.data FROM adcp_reporting_obligations obligation
|
|
414
473
|
LEFT JOIN LATERAL (
|
|
415
|
-
SELECT transition.data->>'health' AS health
|
|
474
|
+
SELECT transition.data->>'health' AS health, transition.occurred_at
|
|
416
475
|
FROM adcp_reporting_transitions transition
|
|
417
476
|
WHERE transition.obligation_id = obligation.obligation_id
|
|
418
477
|
ORDER BY transition.transition_sequence DESC LIMIT 1
|
|
419
478
|
) latest ON TRUE
|
|
479
|
+
LEFT JOIN adcp_reporting_lifecycle_state state
|
|
480
|
+
ON state.obligation_id = obligation.obligation_id
|
|
481
|
+
CROSS JOIN LATERAL (
|
|
482
|
+
-- A reconcile that changed no health still has to count as work done,
|
|
483
|
+
-- or a managed change with no health effect keeps the obligation due
|
|
484
|
+
-- forever and, at small page sizes, starves everything behind it.
|
|
485
|
+
SELECT GREATEST(
|
|
486
|
+
COALESCE(state.processed_at, obligation.created_at),
|
|
487
|
+
COALESCE(latest.occurred_at, obligation.created_at)
|
|
488
|
+
) AS since
|
|
489
|
+
) watermark
|
|
420
490
|
WHERE ($1::text IS NULL OR obligation.account_id = $1)
|
|
421
491
|
AND ((COALESCE(latest.health, 'waiting') = 'waiting'
|
|
422
492
|
AND (obligation.data->>'expectedAt')::timestamptz <= $2)
|
|
@@ -427,8 +497,14 @@ class PostgresReportingLedgerStore {
|
|
|
427
497
|
AND EXISTS (
|
|
428
498
|
SELECT 1 FROM adcp_reporting_revisions revision
|
|
429
499
|
WHERE revision.obligation_id = obligation.obligation_id
|
|
430
|
-
))
|
|
431
|
-
|
|
500
|
+
))
|
|
501
|
+
${managedDueArm} )
|
|
502
|
+
-- A tenant that keeps failing must not monopolise every sweep. Its
|
|
503
|
+
-- backoff cursor holds it out of selection until it is due again,
|
|
504
|
+
-- while processed_at ordering still favours whoever has waited
|
|
505
|
+
-- longest among the eligible.
|
|
506
|
+
AND (state.next_attempt_at IS NULL OR state.next_attempt_at <= $2)
|
|
507
|
+
ORDER BY watermark.since, LEAST(
|
|
432
508
|
(obligation.data->>'expectedAt')::timestamptz,
|
|
433
509
|
(obligation.data->>'recoveryDeadlineAt')::timestamptz
|
|
434
510
|
), obligation.obligation_id
|
|
@@ -575,7 +651,8 @@ class PostgresReportingLedgerStore {
|
|
|
575
651
|
obligationId: revision.reporting_obligation_id,
|
|
576
652
|
owner: lease.owner,
|
|
577
653
|
generation: lease.generation
|
|
578
|
-
}
|
|
654
|
+
},
|
|
655
|
+
revisionLegacyCanonicalDigestReplay
|
|
579
656
|
);
|
|
580
657
|
}
|
|
581
658
|
async getRevision(id, accountId) {
|
|
@@ -649,7 +726,8 @@ class PostgresReportingLedgerStore {
|
|
|
649
726
|
obligationId: adjustment.reporting_obligation_id,
|
|
650
727
|
owner: lease.owner,
|
|
651
728
|
generation: lease.generation
|
|
652
|
-
}
|
|
729
|
+
},
|
|
730
|
+
adjustmentLegacyCanonicalDigestReplay
|
|
653
731
|
);
|
|
654
732
|
}
|
|
655
733
|
async listAdjustments(obligationId) {
|
|
@@ -1040,27 +1118,59 @@ class PostgresReportingLedgerStore {
|
|
|
1040
1118
|
);
|
|
1041
1119
|
return result.rows.map((row) => clone(row.data));
|
|
1042
1120
|
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Resolves — and for pre-SDK-14 rows persists — the observed finality baseline
|
|
1123
|
+
* of the obligation's latest transition, so the lifecycle decision and this
|
|
1124
|
+
* store's compare-and-set read one committed value.
|
|
1125
|
+
*/
|
|
1126
|
+
async resolveTransitionFinalityBaseline(obligationId) {
|
|
1127
|
+
const lock = await this.accountLockForObligation(obligationId);
|
|
1128
|
+
return this.transaction((client) => resolveStoredFinalityBaseline(client, obligationId), {
|
|
1129
|
+
preBeginAdvisoryLock: lock
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1043
1132
|
async appendTransition(transition) {
|
|
1044
1133
|
const lock = await this.accountLockForObligation(transition.reporting_obligation_id);
|
|
1134
|
+
return assertFinalityWriterFence(() => this.appendTransitionWithinLock(transition, lock));
|
|
1135
|
+
}
|
|
1136
|
+
async appendTransitionWithinLock(transition, lock) {
|
|
1045
1137
|
return this.transaction(
|
|
1046
1138
|
async (client) => {
|
|
1139
|
+
if (this.notificationActivityPort) {
|
|
1140
|
+
await assertNoLegacyPendingTransitions(client, transition.reporting_obligation_id);
|
|
1141
|
+
}
|
|
1142
|
+
const previousFinality = await resolveStoredFinalityBaseline(client, transition.reporting_obligation_id);
|
|
1143
|
+
const latest = await client.query(
|
|
1144
|
+
`SELECT data->>'health' AS health FROM adcp_reporting_transitions
|
|
1145
|
+
WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1`,
|
|
1146
|
+
[transition.reporting_obligation_id]
|
|
1147
|
+
);
|
|
1148
|
+
if ((latest.rows[0]?.health ?? "waiting") !== transition.previousHealth || transition.previousFinality !== void 0 && previousFinality !== transition.previousFinality) {
|
|
1149
|
+
return { inserted: false };
|
|
1150
|
+
}
|
|
1151
|
+
const storedTransition = this.notificationActivityPort ? { ...transition, notifiedAt: transition.occurredAt } : transition;
|
|
1047
1152
|
const result = await client.query(
|
|
1048
1153
|
`INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
SELECT previous.data->>'health' FROM adcp_reporting_transitions previous
|
|
1052
|
-
WHERE previous.obligation_id = $2
|
|
1053
|
-
ORDER BY previous.transition_sequence DESC LIMIT 1
|
|
1054
|
-
), 'waiting') = $5
|
|
1055
|
-
ON CONFLICT DO NOTHING RETURNING transition_id`,
|
|
1154
|
+
VALUES ($1, $2, $3::jsonb, $4)
|
|
1155
|
+
ON CONFLICT DO NOTHING RETURNING transition_id`,
|
|
1056
1156
|
[
|
|
1057
1157
|
transition.transitionId,
|
|
1058
1158
|
transition.reporting_obligation_id,
|
|
1059
|
-
JSON.stringify(
|
|
1060
|
-
transition.occurredAt
|
|
1061
|
-
transition.previousHealth
|
|
1159
|
+
JSON.stringify(storedTransition),
|
|
1160
|
+
transition.occurredAt
|
|
1062
1161
|
]
|
|
1063
1162
|
);
|
|
1163
|
+
if (result.rowCount === 1 && this.notificationActivityPort) {
|
|
1164
|
+
const obligation = await client.query(
|
|
1165
|
+
"SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1",
|
|
1166
|
+
[transition.reporting_obligation_id]
|
|
1167
|
+
);
|
|
1168
|
+
if (!obligation.rows[0]) throw new Error("Reporting obligation is unavailable");
|
|
1169
|
+
await this.notificationActivityPort.recordTransition(
|
|
1170
|
+
{ transition, obligation: obligation.rows[0].data },
|
|
1171
|
+
client
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1064
1174
|
return { inserted: result.rowCount === 1 };
|
|
1065
1175
|
},
|
|
1066
1176
|
{ preBeginAdvisoryLock: lock }
|
|
@@ -1068,12 +1178,19 @@ class PostgresReportingLedgerStore {
|
|
|
1068
1178
|
}
|
|
1069
1179
|
async applyLifecycleProjection(input) {
|
|
1070
1180
|
const lock = await this.accountLockForObligation(input.reporting_obligation_id);
|
|
1181
|
+
return assertFinalityWriterFence(() => this.applyLifecycleProjectionWithinLock(input, lock));
|
|
1182
|
+
}
|
|
1183
|
+
async applyLifecycleProjectionWithinLock(input, lock) {
|
|
1071
1184
|
return this.transaction(
|
|
1072
1185
|
async (client) => {
|
|
1073
1186
|
const obligations = await client.query(
|
|
1074
1187
|
`SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1 FOR UPDATE`,
|
|
1075
1188
|
[input.reporting_obligation_id]
|
|
1076
1189
|
);
|
|
1190
|
+
if (this.notificationActivityPort) {
|
|
1191
|
+
await assertNoLegacyPendingTransitions(client, input.reporting_obligation_id);
|
|
1192
|
+
}
|
|
1193
|
+
const previousFinality = await resolveStoredFinalityBaseline(client, input.reporting_obligation_id);
|
|
1077
1194
|
const revisions = await client.query(
|
|
1078
1195
|
`SELECT revision_id FROM adcp_reporting_revisions
|
|
1079
1196
|
WHERE obligation_id = $1 ORDER BY revision_number, revision_id`,
|
|
@@ -1087,11 +1204,26 @@ class PostgresReportingLedgerStore {
|
|
|
1087
1204
|
const revisionIds = revisions.rows.map((value) => value.revision_id);
|
|
1088
1205
|
const previousHealth = latest.rows[0]?.health ?? "waiting";
|
|
1089
1206
|
const obligation = obligations.rows[0]?.data;
|
|
1090
|
-
if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || canonicalJsonV1(revisionIds) !== canonicalJsonV1(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth) {
|
|
1207
|
+
if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || canonicalJsonV1(revisionIds) !== canonicalJsonV1(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth || input.expectedPreviousFinality !== void 0 && previousFinality !== input.expectedPreviousFinality) {
|
|
1091
1208
|
return { applied: false, transitionInserted: false };
|
|
1092
1209
|
}
|
|
1210
|
+
if (input.expectedManagedStateVersion !== void 0 && await this.readManagedStateVersion(client, input.reporting_obligation_id) !== input.expectedManagedStateVersion) {
|
|
1211
|
+
return { applied: false, transitionInserted: false };
|
|
1212
|
+
}
|
|
1213
|
+
if (input.expectedObligatedConsumerRosterVersion !== void 0) {
|
|
1214
|
+
const state = await client.query(
|
|
1215
|
+
`SELECT current_roster_version FROM adcp_reporting_lifecycle_state
|
|
1216
|
+
WHERE obligation_id = $1 FOR UPDATE`,
|
|
1217
|
+
[input.reporting_obligation_id]
|
|
1218
|
+
);
|
|
1219
|
+
const observed = state.rows[0]?.current_roster_version;
|
|
1220
|
+
if (observed !== void 0 && observed !== input.expectedObligatedConsumerRosterVersion) {
|
|
1221
|
+
return { applied: false, transitionInserted: false };
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1093
1224
|
let transitionInserted = false;
|
|
1094
1225
|
if (input.transition) {
|
|
1226
|
+
const storedTransition = this.notificationActivityPort ? { ...input.transition, notifiedAt: input.ledgerAsOf } : input.transition;
|
|
1095
1227
|
const inserted = await client.query(
|
|
1096
1228
|
`INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
|
|
1097
1229
|
VALUES ($1, $2, $3::jsonb, $4)
|
|
@@ -1099,12 +1231,60 @@ class PostgresReportingLedgerStore {
|
|
|
1099
1231
|
[
|
|
1100
1232
|
input.transition.transitionId,
|
|
1101
1233
|
input.reporting_obligation_id,
|
|
1102
|
-
JSON.stringify(
|
|
1234
|
+
JSON.stringify(storedTransition),
|
|
1103
1235
|
input.transition.occurredAt
|
|
1104
1236
|
]
|
|
1105
1237
|
);
|
|
1106
1238
|
if (inserted.rowCount !== 1) return { applied: false, transitionInserted: false };
|
|
1107
1239
|
transitionInserted = true;
|
|
1240
|
+
if (this.notificationActivityPort) {
|
|
1241
|
+
await this.notificationActivityPort.recordTransition({ transition: input.transition, obligation }, client);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
{
|
|
1245
|
+
await client.query(
|
|
1246
|
+
`INSERT INTO adcp_reporting_lifecycle_state
|
|
1247
|
+
(obligation_id, processed_state_version, processed_roster_version,
|
|
1248
|
+
current_roster_version, processed_at)
|
|
1249
|
+
VALUES ($1, $2, $3, $3, LEAST($4::timestamptz, clock_timestamp()))
|
|
1250
|
+
ON CONFLICT (obligation_id) DO UPDATE SET
|
|
1251
|
+
processed_state_version = EXCLUDED.processed_state_version,
|
|
1252
|
+
processed_roster_version = EXCLUDED.processed_roster_version,
|
|
1253
|
+
-- Reconciling also observes the roster, so the current version
|
|
1254
|
+
-- moves with the processed one. A later external change
|
|
1255
|
+
-- publishes a different current version and re-arms; without
|
|
1256
|
+
-- this the two never matched and the obligation stayed due.
|
|
1257
|
+
-- Never over a version this reconcile did not observe: if the
|
|
1258
|
+
-- row was created or advanced after the check above, that
|
|
1259
|
+
-- newer observation is what has to survive, or its re-arm is
|
|
1260
|
+
-- silently dropped.
|
|
1261
|
+
current_roster_version = CASE
|
|
1262
|
+
WHEN $5::text IS NOT NULL
|
|
1263
|
+
AND adcp_reporting_lifecycle_state.current_roster_version IS DISTINCT FROM $5::text
|
|
1264
|
+
THEN adcp_reporting_lifecycle_state.current_roster_version
|
|
1265
|
+
ELSE EXCLUDED.processed_roster_version END,
|
|
1266
|
+
processed_at = EXCLUDED.processed_at,
|
|
1267
|
+
failure_count = 0,
|
|
1268
|
+
next_attempt_at = NULL`,
|
|
1269
|
+
[
|
|
1270
|
+
input.reporting_obligation_id,
|
|
1271
|
+
input.processedManagedStateVersion ?? null,
|
|
1272
|
+
// Empty string, not null: null means "never reconciled", which
|
|
1273
|
+
// is a due condition. A deployment with no roster source would
|
|
1274
|
+
// otherwise stay due forever.
|
|
1275
|
+
input.processedObligatedConsumerRosterVersion ?? "",
|
|
1276
|
+
// The cutoff the projection actually read at, never the commit
|
|
1277
|
+
// instant. Watermarking at commit time swallowed everything
|
|
1278
|
+
// that landed between the two: excluded from the projection,
|
|
1279
|
+
// and then behind the watermark forever, so it never became
|
|
1280
|
+
// due again. It is clamped to this database's clock in SQL
|
|
1281
|
+
// below, because a cutoff ahead of the ledger buries
|
|
1282
|
+
// database-timestamped work the same way for the opposite
|
|
1283
|
+
// reason.
|
|
1284
|
+
input.ledgerAsOf,
|
|
1285
|
+
input.expectedObligatedConsumerRosterVersion ?? null
|
|
1286
|
+
]
|
|
1287
|
+
);
|
|
1108
1288
|
}
|
|
1109
1289
|
const projectedIds = input.projectedIssues.map((issue) => issue.issueId);
|
|
1110
1290
|
for (const issue of input.projectedIssues) {
|
|
@@ -1127,7 +1307,17 @@ class PostgresReportingLedgerStore {
|
|
|
1127
1307
|
data = data || jsonb_build_object('resolvedAt', $2::text),
|
|
1128
1308
|
changed_at = clock_timestamp()
|
|
1129
1309
|
WHERE obligation_id = $1 AND resolved_at IS NULL
|
|
1130
|
-
|
|
1310
|
+
-- Every code this projection can emit, not only the Core two.
|
|
1311
|
+
-- A managed DELIVERY_FAILED persisted by an earlier reconcile
|
|
1312
|
+
-- was never cleared, so after a successful redelivery the
|
|
1313
|
+
-- status kept publishing a stale delivery failure for as long
|
|
1314
|
+
-- as Core stayed degraded for some unrelated reason.
|
|
1315
|
+
AND data->>'code' IN (
|
|
1316
|
+
'REPORT_OVERDUE', 'REPORTING_COVERAGE_INCOMPLETE',
|
|
1317
|
+
'DELIVERY_FAILED', 'RESOURCE_EXPIRED',
|
|
1318
|
+
'RECEIPT_REQUIRED', 'RECEIPT_REJECTED',
|
|
1319
|
+
'ADJUSTMENT_RECEIPT_REQUIRED', 'ADJUSTMENT_RECEIPT_REJECTED'
|
|
1320
|
+
)
|
|
1131
1321
|
AND NOT (issue_id = ANY($3::text[]))`,
|
|
1132
1322
|
[input.reporting_obligation_id, input.ledgerAsOf, projectedIds]
|
|
1133
1323
|
);
|
|
@@ -1144,6 +1334,7 @@ class PostgresReportingLedgerStore {
|
|
|
1144
1334
|
await client.query(
|
|
1145
1335
|
`UPDATE adcp_reporting_transitions
|
|
1146
1336
|
SET data = data || jsonb_build_object('notifiedAt', $2::text)
|
|
1337
|
+
|| CASE WHEN data ? 'finality' THEN '{}'::jsonb ELSE jsonb_build_object('finality', 'none') END
|
|
1147
1338
|
WHERE transition_id = $1 AND NOT (data ? 'notifiedAt')`,
|
|
1148
1339
|
[transitionId, notifiedAt]
|
|
1149
1340
|
);
|
|
@@ -1201,11 +1392,31 @@ class PostgresReportingLedgerStore {
|
|
|
1201
1392
|
}
|
|
1202
1393
|
const activeSnapshotBytes = Number(active.rows[0]?.bytes ?? 0);
|
|
1203
1394
|
const changesAfter = query.changes_after ? await this.resolveChangesCheckpoint(client, query, query.changes_after) : void 0;
|
|
1395
|
+
const managedInstalled = this.managedDelivery && await this.managedTablesInstalled(client);
|
|
1204
1396
|
const configurations = await this.listSnapshotConfigurations(client, query, ledgerAsOf);
|
|
1205
1397
|
if (configurations.length > MAX_SNAPSHOT_ITEMS) {
|
|
1206
1398
|
throw new Error("Reporting ledger snapshot exceeds the configuration limit");
|
|
1207
1399
|
}
|
|
1208
1400
|
let obligations = await this.listSnapshotObligations(client, query, ledgerAsOf, changesAfter);
|
|
1401
|
+
if (managedInstalled && changesAfter) {
|
|
1402
|
+
const managedChangedIds = await this.listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter);
|
|
1403
|
+
if (managedChangedIds.size) {
|
|
1404
|
+
const fullScope = await this.listSnapshotObligations(
|
|
1405
|
+
client,
|
|
1406
|
+
{ ...query, changes_after: void 0, health: void 0 },
|
|
1407
|
+
ledgerAsOf
|
|
1408
|
+
);
|
|
1409
|
+
const byId = new Map(obligations.map((value) => [value.reporting_obligation_id, value]));
|
|
1410
|
+
for (const obligation of fullScope) {
|
|
1411
|
+
if (managedChangedIds.has(obligation.reporting_obligation_id)) {
|
|
1412
|
+
byId.set(obligation.reporting_obligation_id, obligation);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
obligations = [...byId.values()].sort(
|
|
1416
|
+
(left, right) => left.period.start.localeCompare(right.period.start) || left.reporting_obligation_id.localeCompare(right.reporting_obligation_id)
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1209
1420
|
if (obligations.length > MAX_SNAPSHOT_ITEMS) {
|
|
1210
1421
|
throw new Error("Reporting ledger snapshot exceeds the item limit");
|
|
1211
1422
|
}
|
|
@@ -1257,6 +1468,30 @@ class PostgresReportingLedgerStore {
|
|
|
1257
1468
|
throw new Error("Reporting ledger snapshot exceeds the item limit");
|
|
1258
1469
|
}
|
|
1259
1470
|
let issues = await this.listSnapshotIssues(client, obligationIds, MAX_SNAPSHOT_ITEMS + 1);
|
|
1471
|
+
const managedBindings = managedInstalled ? await this.listSnapshotManagedBindings(
|
|
1472
|
+
client,
|
|
1473
|
+
configurations.map((value) => value.configurationId)
|
|
1474
|
+
) : [];
|
|
1475
|
+
let materializations = managedInstalled ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) : [];
|
|
1476
|
+
let receipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "revision") : [];
|
|
1477
|
+
let adjustmentReceipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "adjustment") : [];
|
|
1478
|
+
const tombstonedAcceptedSubjects = managedInstalled ? (await this.listTombstonedAcceptedSubjects(client, query, obligationIds, false, ledgerAsOf)).subjects : [];
|
|
1479
|
+
const tombstonedDeliveredRevisionIds = managedInstalled ? (await client.query(
|
|
1480
|
+
`SELECT revision_id FROM adcp_reporting_materialization_tombstones
|
|
1481
|
+
WHERE obligation_id = ANY($1::text[]) AND reached_success
|
|
1482
|
+
AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
|
|
1483
|
+
[obligationIds, MAX_SNAPSHOT_ITEMS + 1, ledgerAsOf]
|
|
1484
|
+
)).rows.map((row) => row.revision_id) : [];
|
|
1485
|
+
const materializationProjection = managedInstalled ? await this.listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) : [];
|
|
1486
|
+
const materializationHistoryProjection = managedInstalled && changesAfter ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf) : materializations;
|
|
1487
|
+
const receiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "revision") : receipts;
|
|
1488
|
+
const adjustmentReceiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "adjustment") : adjustmentReceipts;
|
|
1489
|
+
if (materializationProjection.length > MAX_SNAPSHOT_ITEMS || materializationHistoryProjection.length > MAX_SNAPSHOT_ITEMS || receiptProjection.length > MAX_SNAPSHOT_ITEMS || adjustmentReceiptProjection.length > MAX_SNAPSHOT_ITEMS) {
|
|
1490
|
+
throw new Error("Reporting ledger snapshot exceeds the managed projection limit");
|
|
1491
|
+
}
|
|
1492
|
+
if (obligations.length + revisions.length + adjustments.length + consumerStatuses.length + materializations.length + receipts.length + adjustmentReceipts.length > MAX_SNAPSHOT_ITEMS) {
|
|
1493
|
+
throw new Error("Reporting ledger snapshot exceeds the item limit");
|
|
1494
|
+
}
|
|
1260
1495
|
if (issues.length > MAX_SNAPSHOT_ITEMS) throw new Error("Reporting ledger snapshot exceeds the issue limit");
|
|
1261
1496
|
if (query.view === "periods" && query.health) {
|
|
1262
1497
|
const scopedObligations = obligations;
|
|
@@ -1281,7 +1516,34 @@ class PostgresReportingLedgerStore {
|
|
|
1281
1516
|
ledgerAsOf,
|
|
1282
1517
|
this.consumerMismatchEscalation
|
|
1283
1518
|
);
|
|
1284
|
-
|
|
1519
|
+
const baseProjection = projectReportingObligationHealthV1(
|
|
1520
|
+
value,
|
|
1521
|
+
obligationRevisions,
|
|
1522
|
+
ledgerAsOf,
|
|
1523
|
+
reportingLedgerScopeClosed(query, ledgerAsOf, ledgerCoverage.complete)
|
|
1524
|
+
);
|
|
1525
|
+
const managed = projectManagedDelivery({
|
|
1526
|
+
obligation: value,
|
|
1527
|
+
binding: managedBindings.find((binding) => binding.configurationId === value.configurationId),
|
|
1528
|
+
revisions: obligationRevisions,
|
|
1529
|
+
adjustments: adjustments.filter(
|
|
1530
|
+
(item) => item.reporting_obligation_id === value.reporting_obligation_id
|
|
1531
|
+
),
|
|
1532
|
+
materializations: materializationProjection.filter(
|
|
1533
|
+
(item) => item.reporting_obligation_id === value.reporting_obligation_id
|
|
1534
|
+
),
|
|
1535
|
+
materializationHistory: materializationHistoryProjection.filter(
|
|
1536
|
+
(item) => item.reporting_obligation_id === value.reporting_obligation_id
|
|
1537
|
+
),
|
|
1538
|
+
receipts: receiptProjection,
|
|
1539
|
+
adjustmentReceipts: adjustmentReceiptProjection,
|
|
1540
|
+
base: baseProjection,
|
|
1541
|
+
ledgerAsOf,
|
|
1542
|
+
tombstonedAcceptedSubjects,
|
|
1543
|
+
tombstonedDeliveredRevisionIds
|
|
1544
|
+
});
|
|
1545
|
+
const health = mismatch ? moreSevereReportingHealthV1(managed?.projection.health ?? sellerHealth, mismatch.health) : managed?.projection.health ?? sellerHealth;
|
|
1546
|
+
return query.health.includes(health);
|
|
1285
1547
|
}).map((value) => value.reporting_obligation_id)
|
|
1286
1548
|
);
|
|
1287
1549
|
const obligationsByConsumerStatusChain = new Map(
|
|
@@ -1297,6 +1559,13 @@ class PostgresReportingLedgerStore {
|
|
|
1297
1559
|
obligations = obligations.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1298
1560
|
revisions = revisions.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1299
1561
|
adjustments = adjustments.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1562
|
+
materializations = materializations.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1563
|
+
const acceptedRevisionIds = new Set(revisions.map((value) => value.reporting_revision_id));
|
|
1564
|
+
const acceptedAdjustmentIds = new Set(adjustments.map((value) => value.reporting_adjustment_id));
|
|
1565
|
+
receipts = receipts.filter((value) => acceptedRevisionIds.has(value.reporting_revision_id));
|
|
1566
|
+
adjustmentReceipts = adjustmentReceipts.filter(
|
|
1567
|
+
(value) => acceptedAdjustmentIds.has(value.reporting_adjustment_id)
|
|
1568
|
+
);
|
|
1300
1569
|
consumerStatuses = consumerStatuses.filter((value) => {
|
|
1301
1570
|
const key = reportingConsumerStatusChainKeyV1(value);
|
|
1302
1571
|
const matchingObligation = obligationsByConsumerStatusChain.get(key);
|
|
@@ -1321,6 +1590,18 @@ class PostgresReportingLedgerStore {
|
|
|
1321
1590
|
obligations,
|
|
1322
1591
|
revisions,
|
|
1323
1592
|
adjustments,
|
|
1593
|
+
...managedInstalled ? {
|
|
1594
|
+
tombstonedAcceptedSubjects,
|
|
1595
|
+
tombstonedDeliveredRevisionIds,
|
|
1596
|
+
managedBindings,
|
|
1597
|
+
materializations,
|
|
1598
|
+
materializationHistoryProjection,
|
|
1599
|
+
materializationProjection,
|
|
1600
|
+
receipts,
|
|
1601
|
+
receiptProjection,
|
|
1602
|
+
adjustmentReceipts,
|
|
1603
|
+
adjustmentReceiptProjection
|
|
1604
|
+
} : {},
|
|
1324
1605
|
consumerStatuses,
|
|
1325
1606
|
consumerStatusProjection,
|
|
1326
1607
|
issues
|
|
@@ -1390,6 +1671,13 @@ class PostgresReportingLedgerStore {
|
|
|
1390
1671
|
const consumerStatuses = selected.filter(
|
|
1391
1672
|
(value) => value.kind === "consumer_status"
|
|
1392
1673
|
).map((value) => value.value);
|
|
1674
|
+
const materializations = selected.filter(
|
|
1675
|
+
(value) => value.kind === "materialization"
|
|
1676
|
+
).map((value) => value.value);
|
|
1677
|
+
const receipts = selected.filter((value) => value.kind === "receipt").map((value) => value.value);
|
|
1678
|
+
const adjustmentReceipts = selected.filter(
|
|
1679
|
+
(value) => value.kind === "adjustment_receipt"
|
|
1680
|
+
).map((value) => value.value);
|
|
1393
1681
|
const nextOffset = offset + selected.length;
|
|
1394
1682
|
const hasMore = nextOffset < items.length;
|
|
1395
1683
|
return {
|
|
@@ -1397,6 +1685,9 @@ class PostgresReportingLedgerStore {
|
|
|
1397
1685
|
obligations,
|
|
1398
1686
|
revisions,
|
|
1399
1687
|
adjustments,
|
|
1688
|
+
materializations,
|
|
1689
|
+
receipts,
|
|
1690
|
+
adjustmentReceipts,
|
|
1400
1691
|
consumerStatuses,
|
|
1401
1692
|
totalCount: items.length,
|
|
1402
1693
|
offset,
|
|
@@ -1576,11 +1867,614 @@ class PostgresReportingLedgerStore {
|
|
|
1576
1867
|
);
|
|
1577
1868
|
return result.rows.map((row) => row.data);
|
|
1578
1869
|
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Managed Delivery projection inputs for one obligation, grouped per consumer.
|
|
1872
|
+
*
|
|
1873
|
+
* The lifecycle reconciler folds these through the same
|
|
1874
|
+
* `projectManagedDelivery` the read path uses, so a persisted transition and
|
|
1875
|
+
* its webhook report the health a read of the same obligation would return.
|
|
1876
|
+
* Returns `null` when Managed Delivery is not installed or the obligation's
|
|
1877
|
+
* configuration has no binding, which keeps a Core-only ledger unchanged.
|
|
1878
|
+
*/
|
|
1879
|
+
async getManagedLifecycleProjection(input) {
|
|
1880
|
+
if (!this.managedDelivery) return null;
|
|
1881
|
+
const base = await this.transaction(
|
|
1882
|
+
async (client) => {
|
|
1883
|
+
if (!await this.managedTablesInstalled(client)) return null;
|
|
1884
|
+
const obligation = await client.query(
|
|
1885
|
+
"SELECT configuration_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
|
|
1886
|
+
[input.reporting_obligation_id]
|
|
1887
|
+
);
|
|
1888
|
+
const configurationId = obligation.rows[0]?.configuration_id;
|
|
1889
|
+
if (!configurationId) return null;
|
|
1890
|
+
const [binding] = await this.listSnapshotManagedBindings(client, [configurationId]);
|
|
1891
|
+
if (!binding) return null;
|
|
1892
|
+
const resolvedLedgerAsOf = (await client.query(
|
|
1893
|
+
// RFC 3339 with microseconds. PostgreSQL's own ::text rendering
|
|
1894
|
+
// uses a space separator and a two-digit offset, which the SDK's
|
|
1895
|
+
// instant parser rejects, and round-tripping through a JS Date
|
|
1896
|
+
// would truncate the microseconds this exists to preserve.
|
|
1897
|
+
`SELECT ${rfc3339Microseconds("COALESCE($1::timestamptz, clock_timestamp())")} AS instant`,
|
|
1898
|
+
[input.ledgerAsOf ?? null]
|
|
1899
|
+
)).rows[0].instant;
|
|
1900
|
+
const rows = await client.query(
|
|
1901
|
+
`SELECT materialization.data,
|
|
1902
|
+
materialization.changed_at > $2::timestamptz AS settled_after_cutoff,
|
|
1903
|
+
CASE WHEN authz.revoked_at <= $2::timestamptz THEN authz.revoked_at END AS revoked_at
|
|
1904
|
+
FROM adcp_reporting_materializations materialization
|
|
1905
|
+
JOIN adcp_reporting_destination_authorizations authz
|
|
1906
|
+
ON authz.account_id = materialization.account_id
|
|
1907
|
+
AND authz.destination_ref = materialization.destination_ref
|
|
1908
|
+
AND authz.generation = materialization.authorization_generation
|
|
1909
|
+
WHERE materialization.obligation_id = $1
|
|
1910
|
+
AND materialization.recorded_at <= $2::timestamptz
|
|
1911
|
+
ORDER BY materialization.attempt, materialization.materialization_id
|
|
1912
|
+
LIMIT $3`,
|
|
1913
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
1914
|
+
);
|
|
1915
|
+
if (rows.rows.length > MAX_SNAPSHOT_ITEMS) {
|
|
1916
|
+
throw new Error("Reporting lifecycle projection exceeds the managed materialization limit");
|
|
1917
|
+
}
|
|
1918
|
+
const placed = rows.rows.map((row) => ({
|
|
1919
|
+
atAsOf: row.settled_after_cutoff ? pendingAtCutoff(row.data) : clone(row.data),
|
|
1920
|
+
revokedAt: row.revoked_at
|
|
1921
|
+
}));
|
|
1922
|
+
const materializationHistory = placed.map((value) => value.atAsOf);
|
|
1923
|
+
const materializations = placed.map(
|
|
1924
|
+
({ atAsOf, revokedAt }) => revokedAt && (atAsOf.status === "available" || atAsOf.status === "delivered") ? {
|
|
1925
|
+
...atAsOf,
|
|
1926
|
+
status: "failed",
|
|
1927
|
+
failed_at: revokedAt.toISOString(),
|
|
1928
|
+
failure_code: "AUTHORIZATION_REVOKED"
|
|
1929
|
+
} : atAsOf
|
|
1930
|
+
);
|
|
1931
|
+
const receipts = await client.query(
|
|
1932
|
+
`WITH subject AS (
|
|
1933
|
+
-- Drive from this obligation's own subjects. Asking the global
|
|
1934
|
+
-- receipt table "which of your rows belong to this obligation"
|
|
1935
|
+
-- matched no index \u2014 they lead with account and consumer, and
|
|
1936
|
+
-- this question supplies neither \u2014 so every due obligation
|
|
1937
|
+
-- re-scanned the whole receipt history.
|
|
1938
|
+
SELECT revision_id AS subject_id, 'revision' AS receipt_kind
|
|
1939
|
+
FROM adcp_reporting_revisions WHERE obligation_id = $1
|
|
1940
|
+
UNION ALL
|
|
1941
|
+
SELECT adjustment_id, 'adjustment'
|
|
1942
|
+
FROM adcp_reporting_adjustments WHERE obligation_id = $1
|
|
1943
|
+
)
|
|
1944
|
+
SELECT receipt.consumer_id, receipt.receipt_kind, receipt.data
|
|
1945
|
+
FROM subject
|
|
1946
|
+
JOIN adcp_reporting_receipts receipt
|
|
1947
|
+
ON receipt.subject_id = subject.subject_id
|
|
1948
|
+
AND receipt.receipt_kind = subject.receipt_kind
|
|
1949
|
+
-- Ordered and cut off by the database clock, exactly as every other
|
|
1950
|
+
-- receipt read path is, so a skewed host cannot make the lifecycle
|
|
1951
|
+
-- projection see a different receipt set than get_reporting_status.
|
|
1952
|
+
--
|
|
1953
|
+
-- One leaf per chain, resolved AS OF the cutoff. The fold reads
|
|
1954
|
+
-- exactly one thing from each chain, and walking the whole history
|
|
1955
|
+
-- to find it pushed this read past its bound for a subject repaired
|
|
1956
|
+
-- often enough. But is_current is today's answer: at a cutoff
|
|
1957
|
+
-- before an acceptance superseded a rejection, the rejection was no
|
|
1958
|
+
-- longer current and the acceptance was not yet in scope, so the
|
|
1959
|
+
-- projection saw neither and persisted RECEIPT_REQUIRED over a
|
|
1960
|
+
-- rejection the buyer had already filed. The leaf at the cutoff is
|
|
1961
|
+
-- the receipt that nothing recorded by then supersedes. Wire
|
|
1962
|
+
-- counters are computed on the read path, which still sees every
|
|
1963
|
+
-- row.
|
|
1964
|
+
WHERE receipt.recorded_at <= $2
|
|
1965
|
+
AND NOT EXISTS (
|
|
1966
|
+
SELECT 1 FROM adcp_reporting_receipts successor
|
|
1967
|
+
WHERE successor.account_id = receipt.account_id
|
|
1968
|
+
AND successor.consumer_id = receipt.consumer_id
|
|
1969
|
+
AND successor.supersedes_receipt_id = receipt.reporting_receipt_id
|
|
1970
|
+
AND successor.recorded_at <= $2
|
|
1971
|
+
)
|
|
1972
|
+
-- A successor whose body has been pruned still superseded this
|
|
1973
|
+
-- row. Counting only live successors let retention resurrect the
|
|
1974
|
+
-- rejected predecessor of a pruned acceptance as the live leaf,
|
|
1975
|
+
-- which overrode that acceptance's own tombstone and reopened a
|
|
1976
|
+
-- settled subject the write path then refused to repair.
|
|
1977
|
+
AND NOT EXISTS (
|
|
1978
|
+
SELECT 1 FROM adcp_reporting_receipt_tombstones gone
|
|
1979
|
+
WHERE gone.account_id = receipt.account_id
|
|
1980
|
+
AND gone.consumer_id = receipt.consumer_id
|
|
1981
|
+
AND gone.supersedes_receipt_id = receipt.reporting_receipt_id
|
|
1982
|
+
AND COALESCE(gone.subject_recorded_at, gone.pruned_at) <= $2
|
|
1983
|
+
)
|
|
1984
|
+
ORDER BY receipt.consumer_id, receipt.subject_id, receipt.recorded_at, receipt.reporting_receipt_id
|
|
1985
|
+
LIMIT $3`,
|
|
1986
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_LIFECYCLE_RECEIPT_LEAVES + 1]
|
|
1987
|
+
);
|
|
1988
|
+
let receiptEvidenceComplete = true;
|
|
1989
|
+
let leaves = receipts.rows;
|
|
1990
|
+
if (leaves.length > MAX_LIFECYCLE_RECEIPT_LEAVES) {
|
|
1991
|
+
receiptEvidenceComplete = false;
|
|
1992
|
+
const partial = leaves[MAX_LIFECYCLE_RECEIPT_LEAVES].consumer_id;
|
|
1993
|
+
leaves = leaves.slice(0, MAX_LIFECYCLE_RECEIPT_LEAVES).filter((row) => row.consumer_id !== partial);
|
|
1994
|
+
}
|
|
1995
|
+
const byConsumer = /* @__PURE__ */ new Map();
|
|
1996
|
+
for (const row of leaves) {
|
|
1997
|
+
const consumer = byConsumer.get(row.consumer_id) ?? {
|
|
1998
|
+
consumer_id: row.consumer_id,
|
|
1999
|
+
receipts: [],
|
|
2000
|
+
adjustmentReceipts: []
|
|
2001
|
+
};
|
|
2002
|
+
if (row.receipt_kind === "revision") consumer.receipts.push(clone(row.data));
|
|
2003
|
+
else consumer.adjustmentReceipts.push(clone(row.data));
|
|
2004
|
+
byConsumer.set(row.consumer_id, consumer);
|
|
2005
|
+
}
|
|
2006
|
+
const engaged = await client.query(
|
|
2007
|
+
`SELECT DISTINCT consumer_id FROM adcp_reporting_consumer_statuses
|
|
2008
|
+
WHERE obligation_id = $1 AND consumer_id <> '__legacy_unscoped_consumer__'
|
|
2009
|
+
ORDER BY consumer_id LIMIT $2`,
|
|
2010
|
+
[input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1]
|
|
2011
|
+
);
|
|
2012
|
+
if (engaged.rows.length > MAX_SNAPSHOT_ITEMS) {
|
|
2013
|
+
receiptEvidenceComplete = false;
|
|
2014
|
+
engaged.rows = engaged.rows.slice(0, MAX_SNAPSHOT_ITEMS);
|
|
2015
|
+
}
|
|
2016
|
+
const observed = [.../* @__PURE__ */ new Set([...byConsumer.keys(), ...engaged.rows.map((row) => row.consumer_id)])].sort();
|
|
2017
|
+
const tombstoned = await this.listTombstonedAcceptedSubjects(
|
|
2018
|
+
client,
|
|
2019
|
+
{ account_id: binding.account_id, consumer_id: void 0, view: "periods" },
|
|
2020
|
+
[input.reporting_obligation_id],
|
|
2021
|
+
true,
|
|
2022
|
+
resolvedLedgerAsOf
|
|
2023
|
+
);
|
|
2024
|
+
if (!tombstoned.complete) receiptEvidenceComplete = false;
|
|
2025
|
+
const visible = await client.query(
|
|
2026
|
+
`SELECT adjustment_id FROM adcp_reporting_adjustments
|
|
2027
|
+
WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
|
|
2028
|
+
ORDER BY adjustment_number, adjustment_id LIMIT $3`,
|
|
2029
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
2030
|
+
);
|
|
2031
|
+
if (visible.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
|
|
2032
|
+
const visibleAdjustmentIds = visible.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.adjustment_id);
|
|
2033
|
+
const visibleRevisions = await client.query(
|
|
2034
|
+
`SELECT revision_id FROM adcp_reporting_revisions
|
|
2035
|
+
WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
|
|
2036
|
+
ORDER BY revision_number, revision_id LIMIT $3`,
|
|
2037
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
2038
|
+
);
|
|
2039
|
+
if (visibleRevisions.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
|
|
2040
|
+
const visibleRevisionIds = visibleRevisions.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
|
|
2041
|
+
const tombstonedAcceptedSubjects = tombstoned.subjects;
|
|
2042
|
+
const delivered = await client.query(
|
|
2043
|
+
`SELECT revision_id FROM adcp_reporting_materialization_tombstones
|
|
2044
|
+
WHERE obligation_id = $1 AND reached_success
|
|
2045
|
+
AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
|
|
2046
|
+
[input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1, resolvedLedgerAsOf]
|
|
2047
|
+
);
|
|
2048
|
+
if (delivered.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
|
|
2049
|
+
const tombstonedDeliveredRevisionIds = delivered.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
|
|
2050
|
+
const managedStateVersion = await this.readManagedStateVersion(client, input.reporting_obligation_id);
|
|
2051
|
+
return {
|
|
2052
|
+
binding,
|
|
2053
|
+
materializations,
|
|
2054
|
+
materializationHistory,
|
|
2055
|
+
consumers: [...byConsumer.values()],
|
|
2056
|
+
obligatedConsumerIds: observed,
|
|
2057
|
+
obligatedConsumerRosterComplete: false,
|
|
2058
|
+
receiptEvidenceComplete,
|
|
2059
|
+
visibleAdjustmentIds,
|
|
2060
|
+
visibleRevisionIds,
|
|
2061
|
+
managedStateVersion,
|
|
2062
|
+
resolvedLedgerAsOf,
|
|
2063
|
+
tombstonedAcceptedSubjects,
|
|
2064
|
+
tombstonedDeliveredRevisionIds
|
|
2065
|
+
};
|
|
2066
|
+
},
|
|
2067
|
+
{ isolation: "REPEATABLE READ" }
|
|
2068
|
+
);
|
|
2069
|
+
if (!base || !this.obligatedConsumers) return base;
|
|
2070
|
+
const supplied = await withReportingCallbackDeadline(
|
|
2071
|
+
this.obligatedConsumers({
|
|
2072
|
+
reporting_obligation_id: input.reporting_obligation_id,
|
|
2073
|
+
account_id: base.binding.account_id
|
|
2074
|
+
}),
|
|
2075
|
+
OBLIGATED_CONSUMERS_DEADLINE_MS,
|
|
2076
|
+
"Reporting obligated-consumer lookup deadline elapsed"
|
|
2077
|
+
);
|
|
2078
|
+
const all = obligatedConsumerIdsFor(supplied, base.obligatedConsumerIds ?? []);
|
|
2079
|
+
const identityIds = supplied.version === void 0 && supplied.complete !== true ? obligatedConsumerIdsFor(supplied, await this.observedConsumerIds(input.reporting_obligation_id)) : all;
|
|
2080
|
+
const ids = all.slice(0, MAX_SNAPSHOT_ITEMS);
|
|
2081
|
+
const rosterComplete = supplied.complete === true && all.length === ids.length;
|
|
2082
|
+
return {
|
|
2083
|
+
...base,
|
|
2084
|
+
obligatedConsumerIds: ids,
|
|
2085
|
+
obligatedConsumerRosterComplete: rosterComplete,
|
|
2086
|
+
...all.length === ids.length ? {} : { receiptEvidenceComplete: false },
|
|
2087
|
+
obligatedConsumerRosterVersion: obligatedConsumerRosterVersionFor(supplied, identityIds)
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Digest over every managed row that can change this obligation's projected
|
|
2092
|
+
* health: materialization status, receipt existence and current-leaf flag,
|
|
2093
|
+
* adjustments, and the destination authorization's revocation/cleanup state.
|
|
2094
|
+
*
|
|
2095
|
+
* `is_current` is included because superseding a receipt flips it with no
|
|
2096
|
+
* timestamp of its own, and the authorization's `cleanup_completed_at` is
|
|
2097
|
+
* included because `completeRevocation` does not touch `changed_at`.
|
|
2098
|
+
*/
|
|
2099
|
+
async readManagedStateVersion(client, obligationId) {
|
|
2100
|
+
const result = await client.query(
|
|
2101
|
+
`SELECT md5(COALESCE(string_agg(marker, '|' ORDER BY marker), '')) AS version FROM (
|
|
2102
|
+
SELECT 'm:' || materialization_id || ':' || status || ':'
|
|
2103
|
+
|| EXTRACT(EPOCH FROM changed_at)::text AS marker
|
|
2104
|
+
FROM adcp_reporting_materializations WHERE obligation_id = $1
|
|
2105
|
+
UNION ALL
|
|
2106
|
+
-- Driven from this obligation's subjects into the receipt subject
|
|
2107
|
+
-- index. This digest is read while the apply holds the account
|
|
2108
|
+
-- lock, so asking the global receipt table which of its rows belong
|
|
2109
|
+
-- to an obligation -- a question supplying neither account nor
|
|
2110
|
+
-- consumer -- turned every apply into a full scan with that lock
|
|
2111
|
+
-- held, and concurrent Core writes failed with 55P03.
|
|
2112
|
+
SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
|
|
2113
|
+
|| EXTRACT(EPOCH FROM receipt.recorded_at)::text
|
|
2114
|
+
FROM adcp_reporting_revisions revision
|
|
2115
|
+
JOIN adcp_reporting_receipts receipt
|
|
2116
|
+
ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
|
|
2117
|
+
WHERE revision.obligation_id = $1
|
|
2118
|
+
UNION ALL
|
|
2119
|
+
SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
|
|
2120
|
+
|| EXTRACT(EPOCH FROM receipt.recorded_at)::text
|
|
2121
|
+
FROM adcp_reporting_adjustments adjustment
|
|
2122
|
+
JOIN adcp_reporting_receipts receipt
|
|
2123
|
+
ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
|
|
2124
|
+
WHERE adjustment.obligation_id = $1
|
|
2125
|
+
UNION ALL
|
|
2126
|
+
SELECT 'a:' || adjustment_id || ':' || EXTRACT(EPOCH FROM recorded_at)::text
|
|
2127
|
+
FROM adcp_reporting_adjustments WHERE obligation_id = $1
|
|
2128
|
+
UNION ALL
|
|
2129
|
+
-- Consumer statuses are a health input through the mismatch
|
|
2130
|
+
-- projection and they widen the observed roster, so a status landing
|
|
2131
|
+
-- between projection and apply has to move the token too.
|
|
2132
|
+
SELECT 'c:' || status.consumer_id || ':' || status.consumer_status_id || ':'
|
|
2133
|
+
|| status.is_current::text || ':' || EXTRACT(EPOCH FROM status.created_at)::text
|
|
2134
|
+
FROM adcp_reporting_consumer_statuses status
|
|
2135
|
+
WHERE status.obligation_id = $1
|
|
2136
|
+
UNION ALL
|
|
2137
|
+
SELECT 'z:' || authz.generation::text
|
|
2138
|
+
|| ':' || COALESCE(EXTRACT(EPOCH FROM authz.revoked_at)::text, '')
|
|
2139
|
+
|| ':' || COALESCE(EXTRACT(EPOCH FROM authz.cleanup_completed_at)::text, '')
|
|
2140
|
+
|| ':' || EXTRACT(EPOCH FROM authz.changed_at)::text
|
|
2141
|
+
FROM adcp_reporting_destination_authorizations authz
|
|
2142
|
+
JOIN adcp_reporting_managed_bindings binding
|
|
2143
|
+
ON binding.account_id = authz.account_id AND binding.destination_ref = authz.destination_ref
|
|
2144
|
+
JOIN adcp_reporting_obligations obligation
|
|
2145
|
+
ON obligation.configuration_id = binding.configuration_id
|
|
2146
|
+
WHERE obligation.obligation_id = $1
|
|
2147
|
+
) markers`,
|
|
2148
|
+
[obligationId]
|
|
2149
|
+
);
|
|
2150
|
+
return result.rows[0]?.version ?? "";
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Current external roster version, read outside any transaction so the
|
|
2154
|
+
* reconciler can re-check it immediately before an apply.
|
|
2155
|
+
*/
|
|
2156
|
+
/**
|
|
2157
|
+
* Publishes the roster version currently observed for an obligation.
|
|
2158
|
+
*
|
|
2159
|
+
* The roster is external, so nothing in this database changes when it does.
|
|
2160
|
+
* Recording what a reader saw is what lets the due query notice drift from
|
|
2161
|
+
* the version last reconciled. Never lowers a watermark; it only reports.
|
|
2162
|
+
*/
|
|
2163
|
+
async recordObligatedConsumerRosterVersion(input) {
|
|
2164
|
+
await this.query(
|
|
2165
|
+
`INSERT INTO adcp_reporting_lifecycle_state
|
|
2166
|
+
(obligation_id, current_roster_version, roster_refreshed_at, processed_at)
|
|
2167
|
+
VALUES ($1, $2, clock_timestamp(), clock_timestamp())
|
|
2168
|
+
ON CONFLICT (obligation_id) DO UPDATE SET
|
|
2169
|
+
current_roster_version = EXCLUDED.current_roster_version,
|
|
2170
|
+
-- Advances the refresh cursor, never the reconciliation watermark:
|
|
2171
|
+
-- observing a roster is not reconciling an obligation.
|
|
2172
|
+
roster_refreshed_at = clock_timestamp()`,
|
|
2173
|
+
[input.reporting_obligation_id, input.version]
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Re-reads the external roster for managed obligations and publishes what
|
|
2178
|
+
* it saw, so a change made while nothing was scheduled can re-arm.
|
|
2179
|
+
*
|
|
2180
|
+
* Ordered by least-recently-refreshed and bounded, so a large account is
|
|
2181
|
+
* swept over successive passes rather than in one burst of callbacks.
|
|
2182
|
+
*/
|
|
2183
|
+
async refreshObligatedConsumerRosterVersions(input) {
|
|
2184
|
+
if (!this.managedDelivery || !this.obligatedConsumers) return 0;
|
|
2185
|
+
positiveInteger(input.limit, "limit");
|
|
2186
|
+
if (!await this.managedDueTablesReady()) return 0;
|
|
2187
|
+
const budget = Math.min(input.limit, MAX_ROSTER_REFRESH_PER_SWEEP);
|
|
2188
|
+
const candidates = await this.query(
|
|
2189
|
+
`SELECT obligation.obligation_id
|
|
2190
|
+
FROM adcp_reporting_obligations obligation
|
|
2191
|
+
JOIN adcp_reporting_managed_bindings binding
|
|
2192
|
+
ON binding.configuration_id = obligation.configuration_id
|
|
2193
|
+
LEFT JOIN adcp_reporting_lifecycle_state state
|
|
2194
|
+
ON state.obligation_id = obligation.obligation_id
|
|
2195
|
+
WHERE ($1::text IS NULL OR obligation.account_id = $1)
|
|
2196
|
+
-- Its own cursor, advanced by the refresh itself. Ordering by the
|
|
2197
|
+
-- reconciliation watermark, which a refresh never moves, meant every
|
|
2198
|
+
-- sweep re-read the same first page and nothing beyond one page of quiet
|
|
2199
|
+
-- obligations was ever refreshed, so their roster changes could never
|
|
2200
|
+
-- re-arm.
|
|
2201
|
+
ORDER BY state.roster_refreshed_at NULLS FIRST, obligation.obligation_id
|
|
2202
|
+
LIMIT $2`,
|
|
2203
|
+
[input.account_id ?? null, budget]
|
|
2204
|
+
);
|
|
2205
|
+
let refreshed = 0;
|
|
2206
|
+
for (const row of candidates.rows) {
|
|
2207
|
+
try {
|
|
2208
|
+
await this.readObligatedConsumerRosterVersion({ reporting_obligation_id: row.obligation_id });
|
|
2209
|
+
refreshed += 1;
|
|
2210
|
+
} catch {
|
|
2211
|
+
await this.query(
|
|
2212
|
+
`INSERT INTO adcp_reporting_lifecycle_state (obligation_id, roster_refreshed_at, processed_at)
|
|
2213
|
+
VALUES ($1, clock_timestamp(), clock_timestamp())
|
|
2214
|
+
ON CONFLICT (obligation_id) DO UPDATE SET roster_refreshed_at = clock_timestamp()`,
|
|
2215
|
+
[row.obligation_id]
|
|
2216
|
+
).catch(() => void 0);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return refreshed;
|
|
2220
|
+
}
|
|
2221
|
+
/**
|
|
2222
|
+
* Records a failed reconcile so the obligation backs off instead of
|
|
2223
|
+
* re-occupying the head of every sweep, without ever hiding it: the
|
|
2224
|
+
* watermark is untouched, so it stays unresolved work.
|
|
2225
|
+
*/
|
|
2226
|
+
async recordLifecycleFailure(input) {
|
|
2227
|
+
await this.query(
|
|
2228
|
+
`INSERT INTO adcp_reporting_lifecycle_state (obligation_id, failure_count, next_attempt_at, processed_at)
|
|
2229
|
+
VALUES ($1, 1, clock_timestamp() + INTERVAL '30 seconds', clock_timestamp())
|
|
2230
|
+
ON CONFLICT (obligation_id) DO UPDATE SET
|
|
2231
|
+
failure_count = adcp_reporting_lifecycle_state.failure_count + 1,
|
|
2232
|
+
next_attempt_at = clock_timestamp() + (LEAST(
|
|
2233
|
+
${MAX_LIFECYCLE_BACKOFF_SECONDS}, 30 * POWER(2, LEAST(6, adcp_reporting_lifecycle_state.failure_count))
|
|
2234
|
+
) * INTERVAL '1 second')`,
|
|
2235
|
+
[input.reporting_obligation_id]
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
/** Authoritative instant from the database clock, at microsecond precision. */
|
|
2239
|
+
async readLedgerInstant() {
|
|
2240
|
+
const result = await this.query(
|
|
2241
|
+
`SELECT ${rfc3339Microseconds("clock_timestamp()")} AS instant`
|
|
2242
|
+
);
|
|
2243
|
+
return result.rows[0].instant;
|
|
2244
|
+
}
|
|
2245
|
+
async readObligatedConsumerRosterVersion(input) {
|
|
2246
|
+
if (!this.managedDelivery || !this.obligatedConsumers) return void 0;
|
|
2247
|
+
const accountId = await this.query(
|
|
2248
|
+
"SELECT account_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
|
|
2249
|
+
[input.reporting_obligation_id]
|
|
2250
|
+
);
|
|
2251
|
+
const account_id = accountId.rows[0]?.account_id;
|
|
2252
|
+
if (!account_id) return void 0;
|
|
2253
|
+
const supplied = await withReportingCallbackDeadline(
|
|
2254
|
+
this.obligatedConsumers({ reporting_obligation_id: input.reporting_obligation_id, account_id }),
|
|
2255
|
+
OBLIGATED_CONSUMERS_DEADLINE_MS,
|
|
2256
|
+
"Reporting obligated-consumer lookup deadline elapsed"
|
|
2257
|
+
);
|
|
2258
|
+
const observed = await this.observedConsumerIds(input.reporting_obligation_id);
|
|
2259
|
+
const version = obligatedConsumerRosterVersionFor(supplied, obligatedConsumerIdsFor(supplied, observed));
|
|
2260
|
+
await this.recordObligatedConsumerRosterVersion({
|
|
2261
|
+
reporting_obligation_id: input.reporting_obligation_id,
|
|
2262
|
+
version
|
|
2263
|
+
});
|
|
2264
|
+
return version;
|
|
2265
|
+
}
|
|
2266
|
+
/** Principals that have engaged with this obligation, as the projection sees them. */
|
|
2267
|
+
async observedConsumerIds(obligationId) {
|
|
2268
|
+
const result = await this.query(
|
|
2269
|
+
`SELECT DISTINCT consumer_id FROM (
|
|
2270
|
+
-- Driven from this obligation's subjects into the receipt subject
|
|
2271
|
+
-- index, like every other obligation-scoped receipt read. Asking the
|
|
2272
|
+
-- global receipt table which of its rows belong to an obligation
|
|
2273
|
+
-- supplies neither account nor consumer, so the roster read scanned
|
|
2274
|
+
-- the whole receipt history once per reconcile and once per
|
|
2275
|
+
-- pre-apply re-check.
|
|
2276
|
+
SELECT receipt.consumer_id
|
|
2277
|
+
FROM adcp_reporting_revisions revision
|
|
2278
|
+
JOIN adcp_reporting_receipts receipt
|
|
2279
|
+
ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
|
|
2280
|
+
WHERE revision.obligation_id = $1
|
|
2281
|
+
UNION
|
|
2282
|
+
SELECT receipt.consumer_id
|
|
2283
|
+
FROM adcp_reporting_adjustments adjustment
|
|
2284
|
+
JOIN adcp_reporting_receipts receipt
|
|
2285
|
+
ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
|
|
2286
|
+
WHERE adjustment.obligation_id = $1
|
|
2287
|
+
UNION
|
|
2288
|
+
SELECT status.consumer_id FROM adcp_reporting_consumer_statuses status
|
|
2289
|
+
WHERE status.obligation_id = $1 AND status.consumer_id <> '__legacy_unscoped_consumer__'
|
|
2290
|
+
) engaged ORDER BY consumer_id`,
|
|
2291
|
+
[obligationId]
|
|
2292
|
+
);
|
|
2293
|
+
return result.rows.map((row) => row.consumer_id);
|
|
2294
|
+
}
|
|
2295
|
+
managedDueTablesReadyCache;
|
|
2296
|
+
/** Whether the managed schema exists, resolved once per store instance. */
|
|
2297
|
+
managedDueTablesReady() {
|
|
2298
|
+
if (!this.managedDelivery) return Promise.resolve(false);
|
|
2299
|
+
this.managedDueTablesReadyCache ??= this.query(
|
|
2300
|
+
`SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
|
|
2301
|
+
AND to_regclass('adcp_reporting_materializations') IS NOT NULL
|
|
2302
|
+
AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
|
|
2303
|
+
).then((result) => result.rows[0]?.ready === true).catch((cause) => {
|
|
2304
|
+
this.managedDueTablesReadyCache = void 0;
|
|
2305
|
+
throw cause;
|
|
2306
|
+
});
|
|
2307
|
+
return this.managedDueTablesReadyCache;
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* Terminal receipt subjects whose bodies have been pruned.
|
|
2311
|
+
*
|
|
2312
|
+
* The projection decides reconciliation from the receipt rows it can see,
|
|
2313
|
+
* so once an accepted leaf ages out the subject looks unreconciled again
|
|
2314
|
+
* and a settled billing period reopens. The tombstone is the only remaining
|
|
2315
|
+
* proof, so the projection has to consult it.
|
|
2316
|
+
*/
|
|
2317
|
+
async listTombstonedAcceptedSubjects(client, query, obligationIds, allConsumers = false, ledgerAsOf) {
|
|
2318
|
+
if (!obligationIds.length || !query.consumer_id && !allConsumers) return { subjects: [], complete: true };
|
|
2319
|
+
const result = await client.query(
|
|
2320
|
+
`SELECT tombstone.receipt_kind, tombstone.subject_id, tombstone.consumer_id
|
|
2321
|
+
FROM adcp_reporting_receipt_tombstones tombstone
|
|
2322
|
+
WHERE tombstone.account_id = $1 AND ($2::text IS NULL OR tombstone.consumer_id = $2)
|
|
2323
|
+
AND tombstone.status = 'accepted' AND tombstone.was_current
|
|
2324
|
+
-- A conclusion still has a place in time. Applying every accepted
|
|
2325
|
+
-- tombstone regardless of when its receipt was filed let a
|
|
2326
|
+
-- historical projection settle a subject on an acceptance that had
|
|
2327
|
+
-- not happened yet at its cutoff. Rows written before the column
|
|
2328
|
+
-- existed fall back to pruned_at, which is never earlier, so the
|
|
2329
|
+
-- fallback can only withhold a conclusion, never invent one.
|
|
2330
|
+
AND ($5::timestamptz IS NULL
|
|
2331
|
+
OR COALESCE(tombstone.subject_recorded_at, tombstone.pruned_at) <= $5::timestamptz)
|
|
2332
|
+
AND ((tombstone.receipt_kind = 'revision' AND EXISTS (
|
|
2333
|
+
SELECT 1 FROM adcp_reporting_revisions revision
|
|
2334
|
+
WHERE revision.revision_id = tombstone.subject_id
|
|
2335
|
+
AND revision.obligation_id = ANY($3::text[])))
|
|
2336
|
+
OR (tombstone.receipt_kind = 'adjustment' AND EXISTS (
|
|
2337
|
+
SELECT 1 FROM adcp_reporting_adjustments adjustment
|
|
2338
|
+
WHERE adjustment.adjustment_id = tombstone.subject_id
|
|
2339
|
+
AND adjustment.obligation_id = ANY($3::text[]))))
|
|
2340
|
+
LIMIT $4`,
|
|
2341
|
+
[
|
|
2342
|
+
query.account_id,
|
|
2343
|
+
allConsumers ? null : query.consumer_id,
|
|
2344
|
+
obligationIds,
|
|
2345
|
+
MAX_SNAPSHOT_ITEMS + 1,
|
|
2346
|
+
ledgerAsOf ?? null
|
|
2347
|
+
]
|
|
2348
|
+
);
|
|
2349
|
+
const complete = result.rows.length <= MAX_SNAPSHOT_ITEMS;
|
|
2350
|
+
return {
|
|
2351
|
+
complete,
|
|
2352
|
+
subjects: result.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => ({
|
|
2353
|
+
kind: row.receipt_kind === "adjustment" ? "adjustment" : "revision",
|
|
2354
|
+
subjectId: row.subject_id,
|
|
2355
|
+
// Carried through deliberately. An acceptance belongs to the consumer
|
|
2356
|
+
// that gave it, so a lifecycle fold that runs per consumer must not
|
|
2357
|
+
// let A's pruned acceptance settle the obligation on B's behalf.
|
|
2358
|
+
consumerId: row.consumer_id
|
|
2359
|
+
}))
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
async managedTablesInstalled(client) {
|
|
2363
|
+
const result = await client.query(
|
|
2364
|
+
`SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
|
|
2365
|
+
AND to_regclass('adcp_reporting_materializations') IS NOT NULL
|
|
2366
|
+
AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
|
|
2367
|
+
);
|
|
2368
|
+
return result.rows[0]?.ready === true;
|
|
2369
|
+
}
|
|
2370
|
+
async listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter) {
|
|
2371
|
+
const result = await client.query(
|
|
2372
|
+
`SELECT DISTINCT changed.obligation_id FROM (
|
|
2373
|
+
SELECT materialization.obligation_id
|
|
2374
|
+
FROM adcp_reporting_materializations materialization
|
|
2375
|
+
WHERE materialization.account_id = $1
|
|
2376
|
+
AND GREATEST(materialization.recorded_at, materialization.changed_at) > $3
|
|
2377
|
+
AND GREATEST(materialization.recorded_at, materialization.changed_at) <= $2
|
|
2378
|
+
UNION ALL
|
|
2379
|
+
SELECT COALESCE(revision.obligation_id, adjustment.obligation_id) AS obligation_id
|
|
2380
|
+
FROM adcp_reporting_receipts receipt
|
|
2381
|
+
LEFT JOIN adcp_reporting_revisions revision
|
|
2382
|
+
ON receipt.receipt_kind = 'revision' AND revision.revision_id = receipt.subject_id
|
|
2383
|
+
LEFT JOIN adcp_reporting_adjustments adjustment
|
|
2384
|
+
ON receipt.receipt_kind = 'adjustment' AND adjustment.adjustment_id = receipt.subject_id
|
|
2385
|
+
WHERE receipt.account_id = $1 AND receipt.consumer_id = $4
|
|
2386
|
+
AND receipt.recorded_at > $3 AND receipt.recorded_at <= $2
|
|
2387
|
+
UNION ALL
|
|
2388
|
+
SELECT obligation.obligation_id
|
|
2389
|
+
FROM adcp_reporting_destination_authorizations authz
|
|
2390
|
+
JOIN adcp_reporting_managed_bindings binding
|
|
2391
|
+
ON binding.account_id = authz.account_id
|
|
2392
|
+
AND binding.destination_ref = authz.destination_ref
|
|
2393
|
+
AND binding.authorization_generation = authz.generation
|
|
2394
|
+
JOIN adcp_reporting_obligations obligation ON obligation.configuration_id = binding.configuration_id
|
|
2395
|
+
WHERE authz.account_id = $1 AND authz.revoked_at IS NOT NULL
|
|
2396
|
+
AND authz.changed_at > $3 AND authz.changed_at <= $2
|
|
2397
|
+
) changed WHERE changed.obligation_id IS NOT NULL`,
|
|
2398
|
+
[query.account_id, ledgerAsOf, changesAfter, query.consumer_id ?? null]
|
|
2399
|
+
);
|
|
2400
|
+
return new Set(result.rows.map((row) => row.obligation_id));
|
|
2401
|
+
}
|
|
2402
|
+
async listSnapshotManagedBindings(client, configurationIds) {
|
|
2403
|
+
if (!configurationIds.length) return [];
|
|
2404
|
+
const result = await client.query(
|
|
2405
|
+
`SELECT data FROM adcp_reporting_managed_bindings
|
|
2406
|
+
WHERE configuration_id = ANY($1::text[]) ORDER BY configuration_id`,
|
|
2407
|
+
[configurationIds]
|
|
2408
|
+
);
|
|
2409
|
+
return result.rows.map((row) => row.data);
|
|
2410
|
+
}
|
|
2411
|
+
async listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) {
|
|
2412
|
+
if (!obligationIds.length) return [];
|
|
2413
|
+
const result = await client.query(
|
|
2414
|
+
`SELECT data FROM adcp_reporting_materializations
|
|
2415
|
+
WHERE obligation_id = ANY($1::text[])
|
|
2416
|
+
AND recorded_at <= $2
|
|
2417
|
+
AND ($3::timestamptz IS NULL OR GREATEST(recorded_at, changed_at) > $3)
|
|
2418
|
+
ORDER BY obligation_id, attempt, materialization_id
|
|
2419
|
+
LIMIT $4`,
|
|
2420
|
+
[obligationIds, ledgerAsOf, changesAfter ?? null, MAX_SNAPSHOT_ITEMS + 1]
|
|
2421
|
+
);
|
|
2422
|
+
return result.rows.map((row) => row.data);
|
|
2423
|
+
}
|
|
2424
|
+
async listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) {
|
|
2425
|
+
if (!obligationIds.length) return [];
|
|
2426
|
+
const result = await client.query(
|
|
2427
|
+
`SELECT materialization.data, authz.revoked_at
|
|
2428
|
+
FROM adcp_reporting_materializations materialization
|
|
2429
|
+
JOIN adcp_reporting_destination_authorizations authz
|
|
2430
|
+
ON authz.account_id = materialization.account_id
|
|
2431
|
+
AND authz.destination_ref = materialization.destination_ref
|
|
2432
|
+
AND authz.generation = materialization.authorization_generation
|
|
2433
|
+
WHERE materialization.obligation_id = ANY($1::text[])
|
|
2434
|
+
AND materialization.recorded_at <= $2
|
|
2435
|
+
ORDER BY materialization.obligation_id, materialization.attempt, materialization.materialization_id
|
|
2436
|
+
LIMIT $3`,
|
|
2437
|
+
[obligationIds, ledgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
2438
|
+
);
|
|
2439
|
+
return result.rows.map((row) => {
|
|
2440
|
+
if (!row.revoked_at || row.data.status !== "available" && row.data.status !== "delivered") return row.data;
|
|
2441
|
+
return {
|
|
2442
|
+
...row.data,
|
|
2443
|
+
status: "failed",
|
|
2444
|
+
failed_at: row.revoked_at.toISOString(),
|
|
2445
|
+
failure_code: "AUTHORIZATION_REVOKED"
|
|
2446
|
+
};
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2449
|
+
async listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, kind) {
|
|
2450
|
+
if (!obligationIds.length || !query.consumer_id) return [];
|
|
2451
|
+
const join = kind === "revision" ? "JOIN adcp_reporting_revisions subject ON subject.revision_id = receipt.subject_id" : "JOIN adcp_reporting_adjustments subject ON subject.adjustment_id = receipt.subject_id";
|
|
2452
|
+
const result = await client.query(
|
|
2453
|
+
`SELECT receipt.data FROM adcp_reporting_receipts receipt
|
|
2454
|
+
${join}
|
|
2455
|
+
WHERE receipt.account_id = $1 AND receipt.consumer_id = $2
|
|
2456
|
+
AND receipt.receipt_kind = $3 AND subject.obligation_id = ANY($4::text[])
|
|
2457
|
+
AND receipt.recorded_at <= $5
|
|
2458
|
+
AND ($6::timestamptz IS NULL OR receipt.recorded_at > $6)
|
|
2459
|
+
ORDER BY subject.obligation_id, receipt.recorded_at, receipt.reporting_receipt_id
|
|
2460
|
+
LIMIT $7`,
|
|
2461
|
+
[
|
|
2462
|
+
query.account_id,
|
|
2463
|
+
query.consumer_id,
|
|
2464
|
+
kind,
|
|
2465
|
+
obligationIds,
|
|
2466
|
+
ledgerAsOf,
|
|
2467
|
+
changesAfter ?? null,
|
|
2468
|
+
MAX_SNAPSHOT_ITEMS + 1
|
|
2469
|
+
]
|
|
2470
|
+
);
|
|
2471
|
+
return result.rows.map((row) => row.data);
|
|
2472
|
+
}
|
|
1579
2473
|
async one(sql, params) {
|
|
1580
2474
|
const result = await this.query(sql, params);
|
|
1581
2475
|
return result.rows[0] ? clone(result.rows[0].data) : null;
|
|
1582
2476
|
}
|
|
1583
|
-
async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence) {
|
|
2477
|
+
async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence, legacyReplayEquivalent) {
|
|
1584
2478
|
return this.transaction(
|
|
1585
2479
|
async (client) => {
|
|
1586
2480
|
const inserted = await client.query(insertSql, insertParams);
|
|
@@ -1598,7 +2492,7 @@ class PostgresReportingLedgerStore {
|
|
|
1598
2492
|
}
|
|
1599
2493
|
throw new Error("Immutable reporting ledger replay could not be resolved");
|
|
1600
2494
|
}
|
|
1601
|
-
if (fingerprint(value) !== fingerprint(proposed)) {
|
|
2495
|
+
if (fingerprint(value) !== fingerprint(proposed) && !legacyReplayEquivalent?.(value, proposed)) {
|
|
1602
2496
|
throw new Error("Immutable reporting ledger identity names different content");
|
|
1603
2497
|
}
|
|
1604
2498
|
return { inserted: inserted.rowCount === 1, value: clone(value) };
|
|
@@ -1710,6 +2604,38 @@ function snapshotItems(snapshot) {
|
|
|
1710
2604
|
values.push(adjustment);
|
|
1711
2605
|
adjustments.set(adjustment.reporting_obligation_id, values);
|
|
1712
2606
|
}
|
|
2607
|
+
const obligationByRevision = new Map(
|
|
2608
|
+
snapshot.revisions.map((value) => [value.reporting_revision_id, value.reporting_obligation_id])
|
|
2609
|
+
);
|
|
2610
|
+
const obligationByAdjustment = new Map(
|
|
2611
|
+
snapshot.adjustments.map((value) => [value.reporting_adjustment_id, value.reporting_obligation_id])
|
|
2612
|
+
);
|
|
2613
|
+
const materializations = /* @__PURE__ */ new Map();
|
|
2614
|
+
for (const materialization of snapshot.materializations ?? []) {
|
|
2615
|
+
if (snapshot.query.finality && !visibleRevisionIds.has(materialization.reporting_revision_id)) continue;
|
|
2616
|
+
const values = materializations.get(materialization.reporting_obligation_id) ?? [];
|
|
2617
|
+
values.push(materialization);
|
|
2618
|
+
materializations.set(materialization.reporting_obligation_id, values);
|
|
2619
|
+
}
|
|
2620
|
+
const receipts = /* @__PURE__ */ new Map();
|
|
2621
|
+
for (const receipt of snapshot.receipts ?? []) {
|
|
2622
|
+
if (snapshot.query.finality && !visibleRevisionIds.has(receipt.reporting_revision_id)) continue;
|
|
2623
|
+
const obligationId = obligationByRevision.get(receipt.reporting_revision_id);
|
|
2624
|
+
if (!obligationId) continue;
|
|
2625
|
+
const values = receipts.get(obligationId) ?? [];
|
|
2626
|
+
values.push(receipt);
|
|
2627
|
+
receipts.set(obligationId, values);
|
|
2628
|
+
}
|
|
2629
|
+
const adjustmentReceipts = /* @__PURE__ */ new Map();
|
|
2630
|
+
const visibleAdjustmentIds = new Set([...adjustments.values()].flat().map((value) => value.reporting_adjustment_id));
|
|
2631
|
+
for (const receipt of snapshot.adjustmentReceipts ?? []) {
|
|
2632
|
+
if (snapshot.query.finality && !visibleAdjustmentIds.has(receipt.reporting_adjustment_id)) continue;
|
|
2633
|
+
const obligationId = obligationByAdjustment.get(receipt.reporting_adjustment_id);
|
|
2634
|
+
if (!obligationId) continue;
|
|
2635
|
+
const values = adjustmentReceipts.get(obligationId) ?? [];
|
|
2636
|
+
values.push(receipt);
|
|
2637
|
+
adjustmentReceipts.set(obligationId, values);
|
|
2638
|
+
}
|
|
1713
2639
|
return [
|
|
1714
2640
|
...snapshot.obligations.flatMap((obligation) => [
|
|
1715
2641
|
...snapshot.query.view === "revision" ? [] : [{ kind: "obligation", value: obligation }],
|
|
@@ -1720,6 +2646,18 @@ function snapshotItems(snapshot) {
|
|
|
1720
2646
|
...(adjustments.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
1721
2647
|
kind: "adjustment",
|
|
1722
2648
|
value
|
|
2649
|
+
})),
|
|
2650
|
+
...(materializations.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
2651
|
+
kind: "materialization",
|
|
2652
|
+
value
|
|
2653
|
+
})),
|
|
2654
|
+
...(receipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
2655
|
+
kind: "receipt",
|
|
2656
|
+
value
|
|
2657
|
+
})),
|
|
2658
|
+
...(adjustmentReceipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
2659
|
+
kind: "adjustment_receipt",
|
|
2660
|
+
value
|
|
1723
2661
|
}))
|
|
1724
2662
|
]),
|
|
1725
2663
|
...snapshot.query.view === "periods" || snapshot.query.view === "revision" ? (snapshot.consumerStatuses ?? []).map((value) => ({ kind: "consumer_status", value })) : []
|
|
@@ -1800,9 +2738,200 @@ function adjustmentIdentityFingerprint(value) {
|
|
|
1800
2738
|
const { created_at: _wireCreatedAt, ...stableWireAdjustment } = wireAdjustment;
|
|
1801
2739
|
return digest(JSON.parse(JSON.stringify({ ...identity, wireAdjustment: stableWireAdjustment })));
|
|
1802
2740
|
}
|
|
2741
|
+
function revisionLegacyCanonicalDigestReplay(stored, proposed) {
|
|
2742
|
+
if (stored.wireRevision.canonical_content_digest !== void 0) return false;
|
|
2743
|
+
if (proposed.wireRevision.canonical_content_digest === void 0) return false;
|
|
2744
|
+
const { canonical_content_digest: _digest, ...withoutDigest } = proposed.wireRevision;
|
|
2745
|
+
return revisionIdentityFingerprint({
|
|
2746
|
+
...proposed,
|
|
2747
|
+
wireRevision: withoutDigest
|
|
2748
|
+
}) === revisionIdentityFingerprint(stored);
|
|
2749
|
+
}
|
|
2750
|
+
function adjustmentLegacyCanonicalDigestReplay(stored, proposed) {
|
|
2751
|
+
if (stored.wireAdjustment.canonical_adjustment_sha256 !== void 0) return false;
|
|
2752
|
+
const proposedDigest = proposed.wireAdjustment.canonical_adjustment_sha256;
|
|
2753
|
+
if (proposedDigest === void 0) return false;
|
|
2754
|
+
const { canonical_adjustment_sha256: _digest, ...withoutDigest } = proposed.wireAdjustment;
|
|
2755
|
+
if (reportingCanonicalAdjustmentSha256V1(withoutDigest) !== proposedDigest) return false;
|
|
2756
|
+
return adjustmentIdentityFingerprint({
|
|
2757
|
+
...proposed,
|
|
2758
|
+
wireAdjustment: withoutDigest
|
|
2759
|
+
}) === adjustmentIdentityFingerprint(stored);
|
|
2760
|
+
}
|
|
2761
|
+
const OBLIGATED_CONSUMERS_DEADLINE_MS = 1e4;
|
|
2762
|
+
async function withReportingCallbackDeadline(value, milliseconds, message) {
|
|
2763
|
+
let timer;
|
|
2764
|
+
try {
|
|
2765
|
+
return await Promise.race([
|
|
2766
|
+
value,
|
|
2767
|
+
new Promise((_resolve, reject) => {
|
|
2768
|
+
timer = setTimeout(() => reject(new Error(message)), milliseconds);
|
|
2769
|
+
})
|
|
2770
|
+
]);
|
|
2771
|
+
} finally {
|
|
2772
|
+
if (timer) clearTimeout(timer);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
function pendingAtCutoff(value) {
|
|
2776
|
+
const {
|
|
2777
|
+
ready_at: _readyAt,
|
|
2778
|
+
resource: _resource,
|
|
2779
|
+
verification: _verification,
|
|
2780
|
+
failed_at: _failedAt,
|
|
2781
|
+
failure_code: _failureCode,
|
|
2782
|
+
...pending
|
|
2783
|
+
} = clone(value);
|
|
2784
|
+
return { ...pending, status: "pending" };
|
|
2785
|
+
}
|
|
2786
|
+
const MANAGED_DUE_ARM = ` OR (EXISTS (
|
|
2787
|
+
SELECT 1 FROM adcp_reporting_managed_bindings binding
|
|
2788
|
+
WHERE binding.configuration_id = obligation.configuration_id
|
|
2789
|
+
AND (
|
|
2790
|
+
-- A managed change after the last transition. Health is
|
|
2791
|
+
-- composed from managed state, but only Core evidence
|
|
2792
|
+
-- and the clock used to schedule a reconcile \u2014 so a
|
|
2793
|
+
-- settlement, revocation, receipt, consumer status or
|
|
2794
|
+
-- roster-visible change arriving after a complete left
|
|
2795
|
+
-- that complete persisted and webhooked forever while
|
|
2796
|
+
-- a live read of the same obligation degraded.
|
|
2797
|
+
EXISTS (
|
|
2798
|
+
SELECT 1 FROM adcp_reporting_materializations m
|
|
2799
|
+
WHERE m.obligation_id = obligation.obligation_id
|
|
2800
|
+
AND m.changed_at > watermark.since
|
|
2801
|
+
AND m.changed_at <= $2)
|
|
2802
|
+
OR EXISTS (
|
|
2803
|
+
-- Adjustments are managed health inputs too: after an
|
|
2804
|
+
-- accepted billing receipt, a correction makes the live
|
|
2805
|
+
-- read action_required while the persisted health and
|
|
2806
|
+
-- its webhook stayed complete.
|
|
2807
|
+
SELECT 1 FROM adcp_reporting_adjustments adj
|
|
2808
|
+
WHERE adj.obligation_id = obligation.obligation_id
|
|
2809
|
+
AND adj.recorded_at > watermark.since
|
|
2810
|
+
AND adj.recorded_at <= $2)
|
|
2811
|
+
OR EXISTS (
|
|
2812
|
+
SELECT 1 FROM adcp_reporting_destination_authorizations authz
|
|
2813
|
+
WHERE authz.account_id = binding.account_id
|
|
2814
|
+
AND authz.destination_ref = binding.destination_ref
|
|
2815
|
+
AND GREATEST(authz.changed_at, COALESCE(authz.cleanup_completed_at, authz.changed_at))
|
|
2816
|
+
> watermark.since
|
|
2817
|
+
AND authz.changed_at <= $2)
|
|
2818
|
+
-- Driven from this obligation's subjects into the
|
|
2819
|
+
-- receipt table's subject index. Asking the global
|
|
2820
|
+
-- receipt table which of its rows belong to this
|
|
2821
|
+
-- obligation supplied neither account nor consumer, so
|
|
2822
|
+
-- every candidate obligation in every sweep re-scanned
|
|
2823
|
+
-- the whole receipt history.
|
|
2824
|
+
OR EXISTS (
|
|
2825
|
+
SELECT 1 FROM adcp_reporting_revisions rv
|
|
2826
|
+
JOIN adcp_reporting_receipts receipt
|
|
2827
|
+
ON receipt.receipt_kind = 'revision'
|
|
2828
|
+
AND receipt.subject_id = rv.revision_id
|
|
2829
|
+
WHERE rv.obligation_id = obligation.obligation_id
|
|
2830
|
+
AND receipt.recorded_at > watermark.since
|
|
2831
|
+
AND receipt.recorded_at <= $2)
|
|
2832
|
+
OR EXISTS (
|
|
2833
|
+
SELECT 1 FROM adcp_reporting_adjustments aj
|
|
2834
|
+
JOIN adcp_reporting_receipts receipt
|
|
2835
|
+
ON receipt.receipt_kind = 'adjustment'
|
|
2836
|
+
AND receipt.subject_id = aj.adjustment_id
|
|
2837
|
+
WHERE aj.obligation_id = obligation.obligation_id
|
|
2838
|
+
AND receipt.recorded_at > watermark.since
|
|
2839
|
+
AND receipt.recorded_at <= $2)
|
|
2840
|
+
OR EXISTS (
|
|
2841
|
+
SELECT 1 FROM adcp_reporting_consumer_statuses status
|
|
2842
|
+
WHERE status.obligation_id = obligation.obligation_id
|
|
2843
|
+
AND status.created_at > watermark.since
|
|
2844
|
+
AND status.created_at <= $2)
|
|
2845
|
+
-- An external roster change writes nothing here at all,
|
|
2846
|
+
-- so the only durable trace is the version recorded at
|
|
2847
|
+
-- the last reconcile. A null one means never reconciled.
|
|
2848
|
+
OR state.processed_roster_version IS NULL
|
|
2849
|
+
-- A roster lives outside this database, so the only way
|
|
2850
|
+
-- a change can re-arm anything is if someone records
|
|
2851
|
+
-- it. Every read of the roster publishes the version it
|
|
2852
|
+
-- saw; a difference from what was last reconciled is a
|
|
2853
|
+
-- due condition. Without this the first reconcile made
|
|
2854
|
+
-- processed_roster_version non-null and no later roster
|
|
2855
|
+
-- change could ever schedule work again.
|
|
2856
|
+
OR state.current_roster_version IS DISTINCT FROM state.processed_roster_version
|
|
2857
|
+
-- A retained resource expiring is a health change with
|
|
2858
|
+
-- no row written anywhere, so nothing else can notice it.
|
|
2859
|
+
OR EXISTS (
|
|
2860
|
+
SELECT 1 FROM adcp_reporting_materializations m
|
|
2861
|
+
WHERE m.obligation_id = obligation.obligation_id
|
|
2862
|
+
AND m.status IN ('available', 'delivered')
|
|
2863
|
+
AND (m.data -> 'resource' ->> 'expires_at')::timestamptz <= $2
|
|
2864
|
+
AND (m.data -> 'resource' ->> 'expires_at')::timestamptz
|
|
2865
|
+
> watermark.since)
|
|
2866
|
+
)
|
|
2867
|
+
))
|
|
2868
|
+
`;
|
|
2869
|
+
function obligatedConsumerIdsFor(supplied, observed) {
|
|
2870
|
+
return supplied.complete === true ? [...new Set(supplied.ids)].sort() : [.../* @__PURE__ */ new Set([...supplied.ids, ...observed])].sort();
|
|
2871
|
+
}
|
|
2872
|
+
function obligatedConsumerRosterVersionFor(supplied, ids) {
|
|
2873
|
+
return supplied.version ?? digest({ ids, complete: supplied.complete === true });
|
|
2874
|
+
}
|
|
2875
|
+
const MAX_ROSTER_REFRESH_PER_SWEEP = 25;
|
|
2876
|
+
const MAX_LIFECYCLE_BACKOFF_SECONDS = 900;
|
|
2877
|
+
function rfc3339Microseconds(expression) {
|
|
2878
|
+
return `to_char(${expression} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`;
|
|
2879
|
+
}
|
|
1803
2880
|
function accountLock(accountId) {
|
|
1804
2881
|
return `adcp-reporting-account:${accountId}`;
|
|
1805
2882
|
}
|
|
2883
|
+
async function resolveStoredFinalityBaseline(transaction, obligationId) {
|
|
2884
|
+
const latest = await transaction.query(
|
|
2885
|
+
`SELECT transition_id, data->>'finality' AS finality
|
|
2886
|
+
FROM adcp_reporting_transitions
|
|
2887
|
+
WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1
|
|
2888
|
+
FOR UPDATE`,
|
|
2889
|
+
[obligationId]
|
|
2890
|
+
);
|
|
2891
|
+
const previous = latest.rows[0];
|
|
2892
|
+
if (!previous) return "none";
|
|
2893
|
+
if (previous.finality) return previous.finality;
|
|
2894
|
+
await transaction.query(
|
|
2895
|
+
`UPDATE adcp_reporting_transitions
|
|
2896
|
+
SET data = data || jsonb_build_object('finality', 'none')
|
|
2897
|
+
WHERE transition_id = $1 AND NOT (data ? 'finality')`,
|
|
2898
|
+
[previous.transition_id]
|
|
2899
|
+
);
|
|
2900
|
+
return "none";
|
|
2901
|
+
}
|
|
2902
|
+
async function assertNoLegacyPendingTransitions(transaction, obligationId) {
|
|
2903
|
+
const pending = await transaction.query(
|
|
2904
|
+
`SELECT transition_id FROM adcp_reporting_transitions
|
|
2905
|
+
WHERE obligation_id = $1 AND data->>'notifiedAt' IS NULL
|
|
2906
|
+
LIMIT 1`,
|
|
2907
|
+
[obligationId]
|
|
2908
|
+
);
|
|
2909
|
+
if (pending.rowCount !== 0) {
|
|
2910
|
+
throw new Error(
|
|
2911
|
+
"Drain or explicitly resolve legacy pending reporting transitions before enabling transactional notification activity"
|
|
2912
|
+
);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
async function assertFinalityWriterFence(work) {
|
|
2916
|
+
try {
|
|
2917
|
+
return await work();
|
|
2918
|
+
} catch (error) {
|
|
2919
|
+
if (isFinalityWriterFenceViolation(error)) {
|
|
2920
|
+
throw new Error(
|
|
2921
|
+
"Reporting transition rejected by the finality writer fence: every transition written after REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION must record an observed finality. Drain pre-SDK-14 writers before installing the fence.",
|
|
2922
|
+
{ cause: error }
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
2925
|
+
throw error;
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
function isFinalityWriterFenceViolation(error) {
|
|
2929
|
+
for (let current = error; current instanceof Error; current = current.cause) {
|
|
2930
|
+
const candidate = current;
|
|
2931
|
+
if (candidate.code === "23514" && candidate.constraint === REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT) return true;
|
|
2932
|
+
}
|
|
2933
|
+
return false;
|
|
2934
|
+
}
|
|
1806
2935
|
function consumerStatusChainKeyForObligation(obligation) {
|
|
1807
2936
|
return reportingConsumerStatusChainKeyFromIdentityV1({
|
|
1808
2937
|
delivery_config_id: obligation.delivery_config_id,
|
|
@@ -1882,6 +3011,8 @@ function positiveInteger(value, name) {
|
|
|
1882
3011
|
}
|
|
1883
3012
|
export {
|
|
1884
3013
|
PostgresReportingLedgerStore,
|
|
3014
|
+
REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT,
|
|
3015
|
+
REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
|
|
1885
3016
|
REPORTING_LEDGER_MIGRATION,
|
|
1886
3017
|
ReportingLedgerContinuityError,
|
|
1887
3018
|
ReportingLedgerLeaseLostError,
|