@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,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 { randomUUID } from "node:crypto";
|
|
8
2
|
import { injectContext } from "./context.mjs";
|
|
9
3
|
import { WEBHOOK_IDEMPOTENCY_KEY_PATTERN } from "./types.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/storyboard/webhook-assertions.ts"],"sourcesContent":["/**\n * Webhook-assertion pseudo-tasks.\n *\n * Three step `task` values graded by observing the runner's webhook\n * receiver rather than by calling the agent:\n *\n * - `expect_webhook`\n * Assert at least one matching webhook arrived carrying a\n * well-formed `idempotency_key`. Optional: body-schema validation and\n * max-deliveries-per-logical-event cap (catches duplicate-side-effect\n * bugs on replay).\n *\n * - `expect_webhook_retry_keys_stable`\n * Configure the receiver to reject the first N deliveries with 5xx so\n * the sender retries, then assert every delivery within the window\n * carries the byte-identical `idempotency_key` (a key rotation across\n * retries breaks at-least-once delivery semantics).\n *\n * - `expect_webhook_signature_valid`\n * Delegate to the @adcp/sdk 9421 webhook verifier (pending; grades\n * `not_applicable` until the client verifier lands). Asserts the\n * arriving webhook verifies under the `adcp/webhook-signing/v1` profile.\n *\n * Spec: adcontextprotocol/adcp#2431 (storyboard-schema.yaml webhook section).\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { injectContext, type RunnerVariables } from './context';\nimport type {\n HttpProbeResult,\n RunnerExtractionRecord,\n RunnerRequestRecord,\n SchemaValidationError,\n Storyboard,\n StoryboardContext,\n StoryboardRunOptions,\n StoryboardStep,\n StoryboardStepPreview,\n StoryboardStepResult,\n ValidationResult,\n WebhookAssertionErrorCode,\n} from './types';\nimport { WEBHOOK_IDEMPOTENCY_KEY_PATTERN } from './types';\nimport type { CapturedWebhook, RetryReplayPolicy, WebhookFilter, WebhookReceiver } from './webhook-receiver';\nimport { verifyWebhookSignature } from '../../signing/webhook-verifier';\nimport { WebhookSignatureError } from '../../signing/errors';\nimport { InMemoryReplayStore, type ReplayStore } from '../../signing/replay';\nimport { InMemoryRevocationStore, type RevocationStore } from '../../signing/revocation';\nimport type { RequestLike } from '../../signing/canonicalize';\nimport { getSchemaValidatorByRef } from '../../validation/schema-loader';\nimport { ADCP_VERSION } from '../../version';\n\nconst RUN_STATE_REPLAY_KEY = '__webhook_signing_replay_store';\nconst RUN_STATE_REVOCATION_KEY = '__webhook_signing_revocation_store';\nconst SCHEMA_URL_BASE = 'https://adcontextprotocol.org';\n\n/**\n * Return a replay store that's shared for the rest of this run, so a\n * replayed `(keyid, nonce)` across two deliveries of the same event is\n * detected. Lazy-constructed on first use so runs without signature\n * assertions pay zero cost. Explicit caller-supplied stores take\n * precedence — operators who want durable cross-run detection wire their\n * own store via `options.webhook_signing.replayStore`.\n */\nfunction getSharedReplayStore(runnerVars: RunnerVariables, override: ReplayStore | undefined): ReplayStore {\n if (override) return override;\n const existing = runnerVars.runState.get(RUN_STATE_REPLAY_KEY);\n if (existing) return existing as ReplayStore;\n const store = new InMemoryReplayStore();\n runnerVars.runState.set(RUN_STATE_REPLAY_KEY, store);\n return store;\n}\n\nfunction getSharedRevocationStore(runnerVars: RunnerVariables, override: RevocationStore | undefined): RevocationStore {\n if (override) return override;\n const existing = runnerVars.runState.get(RUN_STATE_REVOCATION_KEY);\n if (existing) return existing as RevocationStore;\n const store = new InMemoryRevocationStore();\n runnerVars.runState.set(RUN_STATE_REVOCATION_KEY, store);\n return store;\n}\n\nexport const WEBHOOK_ASSERTION_TASKS: Set<string> = new Set([\n 'expect_webhook',\n 'expect_webhook_retry_keys_stable',\n 'expect_webhook_signature_valid',\n]);\n\nconst DEFAULT_TIMEOUT_SECONDS = 30;\nconst DEFAULT_RETRY_REPLAY_TIMEOUT_SECONDS = 90;\nconst DEFAULT_RETRY_COUNT = 3;\nconst DEFAULT_RETRY_HTTP_STATUS = 503;\nconst DEFAULT_MIN_DELIVERIES = 2;\nconst DEFAULT_WEBHOOK_SIGNING_TAG = 'adcp/webhook-signing/v1';\n\n/**\n * Caps on storyboard-declared knobs. Storyboards are data; treat their\n * fields as untrusted when they drive network behavior against a real\n * seller. A typo'd `retry_trigger.count: 1_000_000` would otherwise turn\n * the runner into a DoS amplifier; a `timeout_seconds: 86400` would wedge\n * CI for a day. Clamps here fail-safe (use the max, don't reject) so a\n * compliance run never dies on a storyboard author's fat-fingered YAML.\n */\nconst MAX_RETRY_REPLAY_COUNT = 10;\nconst MAX_TIMEOUT_SECONDS = 300;\n/** HTTP statuses the retry-replay policy may return. 429 + 5xx are the\n * shapes any conformant at-least-once sender treats as retryable. */\nconst ALLOWED_RETRY_HTTP_STATUSES: ReadonlySet<number> = new Set([429, 500, 502, 503, 504]);\n\nfunction clampTimeoutSeconds(raw: number | undefined, fallback: number): number {\n const resolved = typeof raw === 'number' && raw > 0 ? raw : fallback;\n return Math.min(resolved, MAX_TIMEOUT_SECONDS);\n}\n\nfunction clampRetryPolicy(spec: { count?: number; http_status?: number } | undefined): {\n count: number;\n http_status: number;\n} {\n const requestedCount = spec?.count ?? DEFAULT_RETRY_COUNT;\n const count = Math.max(1, Math.min(requestedCount, MAX_RETRY_REPLAY_COUNT));\n const requestedStatus = spec?.http_status ?? DEFAULT_RETRY_HTTP_STATUS;\n const http_status = ALLOWED_RETRY_HTTP_STATUSES.has(requestedStatus) ? requestedStatus : DEFAULT_RETRY_HTTP_STATUS;\n return { count, http_status };\n}\n\ninterface WebhookAssertionRunState {\n contributions: Set<string>;\n priorStepResults: Map<string, StoryboardStepResult>;\n priorProbes: Map<string, HttpProbeResult>;\n agentUrl: string;\n webhookReceiver?: WebhookReceiver;\n runnerVars?: RunnerVariables;\n}\n\ninterface FlatStep {\n step: StoryboardStep;\n phaseId: string;\n globalIndex: number;\n}\n\ntype GetNextPreview = (currentStepId: string) => StoryboardStepPreview | undefined;\n\n/**\n * Pre-mint operation ids and install retry-replay policies for every\n * `expect_webhook_retry_keys_stable` step declared by the storyboard.\n *\n * Called once at runStoryboard init — ordering matters: the receiver must\n * be armed BEFORE the triggering step runs or the first delivery will be\n * accepted and the sender won't retry.\n */\nexport function armWebhookAssertions(\n storyboard: Storyboard,\n runnerVars: RunnerVariables,\n receiver: WebhookReceiver\n): void {\n for (const phase of storyboard.phases) {\n for (const step of phase.steps) {\n if (step.task !== 'expect_webhook_retry_keys_stable') continue;\n if (!step.triggered_by) continue;\n\n // Pre-mint operation_id so the triggering step's {{runner.webhook_url:<id>}}\n // expansion matches the retry-policy key here.\n let opId = runnerVars.stepOperationIds.get(step.triggered_by);\n if (!opId) {\n opId = randomUUID();\n runnerVars.stepOperationIds.set(step.triggered_by, opId);\n }\n const policy: RetryReplayPolicy = clampRetryPolicy(step.retry_trigger);\n receiver.set_retry_replay({ step_id: step.triggered_by, operation_id: opId }, policy);\n }\n }\n}\n\n/**\n * Dispatch any of the three webhook-assertion pseudo-tasks.\n *\n * Called by runner.executeStep when the step's `task` is in\n * `WEBHOOK_ASSERTION_TASKS`. Every path returns a `StoryboardStepResult`\n * with validations that carry the spec error codes in `actual.code`.\n */\nexport async function executeWebhookAssertionStep(\n step: StoryboardStep,\n phaseId: string,\n context: StoryboardContext,\n allSteps: FlatStep[],\n options: StoryboardRunOptions,\n runState: WebhookAssertionRunState,\n getNextPreview?: GetNextPreview\n): Promise<StoryboardStepResult> {\n const start = Date.now();\n const next = getNextPreview?.(step.id);\n const requestRecord: RunnerRequestRecord = {\n transport: 'http',\n operation: step.task,\n payload: null,\n };\n const extraction: RunnerExtractionRecord = { path: 'none', note: 'webhook-assertion step' };\n\n // `requires_contract` skip — assertion opts out cleanly when the harness\n // contract the author depended on isn't configured on this runner.\n if (step.requires_contract) {\n const contracts = new Set(options.contracts ?? []);\n if (!contracts.has(step.requires_contract)) {\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'unsatisfied_contract',\n detail: `Test-kit contract \"${step.requires_contract}\" is not configured on this runner.`,\n extraction,\n request: requestRecord,\n next,\n });\n }\n }\n\n if (!runState.webhookReceiver || !runState.runnerVars) {\n // Webhook receiver isn't enabled — equivalent to \"contract not in scope\"\n // for compliance reports. Grade as not_applicable, not fail.\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'unsatisfied_contract',\n detail:\n `Step \"${step.task}\" requires an ephemeral webhook receiver. Pass ` +\n '`webhook_receiver` on runStoryboard options to enable.',\n extraction,\n request: requestRecord,\n next,\n });\n }\n\n // If the triggering step failed or was skipped, there's no webhook to\n // observe — surface as prerequisite_failed rather than timing out.\n if (step.triggered_by) {\n const prior = runState.priorStepResults.get(step.triggered_by);\n if (prior && (prior.skipped || !prior.passed)) {\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'prerequisite_failed',\n detail: `Triggering step \"${step.triggered_by}\" did not complete successfully.`,\n extraction,\n request: requestRecord,\n next,\n });\n }\n }\n\n const receiver = runState.webhookReceiver;\n const runnerVars = runState.runnerVars;\n const filter = buildFilter(step, context, runnerVars);\n\n let validations: ValidationResult[];\n let passed: boolean;\n\n switch (step.task) {\n case 'expect_webhook': {\n const outcome = await runExpectWebhook(step, filter, receiver);\n validations = outcome.validations;\n passed = outcome.passed;\n break;\n }\n case 'expect_webhook_retry_keys_stable': {\n const outcome = await runExpectRetryKeysStable(step, filter, receiver);\n validations = outcome.validations;\n passed = outcome.passed;\n break;\n }\n case 'expect_webhook_signature_valid': {\n const outcome = await runExpectSignatureValid(step, filter, receiver, options, runnerVars);\n validations = outcome.validations;\n passed = outcome.passed;\n // When the verifier isn't configured, the spec says not_applicable\n // rather than fail. Return the skipped shape.\n if (outcome.skipped) {\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'unsatisfied_contract',\n detail: outcome.skipReason ?? 'Signature verifier not configured.',\n extraction,\n request: requestRecord,\n next,\n });\n }\n break;\n }\n default: {\n // Defensive — this function is only called for tasks in WEBHOOK_ASSERTION_TASKS.\n validations = [];\n passed = false;\n }\n }\n\n return {\n step_id: step.id,\n phase_id: phaseId,\n title: step.title,\n task: step.task,\n passed,\n expect_error: step.expect_error,\n duration_ms: Date.now() - start,\n validations,\n context,\n ...(next && { next }),\n request: requestRecord,\n extraction,\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// Filter construction\n// ────────────────────────────────────────────────────────────\n\nfunction buildFilter(step: StoryboardStep, context: StoryboardContext, runnerVars: RunnerVariables): WebhookFilter {\n const filter: WebhookFilter = {};\n\n // Default: scope to the triggering step's URL. Authors can override via\n // an explicit `filter.operation_id` (useful for fan-in tests).\n if (step.triggered_by) {\n filter.step_id = step.triggered_by;\n const priorOpId = runnerVars.stepOperationIds.get(step.triggered_by);\n if (priorOpId) filter.operation_id = priorOpId;\n }\n\n if (step.filter) {\n // Resolve any `{{prior_step.<id>.operation_id}}` / `$context.*` tokens\n // before matching — the storyboard may author operation_id this way.\n const resolved = injectContext({ __f: { ...step.filter } }, context, runnerVars).__f as Record<string, unknown>;\n if (resolved && typeof resolved === 'object') {\n if (typeof resolved.operation_id === 'string') {\n filter.operation_id = resolved.operation_id;\n }\n if (resolved.body && typeof resolved.body === 'object' && !Array.isArray(resolved.body)) {\n filter.body = resolved.body as Record<string, unknown>;\n }\n }\n }\n return filter;\n}\n\n// ────────────────────────────────────────────────────────────\n// expect_webhook\n// ────────────────────────────────────────────────────────────\n\nasync function runExpectWebhook(\n step: StoryboardStep,\n filter: WebhookFilter,\n receiver: WebhookReceiver\n): Promise<{ validations: ValidationResult[]; passed: boolean }> {\n const timeoutMs = clampTimeoutSeconds(step.timeout_seconds, DEFAULT_TIMEOUT_SECONDS) * 1000;\n const checkIdempotency = step.expect_idempotency_key !== false;\n const capCount = step.expect_max_deliveries_per_logical_event;\n\n // When we're asserting a cap (e.g., \"at most 1 logical event on replay\"),\n // wait the full window to let any duplicate delivery arrive before judging.\n // When we just need ≥1 delivery, resolve on first match.\n let matches: CapturedWebhook[];\n if (typeof capCount === 'number') {\n matches = await receiver.wait_all(filter, timeoutMs);\n } else {\n const first = await receiver.wait(filter, timeoutMs);\n matches = first.webhook ? [first.webhook] : [];\n }\n\n if (matches.length === 0) {\n return singleFailure(\n step,\n 'no_webhook_received',\n `No webhook matching filter arrived within ${timeoutMs}ms.`,\n 'webhook delivery matching filter',\n null\n );\n }\n\n const first = matches[0]!;\n if (first.parse_error !== undefined) {\n return singleFailure(\n step,\n 'schema_violation',\n `Webhook body was not valid JSON: ${first.parse_error}`,\n 'JSON object body',\n first.raw_body.slice(0, 256)\n );\n }\n\n const schemaCheck = validateWebhookPayloadSchema(step, first);\n if (schemaCheck && !schemaCheck.passed) return { validations: [schemaCheck], passed: false };\n\n if (checkIdempotency) {\n const idempotencyCheck = assertIdempotencyKey(first);\n if (idempotencyCheck) return { validations: [idempotencyCheck], passed: false };\n }\n\n if (typeof capCount === 'number') {\n const distinctKeys = new Set(matches.map(m => extractIdempotencyKey(m)).filter((k): k is string => !!k));\n if (distinctKeys.size > capCount) {\n return singleFailure(\n step,\n 'duplicate_webhook_on_replay',\n `Observed ${distinctKeys.size} distinct logical events in the window (idempotency_keys); expected ≤ ${capCount}.`,\n `≤ ${capCount} distinct idempotency_keys`,\n Array.from(distinctKeys)\n );\n }\n }\n\n return {\n validations: [\n ...(schemaCheck ? [schemaCheck] : []),\n {\n check: 'expect_webhook',\n passed: true,\n description: `Webhook arrived at ${filter.step_id ?? 'receiver'} with valid idempotency_key.`,\n json_pointer: '/idempotency_key',\n },\n ],\n passed: true,\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// expect_webhook_retry_keys_stable\n// ────────────────────────────────────────────────────────────\n\nasync function runExpectRetryKeysStable(\n step: StoryboardStep,\n filter: WebhookFilter,\n receiver: WebhookReceiver\n): Promise<{ validations: ValidationResult[]; passed: boolean }> {\n const timeoutMs = clampTimeoutSeconds(step.timeout_seconds, DEFAULT_RETRY_REPLAY_TIMEOUT_SECONDS) * 1000;\n const minDeliveries = step.expect_min_deliveries ?? DEFAULT_MIN_DELIVERIES;\n\n const matches = await receiver.wait_all(filter, timeoutMs);\n\n if (matches.length < minDeliveries) {\n return singleFailure(\n step,\n 'insufficient_retries',\n `Observed ${matches.length} deliveries; expected at least ${minDeliveries}. ` +\n 'The sender may not be retrying on 5xx responses.',\n `≥ ${minDeliveries} deliveries`,\n matches.length\n );\n }\n\n const keys = matches.map(m => extractIdempotencyKey(m));\n const missingKey = matches.find((_, i) => !keys[i]);\n if (missingKey) {\n return singleFailure(\n step,\n 'missing_idempotency_key',\n `Delivery ${missingKey.delivery_index} carried no idempotency_key.`,\n 'every delivery carries idempotency_key',\n null\n );\n }\n\n const firstKey = keys[0]!;\n const formatValid = keys.every(k => k && WEBHOOK_IDEMPOTENCY_KEY_PATTERN.test(k));\n if (!formatValid) {\n const bad = keys.find(k => !k || !WEBHOOK_IDEMPOTENCY_KEY_PATTERN.test(k));\n return singleFailure(\n step,\n 'idempotency_key_format_changed',\n `One or more deliveries carried a malformed idempotency_key. Required pattern: ${WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source}`,\n WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source,\n bad ?? null\n );\n }\n\n const rotated = keys.some(k => k !== firstKey);\n if (rotated) {\n return singleFailure(\n step,\n 'idempotency_key_rotated',\n \"idempotency_key rotated across retries. Senders MUST reuse the first delivery's key for every retry of the same logical event.\",\n 'byte-identical idempotency_key on every delivery',\n keys\n );\n }\n\n return {\n validations: [\n {\n check: 'expect_webhook_retry_keys_stable',\n passed: true,\n description: `${matches.length} deliveries observed; all carried byte-identical idempotency_key.`,\n json_pointer: '/idempotency_key',\n },\n ],\n passed: true,\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// expect_webhook_signature_valid\n// ────────────────────────────────────────────────────────────\n\nasync function runExpectSignatureValid(\n step: StoryboardStep,\n filter: WebhookFilter,\n receiver: WebhookReceiver,\n options: StoryboardRunOptions,\n runnerVars: RunnerVariables\n): Promise<{ validations: ValidationResult[]; passed: boolean; skipped?: boolean; skipReason?: string }> {\n const config = options.webhook_signing;\n if (!config) {\n return {\n validations: [],\n passed: true,\n skipped: true,\n skipReason:\n '`expect_webhook_signature_valid` requires `webhook_signing` on runStoryboard options ' +\n '(pass a JwksResolver; replay + revocation stores default to in-memory).',\n };\n }\n\n const timeoutMs = clampTimeoutSeconds(step.timeout_seconds, DEFAULT_TIMEOUT_SECONDS) * 1000;\n const wait = await receiver.wait(filter, timeoutMs);\n if (wait.timed_out || !wait.webhook) {\n return singleFailure(\n step,\n 'no_webhook_received',\n `No webhook matching filter arrived within ${timeoutMs}ms.`,\n 'signed webhook delivery matching filter',\n null\n );\n }\n\n const webhook = wait.webhook;\n void runnerVars;\n const requestLike = toRequestLike(webhook, receiver);\n const requiredTag = step.require_tag ?? config.required_tag ?? DEFAULT_WEBHOOK_SIGNING_TAG;\n\n try {\n await verifyWebhookSignature(requestLike, {\n jwks: config.jwks,\n replayStore: getSharedReplayStore(runnerVars, config.replayStore),\n revocationStore: getSharedRevocationStore(runnerVars, config.revocationStore),\n requiredTag,\n });\n } catch (err) {\n if (err instanceof WebhookSignatureError) {\n return singleFailure(\n step,\n mapSignatureErrorCode(err.code),\n err.message,\n `checklist step ${err.failedStep} passes`,\n { code: err.code, step: err.failedStep }\n );\n }\n return singleFailure(\n step,\n 'signature_invalid',\n err instanceof Error ? err.message : String(err),\n '9421 webhook signature verification',\n null\n );\n }\n\n return {\n validations: [\n {\n check: 'expect_webhook_signature_valid',\n passed: true,\n description: `9421 webhook signature verified under ${requiredTag}.`,\n },\n ],\n passed: true,\n };\n}\n\n/**\n * Translate a captured webhook into the `RequestLike` shape the shared\n * canonicalizer expects. The receiver's `base_url` plus `step_id` /\n * `operation_id` reconstruct the `@target-uri` the publisher signed.\n */\nfunction toRequestLike(webhook: CapturedWebhook, receiver: WebhookReceiver): RequestLike {\n const url = `${receiver.base_url}/step/${webhook.step_id}/${webhook.operation_id}`;\n return {\n method: webhook.method,\n url,\n headers: webhook.headers,\n body: webhook.raw_body,\n };\n}\n\n/**\n * Map the verifier's `webhook_signature_*` error codes to the storyboard-side\n * `signature_*` codes defined by adcontextprotocol/adcp#2431. Exhaustive so\n * that adding a new verifier code without a mapping is a TS compile error\n * rather than a silent collapse onto `signature_invalid` — distinct\n * remediation paths deserve distinct codes (a revoked key needs rotation,\n * a malformed window is a signer bug, etc.).\n */\nfunction mapSignatureErrorCode(\n code: import('../../signing/errors').WebhookSignatureErrorCode\n): WebhookAssertionErrorCode {\n switch (code) {\n case 'webhook_signature_header_malformed':\n return 'signature_header_malformed';\n case 'webhook_signature_params_incomplete':\n return 'signature_params_incomplete';\n case 'webhook_signature_tag_invalid':\n return 'signature_tag_invalid';\n case 'webhook_signature_alg_not_allowed':\n return 'signature_alg_not_allowed';\n case 'webhook_signature_window_invalid':\n return 'signature_window_invalid';\n case 'webhook_signature_components_incomplete':\n return 'signature_components_incomplete';\n case 'webhook_signature_key_unknown':\n return 'signature_key_unknown';\n case 'webhook_signature_key_purpose_invalid':\n return 'signature_key_purpose_invalid';\n case 'webhook_mode_mismatch':\n return 'signature_mode_mismatch';\n case 'webhook_target_uri_malformed':\n return 'signature_target_uri_malformed';\n case 'webhook_signature_key_revoked':\n return 'signature_key_revoked';\n case 'webhook_signature_revocation_stale':\n return 'signature_revocation_stale';\n case 'webhook_signature_rate_abuse':\n return 'signature_rate_abuse';\n case 'webhook_signature_digest_mismatch':\n return 'signature_digest_mismatch';\n case 'webhook_signature_replayed':\n return 'signature_replayed';\n case 'webhook_signature_invalid':\n return 'signature_invalid';\n default: {\n // Exhaustiveness: any unhandled code is a compile error.\n const _exhaustive: never = code;\n void _exhaustive;\n return 'signature_invalid';\n }\n }\n}\n\n// ────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────\n\nfunction validateWebhookPayloadSchema(step: StoryboardStep, webhook: CapturedWebhook): ValidationResult | undefined {\n const schemaRef = step.webhook_payload_schema_ref ?? step.response_schema_webhook_ref;\n if (!schemaRef) return undefined;\n\n const { schema_id, schema_url } = resolveWebhookSchemaIdentity(schemaRef);\n const validator = getSchemaValidatorByRef(schemaRef);\n if (!validator) {\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook payload schema validation failed.',\n error: `No webhook payload schema found for ${schemaRef}.`,\n json_pointer: null,\n expected: schema_id,\n actual: { code: 'schema_violation', reason: 'schema_not_found', schema_ref: schemaRef },\n schema_id,\n schema_url,\n };\n }\n\n const body = webhook.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook payload schema validation failed.',\n error: 'Webhook body was not a JSON object.',\n json_pointer: null,\n expected: schema_id,\n actual: { code: 'schema_violation', reason: 'non_object_body', body },\n schema_id,\n schema_url,\n };\n }\n\n const passed = validator(body);\n if (passed) {\n return {\n check: 'expect_webhook',\n passed: true,\n description: `Webhook payload validates against ${schemaRef}.`,\n json_pointer: null,\n expected: schema_id,\n actual: 'schema_valid',\n schema_id,\n schema_url,\n };\n }\n\n const issues = (validator.errors ?? []).map(ajvErrorToSchemaError);\n const firstIssue = issues[0];\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook payload schema validation failed.',\n error: firstIssue?.message ?? `Webhook body failed ${schemaRef}.`,\n json_pointer: firstIssue?.instance_path ?? null,\n expected: schema_id,\n actual: { code: 'schema_violation', issues },\n schema_id,\n schema_url,\n };\n}\n\nfunction resolveWebhookSchemaIdentity(schemaRef: string): { schema_id: string; schema_url: string } {\n const trimmed = schemaRef.replace(/^\\/+/, '');\n const schemaId = `/schemas/${ADCP_VERSION}/${trimmed}`;\n return { schema_id: schemaId, schema_url: `${SCHEMA_URL_BASE}${schemaId}` };\n}\n\nfunction ajvErrorToSchemaError(error: {\n instancePath?: string;\n schemaPath?: string;\n keyword?: string;\n message?: string;\n}): SchemaValidationError {\n return {\n instance_path: error.instancePath || '/',\n schema_path: error.schemaPath || '#',\n keyword: error.keyword || 'schema',\n message: error.message || 'Webhook payload failed schema validation.',\n };\n}\n\nfunction extractIdempotencyKey(webhook: CapturedWebhook): string | undefined {\n const body = webhook.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) return undefined;\n const key = (body as Record<string, unknown>).idempotency_key;\n return typeof key === 'string' ? key : undefined;\n}\n\nfunction assertIdempotencyKey(webhook: CapturedWebhook): ValidationResult | undefined {\n const body = webhook.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return failure('schema_violation', 'Webhook body was not a JSON object.', 'JSON object body', null);\n }\n const key = (body as Record<string, unknown>).idempotency_key;\n if (typeof key !== 'string' || key.length === 0) {\n return failure(\n 'missing_idempotency_key',\n 'Webhook body is missing a non-empty `idempotency_key` — required by AdCP 3.0.',\n 'non-empty string',\n key ?? null\n );\n }\n if (!WEBHOOK_IDEMPOTENCY_KEY_PATTERN.test(key)) {\n return failure(\n 'invalid_idempotency_key_format',\n `idempotency_key \"${key}\" does not match required pattern ${WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source}.`,\n WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source,\n key\n );\n }\n return undefined;\n}\n\nfunction failure(\n code: WebhookAssertionErrorCode,\n message: string,\n expected: unknown,\n actual: unknown\n): ValidationResult {\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook conformance assertion failed.',\n error: message,\n json_pointer: '/idempotency_key',\n expected,\n actual: { code, actual },\n };\n}\n\nfunction singleFailure(\n step: StoryboardStep,\n code: WebhookAssertionErrorCode,\n message: string,\n expected: unknown,\n actual: unknown\n): { validations: ValidationResult[]; passed: boolean } {\n return {\n validations: [\n {\n check: step.task,\n passed: false,\n description: step.expected ?? step.title,\n error: message,\n json_pointer: '/idempotency_key',\n expected,\n actual: { code, actual },\n },\n ],\n passed: false,\n };\n}\n\nfunction skippedResult(\n step: StoryboardStep,\n phaseId: string,\n context: StoryboardContext,\n start: number,\n opts: {\n skip_reason: 'unsatisfied_contract' | 'prerequisite_failed';\n detail: string;\n extraction: RunnerExtractionRecord;\n request: RunnerRequestRecord;\n next?: StoryboardStepPreview;\n }\n): StoryboardStepResult {\n return {\n step_id: step.id,\n phase_id: phaseId,\n title: step.title,\n task: step.task,\n passed: true,\n skipped: true,\n skip_reason: opts.skip_reason,\n skip: { reason: opts.skip_reason, detail: opts.detail },\n duration_ms: Date.now() - start,\n validations: [],\n context,\n error: opts.detail,\n ...(opts.next && { next: opts.next }),\n request: opts.request,\n extraction: opts.extraction,\n };\n}\n"],"mappings":"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","names":["first"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/storyboard/webhook-assertions.ts"],"sourcesContent":["/**\n * Webhook-assertion pseudo-tasks.\n *\n * Three step `task` values graded by observing the runner's webhook\n * receiver rather than by calling the agent:\n *\n * - `expect_webhook`\n * Assert at least one matching webhook arrived carrying a\n * well-formed `idempotency_key`. Optional: body-schema validation and\n * max-deliveries-per-logical-event cap (catches duplicate-side-effect\n * bugs on replay).\n *\n * - `expect_webhook_retry_keys_stable`\n * Configure the receiver to reject the first N deliveries with 5xx so\n * the sender retries, then assert every delivery within the window\n * carries the byte-identical `idempotency_key` (a key rotation across\n * retries breaks at-least-once delivery semantics).\n *\n * - `expect_webhook_signature_valid`\n * Delegate to the @adcp/sdk 9421 webhook verifier (pending; grades\n * `not_applicable` until the client verifier lands). Asserts the\n * arriving webhook verifies under the `adcp/webhook-signing/v1` profile.\n *\n * Spec: adcontextprotocol/adcp#2431 (storyboard-schema.yaml webhook section).\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { injectContext, type RunnerVariables } from './context';\nimport type {\n HttpProbeResult,\n RunnerExtractionRecord,\n RunnerRequestRecord,\n SchemaValidationError,\n Storyboard,\n StoryboardContext,\n StoryboardRunOptions,\n StoryboardStep,\n StoryboardStepPreview,\n StoryboardStepResult,\n ValidationResult,\n WebhookAssertionErrorCode,\n} from './types';\nimport { WEBHOOK_IDEMPOTENCY_KEY_PATTERN } from './types';\nimport type { CapturedWebhook, RetryReplayPolicy, WebhookFilter, WebhookReceiver } from './webhook-receiver';\nimport { verifyWebhookSignature } from '../../signing/webhook-verifier';\nimport { WebhookSignatureError } from '../../signing/errors';\nimport { InMemoryReplayStore, type ReplayStore } from '../../signing/replay';\nimport { InMemoryRevocationStore, type RevocationStore } from '../../signing/revocation';\nimport type { RequestLike } from '../../signing/canonicalize';\nimport { getSchemaValidatorByRef } from '../../validation/schema-loader';\nimport { ADCP_VERSION } from '../../version';\n\nconst RUN_STATE_REPLAY_KEY = '__webhook_signing_replay_store';\nconst RUN_STATE_REVOCATION_KEY = '__webhook_signing_revocation_store';\nconst SCHEMA_URL_BASE = 'https://adcontextprotocol.org';\n\n/**\n * Return a replay store that's shared for the rest of this run, so a\n * replayed `(keyid, nonce)` across two deliveries of the same event is\n * detected. Lazy-constructed on first use so runs without signature\n * assertions pay zero cost. Explicit caller-supplied stores take\n * precedence — operators who want durable cross-run detection wire their\n * own store via `options.webhook_signing.replayStore`.\n */\nfunction getSharedReplayStore(runnerVars: RunnerVariables, override: ReplayStore | undefined): ReplayStore {\n if (override) return override;\n const existing = runnerVars.runState.get(RUN_STATE_REPLAY_KEY);\n if (existing) return existing as ReplayStore;\n const store = new InMemoryReplayStore();\n runnerVars.runState.set(RUN_STATE_REPLAY_KEY, store);\n return store;\n}\n\nfunction getSharedRevocationStore(runnerVars: RunnerVariables, override: RevocationStore | undefined): RevocationStore {\n if (override) return override;\n const existing = runnerVars.runState.get(RUN_STATE_REVOCATION_KEY);\n if (existing) return existing as RevocationStore;\n const store = new InMemoryRevocationStore();\n runnerVars.runState.set(RUN_STATE_REVOCATION_KEY, store);\n return store;\n}\n\nexport const WEBHOOK_ASSERTION_TASKS: Set<string> = new Set([\n 'expect_webhook',\n 'expect_webhook_retry_keys_stable',\n 'expect_webhook_signature_valid',\n]);\n\nconst DEFAULT_TIMEOUT_SECONDS = 30;\nconst DEFAULT_RETRY_REPLAY_TIMEOUT_SECONDS = 90;\nconst DEFAULT_RETRY_COUNT = 3;\nconst DEFAULT_RETRY_HTTP_STATUS = 503;\nconst DEFAULT_MIN_DELIVERIES = 2;\nconst DEFAULT_WEBHOOK_SIGNING_TAG = 'adcp/webhook-signing/v1';\n\n/**\n * Caps on storyboard-declared knobs. Storyboards are data; treat their\n * fields as untrusted when they drive network behavior against a real\n * seller. A typo'd `retry_trigger.count: 1_000_000` would otherwise turn\n * the runner into a DoS amplifier; a `timeout_seconds: 86400` would wedge\n * CI for a day. Clamps here fail-safe (use the max, don't reject) so a\n * compliance run never dies on a storyboard author's fat-fingered YAML.\n */\nconst MAX_RETRY_REPLAY_COUNT = 10;\nconst MAX_TIMEOUT_SECONDS = 300;\n/** HTTP statuses the retry-replay policy may return. 429 + 5xx are the\n * shapes any conformant at-least-once sender treats as retryable. */\nconst ALLOWED_RETRY_HTTP_STATUSES: ReadonlySet<number> = new Set([429, 500, 502, 503, 504]);\n\nfunction clampTimeoutSeconds(raw: number | undefined, fallback: number): number {\n const resolved = typeof raw === 'number' && raw > 0 ? raw : fallback;\n return Math.min(resolved, MAX_TIMEOUT_SECONDS);\n}\n\nfunction clampRetryPolicy(spec: { count?: number; http_status?: number } | undefined): {\n count: number;\n http_status: number;\n} {\n const requestedCount = spec?.count ?? DEFAULT_RETRY_COUNT;\n const count = Math.max(1, Math.min(requestedCount, MAX_RETRY_REPLAY_COUNT));\n const requestedStatus = spec?.http_status ?? DEFAULT_RETRY_HTTP_STATUS;\n const http_status = ALLOWED_RETRY_HTTP_STATUSES.has(requestedStatus) ? requestedStatus : DEFAULT_RETRY_HTTP_STATUS;\n return { count, http_status };\n}\n\ninterface WebhookAssertionRunState {\n contributions: Set<string>;\n priorStepResults: Map<string, StoryboardStepResult>;\n priorProbes: Map<string, HttpProbeResult>;\n agentUrl: string;\n webhookReceiver?: WebhookReceiver;\n runnerVars?: RunnerVariables;\n}\n\ninterface FlatStep {\n step: StoryboardStep;\n phaseId: string;\n globalIndex: number;\n}\n\ntype GetNextPreview = (currentStepId: string) => StoryboardStepPreview | undefined;\n\n/**\n * Pre-mint operation ids and install retry-replay policies for every\n * `expect_webhook_retry_keys_stable` step declared by the storyboard.\n *\n * Called once at runStoryboard init — ordering matters: the receiver must\n * be armed BEFORE the triggering step runs or the first delivery will be\n * accepted and the sender won't retry.\n */\nexport function armWebhookAssertions(\n storyboard: Storyboard,\n runnerVars: RunnerVariables,\n receiver: WebhookReceiver\n): void {\n for (const phase of storyboard.phases) {\n for (const step of phase.steps) {\n if (step.task !== 'expect_webhook_retry_keys_stable') continue;\n if (!step.triggered_by) continue;\n\n // Pre-mint operation_id so the triggering step's {{runner.webhook_url:<id>}}\n // expansion matches the retry-policy key here.\n let opId = runnerVars.stepOperationIds.get(step.triggered_by);\n if (!opId) {\n opId = randomUUID();\n runnerVars.stepOperationIds.set(step.triggered_by, opId);\n }\n const policy: RetryReplayPolicy = clampRetryPolicy(step.retry_trigger);\n receiver.set_retry_replay({ step_id: step.triggered_by, operation_id: opId }, policy);\n }\n }\n}\n\n/**\n * Dispatch any of the three webhook-assertion pseudo-tasks.\n *\n * Called by runner.executeStep when the step's `task` is in\n * `WEBHOOK_ASSERTION_TASKS`. Every path returns a `StoryboardStepResult`\n * with validations that carry the spec error codes in `actual.code`.\n */\nexport async function executeWebhookAssertionStep(\n step: StoryboardStep,\n phaseId: string,\n context: StoryboardContext,\n allSteps: FlatStep[],\n options: StoryboardRunOptions,\n runState: WebhookAssertionRunState,\n getNextPreview?: GetNextPreview\n): Promise<StoryboardStepResult> {\n const start = Date.now();\n const next = getNextPreview?.(step.id);\n const requestRecord: RunnerRequestRecord = {\n transport: 'http',\n operation: step.task,\n payload: null,\n };\n const extraction: RunnerExtractionRecord = { path: 'none', note: 'webhook-assertion step' };\n\n // `requires_contract` skip — assertion opts out cleanly when the harness\n // contract the author depended on isn't configured on this runner.\n if (step.requires_contract) {\n const contracts = new Set(options.contracts ?? []);\n if (!contracts.has(step.requires_contract)) {\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'unsatisfied_contract',\n detail: `Test-kit contract \"${step.requires_contract}\" is not configured on this runner.`,\n extraction,\n request: requestRecord,\n next,\n });\n }\n }\n\n if (!runState.webhookReceiver || !runState.runnerVars) {\n // Webhook receiver isn't enabled — equivalent to \"contract not in scope\"\n // for compliance reports. Grade as not_applicable, not fail.\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'unsatisfied_contract',\n detail:\n `Step \"${step.task}\" requires an ephemeral webhook receiver. Pass ` +\n '`webhook_receiver` on runStoryboard options to enable.',\n extraction,\n request: requestRecord,\n next,\n });\n }\n\n // If the triggering step failed or was skipped, there's no webhook to\n // observe — surface as prerequisite_failed rather than timing out.\n if (step.triggered_by) {\n const prior = runState.priorStepResults.get(step.triggered_by);\n if (prior && (prior.skipped || !prior.passed)) {\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'prerequisite_failed',\n detail: `Triggering step \"${step.triggered_by}\" did not complete successfully.`,\n extraction,\n request: requestRecord,\n next,\n });\n }\n }\n\n const receiver = runState.webhookReceiver;\n const runnerVars = runState.runnerVars;\n const filter = buildFilter(step, context, runnerVars);\n\n let validations: ValidationResult[];\n let passed: boolean;\n\n switch (step.task) {\n case 'expect_webhook': {\n const outcome = await runExpectWebhook(step, filter, receiver);\n validations = outcome.validations;\n passed = outcome.passed;\n break;\n }\n case 'expect_webhook_retry_keys_stable': {\n const outcome = await runExpectRetryKeysStable(step, filter, receiver);\n validations = outcome.validations;\n passed = outcome.passed;\n break;\n }\n case 'expect_webhook_signature_valid': {\n const outcome = await runExpectSignatureValid(step, filter, receiver, options, runnerVars);\n validations = outcome.validations;\n passed = outcome.passed;\n // When the verifier isn't configured, the spec says not_applicable\n // rather than fail. Return the skipped shape.\n if (outcome.skipped) {\n return skippedResult(step, phaseId, context, start, {\n skip_reason: 'unsatisfied_contract',\n detail: outcome.skipReason ?? 'Signature verifier not configured.',\n extraction,\n request: requestRecord,\n next,\n });\n }\n break;\n }\n default: {\n // Defensive — this function is only called for tasks in WEBHOOK_ASSERTION_TASKS.\n validations = [];\n passed = false;\n }\n }\n\n return {\n step_id: step.id,\n phase_id: phaseId,\n title: step.title,\n task: step.task,\n passed,\n expect_error: step.expect_error,\n duration_ms: Date.now() - start,\n validations,\n context,\n ...(next && { next }),\n request: requestRecord,\n extraction,\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// Filter construction\n// ────────────────────────────────────────────────────────────\n\nfunction buildFilter(step: StoryboardStep, context: StoryboardContext, runnerVars: RunnerVariables): WebhookFilter {\n const filter: WebhookFilter = {};\n\n // Default: scope to the triggering step's URL. Authors can override via\n // an explicit `filter.operation_id` (useful for fan-in tests).\n if (step.triggered_by) {\n filter.step_id = step.triggered_by;\n const priorOpId = runnerVars.stepOperationIds.get(step.triggered_by);\n if (priorOpId) filter.operation_id = priorOpId;\n }\n\n if (step.filter) {\n // Resolve any `{{prior_step.<id>.operation_id}}` / `$context.*` tokens\n // before matching — the storyboard may author operation_id this way.\n const resolved = injectContext({ __f: { ...step.filter } }, context, runnerVars).__f as Record<string, unknown>;\n if (resolved && typeof resolved === 'object') {\n if (typeof resolved.operation_id === 'string') {\n filter.operation_id = resolved.operation_id;\n }\n if (resolved.body && typeof resolved.body === 'object' && !Array.isArray(resolved.body)) {\n filter.body = resolved.body as Record<string, unknown>;\n }\n }\n }\n return filter;\n}\n\n// ────────────────────────────────────────────────────────────\n// expect_webhook\n// ────────────────────────────────────────────────────────────\n\nasync function runExpectWebhook(\n step: StoryboardStep,\n filter: WebhookFilter,\n receiver: WebhookReceiver\n): Promise<{ validations: ValidationResult[]; passed: boolean }> {\n const timeoutMs = clampTimeoutSeconds(step.timeout_seconds, DEFAULT_TIMEOUT_SECONDS) * 1000;\n const checkIdempotency = step.expect_idempotency_key !== false;\n const capCount = step.expect_max_deliveries_per_logical_event;\n\n // When we're asserting a cap (e.g., \"at most 1 logical event on replay\"),\n // wait the full window to let any duplicate delivery arrive before judging.\n // When we just need ≥1 delivery, resolve on first match.\n let matches: CapturedWebhook[];\n if (typeof capCount === 'number') {\n matches = await receiver.wait_all(filter, timeoutMs);\n } else {\n const first = await receiver.wait(filter, timeoutMs);\n matches = first.webhook ? [first.webhook] : [];\n }\n\n if (matches.length === 0) {\n return singleFailure(\n step,\n 'no_webhook_received',\n `No webhook matching filter arrived within ${timeoutMs}ms.`,\n 'webhook delivery matching filter',\n null\n );\n }\n\n const first = matches[0]!;\n if (first.parse_error !== undefined) {\n return singleFailure(\n step,\n 'schema_violation',\n `Webhook body was not valid JSON: ${first.parse_error}`,\n 'JSON object body',\n first.raw_body.slice(0, 256)\n );\n }\n\n const schemaCheck = validateWebhookPayloadSchema(step, first);\n if (schemaCheck && !schemaCheck.passed) return { validations: [schemaCheck], passed: false };\n\n if (checkIdempotency) {\n const idempotencyCheck = assertIdempotencyKey(first);\n if (idempotencyCheck) return { validations: [idempotencyCheck], passed: false };\n }\n\n if (typeof capCount === 'number') {\n const distinctKeys = new Set(matches.map(m => extractIdempotencyKey(m)).filter((k): k is string => !!k));\n if (distinctKeys.size > capCount) {\n return singleFailure(\n step,\n 'duplicate_webhook_on_replay',\n `Observed ${distinctKeys.size} distinct logical events in the window (idempotency_keys); expected ≤ ${capCount}.`,\n `≤ ${capCount} distinct idempotency_keys`,\n Array.from(distinctKeys)\n );\n }\n }\n\n return {\n validations: [\n ...(schemaCheck ? [schemaCheck] : []),\n {\n check: 'expect_webhook',\n passed: true,\n description: `Webhook arrived at ${filter.step_id ?? 'receiver'} with valid idempotency_key.`,\n json_pointer: '/idempotency_key',\n },\n ],\n passed: true,\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// expect_webhook_retry_keys_stable\n// ────────────────────────────────────────────────────────────\n\nasync function runExpectRetryKeysStable(\n step: StoryboardStep,\n filter: WebhookFilter,\n receiver: WebhookReceiver\n): Promise<{ validations: ValidationResult[]; passed: boolean }> {\n const timeoutMs = clampTimeoutSeconds(step.timeout_seconds, DEFAULT_RETRY_REPLAY_TIMEOUT_SECONDS) * 1000;\n const minDeliveries = step.expect_min_deliveries ?? DEFAULT_MIN_DELIVERIES;\n\n const matches = await receiver.wait_all(filter, timeoutMs);\n\n if (matches.length < minDeliveries) {\n return singleFailure(\n step,\n 'insufficient_retries',\n `Observed ${matches.length} deliveries; expected at least ${minDeliveries}. ` +\n 'The sender may not be retrying on 5xx responses.',\n `≥ ${minDeliveries} deliveries`,\n matches.length\n );\n }\n\n const keys = matches.map(m => extractIdempotencyKey(m));\n const missingKey = matches.find((_, i) => !keys[i]);\n if (missingKey) {\n return singleFailure(\n step,\n 'missing_idempotency_key',\n `Delivery ${missingKey.delivery_index} carried no idempotency_key.`,\n 'every delivery carries idempotency_key',\n null\n );\n }\n\n const firstKey = keys[0]!;\n const formatValid = keys.every(k => k && WEBHOOK_IDEMPOTENCY_KEY_PATTERN.test(k));\n if (!formatValid) {\n const bad = keys.find(k => !k || !WEBHOOK_IDEMPOTENCY_KEY_PATTERN.test(k));\n return singleFailure(\n step,\n 'idempotency_key_format_changed',\n `One or more deliveries carried a malformed idempotency_key. Required pattern: ${WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source}`,\n WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source,\n bad ?? null\n );\n }\n\n const rotated = keys.some(k => k !== firstKey);\n if (rotated) {\n return singleFailure(\n step,\n 'idempotency_key_rotated',\n \"idempotency_key rotated across retries. Senders MUST reuse the first delivery's key for every retry of the same logical event.\",\n 'byte-identical idempotency_key on every delivery',\n keys\n );\n }\n\n return {\n validations: [\n {\n check: 'expect_webhook_retry_keys_stable',\n passed: true,\n description: `${matches.length} deliveries observed; all carried byte-identical idempotency_key.`,\n json_pointer: '/idempotency_key',\n },\n ],\n passed: true,\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// expect_webhook_signature_valid\n// ────────────────────────────────────────────────────────────\n\nasync function runExpectSignatureValid(\n step: StoryboardStep,\n filter: WebhookFilter,\n receiver: WebhookReceiver,\n options: StoryboardRunOptions,\n runnerVars: RunnerVariables\n): Promise<{ validations: ValidationResult[]; passed: boolean; skipped?: boolean; skipReason?: string }> {\n const config = options.webhook_signing;\n if (!config) {\n return {\n validations: [],\n passed: true,\n skipped: true,\n skipReason:\n '`expect_webhook_signature_valid` requires `webhook_signing` on runStoryboard options ' +\n '(pass a JwksResolver; replay + revocation stores default to in-memory).',\n };\n }\n\n const timeoutMs = clampTimeoutSeconds(step.timeout_seconds, DEFAULT_TIMEOUT_SECONDS) * 1000;\n const wait = await receiver.wait(filter, timeoutMs);\n if (wait.timed_out || !wait.webhook) {\n return singleFailure(\n step,\n 'no_webhook_received',\n `No webhook matching filter arrived within ${timeoutMs}ms.`,\n 'signed webhook delivery matching filter',\n null\n );\n }\n\n const webhook = wait.webhook;\n void runnerVars;\n const requestLike = toRequestLike(webhook, receiver);\n const requiredTag = step.require_tag ?? config.required_tag ?? DEFAULT_WEBHOOK_SIGNING_TAG;\n\n try {\n await verifyWebhookSignature(requestLike, {\n jwks: config.jwks,\n replayStore: getSharedReplayStore(runnerVars, config.replayStore),\n revocationStore: getSharedRevocationStore(runnerVars, config.revocationStore),\n requiredTag,\n });\n } catch (err) {\n if (err instanceof WebhookSignatureError) {\n return singleFailure(\n step,\n mapSignatureErrorCode(err.code),\n err.message,\n `checklist step ${err.failedStep} passes`,\n { code: err.code, step: err.failedStep }\n );\n }\n return singleFailure(\n step,\n 'signature_invalid',\n err instanceof Error ? err.message : String(err),\n '9421 webhook signature verification',\n null\n );\n }\n\n return {\n validations: [\n {\n check: 'expect_webhook_signature_valid',\n passed: true,\n description: `9421 webhook signature verified under ${requiredTag}.`,\n },\n ],\n passed: true,\n };\n}\n\n/**\n * Translate a captured webhook into the `RequestLike` shape the shared\n * canonicalizer expects. The receiver's `base_url` plus `step_id` /\n * `operation_id` reconstruct the `@target-uri` the publisher signed.\n */\nfunction toRequestLike(webhook: CapturedWebhook, receiver: WebhookReceiver): RequestLike {\n const url = `${receiver.base_url}/step/${webhook.step_id}/${webhook.operation_id}`;\n return {\n method: webhook.method,\n url,\n headers: webhook.headers,\n body: webhook.raw_body,\n };\n}\n\n/**\n * Map the verifier's `webhook_signature_*` error codes to the storyboard-side\n * `signature_*` codes defined by adcontextprotocol/adcp#2431. Exhaustive so\n * that adding a new verifier code without a mapping is a TS compile error\n * rather than a silent collapse onto `signature_invalid` — distinct\n * remediation paths deserve distinct codes (a revoked key needs rotation,\n * a malformed window is a signer bug, etc.).\n */\nfunction mapSignatureErrorCode(\n code: import('../../signing/errors').WebhookSignatureErrorCode\n): WebhookAssertionErrorCode {\n switch (code) {\n case 'webhook_signature_header_malformed':\n return 'signature_header_malformed';\n case 'webhook_signature_params_incomplete':\n return 'signature_params_incomplete';\n case 'webhook_signature_tag_invalid':\n return 'signature_tag_invalid';\n case 'webhook_signature_alg_not_allowed':\n return 'signature_alg_not_allowed';\n case 'webhook_signature_window_invalid':\n return 'signature_window_invalid';\n case 'webhook_signature_components_incomplete':\n return 'signature_components_incomplete';\n case 'webhook_signature_key_unknown':\n return 'signature_key_unknown';\n case 'webhook_signature_key_purpose_invalid':\n return 'signature_key_purpose_invalid';\n case 'webhook_mode_mismatch':\n return 'signature_mode_mismatch';\n case 'webhook_target_uri_malformed':\n return 'signature_target_uri_malformed';\n case 'webhook_signature_key_revoked':\n return 'signature_key_revoked';\n case 'webhook_signature_revocation_stale':\n return 'signature_revocation_stale';\n case 'webhook_signature_rate_abuse':\n return 'signature_rate_abuse';\n case 'webhook_signature_digest_mismatch':\n return 'signature_digest_mismatch';\n case 'webhook_signature_replayed':\n return 'signature_replayed';\n case 'webhook_signature_invalid':\n return 'signature_invalid';\n default: {\n // Exhaustiveness: any unhandled code is a compile error.\n const _exhaustive: never = code;\n void _exhaustive;\n return 'signature_invalid';\n }\n }\n}\n\n// ────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────\n\nfunction validateWebhookPayloadSchema(step: StoryboardStep, webhook: CapturedWebhook): ValidationResult | undefined {\n const schemaRef = step.webhook_payload_schema_ref ?? step.response_schema_webhook_ref;\n if (!schemaRef) return undefined;\n\n const { schema_id, schema_url } = resolveWebhookSchemaIdentity(schemaRef);\n const validator = getSchemaValidatorByRef(schemaRef);\n if (!validator) {\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook payload schema validation failed.',\n error: `No webhook payload schema found for ${schemaRef}.`,\n json_pointer: null,\n expected: schema_id,\n actual: { code: 'schema_violation', reason: 'schema_not_found', schema_ref: schemaRef },\n schema_id,\n schema_url,\n };\n }\n\n const body = webhook.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook payload schema validation failed.',\n error: 'Webhook body was not a JSON object.',\n json_pointer: null,\n expected: schema_id,\n actual: { code: 'schema_violation', reason: 'non_object_body', body },\n schema_id,\n schema_url,\n };\n }\n\n const passed = validator(body);\n if (passed) {\n return {\n check: 'expect_webhook',\n passed: true,\n description: `Webhook payload validates against ${schemaRef}.`,\n json_pointer: null,\n expected: schema_id,\n actual: 'schema_valid',\n schema_id,\n schema_url,\n };\n }\n\n const issues = (validator.errors ?? []).map(ajvErrorToSchemaError);\n const firstIssue = issues[0];\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook payload schema validation failed.',\n error: firstIssue?.message ?? `Webhook body failed ${schemaRef}.`,\n json_pointer: firstIssue?.instance_path ?? null,\n expected: schema_id,\n actual: { code: 'schema_violation', issues },\n schema_id,\n schema_url,\n };\n}\n\nfunction resolveWebhookSchemaIdentity(schemaRef: string): { schema_id: string; schema_url: string } {\n const trimmed = schemaRef.replace(/^\\/+/, '');\n const schemaId = `/schemas/${ADCP_VERSION}/${trimmed}`;\n return { schema_id: schemaId, schema_url: `${SCHEMA_URL_BASE}${schemaId}` };\n}\n\nfunction ajvErrorToSchemaError(error: {\n instancePath?: string;\n schemaPath?: string;\n keyword?: string;\n message?: string;\n}): SchemaValidationError {\n return {\n instance_path: error.instancePath || '/',\n schema_path: error.schemaPath || '#',\n keyword: error.keyword || 'schema',\n message: error.message || 'Webhook payload failed schema validation.',\n };\n}\n\nfunction extractIdempotencyKey(webhook: CapturedWebhook): string | undefined {\n const body = webhook.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) return undefined;\n const key = (body as Record<string, unknown>).idempotency_key;\n return typeof key === 'string' ? key : undefined;\n}\n\nfunction assertIdempotencyKey(webhook: CapturedWebhook): ValidationResult | undefined {\n const body = webhook.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return failure('schema_violation', 'Webhook body was not a JSON object.', 'JSON object body', null);\n }\n const key = (body as Record<string, unknown>).idempotency_key;\n if (typeof key !== 'string' || key.length === 0) {\n return failure(\n 'missing_idempotency_key',\n 'Webhook body is missing a non-empty `idempotency_key` — required by AdCP 3.0.',\n 'non-empty string',\n key ?? null\n );\n }\n if (!WEBHOOK_IDEMPOTENCY_KEY_PATTERN.test(key)) {\n return failure(\n 'invalid_idempotency_key_format',\n `idempotency_key \"${key}\" does not match required pattern ${WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source}.`,\n WEBHOOK_IDEMPOTENCY_KEY_PATTERN.source,\n key\n );\n }\n return undefined;\n}\n\nfunction failure(\n code: WebhookAssertionErrorCode,\n message: string,\n expected: unknown,\n actual: unknown\n): ValidationResult {\n return {\n check: 'expect_webhook',\n passed: false,\n description: 'Webhook conformance assertion failed.',\n error: message,\n json_pointer: '/idempotency_key',\n expected,\n actual: { code, actual },\n };\n}\n\nfunction singleFailure(\n step: StoryboardStep,\n code: WebhookAssertionErrorCode,\n message: string,\n expected: unknown,\n actual: unknown\n): { validations: ValidationResult[]; passed: boolean } {\n return {\n validations: [\n {\n check: step.task,\n passed: false,\n description: step.expected ?? step.title,\n error: message,\n json_pointer: '/idempotency_key',\n expected,\n actual: { code, actual },\n },\n ],\n passed: false,\n };\n}\n\nfunction skippedResult(\n step: StoryboardStep,\n phaseId: string,\n context: StoryboardContext,\n start: number,\n opts: {\n skip_reason: 'unsatisfied_contract' | 'prerequisite_failed';\n detail: string;\n extraction: RunnerExtractionRecord;\n request: RunnerRequestRecord;\n next?: StoryboardStepPreview;\n }\n): StoryboardStepResult {\n return {\n step_id: step.id,\n phase_id: phaseId,\n title: step.title,\n task: step.task,\n passed: true,\n skipped: true,\n skip_reason: opts.skip_reason,\n skip: { reason: opts.skip_reason, detail: opts.detail },\n duration_ms: Date.now() - start,\n validations: [],\n context,\n error: opts.detail,\n ...(opts.next && { next: opts.next }),\n request: opts.request,\n extraction: opts.extraction,\n };\n}\n"],"mappings":"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","names":["first"]}
|
|
@@ -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 { createServer } from "node:http";
|
|
8
2
|
import { randomUUID } from "node:crypto";
|
|
9
3
|
const MAX_BODY_BYTES = 1048576;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/storyboard/webhook-receiver.ts"],"sourcesContent":["/**\n * Ephemeral webhook receiver for storyboard runs.\n *\n * The runner binds an HTTP listener, mints per-step URLs under\n * `/step/<step_id>/<operation_id>`, and captures inbound POSTs so\n * `expect_webhook*` pseudo-steps can observe outbound webhook behavior\n * (idempotency_key presence, retry-key stability, 9421 signature validity).\n *\n * Matches the `webhook_receiver_runner` test-kit contract in\n * adcontextprotocol/adcp#2431:\n * - Per-step URL template `{{runner.webhook_base}}/step/<step_id>/<operation_id>`.\n * - Retry-replay: the receiver returns a configurable 5xx for the first N\n * deliveries on a given `(step_id, operation_id)`, then 2xx, so senders\n * are forced to retry and retry-key stability can be graded.\n * - Loopback-mock mode (HTTP on 127.0.0.1, zero external deps) by default;\n * proxy-URL mode swaps in an operator-supplied public base.\n */\n\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { AddressInfo } from 'node:net';\n\n/**\n * Size cap on any single webhook body. A non-conformant sender retrying\n * with oversize payloads shouldn't be able to exhaust the runner's memory.\n */\nconst MAX_BODY_BYTES = 1_048_576; // 1 MiB\n\n/**\n * Caps on cumulative capture state. `MAX_CAPTURED_TOTAL` bounds the total\n * number of webhooks retained for the run; `MAX_CAPTURED_PER_KEY` bounds\n * per-(step_id, operation_id) deliveries — a publisher stuck in a retry\n * loop can't exhaust memory. Once a cap is hit the receiver returns 503\n * (signalling back-pressure to the sender) and drops the capture.\n */\nconst MAX_CAPTURED_TOTAL = 1000;\nconst MAX_CAPTURED_PER_KEY = 100;\n\n/**\n * HTTP transport hardening. Defaults in Node 20 are generous (headers\n * 60s, no body timeout). A hostile / slow publisher shouldn't be able to\n * hold the runner's event loop with trickled requests or exhaust the\n * socket table.\n */\nconst HEADERS_TIMEOUT_MS = 10_000;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst KEEP_ALIVE_TIMEOUT_MS = 5_000;\nconst MAX_CONNECTIONS = 64;\nconst MAX_HEADERS_COUNT = 64;\n\n/** Default path shape under the receiver base. */\nconst STEP_PATH_RE = /^\\/step\\/([A-Za-z0-9_]+)\\/([A-Za-z0-9_-]+)\\/?$/;\n\n/**\n * Header names whose values are redacted before a captured webhook lands\n * in a compliance report. Mirrors the `SECRET_KEY_PATTERN` in runner.ts\n * so both paths honor the contract's \"Secrets SHOULD be redacted\" rule.\n */\nconst SECRET_HEADER_PATTERN =\n /^(authorization|credentials?|token|api[_-]?key|x-api[_-]?key|x-auth[_-]?token|password|secret|client[_-]secret|refresh[_-]token|access[_-]token|bearer|session[_-]token|cookie|set[_-]cookie)$/i;\n\n/**\n * Strip user-info, scheme-policed, and CRLF/NUL-rejected proxy URLs so a\n * misconfigured operator (or a compromised storyboard loader) can't\n * advertise an `http://169.254.169.254`, `file://…`, or header-splitting\n * URL that would then be embedded in outbound `push_notification_config.url`.\n */\nfunction validateProxyUrl(raw: string): string {\n if (/[\\r\\n\\x00]/.test(raw)) {\n throw new Error('webhook_receiver.public_url must not contain CR/LF/NUL');\n }\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`webhook_receiver.public_url is not a valid URL: ${raw}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`webhook_receiver.public_url must be http(s); got ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password) {\n throw new Error('webhook_receiver.public_url must not include userinfo');\n }\n return raw.replace(/\\/$/, '');\n}\n\nexport interface CapturedWebhook {\n /** Runner-assigned id; stable across filter matches. */\n id: string;\n /** Step id extracted from the URL path. */\n step_id: string;\n /** Operation id extracted from the URL path. */\n operation_id: string;\n /** Delivery sequence number for this (step_id, operation_id) — 1-based. */\n delivery_index: number;\n /** Wall-clock millis at receipt. */\n received_at: number;\n method: string;\n /** Path + query. */\n path: string;\n /** Lowercased header names; duplicate values joined with \", \". */\n headers: Record<string, string>;\n /** Raw request body — needed for RFC 9421 content-digest recompute. */\n raw_body: string;\n /** Parsed JSON body; undefined on parse failure. */\n body?: unknown;\n /** Parse error message when raw_body was non-empty but not valid JSON. */\n parse_error?: string;\n /** HTTP status the receiver returned (reflects retry-replay policy). */\n response_status: number;\n}\n\n/** Match predicate applied when waiting for a webhook. */\nexport interface WebhookFilter {\n /** Restrict matches to this step id's URL. */\n step_id?: string;\n /** Restrict matches to this operation id. */\n operation_id?: string;\n /** Dotted-path → expected-value pairs compared against the parsed body. */\n body?: Record<string, unknown>;\n}\n\nexport type WebhookWaitResult =\n | { webhook: CapturedWebhook; timed_out?: false }\n | { timed_out: true; webhook?: undefined };\n\nexport interface RetryReplayPolicy {\n /** How many deliveries to reject before accepting. */\n count: number;\n /** HTTP status to return on rejection. */\n http_status: number;\n}\n\nexport interface WebhookReceiver {\n /** Public base URL (`http://host:port` for loopback, operator URL for proxy). */\n readonly base_url: string;\n /** Whether the receiver is in loopback-mock or proxy-url mode. */\n readonly mode: 'loopback_mock' | 'proxy_url';\n /** All webhooks captured so far, in arrival order. */\n all(): CapturedWebhook[];\n /** Webhooks matching a filter, in arrival order. */\n matching(filter: WebhookFilter): CapturedWebhook[];\n /**\n * Configure the receiver to return `http_status` for the first `count`\n * deliveries at `(step_id, operation_id)` and 2xx afterwards. Idempotent:\n * setting a new policy replaces any existing one for the same key.\n */\n set_retry_replay(key: { step_id: string; operation_id: string }, policy: RetryReplayPolicy): void;\n /** Resolve with the first match within `timeout_ms`, or `timed_out: true`. */\n wait(filter: WebhookFilter, timeout_ms: number): Promise<WebhookWaitResult>;\n /**\n * Resolve with every match observed between call time and deadline. Used\n * by `expect_webhook_retry_keys_stable` which needs the full sequence of\n * deliveries for the retry-replay assertion.\n */\n wait_all(filter: WebhookFilter, timeout_ms: number): Promise<CapturedWebhook[]>;\n /** Stop the listener. Idempotent. */\n close(): Promise<void>;\n}\n\nexport interface CreateWebhookReceiverOptions {\n mode?: 'loopback_mock' | 'proxy_url';\n /** Bind host for the local listener. Defaults to `127.0.0.1`. */\n host?: string;\n /** Bind port. `0` (default) lets the kernel assign one. */\n port?: number;\n /** Public URL to advertise when `mode: 'proxy_url'`. */\n public_url?: string;\n}\n\ninterface RetryKey {\n step_id: string;\n operation_id: string;\n}\n\nfunction retryKeyString(k: RetryKey): string {\n return `${k.step_id}::${k.operation_id}`;\n}\n\nexport async function createWebhookReceiver(options: CreateWebhookReceiverOptions = {}): Promise<WebhookReceiver> {\n const mode = options.mode ?? 'loopback_mock';\n if (mode === 'proxy_url' && !options.public_url) {\n throw new Error('webhook_receiver.mode=proxy_url requires `public_url`');\n }\n\n const host = options.host ?? '127.0.0.1';\n // A loopback_mock receiver bound on 0.0.0.0 is publicly reachable from any\n // interface — which would turn a CI runner into an open POST endpoint.\n // Operators who genuinely want public exposure use mode=proxy_url.\n if (mode === 'loopback_mock' && (host === '0.0.0.0' || host === '::')) {\n throw new Error(\n `webhook_receiver host ${host} is not permitted in loopback_mock mode. ` +\n 'Use mode=proxy_url with an explicit public_url for publicly-reachable runs.'\n );\n }\n const port = options.port ?? 0;\n const proxyBase = mode === 'proxy_url' ? validateProxyUrl(options.public_url!) : undefined;\n\n const captured: CapturedWebhook[] = [];\n const waiters: Array<{\n filter: WebhookFilter;\n resolve: (result: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }> = [];\n const retryPolicies = new Map<string, { policy: RetryReplayPolicy; delivered: number }>();\n const deliveryCounts = new Map<string, number>();\n\n const server = createServer((req, res) =>\n handleRequest(req, res, { captured, waiters, retryPolicies, deliveryCounts })\n );\n // Transport-level hardening — trim Node's generous defaults so a slow /\n // hostile publisher can't wedge the runner.\n server.headersTimeout = HEADERS_TIMEOUT_MS;\n server.requestTimeout = REQUEST_TIMEOUT_MS;\n server.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;\n server.maxConnections = MAX_CONNECTIONS;\n server.maxHeadersCount = MAX_HEADERS_COUNT;\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => reject(err);\n server.once('error', onError);\n server.listen(port, host, () => {\n server.off('error', onError);\n resolve();\n });\n });\n\n const bound = server.address() as AddressInfo;\n const base_url = proxyBase ?? `http://${formatHost(bound.address)}:${bound.port}`;\n\n return {\n base_url,\n mode,\n all: () => captured.slice(),\n matching: filter => captured.filter(w => matchesFilter(w, filter)),\n set_retry_replay: (key, policy) => {\n retryPolicies.set(retryKeyString(key), { policy, delivered: 0 });\n },\n wait: (filter, timeout_ms) => wait(filter, timeout_ms, captured, waiters),\n wait_all: (filter, timeout_ms) => waitAll(filter, timeout_ms, captured),\n close: () => closeServer(server, waiters),\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// Request handling\n// ────────────────────────────────────────────────────────────\n\ninterface HandlerState {\n captured: CapturedWebhook[];\n waiters: Array<{\n filter: WebhookFilter;\n resolve: (r: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }>;\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>;\n deliveryCounts: Map<string, number>;\n}\n\nfunction handleRequest(req: IncomingMessage, res: ServerResponse, state: HandlerState): void {\n if (req.method !== 'POST') {\n res.statusCode = 405;\n res.end();\n return;\n }\n\n const pathParts = parseStepPath(req.url ?? '');\n if (!pathParts) {\n res.statusCode = 404;\n res.end();\n return;\n }\n const { step_id, operation_id } = pathParts;\n\n let size = 0;\n const chunks: Buffer[] = [];\n let tooLarge = false;\n\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n tooLarge = true;\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n if (tooLarge) {\n if (!res.headersSent) {\n res.statusCode = 413;\n res.end();\n }\n return;\n }\n const raw = Buffer.concat(chunks).toString('utf8');\n const headers = normalizeHeaders(req.headers);\n\n const key = retryKeyString({ step_id, operation_id });\n const deliveryIndex = (state.deliveryCounts.get(key) ?? 0) + 1;\n\n // Back-pressure on capture exhaustion — signal 503 so a conformant\n // sender retries later, drop the capture so the runner's memory\n // footprint stays bounded. Drops a delivery quietly is worse than\n // surfacing a clearly observable refusal.\n const perKey = countPerKey(state.captured, step_id, operation_id);\n if (state.captured.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {\n res.statusCode = 503;\n res.setHeader('retry-after', '1');\n res.end();\n return;\n }\n\n state.deliveryCounts.set(key, deliveryIndex);\n const status = nextResponseStatus(state.retryPolicies, key);\n\n const webhook: CapturedWebhook = {\n id: randomUUID(),\n step_id,\n operation_id,\n delivery_index: deliveryIndex,\n received_at: Date.now(),\n method: req.method ?? 'POST',\n path: req.url ?? '/',\n headers: redactHeaders(headers),\n raw_body: raw,\n ...parseBody(raw, headers['content-type']),\n response_status: status,\n };\n state.captured.push(webhook);\n\n // Deliver to any matching waiters. First-match-wins per waiter; later\n // arrivals may still deliver to other waiters in the queue.\n for (let i = state.waiters.length - 1; i >= 0; i--) {\n const waiter = state.waiters[i]!;\n if (matchesFilter(webhook, waiter.filter)) {\n clearTimeout(waiter.timer);\n state.waiters.splice(i, 1);\n waiter.resolve({ webhook });\n }\n }\n\n res.statusCode = status;\n res.end();\n });\n\n req.on('error', () => {\n if (!res.headersSent) {\n res.statusCode = 400;\n res.end();\n }\n });\n}\n\nfunction parseStepPath(reqUrl: string): { step_id: string; operation_id: string } | undefined {\n const q = reqUrl.indexOf('?');\n const pathname = q === -1 ? reqUrl : reqUrl.slice(0, q);\n const match = STEP_PATH_RE.exec(pathname);\n if (!match) return undefined;\n return { step_id: match[1]!, operation_id: match[2]! };\n}\n\nfunction normalizeHeaders(raw: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value;\n }\n return out;\n}\n\nfunction redactHeaders(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k] = SECRET_HEADER_PATTERN.test(k) ? '[redacted]' : v;\n }\n return out;\n}\n\nfunction countPerKey(captured: ReadonlyArray<CapturedWebhook>, step_id: string, operation_id: string): number {\n let n = 0;\n for (const w of captured) if (w.step_id === step_id && w.operation_id === operation_id) n++;\n return n;\n}\n\nfunction parseBody(raw: string, contentType: string | undefined): Pick<CapturedWebhook, 'body' | 'parse_error'> {\n if (raw.length === 0) return {};\n const looksJson = !contentType || /json/i.test(contentType);\n if (!looksJson) return {};\n try {\n return { body: JSON.parse(raw) };\n } catch (err) {\n return { parse_error: err instanceof Error ? err.message : String(err) };\n }\n}\n\n// ────────────────────────────────────────────────────────────\n// Filter matching\n// ────────────────────────────────────────────────────────────\n\nfunction matchesFilter(webhook: CapturedWebhook, filter: WebhookFilter): boolean {\n if (filter.step_id && webhook.step_id !== filter.step_id) return false;\n if (filter.operation_id && webhook.operation_id !== filter.operation_id) return false;\n if (filter.body && Object.keys(filter.body).length > 0) {\n if (webhook.body === undefined) return false;\n for (const [path, expected] of Object.entries(filter.body)) {\n const actual = resolveDottedPath(webhook.body, path);\n if (!deepEqual(actual, expected)) return false;\n }\n }\n return true;\n}\n\nfunction resolveDottedPath(obj: unknown, path: string): unknown {\n if (obj === null || obj === undefined) return undefined;\n const segments = path.split('.');\n let cursor: unknown = obj;\n for (const seg of segments) {\n if (cursor === null || cursor === undefined) return undefined;\n if (typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n return cursor;\n}\n\n/**\n * Structural deep-equal. Stringify-based comparison is key-order-sensitive\n * (`{a:1,b:2}` !== `{b:2,a:1}`); for filter bodies that's a false negative\n * when a publisher's JSON serializer emits fields in a different order\n * than the storyboard author. Recurse explicitly instead.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n const keysA = Object.keys(a as Record<string, unknown>);\n const keysB = Object.keys(b as Record<string, unknown>);\n if (keysA.length !== keysB.length) return false;\n for (const k of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n// ────────────────────────────────────────────────────────────\n// Retry-replay policy\n// ────────────────────────────────────────────────────────────\n\nfunction nextResponseStatus(\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>,\n key: string\n): number {\n const entry = retryPolicies.get(key);\n if (!entry) return 204;\n if (entry.delivered < entry.policy.count) {\n entry.delivered += 1;\n return entry.policy.http_status;\n }\n return 204;\n}\n\n// ────────────────────────────────────────────────────────────\n// wait / wait_all\n// ────────────────────────────────────────────────────────────\n\nfunction wait(\n filter: WebhookFilter,\n timeout_ms: number,\n captured: CapturedWebhook[],\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>\n): Promise<WebhookWaitResult> {\n const already = captured.find(w => matchesFilter(w, filter));\n if (already) return Promise.resolve({ webhook: already });\n return new Promise<WebhookWaitResult>(resolve => {\n const timer = setTimeout(\n () => {\n const idx = waiters.findIndex(w => w.timer === timer);\n if (idx >= 0) waiters.splice(idx, 1);\n resolve({ timed_out: true });\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n waiters.push({ filter, resolve, timer });\n });\n}\n\n/**\n * Poll until the deadline, then return every capture matching `filter`.\n * Used when the assertion needs the whole delivery sequence (retry-stability).\n * Waits a minimum of `timeout_ms` even if matches arrive earlier — callers\n * that need \"resolve on first match\" should use `wait` instead.\n */\nfunction waitAll(filter: WebhookFilter, timeout_ms: number, captured: CapturedWebhook[]): Promise<CapturedWebhook[]> {\n return new Promise<CapturedWebhook[]>(resolve => {\n const timer = setTimeout(\n () => {\n resolve(captured.filter(w => matchesFilter(w, filter)));\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n });\n}\n\n// ────────────────────────────────────────────────────────────\n// close\n// ────────────────────────────────────────────────────────────\n\nfunction closeServer(\n server: Server,\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>\n): Promise<void> {\n while (waiters.length > 0) {\n const w = waiters.pop()!;\n clearTimeout(w.timer);\n w.resolve({ timed_out: true });\n }\n // Force-close keep-alive sockets so shutdown doesn't hang up to\n // keepAliveTimeout waiting for idle connections to drain.\n server.closeAllConnections?.();\n return new Promise<void>((resolve, reject) => {\n server.close(err => {\n if (err && (err as NodeJS.ErrnoException).code !== 'ERR_SERVER_NOT_RUNNING') reject(err);\n else resolve();\n });\n });\n}\n\nfunction formatHost(address: string): string {\n return address.includes(':') ? `[${address}]` : address;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/storyboard/webhook-receiver.ts"],"sourcesContent":["/**\n * Ephemeral webhook receiver for storyboard runs.\n *\n * The runner binds an HTTP listener, mints per-step URLs under\n * `/step/<step_id>/<operation_id>`, and captures inbound POSTs so\n * `expect_webhook*` pseudo-steps can observe outbound webhook behavior\n * (idempotency_key presence, retry-key stability, 9421 signature validity).\n *\n * Matches the `webhook_receiver_runner` test-kit contract in\n * adcontextprotocol/adcp#2431:\n * - Per-step URL template `{{runner.webhook_base}}/step/<step_id>/<operation_id>`.\n * - Retry-replay: the receiver returns a configurable 5xx for the first N\n * deliveries on a given `(step_id, operation_id)`, then 2xx, so senders\n * are forced to retry and retry-key stability can be graded.\n * - Loopback-mock mode (HTTP on 127.0.0.1, zero external deps) by default;\n * proxy-URL mode swaps in an operator-supplied public base.\n */\n\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { AddressInfo } from 'node:net';\n\n/**\n * Size cap on any single webhook body. A non-conformant sender retrying\n * with oversize payloads shouldn't be able to exhaust the runner's memory.\n */\nconst MAX_BODY_BYTES = 1_048_576; // 1 MiB\n\n/**\n * Caps on cumulative capture state. `MAX_CAPTURED_TOTAL` bounds the total\n * number of webhooks retained for the run; `MAX_CAPTURED_PER_KEY` bounds\n * per-(step_id, operation_id) deliveries — a publisher stuck in a retry\n * loop can't exhaust memory. Once a cap is hit the receiver returns 503\n * (signalling back-pressure to the sender) and drops the capture.\n */\nconst MAX_CAPTURED_TOTAL = 1000;\nconst MAX_CAPTURED_PER_KEY = 100;\n\n/**\n * HTTP transport hardening. Defaults in Node 20 are generous (headers\n * 60s, no body timeout). A hostile / slow publisher shouldn't be able to\n * hold the runner's event loop with trickled requests or exhaust the\n * socket table.\n */\nconst HEADERS_TIMEOUT_MS = 10_000;\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst KEEP_ALIVE_TIMEOUT_MS = 5_000;\nconst MAX_CONNECTIONS = 64;\nconst MAX_HEADERS_COUNT = 64;\n\n/** Default path shape under the receiver base. */\nconst STEP_PATH_RE = /^\\/step\\/([A-Za-z0-9_]+)\\/([A-Za-z0-9_-]+)\\/?$/;\n\n/**\n * Header names whose values are redacted before a captured webhook lands\n * in a compliance report. Mirrors the `SECRET_KEY_PATTERN` in runner.ts\n * so both paths honor the contract's \"Secrets SHOULD be redacted\" rule.\n */\nconst SECRET_HEADER_PATTERN =\n /^(authorization|credentials?|token|api[_-]?key|x-api[_-]?key|x-auth[_-]?token|password|secret|client[_-]secret|refresh[_-]token|access[_-]token|bearer|session[_-]token|cookie|set[_-]cookie)$/i;\n\n/**\n * Strip user-info, scheme-policed, and CRLF/NUL-rejected proxy URLs so a\n * misconfigured operator (or a compromised storyboard loader) can't\n * advertise an `http://169.254.169.254`, `file://…`, or header-splitting\n * URL that would then be embedded in outbound `push_notification_config.url`.\n */\nfunction validateProxyUrl(raw: string): string {\n if (/[\\r\\n\\x00]/.test(raw)) {\n throw new Error('webhook_receiver.public_url must not contain CR/LF/NUL');\n }\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error(`webhook_receiver.public_url is not a valid URL: ${raw}`);\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`webhook_receiver.public_url must be http(s); got ${parsed.protocol}`);\n }\n if (parsed.username || parsed.password) {\n throw new Error('webhook_receiver.public_url must not include userinfo');\n }\n return raw.replace(/\\/$/, '');\n}\n\nexport interface CapturedWebhook {\n /** Runner-assigned id; stable across filter matches. */\n id: string;\n /** Step id extracted from the URL path. */\n step_id: string;\n /** Operation id extracted from the URL path. */\n operation_id: string;\n /** Delivery sequence number for this (step_id, operation_id) — 1-based. */\n delivery_index: number;\n /** Wall-clock millis at receipt. */\n received_at: number;\n method: string;\n /** Path + query. */\n path: string;\n /** Lowercased header names; duplicate values joined with \", \". */\n headers: Record<string, string>;\n /** Raw request body — needed for RFC 9421 content-digest recompute. */\n raw_body: string;\n /** Parsed JSON body; undefined on parse failure. */\n body?: unknown;\n /** Parse error message when raw_body was non-empty but not valid JSON. */\n parse_error?: string;\n /** HTTP status the receiver returned (reflects retry-replay policy). */\n response_status: number;\n}\n\n/** Match predicate applied when waiting for a webhook. */\nexport interface WebhookFilter {\n /** Restrict matches to this step id's URL. */\n step_id?: string;\n /** Restrict matches to this operation id. */\n operation_id?: string;\n /** Dotted-path → expected-value pairs compared against the parsed body. */\n body?: Record<string, unknown>;\n}\n\nexport type WebhookWaitResult =\n | { webhook: CapturedWebhook; timed_out?: false }\n | { timed_out: true; webhook?: undefined };\n\nexport interface RetryReplayPolicy {\n /** How many deliveries to reject before accepting. */\n count: number;\n /** HTTP status to return on rejection. */\n http_status: number;\n}\n\nexport interface WebhookReceiver {\n /** Public base URL (`http://host:port` for loopback, operator URL for proxy). */\n readonly base_url: string;\n /** Whether the receiver is in loopback-mock or proxy-url mode. */\n readonly mode: 'loopback_mock' | 'proxy_url';\n /** All webhooks captured so far, in arrival order. */\n all(): CapturedWebhook[];\n /** Webhooks matching a filter, in arrival order. */\n matching(filter: WebhookFilter): CapturedWebhook[];\n /**\n * Configure the receiver to return `http_status` for the first `count`\n * deliveries at `(step_id, operation_id)` and 2xx afterwards. Idempotent:\n * setting a new policy replaces any existing one for the same key.\n */\n set_retry_replay(key: { step_id: string; operation_id: string }, policy: RetryReplayPolicy): void;\n /** Resolve with the first match within `timeout_ms`, or `timed_out: true`. */\n wait(filter: WebhookFilter, timeout_ms: number): Promise<WebhookWaitResult>;\n /**\n * Resolve with every match observed between call time and deadline. Used\n * by `expect_webhook_retry_keys_stable` which needs the full sequence of\n * deliveries for the retry-replay assertion.\n */\n wait_all(filter: WebhookFilter, timeout_ms: number): Promise<CapturedWebhook[]>;\n /** Stop the listener. Idempotent. */\n close(): Promise<void>;\n}\n\nexport interface CreateWebhookReceiverOptions {\n mode?: 'loopback_mock' | 'proxy_url';\n /** Bind host for the local listener. Defaults to `127.0.0.1`. */\n host?: string;\n /** Bind port. `0` (default) lets the kernel assign one. */\n port?: number;\n /** Public URL to advertise when `mode: 'proxy_url'`. */\n public_url?: string;\n}\n\ninterface RetryKey {\n step_id: string;\n operation_id: string;\n}\n\nfunction retryKeyString(k: RetryKey): string {\n return `${k.step_id}::${k.operation_id}`;\n}\n\nexport async function createWebhookReceiver(options: CreateWebhookReceiverOptions = {}): Promise<WebhookReceiver> {\n const mode = options.mode ?? 'loopback_mock';\n if (mode === 'proxy_url' && !options.public_url) {\n throw new Error('webhook_receiver.mode=proxy_url requires `public_url`');\n }\n\n const host = options.host ?? '127.0.0.1';\n // A loopback_mock receiver bound on 0.0.0.0 is publicly reachable from any\n // interface — which would turn a CI runner into an open POST endpoint.\n // Operators who genuinely want public exposure use mode=proxy_url.\n if (mode === 'loopback_mock' && (host === '0.0.0.0' || host === '::')) {\n throw new Error(\n `webhook_receiver host ${host} is not permitted in loopback_mock mode. ` +\n 'Use mode=proxy_url with an explicit public_url for publicly-reachable runs.'\n );\n }\n const port = options.port ?? 0;\n const proxyBase = mode === 'proxy_url' ? validateProxyUrl(options.public_url!) : undefined;\n\n const captured: CapturedWebhook[] = [];\n const waiters: Array<{\n filter: WebhookFilter;\n resolve: (result: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }> = [];\n const retryPolicies = new Map<string, { policy: RetryReplayPolicy; delivered: number }>();\n const deliveryCounts = new Map<string, number>();\n\n const server = createServer((req, res) =>\n handleRequest(req, res, { captured, waiters, retryPolicies, deliveryCounts })\n );\n // Transport-level hardening — trim Node's generous defaults so a slow /\n // hostile publisher can't wedge the runner.\n server.headersTimeout = HEADERS_TIMEOUT_MS;\n server.requestTimeout = REQUEST_TIMEOUT_MS;\n server.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;\n server.maxConnections = MAX_CONNECTIONS;\n server.maxHeadersCount = MAX_HEADERS_COUNT;\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => reject(err);\n server.once('error', onError);\n server.listen(port, host, () => {\n server.off('error', onError);\n resolve();\n });\n });\n\n const bound = server.address() as AddressInfo;\n const base_url = proxyBase ?? `http://${formatHost(bound.address)}:${bound.port}`;\n\n return {\n base_url,\n mode,\n all: () => captured.slice(),\n matching: filter => captured.filter(w => matchesFilter(w, filter)),\n set_retry_replay: (key, policy) => {\n retryPolicies.set(retryKeyString(key), { policy, delivered: 0 });\n },\n wait: (filter, timeout_ms) => wait(filter, timeout_ms, captured, waiters),\n wait_all: (filter, timeout_ms) => waitAll(filter, timeout_ms, captured),\n close: () => closeServer(server, waiters),\n };\n}\n\n// ────────────────────────────────────────────────────────────\n// Request handling\n// ────────────────────────────────────────────────────────────\n\ninterface HandlerState {\n captured: CapturedWebhook[];\n waiters: Array<{\n filter: WebhookFilter;\n resolve: (r: WebhookWaitResult) => void;\n timer: NodeJS.Timeout;\n }>;\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>;\n deliveryCounts: Map<string, number>;\n}\n\nfunction handleRequest(req: IncomingMessage, res: ServerResponse, state: HandlerState): void {\n if (req.method !== 'POST') {\n res.statusCode = 405;\n res.end();\n return;\n }\n\n const pathParts = parseStepPath(req.url ?? '');\n if (!pathParts) {\n res.statusCode = 404;\n res.end();\n return;\n }\n const { step_id, operation_id } = pathParts;\n\n let size = 0;\n const chunks: Buffer[] = [];\n let tooLarge = false;\n\n req.on('data', (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n tooLarge = true;\n req.destroy();\n return;\n }\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n if (tooLarge) {\n if (!res.headersSent) {\n res.statusCode = 413;\n res.end();\n }\n return;\n }\n const raw = Buffer.concat(chunks).toString('utf8');\n const headers = normalizeHeaders(req.headers);\n\n const key = retryKeyString({ step_id, operation_id });\n const deliveryIndex = (state.deliveryCounts.get(key) ?? 0) + 1;\n\n // Back-pressure on capture exhaustion — signal 503 so a conformant\n // sender retries later, drop the capture so the runner's memory\n // footprint stays bounded. Drops a delivery quietly is worse than\n // surfacing a clearly observable refusal.\n const perKey = countPerKey(state.captured, step_id, operation_id);\n if (state.captured.length >= MAX_CAPTURED_TOTAL || perKey >= MAX_CAPTURED_PER_KEY) {\n res.statusCode = 503;\n res.setHeader('retry-after', '1');\n res.end();\n return;\n }\n\n state.deliveryCounts.set(key, deliveryIndex);\n const status = nextResponseStatus(state.retryPolicies, key);\n\n const webhook: CapturedWebhook = {\n id: randomUUID(),\n step_id,\n operation_id,\n delivery_index: deliveryIndex,\n received_at: Date.now(),\n method: req.method ?? 'POST',\n path: req.url ?? '/',\n headers: redactHeaders(headers),\n raw_body: raw,\n ...parseBody(raw, headers['content-type']),\n response_status: status,\n };\n state.captured.push(webhook);\n\n // Deliver to any matching waiters. First-match-wins per waiter; later\n // arrivals may still deliver to other waiters in the queue.\n for (let i = state.waiters.length - 1; i >= 0; i--) {\n const waiter = state.waiters[i]!;\n if (matchesFilter(webhook, waiter.filter)) {\n clearTimeout(waiter.timer);\n state.waiters.splice(i, 1);\n waiter.resolve({ webhook });\n }\n }\n\n res.statusCode = status;\n res.end();\n });\n\n req.on('error', () => {\n if (!res.headersSent) {\n res.statusCode = 400;\n res.end();\n }\n });\n}\n\nfunction parseStepPath(reqUrl: string): { step_id: string; operation_id: string } | undefined {\n const q = reqUrl.indexOf('?');\n const pathname = q === -1 ? reqUrl : reqUrl.slice(0, q);\n const match = STEP_PATH_RE.exec(pathname);\n if (!match) return undefined;\n return { step_id: match[1]!, operation_id: match[2]! };\n}\n\nfunction normalizeHeaders(raw: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (value === undefined) continue;\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : value;\n }\n return out;\n}\n\nfunction redactHeaders(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers)) {\n out[k] = SECRET_HEADER_PATTERN.test(k) ? '[redacted]' : v;\n }\n return out;\n}\n\nfunction countPerKey(captured: ReadonlyArray<CapturedWebhook>, step_id: string, operation_id: string): number {\n let n = 0;\n for (const w of captured) if (w.step_id === step_id && w.operation_id === operation_id) n++;\n return n;\n}\n\nfunction parseBody(raw: string, contentType: string | undefined): Pick<CapturedWebhook, 'body' | 'parse_error'> {\n if (raw.length === 0) return {};\n const looksJson = !contentType || /json/i.test(contentType);\n if (!looksJson) return {};\n try {\n return { body: JSON.parse(raw) };\n } catch (err) {\n return { parse_error: err instanceof Error ? err.message : String(err) };\n }\n}\n\n// ────────────────────────────────────────────────────────────\n// Filter matching\n// ────────────────────────────────────────────────────────────\n\nfunction matchesFilter(webhook: CapturedWebhook, filter: WebhookFilter): boolean {\n if (filter.step_id && webhook.step_id !== filter.step_id) return false;\n if (filter.operation_id && webhook.operation_id !== filter.operation_id) return false;\n if (filter.body && Object.keys(filter.body).length > 0) {\n if (webhook.body === undefined) return false;\n for (const [path, expected] of Object.entries(filter.body)) {\n const actual = resolveDottedPath(webhook.body, path);\n if (!deepEqual(actual, expected)) return false;\n }\n }\n return true;\n}\n\nfunction resolveDottedPath(obj: unknown, path: string): unknown {\n if (obj === null || obj === undefined) return undefined;\n const segments = path.split('.');\n let cursor: unknown = obj;\n for (const seg of segments) {\n if (cursor === null || cursor === undefined) return undefined;\n if (typeof cursor !== 'object') return undefined;\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n return cursor;\n}\n\n/**\n * Structural deep-equal. Stringify-based comparison is key-order-sensitive\n * (`{a:1,b:2}` !== `{b:2,a:1}`); for filter bodies that's a false negative\n * when a publisher's JSON serializer emits fields in a different order\n * than the storyboard author. Recurse explicitly instead.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a === null || b === null) return a === b;\n if (typeof a !== 'object') return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n const keysA = Object.keys(a as Record<string, unknown>);\n const keysB = Object.keys(b as Record<string, unknown>);\n if (keysA.length !== keysB.length) return false;\n for (const k of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n// ────────────────────────────────────────────────────────────\n// Retry-replay policy\n// ────────────────────────────────────────────────────────────\n\nfunction nextResponseStatus(\n retryPolicies: Map<string, { policy: RetryReplayPolicy; delivered: number }>,\n key: string\n): number {\n const entry = retryPolicies.get(key);\n if (!entry) return 204;\n if (entry.delivered < entry.policy.count) {\n entry.delivered += 1;\n return entry.policy.http_status;\n }\n return 204;\n}\n\n// ────────────────────────────────────────────────────────────\n// wait / wait_all\n// ────────────────────────────────────────────────────────────\n\nfunction wait(\n filter: WebhookFilter,\n timeout_ms: number,\n captured: CapturedWebhook[],\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>\n): Promise<WebhookWaitResult> {\n const already = captured.find(w => matchesFilter(w, filter));\n if (already) return Promise.resolve({ webhook: already });\n return new Promise<WebhookWaitResult>(resolve => {\n const timer = setTimeout(\n () => {\n const idx = waiters.findIndex(w => w.timer === timer);\n if (idx >= 0) waiters.splice(idx, 1);\n resolve({ timed_out: true });\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n waiters.push({ filter, resolve, timer });\n });\n}\n\n/**\n * Poll until the deadline, then return every capture matching `filter`.\n * Used when the assertion needs the whole delivery sequence (retry-stability).\n * Waits a minimum of `timeout_ms` even if matches arrive earlier — callers\n * that need \"resolve on first match\" should use `wait` instead.\n */\nfunction waitAll(filter: WebhookFilter, timeout_ms: number, captured: CapturedWebhook[]): Promise<CapturedWebhook[]> {\n return new Promise<CapturedWebhook[]>(resolve => {\n const timer = setTimeout(\n () => {\n resolve(captured.filter(w => matchesFilter(w, filter)));\n },\n Math.max(0, timeout_ms)\n );\n timer.unref?.();\n });\n}\n\n// ────────────────────────────────────────────────────────────\n// close\n// ────────────────────────────────────────────────────────────\n\nfunction closeServer(\n server: Server,\n waiters: Array<{ filter: WebhookFilter; resolve: (r: WebhookWaitResult) => void; timer: NodeJS.Timeout }>\n): Promise<void> {\n while (waiters.length > 0) {\n const w = waiters.pop()!;\n clearTimeout(w.timer);\n w.resolve({ timed_out: true });\n }\n // Force-close keep-alive sockets so shutdown doesn't hang up to\n // keepAliveTimeout waiting for idle connections to drain.\n server.closeAllConnections?.();\n return new Promise<void>((resolve, reject) => {\n server.close(err => {\n if (err && (err as NodeJS.ErrnoException).code !== 'ERR_SERVER_NOT_RUNNING') reject(err);\n else resolve();\n });\n });\n}\n\nfunction formatHost(address: string): string {\n return address.includes(':') ? `[${address}]` : address;\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 { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
3
|
import {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/stubs/governance-agent-stub.ts"],"sourcesContent":["/**\n * Stub Governance Agent\n *\n * Minimal MCP server that implements check_governance and report_plan_outcome.\n * Used by comply() to verify sellers correctly call governance agents with\n * governance_context during the media buy lifecycle.\n *\n * - Always returns \"approved\" with a deterministic governance_context\n * - Records every inbound call for assertion by test scenarios\n * - Starts on an ephemeral port (HTTP or HTTPS), shuts down cleanly\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport {\n createServer as createHttpServer,\n type Server as HttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'http';\nimport { createServer as createHttpsServer, type Server as HttpsServer } from 'https';\nimport { randomUUID } from 'crypto';\nimport { execFileSync } from 'child_process';\nimport { tmpdir } from 'os';\nimport { join } from 'path';\nimport { readFileSync, unlinkSync } from 'fs';\n\nimport {\n CheckGovernanceRequestSchema,\n ReportPlanOutcomeRequestSchema,\n SyncPlansRequestSchema,\n GetPlanAuditLogsRequestSchema,\n} from '../../types/schemas.generated';\n\nexport interface StubCallRecord {\n tool: string;\n params: Record<string, unknown>;\n timestamp: string;\n}\n\n/**\n * Generate a self-signed certificate using openssl CLI.\n * Returns PEM-encoded key and cert strings.\n */\nfunction generateSelfSignedCert(): { key: string; cert: string } {\n const id = randomUUID().slice(0, 8);\n const keyPath = join(tmpdir(), `stub-key-${id}.pem`);\n const certPath = join(tmpdir(), `stub-cert-${id}.pem`);\n\n try {\n execFileSync(\n 'openssl',\n [\n 'req',\n '-x509',\n '-newkey',\n 'rsa:2048',\n '-keyout',\n keyPath,\n '-out',\n certPath,\n '-days',\n '1',\n '-nodes',\n '-subj',\n '/CN=localhost',\n '-addext',\n 'subjectAltName=IP:127.0.0.1',\n ],\n { stdio: 'ignore' }\n );\n\n const key = readFileSync(keyPath, 'utf8');\n const cert = readFileSync(certPath, 'utf8');\n return { key, cert };\n } finally {\n try {\n unlinkSync(keyPath);\n } catch {}\n try {\n unlinkSync(certPath);\n } catch {}\n }\n}\n\nexport class GovernanceAgentStub {\n private httpServer: HttpServer | HttpsServer | null = null;\n private callLog: StubCallRecord[] = [];\n private governanceContextPrefix: string;\n private expectedToken: string;\n\n constructor() {\n this.governanceContextPrefix = `stub-gc-${randomUUID().slice(0, 8)}`;\n this.expectedToken = `stub-token-${randomUUID()}`;\n }\n\n /**\n * The bearer token this stub expects. Pass this to register_governance\n * so the seller can authenticate when calling check_governance.\n */\n get authToken(): string {\n return this.expectedToken;\n }\n\n private handleRequest() {\n return async (req: IncomingMessage, res: ServerResponse) => {\n const url = req.url || '';\n if (url === '/mcp' || url === '/mcp/') {\n // Validate bearer token if present (sellers should send the token from register_governance)\n const authHeader = req.headers.authorization;\n if (authHeader && !authHeader.startsWith(`Bearer ${this.expectedToken}`)) {\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid bearer token' }));\n return;\n }\n\n const server = this.createMCPServer();\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });\n try {\n await server.connect(transport);\n await transport.handleRequest(req, res);\n } catch {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n } finally {\n await server.close();\n }\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n };\n }\n\n /**\n * Start the stub server on an ephemeral port (HTTP).\n * Returns the MCP endpoint URL.\n */\n async start(): Promise<{ url: string }> {\n this.httpServer = createHttpServer(this.handleRequest());\n return this.listen('http');\n }\n\n /**\n * Start the stub server on an ephemeral port with HTTPS (self-signed cert).\n * The seller under test needs NODE_TLS_REJECT_UNAUTHORIZED=0 or a custom CA\n * to connect. Returns the HTTPS MCP endpoint URL.\n */\n async startHttps(): Promise<{ url: string }> {\n const { key, cert } = generateSelfSignedCert();\n this.httpServer = createHttpsServer({ key, cert }, this.handleRequest());\n return this.listen('https');\n }\n\n private listen(protocol: 'http' | 'https'): Promise<{ url: string }> {\n return new Promise((resolve, reject) => {\n this.httpServer!.listen(0, '127.0.0.1', () => {\n const addr = this.httpServer!.address();\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to get server address'));\n return;\n }\n resolve({ url: `${protocol}://127.0.0.1:${addr.port}/mcp` });\n });\n this.httpServer!.on('error', reject);\n });\n }\n\n /**\n * Stop the stub server.\n */\n async stop(): Promise<void> {\n return new Promise(resolve => {\n if (this.httpServer) {\n this.httpServer.close(() => resolve());\n } else {\n resolve();\n }\n });\n }\n\n /**\n * Get the call log — every tool call received by this stub.\n */\n getCallLog(): StubCallRecord[] {\n return [...this.callLog];\n }\n\n /**\n * Get calls for a specific tool.\n */\n getCallsForTool(tool: string): StubCallRecord[] {\n return this.callLog.filter(c => c.tool === tool);\n }\n\n /**\n * Check if any call included the expected governance_context.\n */\n hasGovernanceContext(expectedContext: string): boolean {\n return this.callLog.some(c => (c.params as Record<string, unknown>).governance_context === expectedContext);\n }\n\n /**\n * Clear the call log.\n */\n clearCallLog(): void {\n this.callLog = [];\n }\n\n /**\n * Generate the governance_context string this stub issues.\n */\n generateContext(planId: string): string {\n return `${this.governanceContextPrefix}-${planId}`;\n }\n\n private recordCall(tool: string, params: Record<string, unknown>): void {\n this.callLog.push({\n tool,\n params: structuredClone(params),\n timestamp: new Date().toISOString(),\n });\n }\n\n private createMCPServer(): McpServer {\n const server = new McpServer({ name: 'Governance Agent Stub', version: '1.0.0' });\n\n // --- get_adcp_capabilities ---\n server.registerTool('get_adcp_capabilities', { inputSchema: {} }, async () => {\n this.recordCall('get_adcp_capabilities', {});\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n adcp: { major_versions: [3] },\n supported_protocols: ['governance'],\n }),\n },\n ],\n };\n });\n\n // --- sync_plans ---\n server.registerTool(\n 'sync_plans',\n { inputSchema: SyncPlansRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'] },\n (async (args: Record<string, unknown>) => {\n this.recordCall('sync_plans', args);\n const plans = (args.plans as Array<Record<string, unknown>>) || [];\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n plans: plans.map((p: Record<string, unknown>) => ({\n plan_id: p.plan_id,\n status: 'active',\n version: 1,\n categories: [{ category_id: 'budget_authority' }, { category_id: 'geo_compliance' }],\n })),\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n // --- check_governance ---\n server.registerTool(\n 'check_governance',\n { inputSchema: CheckGovernanceRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'] },\n (async (args: Record<string, unknown>) => {\n this.recordCall('check_governance', args);\n\n const planId = args.plan_id as string;\n const checkId = `chk_stub_${randomUUID().slice(0, 8)}`;\n const gc = this.generateContext(planId);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n check_id: checkId,\n verdict: 'approved',\n plan_id: planId,\n explanation: 'Stub governance agent: approved for testing.',\n governance_context: gc,\n expires_at: new Date(Date.now() + 3600_000).toISOString(),\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n // --- report_plan_outcome ---\n server.registerTool(\n 'report_plan_outcome',\n {\n inputSchema: ReportPlanOutcomeRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'],\n },\n (async (args: Record<string, unknown>) => {\n this.recordCall('report_plan_outcome', args);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n outcome_id: `out_stub_${randomUUID().slice(0, 8)}`,\n outcome_state: 'accepted',\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n // --- get_plan_audit_logs ---\n server.registerTool(\n 'get_plan_audit_logs',\n {\n inputSchema: GetPlanAuditLogsRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'],\n },\n (async (args: Record<string, unknown>) => {\n this.recordCall('get_plan_audit_logs', args);\n\n const planIds = (args.plan_ids as string[]) || [];\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n plans: planIds.map(id => ({\n plan_id: id,\n budget: { total_committed: 0, budget_remaining: 10000 },\n entries: [],\n })),\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n return server;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/stubs/governance-agent-stub.ts"],"sourcesContent":["/**\n * Stub Governance Agent\n *\n * Minimal MCP server that implements check_governance and report_plan_outcome.\n * Used by comply() to verify sellers correctly call governance agents with\n * governance_context during the media buy lifecycle.\n *\n * - Always returns \"approved\" with a deterministic governance_context\n * - Records every inbound call for assertion by test scenarios\n * - Starts on an ephemeral port (HTTP or HTTPS), shuts down cleanly\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport {\n createServer as createHttpServer,\n type Server as HttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'http';\nimport { createServer as createHttpsServer, type Server as HttpsServer } from 'https';\nimport { randomUUID } from 'crypto';\nimport { execFileSync } from 'child_process';\nimport { tmpdir } from 'os';\nimport { join } from 'path';\nimport { readFileSync, unlinkSync } from 'fs';\n\nimport {\n CheckGovernanceRequestSchema,\n ReportPlanOutcomeRequestSchema,\n SyncPlansRequestSchema,\n GetPlanAuditLogsRequestSchema,\n} from '../../types/schemas.generated';\n\nexport interface StubCallRecord {\n tool: string;\n params: Record<string, unknown>;\n timestamp: string;\n}\n\n/**\n * Generate a self-signed certificate using openssl CLI.\n * Returns PEM-encoded key and cert strings.\n */\nfunction generateSelfSignedCert(): { key: string; cert: string } {\n const id = randomUUID().slice(0, 8);\n const keyPath = join(tmpdir(), `stub-key-${id}.pem`);\n const certPath = join(tmpdir(), `stub-cert-${id}.pem`);\n\n try {\n execFileSync(\n 'openssl',\n [\n 'req',\n '-x509',\n '-newkey',\n 'rsa:2048',\n '-keyout',\n keyPath,\n '-out',\n certPath,\n '-days',\n '1',\n '-nodes',\n '-subj',\n '/CN=localhost',\n '-addext',\n 'subjectAltName=IP:127.0.0.1',\n ],\n { stdio: 'ignore' }\n );\n\n const key = readFileSync(keyPath, 'utf8');\n const cert = readFileSync(certPath, 'utf8');\n return { key, cert };\n } finally {\n try {\n unlinkSync(keyPath);\n } catch {}\n try {\n unlinkSync(certPath);\n } catch {}\n }\n}\n\nexport class GovernanceAgentStub {\n private httpServer: HttpServer | HttpsServer | null = null;\n private callLog: StubCallRecord[] = [];\n private governanceContextPrefix: string;\n private expectedToken: string;\n\n constructor() {\n this.governanceContextPrefix = `stub-gc-${randomUUID().slice(0, 8)}`;\n this.expectedToken = `stub-token-${randomUUID()}`;\n }\n\n /**\n * The bearer token this stub expects. Pass this to register_governance\n * so the seller can authenticate when calling check_governance.\n */\n get authToken(): string {\n return this.expectedToken;\n }\n\n private handleRequest() {\n return async (req: IncomingMessage, res: ServerResponse) => {\n const url = req.url || '';\n if (url === '/mcp' || url === '/mcp/') {\n // Validate bearer token if present (sellers should send the token from register_governance)\n const authHeader = req.headers.authorization;\n if (authHeader && !authHeader.startsWith(`Bearer ${this.expectedToken}`)) {\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid bearer token' }));\n return;\n }\n\n const server = this.createMCPServer();\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });\n try {\n await server.connect(transport);\n await transport.handleRequest(req, res);\n } catch {\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n } finally {\n await server.close();\n }\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n };\n }\n\n /**\n * Start the stub server on an ephemeral port (HTTP).\n * Returns the MCP endpoint URL.\n */\n async start(): Promise<{ url: string }> {\n this.httpServer = createHttpServer(this.handleRequest());\n return this.listen('http');\n }\n\n /**\n * Start the stub server on an ephemeral port with HTTPS (self-signed cert).\n * The seller under test needs NODE_TLS_REJECT_UNAUTHORIZED=0 or a custom CA\n * to connect. Returns the HTTPS MCP endpoint URL.\n */\n async startHttps(): Promise<{ url: string }> {\n const { key, cert } = generateSelfSignedCert();\n this.httpServer = createHttpsServer({ key, cert }, this.handleRequest());\n return this.listen('https');\n }\n\n private listen(protocol: 'http' | 'https'): Promise<{ url: string }> {\n return new Promise((resolve, reject) => {\n this.httpServer!.listen(0, '127.0.0.1', () => {\n const addr = this.httpServer!.address();\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to get server address'));\n return;\n }\n resolve({ url: `${protocol}://127.0.0.1:${addr.port}/mcp` });\n });\n this.httpServer!.on('error', reject);\n });\n }\n\n /**\n * Stop the stub server.\n */\n async stop(): Promise<void> {\n return new Promise(resolve => {\n if (this.httpServer) {\n this.httpServer.close(() => resolve());\n } else {\n resolve();\n }\n });\n }\n\n /**\n * Get the call log — every tool call received by this stub.\n */\n getCallLog(): StubCallRecord[] {\n return [...this.callLog];\n }\n\n /**\n * Get calls for a specific tool.\n */\n getCallsForTool(tool: string): StubCallRecord[] {\n return this.callLog.filter(c => c.tool === tool);\n }\n\n /**\n * Check if any call included the expected governance_context.\n */\n hasGovernanceContext(expectedContext: string): boolean {\n return this.callLog.some(c => (c.params as Record<string, unknown>).governance_context === expectedContext);\n }\n\n /**\n * Clear the call log.\n */\n clearCallLog(): void {\n this.callLog = [];\n }\n\n /**\n * Generate the governance_context string this stub issues.\n */\n generateContext(planId: string): string {\n return `${this.governanceContextPrefix}-${planId}`;\n }\n\n private recordCall(tool: string, params: Record<string, unknown>): void {\n this.callLog.push({\n tool,\n params: structuredClone(params),\n timestamp: new Date().toISOString(),\n });\n }\n\n private createMCPServer(): McpServer {\n const server = new McpServer({ name: 'Governance Agent Stub', version: '1.0.0' });\n\n // --- get_adcp_capabilities ---\n server.registerTool('get_adcp_capabilities', { inputSchema: {} }, async () => {\n this.recordCall('get_adcp_capabilities', {});\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n adcp: { major_versions: [3] },\n supported_protocols: ['governance'],\n }),\n },\n ],\n };\n });\n\n // --- sync_plans ---\n server.registerTool(\n 'sync_plans',\n { inputSchema: SyncPlansRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'] },\n (async (args: Record<string, unknown>) => {\n this.recordCall('sync_plans', args);\n const plans = (args.plans as Array<Record<string, unknown>>) || [];\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n plans: plans.map((p: Record<string, unknown>) => ({\n plan_id: p.plan_id,\n status: 'active',\n version: 1,\n categories: [{ category_id: 'budget_authority' }, { category_id: 'geo_compliance' }],\n })),\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n // --- check_governance ---\n server.registerTool(\n 'check_governance',\n { inputSchema: CheckGovernanceRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'] },\n (async (args: Record<string, unknown>) => {\n this.recordCall('check_governance', args);\n\n const planId = args.plan_id as string;\n const checkId = `chk_stub_${randomUUID().slice(0, 8)}`;\n const gc = this.generateContext(planId);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n check_id: checkId,\n verdict: 'approved',\n plan_id: planId,\n explanation: 'Stub governance agent: approved for testing.',\n governance_context: gc,\n expires_at: new Date(Date.now() + 3600_000).toISOString(),\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n // --- report_plan_outcome ---\n server.registerTool(\n 'report_plan_outcome',\n {\n inputSchema: ReportPlanOutcomeRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'],\n },\n (async (args: Record<string, unknown>) => {\n this.recordCall('report_plan_outcome', args);\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n outcome_id: `out_stub_${randomUUID().slice(0, 8)}`,\n outcome_state: 'accepted',\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n // --- get_plan_audit_logs ---\n server.registerTool(\n 'get_plan_audit_logs',\n {\n inputSchema: GetPlanAuditLogsRequestSchema.shape as Parameters<typeof server.registerTool>[1]['inputSchema'],\n },\n (async (args: Record<string, unknown>) => {\n this.recordCall('get_plan_audit_logs', args);\n\n const planIds = (args.plan_ids as string[]) || [];\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n plans: planIds.map(id => ({\n plan_id: id,\n budget: { total_committed: 0, budget_remaining: 10000 },\n entries: [],\n })),\n }),\n },\n ],\n };\n }) as Parameters<typeof server.registerTool>[2]\n );\n\n return server;\n }\n}\n"],"mappings":"AAYA,SAAS,iBAAiB;AAC1B,SAAS,qCAAqC;AAC9C;AAAA,EACE,gBAAgB;AAAA,OAIX;AACP,SAAS,gBAAgB,yBAAqD;AAC9E,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,cAAc,kBAAkB;AAEzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYP,SAAS,yBAAwD;AAC/D,QAAM,KAAK,WAAW,EAAE,MAAM,GAAG,CAAC;AAClC,QAAM,UAAU,KAAK,OAAO,GAAG,YAAY,EAAE,MAAM;AACnD,QAAM,WAAW,KAAK,OAAO,GAAG,aAAa,EAAE,MAAM;AAErD,MAAI;AACF;AAAA,MACE;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,EAAE,OAAO,SAAS;AAAA,IACpB;AAEA,UAAM,MAAM,aAAa,SAAS,MAAM;AACxC,UAAM,OAAO,aAAa,UAAU,MAAM;AAC1C,WAAO,EAAE,KAAK,KAAK;AAAA,EACrB,UAAE;AACA,QAAI;AACF,iBAAW,OAAO;AAAA,IACpB,QAAQ;AAAA,IAAC;AACT,QAAI;AACF,iBAAW,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAAC;AAAA,EACX;AACF;AAEO,MAAM,oBAAoB;AAAA,EACvB,aAA8C;AAAA,EAC9C,UAA4B,CAAC;AAAA,EAC7B;AAAA,EACA;AAAA,EAER,cAAc;AACZ,SAAK,0BAA0B,WAAW,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAClE,SAAK,gBAAgB,cAAc,WAAW,CAAC;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,gBAAgB;AACtB,WAAO,OAAO,KAAsB,QAAwB;AAC1D,YAAM,MAAM,IAAI,OAAO;AACvB,UAAI,QAAQ,UAAU,QAAQ,SAAS;AAErC,cAAM,aAAa,IAAI,QAAQ;AAC/B,YAAI,cAAc,CAAC,WAAW,WAAW,UAAU,KAAK,aAAa,EAAE,GAAG;AACxE,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,uBAAuB,CAAC,CAAC;AACzD;AAAA,QACF;AAEA,cAAM,SAAS,KAAK,gBAAgB;AACpC,cAAM,YAAY,IAAI,8BAA8B,EAAE,oBAAoB,OAAU,CAAC;AACrF,YAAI;AACF,gBAAM,OAAO,QAAQ,SAAS;AAC9B,gBAAM,UAAU,cAAc,KAAK,GAAG;AAAA,QACxC,QAAQ;AACN,cAAI,CAAC,IAAI,aAAa;AACpB,gBAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,gBAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,CAAC;AAAA,UAC5D;AAAA,QACF,UAAE;AACA,gBAAM,OAAO,MAAM;AAAA,QACrB;AAAA,MACF,OAAO;AACL,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAkC;AACtC,SAAK,aAAa,iBAAiB,KAAK,cAAc,CAAC;AACvD,WAAO,KAAK,OAAO,MAAM;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAuC;AAC3C,UAAM,EAAE,KAAK,KAAK,IAAI,uBAAuB;AAC7C,SAAK,aAAa,kBAAkB,EAAE,KAAK,KAAK,GAAG,KAAK,cAAc,CAAC;AACvE,WAAO,KAAK,OAAO,OAAO;AAAA,EAC5B;AAAA,EAEQ,OAAO,UAAsD;AACnE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,WAAY,OAAO,GAAG,aAAa,MAAM;AAC5C,cAAM,OAAO,KAAK,WAAY,QAAQ;AACtC,YAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,iBAAO,IAAI,MAAM,8BAA8B,CAAC;AAChD;AAAA,QACF;AACA,gBAAQ,EAAE,KAAK,GAAG,QAAQ,gBAAgB,KAAK,IAAI,OAAO,CAAC;AAAA,MAC7D,CAAC;AACD,WAAK,WAAY,GAAG,SAAS,MAAM;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,WAAO,IAAI,QAAQ,aAAW;AAC5B,UAAI,KAAK,YAAY;AACnB,aAAK,WAAW,MAAM,MAAM,QAAQ,CAAC;AAAA,MACvC,OAAO;AACL,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAA+B;AAC7B,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAgC;AAC9C,WAAO,KAAK,QAAQ,OAAO,OAAK,EAAE,SAAS,IAAI;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAqB,iBAAkC;AACrD,WAAO,KAAK,QAAQ,KAAK,OAAM,EAAE,OAAmC,uBAAuB,eAAe;AAAA,EAC5G;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,UAAU,CAAC;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,QAAwB;AACtC,WAAO,GAAG,KAAK,uBAAuB,IAAI,MAAM;AAAA,EAClD;AAAA,EAEQ,WAAW,MAAc,QAAuC;AACtE,SAAK,QAAQ,KAAK;AAAA,MAChB;AAAA,MACA,QAAQ,gBAAgB,MAAM;AAAA,MAC9B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEQ,kBAA6B;AACnC,UAAM,SAAS,IAAI,UAAU,EAAE,MAAM,yBAAyB,SAAS,QAAQ,CAAC;AAGhF,WAAO,aAAa,yBAAyB,EAAE,aAAa,CAAC,EAAE,GAAG,YAAY;AAC5E,WAAK,WAAW,yBAAyB,CAAC,CAAC;AAC3C,aAAO;AAAA,QACL,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU;AAAA,cACnB,MAAM,EAAE,gBAAgB,CAAC,CAAC,EAAE;AAAA,cAC5B,qBAAqB,CAAC,YAAY;AAAA,YACpC,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,WAAO;AAAA,MACL;AAAA,MACA,EAAE,aAAa,uBAAuB,MAAkE;AAAA,OACvG,OAAO,SAAkC;AACxC,aAAK,WAAW,cAAc,IAAI;AAClC,cAAM,QAAS,KAAK,SAA4C,CAAC;AACjE,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,OAAO,MAAM,IAAI,CAAC,OAAgC;AAAA,kBAChD,SAAS,EAAE;AAAA,kBACX,QAAQ;AAAA,kBACR,SAAS;AAAA,kBACT,YAAY,CAAC,EAAE,aAAa,mBAAmB,GAAG,EAAE,aAAa,iBAAiB,CAAC;AAAA,gBACrF,EAAE;AAAA,cACJ,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,WAAO;AAAA,MACL;AAAA,MACA,EAAE,aAAa,6BAA6B,MAAkE;AAAA,OAC7G,OAAO,SAAkC;AACxC,aAAK,WAAW,oBAAoB,IAAI;AAExC,cAAM,SAAS,KAAK;AACpB,cAAM,UAAU,YAAY,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AACpD,cAAM,KAAK,KAAK,gBAAgB,MAAM;AAEtC,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,UAAU;AAAA,gBACV,SAAS;AAAA,gBACT,SAAS;AAAA,gBACT,aAAa;AAAA,gBACb,oBAAoB;AAAA,gBACpB,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,IAAQ,EAAE,YAAY;AAAA,cAC1D,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,aAAa,+BAA+B;AAAA,MAC9C;AAAA,OACC,OAAO,SAAkC;AACxC,aAAK,WAAW,uBAAuB,IAAI;AAE3C,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,YAAY,YAAY,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,gBAChD,eAAe;AAAA,cACjB,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,aAAa,8BAA8B;AAAA,MAC7C;AAAA,OACC,OAAO,SAAkC;AACxC,aAAK,WAAW,uBAAuB,IAAI;AAE3C,cAAM,UAAW,KAAK,YAAyB,CAAC;AAChD,eAAO;AAAA,UACL,SAAS;AAAA,YACP;AAAA,cACE,MAAM;AAAA,cACN,MAAM,KAAK,UAAU;AAAA,gBACnB,OAAO,QAAQ,IAAI,SAAO;AAAA,kBACxB,SAAS;AAAA,kBACT,QAAQ,EAAE,iBAAiB,GAAG,kBAAkB,IAAM;AAAA,kBACtD,SAAS,CAAC;AAAA,gBACZ,EAAE;AAAA,cACJ,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","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 { GovernanceAgentStub } from "./governance-agent-stub.mjs";
|
|
8
2
|
export {
|
|
9
3
|
GovernanceAgentStub
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/testing/stubs/index.ts"],"sourcesContent":["export { GovernanceAgentStub } from './governance-agent-stub';\nexport type { StubCallRecord } from './governance-agent-stub';\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/testing/stubs/index.ts"],"sourcesContent":["export { GovernanceAgentStub } from './governance-agent-stub';\nexport type { StubCallRecord } from './governance-agent-stub';\n"],"mappings":"AAAA,SAAS,2BAA2B;","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 { getLogger, resolveAccount } from "./client.mjs";
|
|
8
2
|
const TOOL_NAME = "comply_test_controller";
|
|
9
3
|
const DEFAULT_SANDBOX_ACCOUNT = { brand: { domain: "test.example" }, operator: "test.example", sandbox: true };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/testing/test-controller.ts"],"sourcesContent":["/**\n * Test Controller Client — comply_test_controller integration\n *\n * Encapsulates all interaction with the comply_test_controller tool.\n * Handles detection, scenario discovery, and typed invocations.\n */\n\nimport type { TestClient } from './client';\nimport { getLogger, resolveAccount } from './client';\nimport type { AgentProfile, TaskResult, TestOptions } from './types';\nimport type { RecordedCall } from '../upstream-recorder/types';\nimport type {\n ComplyTestControllerResponse,\n ListScenariosSuccess,\n StateTransitionSuccess,\n SimulationSuccess,\n ControllerError,\n} from '../types/tools.generated';\n\n/** Scenarios the controller can support */\nexport type ControllerScenario =\n | 'force_creative_status'\n | 'force_account_status'\n | 'force_media_buy_status'\n | 'force_session_status'\n | 'force_create_media_buy_arm'\n | 'force_get_products_arm'\n | 'force_get_signals_arm'\n | 'force_task_completion'\n | 'simulate_delivery'\n | 'simulate_budget_spend'\n /**\n * Returns outbound HTTP calls the agent has made since session start\n * (or since a caller-supplied timestamp). Backs the `upstream_traffic`\n * storyboard validation — adopters who advertise this scenario opt into\n * the upstream-traffic conformance contract. Spec:\n * runner-output-contract.yaml v2.0.0, comply-test-controller-request.json.\n */\n | 'query_upstream_traffic';\n\nexport type { DigestRecordedCall, RawRecordedCall, RecordedCall } from '../upstream-recorder/types';\n\n/** What capabilities the seller's test controller exposes */\nexport interface ControllerCapabilities {\n detected: true;\n scenarios: ControllerScenario[];\n}\n\n/** No test controller available */\nexport interface NoController {\n detected: false;\n}\n\nexport type ControllerDetection = ControllerCapabilities | NoController;\n\nconst TOOL_NAME = 'comply_test_controller';\n\n/** Fallback sandbox account when no options are available (detection phase) */\nconst DEFAULT_SANDBOX_ACCOUNT = { brand: { domain: 'test.example' }, operator: 'test.example', sandbox: true };\n\n/**\n * Build the account ref for controller calls.\n * Uses the same resolution as test scenarios so entities are in the same session.\n */\nfunction buildAccount(options?: TestOptions): Record<string, unknown> {\n if (options) {\n const account = resolveAccount(options);\n // Ensure sandbox is set — controller requires it\n return { ...account, sandbox: true };\n }\n return DEFAULT_SANDBOX_ACCOUNT;\n}\n\n/**\n * Call executeTask on the test client and parse the MCP content envelope.\n * executeTask returns { success, data: { content: [{ type: 'text', text: '...' }] } }\n * where the text is the JSON-serialized tool response. This helper extracts and parses it.\n */\nasync function callController(\n client: TestClient,\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<TaskResult> {\n const withAccount = { account: buildAccount(options), ...params };\n const raw = await (\n client as unknown as { executeTask(name: string, params: Record<string, unknown>): Promise<TaskResult> }\n ).executeTask(TOOL_NAME, withAccount);\n\n // Parse the MCP content envelope to extract the JSON response.\n // Success responses come as { content: [{ type: 'text', text: '...' }] }.\n // Error responses are already parsed by the client (data has the fields directly).\n if (raw.data) {\n const data = raw.data as Record<string, unknown>;\n const content = data.content as Array<{ type: string; text?: string }> | undefined;\n if (content?.[0]?.text) {\n try {\n return { success: true, data: JSON.parse(content[0].text) };\n } catch {\n // Fall through to return raw\n }\n }\n // Error responses: data is already the parsed controller response\n if (data.error && data.success === false) {\n return { success: true, data };\n }\n }\n\n return raw;\n}\n\n/** Public wrapper for scenarios that need raw controller access (e.g., controller validation) */\nexport async function callControllerRaw(\n client: TestClient,\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<TaskResult> {\n return callController(client, params, options);\n}\n\n/** Check if the agent exposes comply_test_controller */\nexport function hasTestController(profile: AgentProfile): boolean {\n return profile.tools.includes(TOOL_NAME);\n}\n\n/**\n * Detect the test controller and discover supported scenarios.\n * Returns ControllerCapabilities if the tool is present and list_scenarios succeeds.\n */\nexport async function detectController(\n client: TestClient,\n profile: AgentProfile,\n options?: TestOptions\n): Promise<ControllerDetection> {\n if (!hasTestController(profile)) {\n return { detected: false };\n }\n\n try {\n const result = await callController(client, { scenario: 'list_scenarios' }, options);\n\n if (!result.success || !result.data) {\n getLogger().warn({ tool: TOOL_NAME }, 'comply_test_controller exists but list_scenarios returned no data');\n return { detected: false };\n }\n\n const data = result.data as Record<string, unknown>;\n if (data.success && data.scenarios) {\n // Handle both array format (spec) and object format (training agent returns scenario descriptions)\n const scenarios = Array.isArray(data.scenarios)\n ? (data.scenarios as ControllerScenario[])\n : (Object.keys(data.scenarios) as ControllerScenario[]);\n return {\n detected: true,\n scenarios,\n };\n }\n\n return { detected: false };\n } catch (error) {\n getLogger().warn(\n { tool: TOOL_NAME, error: error instanceof Error ? error.message : String(error) },\n 'comply_test_controller list_scenarios failed — treating as unavailable'\n );\n return { detected: false };\n }\n}\n\n/** Check if a specific scenario is supported */\nexport function supportsScenario(controller: ControllerDetection, scenario: ControllerScenario): boolean {\n return controller.detected && controller.scenarios.includes(scenario);\n}\n\n/**\n * Call a force_* scenario on the test controller.\n * Returns the typed response or a ControllerError on transport failure.\n */\nexport async function forceStatus(\n client: TestClient,\n scenario: 'force_creative_status' | 'force_account_status' | 'force_media_buy_status' | 'force_session_status',\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<StateTransitionSuccess | ControllerError> {\n const result = await callController(client, { scenario, params }, options);\n\n if (!result.success || !result.data) {\n return {\n success: false,\n error: 'INTERNAL_ERROR',\n error_detail: result.error || 'Controller call failed',\n } as ControllerError;\n }\n\n return result.data as StateTransitionSuccess | ControllerError;\n}\n\n/**\n * Call a simulate_* scenario on the test controller.\n */\nexport async function simulate(\n client: TestClient,\n scenario: 'simulate_delivery' | 'simulate_budget_spend',\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<SimulationSuccess | ControllerError> {\n const result = await callController(client, { scenario, params }, options);\n\n if (!result.success || !result.data) {\n return {\n success: false,\n error: 'INTERNAL_ERROR',\n error_detail: result.error || 'Controller call failed',\n } as ControllerError;\n }\n\n return result.data as SimulationSuccess | ControllerError;\n}\n\n/**\n * Per spec PRs adcontextprotocol/adcp#3816 (initial restriction) and\n * adcp#3987 (RFC 6839 §3.1 pin): `payload_must_contain` JSONPath is\n * valid only when `content_type` is `application/json` or any\n * structured-syntax-suffix `*\\/*+json` (e.g., `application/vnd.api+json`,\n * `application/scim+json`). Newline-delimited JSON formats\n * (`application/json-seq`, `application/jsonl`) take the non-JSON path —\n * they don't carry JSON-document semantics that path matching depends\n * on. All other types (form-urlencoded, multipart, plain text) likewise\n * take the non-JSON path.\n */\nexport function isJsonContentType(contentType: string | undefined): boolean {\n if (!contentType) return false;\n const base = contentType.split(';')[0]?.trim().toLowerCase() ?? '';\n return base === 'application/json' || /\\+json$/.test(base);\n}\n\nexport interface UpstreamTrafficSuccess {\n success: true;\n recorded_calls: RecordedCall[];\n total_count: number;\n truncated?: boolean;\n since_timestamp?: string;\n}\n\nexport interface UpstreamTrafficQueryParams {\n /** ISO 8601 lower bound for recorded calls. */\n since_timestamp?: string;\n /** Glob matched against each recorded call's `<METHOD> <URL>` endpoint. */\n endpoint_pattern?: string;\n /** Maximum recorded calls to return. */\n limit?: number;\n /**\n * Requested per-call response shape. `raw` returns redacted payloads;\n * `digest` returns payload digests plus optional identifier proofs.\n */\n attestation_mode?: 'raw' | 'digest';\n /**\n * Lowercase-hex SHA-256 digests of string identifier values the controller\n * should prove in digest mode. Non-string vectors cannot be represented by\n * this field and grade not_applicable when only digest attestations are\n * available.\n */\n identifier_value_digests?: string[];\n}\n\n/**\n * Call the controller's `query_upstream_traffic` scenario. Returns the\n * typed success branch on success, or a `ControllerError` on transport /\n * controller-side failure. The runner uses this to back the\n * `upstream_traffic` storyboard validation.\n */\nexport async function queryUpstreamTraffic(\n client: TestClient,\n params: UpstreamTrafficQueryParams,\n options?: TestOptions\n): Promise<UpstreamTrafficSuccess | ControllerError> {\n const result = await callController(client, { scenario: 'query_upstream_traffic', params }, options);\n if (!result.success || !result.data) {\n return {\n success: false,\n error: 'INTERNAL_ERROR',\n error_detail: result.error || 'query_upstream_traffic call failed',\n } as ControllerError;\n }\n return result.data as UpstreamTrafficSuccess | ControllerError;\n}\n\n/** Type guard: is the response a success? */\nexport function isSuccess(\n response: ComplyTestControllerResponse\n): response is ComplyTestControllerResponse & (StateTransitionSuccess | SimulationSuccess | ListScenariosSuccess) {\n return (response as { success?: boolean }).success === true;\n}\n\n/** Type guard: is the response an error? */\nexport function isControllerError(\n response: ComplyTestControllerResponse\n): response is ComplyTestControllerResponse & ControllerError {\n return (response as { success?: boolean }).success === false;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/testing/test-controller.ts"],"sourcesContent":["/**\n * Test Controller Client — comply_test_controller integration\n *\n * Encapsulates all interaction with the comply_test_controller tool.\n * Handles detection, scenario discovery, and typed invocations.\n */\n\nimport type { TestClient } from './client';\nimport { getLogger, resolveAccount } from './client';\nimport type { AgentProfile, TaskResult, TestOptions } from './types';\nimport type { RecordedCall } from '../upstream-recorder/types';\nimport type {\n ComplyTestControllerResponse,\n ListScenariosSuccess,\n StateTransitionSuccess,\n SimulationSuccess,\n ControllerError,\n} from '../types/tools.generated';\n\n/** Scenarios the controller can support */\nexport type ControllerScenario =\n | 'force_creative_status'\n | 'force_account_status'\n | 'force_media_buy_status'\n | 'force_session_status'\n | 'force_create_media_buy_arm'\n | 'force_get_products_arm'\n | 'force_get_signals_arm'\n | 'force_task_completion'\n | 'simulate_delivery'\n | 'simulate_budget_spend'\n /**\n * Returns outbound HTTP calls the agent has made since session start\n * (or since a caller-supplied timestamp). Backs the `upstream_traffic`\n * storyboard validation — adopters who advertise this scenario opt into\n * the upstream-traffic conformance contract. Spec:\n * runner-output-contract.yaml v2.0.0, comply-test-controller-request.json.\n */\n | 'query_upstream_traffic';\n\nexport type { DigestRecordedCall, RawRecordedCall, RecordedCall } from '../upstream-recorder/types';\n\n/** What capabilities the seller's test controller exposes */\nexport interface ControllerCapabilities {\n detected: true;\n scenarios: ControllerScenario[];\n}\n\n/** No test controller available */\nexport interface NoController {\n detected: false;\n}\n\nexport type ControllerDetection = ControllerCapabilities | NoController;\n\nconst TOOL_NAME = 'comply_test_controller';\n\n/** Fallback sandbox account when no options are available (detection phase) */\nconst DEFAULT_SANDBOX_ACCOUNT = { brand: { domain: 'test.example' }, operator: 'test.example', sandbox: true };\n\n/**\n * Build the account ref for controller calls.\n * Uses the same resolution as test scenarios so entities are in the same session.\n */\nfunction buildAccount(options?: TestOptions): Record<string, unknown> {\n if (options) {\n const account = resolveAccount(options);\n // Ensure sandbox is set — controller requires it\n return { ...account, sandbox: true };\n }\n return DEFAULT_SANDBOX_ACCOUNT;\n}\n\n/**\n * Call executeTask on the test client and parse the MCP content envelope.\n * executeTask returns { success, data: { content: [{ type: 'text', text: '...' }] } }\n * where the text is the JSON-serialized tool response. This helper extracts and parses it.\n */\nasync function callController(\n client: TestClient,\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<TaskResult> {\n const withAccount = { account: buildAccount(options), ...params };\n const raw = await (\n client as unknown as { executeTask(name: string, params: Record<string, unknown>): Promise<TaskResult> }\n ).executeTask(TOOL_NAME, withAccount);\n\n // Parse the MCP content envelope to extract the JSON response.\n // Success responses come as { content: [{ type: 'text', text: '...' }] }.\n // Error responses are already parsed by the client (data has the fields directly).\n if (raw.data) {\n const data = raw.data as Record<string, unknown>;\n const content = data.content as Array<{ type: string; text?: string }> | undefined;\n if (content?.[0]?.text) {\n try {\n return { success: true, data: JSON.parse(content[0].text) };\n } catch {\n // Fall through to return raw\n }\n }\n // Error responses: data is already the parsed controller response\n if (data.error && data.success === false) {\n return { success: true, data };\n }\n }\n\n return raw;\n}\n\n/** Public wrapper for scenarios that need raw controller access (e.g., controller validation) */\nexport async function callControllerRaw(\n client: TestClient,\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<TaskResult> {\n return callController(client, params, options);\n}\n\n/** Check if the agent exposes comply_test_controller */\nexport function hasTestController(profile: AgentProfile): boolean {\n return profile.tools.includes(TOOL_NAME);\n}\n\n/**\n * Detect the test controller and discover supported scenarios.\n * Returns ControllerCapabilities if the tool is present and list_scenarios succeeds.\n */\nexport async function detectController(\n client: TestClient,\n profile: AgentProfile,\n options?: TestOptions\n): Promise<ControllerDetection> {\n if (!hasTestController(profile)) {\n return { detected: false };\n }\n\n try {\n const result = await callController(client, { scenario: 'list_scenarios' }, options);\n\n if (!result.success || !result.data) {\n getLogger().warn({ tool: TOOL_NAME }, 'comply_test_controller exists but list_scenarios returned no data');\n return { detected: false };\n }\n\n const data = result.data as Record<string, unknown>;\n if (data.success && data.scenarios) {\n // Handle both array format (spec) and object format (training agent returns scenario descriptions)\n const scenarios = Array.isArray(data.scenarios)\n ? (data.scenarios as ControllerScenario[])\n : (Object.keys(data.scenarios) as ControllerScenario[]);\n return {\n detected: true,\n scenarios,\n };\n }\n\n return { detected: false };\n } catch (error) {\n getLogger().warn(\n { tool: TOOL_NAME, error: error instanceof Error ? error.message : String(error) },\n 'comply_test_controller list_scenarios failed — treating as unavailable'\n );\n return { detected: false };\n }\n}\n\n/** Check if a specific scenario is supported */\nexport function supportsScenario(controller: ControllerDetection, scenario: ControllerScenario): boolean {\n return controller.detected && controller.scenarios.includes(scenario);\n}\n\n/**\n * Call a force_* scenario on the test controller.\n * Returns the typed response or a ControllerError on transport failure.\n */\nexport async function forceStatus(\n client: TestClient,\n scenario: 'force_creative_status' | 'force_account_status' | 'force_media_buy_status' | 'force_session_status',\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<StateTransitionSuccess | ControllerError> {\n const result = await callController(client, { scenario, params }, options);\n\n if (!result.success || !result.data) {\n return {\n success: false,\n error: 'INTERNAL_ERROR',\n error_detail: result.error || 'Controller call failed',\n } as ControllerError;\n }\n\n return result.data as StateTransitionSuccess | ControllerError;\n}\n\n/**\n * Call a simulate_* scenario on the test controller.\n */\nexport async function simulate(\n client: TestClient,\n scenario: 'simulate_delivery' | 'simulate_budget_spend',\n params: Record<string, unknown>,\n options?: TestOptions\n): Promise<SimulationSuccess | ControllerError> {\n const result = await callController(client, { scenario, params }, options);\n\n if (!result.success || !result.data) {\n return {\n success: false,\n error: 'INTERNAL_ERROR',\n error_detail: result.error || 'Controller call failed',\n } as ControllerError;\n }\n\n return result.data as SimulationSuccess | ControllerError;\n}\n\n/**\n * Per spec PRs adcontextprotocol/adcp#3816 (initial restriction) and\n * adcp#3987 (RFC 6839 §3.1 pin): `payload_must_contain` JSONPath is\n * valid only when `content_type` is `application/json` or any\n * structured-syntax-suffix `*\\/*+json` (e.g., `application/vnd.api+json`,\n * `application/scim+json`). Newline-delimited JSON formats\n * (`application/json-seq`, `application/jsonl`) take the non-JSON path —\n * they don't carry JSON-document semantics that path matching depends\n * on. All other types (form-urlencoded, multipart, plain text) likewise\n * take the non-JSON path.\n */\nexport function isJsonContentType(contentType: string | undefined): boolean {\n if (!contentType) return false;\n const base = contentType.split(';')[0]?.trim().toLowerCase() ?? '';\n return base === 'application/json' || /\\+json$/.test(base);\n}\n\nexport interface UpstreamTrafficSuccess {\n success: true;\n recorded_calls: RecordedCall[];\n total_count: number;\n truncated?: boolean;\n since_timestamp?: string;\n}\n\nexport interface UpstreamTrafficQueryParams {\n /** ISO 8601 lower bound for recorded calls. */\n since_timestamp?: string;\n /** Glob matched against each recorded call's `<METHOD> <URL>` endpoint. */\n endpoint_pattern?: string;\n /** Maximum recorded calls to return. */\n limit?: number;\n /**\n * Requested per-call response shape. `raw` returns redacted payloads;\n * `digest` returns payload digests plus optional identifier proofs.\n */\n attestation_mode?: 'raw' | 'digest';\n /**\n * Lowercase-hex SHA-256 digests of string identifier values the controller\n * should prove in digest mode. Non-string vectors cannot be represented by\n * this field and grade not_applicable when only digest attestations are\n * available.\n */\n identifier_value_digests?: string[];\n}\n\n/**\n * Call the controller's `query_upstream_traffic` scenario. Returns the\n * typed success branch on success, or a `ControllerError` on transport /\n * controller-side failure. The runner uses this to back the\n * `upstream_traffic` storyboard validation.\n */\nexport async function queryUpstreamTraffic(\n client: TestClient,\n params: UpstreamTrafficQueryParams,\n options?: TestOptions\n): Promise<UpstreamTrafficSuccess | ControllerError> {\n const result = await callController(client, { scenario: 'query_upstream_traffic', params }, options);\n if (!result.success || !result.data) {\n return {\n success: false,\n error: 'INTERNAL_ERROR',\n error_detail: result.error || 'query_upstream_traffic call failed',\n } as ControllerError;\n }\n return result.data as UpstreamTrafficSuccess | ControllerError;\n}\n\n/** Type guard: is the response a success? */\nexport function isSuccess(\n response: ComplyTestControllerResponse\n): response is ComplyTestControllerResponse & (StateTransitionSuccess | SimulationSuccess | ListScenariosSuccess) {\n return (response as { success?: boolean }).success === true;\n}\n\n/** Type guard: is the response an error? */\nexport function isControllerError(\n response: ComplyTestControllerResponse\n): response is ComplyTestControllerResponse & ControllerError {\n return (response as { success?: boolean }).success === false;\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 { ADCPMultiAgentClient } from "../core/ADCPMultiAgentClient.mjs";
|
|
8
2
|
import { CreativeAgentClient, STANDARD_CREATIVE_AGENTS } from "../core/CreativeAgentClient.mjs";
|
|
9
3
|
const TEST_AGENT_TOKEN = "1v8tAhASaUYYp4odoQ1PnMpdqNaMiTrCRqYo9OJp6IQ";
|