@adcp/sdk 12.0.0 → 12.0.2
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/dist/lib/adapters/content-standards-adapter.mjs +0 -6
- package/dist/lib/adapters/content-standards-adapter.mjs.map +1 -1
- package/dist/lib/adapters/derived-account-store.mjs +0 -6
- package/dist/lib/adapters/derived-account-store.mjs.map +1 -1
- package/dist/lib/adapters/governance-adapter.mjs +0 -6
- package/dist/lib/adapters/governance-adapter.mjs.map +1 -1
- package/dist/lib/adapters/implicit-account-store.mjs +0 -6
- package/dist/lib/adapters/implicit-account-store.mjs.map +1 -1
- package/dist/lib/adapters/index.mjs +0 -6
- package/dist/lib/adapters/index.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/get_products.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/get_products.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/index.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/index.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/types.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs.map +1 -1
- package/dist/lib/adapters/oauth-passthrough-resolver.mjs +0 -6
- package/dist/lib/adapters/oauth-passthrough-resolver.mjs.map +1 -1
- package/dist/lib/adapters/property-list-adapter.mjs +0 -6
- package/dist/lib/adapters/property-list-adapter.mjs.map +1 -1
- package/dist/lib/adapters/roster-account-store.mjs +0 -6
- package/dist/lib/adapters/roster-account-store.mjs.map +1 -1
- package/dist/lib/adapters/si-session-manager.mjs +0 -6
- package/dist/lib/adapters/si-session-manager.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/brand-fields.mjs +0 -6
- package/dist/lib/adapters/version/3.0/brand-fields.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/get-products.mjs +0 -6
- package/dist/lib/adapters/version/3.0/get-products.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/index.mjs +0 -6
- package/dist/lib/adapters/version/3.0/index.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/sync-accounts.mjs +0 -6
- package/dist/lib/adapters/version/3.0/sync-accounts.mjs.map +1 -1
- package/dist/lib/adapters/version/index.mjs +0 -6
- package/dist/lib/adapters/version/index.mjs.map +1 -1
- package/dist/lib/adapters/version/types.mjs +0 -6
- package/dist/lib/advanced.mjs +0 -6
- package/dist/lib/advanced.mjs.map +1 -1
- package/dist/lib/agents/index.generated.mjs +0 -6
- package/dist/lib/agents/index.generated.mjs.map +1 -1
- package/dist/lib/auth/index.mjs +0 -6
- package/dist/lib/auth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs +0 -6
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +0 -6
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs.map +1 -1
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +0 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs.map +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +0 -6
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/authorization-required.mjs +0 -6
- package/dist/lib/auth/oauth/authorization-required.mjs.map +1 -1
- package/dist/lib/auth/oauth/diagnose.mjs +0 -6
- package/dist/lib/auth/oauth/diagnose.mjs.map +1 -1
- package/dist/lib/auth/oauth/diagnostics.mjs +0 -6
- package/dist/lib/auth/oauth/diagnostics.mjs.map +1 -1
- package/dist/lib/auth/oauth/discovery.mjs +0 -6
- package/dist/lib/auth/oauth/discovery.mjs.map +1 -1
- package/dist/lib/auth/oauth/file-storage.mjs +0 -6
- package/dist/lib/auth/oauth/file-storage.mjs.map +1 -1
- package/dist/lib/auth/oauth/index.mjs +0 -6
- package/dist/lib/auth/oauth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/secret-resolver.mjs +0 -6
- package/dist/lib/auth/oauth/secret-resolver.mjs.map +1 -1
- package/dist/lib/auth/oauth/storage-registry.mjs +0 -6
- package/dist/lib/auth/oauth/storage-registry.mjs.map +1 -1
- package/dist/lib/auth/oauth/types.mjs +0 -6
- package/dist/lib/auth/oauth/types.mjs.map +1 -1
- package/dist/lib/auth/oauth/web-flow.mjs +0 -6
- package/dist/lib/auth/oauth/web-flow.mjs.map +1 -1
- package/dist/lib/brand/index.mjs +0 -6
- package/dist/lib/brand/index.mjs.map +1 -1
- package/dist/lib/canonical-references/index.mjs +6 -7
- package/dist/lib/canonical-references/index.mjs.map +1 -1
- package/dist/lib/client/index.mjs +0 -6
- package/dist/lib/client/index.mjs.map +1 -1
- package/dist/lib/compliance/index.mjs +0 -6
- package/dist/lib/compliance/index.mjs.map +1 -1
- package/dist/lib/compliance-fixtures/index.mjs +0 -6
- package/dist/lib/compliance-fixtures/index.mjs.map +1 -1
- package/dist/lib/compliance-fixtures/test-authorization-server.mjs +0 -6
- package/dist/lib/compliance-fixtures/test-authorization-server.mjs.map +1 -1
- package/dist/lib/conformance/index.mjs +0 -6
- package/dist/lib/conformance/index.mjs.map +1 -1
- package/dist/lib/conformance/invariants/uniformError.mjs +0 -6
- package/dist/lib/conformance/invariants/uniformError.mjs.map +1 -1
- package/dist/lib/conformance/invariants/uniformErrorComparator.mjs +0 -6
- package/dist/lib/conformance/invariants/uniformErrorComparator.mjs.map +1 -1
- package/dist/lib/conformance/oracle.mjs +0 -6
- package/dist/lib/conformance/oracle.mjs.map +1 -1
- package/dist/lib/conformance/runConformance.mjs +0 -6
- package/dist/lib/conformance/runConformance.mjs.map +1 -1
- package/dist/lib/conformance/runners.mjs +0 -6
- package/dist/lib/conformance/runners.mjs.map +1 -1
- package/dist/lib/conformance/schemaArbitrary.mjs +0 -6
- package/dist/lib/conformance/schemaArbitrary.mjs.map +1 -1
- package/dist/lib/conformance/schemaLoader.mjs +5 -6
- package/dist/lib/conformance/schemaLoader.mjs.map +1 -1
- package/dist/lib/conformance/seeder.mjs +0 -6
- package/dist/lib/conformance/seeder.mjs.map +1 -1
- package/dist/lib/conformance/types.mjs +0 -6
- package/dist/lib/conformance/types.mjs.map +1 -1
- package/dist/lib/conformance/webhook.mjs +0 -6
- package/dist/lib/conformance/webhook.mjs.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.mjs +0 -6
- package/dist/lib/core/ADCPMultiAgentClient.mjs.map +1 -1
- package/dist/lib/core/AgentClient.mjs +0 -6
- package/dist/lib/core/AgentClient.mjs.map +1 -1
- package/dist/lib/core/AsyncHandler.mjs +0 -6
- package/dist/lib/core/AsyncHandler.mjs.map +1 -1
- package/dist/lib/core/ConfigurationManager.mjs +0 -6
- package/dist/lib/core/ConfigurationManager.mjs.map +1 -1
- package/dist/lib/core/ConversationTypes.mjs +0 -6
- package/dist/lib/core/CreativeAgentClient.mjs +0 -6
- package/dist/lib/core/CreativeAgentClient.mjs.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.mjs +0 -6
- package/dist/lib/core/GovernanceMiddleware.mjs.map +1 -1
- package/dist/lib/core/GovernanceTypes.mjs +0 -6
- package/dist/lib/core/GovernanceTypes.mjs.map +1 -1
- package/dist/lib/core/ProtocolResponseParser.mjs +0 -6
- package/dist/lib/core/ProtocolResponseParser.mjs.map +1 -1
- package/dist/lib/core/ResponseValidator.mjs +0 -6
- package/dist/lib/core/ResponseValidator.mjs.map +1 -1
- package/dist/lib/core/SingleAgentClient.mjs +0 -6
- package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
- package/dist/lib/core/TaskEventTypes.mjs +0 -6
- package/dist/lib/core/TaskEventTypes.mjs.map +1 -1
- package/dist/lib/core/TaskExecutor.mjs +0 -6
- package/dist/lib/core/TaskExecutor.mjs.map +1 -1
- package/dist/lib/core/match.mjs +0 -6
- package/dist/lib/core/match.mjs.map +1 -1
- package/dist/lib/core/task-status.mjs +0 -6
- package/dist/lib/core/task-status.mjs.map +1 -1
- package/dist/lib/core/webhook-url.mjs +0 -6
- package/dist/lib/core/webhook-url.mjs.map +1 -1
- package/dist/lib/discovery/adagents-redirects.mjs +0 -6
- package/dist/lib/discovery/adagents-redirects.mjs.map +1 -1
- package/dist/lib/discovery/agent-directory.mjs +0 -6
- package/dist/lib/discovery/agent-directory.mjs.map +1 -1
- package/dist/lib/discovery/inline-publisher-properties.mjs +0 -6
- package/dist/lib/discovery/inline-publisher-properties.mjs.map +1 -1
- package/dist/lib/discovery/network-consistency-checker.mjs +0 -6
- package/dist/lib/discovery/network-consistency-checker.mjs.map +1 -1
- package/dist/lib/discovery/property-crawler.mjs +0 -6
- package/dist/lib/discovery/property-crawler.mjs.map +1 -1
- package/dist/lib/discovery/property-index.mjs +0 -6
- package/dist/lib/discovery/property-index.mjs.map +1 -1
- package/dist/lib/discovery/publisher-property-selector.mjs +0 -6
- package/dist/lib/discovery/publisher-property-selector.mjs.map +1 -1
- package/dist/lib/discovery/resolve-agent-properties.mjs +0 -6
- package/dist/lib/discovery/resolve-agent-properties.mjs.map +1 -1
- package/dist/lib/discovery/types.mjs +0 -6
- package/dist/lib/discovery/validate-adagents.mjs +0 -6
- package/dist/lib/discovery/validate-adagents.mjs.map +1 -1
- package/dist/lib/enums.mjs +0 -6
- package/dist/lib/enums.mjs.map +1 -1
- package/dist/lib/errors/index.mjs +0 -6
- package/dist/lib/errors/index.mjs.map +1 -1
- package/dist/lib/express-mcp/index.mjs +0 -6
- package/dist/lib/express-mcp/index.mjs.map +1 -1
- package/dist/lib/governance/index.mjs +0 -6
- package/dist/lib/governance/index.mjs.map +1 -1
- package/dist/lib/handlers/types.mjs +0 -6
- package/dist/lib/handlers/types.mjs.map +1 -1
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +0 -6
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/media-buy/available-actions.mjs +0 -6
- package/dist/lib/media-buy/available-actions.mjs.map +1 -1
- package/dist/lib/media-buy/index.mjs +0 -6
- package/dist/lib/media-buy/index.mjs.map +1 -1
- package/dist/lib/media-buy/preflight.mjs +0 -6
- package/dist/lib/media-buy/preflight.mjs.map +1 -1
- package/dist/lib/media-buy/property-policy.mjs +0 -6
- package/dist/lib/media-buy/property-policy.mjs.map +1 -1
- package/dist/lib/media-buy/types.mjs +0 -6
- package/dist/lib/media-buy/types.mjs.map +1 -1
- package/dist/lib/media-buy/update-fields.generated.mjs +0 -6
- package/dist/lib/media-buy/update-fields.generated.mjs.map +1 -1
- package/dist/lib/mock-server/creative-ad-server/seed-data.mjs +0 -6
- package/dist/lib/mock-server/creative-ad-server/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/creative-ad-server/server.mjs +0 -6
- package/dist/lib/mock-server/creative-ad-server/server.mjs.map +1 -1
- package/dist/lib/mock-server/creative-template/seed-data.mjs +0 -6
- package/dist/lib/mock-server/creative-template/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/creative-template/server.mjs +0 -6
- package/dist/lib/mock-server/creative-template/server.mjs.map +1 -1
- package/dist/lib/mock-server/index.mjs +0 -6
- package/dist/lib/mock-server/index.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/recipe.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/recipe.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/server.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/server.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/server.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/server.mjs.map +1 -1
- package/dist/lib/mock-server/sales-social/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-social/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-social/server.mjs +0 -6
- package/dist/lib/mock-server/sales-social/server.mjs.map +1 -1
- package/dist/lib/mock-server/scenario.mjs +0 -6
- package/dist/lib/mock-server/scenario.mjs.map +1 -1
- package/dist/lib/mock-server/signal-marketplace/seed-data.mjs +0 -6
- package/dist/lib/mock-server/signal-marketplace/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/signal-marketplace/server.mjs +0 -6
- package/dist/lib/mock-server/signal-marketplace/server.mjs.map +1 -1
- package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sponsored-intelligence/server.mjs +0 -6
- package/dist/lib/mock-server/sponsored-intelligence/server.mjs.map +1 -1
- package/dist/lib/net/address-guards.mjs +0 -6
- package/dist/lib/net/address-guards.mjs.map +1 -1
- package/dist/lib/net/index.mjs +0 -6
- package/dist/lib/net/index.mjs.map +1 -1
- package/dist/lib/net/ssrf-fetch.mjs +0 -6
- package/dist/lib/net/ssrf-fetch.mjs.map +1 -1
- package/dist/lib/observability/index.mjs +0 -6
- package/dist/lib/observability/index.mjs.map +1 -1
- package/dist/lib/observability/tracing.mjs +6 -7
- package/dist/lib/observability/tracing.mjs.map +1 -1
- package/dist/lib/protocols/a2a.mjs +0 -6
- package/dist/lib/protocols/a2a.mjs.map +1 -1
- package/dist/lib/protocols/abort.mjs +0 -6
- package/dist/lib/protocols/abort.mjs.map +1 -1
- package/dist/lib/protocols/index.mjs +0 -6
- package/dist/lib/protocols/index.mjs.map +1 -1
- package/dist/lib/protocols/mcp-modern.mjs +0 -6
- package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
- package/dist/lib/protocols/mcp-tasks.mjs +0 -6
- package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
- package/dist/lib/protocols/mcp.mjs +0 -6
- package/dist/lib/protocols/mcp.mjs.map +1 -1
- package/dist/lib/protocols/rawResponseCapture.mjs +0 -6
- package/dist/lib/protocols/rawResponseCapture.mjs.map +1 -1
- package/dist/lib/protocols/responseSizeLimit.mjs +0 -6
- package/dist/lib/protocols/responseSizeLimit.mjs.map +1 -1
- package/dist/lib/protocols/transportDiagnostics.mjs +0 -6
- package/dist/lib/protocols/transportDiagnostics.mjs.map +1 -1
- package/dist/lib/registry/cursor-store.mjs +0 -6
- package/dist/lib/registry/cursor-store.mjs.map +1 -1
- package/dist/lib/registry/feed-stream.mjs +0 -6
- package/dist/lib/registry/feed-stream.mjs.map +1 -1
- package/dist/lib/registry/index.mjs +0 -6
- package/dist/lib/registry/index.mjs.map +1 -1
- package/dist/lib/registry/property-registry.mjs +0 -6
- package/dist/lib/registry/property-registry.mjs.map +1 -1
- package/dist/lib/registry/sync.mjs +0 -6
- package/dist/lib/registry/sync.mjs.map +1 -1
- package/dist/lib/registry/types.generated.mjs +0 -6
- package/dist/lib/registry/types.mjs +0 -6
- package/dist/lib/schemas/index.mjs +0 -6
- package/dist/lib/schemas/index.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.mjs +0 -6
- package/dist/lib/server/a2a-adapter.mjs.map +1 -1
- package/dist/lib/server/account-mode.mjs +0 -6
- package/dist/lib/server/account-mode.mjs.map +1 -1
- package/dist/lib/server/adcp-server.mjs +0 -6
- package/dist/lib/server/adcp-server.mjs.map +1 -1
- package/dist/lib/server/auth-introspection.mjs +0 -6
- package/dist/lib/server/auth-introspection.mjs.map +1 -1
- package/dist/lib/server/auth-signature.mjs +0 -6
- package/dist/lib/server/auth-signature.mjs.map +1 -1
- package/dist/lib/server/auth.mjs +0 -6
- package/dist/lib/server/auth.mjs.map +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +13 -0
- package/dist/lib/server/create-adcp-server.d.ts +13 -0
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +3 -2
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/create-adcp-server.mjs +3 -8
- package/dist/lib/server/create-adcp-server.mjs.map +1 -1
- package/dist/lib/server/credential-policy.mjs +0 -6
- package/dist/lib/server/credential-policy.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/memory.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/memory.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/pg.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/pg.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/redis.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/redis.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/index.mjs +0 -6
- package/dist/lib/server/ctx-metadata/index.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/store.mjs +0 -6
- package/dist/lib/server/ctx-metadata/store.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/wire-shape.mjs +0 -6
- package/dist/lib/server/ctx-metadata/wire-shape.mjs.map +1 -1
- package/dist/lib/server/decisioning/account.mjs +0 -6
- package/dist/lib/server/decisioning/account.mjs.map +1 -1
- package/dist/lib/server/decisioning/admin-router.mjs +0 -6
- package/dist/lib/server/decisioning/admin-router.mjs.map +1 -1
- package/dist/lib/server/decisioning/assembly-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/assembly-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/async-outcome.mjs +0 -6
- package/dist/lib/server/decisioning/async-outcome.mjs.map +1 -1
- package/dist/lib/server/decisioning/buyer-agent.mjs +0 -6
- package/dist/lib/server/decisioning/buyer-agent.mjs.map +1 -1
- package/dist/lib/server/decisioning/capabilities.mjs +0 -6
- package/dist/lib/server/decisioning/capabilities.mjs.map +1 -1
- package/dist/lib/server/decisioning/compose.mjs +0 -6
- package/dist/lib/server/decisioning/compose.mjs.map +1 -1
- package/dist/lib/server/decisioning/context.mjs +0 -6
- package/dist/lib/server/decisioning/errors-typed.mjs +0 -6
- package/dist/lib/server/decisioning/errors-typed.mjs.map +1 -1
- package/dist/lib/server/decisioning/helpers.mjs +0 -6
- package/dist/lib/server/decisioning/helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/index.mjs +0 -6
- package/dist/lib/server/decisioning/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/list-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/list-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/manifest-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/manifest-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/pagination.mjs +0 -6
- package/dist/lib/server/decisioning/platform-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/platform-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/platform.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/dispatch.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/dispatch.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/index.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/lifecycle.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/lifecycle.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/mock-manager.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/mock-manager.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/store.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/store.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/types.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/types.mjs.map +1 -1
- package/dist/lib/server/decisioning/resolve-presets.mjs +0 -6
- package/dist/lib/server/decisioning/resolve-presets.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/observed-modes.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/observed-modes.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/validate-platform.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/validate-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/specialisms/audiences.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/brand-rights.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/campaign-governance.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/content-standards.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/creative.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/lists.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/sales.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/signals.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.mjs +0 -6
- package/dist/lib/server/decisioning/start-time.mjs +0 -6
- package/dist/lib/server/decisioning/start-time.mjs.map +1 -1
- package/dist/lib/server/decisioning/status-changes.mjs +0 -6
- package/dist/lib/server/decisioning/status-changes.mjs.map +1 -1
- package/dist/lib/server/decisioning/status-mappers.mjs +0 -6
- package/dist/lib/server/decisioning/status-mappers.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-registry.mjs +0 -6
- package/dist/lib/server/decisioning/tenant-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-store.mjs +0 -6
- package/dist/lib/server/decisioning/tenant-store.mjs.map +1 -1
- package/dist/lib/server/decisioning/validate-specialisms.mjs +0 -6
- package/dist/lib/server/decisioning/validate-specialisms.mjs.map +1 -1
- package/dist/lib/server/dynamic-registry.mjs +0 -6
- package/dist/lib/server/dynamic-registry.mjs.map +1 -1
- package/dist/lib/server/envelope-allowlist.mjs +0 -6
- package/dist/lib/server/envelope-allowlist.mjs.map +1 -1
- package/dist/lib/server/error-arm-tools.mjs +5 -6
- package/dist/lib/server/error-arm-tools.mjs.map +1 -1
- package/dist/lib/server/errors.mjs +0 -6
- package/dist/lib/server/errors.mjs.map +1 -1
- package/dist/lib/server/example-tld-guard.mjs +0 -6
- package/dist/lib/server/example-tld-guard.mjs.map +1 -1
- package/dist/lib/server/express-adapter.mjs +0 -6
- package/dist/lib/server/express-adapter.mjs.map +1 -1
- package/dist/lib/server/governance.mjs +0 -6
- package/dist/lib/server/governance.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/lazy.mjs +0 -6
- package/dist/lib/server/idempotency/backends/lazy.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/memory.mjs +0 -6
- package/dist/lib/server/idempotency/backends/memory.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/pg.mjs +0 -6
- package/dist/lib/server/idempotency/backends/pg.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/redis.mjs +0 -6
- package/dist/lib/server/idempotency/backends/redis.mjs.map +1 -1
- package/dist/lib/server/idempotency/index.mjs +0 -6
- package/dist/lib/server/idempotency/index.mjs.map +1 -1
- package/dist/lib/server/idempotency/store.mjs +0 -6
- package/dist/lib/server/idempotency/store.mjs.map +1 -1
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/index.mjs +0 -6
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/server/legacy/v5/index.d.mts +1 -1
- package/dist/lib/server/legacy/v5/index.d.ts +1 -1
- package/dist/lib/server/legacy/v5/index.d.ts.map +1 -1
- package/dist/lib/server/legacy/v5/index.js.map +1 -1
- package/dist/lib/server/legacy/v5/index.mjs +0 -6
- package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
- package/dist/lib/server/mcp-modern-server.mjs +0 -6
- package/dist/lib/server/mcp-modern-server.mjs.map +1 -1
- package/dist/lib/server/media-buy-actions.mjs +0 -6
- package/dist/lib/server/media-buy-actions.mjs.map +1 -1
- package/dist/lib/server/media-buy-helpers.mjs +0 -6
- package/dist/lib/server/media-buy-helpers.mjs.map +1 -1
- package/dist/lib/server/media-buy-store.mjs +0 -6
- package/dist/lib/server/media-buy-store.mjs.map +1 -1
- package/dist/lib/server/normalize-errors.mjs +0 -6
- package/dist/lib/server/normalize-errors.mjs.map +1 -1
- package/dist/lib/server/operational-platform.mjs +0 -6
- package/dist/lib/server/operational-platform.mjs.map +1 -1
- package/dist/lib/server/pick-safe-details.mjs +0 -6
- package/dist/lib/server/pick-safe-details.mjs.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.mjs +0 -6
- package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
- package/dist/lib/server/postgres-state-store.mjs +0 -6
- package/dist/lib/server/postgres-state-store.mjs.map +1 -1
- package/dist/lib/server/postgres-task-store.mjs +0 -6
- package/dist/lib/server/postgres-task-store.mjs.map +1 -1
- package/dist/lib/server/product-defaults.mjs +0 -6
- package/dist/lib/server/product-defaults.mjs.map +1 -1
- package/dist/lib/server/redact.mjs +0 -6
- package/dist/lib/server/redact.mjs.map +1 -1
- package/dist/lib/server/responses.mjs +0 -6
- package/dist/lib/server/responses.mjs.map +1 -1
- package/dist/lib/server/serve.mjs +0 -6
- package/dist/lib/server/serve.mjs.map +1 -1
- package/dist/lib/server/socket-mode/conformance-client.mjs +0 -6
- package/dist/lib/server/socket-mode/conformance-client.mjs.map +1 -1
- package/dist/lib/server/socket-mode/index.mjs +0 -6
- package/dist/lib/server/socket-mode/index.mjs.map +1 -1
- package/dist/lib/server/socket-mode/ws-transport.mjs +0 -6
- package/dist/lib/server/socket-mode/ws-transport.mjs.map +1 -1
- package/dist/lib/server/state-machine.mjs +0 -6
- package/dist/lib/server/state-machine.mjs.map +1 -1
- package/dist/lib/server/state-store.mjs +0 -6
- package/dist/lib/server/state-store.mjs.map +1 -1
- package/dist/lib/server/structured-serialize.mjs +0 -6
- package/dist/lib/server/structured-serialize.mjs.map +1 -1
- package/dist/lib/server/targeting-helpers.mjs +0 -6
- package/dist/lib/server/targeting-helpers.mjs.map +1 -1
- package/dist/lib/server/tasks.mjs +0 -6
- package/dist/lib/server/tasks.mjs.map +1 -1
- package/dist/lib/server/test-controller-bridge.mjs +0 -6
- package/dist/lib/server/test-controller-bridge.mjs.map +1 -1
- package/dist/lib/server/test-controller.mjs +0 -6
- package/dist/lib/server/test-controller.mjs.map +1 -1
- package/dist/lib/server/upstream-helpers.mjs +0 -6
- package/dist/lib/server/upstream-helpers.mjs.map +1 -1
- package/dist/lib/server/webhook-emitter.mjs +0 -6
- package/dist/lib/server/webhook-emitter.mjs.map +1 -1
- package/dist/lib/server/wire-safe.mjs +0 -6
- package/dist/lib/server/wire-safe.mjs.map +1 -1
- package/dist/lib/server/wire-spec-fields.generated.mjs +0 -6
- package/dist/lib/server/wire-spec-fields.generated.mjs.map +1 -1
- package/dist/lib/server/wrap-envelope.mjs +0 -6
- package/dist/lib/server/wrap-envelope.mjs.map +1 -1
- package/dist/lib/signing/agent-context.mjs +0 -6
- package/dist/lib/signing/agent-context.mjs.map +1 -1
- package/dist/lib/signing/agent-fetch.mjs +0 -6
- package/dist/lib/signing/agent-fetch.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/canonicalize.mjs +0 -6
- package/dist/lib/signing/agent-resolver/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs +0 -6
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -6
- package/dist/lib/signing/agent-resolver/consistency.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/errors.mjs +0 -6
- package/dist/lib/signing/agent-resolver/errors.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/etld.mjs +0 -6
- package/dist/lib/signing/agent-resolver/etld.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +0 -6
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/index.mjs +0 -6
- package/dist/lib/signing/agent-resolver/index.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +0 -6
- package/dist/lib/signing/agent-resolver/jwks-set.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/select-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/select-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/strict-json.mjs +0 -6
- package/dist/lib/signing/agent-resolver/strict-json.mjs.map +1 -1
- package/dist/lib/signing/brand-jwks.mjs +0 -6
- package/dist/lib/signing/brand-jwks.mjs.map +1 -1
- package/dist/lib/signing/canonicalize.mjs +0 -6
- package/dist/lib/signing/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/capability-cache.mjs +0 -6
- package/dist/lib/signing/capability-cache.mjs.map +1 -1
- package/dist/lib/signing/capability-priming.mjs +0 -6
- package/dist/lib/signing/capability-priming.mjs.map +1 -1
- package/dist/lib/signing/client.mjs +0 -6
- package/dist/lib/signing/client.mjs.map +1 -1
- package/dist/lib/signing/content-digest.mjs +0 -6
- package/dist/lib/signing/content-digest.mjs.map +1 -1
- package/dist/lib/signing/crypto.mjs +0 -6
- package/dist/lib/signing/crypto.mjs.map +1 -1
- package/dist/lib/signing/ecdsa-encoding.mjs +0 -6
- package/dist/lib/signing/ecdsa-encoding.mjs.map +1 -1
- package/dist/lib/signing/errors.mjs +0 -6
- package/dist/lib/signing/errors.mjs.map +1 -1
- package/dist/lib/signing/fetch-async.mjs +0 -6
- package/dist/lib/signing/fetch-async.mjs.map +1 -1
- package/dist/lib/signing/fetch.mjs +0 -6
- package/dist/lib/signing/fetch.mjs.map +1 -1
- package/dist/lib/signing/index.mjs +0 -6
- package/dist/lib/signing/index.mjs.map +1 -1
- package/dist/lib/signing/jwks-helpers.mjs +0 -6
- package/dist/lib/signing/jwks-helpers.mjs.map +1 -1
- package/dist/lib/signing/jwks-https.mjs +0 -6
- package/dist/lib/signing/jwks-https.mjs.map +1 -1
- package/dist/lib/signing/jwks.mjs +0 -6
- package/dist/lib/signing/jwks.mjs.map +1 -1
- package/dist/lib/signing/middleware.mjs +0 -6
- package/dist/lib/signing/middleware.mjs.map +1 -1
- package/dist/lib/signing/parser.mjs +0 -6
- package/dist/lib/signing/parser.mjs.map +1 -1
- package/dist/lib/signing/postgres-replay-store.mjs +0 -6
- package/dist/lib/signing/postgres-replay-store.mjs.map +1 -1
- package/dist/lib/signing/protocol-response.mjs +0 -6
- package/dist/lib/signing/protocol-response.mjs.map +1 -1
- package/dist/lib/signing/provider.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs.map +1 -1
- package/dist/lib/signing/replay.mjs +0 -6
- package/dist/lib/signing/replay.mjs.map +1 -1
- package/dist/lib/signing/request-context.mjs +0 -6
- package/dist/lib/signing/request-context.mjs.map +1 -1
- package/dist/lib/signing/revocation-https.mjs +0 -6
- package/dist/lib/signing/revocation-https.mjs.map +1 -1
- package/dist/lib/signing/revocation.mjs +0 -6
- package/dist/lib/signing/revocation.mjs.map +1 -1
- package/dist/lib/signing/server.mjs +0 -6
- package/dist/lib/signing/server.mjs.map +1 -1
- package/dist/lib/signing/signer-async.mjs +0 -6
- package/dist/lib/signing/signer-async.mjs.map +1 -1
- package/dist/lib/signing/signer.mjs +0 -6
- package/dist/lib/signing/signer.mjs.map +1 -1
- package/dist/lib/signing/testing.mjs +0 -6
- package/dist/lib/signing/testing.mjs.map +1 -1
- package/dist/lib/signing/types.mjs +0 -6
- package/dist/lib/signing/types.mjs.map +1 -1
- package/dist/lib/signing/verifier.mjs +0 -6
- package/dist/lib/signing/verifier.mjs.map +1 -1
- package/dist/lib/signing/webhook-auth-detection.mjs +0 -6
- package/dist/lib/signing/webhook-auth-detection.mjs.map +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +0 -6
- package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
- package/dist/lib/storage/MemoryStorage.mjs +0 -6
- package/dist/lib/storage/MemoryStorage.mjs.map +1 -1
- package/dist/lib/storage/interfaces.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs.map +1 -1
- package/dist/lib/substitution/encoder/index.mjs +0 -6
- package/dist/lib/substitution/encoder/index.mjs.map +1 -1
- package/dist/lib/substitution/index.mjs +0 -6
- package/dist/lib/substitution/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs +0 -6
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs.map +1 -1
- package/dist/lib/substitution/observer/alignment.mjs +0 -6
- package/dist/lib/substitution/observer/alignment.mjs.map +1 -1
- package/dist/lib/substitution/observer/assertions.mjs +0 -6
- package/dist/lib/substitution/observer/assertions.mjs.map +1 -1
- package/dist/lib/substitution/observer/html-parser.mjs +0 -6
- package/dist/lib/substitution/observer/html-parser.mjs.map +1 -1
- package/dist/lib/substitution/observer/index.mjs +0 -6
- package/dist/lib/substitution/observer/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/ssrf.mjs +0 -6
- package/dist/lib/substitution/observer/ssrf.mjs.map +1 -1
- package/dist/lib/substitution/rfc3986.mjs +0 -6
- package/dist/lib/substitution/rfc3986.mjs.map +1 -1
- package/dist/lib/substitution/translate.mjs +0 -6
- package/dist/lib/substitution/translate.mjs.map +1 -1
- package/dist/lib/substitution/types.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs.map +1 -1
- package/dist/lib/testing/agent-tester.mjs +0 -6
- package/dist/lib/testing/agent-tester.mjs.map +1 -1
- package/dist/lib/testing/client.mjs +0 -6
- package/dist/lib/testing/client.mjs.map +1 -1
- package/dist/lib/testing/compliance/briefs.mjs +0 -6
- package/dist/lib/testing/compliance/briefs.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +0 -6
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/compliance/index.mjs +0 -6
- package/dist/lib/testing/compliance/index.mjs.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.mjs +0 -6
- package/dist/lib/testing/compliance/spec-conformance.mjs.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.mjs +0 -6
- package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
- package/dist/lib/testing/compliance/summary.mjs +0 -6
- package/dist/lib/testing/compliance/summary.mjs.map +1 -1
- package/dist/lib/testing/compliance/types.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs.map +1 -1
- package/dist/lib/testing/controller-assertions.mjs +0 -6
- package/dist/lib/testing/controller-assertions.mjs.map +1 -1
- package/dist/lib/testing/formatter.mjs +0 -6
- package/dist/lib/testing/formatter.mjs.map +1 -1
- package/dist/lib/testing/index.mjs +0 -6
- package/dist/lib/testing/index.mjs.map +1 -1
- package/dist/lib/testing/local-agent-runner.mjs +0 -6
- package/dist/lib/testing/local-agent-runner.mjs.map +1 -1
- package/dist/lib/testing/orchestrator.mjs +0 -6
- package/dist/lib/testing/orchestrator.mjs.map +1 -1
- package/dist/lib/testing/personas/index.mjs +0 -6
- package/dist/lib/testing/personas/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/brand-rights.mjs +0 -6
- package/dist/lib/testing/scenarios/brand-rights.mjs.map +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +0 -6
- package/dist/lib/testing/scenarios/capabilities.mjs.map +1 -1
- package/dist/lib/testing/scenarios/creative.mjs +0 -6
- package/dist/lib/testing/scenarios/creative.mjs.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.mjs +0 -6
- package/dist/lib/testing/scenarios/deterministic.mjs.map +1 -1
- package/dist/lib/testing/scenarios/discovery.mjs +0 -6
- package/dist/lib/testing/scenarios/discovery.mjs.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.mjs +0 -6
- package/dist/lib/testing/scenarios/edge-cases.mjs.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/error-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/governance.mjs +0 -6
- package/dist/lib/testing/scenarios/governance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/health.mjs +0 -6
- package/dist/lib/testing/scenarios/health.mjs.map +1 -1
- package/dist/lib/testing/scenarios/index.mjs +0 -6
- package/dist/lib/testing/scenarios/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.mjs +0 -6
- package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/schema-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/signals.mjs +0 -6
- package/dist/lib/testing/scenarios/signals.mjs.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs +0 -6
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs.map +1 -1
- package/dist/lib/testing/scenarios/trusted-match.mjs +0 -6
- package/dist/lib/testing/scenarios/trusted-match.mjs.map +1 -1
- package/dist/lib/testing/seed-merge.mjs +0 -6
- package/dist/lib/testing/seed-merge.mjs.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.mjs +0 -6
- package/dist/lib/testing/storyboard/agent-routing.mjs.map +1 -1
- package/dist/lib/testing/storyboard/assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs +0 -6
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs.map +1 -1
- package/dist/lib/testing/storyboard/compliance.mjs +5 -6
- package/dist/lib/testing/storyboard/compliance.mjs.map +1 -1
- package/dist/lib/testing/storyboard/context.mjs +0 -6
- package/dist/lib/testing/storyboard/context.mjs.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.mjs +0 -6
- package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs +0 -6
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/junit.mjs +0 -6
- package/dist/lib/testing/storyboard/junit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/loader.mjs +0 -6
- package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/path.mjs +0 -6
- package/dist/lib/testing/storyboard/path.mjs.map +1 -1
- package/dist/lib/testing/storyboard/probes.mjs +0 -6
- package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs +0 -6
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rejection-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/rejection-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +0 -6
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/sandbox-entities.mjs +0 -6
- package/dist/lib/testing/storyboard/sandbox-entities.mjs.map +1 -1
- package/dist/lib/testing/storyboard/seeding.mjs +0 -6
- package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/index.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/types.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +0 -6
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs +0 -6
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.mjs +0 -6
- package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
- package/dist/lib/testing/stubs/governance-agent-stub.mjs +0 -6
- package/dist/lib/testing/stubs/governance-agent-stub.mjs.map +1 -1
- package/dist/lib/testing/stubs/index.mjs +0 -6
- package/dist/lib/testing/stubs/index.mjs.map +1 -1
- package/dist/lib/testing/test-controller.mjs +0 -6
- package/dist/lib/testing/test-controller.mjs.map +1 -1
- package/dist/lib/testing/test-helpers.mjs +0 -6
- package/dist/lib/testing/test-helpers.mjs.map +1 -1
- package/dist/lib/testing/types.mjs +0 -6
- package/dist/lib/types/adcp.mjs +0 -6
- package/dist/lib/types/adcp.mjs.map +1 -1
- package/dist/lib/types/asset-instances.mjs +0 -6
- package/dist/lib/types/compat.mjs +0 -6
- package/dist/lib/types/compat.mjs.map +1 -1
- package/dist/lib/types/core.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs.map +1 -1
- package/dist/lib/types/error-codes.mjs +0 -6
- package/dist/lib/types/error-codes.mjs.map +1 -1
- package/dist/lib/types/error-details.aliases.mjs +0 -6
- package/dist/lib/types/error-details.aliases.mjs.map +1 -1
- package/dist/lib/types/format-asset-slots.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs.map +1 -1
- package/dist/lib/types/index.mjs +0 -6
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/types/inline-enums.aliases.mjs +0 -6
- package/dist/lib/types/inline-enums.aliases.mjs.map +1 -1
- package/dist/lib/types/inline-enums.generated.mjs +0 -6
- package/dist/lib/types/inline-enums.generated.mjs.map +1 -1
- package/dist/lib/types/manifest.generated.mjs +0 -6
- package/dist/lib/types/manifest.generated.mjs.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +0 -6
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.mjs +0 -6
- package/dist/lib/types/server-payload.mjs +0 -6
- package/dist/lib/types/sync-rows.mjs +0 -6
- package/dist/lib/types/tools.generated.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs.map +1 -1
- package/dist/lib/types/v2-5/tools.generated.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs.map +1 -1
- package/dist/lib/types/v3-1-beta/tools.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs.map +1 -1
- package/dist/lib/upstream-recorder/constants.mjs +0 -6
- package/dist/lib/upstream-recorder/constants.mjs.map +1 -1
- package/dist/lib/upstream-recorder/index.mjs +0 -6
- package/dist/lib/upstream-recorder/index.mjs.map +1 -1
- package/dist/lib/upstream-recorder/recorder.mjs +0 -6
- package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
- package/dist/lib/upstream-recorder/types.mjs +0 -6
- package/dist/lib/upstream-recorder/types.mjs.map +1 -1
- package/dist/lib/utils/a2a-artifacts.mjs +0 -6
- package/dist/lib/utils/a2a-artifacts.mjs.map +1 -1
- package/dist/lib/utils/a2a-discovery.mjs +0 -6
- package/dist/lib/utils/a2a-discovery.mjs.map +1 -1
- package/dist/lib/utils/activation-key-builders.mjs +0 -6
- package/dist/lib/utils/activation-key-builders.mjs.map +1 -1
- package/dist/lib/utils/adcp-version-config.mjs +0 -6
- package/dist/lib/utils/adcp-version-config.mjs.map +1 -1
- package/dist/lib/utils/asset-builders.mjs +0 -6
- package/dist/lib/utils/asset-builders.mjs.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.mjs +0 -6
- package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
- package/dist/lib/utils/buyer-retry-policy.mjs +0 -6
- package/dist/lib/utils/buyer-retry-policy.mjs.map +1 -1
- package/dist/lib/utils/capabilities.mjs +0 -6
- package/dist/lib/utils/capabilities.mjs.map +1 -1
- package/dist/lib/utils/capability-rollups.mjs +0 -6
- package/dist/lib/utils/capability-rollups.mjs.map +1 -1
- package/dist/lib/utils/creative-adapter.mjs +0 -6
- package/dist/lib/utils/creative-adapter.mjs.map +1 -1
- package/dist/lib/utils/creative-delivery.mjs +0 -6
- package/dist/lib/utils/creative-delivery.mjs.map +1 -1
- package/dist/lib/utils/deprecation.mjs +0 -6
- package/dist/lib/utils/deprecation.mjs.map +1 -1
- package/dist/lib/utils/envelope-status-compat.mjs +0 -6
- package/dist/lib/utils/envelope-status-compat.mjs.map +1 -1
- package/dist/lib/utils/error-extraction.mjs +0 -6
- package/dist/lib/utils/error-extraction.mjs.map +1 -1
- package/dist/lib/utils/extract-result.mjs +0 -6
- package/dist/lib/utils/extract-result.mjs.map +1 -1
- package/dist/lib/utils/format-asset-slot-builders.mjs +0 -6
- package/dist/lib/utils/format-asset-slot-builders.mjs.map +1 -1
- package/dist/lib/utils/format-assets.mjs +0 -6
- package/dist/lib/utils/format-assets.mjs.map +1 -1
- package/dist/lib/utils/format-render-builders.mjs +0 -6
- package/dist/lib/utils/format-render-builders.mjs.map +1 -1
- package/dist/lib/utils/format-renders.mjs +0 -6
- package/dist/lib/utils/format-renders.mjs.map +1 -1
- package/dist/lib/utils/get-products-cache-scope.mjs +0 -6
- package/dist/lib/utils/get-products-cache-scope.mjs.map +1 -1
- package/dist/lib/utils/glob.mjs +0 -6
- package/dist/lib/utils/glob.mjs.map +1 -1
- package/dist/lib/utils/global-async-local-storage.mjs +0 -6
- package/dist/lib/utils/global-async-local-storage.mjs.map +1 -1
- package/dist/lib/utils/idempotency.mjs +0 -6
- package/dist/lib/utils/idempotency.mjs.map +1 -1
- package/dist/lib/utils/index.mjs +0 -6
- package/dist/lib/utils/index.mjs.map +1 -1
- package/dist/lib/utils/jcs.mjs +0 -6
- package/dist/lib/utils/jcs.mjs.map +1 -1
- package/dist/lib/utils/json-depth.mjs +0 -6
- package/dist/lib/utils/json-depth.mjs.map +1 -1
- package/dist/lib/utils/logger.mjs +0 -6
- package/dist/lib/utils/logger.mjs.map +1 -1
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs +0 -6
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs.map +1 -1
- package/dist/lib/utils/media-buy-status.mjs +0 -6
- package/dist/lib/utils/media-buy-status.mjs.map +1 -1
- package/dist/lib/utils/pagination.mjs +0 -6
- package/dist/lib/utils/pagination.mjs.map +1 -1
- package/dist/lib/utils/preview-creative-builders.mjs +0 -6
- package/dist/lib/utils/preview-creative-builders.mjs.map +1 -1
- package/dist/lib/utils/preview-normalizer.mjs +0 -6
- package/dist/lib/utils/preview-normalizer.mjs.map +1 -1
- package/dist/lib/utils/preview-utils.mjs +0 -6
- package/dist/lib/utils/preview-utils.mjs.map +1 -1
- package/dist/lib/utils/pricing-adapter.mjs +0 -6
- package/dist/lib/utils/pricing-adapter.mjs.map +1 -1
- package/dist/lib/utils/probe-policy.mjs +0 -6
- package/dist/lib/utils/probe-policy.mjs.map +1 -1
- package/dist/lib/utils/protocol-detection.mjs +0 -6
- package/dist/lib/utils/protocol-detection.mjs.map +1 -1
- package/dist/lib/utils/redact-secrets.mjs +0 -6
- package/dist/lib/utils/redact-secrets.mjs.map +1 -1
- package/dist/lib/utils/redis-default-prefix-warn.mjs +0 -6
- package/dist/lib/utils/redis-default-prefix-warn.mjs.map +1 -1
- package/dist/lib/utils/render-builders.mjs +0 -6
- package/dist/lib/utils/render-builders.mjs.map +1 -1
- package/dist/lib/utils/request-normalizer.mjs +0 -6
- package/dist/lib/utils/request-normalizer.mjs.map +1 -1
- package/dist/lib/utils/response-schemas.mjs +0 -6
- package/dist/lib/utils/response-schemas.mjs.map +1 -1
- package/dist/lib/utils/response-unwrapper.mjs +0 -6
- package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
- package/dist/lib/utils/retry.mjs +0 -6
- package/dist/lib/utils/retry.mjs.map +1 -1
- package/dist/lib/utils/signal-discovery-helpers.mjs +0 -6
- package/dist/lib/utils/signal-discovery-helpers.mjs.map +1 -1
- package/dist/lib/utils/signal-id-builders.mjs +0 -6
- package/dist/lib/utils/signal-id-builders.mjs.map +1 -1
- package/dist/lib/utils/sync-creatives-adapter.mjs +0 -6
- package/dist/lib/utils/sync-creatives-adapter.mjs.map +1 -1
- package/dist/lib/utils/task-state.mjs +0 -6
- package/dist/lib/utils/task-state.mjs.map +1 -1
- package/dist/lib/utils/tool-request-schemas.mjs +0 -6
- package/dist/lib/utils/tool-request-schemas.mjs.map +1 -1
- package/dist/lib/utils/typeGuards.mjs +0 -6
- package/dist/lib/utils/typeGuards.mjs.map +1 -1
- package/dist/lib/utils/union-errors.mjs +0 -6
- package/dist/lib/utils/union-errors.mjs.map +1 -1
- package/dist/lib/utils/validate-user-agent.mjs +0 -6
- package/dist/lib/utils/validate-user-agent.mjs.map +1 -1
- package/dist/lib/v2/format-schema/fetch.mjs +0 -6
- package/dist/lib/v2/format-schema/fetch.mjs.map +1 -1
- package/dist/lib/v2/format-schema/index.mjs +0 -6
- package/dist/lib/v2/format-schema/index.mjs.map +1 -1
- package/dist/lib/v2/format-schema/regex-safety.mjs +0 -6
- package/dist/lib/v2/format-schema/regex-safety.mjs.map +1 -1
- package/dist/lib/v2/format-schema/resolver.mjs +7 -8
- package/dist/lib/v2/format-schema/resolver.mjs.map +1 -1
- package/dist/lib/v2/format-schema/sandbox-refs.mjs +0 -6
- package/dist/lib/v2/format-schema/sandbox-refs.mjs.map +1 -1
- package/dist/lib/v2/projection/augment-response.mjs +0 -6
- package/dist/lib/v2/projection/augment-response.mjs.map +1 -1
- package/dist/lib/v2/projection/builders.mjs +0 -6
- package/dist/lib/v2/projection/builders.mjs.map +1 -1
- package/dist/lib/v2/projection/cache-versions.mjs +0 -6
- package/dist/lib/v2/projection/cache-versions.mjs.map +1 -1
- package/dist/lib/v2/projection/canonical-properties.mjs +5 -6
- package/dist/lib/v2/projection/canonical-properties.mjs.map +1 -1
- package/dist/lib/v2/projection/catalog.mjs +5 -6
- package/dist/lib/v2/projection/catalog.mjs.map +1 -1
- package/dist/lib/v2/projection/constants.mjs +0 -6
- package/dist/lib/v2/projection/constants.mjs.map +1 -1
- package/dist/lib/v2/projection/index.mjs +0 -6
- package/dist/lib/v2/projection/index.mjs.map +1 -1
- package/dist/lib/v2/projection/registry.mjs +5 -6
- package/dist/lib/v2/projection/registry.mjs.map +1 -1
- package/dist/lib/v2/projection/types.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs.map +1 -1
- package/dist/lib/v2/projection/v2-to-v1.mjs +0 -6
- package/dist/lib/v2/projection/v2-to-v1.mjs.map +1 -1
- package/dist/lib/v2/projection/write-side.mjs +0 -6
- package/dist/lib/v2/projection/write-side.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/formats.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/formats.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/index.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/index.mjs.map +1 -1
- package/dist/lib/validation/client-hooks.mjs +0 -6
- package/dist/lib/validation/client-hooks.mjs.map +1 -1
- package/dist/lib/validation/hints.mjs +0 -6
- package/dist/lib/validation/hints.mjs.map +1 -1
- package/dist/lib/validation/index.mjs +0 -6
- package/dist/lib/validation/index.mjs.map +1 -1
- package/dist/lib/validation/schema-errors.mjs +0 -6
- package/dist/lib/validation/schema-errors.mjs.map +1 -1
- package/dist/lib/validation/schema-loader.mjs +5 -6
- package/dist/lib/validation/schema-loader.mjs.map +1 -1
- package/dist/lib/validation/schema-validator.mjs +0 -6
- package/dist/lib/validation/schema-validator.mjs.map +1 -1
- package/dist/lib/validation/sync-creatives.mjs +0 -6
- package/dist/lib/validation/sync-creatives.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -9
- package/dist/lib/version.mjs.map +1 -1
- package/dist/lib/webhooks/index.mjs +0 -6
- package/dist/lib/webhooks/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/index.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/sync.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/sync.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/types.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/pin-and-bind-fetch.ts"],"sourcesContent":["/**\n * Pin-and-bind fetch — DNS-rebinding-resistant `fetch` for outbound webhook\n * delivery and other callbacks where the destination is buyer-supplied.\n *\n * The problem: validating a `push_notification_config.url`'s LITERAL hostname\n * against an SSRF deny list does not protect against a DNS-rebinding attack.\n * A buyer can register `https://rebind.attacker.com/`, pass the literal-host\n * check, and then flip the A-record TTL to `169.254.169.254` (cloud metadata)\n * or `127.0.0.1` (loopback) before the webhook fires. Node's `fetch` resolves\n * the host fresh at request time, gets the rebound IP, and posts the\n * signed payload to the attacker-controlled destination.\n *\n * The fix: control DNS resolution and connect-target inside the fetch itself.\n * 1. Resolve the hostname.\n * 2. Validate every resolved IP against the SSRF policy (CIDR deny lists,\n * metadata-host names, scheme rules).\n * 3. Pin the connection to the validated IP — undici opens TCP/TLS to that\n * specific address, but the original hostname is preserved for TLS SNI\n * and the `Host:` header so HTTPS routing still works.\n *\n * Implementation note: undici's `Agent` accepts a `connect.lookup` callback\n * with the same signature as `dns.lookup`. We hook the callback, resolve via\n * `dns.lookup({ all: true })` to see EVERY address the host can reach, run\n * the resolved IPs through {@link enforceSsrfPolicyResolved}, and only return\n * a pinned address when every resolved IP is in an allowed range. This\n * matches the AdCP substitution-observer SSRF contract — a host that\n * resolves to *any* denied IP is treated as suspect and rejected wholesale.\n */\n\nimport { Agent, fetch as undiciFetch, type Dispatcher } from 'undici';\nimport { lookup as nativeDnsLookup, type LookupAddress } from 'node:dns';\nimport { isIPv6 } from 'node:net';\n\nimport { enforceSsrfPolicy, enforceSsrfPolicyResolved } from '../substitution/observer/ssrf';\nimport type { SsrfPolicy } from '../substitution/types';\n\n/**\n * Default SSRF policy for outbound webhook delivery. Stricter than\n * `DEFAULT_SSRF_POLICY` (substitution observer) only in that it allows\n * `host_literal_policy: 'allow'` — webhook URLs MAY use IP literals\n * (e.g. `https://203.0.113.10/cb`) as long as the IP is not in a denied\n * CIDR range. Schemes restricted to https; signed webhooks SHOULD be\n * delivered over TLS.\n *\n * For storyboard / in-process tests where the receiver runs on\n * `http://127.0.0.1:port`, use {@link LOOPBACK_OK_WEBHOOK_SSRF_POLICY}\n * instead. That preset relaxes only the loopback + http rules and keeps\n * every other deny range — adopters get most of the SSRF protection\n * during tests without disabling pin-and-bind entirely.\n */\nexport const WEBHOOK_SSRF_POLICY: SsrfPolicy = Object.freeze({\n schemes_allowed: Object.freeze(['https']),\n schemes_denied: Object.freeze(['http', 'file', 'gopher', 'ftp', 'ftps', 'data', 'javascript', 'about', 'ws', 'wss']),\n hosts_denied_ipv4_cidrs: Object.freeze([\n '0.0.0.0/8',\n '10.0.0.0/8',\n '100.64.0.0/10',\n '127.0.0.0/8',\n '169.254.0.0/16',\n '172.16.0.0/12',\n '192.0.0.0/24',\n '192.168.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n ]),\n hosts_denied_ipv6_cidrs: Object.freeze([\n '::1/128',\n '::/128',\n '::ffff:0:0/96',\n '64:ff9b::/96',\n 'fc00::/7',\n 'fe80::/10',\n 'ff00::/8',\n ]),\n hosts_denied_metadata: Object.freeze([\n 'metadata.google.internal',\n 'metadata',\n 'metadata.packet.net',\n 'fd00:ec2::254',\n ]),\n host_literal_policy: 'allow',\n}) as SsrfPolicy;\n\n/**\n * Pin-and-bind policy that allows http URLs and IPv4/IPv6 loopback so\n * adopters can enable pin-and-bind for production webhook delivery while\n * keeping storyboard / in-process tests working — `createWebhookReceiver`\n * listens on `http://127.0.0.1:port`. Every other private CIDR, metadata\n * host, and link-local range is still denied, so loopback is the only\n * relaxation. Pass to `createPinAndBindFetch({ policy })` from your test\n * fixture or storyboard runner harness; do NOT use in production.\n */\nexport const LOOPBACK_OK_WEBHOOK_SSRF_POLICY: SsrfPolicy = Object.freeze({\n schemes_allowed: Object.freeze(['http', 'https']),\n schemes_denied: Object.freeze(['file', 'gopher', 'ftp', 'ftps', 'data', 'javascript', 'about', 'ws', 'wss']),\n hosts_denied_ipv4_cidrs: Object.freeze([\n '0.0.0.0/8',\n '10.0.0.0/8',\n '100.64.0.0/10',\n // 127.0.0.0/8 omitted — loopback allowed for tests.\n '169.254.0.0/16',\n '172.16.0.0/12',\n '192.0.0.0/24',\n '192.168.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n ]),\n hosts_denied_ipv6_cidrs: Object.freeze([\n // ::1/128 and ::/128 omitted — IPv6 loopback allowed for tests.\n '::ffff:0:0/96',\n '64:ff9b::/96',\n 'fc00::/7',\n 'fe80::/10',\n 'ff00::/8',\n ]),\n hosts_denied_metadata: Object.freeze([\n 'metadata.google.internal',\n 'metadata',\n 'metadata.packet.net',\n 'fd00:ec2::254',\n ]),\n host_literal_policy: 'allow',\n}) as SsrfPolicy;\n\n/**\n * Signature of the lookup callback `dns.lookup` accepts. Re-declared here\n * because the native type from `node:dns` is a complex overload set; the\n * shape we actually need is the all=true variant the Agent expects.\n */\nexport type DnsLookupAll = (\n hostname: string,\n options: { family?: number; hints?: number; all: true; verbatim?: boolean },\n callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void\n) => void;\n\nexport interface PinAndBindFetchOptions {\n /**\n * SSRF policy to enforce against resolved IPs. Defaults to\n * {@link WEBHOOK_SSRF_POLICY} (https-only, all private/loopback/metadata\n * ranges denied, IP literals allowed).\n */\n policy?: SsrfPolicy;\n /**\n * Override the underlying DNS lookup. Default uses `dns.lookup` with\n * `all: true`. Tests inject a stub to simulate rebinding attacks without\n * touching real DNS.\n */\n lookup?: DnsLookupAll;\n /**\n * Forward to `Agent` — connect-attempt timeout, TLS options, etc. Cannot\n * override `lookup`; that is wired by this helper.\n */\n agentOptions?: Omit<Agent.Options, 'connect'> & {\n connect?: Omit<NonNullable<Agent.Options['connect']>, 'lookup'>;\n };\n}\n\nconst DEFAULT_LOOKUP_ALL: DnsLookupAll = (hostname, options, callback) => {\n nativeDnsLookup(hostname, { ...options, all: true }, (err, addresses) => {\n callback(err, addresses as LookupAddress[]);\n });\n};\n\n/**\n * Build a `fetch` that pins outbound connections to the IPs the SSRF policy\n * allows, defeating DNS-rebinding attacks against per-attempt DNS resolution.\n *\n * This is the default `fetch` for `createWebhookEmitter` /\n * `createAdcpServer({ webhooks })`, so production webhook delivery is\n * rebinding-protected without extra wiring. Call it explicitly only to\n * override the policy — e.g. {@link LOOPBACK_OK_WEBHOOK_SSRF_POLICY} for\n * storyboard tests that deliver to a loopback http receiver. See\n * `docs/guides/SIGNING-GUIDE.md` § Webhook SSRF defense.\n *\n * Construct once per emitter and reuse — each call instantiates a fresh\n * `undici.Agent` with its own connection pool.\n *\n * @example\n * ```ts\n * import { createWebhookEmitter, createPinAndBindFetch } from '@adcp/sdk/server';\n *\n * const emitter = createWebhookEmitter({\n * signerKey: webhookKey,\n * fetch: createPinAndBindFetch(),\n * });\n * ```\n */\nexport function createPinAndBindFetch(options: PinAndBindFetchOptions = {}): typeof fetch {\n const policy = options.policy ?? WEBHOOK_SSRF_POLICY;\n const lookupImpl = options.lookup ?? DEFAULT_LOOKUP_ALL;\n\n const guardedLookup = (\n hostname: string,\n opts: { family?: number; hints?: number; all?: boolean; verbatim?: boolean } | undefined,\n callback: (err: NodeJS.ErrnoException | null, addressOrAll?: string | LookupAddress[], family?: number) => void\n ): void => {\n const wantsAll = opts?.all === true;\n lookupImpl(hostname, { ...(opts ?? {}), all: true }, (err, addresses) => {\n if (err) {\n callback(err);\n return;\n }\n if (!Array.isArray(addresses) || addresses.length === 0) {\n callback(\n makeSsrfError(`DNS resolution returned no addresses for ${hostname}`, 'dns_revalidation:no_addresses')\n );\n return;\n }\n\n // The URL passed in here only matters for scheme + hostname checks,\n // both of which were already validated synchronously by undici when\n // the request started. We re-build a placeholder URL to feed the\n // resolved-address rule, which is the load-bearing check for\n // rebinding defense.\n const url = new URL(`https://${bracketIfV6(hostname)}`);\n const ips = addresses.map(a => a.address);\n const result = enforceSsrfPolicyResolved(url, ips, policy);\n if (!result.allowed) {\n callback(makeSsrfError(result.message ?? 'SSRF policy denied resolved address', result.rule ?? 'ssrf'));\n return;\n }\n\n if (wantsAll) {\n callback(null, addresses);\n return;\n }\n // Pin to the first resolved address. enforceSsrfPolicyResolved is\n // all-or-none: if it allowed the resolution, every entry passed.\n const first = addresses[0]!;\n callback(null, first.address, first.family);\n });\n };\n\n const dispatcher = new Agent({\n ...(options.agentOptions ?? {}),\n connect: {\n ...(options.agentOptions?.connect ?? {}),\n // undici's connect type accepts a lookup with the dns.lookup signature.\n lookup: guardedLookup as unknown as Agent.Options['connect'] extends infer T\n ? T extends { lookup?: infer L }\n ? L\n : never\n : never,\n },\n });\n\n const wrapped = async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): Promise<Response> => {\n // Synchronous pre-check for the URL's literal scheme + (if it's an IP)\n // its CIDR membership. undici skips `connect.lookup` for IP-literal\n // hostnames, so the resolved-IP path below would never see them.\n // This pre-check enforces the same SSRF policy on URLs like\n // `https://127.0.0.1/cb` or `https://[::1]/cb`.\n const url = resolveRequestUrl(input);\n if (url) {\n const sync = enforceSsrfPolicy(url, policy);\n if (!sync.allowed) {\n throw makeSsrfError(sync.message ?? 'SSRF policy denied URL', sync.rule ?? 'ssrf');\n }\n }\n return undiciFetch(input as Parameters<typeof undiciFetch>[0], {\n ...(init as Parameters<typeof undiciFetch>[1]),\n dispatcher: dispatcher as unknown as Dispatcher,\n }) as unknown as Response;\n };\n\n return wrapped as typeof fetch;\n}\n\nfunction resolveRequestUrl(input: Parameters<typeof fetch>[0]): URL | null {\n try {\n if (typeof input === 'string') return new URL(input);\n if (input instanceof URL) return input;\n if (\n typeof input === 'object' &&\n input !== null &&\n 'url' in input &&\n typeof (input as { url: unknown }).url === 'string'\n ) {\n return new URL((input as { url: string }).url);\n }\n } catch {\n // Let undici surface the parse error in its own shape.\n return null;\n }\n return null;\n}\n\nfunction bracketIfV6(host: string): string {\n return isIPv6(host) ? `[${host}]` : host;\n}\n\nfunction makeSsrfError(message: string, rule: string): NodeJS.ErrnoException {\n const err = new Error(`pin-and-bind: ${rule}: ${message}`) as NodeJS.ErrnoException;\n err.code = 'EADCP_SSRF_BLOCKED';\n return err;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/pin-and-bind-fetch.ts"],"sourcesContent":["/**\n * Pin-and-bind fetch — DNS-rebinding-resistant `fetch` for outbound webhook\n * delivery and other callbacks where the destination is buyer-supplied.\n *\n * The problem: validating a `push_notification_config.url`'s LITERAL hostname\n * against an SSRF deny list does not protect against a DNS-rebinding attack.\n * A buyer can register `https://rebind.attacker.com/`, pass the literal-host\n * check, and then flip the A-record TTL to `169.254.169.254` (cloud metadata)\n * or `127.0.0.1` (loopback) before the webhook fires. Node's `fetch` resolves\n * the host fresh at request time, gets the rebound IP, and posts the\n * signed payload to the attacker-controlled destination.\n *\n * The fix: control DNS resolution and connect-target inside the fetch itself.\n * 1. Resolve the hostname.\n * 2. Validate every resolved IP against the SSRF policy (CIDR deny lists,\n * metadata-host names, scheme rules).\n * 3. Pin the connection to the validated IP — undici opens TCP/TLS to that\n * specific address, but the original hostname is preserved for TLS SNI\n * and the `Host:` header so HTTPS routing still works.\n *\n * Implementation note: undici's `Agent` accepts a `connect.lookup` callback\n * with the same signature as `dns.lookup`. We hook the callback, resolve via\n * `dns.lookup({ all: true })` to see EVERY address the host can reach, run\n * the resolved IPs through {@link enforceSsrfPolicyResolved}, and only return\n * a pinned address when every resolved IP is in an allowed range. This\n * matches the AdCP substitution-observer SSRF contract — a host that\n * resolves to *any* denied IP is treated as suspect and rejected wholesale.\n */\n\nimport { Agent, fetch as undiciFetch, type Dispatcher } from 'undici';\nimport { lookup as nativeDnsLookup, type LookupAddress } from 'node:dns';\nimport { isIPv6 } from 'node:net';\n\nimport { enforceSsrfPolicy, enforceSsrfPolicyResolved } from '../substitution/observer/ssrf';\nimport type { SsrfPolicy } from '../substitution/types';\n\n/**\n * Default SSRF policy for outbound webhook delivery. Stricter than\n * `DEFAULT_SSRF_POLICY` (substitution observer) only in that it allows\n * `host_literal_policy: 'allow'` — webhook URLs MAY use IP literals\n * (e.g. `https://203.0.113.10/cb`) as long as the IP is not in a denied\n * CIDR range. Schemes restricted to https; signed webhooks SHOULD be\n * delivered over TLS.\n *\n * For storyboard / in-process tests where the receiver runs on\n * `http://127.0.0.1:port`, use {@link LOOPBACK_OK_WEBHOOK_SSRF_POLICY}\n * instead. That preset relaxes only the loopback + http rules and keeps\n * every other deny range — adopters get most of the SSRF protection\n * during tests without disabling pin-and-bind entirely.\n */\nexport const WEBHOOK_SSRF_POLICY: SsrfPolicy = Object.freeze({\n schemes_allowed: Object.freeze(['https']),\n schemes_denied: Object.freeze(['http', 'file', 'gopher', 'ftp', 'ftps', 'data', 'javascript', 'about', 'ws', 'wss']),\n hosts_denied_ipv4_cidrs: Object.freeze([\n '0.0.0.0/8',\n '10.0.0.0/8',\n '100.64.0.0/10',\n '127.0.0.0/8',\n '169.254.0.0/16',\n '172.16.0.0/12',\n '192.0.0.0/24',\n '192.168.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n ]),\n hosts_denied_ipv6_cidrs: Object.freeze([\n '::1/128',\n '::/128',\n '::ffff:0:0/96',\n '64:ff9b::/96',\n 'fc00::/7',\n 'fe80::/10',\n 'ff00::/8',\n ]),\n hosts_denied_metadata: Object.freeze([\n 'metadata.google.internal',\n 'metadata',\n 'metadata.packet.net',\n 'fd00:ec2::254',\n ]),\n host_literal_policy: 'allow',\n}) as SsrfPolicy;\n\n/**\n * Pin-and-bind policy that allows http URLs and IPv4/IPv6 loopback so\n * adopters can enable pin-and-bind for production webhook delivery while\n * keeping storyboard / in-process tests working — `createWebhookReceiver`\n * listens on `http://127.0.0.1:port`. Every other private CIDR, metadata\n * host, and link-local range is still denied, so loopback is the only\n * relaxation. Pass to `createPinAndBindFetch({ policy })` from your test\n * fixture or storyboard runner harness; do NOT use in production.\n */\nexport const LOOPBACK_OK_WEBHOOK_SSRF_POLICY: SsrfPolicy = Object.freeze({\n schemes_allowed: Object.freeze(['http', 'https']),\n schemes_denied: Object.freeze(['file', 'gopher', 'ftp', 'ftps', 'data', 'javascript', 'about', 'ws', 'wss']),\n hosts_denied_ipv4_cidrs: Object.freeze([\n '0.0.0.0/8',\n '10.0.0.0/8',\n '100.64.0.0/10',\n // 127.0.0.0/8 omitted — loopback allowed for tests.\n '169.254.0.0/16',\n '172.16.0.0/12',\n '192.0.0.0/24',\n '192.168.0.0/16',\n '224.0.0.0/4',\n '240.0.0.0/4',\n ]),\n hosts_denied_ipv6_cidrs: Object.freeze([\n // ::1/128 and ::/128 omitted — IPv6 loopback allowed for tests.\n '::ffff:0:0/96',\n '64:ff9b::/96',\n 'fc00::/7',\n 'fe80::/10',\n 'ff00::/8',\n ]),\n hosts_denied_metadata: Object.freeze([\n 'metadata.google.internal',\n 'metadata',\n 'metadata.packet.net',\n 'fd00:ec2::254',\n ]),\n host_literal_policy: 'allow',\n}) as SsrfPolicy;\n\n/**\n * Signature of the lookup callback `dns.lookup` accepts. Re-declared here\n * because the native type from `node:dns` is a complex overload set; the\n * shape we actually need is the all=true variant the Agent expects.\n */\nexport type DnsLookupAll = (\n hostname: string,\n options: { family?: number; hints?: number; all: true; verbatim?: boolean },\n callback: (err: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void\n) => void;\n\nexport interface PinAndBindFetchOptions {\n /**\n * SSRF policy to enforce against resolved IPs. Defaults to\n * {@link WEBHOOK_SSRF_POLICY} (https-only, all private/loopback/metadata\n * ranges denied, IP literals allowed).\n */\n policy?: SsrfPolicy;\n /**\n * Override the underlying DNS lookup. Default uses `dns.lookup` with\n * `all: true`. Tests inject a stub to simulate rebinding attacks without\n * touching real DNS.\n */\n lookup?: DnsLookupAll;\n /**\n * Forward to `Agent` — connect-attempt timeout, TLS options, etc. Cannot\n * override `lookup`; that is wired by this helper.\n */\n agentOptions?: Omit<Agent.Options, 'connect'> & {\n connect?: Omit<NonNullable<Agent.Options['connect']>, 'lookup'>;\n };\n}\n\nconst DEFAULT_LOOKUP_ALL: DnsLookupAll = (hostname, options, callback) => {\n nativeDnsLookup(hostname, { ...options, all: true }, (err, addresses) => {\n callback(err, addresses as LookupAddress[]);\n });\n};\n\n/**\n * Build a `fetch` that pins outbound connections to the IPs the SSRF policy\n * allows, defeating DNS-rebinding attacks against per-attempt DNS resolution.\n *\n * This is the default `fetch` for `createWebhookEmitter` /\n * `createAdcpServer({ webhooks })`, so production webhook delivery is\n * rebinding-protected without extra wiring. Call it explicitly only to\n * override the policy — e.g. {@link LOOPBACK_OK_WEBHOOK_SSRF_POLICY} for\n * storyboard tests that deliver to a loopback http receiver. See\n * `docs/guides/SIGNING-GUIDE.md` § Webhook SSRF defense.\n *\n * Construct once per emitter and reuse — each call instantiates a fresh\n * `undici.Agent` with its own connection pool.\n *\n * @example\n * ```ts\n * import { createWebhookEmitter, createPinAndBindFetch } from '@adcp/sdk/server';\n *\n * const emitter = createWebhookEmitter({\n * signerKey: webhookKey,\n * fetch: createPinAndBindFetch(),\n * });\n * ```\n */\nexport function createPinAndBindFetch(options: PinAndBindFetchOptions = {}): typeof fetch {\n const policy = options.policy ?? WEBHOOK_SSRF_POLICY;\n const lookupImpl = options.lookup ?? DEFAULT_LOOKUP_ALL;\n\n const guardedLookup = (\n hostname: string,\n opts: { family?: number; hints?: number; all?: boolean; verbatim?: boolean } | undefined,\n callback: (err: NodeJS.ErrnoException | null, addressOrAll?: string | LookupAddress[], family?: number) => void\n ): void => {\n const wantsAll = opts?.all === true;\n lookupImpl(hostname, { ...(opts ?? {}), all: true }, (err, addresses) => {\n if (err) {\n callback(err);\n return;\n }\n if (!Array.isArray(addresses) || addresses.length === 0) {\n callback(\n makeSsrfError(`DNS resolution returned no addresses for ${hostname}`, 'dns_revalidation:no_addresses')\n );\n return;\n }\n\n // The URL passed in here only matters for scheme + hostname checks,\n // both of which were already validated synchronously by undici when\n // the request started. We re-build a placeholder URL to feed the\n // resolved-address rule, which is the load-bearing check for\n // rebinding defense.\n const url = new URL(`https://${bracketIfV6(hostname)}`);\n const ips = addresses.map(a => a.address);\n const result = enforceSsrfPolicyResolved(url, ips, policy);\n if (!result.allowed) {\n callback(makeSsrfError(result.message ?? 'SSRF policy denied resolved address', result.rule ?? 'ssrf'));\n return;\n }\n\n if (wantsAll) {\n callback(null, addresses);\n return;\n }\n // Pin to the first resolved address. enforceSsrfPolicyResolved is\n // all-or-none: if it allowed the resolution, every entry passed.\n const first = addresses[0]!;\n callback(null, first.address, first.family);\n });\n };\n\n const dispatcher = new Agent({\n ...(options.agentOptions ?? {}),\n connect: {\n ...(options.agentOptions?.connect ?? {}),\n // undici's connect type accepts a lookup with the dns.lookup signature.\n lookup: guardedLookup as unknown as Agent.Options['connect'] extends infer T\n ? T extends { lookup?: infer L }\n ? L\n : never\n : never,\n },\n });\n\n const wrapped = async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]): Promise<Response> => {\n // Synchronous pre-check for the URL's literal scheme + (if it's an IP)\n // its CIDR membership. undici skips `connect.lookup` for IP-literal\n // hostnames, so the resolved-IP path below would never see them.\n // This pre-check enforces the same SSRF policy on URLs like\n // `https://127.0.0.1/cb` or `https://[::1]/cb`.\n const url = resolveRequestUrl(input);\n if (url) {\n const sync = enforceSsrfPolicy(url, policy);\n if (!sync.allowed) {\n throw makeSsrfError(sync.message ?? 'SSRF policy denied URL', sync.rule ?? 'ssrf');\n }\n }\n return undiciFetch(input as Parameters<typeof undiciFetch>[0], {\n ...(init as Parameters<typeof undiciFetch>[1]),\n dispatcher: dispatcher as unknown as Dispatcher,\n }) as unknown as Response;\n };\n\n return wrapped as typeof fetch;\n}\n\nfunction resolveRequestUrl(input: Parameters<typeof fetch>[0]): URL | null {\n try {\n if (typeof input === 'string') return new URL(input);\n if (input instanceof URL) return input;\n if (\n typeof input === 'object' &&\n input !== null &&\n 'url' in input &&\n typeof (input as { url: unknown }).url === 'string'\n ) {\n return new URL((input as { url: string }).url);\n }\n } catch {\n // Let undici surface the parse error in its own shape.\n return null;\n }\n return null;\n}\n\nfunction bracketIfV6(host: string): string {\n return isIPv6(host) ? `[${host}]` : host;\n}\n\nfunction makeSsrfError(message: string, rule: string): NodeJS.ErrnoException {\n const err = new Error(`pin-and-bind: ${rule}: ${message}`) as NodeJS.ErrnoException;\n err.code = 'EADCP_SSRF_BLOCKED';\n return err;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import {
|
|
8
2
|
DEFAULT_MAX_DOCUMENT_BYTES,
|
|
9
3
|
createSessionedStore,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/postgres-state-store.ts"],"sourcesContent":["/**\n * PostgreSQL-backed state store for distributed AdCP servers.\n *\n * Replaces InMemoryStateStore when running multiple server instances behind\n * a load balancer. Domain objects are stored in a shared JSONB table so any\n * instance can read or write state regardless of which instance handled\n * the original request.\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg';\n * import { PostgresStateStore, createAdcpServer, serve } from '@adcp/sdk/server/legacy/v5';\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * const stateStore = new PostgresStateStore(pool);\n *\n * // Run migration once at startup\n * await pool.query(getAdcpStateMigration());\n *\n * serve(() => createAdcpServer({\n * name: 'My Publisher', version: '1.0.0',\n * stateStore,\n * mediaBuy: {\n * createMediaBuy: async (params, ctx) => {\n * const buy = { media_buy_id: 'mb_1', status: 'active', packages: [] };\n * await ctx.store.put('media_buys', buy.media_buy_id, buy);\n * return buy;\n * },\n * },\n * }));\n * ```\n */\n\nimport type { PgQueryable } from './postgres-task-store';\nimport {\n DEFAULT_MAX_DOCUMENT_BYTES,\n createSessionedStore,\n validateCollection,\n validateId,\n validateWrite,\n type AdcpStateStore,\n type ListOptions,\n type ListResult,\n type PutIfMatchResult,\n type VersionedDocument,\n} from './state-store';\n\nexport interface PostgresStateStoreOptions {\n /** Table name. Must contain only lowercase letters, digits, and underscores. Defaults to `\"adcp_state\"`. */\n tableName?: string;\n /** Max bytes per document. Defaults to {@link DEFAULT_MAX_DOCUMENT_BYTES}. */\n maxDocumentBytes?: number;\n}\n\nconst DEFAULT_TABLE = 'adcp_state';\nconst VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\nconst PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 500;\n\n/**\n * Generate the SQL DDL for the state store table.\n *\n * Idempotent: safe to run on existing tables. The `ADD COLUMN IF NOT EXISTS`\n * clause upgrades databases created by earlier SDK versions (no `version`\n * column) without rewriting data — existing rows start at version 1. A seller's\n * first `putIfMatch` against an existing (pre-migration) row will see\n * `currentVersion: 1` — same as a freshly inserted row. Treat version as opaque.\n *\n * Do not attach triggers that suppress `UPDATE` or return `OLD` for this table —\n * `putIfMatch` relies on affected-row count to detect conflicts, and a trigger\n * that drops the update will be reported as a CAS conflict. RLS policies that\n * silently reject writes have the same problem.\n */\nexport function getAdcpStateMigration(tableName = DEFAULT_TABLE): string {\n if (!VALID_IDENTIFIER.test(tableName)) {\n throw new Error(`Invalid table name: \"${tableName}\". Must match /^[a-z_][a-z0-9_]*$/.`);\n }\n\n return `\n CREATE TABLE IF NOT EXISTS ${tableName} (\n collection TEXT NOT NULL,\n id TEXT NOT NULL,\n data JSONB NOT NULL,\n version INTEGER NOT NULL DEFAULT 1,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (collection, id)\n );\n\n ALTER TABLE ${tableName}\n ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1;\n\n CREATE INDEX IF NOT EXISTS idx_${tableName}_collection\n ON ${tableName}(collection);\n\n CREATE INDEX IF NOT EXISTS idx_${tableName}_updated\n ON ${tableName}(updated_at);\n `;\n}\n\n/** The migration SQL as a constant for the default table name. */\nexport const ADCP_STATE_MIGRATION = getAdcpStateMigration();\n\nexport class PostgresStateStore implements AdcpStateStore {\n private readonly db: PgQueryable;\n private readonly table: string;\n private readonly maxDocumentBytes: number;\n\n constructor(db: PgQueryable, options?: PostgresStateStoreOptions) {\n this.db = db;\n this.table = options?.tableName ?? DEFAULT_TABLE;\n this.maxDocumentBytes = options?.maxDocumentBytes ?? DEFAULT_MAX_DOCUMENT_BYTES;\n if (!VALID_IDENTIFIER.test(this.table)) {\n throw new Error(`Invalid table name: \"${this.table}\". Must match /^[a-z_][a-z0-9_]*$/.`);\n }\n }\n\n async get<T extends Record<string, unknown> = Record<string, unknown>>(\n collection: string,\n id: string\n ): Promise<T | null> {\n validateCollection(collection);\n validateId(id);\n const { rows } = await this.db.query(`SELECT data FROM ${this.table} WHERE collection = $1 AND id = $2`, [\n collection,\n id,\n ]);\n if (rows.length === 0) return null;\n return rows[0]!.data as T;\n }\n\n async getWithVersion<T extends Record<string, unknown> = Record<string, unknown>>(\n collection: string,\n id: string\n ): Promise<VersionedDocument<T> | null> {\n validateCollection(collection);\n validateId(id);\n const { rows } = await this.db.query(`SELECT data, version FROM ${this.table} WHERE collection = $1 AND id = $2`, [\n collection,\n id,\n ]);\n if (rows.length === 0) return null;\n const row = rows[0]!;\n return { data: row.data as T, version: row.version as number };\n }\n\n async put(collection: string, id: string, data: Record<string, unknown>): Promise<void> {\n const serialized = validateWrite(collection, id, data, this.maxDocumentBytes);\n await this.db.query(\n `INSERT INTO ${this.table} (collection, id, data, version)\n VALUES ($1, $2, $3, 1)\n ON CONFLICT (collection, id)\n DO UPDATE SET data = $3, version = ${this.table}.version + 1, updated_at = NOW()`,\n [collection, id, serialized]\n );\n }\n\n async putIfMatch(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n expectedVersion: number | null\n ): Promise<PutIfMatchResult> {\n const serialized = validateWrite(collection, id, data, this.maxDocumentBytes);\n\n if (expectedVersion === null) {\n // Insert-only path: succeed iff no row exists. Returns version on success,\n // nothing on conflict (then we re-read to report the current version).\n const { rows } = await this.db.query(\n `INSERT INTO ${this.table} (collection, id, data, version)\n VALUES ($1, $2, $3, 1)\n ON CONFLICT (collection, id) DO NOTHING\n RETURNING version`,\n [collection, id, serialized]\n );\n if (rows.length > 0) return { ok: true, version: rows[0]!.version as number };\n const { rows: existing } = await this.db.query(\n `SELECT version FROM ${this.table} WHERE collection = $1 AND id = $2`,\n [collection, id]\n );\n return { ok: false, currentVersion: (existing[0]?.version as number | undefined) ?? null };\n }\n\n const { rows } = await this.db.query(\n `UPDATE ${this.table}\n SET data = $3, version = version + 1, updated_at = NOW()\n WHERE collection = $1 AND id = $2 AND version = $4\n RETURNING version`,\n [collection, id, serialized, expectedVersion]\n );\n if (rows.length > 0) return { ok: true, version: rows[0]!.version as number };\n\n const { rows: existing } = await this.db.query(\n `SELECT version FROM ${this.table} WHERE collection = $1 AND id = $2`,\n [collection, id]\n );\n return { ok: false, currentVersion: (existing[0]?.version as number | undefined) ?? null };\n }\n\n async patch(collection: string, id: string, partial: Record<string, unknown>): Promise<void> {\n const serialized = validateWrite(collection, id, partial, this.maxDocumentBytes);\n await this.db.query(\n `INSERT INTO ${this.table} (collection, id, data, version)\n VALUES ($1, $2, $3, 1)\n ON CONFLICT (collection, id)\n DO UPDATE SET data = ${this.table}.data || $3, version = ${this.table}.version + 1, updated_at = NOW()`,\n [collection, id, serialized]\n );\n }\n\n async delete(collection: string, id: string): Promise<boolean> {\n validateCollection(collection);\n validateId(id);\n const { rowCount } = await this.db.query(`DELETE FROM ${this.table} WHERE collection = $1 AND id = $2`, [\n collection,\n id,\n ]);\n return (rowCount ?? 0) > 0;\n }\n\n async list<T extends Record<string, unknown> = Record<string, unknown>>(\n collection: string,\n options?: ListOptions\n ): Promise<ListResult<T>> {\n validateCollection(collection);\n const limit = Math.min(options?.limit ?? PAGE_SIZE, MAX_PAGE_SIZE);\n const conditions = ['collection = $1'];\n const values: unknown[] = [collection];\n let paramIndex = 2;\n\n // Filter by JSONB containment\n if (options?.filter && Object.keys(options.filter).length > 0) {\n conditions.push(`data @> $${paramIndex}`);\n values.push(JSON.stringify(options.filter));\n paramIndex++;\n }\n\n // Cursor-based pagination (cursor = \"updated_at|id\")\n if (options?.cursor) {\n const parts = options.cursor.split('|', 2);\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`Invalid cursor format: expected \"timestamp|id\"`);\n }\n const [cursorTime, cursorId] = parts;\n conditions.push(`(updated_at, id) > ($${paramIndex}::timestamptz, $${paramIndex + 1})`);\n values.push(cursorTime, cursorId);\n paramIndex += 2;\n }\n\n const where = conditions.join(' AND ');\n // Fetch one extra to detect hasMore\n values.push(limit + 1);\n\n const { rows } = await this.db.query(\n `SELECT id, data, updated_at::text AS updated_at_raw\n FROM ${this.table}\n WHERE ${where}\n ORDER BY updated_at, id\n LIMIT $${paramIndex}`,\n values\n );\n\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const items = resultRows.map(r => r.data as T);\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const last = resultRows[resultRows.length - 1]!;\n nextCursor = `${last.updated_at_raw as string}|${last.id as string}`;\n }\n\n return { items, nextCursor };\n }\n\n /**\n * Delete all documents in a collection.\n */\n async clearCollection(collection: string): Promise<number> {\n const { rowCount } = await this.db.query(`DELETE FROM ${this.table} WHERE collection = $1`, [collection]);\n return rowCount ?? 0;\n }\n\n scoped(sessionKey: string): AdcpStateStore {\n return createSessionedStore(this, sessionKey);\n }\n}\n"],"mappings":"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","names":["rows","existing"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/postgres-state-store.ts"],"sourcesContent":["/**\n * PostgreSQL-backed state store for distributed AdCP servers.\n *\n * Replaces InMemoryStateStore when running multiple server instances behind\n * a load balancer. Domain objects are stored in a shared JSONB table so any\n * instance can read or write state regardless of which instance handled\n * the original request.\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg';\n * import { PostgresStateStore, createAdcpServer, serve } from '@adcp/sdk/server/legacy/v5';\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * const stateStore = new PostgresStateStore(pool);\n *\n * // Run migration once at startup\n * await pool.query(getAdcpStateMigration());\n *\n * serve(() => createAdcpServer({\n * name: 'My Publisher', version: '1.0.0',\n * stateStore,\n * mediaBuy: {\n * createMediaBuy: async (params, ctx) => {\n * const buy = { media_buy_id: 'mb_1', status: 'active', packages: [] };\n * await ctx.store.put('media_buys', buy.media_buy_id, buy);\n * return buy;\n * },\n * },\n * }));\n * ```\n */\n\nimport type { PgQueryable } from './postgres-task-store';\nimport {\n DEFAULT_MAX_DOCUMENT_BYTES,\n createSessionedStore,\n validateCollection,\n validateId,\n validateWrite,\n type AdcpStateStore,\n type ListOptions,\n type ListResult,\n type PutIfMatchResult,\n type VersionedDocument,\n} from './state-store';\n\nexport interface PostgresStateStoreOptions {\n /** Table name. Must contain only lowercase letters, digits, and underscores. Defaults to `\"adcp_state\"`. */\n tableName?: string;\n /** Max bytes per document. Defaults to {@link DEFAULT_MAX_DOCUMENT_BYTES}. */\n maxDocumentBytes?: number;\n}\n\nconst DEFAULT_TABLE = 'adcp_state';\nconst VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\nconst PAGE_SIZE = 50;\nconst MAX_PAGE_SIZE = 500;\n\n/**\n * Generate the SQL DDL for the state store table.\n *\n * Idempotent: safe to run on existing tables. The `ADD COLUMN IF NOT EXISTS`\n * clause upgrades databases created by earlier SDK versions (no `version`\n * column) without rewriting data — existing rows start at version 1. A seller's\n * first `putIfMatch` against an existing (pre-migration) row will see\n * `currentVersion: 1` — same as a freshly inserted row. Treat version as opaque.\n *\n * Do not attach triggers that suppress `UPDATE` or return `OLD` for this table —\n * `putIfMatch` relies on affected-row count to detect conflicts, and a trigger\n * that drops the update will be reported as a CAS conflict. RLS policies that\n * silently reject writes have the same problem.\n */\nexport function getAdcpStateMigration(tableName = DEFAULT_TABLE): string {\n if (!VALID_IDENTIFIER.test(tableName)) {\n throw new Error(`Invalid table name: \"${tableName}\". Must match /^[a-z_][a-z0-9_]*$/.`);\n }\n\n return `\n CREATE TABLE IF NOT EXISTS ${tableName} (\n collection TEXT NOT NULL,\n id TEXT NOT NULL,\n data JSONB NOT NULL,\n version INTEGER NOT NULL DEFAULT 1,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\n PRIMARY KEY (collection, id)\n );\n\n ALTER TABLE ${tableName}\n ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1;\n\n CREATE INDEX IF NOT EXISTS idx_${tableName}_collection\n ON ${tableName}(collection);\n\n CREATE INDEX IF NOT EXISTS idx_${tableName}_updated\n ON ${tableName}(updated_at);\n `;\n}\n\n/** The migration SQL as a constant for the default table name. */\nexport const ADCP_STATE_MIGRATION = getAdcpStateMigration();\n\nexport class PostgresStateStore implements AdcpStateStore {\n private readonly db: PgQueryable;\n private readonly table: string;\n private readonly maxDocumentBytes: number;\n\n constructor(db: PgQueryable, options?: PostgresStateStoreOptions) {\n this.db = db;\n this.table = options?.tableName ?? DEFAULT_TABLE;\n this.maxDocumentBytes = options?.maxDocumentBytes ?? DEFAULT_MAX_DOCUMENT_BYTES;\n if (!VALID_IDENTIFIER.test(this.table)) {\n throw new Error(`Invalid table name: \"${this.table}\". Must match /^[a-z_][a-z0-9_]*$/.`);\n }\n }\n\n async get<T extends Record<string, unknown> = Record<string, unknown>>(\n collection: string,\n id: string\n ): Promise<T | null> {\n validateCollection(collection);\n validateId(id);\n const { rows } = await this.db.query(`SELECT data FROM ${this.table} WHERE collection = $1 AND id = $2`, [\n collection,\n id,\n ]);\n if (rows.length === 0) return null;\n return rows[0]!.data as T;\n }\n\n async getWithVersion<T extends Record<string, unknown> = Record<string, unknown>>(\n collection: string,\n id: string\n ): Promise<VersionedDocument<T> | null> {\n validateCollection(collection);\n validateId(id);\n const { rows } = await this.db.query(`SELECT data, version FROM ${this.table} WHERE collection = $1 AND id = $2`, [\n collection,\n id,\n ]);\n if (rows.length === 0) return null;\n const row = rows[0]!;\n return { data: row.data as T, version: row.version as number };\n }\n\n async put(collection: string, id: string, data: Record<string, unknown>): Promise<void> {\n const serialized = validateWrite(collection, id, data, this.maxDocumentBytes);\n await this.db.query(\n `INSERT INTO ${this.table} (collection, id, data, version)\n VALUES ($1, $2, $3, 1)\n ON CONFLICT (collection, id)\n DO UPDATE SET data = $3, version = ${this.table}.version + 1, updated_at = NOW()`,\n [collection, id, serialized]\n );\n }\n\n async putIfMatch(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n expectedVersion: number | null\n ): Promise<PutIfMatchResult> {\n const serialized = validateWrite(collection, id, data, this.maxDocumentBytes);\n\n if (expectedVersion === null) {\n // Insert-only path: succeed iff no row exists. Returns version on success,\n // nothing on conflict (then we re-read to report the current version).\n const { rows } = await this.db.query(\n `INSERT INTO ${this.table} (collection, id, data, version)\n VALUES ($1, $2, $3, 1)\n ON CONFLICT (collection, id) DO NOTHING\n RETURNING version`,\n [collection, id, serialized]\n );\n if (rows.length > 0) return { ok: true, version: rows[0]!.version as number };\n const { rows: existing } = await this.db.query(\n `SELECT version FROM ${this.table} WHERE collection = $1 AND id = $2`,\n [collection, id]\n );\n return { ok: false, currentVersion: (existing[0]?.version as number | undefined) ?? null };\n }\n\n const { rows } = await this.db.query(\n `UPDATE ${this.table}\n SET data = $3, version = version + 1, updated_at = NOW()\n WHERE collection = $1 AND id = $2 AND version = $4\n RETURNING version`,\n [collection, id, serialized, expectedVersion]\n );\n if (rows.length > 0) return { ok: true, version: rows[0]!.version as number };\n\n const { rows: existing } = await this.db.query(\n `SELECT version FROM ${this.table} WHERE collection = $1 AND id = $2`,\n [collection, id]\n );\n return { ok: false, currentVersion: (existing[0]?.version as number | undefined) ?? null };\n }\n\n async patch(collection: string, id: string, partial: Record<string, unknown>): Promise<void> {\n const serialized = validateWrite(collection, id, partial, this.maxDocumentBytes);\n await this.db.query(\n `INSERT INTO ${this.table} (collection, id, data, version)\n VALUES ($1, $2, $3, 1)\n ON CONFLICT (collection, id)\n DO UPDATE SET data = ${this.table}.data || $3, version = ${this.table}.version + 1, updated_at = NOW()`,\n [collection, id, serialized]\n );\n }\n\n async delete(collection: string, id: string): Promise<boolean> {\n validateCollection(collection);\n validateId(id);\n const { rowCount } = await this.db.query(`DELETE FROM ${this.table} WHERE collection = $1 AND id = $2`, [\n collection,\n id,\n ]);\n return (rowCount ?? 0) > 0;\n }\n\n async list<T extends Record<string, unknown> = Record<string, unknown>>(\n collection: string,\n options?: ListOptions\n ): Promise<ListResult<T>> {\n validateCollection(collection);\n const limit = Math.min(options?.limit ?? PAGE_SIZE, MAX_PAGE_SIZE);\n const conditions = ['collection = $1'];\n const values: unknown[] = [collection];\n let paramIndex = 2;\n\n // Filter by JSONB containment\n if (options?.filter && Object.keys(options.filter).length > 0) {\n conditions.push(`data @> $${paramIndex}`);\n values.push(JSON.stringify(options.filter));\n paramIndex++;\n }\n\n // Cursor-based pagination (cursor = \"updated_at|id\")\n if (options?.cursor) {\n const parts = options.cursor.split('|', 2);\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new Error(`Invalid cursor format: expected \"timestamp|id\"`);\n }\n const [cursorTime, cursorId] = parts;\n conditions.push(`(updated_at, id) > ($${paramIndex}::timestamptz, $${paramIndex + 1})`);\n values.push(cursorTime, cursorId);\n paramIndex += 2;\n }\n\n const where = conditions.join(' AND ');\n // Fetch one extra to detect hasMore\n values.push(limit + 1);\n\n const { rows } = await this.db.query(\n `SELECT id, data, updated_at::text AS updated_at_raw\n FROM ${this.table}\n WHERE ${where}\n ORDER BY updated_at, id\n LIMIT $${paramIndex}`,\n values\n );\n\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const items = resultRows.map(r => r.data as T);\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const last = resultRows[resultRows.length - 1]!;\n nextCursor = `${last.updated_at_raw as string}|${last.id as string}`;\n }\n\n return { items, nextCursor };\n }\n\n /**\n * Delete all documents in a collection.\n */\n async clearCollection(collection: string): Promise<number> {\n const { rowCount } = await this.db.query(`DELETE FROM ${this.table} WHERE collection = $1`, [collection]);\n return rowCount ?? 0;\n }\n\n scoped(sessionKey: string): AdcpStateStore {\n return createSessionedStore(this, sessionKey);\n }\n}\n"],"mappings":"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","names":["rows","existing"]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import { randomBytes } from "node:crypto";
|
|
8
2
|
const DEFAULT_TABLE = "adcp_mcp_tasks";
|
|
9
3
|
const VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/postgres-task-store.ts"],"sourcesContent":["/**\n * PostgreSQL-backed TaskStore for distributed MCP servers.\n *\n * Replaces InMemoryTaskStore when running multiple server instances behind\n * a load balancer. Tasks are stored in a shared table so any instance can\n * create, retrieve, or update a task regardless of which instance handled\n * the original request.\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg';\n * import { PostgresTaskStore, serve, createTaskCapableServer } from '@adcp/sdk';\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * const taskStore = new PostgresTaskStore(pool);\n *\n * serve(({ taskStore }) => createTaskCapableServer('My Agent', '1.0.0', { taskStore }), {\n * taskStore,\n * });\n * ```\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { isTerminal } from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js';\nimport type { TaskStore, CreateTaskOptions } from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js';\nimport type { Task, RequestId, Result, Request } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Minimal subset of the `pg.Pool` interface used by PostgresTaskStore.\n *\n * Accepting this instead of a concrete Pool lets callers pass any\n * pg-compatible query executor (connection pools, transaction wrappers, etc.)\n * without forcing a hard dependency on the `pg` package.\n */\nexport interface PgQueryable {\n query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[]; rowCount: number | null }>;\n}\n\n/** Configuration for PostgresTaskStore. */\nexport interface PostgresTaskStoreOptions {\n /** Table name for task storage. Must contain only lowercase letters, digits, and underscores. Defaults to `\"adcp_mcp_tasks\"`. */\n tableName?: string;\n}\n\n/** Default table name — vendor-prefixed to avoid collisions in consumer databases. */\nconst DEFAULT_TABLE = 'adcp_mcp_tasks';\n\n/** Validates a SQL identifier to prevent injection via table/constraint names. */\nconst VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\n\n/** Page size for listTasks pagination (matches InMemoryTaskStore). */\nconst PAGE_SIZE = 10;\n\n/**\n * Generate the SQL DDL for the task store table.\n *\n * Constraint and index names are derived from the table name to avoid\n * collisions when multiple stores share the same database.\n *\n * @example\n * ```typescript\n * import { getMcpTasksMigration } from '@adcp/sdk';\n * await pool.query(getMcpTasksMigration()); // creates adcp_mcp_tasks\n * await pool.query(getMcpTasksMigration({ tableName: 'my_tasks' })); // custom name\n * ```\n */\nexport function getMcpTasksMigration(options?: { tableName?: string }): string {\n const table = options?.tableName ?? DEFAULT_TABLE;\n if (!VALID_IDENTIFIER.test(table)) {\n throw new Error(`Invalid table name \"${table}\": must match ${VALID_IDENTIFIER}`);\n }\n return `\nCREATE TABLE IF NOT EXISTS ${table} (\n task_id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'working',\n ttl INTEGER,\n poll_interval INTEGER NOT NULL DEFAULT 1000,\n status_message TEXT,\n request_id TEXT NOT NULL,\n request JSONB NOT NULL,\n result JSONB,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n last_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ,\n\n CONSTRAINT ${table}_valid_status CHECK (\n status IN ('working', 'input_required', 'completed', 'failed', 'cancelled')\n )\n);\n\nCREATE INDEX IF NOT EXISTS idx_${table}_expires_at\n ON ${table}(expires_at) WHERE expires_at IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_${table}_created_at\n ON ${table}(created_at);\n`.trim();\n}\n\n/** Backward-compatible constant using the default table name. */\nexport const MCP_TASKS_MIGRATION = getMcpTasksMigration();\n\n/** Row shape returned by SELECT queries. */\ninterface TaskRow {\n task_id: string;\n status: Task['status'];\n ttl: number | null;\n poll_interval: number;\n status_message: string | null;\n request_id: string;\n request: Record<string, unknown>;\n result: Record<string, unknown> | null;\n created_at: string;\n last_updated_at: string;\n expires_at: string | null;\n}\n\n/** Convert a database row to an MCP Task object. */\nfunction rowToTask(row: TaskRow): Task {\n const task: Task = {\n taskId: row.task_id,\n status: row.status,\n ttl: row.ttl,\n createdAt: new Date(row.created_at).toISOString(),\n lastUpdatedAt: new Date(row.last_updated_at).toISOString(),\n };\n if (row.poll_interval != null) {\n task.pollInterval = row.poll_interval;\n }\n if (row.status_message != null) {\n task.statusMessage = row.status_message;\n }\n return task;\n}\n\n/** WHERE clause that filters out expired tasks. */\nconst NOT_EXPIRED = `(expires_at IS NULL OR expires_at > NOW())`;\n\n/**\n * PostgreSQL-backed implementation of the MCP SDK TaskStore interface.\n *\n * All reads filter out expired tasks (via `expires_at`), so no background\n * timer is needed — expired tasks are simply invisible. Call\n * `cleanupExpired()` periodically to reclaim storage.\n */\nexport class PostgresTaskStore implements TaskStore {\n private readonly table: string;\n\n constructor(\n private readonly db: PgQueryable,\n options?: PostgresTaskStoreOptions\n ) {\n this.table = options?.tableName ?? DEFAULT_TABLE;\n if (!VALID_IDENTIFIER.test(this.table)) {\n throw new Error(`Invalid table name \"${this.table}\": must match ${VALID_IDENTIFIER}`);\n }\n }\n\n /**\n * Create a new task. Pass `taskParams.taskId` to use a caller-supplied ID verbatim\n * (useful for compliance-controller scenarios where the runner needs deterministic\n * task IDs). If omitted, a random hex ID is generated. Throws if the supplied ID\n * is empty / longer than 128 chars, or already exists. The 128-char ceiling is an\n * SDK policy (matches typical request-id / session-id field lengths and keeps\n * the task_id index efficient) — Postgres TEXT itself imposes no limit.\n *\n * The `task_id` namespace on this store is global (no tenant scoping in the schema\n * today). Callers using caller-supplied IDs are responsible for namespace isolation;\n * cross-tenant collisions surface as `already exists`. Track the schema fix\n * (composite key on tenant + task_id) in a future SDK migration if production paths\n * ever wire caller-supplied IDs.\n */\n async createTask(\n taskParams: CreateTaskOptions & { taskId?: string },\n requestId: RequestId,\n request: Request,\n _sessionId?: string\n ): Promise<Task> {\n if (taskParams.taskId !== undefined) {\n if (typeof taskParams.taskId !== 'string' || taskParams.taskId.length === 0) {\n throw new Error('taskId must be a non-empty string when supplied');\n }\n if (taskParams.taskId.length > 128) {\n throw new Error(`taskId must be 128 characters or fewer (got ${taskParams.taskId.length})`);\n }\n }\n const taskId = taskParams.taskId ?? randomBytes(16).toString('hex');\n const ttl = taskParams.ttl ?? null;\n const pollInterval = taskParams.pollInterval ?? 1000;\n\n try {\n const { rows } = await this.db.query(\n `INSERT INTO ${this.table} (task_id, status, ttl, poll_interval, request_id, request, expires_at)\n VALUES ($1, 'working', $2, $3, $4, $5,\n CASE WHEN $2::integer IS NOT NULL\n THEN NOW() + ($2::integer || ' milliseconds')::interval\n ELSE NULL END)\n RETURNING *`,\n [taskId, ttl, pollInterval, String(requestId), JSON.stringify(request)]\n );\n\n return rowToTask(rows[0] as unknown as TaskRow);\n } catch (err) {\n // Unique constraint violation — a task with this ID already exists.\n if (typeof err === 'object' && err !== null && (err as { code?: unknown }).code === '23505') {\n throw new Error(\n `Task with ID ${taskId} already exists. Use a different taskId or retrieve the existing task via getTask().`\n );\n }\n throw err;\n }\n }\n\n async getTask(taskId: string, _sessionId?: string): Promise<Task | null> {\n const { rows } = await this.db.query(`SELECT * FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`, [taskId]);\n return rows.length > 0 ? rowToTask(rows[0] as unknown as TaskRow) : null;\n }\n\n async storeTaskResult(\n taskId: string,\n status: 'completed' | 'failed',\n result: Result,\n _sessionId?: string\n ): Promise<void> {\n // Atomic check-and-update: only modify if task exists and is non-terminal.\n const { rowCount, rows } = await this.db.query(\n `UPDATE ${this.table}\n SET status = $2,\n result = $3,\n last_updated_at = NOW(),\n expires_at = CASE WHEN ttl IS NOT NULL\n THEN NOW() + (ttl || ' milliseconds')::interval\n ELSE NULL END\n WHERE task_id = $1\n AND status NOT IN ('completed', 'failed', 'cancelled')\n AND ${NOT_EXPIRED}\n RETURNING status`,\n [taskId, status, JSON.stringify(result)]\n );\n\n if (!rowCount || rows.length === 0) {\n // Distinguish \"not found\" / \"expired\" from \"already terminal\".\n const { rows: existing } = await this.db.query(\n `SELECT status FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`,\n [taskId]\n );\n if (existing.length === 0) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n throw new Error(\n `Cannot store result for task ${taskId} in terminal status '${existing[0]!.status}'. Task results can only be stored once.`\n );\n }\n }\n\n async getTaskResult(taskId: string, _sessionId?: string): Promise<Result> {\n const { rows } = await this.db.query(`SELECT result FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`, [\n taskId,\n ]);\n\n if (rows.length === 0) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n const row = rows[0]!;\n if (row.result == null) {\n throw new Error(`Task ${taskId} has no result stored`);\n }\n return row.result as Result;\n }\n\n async updateTaskStatus(\n taskId: string,\n status: Task['status'],\n statusMessage?: string,\n _sessionId?: string\n ): Promise<void> {\n // Atomic: only update if not already in a terminal state.\n const { rowCount, rows } = await this.db.query(\n `UPDATE ${this.table}\n SET status = $2,\n status_message = COALESCE($3, status_message),\n last_updated_at = NOW(),\n expires_at = CASE\n WHEN $2 IN ('completed', 'failed', 'cancelled') AND ttl IS NOT NULL\n THEN NOW() + (ttl || ' milliseconds')::interval\n ELSE expires_at END\n WHERE task_id = $1\n AND status NOT IN ('completed', 'failed', 'cancelled')\n AND ${NOT_EXPIRED}\n RETURNING status`,\n [taskId, status, statusMessage ?? null]\n );\n\n if (!rowCount || rows.length === 0) {\n const { rows: existing } = await this.db.query(\n `SELECT status FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`,\n [taskId]\n );\n if (existing.length === 0) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n throw new Error(\n `Cannot update task ${taskId} from terminal status '${existing[0]!.status}' to '${status}'. Terminal states (completed, failed, cancelled) cannot transition to other states.`\n );\n }\n }\n\n async listTasks(cursor?: string, _sessionId?: string): Promise<{ tasks: Task[]; nextCursor?: string }> {\n let rawRows: Record<string, unknown>[];\n\n if (cursor) {\n // Decode cursor: \"created_at|task_id\" — split on first | only,\n // so task IDs containing | are handled correctly.\n const sepIndex = cursor.indexOf('|');\n if (sepIndex < 1) {\n throw new Error(`Invalid cursor: ${cursor}`);\n }\n const cursorCreatedAt = cursor.slice(0, sepIndex);\n const cursorTaskId = cursor.slice(sepIndex + 1);\n if (!cursorTaskId || isNaN(Date.parse(cursorCreatedAt))) {\n throw new Error(`Invalid cursor: ${cursor}`);\n }\n\n ({ rows: rawRows } = await this.db.query(\n `SELECT *, created_at::text AS created_at_raw FROM ${this.table}\n WHERE ${NOT_EXPIRED}\n AND (created_at, task_id) > ($1::timestamptz, $2)\n ORDER BY created_at, task_id\n LIMIT $3`,\n [cursorCreatedAt, cursorTaskId, PAGE_SIZE + 1]\n ));\n } else {\n ({ rows: rawRows } = await this.db.query(\n `SELECT *, created_at::text AS created_at_raw FROM ${this.table}\n WHERE ${NOT_EXPIRED}\n ORDER BY created_at, task_id\n LIMIT $1`,\n [PAGE_SIZE + 1]\n ));\n }\n\n const rows = rawRows as unknown as (TaskRow & { created_at_raw: string })[];\n const hasMore = rows.length > PAGE_SIZE;\n const pageRows = hasMore ? rows.slice(0, PAGE_SIZE) : rows;\n const tasks = pageRows.map(rowToTask);\n\n let nextCursor: string | undefined;\n if (hasMore && pageRows.length > 0) {\n const last = pageRows[pageRows.length - 1]!;\n // Use the raw PostgreSQL text representation to preserve microsecond\n // precision. JavaScript Date only has millisecond precision, which\n // causes keyset pagination to re-include rows when multiple tasks\n // share the same millisecond.\n nextCursor = `${last.created_at_raw}|${last.task_id}`;\n }\n\n return { tasks, nextCursor };\n }\n\n /**\n * Delete expired tasks from the table.\n *\n * Call this on a schedule (e.g., every 5 minutes) to reclaim storage.\n * Reads automatically filter expired rows, so this is purely a storage\n * optimization — not required for correctness.\n *\n * @returns The number of deleted rows.\n */\n async cleanupExpired(): Promise<number> {\n const { rowCount } = await this.db.query(\n `DELETE FROM ${this.table} WHERE expires_at IS NOT NULL AND expires_at <= NOW()`\n );\n return rowCount ?? 0;\n }\n\n /**\n * No-op — PostgresTaskStore has no timers or background processes.\n * Matches the cleanup() method that InMemoryTaskStore exposes.\n */\n cleanup(): void {\n // Nothing to clean up — no timers.\n }\n}\n\n/**\n * Delete expired tasks from the task store table.\n *\n * Standalone version of `PostgresTaskStore.cleanupExpired()` for use\n * without a store instance.\n *\n * @returns The number of deleted rows.\n */\nexport async function cleanupExpiredTasks(db: PgQueryable, tableName: string = DEFAULT_TABLE): Promise<number> {\n if (!VALID_IDENTIFIER.test(tableName)) {\n throw new Error(`Invalid table name \"${tableName}\": must match ${VALID_IDENTIFIER}`);\n }\n const { rowCount } = await db.query(`DELETE FROM ${tableName} WHERE expires_at IS NOT NULL AND expires_at <= NOW()`);\n return rowCount ?? 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/postgres-task-store.ts"],"sourcesContent":["/**\n * PostgreSQL-backed TaskStore for distributed MCP servers.\n *\n * Replaces InMemoryTaskStore when running multiple server instances behind\n * a load balancer. Tasks are stored in a shared table so any instance can\n * create, retrieve, or update a task regardless of which instance handled\n * the original request.\n *\n * @example\n * ```typescript\n * import { Pool } from 'pg';\n * import { PostgresTaskStore, serve, createTaskCapableServer } from '@adcp/sdk';\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL });\n * const taskStore = new PostgresTaskStore(pool);\n *\n * serve(({ taskStore }) => createTaskCapableServer('My Agent', '1.0.0', { taskStore }), {\n * taskStore,\n * });\n * ```\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { isTerminal } from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js';\nimport type { TaskStore, CreateTaskOptions } from '@modelcontextprotocol/sdk/experimental/tasks/interfaces.js';\nimport type { Task, RequestId, Result, Request } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Minimal subset of the `pg.Pool` interface used by PostgresTaskStore.\n *\n * Accepting this instead of a concrete Pool lets callers pass any\n * pg-compatible query executor (connection pools, transaction wrappers, etc.)\n * without forcing a hard dependency on the `pg` package.\n */\nexport interface PgQueryable {\n query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[]; rowCount: number | null }>;\n}\n\n/** Configuration for PostgresTaskStore. */\nexport interface PostgresTaskStoreOptions {\n /** Table name for task storage. Must contain only lowercase letters, digits, and underscores. Defaults to `\"adcp_mcp_tasks\"`. */\n tableName?: string;\n}\n\n/** Default table name — vendor-prefixed to avoid collisions in consumer databases. */\nconst DEFAULT_TABLE = 'adcp_mcp_tasks';\n\n/** Validates a SQL identifier to prevent injection via table/constraint names. */\nconst VALID_IDENTIFIER = /^[a-z_][a-z0-9_]*$/;\n\n/** Page size for listTasks pagination (matches InMemoryTaskStore). */\nconst PAGE_SIZE = 10;\n\n/**\n * Generate the SQL DDL for the task store table.\n *\n * Constraint and index names are derived from the table name to avoid\n * collisions when multiple stores share the same database.\n *\n * @example\n * ```typescript\n * import { getMcpTasksMigration } from '@adcp/sdk';\n * await pool.query(getMcpTasksMigration()); // creates adcp_mcp_tasks\n * await pool.query(getMcpTasksMigration({ tableName: 'my_tasks' })); // custom name\n * ```\n */\nexport function getMcpTasksMigration(options?: { tableName?: string }): string {\n const table = options?.tableName ?? DEFAULT_TABLE;\n if (!VALID_IDENTIFIER.test(table)) {\n throw new Error(`Invalid table name \"${table}\": must match ${VALID_IDENTIFIER}`);\n }\n return `\nCREATE TABLE IF NOT EXISTS ${table} (\n task_id TEXT PRIMARY KEY,\n status TEXT NOT NULL DEFAULT 'working',\n ttl INTEGER,\n poll_interval INTEGER NOT NULL DEFAULT 1000,\n status_message TEXT,\n request_id TEXT NOT NULL,\n request JSONB NOT NULL,\n result JSONB,\n created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n last_updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n expires_at TIMESTAMPTZ,\n\n CONSTRAINT ${table}_valid_status CHECK (\n status IN ('working', 'input_required', 'completed', 'failed', 'cancelled')\n )\n);\n\nCREATE INDEX IF NOT EXISTS idx_${table}_expires_at\n ON ${table}(expires_at) WHERE expires_at IS NOT NULL;\n\nCREATE INDEX IF NOT EXISTS idx_${table}_created_at\n ON ${table}(created_at);\n`.trim();\n}\n\n/** Backward-compatible constant using the default table name. */\nexport const MCP_TASKS_MIGRATION = getMcpTasksMigration();\n\n/** Row shape returned by SELECT queries. */\ninterface TaskRow {\n task_id: string;\n status: Task['status'];\n ttl: number | null;\n poll_interval: number;\n status_message: string | null;\n request_id: string;\n request: Record<string, unknown>;\n result: Record<string, unknown> | null;\n created_at: string;\n last_updated_at: string;\n expires_at: string | null;\n}\n\n/** Convert a database row to an MCP Task object. */\nfunction rowToTask(row: TaskRow): Task {\n const task: Task = {\n taskId: row.task_id,\n status: row.status,\n ttl: row.ttl,\n createdAt: new Date(row.created_at).toISOString(),\n lastUpdatedAt: new Date(row.last_updated_at).toISOString(),\n };\n if (row.poll_interval != null) {\n task.pollInterval = row.poll_interval;\n }\n if (row.status_message != null) {\n task.statusMessage = row.status_message;\n }\n return task;\n}\n\n/** WHERE clause that filters out expired tasks. */\nconst NOT_EXPIRED = `(expires_at IS NULL OR expires_at > NOW())`;\n\n/**\n * PostgreSQL-backed implementation of the MCP SDK TaskStore interface.\n *\n * All reads filter out expired tasks (via `expires_at`), so no background\n * timer is needed — expired tasks are simply invisible. Call\n * `cleanupExpired()` periodically to reclaim storage.\n */\nexport class PostgresTaskStore implements TaskStore {\n private readonly table: string;\n\n constructor(\n private readonly db: PgQueryable,\n options?: PostgresTaskStoreOptions\n ) {\n this.table = options?.tableName ?? DEFAULT_TABLE;\n if (!VALID_IDENTIFIER.test(this.table)) {\n throw new Error(`Invalid table name \"${this.table}\": must match ${VALID_IDENTIFIER}`);\n }\n }\n\n /**\n * Create a new task. Pass `taskParams.taskId` to use a caller-supplied ID verbatim\n * (useful for compliance-controller scenarios where the runner needs deterministic\n * task IDs). If omitted, a random hex ID is generated. Throws if the supplied ID\n * is empty / longer than 128 chars, or already exists. The 128-char ceiling is an\n * SDK policy (matches typical request-id / session-id field lengths and keeps\n * the task_id index efficient) — Postgres TEXT itself imposes no limit.\n *\n * The `task_id` namespace on this store is global (no tenant scoping in the schema\n * today). Callers using caller-supplied IDs are responsible for namespace isolation;\n * cross-tenant collisions surface as `already exists`. Track the schema fix\n * (composite key on tenant + task_id) in a future SDK migration if production paths\n * ever wire caller-supplied IDs.\n */\n async createTask(\n taskParams: CreateTaskOptions & { taskId?: string },\n requestId: RequestId,\n request: Request,\n _sessionId?: string\n ): Promise<Task> {\n if (taskParams.taskId !== undefined) {\n if (typeof taskParams.taskId !== 'string' || taskParams.taskId.length === 0) {\n throw new Error('taskId must be a non-empty string when supplied');\n }\n if (taskParams.taskId.length > 128) {\n throw new Error(`taskId must be 128 characters or fewer (got ${taskParams.taskId.length})`);\n }\n }\n const taskId = taskParams.taskId ?? randomBytes(16).toString('hex');\n const ttl = taskParams.ttl ?? null;\n const pollInterval = taskParams.pollInterval ?? 1000;\n\n try {\n const { rows } = await this.db.query(\n `INSERT INTO ${this.table} (task_id, status, ttl, poll_interval, request_id, request, expires_at)\n VALUES ($1, 'working', $2, $3, $4, $5,\n CASE WHEN $2::integer IS NOT NULL\n THEN NOW() + ($2::integer || ' milliseconds')::interval\n ELSE NULL END)\n RETURNING *`,\n [taskId, ttl, pollInterval, String(requestId), JSON.stringify(request)]\n );\n\n return rowToTask(rows[0] as unknown as TaskRow);\n } catch (err) {\n // Unique constraint violation — a task with this ID already exists.\n if (typeof err === 'object' && err !== null && (err as { code?: unknown }).code === '23505') {\n throw new Error(\n `Task with ID ${taskId} already exists. Use a different taskId or retrieve the existing task via getTask().`\n );\n }\n throw err;\n }\n }\n\n async getTask(taskId: string, _sessionId?: string): Promise<Task | null> {\n const { rows } = await this.db.query(`SELECT * FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`, [taskId]);\n return rows.length > 0 ? rowToTask(rows[0] as unknown as TaskRow) : null;\n }\n\n async storeTaskResult(\n taskId: string,\n status: 'completed' | 'failed',\n result: Result,\n _sessionId?: string\n ): Promise<void> {\n // Atomic check-and-update: only modify if task exists and is non-terminal.\n const { rowCount, rows } = await this.db.query(\n `UPDATE ${this.table}\n SET status = $2,\n result = $3,\n last_updated_at = NOW(),\n expires_at = CASE WHEN ttl IS NOT NULL\n THEN NOW() + (ttl || ' milliseconds')::interval\n ELSE NULL END\n WHERE task_id = $1\n AND status NOT IN ('completed', 'failed', 'cancelled')\n AND ${NOT_EXPIRED}\n RETURNING status`,\n [taskId, status, JSON.stringify(result)]\n );\n\n if (!rowCount || rows.length === 0) {\n // Distinguish \"not found\" / \"expired\" from \"already terminal\".\n const { rows: existing } = await this.db.query(\n `SELECT status FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`,\n [taskId]\n );\n if (existing.length === 0) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n throw new Error(\n `Cannot store result for task ${taskId} in terminal status '${existing[0]!.status}'. Task results can only be stored once.`\n );\n }\n }\n\n async getTaskResult(taskId: string, _sessionId?: string): Promise<Result> {\n const { rows } = await this.db.query(`SELECT result FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`, [\n taskId,\n ]);\n\n if (rows.length === 0) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n const row = rows[0]!;\n if (row.result == null) {\n throw new Error(`Task ${taskId} has no result stored`);\n }\n return row.result as Result;\n }\n\n async updateTaskStatus(\n taskId: string,\n status: Task['status'],\n statusMessage?: string,\n _sessionId?: string\n ): Promise<void> {\n // Atomic: only update if not already in a terminal state.\n const { rowCount, rows } = await this.db.query(\n `UPDATE ${this.table}\n SET status = $2,\n status_message = COALESCE($3, status_message),\n last_updated_at = NOW(),\n expires_at = CASE\n WHEN $2 IN ('completed', 'failed', 'cancelled') AND ttl IS NOT NULL\n THEN NOW() + (ttl || ' milliseconds')::interval\n ELSE expires_at END\n WHERE task_id = $1\n AND status NOT IN ('completed', 'failed', 'cancelled')\n AND ${NOT_EXPIRED}\n RETURNING status`,\n [taskId, status, statusMessage ?? null]\n );\n\n if (!rowCount || rows.length === 0) {\n const { rows: existing } = await this.db.query(\n `SELECT status FROM ${this.table} WHERE task_id = $1 AND ${NOT_EXPIRED}`,\n [taskId]\n );\n if (existing.length === 0) {\n throw new Error(`Task with ID ${taskId} not found`);\n }\n throw new Error(\n `Cannot update task ${taskId} from terminal status '${existing[0]!.status}' to '${status}'. Terminal states (completed, failed, cancelled) cannot transition to other states.`\n );\n }\n }\n\n async listTasks(cursor?: string, _sessionId?: string): Promise<{ tasks: Task[]; nextCursor?: string }> {\n let rawRows: Record<string, unknown>[];\n\n if (cursor) {\n // Decode cursor: \"created_at|task_id\" — split on first | only,\n // so task IDs containing | are handled correctly.\n const sepIndex = cursor.indexOf('|');\n if (sepIndex < 1) {\n throw new Error(`Invalid cursor: ${cursor}`);\n }\n const cursorCreatedAt = cursor.slice(0, sepIndex);\n const cursorTaskId = cursor.slice(sepIndex + 1);\n if (!cursorTaskId || isNaN(Date.parse(cursorCreatedAt))) {\n throw new Error(`Invalid cursor: ${cursor}`);\n }\n\n ({ rows: rawRows } = await this.db.query(\n `SELECT *, created_at::text AS created_at_raw FROM ${this.table}\n WHERE ${NOT_EXPIRED}\n AND (created_at, task_id) > ($1::timestamptz, $2)\n ORDER BY created_at, task_id\n LIMIT $3`,\n [cursorCreatedAt, cursorTaskId, PAGE_SIZE + 1]\n ));\n } else {\n ({ rows: rawRows } = await this.db.query(\n `SELECT *, created_at::text AS created_at_raw FROM ${this.table}\n WHERE ${NOT_EXPIRED}\n ORDER BY created_at, task_id\n LIMIT $1`,\n [PAGE_SIZE + 1]\n ));\n }\n\n const rows = rawRows as unknown as (TaskRow & { created_at_raw: string })[];\n const hasMore = rows.length > PAGE_SIZE;\n const pageRows = hasMore ? rows.slice(0, PAGE_SIZE) : rows;\n const tasks = pageRows.map(rowToTask);\n\n let nextCursor: string | undefined;\n if (hasMore && pageRows.length > 0) {\n const last = pageRows[pageRows.length - 1]!;\n // Use the raw PostgreSQL text representation to preserve microsecond\n // precision. JavaScript Date only has millisecond precision, which\n // causes keyset pagination to re-include rows when multiple tasks\n // share the same millisecond.\n nextCursor = `${last.created_at_raw}|${last.task_id}`;\n }\n\n return { tasks, nextCursor };\n }\n\n /**\n * Delete expired tasks from the table.\n *\n * Call this on a schedule (e.g., every 5 minutes) to reclaim storage.\n * Reads automatically filter expired rows, so this is purely a storage\n * optimization — not required for correctness.\n *\n * @returns The number of deleted rows.\n */\n async cleanupExpired(): Promise<number> {\n const { rowCount } = await this.db.query(\n `DELETE FROM ${this.table} WHERE expires_at IS NOT NULL AND expires_at <= NOW()`\n );\n return rowCount ?? 0;\n }\n\n /**\n * No-op — PostgresTaskStore has no timers or background processes.\n * Matches the cleanup() method that InMemoryTaskStore exposes.\n */\n cleanup(): void {\n // Nothing to clean up — no timers.\n }\n}\n\n/**\n * Delete expired tasks from the task store table.\n *\n * Standalone version of `PostgresTaskStore.cleanupExpired()` for use\n * without a store instance.\n *\n * @returns The number of deleted rows.\n */\nexport async function cleanupExpiredTasks(db: PgQueryable, tableName: string = DEFAULT_TABLE): Promise<number> {\n if (!VALID_IDENTIFIER.test(tableName)) {\n throw new Error(`Invalid table name \"${tableName}\": must match ${VALID_IDENTIFIER}`);\n }\n const { rowCount } = await db.query(`DELETE FROM ${tableName} WHERE expires_at IS NOT NULL AND expires_at <= NOW()`);\n return rowCount ?? 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
const DEFAULT_REPORTING_CAPABILITIES = {
|
|
8
2
|
available_reporting_frequencies: ["daily"],
|
|
9
3
|
expected_delay_minutes: 240,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/product-defaults.ts"],"sourcesContent":["/**\n * Sensible defaults for ReportingCapabilities — a required field on Product\n * with 6 required sub-fields. Spread into your product definitions:\n *\n * @example\n * ```typescript\n * const product = {\n * product_id: 'display-1',\n * name: 'Display Ads',\n * reporting_capabilities: DEFAULT_REPORTING_CAPABILITIES,\n * };\n * ```\n */\n\nimport type { ReportingCapabilities } from '../types/core.generated';\n\nexport const DEFAULT_REPORTING_CAPABILITIES: ReportingCapabilities = {\n available_reporting_frequencies: ['daily'],\n expected_delay_minutes: 240,\n timezone: 'UTC',\n supports_webhooks: false,\n available_metrics: ['impressions', 'spend', 'clicks'],\n date_range_support: 'date_range',\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/product-defaults.ts"],"sourcesContent":["/**\n * Sensible defaults for ReportingCapabilities — a required field on Product\n * with 6 required sub-fields. Spread into your product definitions:\n *\n * @example\n * ```typescript\n * const product = {\n * product_id: 'display-1',\n * name: 'Display Ads',\n * reporting_capabilities: DEFAULT_REPORTING_CAPABILITIES,\n * };\n * ```\n */\n\nimport type { ReportingCapabilities } from '../types/core.generated';\n\nexport const DEFAULT_REPORTING_CAPABILITIES: ReportingCapabilities = {\n available_reporting_frequencies: ['daily'],\n expected_delay_minutes: 240,\n timezone: 'UTC',\n supports_webhooks: false,\n available_metrics: ['impressions', 'spend', 'clicks'],\n date_range_support: 'date_range',\n};\n"],"mappings":"AAgBO,MAAM,iCAAwD;AAAA,EACnE,iCAAiC,CAAC,OAAO;AAAA,EACzC,wBAAwB;AAAA,EACxB,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,mBAAmB,CAAC,eAAe,SAAS,QAAQ;AAAA,EACpD,oBAAoB;AACtB;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
const CREDENTIAL_LABEL = "(api[_-]?key|key[_-]?id|signing[_-]?key|client[_-]?secret|client[_-]?id|password|passwd|pwd|secret|token|key|jwt|bearer)";
|
|
8
2
|
function redactCredentialPatterns(message) {
|
|
9
3
|
if (typeof message !== "string" || message.length === 0) return message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/redact.ts"],"sourcesContent":["/**\n * Credential-shape redactor for diagnostic strings projected to the wire\n * via `error.details.reason`. Stage 4 of #1269 — defense-in-depth against\n * adopter-thrown errors whose `message` includes credential payloads\n * (raw bearer tokens, OAuth client secrets, signing keyids).\n *\n * **Production-default exposure is already gated** on\n * `exposeErrorDetails: process.env.NODE_ENV !== 'production'`. This\n * redactor closes the pre-production gap: adopters running in dev /\n * staging with `exposeErrorDetails: true` no longer leak credential\n * bytes when their resolver throws an error like\n * `Error('lookup failed for token=sk_live_abc')`.\n *\n * The redactor is intentionally conservative — it produces false\n * positives (legitimate IDs that match a credential pattern get\n * redacted too) rather than false negatives. Adopters who need the\n * unredacted error log it server-side, where the framework's logger\n * forwards `err.message` unchanged.\n *\n * @internal — used by the dispatcher's `err.message → details.reason`\n * projections (`create-adcp-server.ts`). Not part of the adopter\n * surface; adopters who want to sanitize `error.details` for their own\n * pipelines reach for `pickSafeDetails` instead.\n */\n\n// Credential-label alternation reused across multiple patterns. Captures\n// common credential field names with both `_` and `-` separators.\n// `key` is the bare form (e.g. `key=foo`); the underscored variants\n// (`api_key`, `key_id`, `signing_key`) are explicit alternation\n// branches because regex alternation matches left-to-right and the\n// longer labels would never match if `key` came first.\nconst CREDENTIAL_LABEL =\n '(api[_-]?key|key[_-]?id|signing[_-]?key|client[_-]?secret|client[_-]?id|password|passwd|pwd|secret|token|key|jwt|bearer)';\n\n/**\n * Patterns the redactor scrubs. Applied in order; each pattern replaces\n * the matched substring with a placeholder. Patterns are case-insensitive\n * where the match makes sense.\n *\n * 1. `Authorization: Bearer <token>` and similar HTTP-Auth shapes.\n * 2. URL-embedded basic-auth: `https://user:pass@host/` → strips the\n * password component (preserves scheme + user for diagnostic value).\n * 3. JSON-quoted credential fields: `\"token\":\"value\"`,\n * `\"client_secret\":\"value\"`, etc.\n * 4. Unquoted / single-quoted credential fields: `token=value`,\n * `client_id: value`, `key=value` — varied separators and quoting.\n * 5. Long alphanumeric strings (length ≥ 32) that look like tokens\n * (no spaces, hex / base64 / base64url charsets). Catches tokens\n * appearing without a labeling prefix.\n *\n * The 32-character threshold for the unlabeled pattern balances\n * catching real tokens (most production tokens are ≥ 32 chars) against\n * false positives on shorter IDs (UUIDs at 36 chars match, which is\n * fine — a UUID embedded in an error message gets redacted, the\n * server-side log keeps it).\n *\n * Signature accepts `unknown` rather than `string` so the dispatcher\n * can pass `String(err)` results without coercing first; the runtime\n * guard returns the input unchanged for non-string values.\n */\nexport function redactCredentialPatterns(message: unknown): unknown {\n if (typeof message !== 'string' || message.length === 0) return message;\n return (\n message\n // 1. Authorization headers — `Bearer <token>`.\n .replace(/\\bBearer\\s+[A-Za-z0-9_\\-.~+/=]{8,}/gi, 'Bearer <redacted>')\n // 2. URL-embedded basic-auth: strip the password component.\n // Matches `scheme://user:password@host/...` and replaces just\n // the password, preserving scheme + user for diagnostic value.\n .replace(/(https?:\\/\\/[^:/\\s@]+:)[^@\\s]+@/gi, '$1<redacted>@')\n // 3. JSON-quoted credential properties: \"token\":\"value\",\n // \"client_secret\":\"value\", etc. Matches any non-empty quoted\n // value (including short ones — labeled credentials are always\n // secrets regardless of length).\n .replace(new RegExp(`\"${CREDENTIAL_LABEL}\"\\\\s*:\\\\s*\"[^\"]+\"`, 'gi'), '\"$1\":\"<redacted>\"')\n // 4. Unquoted / single-quoted credential properties:\n // `token=value`, `client_id: value`. Allows optional quote\n // around both the label and the value.\n .replace(\n new RegExp(`\\\\b${CREDENTIAL_LABEL}['\"]?\\\\s*[=:]\\\\s*['\"]?[A-Za-z0-9_\\\\-.~+/=]{4,}['\"]?`, 'gi'),\n '$1=<redacted>'\n )\n // 5. Long token-shaped strings (≥ 32 chars of base64/hex/url-safe)\n // without an obvious label. Word-boundary anchored so legitimate\n // prose / sentence fragments don't match. The lower bound is 32\n // to avoid eating short hex IDs (8-char UUID prefixes etc.).\n .replace(/\\b[A-Za-z0-9_\\-.~+/=]{32,}\\b/g, '<redacted-token>')\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/redact.ts"],"sourcesContent":["/**\n * Credential-shape redactor for diagnostic strings projected to the wire\n * via `error.details.reason`. Stage 4 of #1269 — defense-in-depth against\n * adopter-thrown errors whose `message` includes credential payloads\n * (raw bearer tokens, OAuth client secrets, signing keyids).\n *\n * **Production-default exposure is already gated** on\n * `exposeErrorDetails: process.env.NODE_ENV !== 'production'`. This\n * redactor closes the pre-production gap: adopters running in dev /\n * staging with `exposeErrorDetails: true` no longer leak credential\n * bytes when their resolver throws an error like\n * `Error('lookup failed for token=sk_live_abc')`.\n *\n * The redactor is intentionally conservative — it produces false\n * positives (legitimate IDs that match a credential pattern get\n * redacted too) rather than false negatives. Adopters who need the\n * unredacted error log it server-side, where the framework's logger\n * forwards `err.message` unchanged.\n *\n * @internal — used by the dispatcher's `err.message → details.reason`\n * projections (`create-adcp-server.ts`). Not part of the adopter\n * surface; adopters who want to sanitize `error.details` for their own\n * pipelines reach for `pickSafeDetails` instead.\n */\n\n// Credential-label alternation reused across multiple patterns. Captures\n// common credential field names with both `_` and `-` separators.\n// `key` is the bare form (e.g. `key=foo`); the underscored variants\n// (`api_key`, `key_id`, `signing_key`) are explicit alternation\n// branches because regex alternation matches left-to-right and the\n// longer labels would never match if `key` came first.\nconst CREDENTIAL_LABEL =\n '(api[_-]?key|key[_-]?id|signing[_-]?key|client[_-]?secret|client[_-]?id|password|passwd|pwd|secret|token|key|jwt|bearer)';\n\n/**\n * Patterns the redactor scrubs. Applied in order; each pattern replaces\n * the matched substring with a placeholder. Patterns are case-insensitive\n * where the match makes sense.\n *\n * 1. `Authorization: Bearer <token>` and similar HTTP-Auth shapes.\n * 2. URL-embedded basic-auth: `https://user:pass@host/` → strips the\n * password component (preserves scheme + user for diagnostic value).\n * 3. JSON-quoted credential fields: `\"token\":\"value\"`,\n * `\"client_secret\":\"value\"`, etc.\n * 4. Unquoted / single-quoted credential fields: `token=value`,\n * `client_id: value`, `key=value` — varied separators and quoting.\n * 5. Long alphanumeric strings (length ≥ 32) that look like tokens\n * (no spaces, hex / base64 / base64url charsets). Catches tokens\n * appearing without a labeling prefix.\n *\n * The 32-character threshold for the unlabeled pattern balances\n * catching real tokens (most production tokens are ≥ 32 chars) against\n * false positives on shorter IDs (UUIDs at 36 chars match, which is\n * fine — a UUID embedded in an error message gets redacted, the\n * server-side log keeps it).\n *\n * Signature accepts `unknown` rather than `string` so the dispatcher\n * can pass `String(err)` results without coercing first; the runtime\n * guard returns the input unchanged for non-string values.\n */\nexport function redactCredentialPatterns(message: unknown): unknown {\n if (typeof message !== 'string' || message.length === 0) return message;\n return (\n message\n // 1. Authorization headers — `Bearer <token>`.\n .replace(/\\bBearer\\s+[A-Za-z0-9_\\-.~+/=]{8,}/gi, 'Bearer <redacted>')\n // 2. URL-embedded basic-auth: strip the password component.\n // Matches `scheme://user:password@host/...` and replaces just\n // the password, preserving scheme + user for diagnostic value.\n .replace(/(https?:\\/\\/[^:/\\s@]+:)[^@\\s]+@/gi, '$1<redacted>@')\n // 3. JSON-quoted credential properties: \"token\":\"value\",\n // \"client_secret\":\"value\", etc. Matches any non-empty quoted\n // value (including short ones — labeled credentials are always\n // secrets regardless of length).\n .replace(new RegExp(`\"${CREDENTIAL_LABEL}\"\\\\s*:\\\\s*\"[^\"]+\"`, 'gi'), '\"$1\":\"<redacted>\"')\n // 4. Unquoted / single-quoted credential properties:\n // `token=value`, `client_id: value`. Allows optional quote\n // around both the label and the value.\n .replace(\n new RegExp(`\\\\b${CREDENTIAL_LABEL}['\"]?\\\\s*[=:]\\\\s*['\"]?[A-Za-z0-9_\\\\-.~+/=]{4,}['\"]?`, 'gi'),\n '$1=<redacted>'\n )\n // 5. Long token-shaped strings (≥ 32 chars of base64/hex/url-safe)\n // without an obvious label. Word-boundary anchored so legitimate\n // prose / sentence fragments don't match. The lower bound is 32\n // to avoid eating short hex IDs (8-char UUID prefixes etc.).\n .replace(/\\b[A-Za-z0-9_\\-.~+/=]{32,}\\b/g, '<redacted-token>')\n );\n}\n"],"mappings":"AA+BA,MAAM,mBACJ;AA4BK,SAAS,yBAAyB,SAA2B;AAClE,MAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,EAAG,QAAO;AAChE,SACE,QAEG,QAAQ,wCAAwC,mBAAmB,EAInE,QAAQ,qCAAqC,eAAe,EAK5D,QAAQ,IAAI,OAAO,IAAI,gBAAgB,qBAAqB,IAAI,GAAG,mBAAmB,EAItF;AAAA,IACC,IAAI,OAAO,MAAM,gBAAgB,uDAAuD,IAAI;AAAA,IAC5F;AAAA,EACF,EAKC,QAAQ,iCAAiC,kBAAkB;AAElE;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import { validActionsForStatus } from "./media-buy-helpers.mjs";
|
|
8
2
|
function toStructuredContent(data) {
|
|
9
3
|
return data;
|