@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,6 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var postgres_exports = {};
|
|
20
20
|
__export(postgres_exports, {
|
|
21
21
|
PostgresReportingLedgerStore: () => PostgresReportingLedgerStore,
|
|
22
|
+
REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT: () => REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT,
|
|
23
|
+
REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION: () => REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
|
|
22
24
|
REPORTING_LEDGER_MIGRATION: () => REPORTING_LEDGER_MIGRATION,
|
|
23
25
|
ReportingLedgerContinuityError: () => ReportingLedgerContinuityError,
|
|
24
26
|
ReportingLedgerLeaseLostError: () => ReportingLedgerLeaseLostError,
|
|
@@ -35,6 +37,8 @@ var import_health = require('./health.js');
|
|
|
35
37
|
var import_instant = require('./instant.js');
|
|
36
38
|
var import_types = require('./types.js');
|
|
37
39
|
var import_types2 = require('./types.js');
|
|
40
|
+
var import_handler = require('./handler.js');
|
|
41
|
+
var import_producer = require('./producer.js');
|
|
38
42
|
var import_consumer_status_identity = require('./consumer-status-identity.js');
|
|
39
43
|
const REPORTING_LEDGER_MIGRATION = `
|
|
40
44
|
CREATE TABLE IF NOT EXISTS adcp_reporting_configurations (
|
|
@@ -182,6 +186,12 @@ CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_scope
|
|
|
182
186
|
ON adcp_reporting_consumer_statuses (account_id, consumer_id, created_at, consumer_status_id);
|
|
183
187
|
CREATE UNIQUE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_current
|
|
184
188
|
ON adcp_reporting_consumer_statuses (account_id, consumer_id, chain_key) WHERE is_current;
|
|
189
|
+
-- The managed state digest is read inside the apply transaction, under the
|
|
190
|
+
-- account lock. Filtering this table by obligation alone matched no index, so
|
|
191
|
+
-- every apply scanned every consumer status in the deployment while holding
|
|
192
|
+
-- that lock and pushed concurrent Core writes past their lock timeout.
|
|
193
|
+
CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_obligation
|
|
194
|
+
ON adcp_reporting_consumer_statuses (obligation_id, created_at, consumer_status_id);
|
|
185
195
|
|
|
186
196
|
CREATE TABLE IF NOT EXISTS adcp_reporting_consumer_status_batches (
|
|
187
197
|
account_id TEXT NOT NULL,
|
|
@@ -206,6 +216,29 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_issues (
|
|
|
206
216
|
CREATE INDEX IF NOT EXISTS adcp_reporting_issues_obligation
|
|
207
217
|
ON adcp_reporting_issues (obligation_id, observed_at, issue_id);
|
|
208
218
|
|
|
219
|
+
-- Per-obligation reconciliation watermark. A reconcile that changes nothing
|
|
220
|
+
-- still has to record that it looked, or a change with no health effect stays
|
|
221
|
+
-- a candidate forever and starves newer work behind it. Core, not managed:
|
|
222
|
+
-- the lifecycle sweep is a Core concern and must not depend on the add-on.
|
|
223
|
+
CREATE TABLE IF NOT EXISTS adcp_reporting_lifecycle_state (
|
|
224
|
+
obligation_id TEXT PRIMARY KEY REFERENCES adcp_reporting_obligations(obligation_id),
|
|
225
|
+
processed_state_version TEXT,
|
|
226
|
+
processed_roster_version TEXT,
|
|
227
|
+
processed_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
228
|
+
);
|
|
229
|
+
CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_processed
|
|
230
|
+
ON adcp_reporting_lifecycle_state (processed_at, obligation_id);
|
|
231
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
232
|
+
ADD COLUMN IF NOT EXISTS current_roster_version TEXT;
|
|
233
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
234
|
+
ADD COLUMN IF NOT EXISTS failure_count INTEGER NOT NULL DEFAULT 0;
|
|
235
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
236
|
+
ADD COLUMN IF NOT EXISTS next_attempt_at TIMESTAMPTZ;
|
|
237
|
+
ALTER TABLE adcp_reporting_lifecycle_state
|
|
238
|
+
ADD COLUMN IF NOT EXISTS roster_refreshed_at TIMESTAMPTZ;
|
|
239
|
+
CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_roster_refreshed
|
|
240
|
+
ON adcp_reporting_lifecycle_state (roster_refreshed_at, obligation_id);
|
|
241
|
+
|
|
209
242
|
CREATE TABLE IF NOT EXISTS adcp_reporting_transitions (
|
|
210
243
|
transition_id TEXT PRIMARY KEY,
|
|
211
244
|
transition_sequence BIGSERIAL NOT NULL UNIQUE,
|
|
@@ -245,7 +278,23 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_checkpoints (
|
|
|
245
278
|
CREATE INDEX IF NOT EXISTS adcp_reporting_checkpoints_expiry
|
|
246
279
|
ON adcp_reporting_checkpoints (expires_at);
|
|
247
280
|
`.trim();
|
|
281
|
+
const REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT = "adcp_reporting_transitions_finality_recorded";
|
|
282
|
+
const REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION = `
|
|
283
|
+
DO $$
|
|
284
|
+
BEGIN
|
|
285
|
+
IF NOT EXISTS (
|
|
286
|
+
SELECT 1 FROM pg_constraint
|
|
287
|
+
WHERE conrelid = 'adcp_reporting_transitions'::regclass
|
|
288
|
+
AND conname = '${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}'
|
|
289
|
+
) THEN
|
|
290
|
+
ALTER TABLE adcp_reporting_transitions
|
|
291
|
+
ADD CONSTRAINT ${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}
|
|
292
|
+
CHECK (data ? 'finality') NOT VALID;
|
|
293
|
+
END IF;
|
|
294
|
+
END $$;
|
|
295
|
+
`.trim();
|
|
248
296
|
const MAX_SNAPSHOT_ITEMS = 1e4;
|
|
297
|
+
const MAX_LIFECYCLE_RECEIPT_LEAVES = 1e5;
|
|
249
298
|
const MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
|
|
250
299
|
const MAX_ACTIVE_SNAPSHOTS_PER_ACCOUNT = 32;
|
|
251
300
|
const MAX_ACTIVE_SNAPSHOT_BYTES_PER_ACCOUNT = 128 * 1024 * 1024;
|
|
@@ -300,6 +349,9 @@ async function sweepExpiredReportingLedgerState(pool, limit = 1e3) {
|
|
|
300
349
|
class PostgresReportingLedgerStore {
|
|
301
350
|
constructor(pool, options = {}) {
|
|
302
351
|
this.pool = pool;
|
|
352
|
+
this.managedDelivery = options.managedDelivery === true;
|
|
353
|
+
this.obligatedConsumers = options.obligatedConsumers;
|
|
354
|
+
this[import_types.REPORTING_LEDGER_AUTHORITY] = { substrate: pool, managedDelivery: this.managedDelivery };
|
|
303
355
|
const development = process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development";
|
|
304
356
|
if (!development && !options.acknowledgeIsolatedDatabase) {
|
|
305
357
|
throw new Error(
|
|
@@ -307,6 +359,8 @@ class PostgresReportingLedgerStore {
|
|
|
307
359
|
);
|
|
308
360
|
}
|
|
309
361
|
this.consumerMismatchEscalation = (0, import_health.assertReportingConsumerMismatchEscalation)(options.consumerMismatchEscalation);
|
|
362
|
+
this.notificationActivityPort = options.notificationActivityPort;
|
|
363
|
+
this.transactionalNotificationActivity = options.notificationActivityPort !== void 0;
|
|
310
364
|
}
|
|
311
365
|
pool;
|
|
312
366
|
/**
|
|
@@ -317,6 +371,11 @@ class PostgresReportingLedgerStore {
|
|
|
317
371
|
* contradict the summary.
|
|
318
372
|
*/
|
|
319
373
|
consumerMismatchEscalation;
|
|
374
|
+
[import_types.REPORTING_LEDGER_AUTHORITY];
|
|
375
|
+
managedDelivery;
|
|
376
|
+
obligatedConsumers;
|
|
377
|
+
transactionalNotificationActivity;
|
|
378
|
+
notificationActivityPort;
|
|
320
379
|
async putConfiguration(configuration) {
|
|
321
380
|
return this.transaction(
|
|
322
381
|
async (client) => {
|
|
@@ -417,14 +476,26 @@ class PostgresReportingLedgerStore {
|
|
|
417
476
|
positiveInteger(input.limit, "limit");
|
|
418
477
|
if (input.limit > 1e3) throw new RangeError("limit must not exceed 1000");
|
|
419
478
|
if (!Number.isFinite(Date.parse(input.ledgerAsOf))) throw new TypeError("ledgerAsOf must be an RFC 3339 instant");
|
|
479
|
+
const managedDueArm = await this.managedDueTablesReady() ? MANAGED_DUE_ARM : "";
|
|
420
480
|
const result = await this.query(
|
|
421
481
|
`SELECT obligation.data FROM adcp_reporting_obligations obligation
|
|
422
482
|
LEFT JOIN LATERAL (
|
|
423
|
-
SELECT transition.data->>'health' AS health
|
|
483
|
+
SELECT transition.data->>'health' AS health, transition.occurred_at
|
|
424
484
|
FROM adcp_reporting_transitions transition
|
|
425
485
|
WHERE transition.obligation_id = obligation.obligation_id
|
|
426
486
|
ORDER BY transition.transition_sequence DESC LIMIT 1
|
|
427
487
|
) latest ON TRUE
|
|
488
|
+
LEFT JOIN adcp_reporting_lifecycle_state state
|
|
489
|
+
ON state.obligation_id = obligation.obligation_id
|
|
490
|
+
CROSS JOIN LATERAL (
|
|
491
|
+
-- A reconcile that changed no health still has to count as work done,
|
|
492
|
+
-- or a managed change with no health effect keeps the obligation due
|
|
493
|
+
-- forever and, at small page sizes, starves everything behind it.
|
|
494
|
+
SELECT GREATEST(
|
|
495
|
+
COALESCE(state.processed_at, obligation.created_at),
|
|
496
|
+
COALESCE(latest.occurred_at, obligation.created_at)
|
|
497
|
+
) AS since
|
|
498
|
+
) watermark
|
|
428
499
|
WHERE ($1::text IS NULL OR obligation.account_id = $1)
|
|
429
500
|
AND ((COALESCE(latest.health, 'waiting') = 'waiting'
|
|
430
501
|
AND (obligation.data->>'expectedAt')::timestamptz <= $2)
|
|
@@ -435,8 +506,14 @@ class PostgresReportingLedgerStore {
|
|
|
435
506
|
AND EXISTS (
|
|
436
507
|
SELECT 1 FROM adcp_reporting_revisions revision
|
|
437
508
|
WHERE revision.obligation_id = obligation.obligation_id
|
|
438
|
-
))
|
|
439
|
-
|
|
509
|
+
))
|
|
510
|
+
${managedDueArm} )
|
|
511
|
+
-- A tenant that keeps failing must not monopolise every sweep. Its
|
|
512
|
+
-- backoff cursor holds it out of selection until it is due again,
|
|
513
|
+
-- while processed_at ordering still favours whoever has waited
|
|
514
|
+
-- longest among the eligible.
|
|
515
|
+
AND (state.next_attempt_at IS NULL OR state.next_attempt_at <= $2)
|
|
516
|
+
ORDER BY watermark.since, LEAST(
|
|
440
517
|
(obligation.data->>'expectedAt')::timestamptz,
|
|
441
518
|
(obligation.data->>'recoveryDeadlineAt')::timestamptz
|
|
442
519
|
), obligation.obligation_id
|
|
@@ -583,7 +660,8 @@ class PostgresReportingLedgerStore {
|
|
|
583
660
|
obligationId: revision.reporting_obligation_id,
|
|
584
661
|
owner: lease.owner,
|
|
585
662
|
generation: lease.generation
|
|
586
|
-
}
|
|
663
|
+
},
|
|
664
|
+
revisionLegacyCanonicalDigestReplay
|
|
587
665
|
);
|
|
588
666
|
}
|
|
589
667
|
async getRevision(id, accountId) {
|
|
@@ -657,7 +735,8 @@ class PostgresReportingLedgerStore {
|
|
|
657
735
|
obligationId: adjustment.reporting_obligation_id,
|
|
658
736
|
owner: lease.owner,
|
|
659
737
|
generation: lease.generation
|
|
660
|
-
}
|
|
738
|
+
},
|
|
739
|
+
adjustmentLegacyCanonicalDigestReplay
|
|
661
740
|
);
|
|
662
741
|
}
|
|
663
742
|
async listAdjustments(obligationId) {
|
|
@@ -1048,27 +1127,59 @@ class PostgresReportingLedgerStore {
|
|
|
1048
1127
|
);
|
|
1049
1128
|
return result.rows.map((row) => clone(row.data));
|
|
1050
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Resolves — and for pre-SDK-14 rows persists — the observed finality baseline
|
|
1132
|
+
* of the obligation's latest transition, so the lifecycle decision and this
|
|
1133
|
+
* store's compare-and-set read one committed value.
|
|
1134
|
+
*/
|
|
1135
|
+
async resolveTransitionFinalityBaseline(obligationId) {
|
|
1136
|
+
const lock = await this.accountLockForObligation(obligationId);
|
|
1137
|
+
return this.transaction((client) => resolveStoredFinalityBaseline(client, obligationId), {
|
|
1138
|
+
preBeginAdvisoryLock: lock
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1051
1141
|
async appendTransition(transition) {
|
|
1052
1142
|
const lock = await this.accountLockForObligation(transition.reporting_obligation_id);
|
|
1143
|
+
return assertFinalityWriterFence(() => this.appendTransitionWithinLock(transition, lock));
|
|
1144
|
+
}
|
|
1145
|
+
async appendTransitionWithinLock(transition, lock) {
|
|
1053
1146
|
return this.transaction(
|
|
1054
1147
|
async (client) => {
|
|
1148
|
+
if (this.notificationActivityPort) {
|
|
1149
|
+
await assertNoLegacyPendingTransitions(client, transition.reporting_obligation_id);
|
|
1150
|
+
}
|
|
1151
|
+
const previousFinality = await resolveStoredFinalityBaseline(client, transition.reporting_obligation_id);
|
|
1152
|
+
const latest = await client.query(
|
|
1153
|
+
`SELECT data->>'health' AS health FROM adcp_reporting_transitions
|
|
1154
|
+
WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1`,
|
|
1155
|
+
[transition.reporting_obligation_id]
|
|
1156
|
+
);
|
|
1157
|
+
if ((latest.rows[0]?.health ?? "waiting") !== transition.previousHealth || transition.previousFinality !== void 0 && previousFinality !== transition.previousFinality) {
|
|
1158
|
+
return { inserted: false };
|
|
1159
|
+
}
|
|
1160
|
+
const storedTransition = this.notificationActivityPort ? { ...transition, notifiedAt: transition.occurredAt } : transition;
|
|
1055
1161
|
const result = await client.query(
|
|
1056
1162
|
`INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
SELECT previous.data->>'health' FROM adcp_reporting_transitions previous
|
|
1060
|
-
WHERE previous.obligation_id = $2
|
|
1061
|
-
ORDER BY previous.transition_sequence DESC LIMIT 1
|
|
1062
|
-
), 'waiting') = $5
|
|
1063
|
-
ON CONFLICT DO NOTHING RETURNING transition_id`,
|
|
1163
|
+
VALUES ($1, $2, $3::jsonb, $4)
|
|
1164
|
+
ON CONFLICT DO NOTHING RETURNING transition_id`,
|
|
1064
1165
|
[
|
|
1065
1166
|
transition.transitionId,
|
|
1066
1167
|
transition.reporting_obligation_id,
|
|
1067
|
-
JSON.stringify(
|
|
1068
|
-
transition.occurredAt
|
|
1069
|
-
transition.previousHealth
|
|
1168
|
+
JSON.stringify(storedTransition),
|
|
1169
|
+
transition.occurredAt
|
|
1070
1170
|
]
|
|
1071
1171
|
);
|
|
1172
|
+
if (result.rowCount === 1 && this.notificationActivityPort) {
|
|
1173
|
+
const obligation = await client.query(
|
|
1174
|
+
"SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1",
|
|
1175
|
+
[transition.reporting_obligation_id]
|
|
1176
|
+
);
|
|
1177
|
+
if (!obligation.rows[0]) throw new Error("Reporting obligation is unavailable");
|
|
1178
|
+
await this.notificationActivityPort.recordTransition(
|
|
1179
|
+
{ transition, obligation: obligation.rows[0].data },
|
|
1180
|
+
client
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1072
1183
|
return { inserted: result.rowCount === 1 };
|
|
1073
1184
|
},
|
|
1074
1185
|
{ preBeginAdvisoryLock: lock }
|
|
@@ -1076,12 +1187,19 @@ class PostgresReportingLedgerStore {
|
|
|
1076
1187
|
}
|
|
1077
1188
|
async applyLifecycleProjection(input) {
|
|
1078
1189
|
const lock = await this.accountLockForObligation(input.reporting_obligation_id);
|
|
1190
|
+
return assertFinalityWriterFence(() => this.applyLifecycleProjectionWithinLock(input, lock));
|
|
1191
|
+
}
|
|
1192
|
+
async applyLifecycleProjectionWithinLock(input, lock) {
|
|
1079
1193
|
return this.transaction(
|
|
1080
1194
|
async (client) => {
|
|
1081
1195
|
const obligations = await client.query(
|
|
1082
1196
|
`SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1 FOR UPDATE`,
|
|
1083
1197
|
[input.reporting_obligation_id]
|
|
1084
1198
|
);
|
|
1199
|
+
if (this.notificationActivityPort) {
|
|
1200
|
+
await assertNoLegacyPendingTransitions(client, input.reporting_obligation_id);
|
|
1201
|
+
}
|
|
1202
|
+
const previousFinality = await resolveStoredFinalityBaseline(client, input.reporting_obligation_id);
|
|
1085
1203
|
const revisions = await client.query(
|
|
1086
1204
|
`SELECT revision_id FROM adcp_reporting_revisions
|
|
1087
1205
|
WHERE obligation_id = $1 ORDER BY revision_number, revision_id`,
|
|
@@ -1095,11 +1213,26 @@ class PostgresReportingLedgerStore {
|
|
|
1095
1213
|
const revisionIds = revisions.rows.map((value) => value.revision_id);
|
|
1096
1214
|
const previousHealth = latest.rows[0]?.health ?? "waiting";
|
|
1097
1215
|
const obligation = obligations.rows[0]?.data;
|
|
1098
|
-
if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || (0, import_source.canonicalJsonV1)(revisionIds) !== (0, import_source.canonicalJsonV1)(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth) {
|
|
1216
|
+
if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || (0, import_source.canonicalJsonV1)(revisionIds) !== (0, import_source.canonicalJsonV1)(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth || input.expectedPreviousFinality !== void 0 && previousFinality !== input.expectedPreviousFinality) {
|
|
1099
1217
|
return { applied: false, transitionInserted: false };
|
|
1100
1218
|
}
|
|
1219
|
+
if (input.expectedManagedStateVersion !== void 0 && await this.readManagedStateVersion(client, input.reporting_obligation_id) !== input.expectedManagedStateVersion) {
|
|
1220
|
+
return { applied: false, transitionInserted: false };
|
|
1221
|
+
}
|
|
1222
|
+
if (input.expectedObligatedConsumerRosterVersion !== void 0) {
|
|
1223
|
+
const state = await client.query(
|
|
1224
|
+
`SELECT current_roster_version FROM adcp_reporting_lifecycle_state
|
|
1225
|
+
WHERE obligation_id = $1 FOR UPDATE`,
|
|
1226
|
+
[input.reporting_obligation_id]
|
|
1227
|
+
);
|
|
1228
|
+
const observed = state.rows[0]?.current_roster_version;
|
|
1229
|
+
if (observed !== void 0 && observed !== input.expectedObligatedConsumerRosterVersion) {
|
|
1230
|
+
return { applied: false, transitionInserted: false };
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1101
1233
|
let transitionInserted = false;
|
|
1102
1234
|
if (input.transition) {
|
|
1235
|
+
const storedTransition = this.notificationActivityPort ? { ...input.transition, notifiedAt: input.ledgerAsOf } : input.transition;
|
|
1103
1236
|
const inserted = await client.query(
|
|
1104
1237
|
`INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
|
|
1105
1238
|
VALUES ($1, $2, $3::jsonb, $4)
|
|
@@ -1107,12 +1240,60 @@ class PostgresReportingLedgerStore {
|
|
|
1107
1240
|
[
|
|
1108
1241
|
input.transition.transitionId,
|
|
1109
1242
|
input.reporting_obligation_id,
|
|
1110
|
-
JSON.stringify(
|
|
1243
|
+
JSON.stringify(storedTransition),
|
|
1111
1244
|
input.transition.occurredAt
|
|
1112
1245
|
]
|
|
1113
1246
|
);
|
|
1114
1247
|
if (inserted.rowCount !== 1) return { applied: false, transitionInserted: false };
|
|
1115
1248
|
transitionInserted = true;
|
|
1249
|
+
if (this.notificationActivityPort) {
|
|
1250
|
+
await this.notificationActivityPort.recordTransition({ transition: input.transition, obligation }, client);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
{
|
|
1254
|
+
await client.query(
|
|
1255
|
+
`INSERT INTO adcp_reporting_lifecycle_state
|
|
1256
|
+
(obligation_id, processed_state_version, processed_roster_version,
|
|
1257
|
+
current_roster_version, processed_at)
|
|
1258
|
+
VALUES ($1, $2, $3, $3, LEAST($4::timestamptz, clock_timestamp()))
|
|
1259
|
+
ON CONFLICT (obligation_id) DO UPDATE SET
|
|
1260
|
+
processed_state_version = EXCLUDED.processed_state_version,
|
|
1261
|
+
processed_roster_version = EXCLUDED.processed_roster_version,
|
|
1262
|
+
-- Reconciling also observes the roster, so the current version
|
|
1263
|
+
-- moves with the processed one. A later external change
|
|
1264
|
+
-- publishes a different current version and re-arms; without
|
|
1265
|
+
-- this the two never matched and the obligation stayed due.
|
|
1266
|
+
-- Never over a version this reconcile did not observe: if the
|
|
1267
|
+
-- row was created or advanced after the check above, that
|
|
1268
|
+
-- newer observation is what has to survive, or its re-arm is
|
|
1269
|
+
-- silently dropped.
|
|
1270
|
+
current_roster_version = CASE
|
|
1271
|
+
WHEN $5::text IS NOT NULL
|
|
1272
|
+
AND adcp_reporting_lifecycle_state.current_roster_version IS DISTINCT FROM $5::text
|
|
1273
|
+
THEN adcp_reporting_lifecycle_state.current_roster_version
|
|
1274
|
+
ELSE EXCLUDED.processed_roster_version END,
|
|
1275
|
+
processed_at = EXCLUDED.processed_at,
|
|
1276
|
+
failure_count = 0,
|
|
1277
|
+
next_attempt_at = NULL`,
|
|
1278
|
+
[
|
|
1279
|
+
input.reporting_obligation_id,
|
|
1280
|
+
input.processedManagedStateVersion ?? null,
|
|
1281
|
+
// Empty string, not null: null means "never reconciled", which
|
|
1282
|
+
// is a due condition. A deployment with no roster source would
|
|
1283
|
+
// otherwise stay due forever.
|
|
1284
|
+
input.processedObligatedConsumerRosterVersion ?? "",
|
|
1285
|
+
// The cutoff the projection actually read at, never the commit
|
|
1286
|
+
// instant. Watermarking at commit time swallowed everything
|
|
1287
|
+
// that landed between the two: excluded from the projection,
|
|
1288
|
+
// and then behind the watermark forever, so it never became
|
|
1289
|
+
// due again. It is clamped to this database's clock in SQL
|
|
1290
|
+
// below, because a cutoff ahead of the ledger buries
|
|
1291
|
+
// database-timestamped work the same way for the opposite
|
|
1292
|
+
// reason.
|
|
1293
|
+
input.ledgerAsOf,
|
|
1294
|
+
input.expectedObligatedConsumerRosterVersion ?? null
|
|
1295
|
+
]
|
|
1296
|
+
);
|
|
1116
1297
|
}
|
|
1117
1298
|
const projectedIds = input.projectedIssues.map((issue) => issue.issueId);
|
|
1118
1299
|
for (const issue of input.projectedIssues) {
|
|
@@ -1135,7 +1316,17 @@ class PostgresReportingLedgerStore {
|
|
|
1135
1316
|
data = data || jsonb_build_object('resolvedAt', $2::text),
|
|
1136
1317
|
changed_at = clock_timestamp()
|
|
1137
1318
|
WHERE obligation_id = $1 AND resolved_at IS NULL
|
|
1138
|
-
|
|
1319
|
+
-- Every code this projection can emit, not only the Core two.
|
|
1320
|
+
-- A managed DELIVERY_FAILED persisted by an earlier reconcile
|
|
1321
|
+
-- was never cleared, so after a successful redelivery the
|
|
1322
|
+
-- status kept publishing a stale delivery failure for as long
|
|
1323
|
+
-- as Core stayed degraded for some unrelated reason.
|
|
1324
|
+
AND data->>'code' IN (
|
|
1325
|
+
'REPORT_OVERDUE', 'REPORTING_COVERAGE_INCOMPLETE',
|
|
1326
|
+
'DELIVERY_FAILED', 'RESOURCE_EXPIRED',
|
|
1327
|
+
'RECEIPT_REQUIRED', 'RECEIPT_REJECTED',
|
|
1328
|
+
'ADJUSTMENT_RECEIPT_REQUIRED', 'ADJUSTMENT_RECEIPT_REJECTED'
|
|
1329
|
+
)
|
|
1139
1330
|
AND NOT (issue_id = ANY($3::text[]))`,
|
|
1140
1331
|
[input.reporting_obligation_id, input.ledgerAsOf, projectedIds]
|
|
1141
1332
|
);
|
|
@@ -1152,6 +1343,7 @@ class PostgresReportingLedgerStore {
|
|
|
1152
1343
|
await client.query(
|
|
1153
1344
|
`UPDATE adcp_reporting_transitions
|
|
1154
1345
|
SET data = data || jsonb_build_object('notifiedAt', $2::text)
|
|
1346
|
+
|| CASE WHEN data ? 'finality' THEN '{}'::jsonb ELSE jsonb_build_object('finality', 'none') END
|
|
1155
1347
|
WHERE transition_id = $1 AND NOT (data ? 'notifiedAt')`,
|
|
1156
1348
|
[transitionId, notifiedAt]
|
|
1157
1349
|
);
|
|
@@ -1209,11 +1401,31 @@ class PostgresReportingLedgerStore {
|
|
|
1209
1401
|
}
|
|
1210
1402
|
const activeSnapshotBytes = Number(active.rows[0]?.bytes ?? 0);
|
|
1211
1403
|
const changesAfter = query.changes_after ? await this.resolveChangesCheckpoint(client, query, query.changes_after) : void 0;
|
|
1404
|
+
const managedInstalled = this.managedDelivery && await this.managedTablesInstalled(client);
|
|
1212
1405
|
const configurations = await this.listSnapshotConfigurations(client, query, ledgerAsOf);
|
|
1213
1406
|
if (configurations.length > MAX_SNAPSHOT_ITEMS) {
|
|
1214
1407
|
throw new Error("Reporting ledger snapshot exceeds the configuration limit");
|
|
1215
1408
|
}
|
|
1216
1409
|
let obligations = await this.listSnapshotObligations(client, query, ledgerAsOf, changesAfter);
|
|
1410
|
+
if (managedInstalled && changesAfter) {
|
|
1411
|
+
const managedChangedIds = await this.listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter);
|
|
1412
|
+
if (managedChangedIds.size) {
|
|
1413
|
+
const fullScope = await this.listSnapshotObligations(
|
|
1414
|
+
client,
|
|
1415
|
+
{ ...query, changes_after: void 0, health: void 0 },
|
|
1416
|
+
ledgerAsOf
|
|
1417
|
+
);
|
|
1418
|
+
const byId = new Map(obligations.map((value) => [value.reporting_obligation_id, value]));
|
|
1419
|
+
for (const obligation of fullScope) {
|
|
1420
|
+
if (managedChangedIds.has(obligation.reporting_obligation_id)) {
|
|
1421
|
+
byId.set(obligation.reporting_obligation_id, obligation);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
obligations = [...byId.values()].sort(
|
|
1425
|
+
(left, right) => left.period.start.localeCompare(right.period.start) || left.reporting_obligation_id.localeCompare(right.reporting_obligation_id)
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1217
1429
|
if (obligations.length > MAX_SNAPSHOT_ITEMS) {
|
|
1218
1430
|
throw new Error("Reporting ledger snapshot exceeds the item limit");
|
|
1219
1431
|
}
|
|
@@ -1265,6 +1477,30 @@ class PostgresReportingLedgerStore {
|
|
|
1265
1477
|
throw new Error("Reporting ledger snapshot exceeds the item limit");
|
|
1266
1478
|
}
|
|
1267
1479
|
let issues = await this.listSnapshotIssues(client, obligationIds, MAX_SNAPSHOT_ITEMS + 1);
|
|
1480
|
+
const managedBindings = managedInstalled ? await this.listSnapshotManagedBindings(
|
|
1481
|
+
client,
|
|
1482
|
+
configurations.map((value) => value.configurationId)
|
|
1483
|
+
) : [];
|
|
1484
|
+
let materializations = managedInstalled ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) : [];
|
|
1485
|
+
let receipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "revision") : [];
|
|
1486
|
+
let adjustmentReceipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "adjustment") : [];
|
|
1487
|
+
const tombstonedAcceptedSubjects = managedInstalled ? (await this.listTombstonedAcceptedSubjects(client, query, obligationIds, false, ledgerAsOf)).subjects : [];
|
|
1488
|
+
const tombstonedDeliveredRevisionIds = managedInstalled ? (await client.query(
|
|
1489
|
+
`SELECT revision_id FROM adcp_reporting_materialization_tombstones
|
|
1490
|
+
WHERE obligation_id = ANY($1::text[]) AND reached_success
|
|
1491
|
+
AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
|
|
1492
|
+
[obligationIds, MAX_SNAPSHOT_ITEMS + 1, ledgerAsOf]
|
|
1493
|
+
)).rows.map((row) => row.revision_id) : [];
|
|
1494
|
+
const materializationProjection = managedInstalled ? await this.listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) : [];
|
|
1495
|
+
const materializationHistoryProjection = managedInstalled && changesAfter ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf) : materializations;
|
|
1496
|
+
const receiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "revision") : receipts;
|
|
1497
|
+
const adjustmentReceiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "adjustment") : adjustmentReceipts;
|
|
1498
|
+
if (materializationProjection.length > MAX_SNAPSHOT_ITEMS || materializationHistoryProjection.length > MAX_SNAPSHOT_ITEMS || receiptProjection.length > MAX_SNAPSHOT_ITEMS || adjustmentReceiptProjection.length > MAX_SNAPSHOT_ITEMS) {
|
|
1499
|
+
throw new Error("Reporting ledger snapshot exceeds the managed projection limit");
|
|
1500
|
+
}
|
|
1501
|
+
if (obligations.length + revisions.length + adjustments.length + consumerStatuses.length + materializations.length + receipts.length + adjustmentReceipts.length > MAX_SNAPSHOT_ITEMS) {
|
|
1502
|
+
throw new Error("Reporting ledger snapshot exceeds the item limit");
|
|
1503
|
+
}
|
|
1268
1504
|
if (issues.length > MAX_SNAPSHOT_ITEMS) throw new Error("Reporting ledger snapshot exceeds the issue limit");
|
|
1269
1505
|
if (query.view === "periods" && query.health) {
|
|
1270
1506
|
const scopedObligations = obligations;
|
|
@@ -1289,7 +1525,34 @@ class PostgresReportingLedgerStore {
|
|
|
1289
1525
|
ledgerAsOf,
|
|
1290
1526
|
this.consumerMismatchEscalation
|
|
1291
1527
|
);
|
|
1292
|
-
|
|
1528
|
+
const baseProjection = (0, import_health.projectReportingObligationHealthV1)(
|
|
1529
|
+
value,
|
|
1530
|
+
obligationRevisions,
|
|
1531
|
+
ledgerAsOf,
|
|
1532
|
+
(0, import_coverage.reportingLedgerScopeClosed)(query, ledgerAsOf, ledgerCoverage.complete)
|
|
1533
|
+
);
|
|
1534
|
+
const managed = (0, import_handler.projectManagedDelivery)({
|
|
1535
|
+
obligation: value,
|
|
1536
|
+
binding: managedBindings.find((binding) => binding.configurationId === value.configurationId),
|
|
1537
|
+
revisions: obligationRevisions,
|
|
1538
|
+
adjustments: adjustments.filter(
|
|
1539
|
+
(item) => item.reporting_obligation_id === value.reporting_obligation_id
|
|
1540
|
+
),
|
|
1541
|
+
materializations: materializationProjection.filter(
|
|
1542
|
+
(item) => item.reporting_obligation_id === value.reporting_obligation_id
|
|
1543
|
+
),
|
|
1544
|
+
materializationHistory: materializationHistoryProjection.filter(
|
|
1545
|
+
(item) => item.reporting_obligation_id === value.reporting_obligation_id
|
|
1546
|
+
),
|
|
1547
|
+
receipts: receiptProjection,
|
|
1548
|
+
adjustmentReceipts: adjustmentReceiptProjection,
|
|
1549
|
+
base: baseProjection,
|
|
1550
|
+
ledgerAsOf,
|
|
1551
|
+
tombstonedAcceptedSubjects,
|
|
1552
|
+
tombstonedDeliveredRevisionIds
|
|
1553
|
+
});
|
|
1554
|
+
const health = mismatch ? (0, import_handler.moreSevereReportingHealthV1)(managed?.projection.health ?? sellerHealth, mismatch.health) : managed?.projection.health ?? sellerHealth;
|
|
1555
|
+
return query.health.includes(health);
|
|
1293
1556
|
}).map((value) => value.reporting_obligation_id)
|
|
1294
1557
|
);
|
|
1295
1558
|
const obligationsByConsumerStatusChain = new Map(
|
|
@@ -1305,6 +1568,13 @@ class PostgresReportingLedgerStore {
|
|
|
1305
1568
|
obligations = obligations.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1306
1569
|
revisions = revisions.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1307
1570
|
adjustments = adjustments.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1571
|
+
materializations = materializations.filter((value) => accepted.has(value.reporting_obligation_id));
|
|
1572
|
+
const acceptedRevisionIds = new Set(revisions.map((value) => value.reporting_revision_id));
|
|
1573
|
+
const acceptedAdjustmentIds = new Set(adjustments.map((value) => value.reporting_adjustment_id));
|
|
1574
|
+
receipts = receipts.filter((value) => acceptedRevisionIds.has(value.reporting_revision_id));
|
|
1575
|
+
adjustmentReceipts = adjustmentReceipts.filter(
|
|
1576
|
+
(value) => acceptedAdjustmentIds.has(value.reporting_adjustment_id)
|
|
1577
|
+
);
|
|
1308
1578
|
consumerStatuses = consumerStatuses.filter((value) => {
|
|
1309
1579
|
const key = (0, import_consumer_status_identity.reportingConsumerStatusChainKeyV1)(value);
|
|
1310
1580
|
const matchingObligation = obligationsByConsumerStatusChain.get(key);
|
|
@@ -1329,6 +1599,18 @@ class PostgresReportingLedgerStore {
|
|
|
1329
1599
|
obligations,
|
|
1330
1600
|
revisions,
|
|
1331
1601
|
adjustments,
|
|
1602
|
+
...managedInstalled ? {
|
|
1603
|
+
tombstonedAcceptedSubjects,
|
|
1604
|
+
tombstonedDeliveredRevisionIds,
|
|
1605
|
+
managedBindings,
|
|
1606
|
+
materializations,
|
|
1607
|
+
materializationHistoryProjection,
|
|
1608
|
+
materializationProjection,
|
|
1609
|
+
receipts,
|
|
1610
|
+
receiptProjection,
|
|
1611
|
+
adjustmentReceipts,
|
|
1612
|
+
adjustmentReceiptProjection
|
|
1613
|
+
} : {},
|
|
1332
1614
|
consumerStatuses,
|
|
1333
1615
|
consumerStatusProjection,
|
|
1334
1616
|
issues
|
|
@@ -1398,6 +1680,13 @@ class PostgresReportingLedgerStore {
|
|
|
1398
1680
|
const consumerStatuses = selected.filter(
|
|
1399
1681
|
(value) => value.kind === "consumer_status"
|
|
1400
1682
|
).map((value) => value.value);
|
|
1683
|
+
const materializations = selected.filter(
|
|
1684
|
+
(value) => value.kind === "materialization"
|
|
1685
|
+
).map((value) => value.value);
|
|
1686
|
+
const receipts = selected.filter((value) => value.kind === "receipt").map((value) => value.value);
|
|
1687
|
+
const adjustmentReceipts = selected.filter(
|
|
1688
|
+
(value) => value.kind === "adjustment_receipt"
|
|
1689
|
+
).map((value) => value.value);
|
|
1401
1690
|
const nextOffset = offset + selected.length;
|
|
1402
1691
|
const hasMore = nextOffset < items.length;
|
|
1403
1692
|
return {
|
|
@@ -1405,6 +1694,9 @@ class PostgresReportingLedgerStore {
|
|
|
1405
1694
|
obligations,
|
|
1406
1695
|
revisions,
|
|
1407
1696
|
adjustments,
|
|
1697
|
+
materializations,
|
|
1698
|
+
receipts,
|
|
1699
|
+
adjustmentReceipts,
|
|
1408
1700
|
consumerStatuses,
|
|
1409
1701
|
totalCount: items.length,
|
|
1410
1702
|
offset,
|
|
@@ -1584,11 +1876,614 @@ class PostgresReportingLedgerStore {
|
|
|
1584
1876
|
);
|
|
1585
1877
|
return result.rows.map((row) => row.data);
|
|
1586
1878
|
}
|
|
1879
|
+
/**
|
|
1880
|
+
* Managed Delivery projection inputs for one obligation, grouped per consumer.
|
|
1881
|
+
*
|
|
1882
|
+
* The lifecycle reconciler folds these through the same
|
|
1883
|
+
* `projectManagedDelivery` the read path uses, so a persisted transition and
|
|
1884
|
+
* its webhook report the health a read of the same obligation would return.
|
|
1885
|
+
* Returns `null` when Managed Delivery is not installed or the obligation's
|
|
1886
|
+
* configuration has no binding, which keeps a Core-only ledger unchanged.
|
|
1887
|
+
*/
|
|
1888
|
+
async getManagedLifecycleProjection(input) {
|
|
1889
|
+
if (!this.managedDelivery) return null;
|
|
1890
|
+
const base = await this.transaction(
|
|
1891
|
+
async (client) => {
|
|
1892
|
+
if (!await this.managedTablesInstalled(client)) return null;
|
|
1893
|
+
const obligation = await client.query(
|
|
1894
|
+
"SELECT configuration_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
|
|
1895
|
+
[input.reporting_obligation_id]
|
|
1896
|
+
);
|
|
1897
|
+
const configurationId = obligation.rows[0]?.configuration_id;
|
|
1898
|
+
if (!configurationId) return null;
|
|
1899
|
+
const [binding] = await this.listSnapshotManagedBindings(client, [configurationId]);
|
|
1900
|
+
if (!binding) return null;
|
|
1901
|
+
const resolvedLedgerAsOf = (await client.query(
|
|
1902
|
+
// RFC 3339 with microseconds. PostgreSQL's own ::text rendering
|
|
1903
|
+
// uses a space separator and a two-digit offset, which the SDK's
|
|
1904
|
+
// instant parser rejects, and round-tripping through a JS Date
|
|
1905
|
+
// would truncate the microseconds this exists to preserve.
|
|
1906
|
+
`SELECT ${rfc3339Microseconds("COALESCE($1::timestamptz, clock_timestamp())")} AS instant`,
|
|
1907
|
+
[input.ledgerAsOf ?? null]
|
|
1908
|
+
)).rows[0].instant;
|
|
1909
|
+
const rows = await client.query(
|
|
1910
|
+
`SELECT materialization.data,
|
|
1911
|
+
materialization.changed_at > $2::timestamptz AS settled_after_cutoff,
|
|
1912
|
+
CASE WHEN authz.revoked_at <= $2::timestamptz THEN authz.revoked_at END AS revoked_at
|
|
1913
|
+
FROM adcp_reporting_materializations materialization
|
|
1914
|
+
JOIN adcp_reporting_destination_authorizations authz
|
|
1915
|
+
ON authz.account_id = materialization.account_id
|
|
1916
|
+
AND authz.destination_ref = materialization.destination_ref
|
|
1917
|
+
AND authz.generation = materialization.authorization_generation
|
|
1918
|
+
WHERE materialization.obligation_id = $1
|
|
1919
|
+
AND materialization.recorded_at <= $2::timestamptz
|
|
1920
|
+
ORDER BY materialization.attempt, materialization.materialization_id
|
|
1921
|
+
LIMIT $3`,
|
|
1922
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
1923
|
+
);
|
|
1924
|
+
if (rows.rows.length > MAX_SNAPSHOT_ITEMS) {
|
|
1925
|
+
throw new Error("Reporting lifecycle projection exceeds the managed materialization limit");
|
|
1926
|
+
}
|
|
1927
|
+
const placed = rows.rows.map((row) => ({
|
|
1928
|
+
atAsOf: row.settled_after_cutoff ? pendingAtCutoff(row.data) : clone(row.data),
|
|
1929
|
+
revokedAt: row.revoked_at
|
|
1930
|
+
}));
|
|
1931
|
+
const materializationHistory = placed.map((value) => value.atAsOf);
|
|
1932
|
+
const materializations = placed.map(
|
|
1933
|
+
({ atAsOf, revokedAt }) => revokedAt && (atAsOf.status === "available" || atAsOf.status === "delivered") ? {
|
|
1934
|
+
...atAsOf,
|
|
1935
|
+
status: "failed",
|
|
1936
|
+
failed_at: revokedAt.toISOString(),
|
|
1937
|
+
failure_code: "AUTHORIZATION_REVOKED"
|
|
1938
|
+
} : atAsOf
|
|
1939
|
+
);
|
|
1940
|
+
const receipts = await client.query(
|
|
1941
|
+
`WITH subject AS (
|
|
1942
|
+
-- Drive from this obligation's own subjects. Asking the global
|
|
1943
|
+
-- receipt table "which of your rows belong to this obligation"
|
|
1944
|
+
-- matched no index \u2014 they lead with account and consumer, and
|
|
1945
|
+
-- this question supplies neither \u2014 so every due obligation
|
|
1946
|
+
-- re-scanned the whole receipt history.
|
|
1947
|
+
SELECT revision_id AS subject_id, 'revision' AS receipt_kind
|
|
1948
|
+
FROM adcp_reporting_revisions WHERE obligation_id = $1
|
|
1949
|
+
UNION ALL
|
|
1950
|
+
SELECT adjustment_id, 'adjustment'
|
|
1951
|
+
FROM adcp_reporting_adjustments WHERE obligation_id = $1
|
|
1952
|
+
)
|
|
1953
|
+
SELECT receipt.consumer_id, receipt.receipt_kind, receipt.data
|
|
1954
|
+
FROM subject
|
|
1955
|
+
JOIN adcp_reporting_receipts receipt
|
|
1956
|
+
ON receipt.subject_id = subject.subject_id
|
|
1957
|
+
AND receipt.receipt_kind = subject.receipt_kind
|
|
1958
|
+
-- Ordered and cut off by the database clock, exactly as every other
|
|
1959
|
+
-- receipt read path is, so a skewed host cannot make the lifecycle
|
|
1960
|
+
-- projection see a different receipt set than get_reporting_status.
|
|
1961
|
+
--
|
|
1962
|
+
-- One leaf per chain, resolved AS OF the cutoff. The fold reads
|
|
1963
|
+
-- exactly one thing from each chain, and walking the whole history
|
|
1964
|
+
-- to find it pushed this read past its bound for a subject repaired
|
|
1965
|
+
-- often enough. But is_current is today's answer: at a cutoff
|
|
1966
|
+
-- before an acceptance superseded a rejection, the rejection was no
|
|
1967
|
+
-- longer current and the acceptance was not yet in scope, so the
|
|
1968
|
+
-- projection saw neither and persisted RECEIPT_REQUIRED over a
|
|
1969
|
+
-- rejection the buyer had already filed. The leaf at the cutoff is
|
|
1970
|
+
-- the receipt that nothing recorded by then supersedes. Wire
|
|
1971
|
+
-- counters are computed on the read path, which still sees every
|
|
1972
|
+
-- row.
|
|
1973
|
+
WHERE receipt.recorded_at <= $2
|
|
1974
|
+
AND NOT EXISTS (
|
|
1975
|
+
SELECT 1 FROM adcp_reporting_receipts successor
|
|
1976
|
+
WHERE successor.account_id = receipt.account_id
|
|
1977
|
+
AND successor.consumer_id = receipt.consumer_id
|
|
1978
|
+
AND successor.supersedes_receipt_id = receipt.reporting_receipt_id
|
|
1979
|
+
AND successor.recorded_at <= $2
|
|
1980
|
+
)
|
|
1981
|
+
-- A successor whose body has been pruned still superseded this
|
|
1982
|
+
-- row. Counting only live successors let retention resurrect the
|
|
1983
|
+
-- rejected predecessor of a pruned acceptance as the live leaf,
|
|
1984
|
+
-- which overrode that acceptance's own tombstone and reopened a
|
|
1985
|
+
-- settled subject the write path then refused to repair.
|
|
1986
|
+
AND NOT EXISTS (
|
|
1987
|
+
SELECT 1 FROM adcp_reporting_receipt_tombstones gone
|
|
1988
|
+
WHERE gone.account_id = receipt.account_id
|
|
1989
|
+
AND gone.consumer_id = receipt.consumer_id
|
|
1990
|
+
AND gone.supersedes_receipt_id = receipt.reporting_receipt_id
|
|
1991
|
+
AND COALESCE(gone.subject_recorded_at, gone.pruned_at) <= $2
|
|
1992
|
+
)
|
|
1993
|
+
ORDER BY receipt.consumer_id, receipt.subject_id, receipt.recorded_at, receipt.reporting_receipt_id
|
|
1994
|
+
LIMIT $3`,
|
|
1995
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_LIFECYCLE_RECEIPT_LEAVES + 1]
|
|
1996
|
+
);
|
|
1997
|
+
let receiptEvidenceComplete = true;
|
|
1998
|
+
let leaves = receipts.rows;
|
|
1999
|
+
if (leaves.length > MAX_LIFECYCLE_RECEIPT_LEAVES) {
|
|
2000
|
+
receiptEvidenceComplete = false;
|
|
2001
|
+
const partial = leaves[MAX_LIFECYCLE_RECEIPT_LEAVES].consumer_id;
|
|
2002
|
+
leaves = leaves.slice(0, MAX_LIFECYCLE_RECEIPT_LEAVES).filter((row) => row.consumer_id !== partial);
|
|
2003
|
+
}
|
|
2004
|
+
const byConsumer = /* @__PURE__ */ new Map();
|
|
2005
|
+
for (const row of leaves) {
|
|
2006
|
+
const consumer = byConsumer.get(row.consumer_id) ?? {
|
|
2007
|
+
consumer_id: row.consumer_id,
|
|
2008
|
+
receipts: [],
|
|
2009
|
+
adjustmentReceipts: []
|
|
2010
|
+
};
|
|
2011
|
+
if (row.receipt_kind === "revision") consumer.receipts.push(clone(row.data));
|
|
2012
|
+
else consumer.adjustmentReceipts.push(clone(row.data));
|
|
2013
|
+
byConsumer.set(row.consumer_id, consumer);
|
|
2014
|
+
}
|
|
2015
|
+
const engaged = await client.query(
|
|
2016
|
+
`SELECT DISTINCT consumer_id FROM adcp_reporting_consumer_statuses
|
|
2017
|
+
WHERE obligation_id = $1 AND consumer_id <> '__legacy_unscoped_consumer__'
|
|
2018
|
+
ORDER BY consumer_id LIMIT $2`,
|
|
2019
|
+
[input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1]
|
|
2020
|
+
);
|
|
2021
|
+
if (engaged.rows.length > MAX_SNAPSHOT_ITEMS) {
|
|
2022
|
+
receiptEvidenceComplete = false;
|
|
2023
|
+
engaged.rows = engaged.rows.slice(0, MAX_SNAPSHOT_ITEMS);
|
|
2024
|
+
}
|
|
2025
|
+
const observed = [.../* @__PURE__ */ new Set([...byConsumer.keys(), ...engaged.rows.map((row) => row.consumer_id)])].sort();
|
|
2026
|
+
const tombstoned = await this.listTombstonedAcceptedSubjects(
|
|
2027
|
+
client,
|
|
2028
|
+
{ account_id: binding.account_id, consumer_id: void 0, view: "periods" },
|
|
2029
|
+
[input.reporting_obligation_id],
|
|
2030
|
+
true,
|
|
2031
|
+
resolvedLedgerAsOf
|
|
2032
|
+
);
|
|
2033
|
+
if (!tombstoned.complete) receiptEvidenceComplete = false;
|
|
2034
|
+
const visible = await client.query(
|
|
2035
|
+
`SELECT adjustment_id FROM adcp_reporting_adjustments
|
|
2036
|
+
WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
|
|
2037
|
+
ORDER BY adjustment_number, adjustment_id LIMIT $3`,
|
|
2038
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
2039
|
+
);
|
|
2040
|
+
if (visible.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
|
|
2041
|
+
const visibleAdjustmentIds = visible.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.adjustment_id);
|
|
2042
|
+
const visibleRevisions = await client.query(
|
|
2043
|
+
`SELECT revision_id FROM adcp_reporting_revisions
|
|
2044
|
+
WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
|
|
2045
|
+
ORDER BY revision_number, revision_id LIMIT $3`,
|
|
2046
|
+
[input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
2047
|
+
);
|
|
2048
|
+
if (visibleRevisions.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
|
|
2049
|
+
const visibleRevisionIds = visibleRevisions.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
|
|
2050
|
+
const tombstonedAcceptedSubjects = tombstoned.subjects;
|
|
2051
|
+
const delivered = await client.query(
|
|
2052
|
+
`SELECT revision_id FROM adcp_reporting_materialization_tombstones
|
|
2053
|
+
WHERE obligation_id = $1 AND reached_success
|
|
2054
|
+
AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
|
|
2055
|
+
[input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1, resolvedLedgerAsOf]
|
|
2056
|
+
);
|
|
2057
|
+
if (delivered.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
|
|
2058
|
+
const tombstonedDeliveredRevisionIds = delivered.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
|
|
2059
|
+
const managedStateVersion = await this.readManagedStateVersion(client, input.reporting_obligation_id);
|
|
2060
|
+
return {
|
|
2061
|
+
binding,
|
|
2062
|
+
materializations,
|
|
2063
|
+
materializationHistory,
|
|
2064
|
+
consumers: [...byConsumer.values()],
|
|
2065
|
+
obligatedConsumerIds: observed,
|
|
2066
|
+
obligatedConsumerRosterComplete: false,
|
|
2067
|
+
receiptEvidenceComplete,
|
|
2068
|
+
visibleAdjustmentIds,
|
|
2069
|
+
visibleRevisionIds,
|
|
2070
|
+
managedStateVersion,
|
|
2071
|
+
resolvedLedgerAsOf,
|
|
2072
|
+
tombstonedAcceptedSubjects,
|
|
2073
|
+
tombstonedDeliveredRevisionIds
|
|
2074
|
+
};
|
|
2075
|
+
},
|
|
2076
|
+
{ isolation: "REPEATABLE READ" }
|
|
2077
|
+
);
|
|
2078
|
+
if (!base || !this.obligatedConsumers) return base;
|
|
2079
|
+
const supplied = await withReportingCallbackDeadline(
|
|
2080
|
+
this.obligatedConsumers({
|
|
2081
|
+
reporting_obligation_id: input.reporting_obligation_id,
|
|
2082
|
+
account_id: base.binding.account_id
|
|
2083
|
+
}),
|
|
2084
|
+
OBLIGATED_CONSUMERS_DEADLINE_MS,
|
|
2085
|
+
"Reporting obligated-consumer lookup deadline elapsed"
|
|
2086
|
+
);
|
|
2087
|
+
const all = obligatedConsumerIdsFor(supplied, base.obligatedConsumerIds ?? []);
|
|
2088
|
+
const identityIds = supplied.version === void 0 && supplied.complete !== true ? obligatedConsumerIdsFor(supplied, await this.observedConsumerIds(input.reporting_obligation_id)) : all;
|
|
2089
|
+
const ids = all.slice(0, MAX_SNAPSHOT_ITEMS);
|
|
2090
|
+
const rosterComplete = supplied.complete === true && all.length === ids.length;
|
|
2091
|
+
return {
|
|
2092
|
+
...base,
|
|
2093
|
+
obligatedConsumerIds: ids,
|
|
2094
|
+
obligatedConsumerRosterComplete: rosterComplete,
|
|
2095
|
+
...all.length === ids.length ? {} : { receiptEvidenceComplete: false },
|
|
2096
|
+
obligatedConsumerRosterVersion: obligatedConsumerRosterVersionFor(supplied, identityIds)
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Digest over every managed row that can change this obligation's projected
|
|
2101
|
+
* health: materialization status, receipt existence and current-leaf flag,
|
|
2102
|
+
* adjustments, and the destination authorization's revocation/cleanup state.
|
|
2103
|
+
*
|
|
2104
|
+
* `is_current` is included because superseding a receipt flips it with no
|
|
2105
|
+
* timestamp of its own, and the authorization's `cleanup_completed_at` is
|
|
2106
|
+
* included because `completeRevocation` does not touch `changed_at`.
|
|
2107
|
+
*/
|
|
2108
|
+
async readManagedStateVersion(client, obligationId) {
|
|
2109
|
+
const result = await client.query(
|
|
2110
|
+
`SELECT md5(COALESCE(string_agg(marker, '|' ORDER BY marker), '')) AS version FROM (
|
|
2111
|
+
SELECT 'm:' || materialization_id || ':' || status || ':'
|
|
2112
|
+
|| EXTRACT(EPOCH FROM changed_at)::text AS marker
|
|
2113
|
+
FROM adcp_reporting_materializations WHERE obligation_id = $1
|
|
2114
|
+
UNION ALL
|
|
2115
|
+
-- Driven from this obligation's subjects into the receipt subject
|
|
2116
|
+
-- index. This digest is read while the apply holds the account
|
|
2117
|
+
-- lock, so asking the global receipt table which of its rows belong
|
|
2118
|
+
-- to an obligation -- a question supplying neither account nor
|
|
2119
|
+
-- consumer -- turned every apply into a full scan with that lock
|
|
2120
|
+
-- held, and concurrent Core writes failed with 55P03.
|
|
2121
|
+
SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
|
|
2122
|
+
|| EXTRACT(EPOCH FROM receipt.recorded_at)::text
|
|
2123
|
+
FROM adcp_reporting_revisions revision
|
|
2124
|
+
JOIN adcp_reporting_receipts receipt
|
|
2125
|
+
ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
|
|
2126
|
+
WHERE revision.obligation_id = $1
|
|
2127
|
+
UNION ALL
|
|
2128
|
+
SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
|
|
2129
|
+
|| EXTRACT(EPOCH FROM receipt.recorded_at)::text
|
|
2130
|
+
FROM adcp_reporting_adjustments adjustment
|
|
2131
|
+
JOIN adcp_reporting_receipts receipt
|
|
2132
|
+
ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
|
|
2133
|
+
WHERE adjustment.obligation_id = $1
|
|
2134
|
+
UNION ALL
|
|
2135
|
+
SELECT 'a:' || adjustment_id || ':' || EXTRACT(EPOCH FROM recorded_at)::text
|
|
2136
|
+
FROM adcp_reporting_adjustments WHERE obligation_id = $1
|
|
2137
|
+
UNION ALL
|
|
2138
|
+
-- Consumer statuses are a health input through the mismatch
|
|
2139
|
+
-- projection and they widen the observed roster, so a status landing
|
|
2140
|
+
-- between projection and apply has to move the token too.
|
|
2141
|
+
SELECT 'c:' || status.consumer_id || ':' || status.consumer_status_id || ':'
|
|
2142
|
+
|| status.is_current::text || ':' || EXTRACT(EPOCH FROM status.created_at)::text
|
|
2143
|
+
FROM adcp_reporting_consumer_statuses status
|
|
2144
|
+
WHERE status.obligation_id = $1
|
|
2145
|
+
UNION ALL
|
|
2146
|
+
SELECT 'z:' || authz.generation::text
|
|
2147
|
+
|| ':' || COALESCE(EXTRACT(EPOCH FROM authz.revoked_at)::text, '')
|
|
2148
|
+
|| ':' || COALESCE(EXTRACT(EPOCH FROM authz.cleanup_completed_at)::text, '')
|
|
2149
|
+
|| ':' || EXTRACT(EPOCH FROM authz.changed_at)::text
|
|
2150
|
+
FROM adcp_reporting_destination_authorizations authz
|
|
2151
|
+
JOIN adcp_reporting_managed_bindings binding
|
|
2152
|
+
ON binding.account_id = authz.account_id AND binding.destination_ref = authz.destination_ref
|
|
2153
|
+
JOIN adcp_reporting_obligations obligation
|
|
2154
|
+
ON obligation.configuration_id = binding.configuration_id
|
|
2155
|
+
WHERE obligation.obligation_id = $1
|
|
2156
|
+
) markers`,
|
|
2157
|
+
[obligationId]
|
|
2158
|
+
);
|
|
2159
|
+
return result.rows[0]?.version ?? "";
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Current external roster version, read outside any transaction so the
|
|
2163
|
+
* reconciler can re-check it immediately before an apply.
|
|
2164
|
+
*/
|
|
2165
|
+
/**
|
|
2166
|
+
* Publishes the roster version currently observed for an obligation.
|
|
2167
|
+
*
|
|
2168
|
+
* The roster is external, so nothing in this database changes when it does.
|
|
2169
|
+
* Recording what a reader saw is what lets the due query notice drift from
|
|
2170
|
+
* the version last reconciled. Never lowers a watermark; it only reports.
|
|
2171
|
+
*/
|
|
2172
|
+
async recordObligatedConsumerRosterVersion(input) {
|
|
2173
|
+
await this.query(
|
|
2174
|
+
`INSERT INTO adcp_reporting_lifecycle_state
|
|
2175
|
+
(obligation_id, current_roster_version, roster_refreshed_at, processed_at)
|
|
2176
|
+
VALUES ($1, $2, clock_timestamp(), clock_timestamp())
|
|
2177
|
+
ON CONFLICT (obligation_id) DO UPDATE SET
|
|
2178
|
+
current_roster_version = EXCLUDED.current_roster_version,
|
|
2179
|
+
-- Advances the refresh cursor, never the reconciliation watermark:
|
|
2180
|
+
-- observing a roster is not reconciling an obligation.
|
|
2181
|
+
roster_refreshed_at = clock_timestamp()`,
|
|
2182
|
+
[input.reporting_obligation_id, input.version]
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Re-reads the external roster for managed obligations and publishes what
|
|
2187
|
+
* it saw, so a change made while nothing was scheduled can re-arm.
|
|
2188
|
+
*
|
|
2189
|
+
* Ordered by least-recently-refreshed and bounded, so a large account is
|
|
2190
|
+
* swept over successive passes rather than in one burst of callbacks.
|
|
2191
|
+
*/
|
|
2192
|
+
async refreshObligatedConsumerRosterVersions(input) {
|
|
2193
|
+
if (!this.managedDelivery || !this.obligatedConsumers) return 0;
|
|
2194
|
+
positiveInteger(input.limit, "limit");
|
|
2195
|
+
if (!await this.managedDueTablesReady()) return 0;
|
|
2196
|
+
const budget = Math.min(input.limit, MAX_ROSTER_REFRESH_PER_SWEEP);
|
|
2197
|
+
const candidates = await this.query(
|
|
2198
|
+
`SELECT obligation.obligation_id
|
|
2199
|
+
FROM adcp_reporting_obligations obligation
|
|
2200
|
+
JOIN adcp_reporting_managed_bindings binding
|
|
2201
|
+
ON binding.configuration_id = obligation.configuration_id
|
|
2202
|
+
LEFT JOIN adcp_reporting_lifecycle_state state
|
|
2203
|
+
ON state.obligation_id = obligation.obligation_id
|
|
2204
|
+
WHERE ($1::text IS NULL OR obligation.account_id = $1)
|
|
2205
|
+
-- Its own cursor, advanced by the refresh itself. Ordering by the
|
|
2206
|
+
-- reconciliation watermark, which a refresh never moves, meant every
|
|
2207
|
+
-- sweep re-read the same first page and nothing beyond one page of quiet
|
|
2208
|
+
-- obligations was ever refreshed, so their roster changes could never
|
|
2209
|
+
-- re-arm.
|
|
2210
|
+
ORDER BY state.roster_refreshed_at NULLS FIRST, obligation.obligation_id
|
|
2211
|
+
LIMIT $2`,
|
|
2212
|
+
[input.account_id ?? null, budget]
|
|
2213
|
+
);
|
|
2214
|
+
let refreshed = 0;
|
|
2215
|
+
for (const row of candidates.rows) {
|
|
2216
|
+
try {
|
|
2217
|
+
await this.readObligatedConsumerRosterVersion({ reporting_obligation_id: row.obligation_id });
|
|
2218
|
+
refreshed += 1;
|
|
2219
|
+
} catch {
|
|
2220
|
+
await this.query(
|
|
2221
|
+
`INSERT INTO adcp_reporting_lifecycle_state (obligation_id, roster_refreshed_at, processed_at)
|
|
2222
|
+
VALUES ($1, clock_timestamp(), clock_timestamp())
|
|
2223
|
+
ON CONFLICT (obligation_id) DO UPDATE SET roster_refreshed_at = clock_timestamp()`,
|
|
2224
|
+
[row.obligation_id]
|
|
2225
|
+
).catch(() => void 0);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
return refreshed;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Records a failed reconcile so the obligation backs off instead of
|
|
2232
|
+
* re-occupying the head of every sweep, without ever hiding it: the
|
|
2233
|
+
* watermark is untouched, so it stays unresolved work.
|
|
2234
|
+
*/
|
|
2235
|
+
async recordLifecycleFailure(input) {
|
|
2236
|
+
await this.query(
|
|
2237
|
+
`INSERT INTO adcp_reporting_lifecycle_state (obligation_id, failure_count, next_attempt_at, processed_at)
|
|
2238
|
+
VALUES ($1, 1, clock_timestamp() + INTERVAL '30 seconds', clock_timestamp())
|
|
2239
|
+
ON CONFLICT (obligation_id) DO UPDATE SET
|
|
2240
|
+
failure_count = adcp_reporting_lifecycle_state.failure_count + 1,
|
|
2241
|
+
next_attempt_at = clock_timestamp() + (LEAST(
|
|
2242
|
+
${MAX_LIFECYCLE_BACKOFF_SECONDS}, 30 * POWER(2, LEAST(6, adcp_reporting_lifecycle_state.failure_count))
|
|
2243
|
+
) * INTERVAL '1 second')`,
|
|
2244
|
+
[input.reporting_obligation_id]
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
/** Authoritative instant from the database clock, at microsecond precision. */
|
|
2248
|
+
async readLedgerInstant() {
|
|
2249
|
+
const result = await this.query(
|
|
2250
|
+
`SELECT ${rfc3339Microseconds("clock_timestamp()")} AS instant`
|
|
2251
|
+
);
|
|
2252
|
+
return result.rows[0].instant;
|
|
2253
|
+
}
|
|
2254
|
+
async readObligatedConsumerRosterVersion(input) {
|
|
2255
|
+
if (!this.managedDelivery || !this.obligatedConsumers) return void 0;
|
|
2256
|
+
const accountId = await this.query(
|
|
2257
|
+
"SELECT account_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
|
|
2258
|
+
[input.reporting_obligation_id]
|
|
2259
|
+
);
|
|
2260
|
+
const account_id = accountId.rows[0]?.account_id;
|
|
2261
|
+
if (!account_id) return void 0;
|
|
2262
|
+
const supplied = await withReportingCallbackDeadline(
|
|
2263
|
+
this.obligatedConsumers({ reporting_obligation_id: input.reporting_obligation_id, account_id }),
|
|
2264
|
+
OBLIGATED_CONSUMERS_DEADLINE_MS,
|
|
2265
|
+
"Reporting obligated-consumer lookup deadline elapsed"
|
|
2266
|
+
);
|
|
2267
|
+
const observed = await this.observedConsumerIds(input.reporting_obligation_id);
|
|
2268
|
+
const version = obligatedConsumerRosterVersionFor(supplied, obligatedConsumerIdsFor(supplied, observed));
|
|
2269
|
+
await this.recordObligatedConsumerRosterVersion({
|
|
2270
|
+
reporting_obligation_id: input.reporting_obligation_id,
|
|
2271
|
+
version
|
|
2272
|
+
});
|
|
2273
|
+
return version;
|
|
2274
|
+
}
|
|
2275
|
+
/** Principals that have engaged with this obligation, as the projection sees them. */
|
|
2276
|
+
async observedConsumerIds(obligationId) {
|
|
2277
|
+
const result = await this.query(
|
|
2278
|
+
`SELECT DISTINCT consumer_id FROM (
|
|
2279
|
+
-- Driven from this obligation's subjects into the receipt subject
|
|
2280
|
+
-- index, like every other obligation-scoped receipt read. Asking the
|
|
2281
|
+
-- global receipt table which of its rows belong to an obligation
|
|
2282
|
+
-- supplies neither account nor consumer, so the roster read scanned
|
|
2283
|
+
-- the whole receipt history once per reconcile and once per
|
|
2284
|
+
-- pre-apply re-check.
|
|
2285
|
+
SELECT receipt.consumer_id
|
|
2286
|
+
FROM adcp_reporting_revisions revision
|
|
2287
|
+
JOIN adcp_reporting_receipts receipt
|
|
2288
|
+
ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
|
|
2289
|
+
WHERE revision.obligation_id = $1
|
|
2290
|
+
UNION
|
|
2291
|
+
SELECT receipt.consumer_id
|
|
2292
|
+
FROM adcp_reporting_adjustments adjustment
|
|
2293
|
+
JOIN adcp_reporting_receipts receipt
|
|
2294
|
+
ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
|
|
2295
|
+
WHERE adjustment.obligation_id = $1
|
|
2296
|
+
UNION
|
|
2297
|
+
SELECT status.consumer_id FROM adcp_reporting_consumer_statuses status
|
|
2298
|
+
WHERE status.obligation_id = $1 AND status.consumer_id <> '__legacy_unscoped_consumer__'
|
|
2299
|
+
) engaged ORDER BY consumer_id`,
|
|
2300
|
+
[obligationId]
|
|
2301
|
+
);
|
|
2302
|
+
return result.rows.map((row) => row.consumer_id);
|
|
2303
|
+
}
|
|
2304
|
+
managedDueTablesReadyCache;
|
|
2305
|
+
/** Whether the managed schema exists, resolved once per store instance. */
|
|
2306
|
+
managedDueTablesReady() {
|
|
2307
|
+
if (!this.managedDelivery) return Promise.resolve(false);
|
|
2308
|
+
this.managedDueTablesReadyCache ??= this.query(
|
|
2309
|
+
`SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
|
|
2310
|
+
AND to_regclass('adcp_reporting_materializations') IS NOT NULL
|
|
2311
|
+
AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
|
|
2312
|
+
).then((result) => result.rows[0]?.ready === true).catch((cause) => {
|
|
2313
|
+
this.managedDueTablesReadyCache = void 0;
|
|
2314
|
+
throw cause;
|
|
2315
|
+
});
|
|
2316
|
+
return this.managedDueTablesReadyCache;
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Terminal receipt subjects whose bodies have been pruned.
|
|
2320
|
+
*
|
|
2321
|
+
* The projection decides reconciliation from the receipt rows it can see,
|
|
2322
|
+
* so once an accepted leaf ages out the subject looks unreconciled again
|
|
2323
|
+
* and a settled billing period reopens. The tombstone is the only remaining
|
|
2324
|
+
* proof, so the projection has to consult it.
|
|
2325
|
+
*/
|
|
2326
|
+
async listTombstonedAcceptedSubjects(client, query, obligationIds, allConsumers = false, ledgerAsOf) {
|
|
2327
|
+
if (!obligationIds.length || !query.consumer_id && !allConsumers) return { subjects: [], complete: true };
|
|
2328
|
+
const result = await client.query(
|
|
2329
|
+
`SELECT tombstone.receipt_kind, tombstone.subject_id, tombstone.consumer_id
|
|
2330
|
+
FROM adcp_reporting_receipt_tombstones tombstone
|
|
2331
|
+
WHERE tombstone.account_id = $1 AND ($2::text IS NULL OR tombstone.consumer_id = $2)
|
|
2332
|
+
AND tombstone.status = 'accepted' AND tombstone.was_current
|
|
2333
|
+
-- A conclusion still has a place in time. Applying every accepted
|
|
2334
|
+
-- tombstone regardless of when its receipt was filed let a
|
|
2335
|
+
-- historical projection settle a subject on an acceptance that had
|
|
2336
|
+
-- not happened yet at its cutoff. Rows written before the column
|
|
2337
|
+
-- existed fall back to pruned_at, which is never earlier, so the
|
|
2338
|
+
-- fallback can only withhold a conclusion, never invent one.
|
|
2339
|
+
AND ($5::timestamptz IS NULL
|
|
2340
|
+
OR COALESCE(tombstone.subject_recorded_at, tombstone.pruned_at) <= $5::timestamptz)
|
|
2341
|
+
AND ((tombstone.receipt_kind = 'revision' AND EXISTS (
|
|
2342
|
+
SELECT 1 FROM adcp_reporting_revisions revision
|
|
2343
|
+
WHERE revision.revision_id = tombstone.subject_id
|
|
2344
|
+
AND revision.obligation_id = ANY($3::text[])))
|
|
2345
|
+
OR (tombstone.receipt_kind = 'adjustment' AND EXISTS (
|
|
2346
|
+
SELECT 1 FROM adcp_reporting_adjustments adjustment
|
|
2347
|
+
WHERE adjustment.adjustment_id = tombstone.subject_id
|
|
2348
|
+
AND adjustment.obligation_id = ANY($3::text[]))))
|
|
2349
|
+
LIMIT $4`,
|
|
2350
|
+
[
|
|
2351
|
+
query.account_id,
|
|
2352
|
+
allConsumers ? null : query.consumer_id,
|
|
2353
|
+
obligationIds,
|
|
2354
|
+
MAX_SNAPSHOT_ITEMS + 1,
|
|
2355
|
+
ledgerAsOf ?? null
|
|
2356
|
+
]
|
|
2357
|
+
);
|
|
2358
|
+
const complete = result.rows.length <= MAX_SNAPSHOT_ITEMS;
|
|
2359
|
+
return {
|
|
2360
|
+
complete,
|
|
2361
|
+
subjects: result.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => ({
|
|
2362
|
+
kind: row.receipt_kind === "adjustment" ? "adjustment" : "revision",
|
|
2363
|
+
subjectId: row.subject_id,
|
|
2364
|
+
// Carried through deliberately. An acceptance belongs to the consumer
|
|
2365
|
+
// that gave it, so a lifecycle fold that runs per consumer must not
|
|
2366
|
+
// let A's pruned acceptance settle the obligation on B's behalf.
|
|
2367
|
+
consumerId: row.consumer_id
|
|
2368
|
+
}))
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
async managedTablesInstalled(client) {
|
|
2372
|
+
const result = await client.query(
|
|
2373
|
+
`SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
|
|
2374
|
+
AND to_regclass('adcp_reporting_materializations') IS NOT NULL
|
|
2375
|
+
AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
|
|
2376
|
+
);
|
|
2377
|
+
return result.rows[0]?.ready === true;
|
|
2378
|
+
}
|
|
2379
|
+
async listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter) {
|
|
2380
|
+
const result = await client.query(
|
|
2381
|
+
`SELECT DISTINCT changed.obligation_id FROM (
|
|
2382
|
+
SELECT materialization.obligation_id
|
|
2383
|
+
FROM adcp_reporting_materializations materialization
|
|
2384
|
+
WHERE materialization.account_id = $1
|
|
2385
|
+
AND GREATEST(materialization.recorded_at, materialization.changed_at) > $3
|
|
2386
|
+
AND GREATEST(materialization.recorded_at, materialization.changed_at) <= $2
|
|
2387
|
+
UNION ALL
|
|
2388
|
+
SELECT COALESCE(revision.obligation_id, adjustment.obligation_id) AS obligation_id
|
|
2389
|
+
FROM adcp_reporting_receipts receipt
|
|
2390
|
+
LEFT JOIN adcp_reporting_revisions revision
|
|
2391
|
+
ON receipt.receipt_kind = 'revision' AND revision.revision_id = receipt.subject_id
|
|
2392
|
+
LEFT JOIN adcp_reporting_adjustments adjustment
|
|
2393
|
+
ON receipt.receipt_kind = 'adjustment' AND adjustment.adjustment_id = receipt.subject_id
|
|
2394
|
+
WHERE receipt.account_id = $1 AND receipt.consumer_id = $4
|
|
2395
|
+
AND receipt.recorded_at > $3 AND receipt.recorded_at <= $2
|
|
2396
|
+
UNION ALL
|
|
2397
|
+
SELECT obligation.obligation_id
|
|
2398
|
+
FROM adcp_reporting_destination_authorizations authz
|
|
2399
|
+
JOIN adcp_reporting_managed_bindings binding
|
|
2400
|
+
ON binding.account_id = authz.account_id
|
|
2401
|
+
AND binding.destination_ref = authz.destination_ref
|
|
2402
|
+
AND binding.authorization_generation = authz.generation
|
|
2403
|
+
JOIN adcp_reporting_obligations obligation ON obligation.configuration_id = binding.configuration_id
|
|
2404
|
+
WHERE authz.account_id = $1 AND authz.revoked_at IS NOT NULL
|
|
2405
|
+
AND authz.changed_at > $3 AND authz.changed_at <= $2
|
|
2406
|
+
) changed WHERE changed.obligation_id IS NOT NULL`,
|
|
2407
|
+
[query.account_id, ledgerAsOf, changesAfter, query.consumer_id ?? null]
|
|
2408
|
+
);
|
|
2409
|
+
return new Set(result.rows.map((row) => row.obligation_id));
|
|
2410
|
+
}
|
|
2411
|
+
async listSnapshotManagedBindings(client, configurationIds) {
|
|
2412
|
+
if (!configurationIds.length) return [];
|
|
2413
|
+
const result = await client.query(
|
|
2414
|
+
`SELECT data FROM adcp_reporting_managed_bindings
|
|
2415
|
+
WHERE configuration_id = ANY($1::text[]) ORDER BY configuration_id`,
|
|
2416
|
+
[configurationIds]
|
|
2417
|
+
);
|
|
2418
|
+
return result.rows.map((row) => row.data);
|
|
2419
|
+
}
|
|
2420
|
+
async listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) {
|
|
2421
|
+
if (!obligationIds.length) return [];
|
|
2422
|
+
const result = await client.query(
|
|
2423
|
+
`SELECT data FROM adcp_reporting_materializations
|
|
2424
|
+
WHERE obligation_id = ANY($1::text[])
|
|
2425
|
+
AND recorded_at <= $2
|
|
2426
|
+
AND ($3::timestamptz IS NULL OR GREATEST(recorded_at, changed_at) > $3)
|
|
2427
|
+
ORDER BY obligation_id, attempt, materialization_id
|
|
2428
|
+
LIMIT $4`,
|
|
2429
|
+
[obligationIds, ledgerAsOf, changesAfter ?? null, MAX_SNAPSHOT_ITEMS + 1]
|
|
2430
|
+
);
|
|
2431
|
+
return result.rows.map((row) => row.data);
|
|
2432
|
+
}
|
|
2433
|
+
async listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) {
|
|
2434
|
+
if (!obligationIds.length) return [];
|
|
2435
|
+
const result = await client.query(
|
|
2436
|
+
`SELECT materialization.data, authz.revoked_at
|
|
2437
|
+
FROM adcp_reporting_materializations materialization
|
|
2438
|
+
JOIN adcp_reporting_destination_authorizations authz
|
|
2439
|
+
ON authz.account_id = materialization.account_id
|
|
2440
|
+
AND authz.destination_ref = materialization.destination_ref
|
|
2441
|
+
AND authz.generation = materialization.authorization_generation
|
|
2442
|
+
WHERE materialization.obligation_id = ANY($1::text[])
|
|
2443
|
+
AND materialization.recorded_at <= $2
|
|
2444
|
+
ORDER BY materialization.obligation_id, materialization.attempt, materialization.materialization_id
|
|
2445
|
+
LIMIT $3`,
|
|
2446
|
+
[obligationIds, ledgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
|
|
2447
|
+
);
|
|
2448
|
+
return result.rows.map((row) => {
|
|
2449
|
+
if (!row.revoked_at || row.data.status !== "available" && row.data.status !== "delivered") return row.data;
|
|
2450
|
+
return {
|
|
2451
|
+
...row.data,
|
|
2452
|
+
status: "failed",
|
|
2453
|
+
failed_at: row.revoked_at.toISOString(),
|
|
2454
|
+
failure_code: "AUTHORIZATION_REVOKED"
|
|
2455
|
+
};
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
async listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, kind) {
|
|
2459
|
+
if (!obligationIds.length || !query.consumer_id) return [];
|
|
2460
|
+
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";
|
|
2461
|
+
const result = await client.query(
|
|
2462
|
+
`SELECT receipt.data FROM adcp_reporting_receipts receipt
|
|
2463
|
+
${join}
|
|
2464
|
+
WHERE receipt.account_id = $1 AND receipt.consumer_id = $2
|
|
2465
|
+
AND receipt.receipt_kind = $3 AND subject.obligation_id = ANY($4::text[])
|
|
2466
|
+
AND receipt.recorded_at <= $5
|
|
2467
|
+
AND ($6::timestamptz IS NULL OR receipt.recorded_at > $6)
|
|
2468
|
+
ORDER BY subject.obligation_id, receipt.recorded_at, receipt.reporting_receipt_id
|
|
2469
|
+
LIMIT $7`,
|
|
2470
|
+
[
|
|
2471
|
+
query.account_id,
|
|
2472
|
+
query.consumer_id,
|
|
2473
|
+
kind,
|
|
2474
|
+
obligationIds,
|
|
2475
|
+
ledgerAsOf,
|
|
2476
|
+
changesAfter ?? null,
|
|
2477
|
+
MAX_SNAPSHOT_ITEMS + 1
|
|
2478
|
+
]
|
|
2479
|
+
);
|
|
2480
|
+
return result.rows.map((row) => row.data);
|
|
2481
|
+
}
|
|
1587
2482
|
async one(sql, params) {
|
|
1588
2483
|
const result = await this.query(sql, params);
|
|
1589
2484
|
return result.rows[0] ? clone(result.rows[0].data) : null;
|
|
1590
2485
|
}
|
|
1591
|
-
async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence) {
|
|
2486
|
+
async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence, legacyReplayEquivalent) {
|
|
1592
2487
|
return this.transaction(
|
|
1593
2488
|
async (client) => {
|
|
1594
2489
|
const inserted = await client.query(insertSql, insertParams);
|
|
@@ -1606,7 +2501,7 @@ class PostgresReportingLedgerStore {
|
|
|
1606
2501
|
}
|
|
1607
2502
|
throw new Error("Immutable reporting ledger replay could not be resolved");
|
|
1608
2503
|
}
|
|
1609
|
-
if (fingerprint(value) !== fingerprint(proposed)) {
|
|
2504
|
+
if (fingerprint(value) !== fingerprint(proposed) && !legacyReplayEquivalent?.(value, proposed)) {
|
|
1610
2505
|
throw new Error("Immutable reporting ledger identity names different content");
|
|
1611
2506
|
}
|
|
1612
2507
|
return { inserted: inserted.rowCount === 1, value: clone(value) };
|
|
@@ -1718,6 +2613,38 @@ function snapshotItems(snapshot) {
|
|
|
1718
2613
|
values.push(adjustment);
|
|
1719
2614
|
adjustments.set(adjustment.reporting_obligation_id, values);
|
|
1720
2615
|
}
|
|
2616
|
+
const obligationByRevision = new Map(
|
|
2617
|
+
snapshot.revisions.map((value) => [value.reporting_revision_id, value.reporting_obligation_id])
|
|
2618
|
+
);
|
|
2619
|
+
const obligationByAdjustment = new Map(
|
|
2620
|
+
snapshot.adjustments.map((value) => [value.reporting_adjustment_id, value.reporting_obligation_id])
|
|
2621
|
+
);
|
|
2622
|
+
const materializations = /* @__PURE__ */ new Map();
|
|
2623
|
+
for (const materialization of snapshot.materializations ?? []) {
|
|
2624
|
+
if (snapshot.query.finality && !visibleRevisionIds.has(materialization.reporting_revision_id)) continue;
|
|
2625
|
+
const values = materializations.get(materialization.reporting_obligation_id) ?? [];
|
|
2626
|
+
values.push(materialization);
|
|
2627
|
+
materializations.set(materialization.reporting_obligation_id, values);
|
|
2628
|
+
}
|
|
2629
|
+
const receipts = /* @__PURE__ */ new Map();
|
|
2630
|
+
for (const receipt of snapshot.receipts ?? []) {
|
|
2631
|
+
if (snapshot.query.finality && !visibleRevisionIds.has(receipt.reporting_revision_id)) continue;
|
|
2632
|
+
const obligationId = obligationByRevision.get(receipt.reporting_revision_id);
|
|
2633
|
+
if (!obligationId) continue;
|
|
2634
|
+
const values = receipts.get(obligationId) ?? [];
|
|
2635
|
+
values.push(receipt);
|
|
2636
|
+
receipts.set(obligationId, values);
|
|
2637
|
+
}
|
|
2638
|
+
const adjustmentReceipts = /* @__PURE__ */ new Map();
|
|
2639
|
+
const visibleAdjustmentIds = new Set([...adjustments.values()].flat().map((value) => value.reporting_adjustment_id));
|
|
2640
|
+
for (const receipt of snapshot.adjustmentReceipts ?? []) {
|
|
2641
|
+
if (snapshot.query.finality && !visibleAdjustmentIds.has(receipt.reporting_adjustment_id)) continue;
|
|
2642
|
+
const obligationId = obligationByAdjustment.get(receipt.reporting_adjustment_id);
|
|
2643
|
+
if (!obligationId) continue;
|
|
2644
|
+
const values = adjustmentReceipts.get(obligationId) ?? [];
|
|
2645
|
+
values.push(receipt);
|
|
2646
|
+
adjustmentReceipts.set(obligationId, values);
|
|
2647
|
+
}
|
|
1721
2648
|
return [
|
|
1722
2649
|
...snapshot.obligations.flatMap((obligation) => [
|
|
1723
2650
|
...snapshot.query.view === "revision" ? [] : [{ kind: "obligation", value: obligation }],
|
|
@@ -1728,6 +2655,18 @@ function snapshotItems(snapshot) {
|
|
|
1728
2655
|
...(adjustments.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
1729
2656
|
kind: "adjustment",
|
|
1730
2657
|
value
|
|
2658
|
+
})),
|
|
2659
|
+
...(materializations.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
2660
|
+
kind: "materialization",
|
|
2661
|
+
value
|
|
2662
|
+
})),
|
|
2663
|
+
...(receipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
2664
|
+
kind: "receipt",
|
|
2665
|
+
value
|
|
2666
|
+
})),
|
|
2667
|
+
...(adjustmentReceipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
|
|
2668
|
+
kind: "adjustment_receipt",
|
|
2669
|
+
value
|
|
1731
2670
|
}))
|
|
1732
2671
|
]),
|
|
1733
2672
|
...snapshot.query.view === "periods" || snapshot.query.view === "revision" ? (snapshot.consumerStatuses ?? []).map((value) => ({ kind: "consumer_status", value })) : []
|
|
@@ -1808,9 +2747,200 @@ function adjustmentIdentityFingerprint(value) {
|
|
|
1808
2747
|
const { created_at: _wireCreatedAt, ...stableWireAdjustment } = wireAdjustment;
|
|
1809
2748
|
return digest(JSON.parse(JSON.stringify({ ...identity, wireAdjustment: stableWireAdjustment })));
|
|
1810
2749
|
}
|
|
2750
|
+
function revisionLegacyCanonicalDigestReplay(stored, proposed) {
|
|
2751
|
+
if (stored.wireRevision.canonical_content_digest !== void 0) return false;
|
|
2752
|
+
if (proposed.wireRevision.canonical_content_digest === void 0) return false;
|
|
2753
|
+
const { canonical_content_digest: _digest, ...withoutDigest } = proposed.wireRevision;
|
|
2754
|
+
return revisionIdentityFingerprint({
|
|
2755
|
+
...proposed,
|
|
2756
|
+
wireRevision: withoutDigest
|
|
2757
|
+
}) === revisionIdentityFingerprint(stored);
|
|
2758
|
+
}
|
|
2759
|
+
function adjustmentLegacyCanonicalDigestReplay(stored, proposed) {
|
|
2760
|
+
if (stored.wireAdjustment.canonical_adjustment_sha256 !== void 0) return false;
|
|
2761
|
+
const proposedDigest = proposed.wireAdjustment.canonical_adjustment_sha256;
|
|
2762
|
+
if (proposedDigest === void 0) return false;
|
|
2763
|
+
const { canonical_adjustment_sha256: _digest, ...withoutDigest } = proposed.wireAdjustment;
|
|
2764
|
+
if ((0, import_producer.reportingCanonicalAdjustmentSha256V1)(withoutDigest) !== proposedDigest) return false;
|
|
2765
|
+
return adjustmentIdentityFingerprint({
|
|
2766
|
+
...proposed,
|
|
2767
|
+
wireAdjustment: withoutDigest
|
|
2768
|
+
}) === adjustmentIdentityFingerprint(stored);
|
|
2769
|
+
}
|
|
2770
|
+
const OBLIGATED_CONSUMERS_DEADLINE_MS = 1e4;
|
|
2771
|
+
async function withReportingCallbackDeadline(value, milliseconds, message) {
|
|
2772
|
+
let timer;
|
|
2773
|
+
try {
|
|
2774
|
+
return await Promise.race([
|
|
2775
|
+
value,
|
|
2776
|
+
new Promise((_resolve, reject) => {
|
|
2777
|
+
timer = setTimeout(() => reject(new Error(message)), milliseconds);
|
|
2778
|
+
})
|
|
2779
|
+
]);
|
|
2780
|
+
} finally {
|
|
2781
|
+
if (timer) clearTimeout(timer);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
function pendingAtCutoff(value) {
|
|
2785
|
+
const {
|
|
2786
|
+
ready_at: _readyAt,
|
|
2787
|
+
resource: _resource,
|
|
2788
|
+
verification: _verification,
|
|
2789
|
+
failed_at: _failedAt,
|
|
2790
|
+
failure_code: _failureCode,
|
|
2791
|
+
...pending
|
|
2792
|
+
} = clone(value);
|
|
2793
|
+
return { ...pending, status: "pending" };
|
|
2794
|
+
}
|
|
2795
|
+
const MANAGED_DUE_ARM = ` OR (EXISTS (
|
|
2796
|
+
SELECT 1 FROM adcp_reporting_managed_bindings binding
|
|
2797
|
+
WHERE binding.configuration_id = obligation.configuration_id
|
|
2798
|
+
AND (
|
|
2799
|
+
-- A managed change after the last transition. Health is
|
|
2800
|
+
-- composed from managed state, but only Core evidence
|
|
2801
|
+
-- and the clock used to schedule a reconcile \u2014 so a
|
|
2802
|
+
-- settlement, revocation, receipt, consumer status or
|
|
2803
|
+
-- roster-visible change arriving after a complete left
|
|
2804
|
+
-- that complete persisted and webhooked forever while
|
|
2805
|
+
-- a live read of the same obligation degraded.
|
|
2806
|
+
EXISTS (
|
|
2807
|
+
SELECT 1 FROM adcp_reporting_materializations m
|
|
2808
|
+
WHERE m.obligation_id = obligation.obligation_id
|
|
2809
|
+
AND m.changed_at > watermark.since
|
|
2810
|
+
AND m.changed_at <= $2)
|
|
2811
|
+
OR EXISTS (
|
|
2812
|
+
-- Adjustments are managed health inputs too: after an
|
|
2813
|
+
-- accepted billing receipt, a correction makes the live
|
|
2814
|
+
-- read action_required while the persisted health and
|
|
2815
|
+
-- its webhook stayed complete.
|
|
2816
|
+
SELECT 1 FROM adcp_reporting_adjustments adj
|
|
2817
|
+
WHERE adj.obligation_id = obligation.obligation_id
|
|
2818
|
+
AND adj.recorded_at > watermark.since
|
|
2819
|
+
AND adj.recorded_at <= $2)
|
|
2820
|
+
OR EXISTS (
|
|
2821
|
+
SELECT 1 FROM adcp_reporting_destination_authorizations authz
|
|
2822
|
+
WHERE authz.account_id = binding.account_id
|
|
2823
|
+
AND authz.destination_ref = binding.destination_ref
|
|
2824
|
+
AND GREATEST(authz.changed_at, COALESCE(authz.cleanup_completed_at, authz.changed_at))
|
|
2825
|
+
> watermark.since
|
|
2826
|
+
AND authz.changed_at <= $2)
|
|
2827
|
+
-- Driven from this obligation's subjects into the
|
|
2828
|
+
-- receipt table's subject index. Asking the global
|
|
2829
|
+
-- receipt table which of its rows belong to this
|
|
2830
|
+
-- obligation supplied neither account nor consumer, so
|
|
2831
|
+
-- every candidate obligation in every sweep re-scanned
|
|
2832
|
+
-- the whole receipt history.
|
|
2833
|
+
OR EXISTS (
|
|
2834
|
+
SELECT 1 FROM adcp_reporting_revisions rv
|
|
2835
|
+
JOIN adcp_reporting_receipts receipt
|
|
2836
|
+
ON receipt.receipt_kind = 'revision'
|
|
2837
|
+
AND receipt.subject_id = rv.revision_id
|
|
2838
|
+
WHERE rv.obligation_id = obligation.obligation_id
|
|
2839
|
+
AND receipt.recorded_at > watermark.since
|
|
2840
|
+
AND receipt.recorded_at <= $2)
|
|
2841
|
+
OR EXISTS (
|
|
2842
|
+
SELECT 1 FROM adcp_reporting_adjustments aj
|
|
2843
|
+
JOIN adcp_reporting_receipts receipt
|
|
2844
|
+
ON receipt.receipt_kind = 'adjustment'
|
|
2845
|
+
AND receipt.subject_id = aj.adjustment_id
|
|
2846
|
+
WHERE aj.obligation_id = obligation.obligation_id
|
|
2847
|
+
AND receipt.recorded_at > watermark.since
|
|
2848
|
+
AND receipt.recorded_at <= $2)
|
|
2849
|
+
OR EXISTS (
|
|
2850
|
+
SELECT 1 FROM adcp_reporting_consumer_statuses status
|
|
2851
|
+
WHERE status.obligation_id = obligation.obligation_id
|
|
2852
|
+
AND status.created_at > watermark.since
|
|
2853
|
+
AND status.created_at <= $2)
|
|
2854
|
+
-- An external roster change writes nothing here at all,
|
|
2855
|
+
-- so the only durable trace is the version recorded at
|
|
2856
|
+
-- the last reconcile. A null one means never reconciled.
|
|
2857
|
+
OR state.processed_roster_version IS NULL
|
|
2858
|
+
-- A roster lives outside this database, so the only way
|
|
2859
|
+
-- a change can re-arm anything is if someone records
|
|
2860
|
+
-- it. Every read of the roster publishes the version it
|
|
2861
|
+
-- saw; a difference from what was last reconciled is a
|
|
2862
|
+
-- due condition. Without this the first reconcile made
|
|
2863
|
+
-- processed_roster_version non-null and no later roster
|
|
2864
|
+
-- change could ever schedule work again.
|
|
2865
|
+
OR state.current_roster_version IS DISTINCT FROM state.processed_roster_version
|
|
2866
|
+
-- A retained resource expiring is a health change with
|
|
2867
|
+
-- no row written anywhere, so nothing else can notice it.
|
|
2868
|
+
OR EXISTS (
|
|
2869
|
+
SELECT 1 FROM adcp_reporting_materializations m
|
|
2870
|
+
WHERE m.obligation_id = obligation.obligation_id
|
|
2871
|
+
AND m.status IN ('available', 'delivered')
|
|
2872
|
+
AND (m.data -> 'resource' ->> 'expires_at')::timestamptz <= $2
|
|
2873
|
+
AND (m.data -> 'resource' ->> 'expires_at')::timestamptz
|
|
2874
|
+
> watermark.since)
|
|
2875
|
+
)
|
|
2876
|
+
))
|
|
2877
|
+
`;
|
|
2878
|
+
function obligatedConsumerIdsFor(supplied, observed) {
|
|
2879
|
+
return supplied.complete === true ? [...new Set(supplied.ids)].sort() : [.../* @__PURE__ */ new Set([...supplied.ids, ...observed])].sort();
|
|
2880
|
+
}
|
|
2881
|
+
function obligatedConsumerRosterVersionFor(supplied, ids) {
|
|
2882
|
+
return supplied.version ?? digest({ ids, complete: supplied.complete === true });
|
|
2883
|
+
}
|
|
2884
|
+
const MAX_ROSTER_REFRESH_PER_SWEEP = 25;
|
|
2885
|
+
const MAX_LIFECYCLE_BACKOFF_SECONDS = 900;
|
|
2886
|
+
function rfc3339Microseconds(expression) {
|
|
2887
|
+
return `to_char(${expression} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`;
|
|
2888
|
+
}
|
|
1811
2889
|
function accountLock(accountId) {
|
|
1812
2890
|
return `adcp-reporting-account:${accountId}`;
|
|
1813
2891
|
}
|
|
2892
|
+
async function resolveStoredFinalityBaseline(transaction, obligationId) {
|
|
2893
|
+
const latest = await transaction.query(
|
|
2894
|
+
`SELECT transition_id, data->>'finality' AS finality
|
|
2895
|
+
FROM adcp_reporting_transitions
|
|
2896
|
+
WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1
|
|
2897
|
+
FOR UPDATE`,
|
|
2898
|
+
[obligationId]
|
|
2899
|
+
);
|
|
2900
|
+
const previous = latest.rows[0];
|
|
2901
|
+
if (!previous) return "none";
|
|
2902
|
+
if (previous.finality) return previous.finality;
|
|
2903
|
+
await transaction.query(
|
|
2904
|
+
`UPDATE adcp_reporting_transitions
|
|
2905
|
+
SET data = data || jsonb_build_object('finality', 'none')
|
|
2906
|
+
WHERE transition_id = $1 AND NOT (data ? 'finality')`,
|
|
2907
|
+
[previous.transition_id]
|
|
2908
|
+
);
|
|
2909
|
+
return "none";
|
|
2910
|
+
}
|
|
2911
|
+
async function assertNoLegacyPendingTransitions(transaction, obligationId) {
|
|
2912
|
+
const pending = await transaction.query(
|
|
2913
|
+
`SELECT transition_id FROM adcp_reporting_transitions
|
|
2914
|
+
WHERE obligation_id = $1 AND data->>'notifiedAt' IS NULL
|
|
2915
|
+
LIMIT 1`,
|
|
2916
|
+
[obligationId]
|
|
2917
|
+
);
|
|
2918
|
+
if (pending.rowCount !== 0) {
|
|
2919
|
+
throw new Error(
|
|
2920
|
+
"Drain or explicitly resolve legacy pending reporting transitions before enabling transactional notification activity"
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
async function assertFinalityWriterFence(work) {
|
|
2925
|
+
try {
|
|
2926
|
+
return await work();
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
if (isFinalityWriterFenceViolation(error)) {
|
|
2929
|
+
throw new Error(
|
|
2930
|
+
"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.",
|
|
2931
|
+
{ cause: error }
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
throw error;
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
function isFinalityWriterFenceViolation(error) {
|
|
2938
|
+
for (let current = error; current instanceof Error; current = current.cause) {
|
|
2939
|
+
const candidate = current;
|
|
2940
|
+
if (candidate.code === "23514" && candidate.constraint === REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT) return true;
|
|
2941
|
+
}
|
|
2942
|
+
return false;
|
|
2943
|
+
}
|
|
1814
2944
|
function consumerStatusChainKeyForObligation(obligation) {
|
|
1815
2945
|
return (0, import_consumer_status_identity.reportingConsumerStatusChainKeyFromIdentityV1)({
|
|
1816
2946
|
delivery_config_id: obligation.delivery_config_id,
|
|
@@ -1891,6 +3021,8 @@ function positiveInteger(value, name) {
|
|
|
1891
3021
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1892
3022
|
0 && (module.exports = {
|
|
1893
3023
|
PostgresReportingLedgerStore,
|
|
3024
|
+
REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT,
|
|
3025
|
+
REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
|
|
1894
3026
|
REPORTING_LEDGER_MIGRATION,
|
|
1895
3027
|
ReportingLedgerContinuityError,
|
|
1896
3028
|
ReportingLedgerLeaseLostError,
|