@adcp/sdk 12.0.0 → 12.0.1
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.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.mjs +0 -6
- 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.mjs +0 -6
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/server/legacy/v5/index.mjs +0 -6
- package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
- package/dist/lib/server/mcp-modern-server.mjs +0 -6
- package/dist/lib/server/mcp-modern-server.mjs.map +1 -1
- package/dist/lib/server/media-buy-actions.mjs +0 -6
- package/dist/lib/server/media-buy-actions.mjs.map +1 -1
- package/dist/lib/server/media-buy-helpers.mjs +0 -6
- package/dist/lib/server/media-buy-helpers.mjs.map +1 -1
- package/dist/lib/server/media-buy-store.mjs +0 -6
- package/dist/lib/server/media-buy-store.mjs.map +1 -1
- package/dist/lib/server/normalize-errors.mjs +0 -6
- package/dist/lib/server/normalize-errors.mjs.map +1 -1
- package/dist/lib/server/operational-platform.mjs +0 -6
- package/dist/lib/server/operational-platform.mjs.map +1 -1
- package/dist/lib/server/pick-safe-details.mjs +0 -6
- package/dist/lib/server/pick-safe-details.mjs.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.mjs +0 -6
- package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
- package/dist/lib/server/postgres-state-store.mjs +0 -6
- package/dist/lib/server/postgres-state-store.mjs.map +1 -1
- package/dist/lib/server/postgres-task-store.mjs +0 -6
- package/dist/lib/server/postgres-task-store.mjs.map +1 -1
- package/dist/lib/server/product-defaults.mjs +0 -6
- package/dist/lib/server/product-defaults.mjs.map +1 -1
- package/dist/lib/server/redact.mjs +0 -6
- package/dist/lib/server/redact.mjs.map +1 -1
- package/dist/lib/server/responses.mjs +0 -6
- package/dist/lib/server/responses.mjs.map +1 -1
- package/dist/lib/server/serve.mjs +0 -6
- package/dist/lib/server/serve.mjs.map +1 -1
- package/dist/lib/server/socket-mode/conformance-client.mjs +0 -6
- package/dist/lib/server/socket-mode/conformance-client.mjs.map +1 -1
- package/dist/lib/server/socket-mode/index.mjs +0 -6
- package/dist/lib/server/socket-mode/index.mjs.map +1 -1
- package/dist/lib/server/socket-mode/ws-transport.mjs +0 -6
- package/dist/lib/server/socket-mode/ws-transport.mjs.map +1 -1
- package/dist/lib/server/state-machine.mjs +0 -6
- package/dist/lib/server/state-machine.mjs.map +1 -1
- package/dist/lib/server/state-store.mjs +0 -6
- package/dist/lib/server/state-store.mjs.map +1 -1
- package/dist/lib/server/structured-serialize.mjs +0 -6
- package/dist/lib/server/structured-serialize.mjs.map +1 -1
- package/dist/lib/server/targeting-helpers.mjs +0 -6
- package/dist/lib/server/targeting-helpers.mjs.map +1 -1
- package/dist/lib/server/tasks.mjs +0 -6
- package/dist/lib/server/tasks.mjs.map +1 -1
- package/dist/lib/server/test-controller-bridge.mjs +0 -6
- package/dist/lib/server/test-controller-bridge.mjs.map +1 -1
- package/dist/lib/server/test-controller.mjs +0 -6
- package/dist/lib/server/test-controller.mjs.map +1 -1
- package/dist/lib/server/upstream-helpers.mjs +0 -6
- package/dist/lib/server/upstream-helpers.mjs.map +1 -1
- package/dist/lib/server/webhook-emitter.mjs +0 -6
- package/dist/lib/server/webhook-emitter.mjs.map +1 -1
- package/dist/lib/server/wire-safe.mjs +0 -6
- package/dist/lib/server/wire-safe.mjs.map +1 -1
- package/dist/lib/server/wire-spec-fields.generated.mjs +0 -6
- package/dist/lib/server/wire-spec-fields.generated.mjs.map +1 -1
- package/dist/lib/server/wrap-envelope.mjs +0 -6
- package/dist/lib/server/wrap-envelope.mjs.map +1 -1
- package/dist/lib/signing/agent-context.mjs +0 -6
- package/dist/lib/signing/agent-context.mjs.map +1 -1
- package/dist/lib/signing/agent-fetch.mjs +0 -6
- package/dist/lib/signing/agent-fetch.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/canonicalize.mjs +0 -6
- package/dist/lib/signing/agent-resolver/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs +0 -6
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -6
- package/dist/lib/signing/agent-resolver/consistency.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/errors.mjs +0 -6
- package/dist/lib/signing/agent-resolver/errors.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/etld.mjs +0 -6
- package/dist/lib/signing/agent-resolver/etld.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +0 -6
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/index.mjs +0 -6
- package/dist/lib/signing/agent-resolver/index.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +0 -6
- package/dist/lib/signing/agent-resolver/jwks-set.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/select-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/select-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/strict-json.mjs +0 -6
- package/dist/lib/signing/agent-resolver/strict-json.mjs.map +1 -1
- package/dist/lib/signing/brand-jwks.mjs +0 -6
- package/dist/lib/signing/brand-jwks.mjs.map +1 -1
- package/dist/lib/signing/canonicalize.mjs +0 -6
- package/dist/lib/signing/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/capability-cache.mjs +0 -6
- package/dist/lib/signing/capability-cache.mjs.map +1 -1
- package/dist/lib/signing/capability-priming.mjs +0 -6
- package/dist/lib/signing/capability-priming.mjs.map +1 -1
- package/dist/lib/signing/client.mjs +0 -6
- package/dist/lib/signing/client.mjs.map +1 -1
- package/dist/lib/signing/content-digest.mjs +0 -6
- package/dist/lib/signing/content-digest.mjs.map +1 -1
- package/dist/lib/signing/crypto.mjs +0 -6
- package/dist/lib/signing/crypto.mjs.map +1 -1
- package/dist/lib/signing/ecdsa-encoding.mjs +0 -6
- package/dist/lib/signing/ecdsa-encoding.mjs.map +1 -1
- package/dist/lib/signing/errors.mjs +0 -6
- package/dist/lib/signing/errors.mjs.map +1 -1
- package/dist/lib/signing/fetch-async.mjs +0 -6
- package/dist/lib/signing/fetch-async.mjs.map +1 -1
- package/dist/lib/signing/fetch.mjs +0 -6
- package/dist/lib/signing/fetch.mjs.map +1 -1
- package/dist/lib/signing/index.mjs +0 -6
- package/dist/lib/signing/index.mjs.map +1 -1
- package/dist/lib/signing/jwks-helpers.mjs +0 -6
- package/dist/lib/signing/jwks-helpers.mjs.map +1 -1
- package/dist/lib/signing/jwks-https.mjs +0 -6
- package/dist/lib/signing/jwks-https.mjs.map +1 -1
- package/dist/lib/signing/jwks.mjs +0 -6
- package/dist/lib/signing/jwks.mjs.map +1 -1
- package/dist/lib/signing/middleware.mjs +0 -6
- package/dist/lib/signing/middleware.mjs.map +1 -1
- package/dist/lib/signing/parser.mjs +0 -6
- package/dist/lib/signing/parser.mjs.map +1 -1
- package/dist/lib/signing/postgres-replay-store.mjs +0 -6
- package/dist/lib/signing/postgres-replay-store.mjs.map +1 -1
- package/dist/lib/signing/protocol-response.mjs +0 -6
- package/dist/lib/signing/protocol-response.mjs.map +1 -1
- package/dist/lib/signing/provider.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs.map +1 -1
- package/dist/lib/signing/replay.mjs +0 -6
- package/dist/lib/signing/replay.mjs.map +1 -1
- package/dist/lib/signing/request-context.mjs +0 -6
- package/dist/lib/signing/request-context.mjs.map +1 -1
- package/dist/lib/signing/revocation-https.mjs +0 -6
- package/dist/lib/signing/revocation-https.mjs.map +1 -1
- package/dist/lib/signing/revocation.mjs +0 -6
- package/dist/lib/signing/revocation.mjs.map +1 -1
- package/dist/lib/signing/server.mjs +0 -6
- package/dist/lib/signing/server.mjs.map +1 -1
- package/dist/lib/signing/signer-async.mjs +0 -6
- package/dist/lib/signing/signer-async.mjs.map +1 -1
- package/dist/lib/signing/signer.mjs +0 -6
- package/dist/lib/signing/signer.mjs.map +1 -1
- package/dist/lib/signing/testing.mjs +0 -6
- package/dist/lib/signing/testing.mjs.map +1 -1
- package/dist/lib/signing/types.mjs +0 -6
- package/dist/lib/signing/types.mjs.map +1 -1
- package/dist/lib/signing/verifier.mjs +0 -6
- package/dist/lib/signing/verifier.mjs.map +1 -1
- package/dist/lib/signing/webhook-auth-detection.mjs +0 -6
- package/dist/lib/signing/webhook-auth-detection.mjs.map +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +0 -6
- package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
- package/dist/lib/storage/MemoryStorage.mjs +0 -6
- package/dist/lib/storage/MemoryStorage.mjs.map +1 -1
- package/dist/lib/storage/interfaces.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs.map +1 -1
- package/dist/lib/substitution/encoder/index.mjs +0 -6
- package/dist/lib/substitution/encoder/index.mjs.map +1 -1
- package/dist/lib/substitution/index.mjs +0 -6
- package/dist/lib/substitution/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs +0 -6
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs.map +1 -1
- package/dist/lib/substitution/observer/alignment.mjs +0 -6
- package/dist/lib/substitution/observer/alignment.mjs.map +1 -1
- package/dist/lib/substitution/observer/assertions.mjs +0 -6
- package/dist/lib/substitution/observer/assertions.mjs.map +1 -1
- package/dist/lib/substitution/observer/html-parser.mjs +0 -6
- package/dist/lib/substitution/observer/html-parser.mjs.map +1 -1
- package/dist/lib/substitution/observer/index.mjs +0 -6
- package/dist/lib/substitution/observer/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/ssrf.mjs +0 -6
- package/dist/lib/substitution/observer/ssrf.mjs.map +1 -1
- package/dist/lib/substitution/rfc3986.mjs +0 -6
- package/dist/lib/substitution/rfc3986.mjs.map +1 -1
- package/dist/lib/substitution/translate.mjs +0 -6
- package/dist/lib/substitution/translate.mjs.map +1 -1
- package/dist/lib/substitution/types.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs.map +1 -1
- package/dist/lib/testing/agent-tester.mjs +0 -6
- package/dist/lib/testing/agent-tester.mjs.map +1 -1
- package/dist/lib/testing/client.mjs +0 -6
- package/dist/lib/testing/client.mjs.map +1 -1
- package/dist/lib/testing/compliance/briefs.mjs +0 -6
- package/dist/lib/testing/compliance/briefs.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +0 -6
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/compliance/index.mjs +0 -6
- package/dist/lib/testing/compliance/index.mjs.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.mjs +0 -6
- package/dist/lib/testing/compliance/spec-conformance.mjs.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.mjs +0 -6
- package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
- package/dist/lib/testing/compliance/summary.mjs +0 -6
- package/dist/lib/testing/compliance/summary.mjs.map +1 -1
- package/dist/lib/testing/compliance/types.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs.map +1 -1
- package/dist/lib/testing/controller-assertions.mjs +0 -6
- package/dist/lib/testing/controller-assertions.mjs.map +1 -1
- package/dist/lib/testing/formatter.mjs +0 -6
- package/dist/lib/testing/formatter.mjs.map +1 -1
- package/dist/lib/testing/index.mjs +0 -6
- package/dist/lib/testing/index.mjs.map +1 -1
- package/dist/lib/testing/local-agent-runner.mjs +0 -6
- package/dist/lib/testing/local-agent-runner.mjs.map +1 -1
- package/dist/lib/testing/orchestrator.mjs +0 -6
- package/dist/lib/testing/orchestrator.mjs.map +1 -1
- package/dist/lib/testing/personas/index.mjs +0 -6
- package/dist/lib/testing/personas/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/brand-rights.mjs +0 -6
- package/dist/lib/testing/scenarios/brand-rights.mjs.map +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +0 -6
- package/dist/lib/testing/scenarios/capabilities.mjs.map +1 -1
- package/dist/lib/testing/scenarios/creative.mjs +0 -6
- package/dist/lib/testing/scenarios/creative.mjs.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.mjs +0 -6
- package/dist/lib/testing/scenarios/deterministic.mjs.map +1 -1
- package/dist/lib/testing/scenarios/discovery.mjs +0 -6
- package/dist/lib/testing/scenarios/discovery.mjs.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.mjs +0 -6
- package/dist/lib/testing/scenarios/edge-cases.mjs.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/error-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/governance.mjs +0 -6
- package/dist/lib/testing/scenarios/governance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/health.mjs +0 -6
- package/dist/lib/testing/scenarios/health.mjs.map +1 -1
- package/dist/lib/testing/scenarios/index.mjs +0 -6
- package/dist/lib/testing/scenarios/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.mjs +0 -6
- package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/schema-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/signals.mjs +0 -6
- package/dist/lib/testing/scenarios/signals.mjs.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs +0 -6
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs.map +1 -1
- package/dist/lib/testing/scenarios/trusted-match.mjs +0 -6
- package/dist/lib/testing/scenarios/trusted-match.mjs.map +1 -1
- package/dist/lib/testing/seed-merge.mjs +0 -6
- package/dist/lib/testing/seed-merge.mjs.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.mjs +0 -6
- package/dist/lib/testing/storyboard/agent-routing.mjs.map +1 -1
- package/dist/lib/testing/storyboard/assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs +0 -6
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs.map +1 -1
- package/dist/lib/testing/storyboard/compliance.mjs +5 -6
- package/dist/lib/testing/storyboard/compliance.mjs.map +1 -1
- package/dist/lib/testing/storyboard/context.mjs +0 -6
- package/dist/lib/testing/storyboard/context.mjs.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.mjs +0 -6
- package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs +0 -6
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/junit.mjs +0 -6
- package/dist/lib/testing/storyboard/junit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/loader.mjs +0 -6
- package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/path.mjs +0 -6
- package/dist/lib/testing/storyboard/path.mjs.map +1 -1
- package/dist/lib/testing/storyboard/probes.mjs +0 -6
- package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs +0 -6
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rejection-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/rejection-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +0 -6
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/sandbox-entities.mjs +0 -6
- package/dist/lib/testing/storyboard/sandbox-entities.mjs.map +1 -1
- package/dist/lib/testing/storyboard/seeding.mjs +0 -6
- package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/index.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/types.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +0 -6
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs +0 -6
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.mjs +0 -6
- package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
- package/dist/lib/testing/stubs/governance-agent-stub.mjs +0 -6
- package/dist/lib/testing/stubs/governance-agent-stub.mjs.map +1 -1
- package/dist/lib/testing/stubs/index.mjs +0 -6
- package/dist/lib/testing/stubs/index.mjs.map +1 -1
- package/dist/lib/testing/test-controller.mjs +0 -6
- package/dist/lib/testing/test-controller.mjs.map +1 -1
- package/dist/lib/testing/test-helpers.mjs +0 -6
- package/dist/lib/testing/test-helpers.mjs.map +1 -1
- package/dist/lib/testing/types.mjs +0 -6
- package/dist/lib/types/adcp.mjs +0 -6
- package/dist/lib/types/adcp.mjs.map +1 -1
- package/dist/lib/types/asset-instances.mjs +0 -6
- package/dist/lib/types/compat.mjs +0 -6
- package/dist/lib/types/compat.mjs.map +1 -1
- package/dist/lib/types/core.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs.map +1 -1
- package/dist/lib/types/error-codes.mjs +0 -6
- package/dist/lib/types/error-codes.mjs.map +1 -1
- package/dist/lib/types/error-details.aliases.mjs +0 -6
- package/dist/lib/types/error-details.aliases.mjs.map +1 -1
- package/dist/lib/types/format-asset-slots.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs.map +1 -1
- package/dist/lib/types/index.mjs +0 -6
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/types/inline-enums.aliases.mjs +0 -6
- package/dist/lib/types/inline-enums.aliases.mjs.map +1 -1
- package/dist/lib/types/inline-enums.generated.mjs +0 -6
- package/dist/lib/types/inline-enums.generated.mjs.map +1 -1
- package/dist/lib/types/manifest.generated.mjs +0 -6
- package/dist/lib/types/manifest.generated.mjs.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +0 -6
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.mjs +0 -6
- package/dist/lib/types/server-payload.mjs +0 -6
- package/dist/lib/types/sync-rows.mjs +0 -6
- package/dist/lib/types/tools.generated.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs.map +1 -1
- package/dist/lib/types/v2-5/tools.generated.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs.map +1 -1
- package/dist/lib/types/v3-1-beta/tools.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs.map +1 -1
- package/dist/lib/upstream-recorder/constants.mjs +0 -6
- package/dist/lib/upstream-recorder/constants.mjs.map +1 -1
- package/dist/lib/upstream-recorder/index.mjs +0 -6
- package/dist/lib/upstream-recorder/index.mjs.map +1 -1
- package/dist/lib/upstream-recorder/recorder.mjs +0 -6
- package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
- package/dist/lib/upstream-recorder/types.mjs +0 -6
- package/dist/lib/upstream-recorder/types.mjs.map +1 -1
- package/dist/lib/utils/a2a-artifacts.mjs +0 -6
- package/dist/lib/utils/a2a-artifacts.mjs.map +1 -1
- package/dist/lib/utils/a2a-discovery.mjs +0 -6
- package/dist/lib/utils/a2a-discovery.mjs.map +1 -1
- package/dist/lib/utils/activation-key-builders.mjs +0 -6
- package/dist/lib/utils/activation-key-builders.mjs.map +1 -1
- package/dist/lib/utils/adcp-version-config.mjs +0 -6
- package/dist/lib/utils/adcp-version-config.mjs.map +1 -1
- package/dist/lib/utils/asset-builders.mjs +0 -6
- package/dist/lib/utils/asset-builders.mjs.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.mjs +0 -6
- package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
- package/dist/lib/utils/buyer-retry-policy.mjs +0 -6
- package/dist/lib/utils/buyer-retry-policy.mjs.map +1 -1
- package/dist/lib/utils/capabilities.mjs +0 -6
- package/dist/lib/utils/capabilities.mjs.map +1 -1
- package/dist/lib/utils/capability-rollups.mjs +0 -6
- package/dist/lib/utils/capability-rollups.mjs.map +1 -1
- package/dist/lib/utils/creative-adapter.mjs +0 -6
- package/dist/lib/utils/creative-adapter.mjs.map +1 -1
- package/dist/lib/utils/creative-delivery.mjs +0 -6
- package/dist/lib/utils/creative-delivery.mjs.map +1 -1
- package/dist/lib/utils/deprecation.mjs +0 -6
- package/dist/lib/utils/deprecation.mjs.map +1 -1
- package/dist/lib/utils/envelope-status-compat.mjs +0 -6
- package/dist/lib/utils/envelope-status-compat.mjs.map +1 -1
- package/dist/lib/utils/error-extraction.mjs +0 -6
- package/dist/lib/utils/error-extraction.mjs.map +1 -1
- package/dist/lib/utils/extract-result.mjs +0 -6
- package/dist/lib/utils/extract-result.mjs.map +1 -1
- package/dist/lib/utils/format-asset-slot-builders.mjs +0 -6
- package/dist/lib/utils/format-asset-slot-builders.mjs.map +1 -1
- package/dist/lib/utils/format-assets.mjs +0 -6
- package/dist/lib/utils/format-assets.mjs.map +1 -1
- package/dist/lib/utils/format-render-builders.mjs +0 -6
- package/dist/lib/utils/format-render-builders.mjs.map +1 -1
- package/dist/lib/utils/format-renders.mjs +0 -6
- package/dist/lib/utils/format-renders.mjs.map +1 -1
- package/dist/lib/utils/get-products-cache-scope.mjs +0 -6
- package/dist/lib/utils/get-products-cache-scope.mjs.map +1 -1
- package/dist/lib/utils/glob.mjs +0 -6
- package/dist/lib/utils/glob.mjs.map +1 -1
- package/dist/lib/utils/global-async-local-storage.mjs +0 -6
- package/dist/lib/utils/global-async-local-storage.mjs.map +1 -1
- package/dist/lib/utils/idempotency.mjs +0 -6
- package/dist/lib/utils/idempotency.mjs.map +1 -1
- package/dist/lib/utils/index.mjs +0 -6
- package/dist/lib/utils/index.mjs.map +1 -1
- package/dist/lib/utils/jcs.mjs +0 -6
- package/dist/lib/utils/jcs.mjs.map +1 -1
- package/dist/lib/utils/json-depth.mjs +0 -6
- package/dist/lib/utils/json-depth.mjs.map +1 -1
- package/dist/lib/utils/logger.mjs +0 -6
- package/dist/lib/utils/logger.mjs.map +1 -1
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs +0 -6
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs.map +1 -1
- package/dist/lib/utils/media-buy-status.mjs +0 -6
- package/dist/lib/utils/media-buy-status.mjs.map +1 -1
- package/dist/lib/utils/pagination.mjs +0 -6
- package/dist/lib/utils/pagination.mjs.map +1 -1
- package/dist/lib/utils/preview-creative-builders.mjs +0 -6
- package/dist/lib/utils/preview-creative-builders.mjs.map +1 -1
- package/dist/lib/utils/preview-normalizer.mjs +0 -6
- package/dist/lib/utils/preview-normalizer.mjs.map +1 -1
- package/dist/lib/utils/preview-utils.mjs +0 -6
- package/dist/lib/utils/preview-utils.mjs.map +1 -1
- package/dist/lib/utils/pricing-adapter.mjs +0 -6
- package/dist/lib/utils/pricing-adapter.mjs.map +1 -1
- package/dist/lib/utils/probe-policy.mjs +0 -6
- package/dist/lib/utils/probe-policy.mjs.map +1 -1
- package/dist/lib/utils/protocol-detection.mjs +0 -6
- package/dist/lib/utils/protocol-detection.mjs.map +1 -1
- package/dist/lib/utils/redact-secrets.mjs +0 -6
- package/dist/lib/utils/redact-secrets.mjs.map +1 -1
- package/dist/lib/utils/redis-default-prefix-warn.mjs +0 -6
- package/dist/lib/utils/redis-default-prefix-warn.mjs.map +1 -1
- package/dist/lib/utils/render-builders.mjs +0 -6
- package/dist/lib/utils/render-builders.mjs.map +1 -1
- package/dist/lib/utils/request-normalizer.mjs +0 -6
- package/dist/lib/utils/request-normalizer.mjs.map +1 -1
- package/dist/lib/utils/response-schemas.mjs +0 -6
- package/dist/lib/utils/response-schemas.mjs.map +1 -1
- package/dist/lib/utils/response-unwrapper.mjs +0 -6
- package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
- package/dist/lib/utils/retry.mjs +0 -6
- package/dist/lib/utils/retry.mjs.map +1 -1
- package/dist/lib/utils/signal-discovery-helpers.mjs +0 -6
- package/dist/lib/utils/signal-discovery-helpers.mjs.map +1 -1
- package/dist/lib/utils/signal-id-builders.mjs +0 -6
- package/dist/lib/utils/signal-id-builders.mjs.map +1 -1
- package/dist/lib/utils/sync-creatives-adapter.mjs +0 -6
- package/dist/lib/utils/sync-creatives-adapter.mjs.map +1 -1
- package/dist/lib/utils/task-state.mjs +0 -6
- package/dist/lib/utils/task-state.mjs.map +1 -1
- package/dist/lib/utils/tool-request-schemas.mjs +0 -6
- package/dist/lib/utils/tool-request-schemas.mjs.map +1 -1
- package/dist/lib/utils/typeGuards.mjs +0 -6
- package/dist/lib/utils/typeGuards.mjs.map +1 -1
- package/dist/lib/utils/union-errors.mjs +0 -6
- package/dist/lib/utils/union-errors.mjs.map +1 -1
- package/dist/lib/utils/validate-user-agent.mjs +0 -6
- package/dist/lib/utils/validate-user-agent.mjs.map +1 -1
- package/dist/lib/v2/format-schema/fetch.mjs +0 -6
- package/dist/lib/v2/format-schema/fetch.mjs.map +1 -1
- package/dist/lib/v2/format-schema/index.mjs +0 -6
- package/dist/lib/v2/format-schema/index.mjs.map +1 -1
- package/dist/lib/v2/format-schema/regex-safety.mjs +0 -6
- package/dist/lib/v2/format-schema/regex-safety.mjs.map +1 -1
- package/dist/lib/v2/format-schema/resolver.mjs +7 -8
- package/dist/lib/v2/format-schema/resolver.mjs.map +1 -1
- package/dist/lib/v2/format-schema/sandbox-refs.mjs +0 -6
- package/dist/lib/v2/format-schema/sandbox-refs.mjs.map +1 -1
- package/dist/lib/v2/projection/augment-response.mjs +0 -6
- package/dist/lib/v2/projection/augment-response.mjs.map +1 -1
- package/dist/lib/v2/projection/builders.mjs +0 -6
- package/dist/lib/v2/projection/builders.mjs.map +1 -1
- package/dist/lib/v2/projection/cache-versions.mjs +0 -6
- package/dist/lib/v2/projection/cache-versions.mjs.map +1 -1
- package/dist/lib/v2/projection/canonical-properties.mjs +5 -6
- package/dist/lib/v2/projection/canonical-properties.mjs.map +1 -1
- package/dist/lib/v2/projection/catalog.mjs +5 -6
- package/dist/lib/v2/projection/catalog.mjs.map +1 -1
- package/dist/lib/v2/projection/constants.mjs +0 -6
- package/dist/lib/v2/projection/constants.mjs.map +1 -1
- package/dist/lib/v2/projection/index.mjs +0 -6
- package/dist/lib/v2/projection/index.mjs.map +1 -1
- package/dist/lib/v2/projection/registry.mjs +5 -6
- package/dist/lib/v2/projection/registry.mjs.map +1 -1
- package/dist/lib/v2/projection/types.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs.map +1 -1
- package/dist/lib/v2/projection/v2-to-v1.mjs +0 -6
- package/dist/lib/v2/projection/v2-to-v1.mjs.map +1 -1
- package/dist/lib/v2/projection/write-side.mjs +0 -6
- package/dist/lib/v2/projection/write-side.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/formats.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/formats.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/index.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/index.mjs.map +1 -1
- package/dist/lib/validation/client-hooks.mjs +0 -6
- package/dist/lib/validation/client-hooks.mjs.map +1 -1
- package/dist/lib/validation/hints.mjs +0 -6
- package/dist/lib/validation/hints.mjs.map +1 -1
- package/dist/lib/validation/index.mjs +0 -6
- package/dist/lib/validation/index.mjs.map +1 -1
- package/dist/lib/validation/schema-errors.mjs +0 -6
- package/dist/lib/validation/schema-errors.mjs.map +1 -1
- package/dist/lib/validation/schema-loader.mjs +5 -6
- package/dist/lib/validation/schema-loader.mjs.map +1 -1
- package/dist/lib/validation/schema-validator.mjs +0 -6
- package/dist/lib/validation/schema-validator.mjs.map +1 -1
- package/dist/lib/validation/sync-creatives.mjs +0 -6
- package/dist/lib/validation/sync-creatives.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -9
- package/dist/lib/version.mjs.map +1 -1
- package/dist/lib/webhooks/index.mjs +0 -6
- package/dist/lib/webhooks/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/index.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/sync.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/sync.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/types.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/server/decisioning/status-mappers.ts"],"sourcesContent":["/**\n * Native status mapping. Real ad systems have their own status models\n * (GAM uses DRAFT/APPROVED/etc.; Spotify uses PENDING/ACTIVE; LiveRamp\n * uses INGESTING/MATCHED/ACTIVATED). The platform implements one mapper\n * per AdCP-typed status; framework calls them where wire responses need\n * the AdCP-typed value.\n *\n * **Scope**: each mapper is a wire-status decoder for a single native\n * status string. Rollup logic — taking N native statuses across multiple\n * objects (GAM Order + LineItems, Criteo campaign + line items + creatives)\n * and computing one AdCP-typed status — is platform-specific and lives in\n * adapter code, NOT here. The mapper answers \"what does GAM's `READY` mean\n * in AdCP terms?\"; the adapter answers \"if any LineItem is `DISAPPROVED`,\n * the buy is `rejected`; if all are `DELIVERING`, the buy is `active`\".\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport type { AdcpAccountStatus } from './account';\n\n/**\n * One method per AdCP-typed status. All optional — platforms only map the\n * statuses their AdCP-claimed specialisms expose.\n */\nexport interface StatusMappers {\n account?(native: string): AdcpAccountStatus;\n mediaBuy?(native: string): AdcpMediaBuyStatus;\n creative?(native: string): AdcpCreativeStatus;\n plan?(native: string): AdcpPlanStatus;\n}\n\nexport type AdcpMediaBuyStatus =\n | 'pending_creatives'\n | 'pending_start'\n | 'active'\n | 'paused'\n | 'completed'\n | 'rejected'\n | 'canceled';\n\nexport type AdcpCreativeStatus = 'pending_review' | 'approved' | 'rejected' | 'archived';\n\nexport type AdcpPlanStatus = 'active' | 'exhausted' | 'expired' | 'paused';\n\n/**\n * Identity mappers — when the platform's native status strings already match\n * AdCP's typed enums (most modern platforms do for `active` / `paused`),\n * adopters use this instead of writing the obvious passthrough functions.\n *\n * Falls back to the AdCP default for unknown native values:\n * - mediaBuy: `'pending_creatives'`\n * - creative: `'pending_review'`\n * - account: `'active'`\n * - plan: `'active'`\n */\nexport const identityStatusMappers: StatusMappers = {\n mediaBuy: native =>\n (['pending_creatives', 'pending_start', 'active', 'paused', 'completed', 'rejected', 'canceled'].includes(native)\n ? native\n : 'pending_creatives') as AdcpMediaBuyStatus,\n creative: native =>\n (['pending_review', 'approved', 'rejected', 'archived'].includes(native)\n ? native\n : 'pending_review') as AdcpCreativeStatus,\n account: native =>\n (['active', 'pending_approval', 'rejected', 'payment_required', 'suspended', 'closed'].includes(native)\n ? native\n : 'active') as AdcpAccountStatus,\n plan: native => (['active', 'exhausted', 'expired', 'paused'].includes(native) ? native : 'active') as AdcpPlanStatus,\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/server/decisioning/status-mappers.ts"],"sourcesContent":["/**\n * Native status mapping. Real ad systems have their own status models\n * (GAM uses DRAFT/APPROVED/etc.; Spotify uses PENDING/ACTIVE; LiveRamp\n * uses INGESTING/MATCHED/ACTIVATED). The platform implements one mapper\n * per AdCP-typed status; framework calls them where wire responses need\n * the AdCP-typed value.\n *\n * **Scope**: each mapper is a wire-status decoder for a single native\n * status string. Rollup logic — taking N native statuses across multiple\n * objects (GAM Order + LineItems, Criteo campaign + line items + creatives)\n * and computing one AdCP-typed status — is platform-specific and lives in\n * adapter code, NOT here. The mapper answers \"what does GAM's `READY` mean\n * in AdCP terms?\"; the adapter answers \"if any LineItem is `DISAPPROVED`,\n * the buy is `rejected`; if all are `DELIVERING`, the buy is `active`\".\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport type { AdcpAccountStatus } from './account';\n\n/**\n * One method per AdCP-typed status. All optional — platforms only map the\n * statuses their AdCP-claimed specialisms expose.\n */\nexport interface StatusMappers {\n account?(native: string): AdcpAccountStatus;\n mediaBuy?(native: string): AdcpMediaBuyStatus;\n creative?(native: string): AdcpCreativeStatus;\n plan?(native: string): AdcpPlanStatus;\n}\n\nexport type AdcpMediaBuyStatus =\n | 'pending_creatives'\n | 'pending_start'\n | 'active'\n | 'paused'\n | 'completed'\n | 'rejected'\n | 'canceled';\n\nexport type AdcpCreativeStatus = 'pending_review' | 'approved' | 'rejected' | 'archived';\n\nexport type AdcpPlanStatus = 'active' | 'exhausted' | 'expired' | 'paused';\n\n/**\n * Identity mappers — when the platform's native status strings already match\n * AdCP's typed enums (most modern platforms do for `active` / `paused`),\n * adopters use this instead of writing the obvious passthrough functions.\n *\n * Falls back to the AdCP default for unknown native values:\n * - mediaBuy: `'pending_creatives'`\n * - creative: `'pending_review'`\n * - account: `'active'`\n * - plan: `'active'`\n */\nexport const identityStatusMappers: StatusMappers = {\n mediaBuy: native =>\n (['pending_creatives', 'pending_start', 'active', 'paused', 'completed', 'rejected', 'canceled'].includes(native)\n ? native\n : 'pending_creatives') as AdcpMediaBuyStatus,\n creative: native =>\n (['pending_review', 'approved', 'rejected', 'archived'].includes(native)\n ? native\n : 'pending_review') as AdcpCreativeStatus,\n account: native =>\n (['active', 'pending_approval', 'rejected', 'payment_required', 'suspended', 'closed'].includes(native)\n ? native\n : 'active') as AdcpAccountStatus,\n plan: native => (['active', 'exhausted', 'expired', 'paused'].includes(native) ? native : 'active') as AdcpPlanStatus,\n};\n"],"mappings":"AAyDO,MAAM,wBAAuC;AAAA,EAClD,UAAU,YACP,CAAC,qBAAqB,iBAAiB,UAAU,UAAU,aAAa,YAAY,UAAU,EAAE,SAAS,MAAM,IAC5G,SACA;AAAA,EACN,UAAU,YACP,CAAC,kBAAkB,YAAY,YAAY,UAAU,EAAE,SAAS,MAAM,IACnE,SACA;AAAA,EACN,SAAS,YACN,CAAC,UAAU,oBAAoB,YAAY,oBAAoB,aAAa,QAAQ,EAAE,SAAS,MAAM,IAClG,SACA;AAAA,EACN,MAAM,YAAW,CAAC,UAAU,aAAa,WAAW,QAAQ,EAAE,SAAS,MAAM,IAAI,SAAS;AAC5F;","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 { createAdcpServerFromPlatform } from "./runtime/from-platform.mjs";
|
|
8
2
|
import { redactCredentialPatterns } from "../redact.mjs";
|
|
9
3
|
function createDefaultJwksValidator(opts) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/server/decisioning/tenant-registry.ts"],"sourcesContent":["/**\n * TenantRegistry — multi-tenant deployment helper for the v6.0 decisioning\n * runtime.\n *\n * Holds a map of `agentUrl → TenantConfig` and tracks per-tenant health.\n * Composes with the existing `serve()` host-routing surface: the registry\n * returns an `AdcpServer` factory that dispatches based on `ctx.host`.\n *\n * **Three health states**, never startup-fatal:\n *\n * - `healthy` — JWKS validated, accepting traffic normally.\n * - `unverified` — validation hasn't completed (server starting, or\n * JWKS unreachable transiently). Tenant accepts traffic with a\n * warning header; framework periodically re-validates.\n * - `disabled` — validation failed deterministically (signing key not\n * in published JWKS, brand.json malformed). Tenant returns\n * `SERVICE_UNAVAILABLE` until an admin recheck succeeds.\n *\n * One bad tenant doesn't take down others — health is per-tenant, the\n * other tenants keep serving.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport type { DecisioningPlatform, RequiredPlatformsFor, RequiredCapabilitiesFor } from './platform';\nimport type { DecisioningAdcpServer, CreateAdcpServerFromPlatformOptions } from './runtime/from-platform';\nimport { createAdcpServerFromPlatform } from './runtime/from-platform';\nimport type { SignerKey } from '../../signing/signer';\nimport type { AdcpJsonWebKey } from '../../signing/types';\nimport { redactCredentialPatterns } from '../redact';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface TenantSigningKey {\n /** Stable key identifier — appears in the `Signature-Input` header. */\n keyId: string;\n /**\n * JWK form of the public key. MUST appear in the JWKS at\n * `{agentUrl}/.well-known/brand.json` for the tenant to validate.\n *\n * **`adcp_use` requirement.** The JWK MUST carry\n * `adcp_use: \"webhook-signing\"` for the registry to auto-wire this\n * key into outbound webhook signing. Per AdCP, request-signing and\n * webhook-signing keys MUST be distinct (key-purpose discriminator,\n * adcp#2423) — a key intended for inbound request-signature\n * verification CANNOT double as a webhook-signing key. If you have\n * both purposes, register two tenants OR wire the request-signing\n * verifier separately on `serverOptions.signedRequests` and put only\n * the webhook-signing key on `signingKey`.\n *\n * Set with: `publicJwk: { ...jwk, adcp_use: 'webhook-signing' }`.\n * `createSelfSignedTenantKey()` sets it for you.\n */\n publicJwk: JsonWebKey;\n /**\n * Private JWK used to sign outbound webhooks (RFC 9421). MUST carry\n * `adcp_use: \"webhook-signing\"`. The registry's `buildServer` plumbs\n * this into `serverOptions.webhooks.signerKey` automatically — set\n * the key once on `signingKey` and outbound webhook deliveries are\n * RFC 9421-signed by default. Adopters who want a different webhook\n * key (or don't want auto-wiring) explicitly set\n * `serverOptions.webhooks.signerKey` / `signerProvider`; the explicit\n * config wins and auto-wiring is skipped.\n */\n privateJwk: JsonWebKey;\n}\n\nexport interface TenantConfig<P extends DecisioningPlatform = DecisioningPlatform> {\n /**\n * Public URL the tenant accepts traffic on (e.g.,\n * `https://acme-tv.example.com`). Used for host-route matching and —\n * unless `jwksUrl` overrides — as the JWKS fetch base (the default\n * validator computes `{host}/.well-known/brand.json` from this URL).\n *\n * For deployments that accept traffic on multiple URLs simultaneously\n * (DNS cutover, vanity domains, internal + public hostname), use\n * {@link agentUrls} instead — `agentUrl` is the single-URL convenience\n * form. When both are set, `agentUrls` wins and `agentUrl` is ignored.\n */\n agentUrl?: string;\n /**\n * Multiple public URLs this tenant accepts traffic on. Use for cutover\n * windows where `old.example.com/mcp` and `new.example.com/mcp` must\n * both resolve to the same tenant for a window before DNS or buyer\n * caches catch up. The first URL is the **canonical** one — JWKS\n * validation uses it (and any `jwksUrl` override applies to all URLs).\n * Additional URLs are aliases; they share the same brand and signing\n * key.\n *\n * Adopters with two truly distinct brands serving from one platform\n * should register separate tenants — each brand has its own JWKS and\n * signing identity. `agentUrls` is for the single-brand-multiple-URLs\n * case.\n *\n * MUST contain at least one URL when set. Exactly one of `agentUrl` or\n * `agentUrls` must be provided; supplying both is a register() error.\n */\n agentUrls?: string[];\n /**\n * Override the JWKS fetch URL for this tenant. Use this when the\n * tenant's brand.json doesn't sit at the host root — i.e., a single\n * host serves multiple agents under path prefixes\n * (`https://shared.example.com/api/agent-a`,\n * `https://shared.example.com/api/agent-b`) and each prefix has its\n * own brand identity. Without this override, the default validator\n * resolves `/.well-known/brand.json` against the host root, which\n * collapses both agents onto the same brand.\n *\n * Spec convention is host-root, so the override is only needed for\n * sub-routed multi-tenant deployments. Custom validators that take a\n * different shape entirely (e.g., reading from a vault) read this\n * field via the `jwksUrl` argument on `JwksValidator.validate`.\n */\n jwksUrl?: string;\n /**\n * Tenant's webhook-signing identity (RFC 9421). **Optional in 3.x;\n * mandated in 4.0.**\n *\n * **Scope is webhooks only.** AdCP 3.x defines exactly one\n * outbound seller-→buyer signing surface: webhook deliveries. The\n * synchronous tools/call reply is **not** signed at the body level\n * — TLS provides session-scoped integrity for the immediate response,\n * and signed webhooks carry durable at-rest integrity for async\n * artifacts. This is the deliberate two-surface design, not a\n * coverage gap; see `docs/building/understanding/security-model.mdx`\n * § \"What gets signed — and what doesn't\" (adcp#3737, resolved by\n * adcp#3742). Adopters who need attestable artifacts for\n * synchronous flows structure the tool to emit a signed webhook\n * (the \"request-the-webhook\" pattern in the same doc).\n *\n * When set, the registry does two things:\n *\n * 1. **JWKS validation.** Confirms `publicJwk` appears in the\n * tenant's published JWKS at `{agentUrl}/.well-known/brand.json`\n * (or `jwksUrl` if overridden) before transitioning the tenant\n * to `healthy`.\n * 2. **Webhook auto-wire.** Plumbs the privateJwk into\n * `serverOptions.webhooks.signerKey` automatically, so outbound\n * webhook deliveries are signed without the adopter wiring the\n * key twice. Strict on `adcp_use: \"webhook-signing\"` per AdCP\n * key-purpose discriminator (adcp#2423). Adopters wiring a\n * KMS-backed signer or a distinct webhook key per tenant set\n * `serverOptions.webhooks.signerKey` / `signerProvider`\n * explicitly — the explicit config wins and auto-wiring is\n * skipped.\n *\n * When omitted, both behaviors short-circuit: JWKS validation is\n * skipped, the tenant transitions directly from `pending` to\n * `healthy` on register() with `reason: 'unsigned (no signingKey)'`,\n * and outbound webhooks are emitted unsigned (or, in 4.0, would be\n * a registration error). AdCP 3.x treats signing as optional, so\n * adopters spiking the SDK before standing up KMS or publishing\n * brand.json can ship without signing material. Buyers MUST NOT\n * break when an agent doesn't sign in 3.x — that's covered by the\n * \"tolerate Signature headers\" baseline regardless of whether the\n * agent itself signs.\n *\n * For local dev with signing enabled, pair `createSelfSignedTenantKey()`\n * (generates an Ed25519 keypair already tagged\n * `adcp_use: \"webhook-signing\"`) with `createNoopJwksValidator()`\n * (skips the brand.json roundtrip in dev/test). Production adopters\n * keep the default validator and publish the public half via\n * brand.json. See `docs/guides/SIGNING-GUIDE.md` § \"Self-signed dev\n * path\" for the worked recipe.\n */\n signingKey?: TenantSigningKey;\n /** The DecisioningPlatform impl for this tenant. */\n platform: P &\n RequiredPlatformsFor<P['capabilities']['specialisms'][number]> &\n RequiredCapabilitiesFor<P['capabilities']['specialisms'][number]>;\n /** Display label for admin / logs. Optional. */\n label?: string;\n /** Per-tenant `createAdcpServerFromPlatform` options override. */\n serverOptions?: Partial<CreateAdcpServerFromPlatformOptions>;\n}\n\n/**\n * Tenant health lifecycle:\n * - `'pending'` — first JWKS validation hasn't succeeded yet. Brand-new\n * tenants land here. `resolveByHost` REFUSES TRAFFIC for `pending`\n * tenants — host transport should respond 503 + Retry-After. This\n * closes the register-then-serve race window where a tenant\n * registered with a wrong signing key would have served signed\n * responses no buyer can verify until the first refresh detected\n * the mismatch (60+ seconds).\n * - `'healthy'` — JWKS validation has succeeded at least once.\n * Periodic rechecks confirm.\n * - `'unverified'` — was previously `healthy`; latest recheck failed\n * transiently (network error, 5xx, etc.). `resolveByHost` still\n * returns the tenant — graceful degradation for known-good tenants\n * when brand.json is briefly unreachable. Distinct from `'pending'`\n * where we've never validated.\n * - `'disabled'` — permanent validation failure (key not in published\n * JWKS, brand.json malformed, etc.). `resolveByHost` returns null;\n * operator must fix and call `recheck()` to revive.\n */\nexport type TenantHealth = 'pending' | 'healthy' | 'unverified' | 'disabled';\n\nexport interface TenantStatus {\n tenantId: string;\n /**\n * Canonical URL — the first entry in the tenant's `agentUrls` (or the\n * single `agentUrl` value). JWKS validates against this URL; ops\n * dashboards page on this field for the primary identity.\n */\n agentUrl: string;\n /**\n * Full URL list when the tenant was registered with `agentUrls[]`.\n * Single-URL tenants get a one-element array. Ops dashboards iterate\n * this field to show every URL serving the tenant; required so admins\n * can detect stale aliases or accidental host overlap with another\n * tenant (#1097 follow-up — collision check below errors at register\n * time, but the operator still wants visibility into what's live).\n */\n agentUrls: readonly string[];\n health: TenantHealth;\n /** Reason for unverified/disabled state. */\n reason?: string;\n /** ISO timestamp of the last health check. */\n lastCheckedAt: string;\n}\n\nexport interface JwksValidationResult {\n ok: boolean;\n /** Recovery classification when `ok === false`. */\n recovery?: 'transient' | 'permanent';\n reason?: string;\n}\n\nexport interface JwksValidator {\n /**\n * Validate that the tenant's signing key appears in the published JWKS.\n *\n * - `agentUrl` is the tenant's public URL (used for host-relative URL\n * computation by the default validator).\n * - `jwksUrl` is the explicit JWKS fetch URL when the tenant's\n * `TenantConfig.jwksUrl` was set; absent for the spec-default\n * host-root resolution.\n * - `signingKey` is what to look for in the published JWKS.\n */\n validate(opts: { agentUrl: string; jwksUrl?: string; signingKey: TenantSigningKey }): Promise<JwksValidationResult>;\n}\n\nexport interface TenantRegistryOptions {\n /**\n * JWKS validator. Defaults to a fetch-based validator that hits\n * `{agentUrl}/.well-known/brand.json`. Tests can pass a fake.\n */\n jwksValidator?: JwksValidator;\n /**\n * Per-tenant `createAdcpServerFromPlatform` options applied to every\n * tenant unless overridden by `TenantConfig.serverOptions`.\n */\n defaultServerOptions: CreateAdcpServerFromPlatformOptions;\n /**\n * Auto-validate tenants when they're registered. Defaults to `true`.\n *\n * Disable ONLY for tests that drive validation manually via `recheck` —\n * with this off, every `register()` leaves the tenant in `'pending'`\n * health and `resolveByRequest` silently refuses traffic until the\n * caller recheck()s each tenant. Production deployments should leave\n * this at the default; the framework emits a one-shot console.warn at\n * registry construction when `autoValidate: false` is set, to surface\n * the \"all traffic blocked\" consequence.\n */\n autoValidate?: boolean;\n}\n\nexport interface TenantRegistry {\n /**\n * Register a tenant. Tenant lands in `'pending'` health initially —\n * `resolveByHost` refuses traffic until the first JWKS validation\n * succeeds. Pass `{ awaitFirstValidation: true }` to block on the\n * synchronous validation outcome (returns the resulting status; throws\n * if registration is incompatible). Without the flag, register fires\n * validation in the background and returns immediately; the caller\n * polls `getStatus(tenantId).health === 'healthy'` if needed.\n *\n * **Admin-API security.** This method is the privileged surface — any\n * caller invoking `register` can introduce a tenant that will sign\n * outbound webhooks. Hosts wiring an HTTP/RPC endpoint in front of\n * `register` MUST gate it with operator-level auth (mTLS, signed\n * admin tokens, network ACL). The framework doesn't ship admin-HTTP\n * scaffolding because the right auth shape varies by deployment;\n * adopters who want a vetted shape can layer Express middleware\n * around their `registry.register(...)` route handler.\n *\n * **Make construction errors observable.** `register` builds the\n * inner `createAdcpServerFromPlatform()` synchronously — config\n * errors (`customTools` collision with framework-promoted tools,\n * missing required handlers, invalid `signingKey` shape) fire\n * inside this call. The cleanest path is eager registration at\n * process boot, where those errors fail the deploy visibly. Lazy\n * shapes are legitimate (autoscale replicas avoiding JWKS-storm,\n * multi-tenant SaaS with mutable tenant tables, serverless\n * warm-start) — if you defer, catch the throw at the registration\n * site and route it through your error pipeline. Otherwise the\n * host framework's default error handler renders the throw as\n * HTTP 500 HTML, MCP clients (correctly) classify the body as a\n * non-MCP response, and the failure surfaces as `discovery_failed`\n * on every probe — looking like a transport bug instead of the\n * config bug it is.\n */\n register<P extends DecisioningPlatform>(\n tenantId: string,\n config: TenantConfig<P>,\n opts?: { awaitFirstValidation?: boolean }\n ): Promise<TenantStatus> | void;\n unregister(tenantId: string): void;\n /**\n * Resolve a tenant by host alone (the lowercased authority of the\n * request). Convenience wrapper around `resolveByRequest(host, '/')` —\n * works for the canonical subdomain-routing pattern (e.g.,\n * `sales.training.example.com`) where each tenant has its own host.\n *\n * For path-based routing (`training.example.com/sales`,\n * `training.example.com/creative` on a single host), use\n * `resolveByRequest(host, pathname)` instead.\n *\n * Returns null if no tenant matches, the tenant is `pending` (first\n * validation hasn't succeeded), or the tenant is `disabled`.\n * `unverified` tenants resolve normally — graceful degradation for\n * known-good tenants whose latest recheck failed transiently.\n */\n resolveByHost(host: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null;\n /**\n * Resolve a tenant by host AND request path. The framework matches\n * tenants whose `agentUrl` host equals the request host AND whose\n * `agentUrl` path is a prefix of the request `pathname`. When multiple\n * tenants share a host, the LONGEST matching path prefix wins (so\n * `/sales-broadcast` is preferred over `/sales` for a request to\n * `/sales-broadcast/mcp`).\n *\n * Use this for path-routed multi-tenant deployments where adopters\n * don't want per-tenant subdomain DNS / TLS overhead. Each tenant's\n * `agentUrl` carries the path: `https://training.example.com/sales`.\n * Subdomain-routed tenants (`https://sales.training.example.com`)\n * keep working — their path prefix is `/`, which matches any pathname.\n *\n * Returns null on no match, `pending`, or `disabled`. `unverified`\n * tenants resolve normally (graceful degradation).\n */\n resolveByRequest(\n host: string,\n pathname: string\n ): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null;\n /**\n * Direct tenant lookup by `tenantId`. Use this when your route layer\n * has already resolved the tenant from session / path / header and\n * needs the registry's `{ config, server }` entry without re-running\n * URL parsing. Same `pending` / `disabled` health gate as the\n * `resolveByXxx` helpers — returns `null` when the tenant isn't\n * accepting traffic so callers don't have to re-check status.\n *\n * Adopters who decouple tenant lookup from URL routing (e.g., bind\n * tenantId at their own Express middleware before calling into the\n * registry) should prefer this over `resolveByRequest(host, '/<id>/mcp')`\n * tricks.\n */\n get(tenantId: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null;\n getStatus(tenantId: string): TenantStatus | null;\n list(): readonly TenantStatus[];\n /**\n * Trigger a JWKS recheck for the tenant. Admin UI calls this after\n * fixing a brand.json mismatch.\n */\n recheck(tenantId: string): Promise<TenantStatus>;\n}\n\n// ---------------------------------------------------------------------------\n// Default JWKS validator\n// ---------------------------------------------------------------------------\n\n/**\n * Default JWKS validator. Fetches `{agentUrl}/.well-known/brand.json` and\n * checks that `signingKey.publicJwk` appears in the JWKS keys array.\n *\n * Uses the global `fetch`. Network errors classify as `transient`;\n * 4xx / parse-error / key-not-in-JWKS classify as `permanent`.\n */\nexport function createDefaultJwksValidator(opts?: { fetchImpl?: typeof fetch; timeoutMs?: number }): JwksValidator {\n const fetchImpl = opts?.fetchImpl ?? fetch;\n // 10-second default timeout. A slow brand.json server (or a malicious\n // one dribbling bytes) would otherwise pin the tenant in `pending`\n // for the lifetime of the fetch — and `register({ awaitFirstValidation: true })`\n // would block the booting host indefinitely. Adopters with strict SLAs\n // pass a tighter value; adopters fronting brand.json behind a slow\n // CDN can extend it. Round-6 Sec-M1.\n const timeoutMs = opts?.timeoutMs ?? 10_000;\n return {\n async validate({ agentUrl, jwksUrl, signingKey }): Promise<JwksValidationResult> {\n // Explicit jwksUrl wins (sub-routed deployments where brand.json\n // lives under a path prefix). Default falls back to the spec-\n // canonical host-root location — `new URL('/.well-known/brand.json',\n // agentUrl)` REPLACES the path because the second arg starts with\n // '/'. That's correct for host-level brand identity (one brand per\n // host), wrong for multi-tenant sub-routed deployments — adopters\n // there set `TenantConfig.jwksUrl` to point at the per-tenant\n // brand.json.\n // Reject empty-string jwksUrl as defense-in-depth — `??` only\n // catches null/undefined, so an empty string would otherwise\n // reach `fetch('')` and produce an opaque error. Treat empty as\n // \"use default.\"\n const url = jwksUrl && jwksUrl.length > 0 ? jwksUrl : new URL('/.well-known/brand.json', agentUrl).toString();\n let response: Response;\n try {\n response = await fetchImpl(url, { method: 'GET', signal: AbortSignal.timeout(timeoutMs) });\n } catch (err) {\n return {\n ok: false,\n recovery: 'transient',\n // Redact credential patterns from the upstream error message\n // before it surfaces on the admin-router wire (#1330). A fetch\n // failure can echo basic-auth-bearing URLs or upstream-library\n // diagnostics that include credential bytes.\n reason: redactCredentialPatterns(\n `JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`\n ) as string,\n };\n }\n if (!response.ok) {\n return {\n ok: false,\n recovery: response.status >= 500 ? 'transient' : 'permanent',\n reason: `JWKS fetch returned ${response.status} ${response.statusText}`,\n };\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n return {\n ok: false,\n recovery: 'permanent',\n reason: redactCredentialPatterns(\n `JWKS body not JSON: ${err instanceof Error ? err.message : String(err)}`\n ) as string,\n };\n }\n const jwks = (body as { jwks?: { keys?: unknown[] } }).jwks;\n if (!jwks || !Array.isArray(jwks.keys)) {\n return {\n ok: false,\n recovery: 'permanent',\n reason: '`brand.json` has no `jwks.keys` array',\n };\n }\n const matched = jwks.keys.find(k => isMatchingKey(k, signingKey.publicJwk, signingKey.keyId));\n if (!matched) {\n return {\n ok: false,\n recovery: 'permanent',\n reason: `signingKey.keyId='${signingKey.keyId}' not present in published JWKS`,\n };\n }\n return { ok: true };\n },\n };\n}\n\nfunction isMatchingKey(jwk: unknown, expected: JsonWebKey, expectedKid: string): boolean {\n if (!jwk || typeof jwk !== 'object') return false;\n const k = jwk as Record<string, unknown>;\n // Both `kid` AND public-key material must match. `kid` alone is NOT\n // sufficient — RFC 7517 § 4.5 explicitly notes that `kid` is just a\n // hint and clashes are possible. An attacker who can publish a JWKS\n // with a colliding `kid` would otherwise bypass verification.\n if (typeof k.kid === 'string' && k.kid !== expectedKid) return false;\n if (k.kty !== expected.kty) return false;\n // Structural equality on the public-half fields (n/e for RSA, x/y for\n // EC, x for OKP). A full JWK thumbprint comparison (RFC 7638) would be\n // more robust but this catches the typical \"wrong key\" case without\n // pulling crypto deps in. Asymmetric pubkeys are public — comparing\n // the components here doesn't need constant-time semantics.\n if (expected.kty === 'RSA') {\n return k.n === expected.n && k.e === expected.e;\n }\n if (expected.kty === 'EC') {\n return k.crv === expected.crv && k.x === expected.x && k.y === expected.y;\n }\n if (expected.kty === 'OKP') {\n return k.crv === expected.crv && k.x === expected.x;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Self-signed key + no-op validator helpers (3.x adoption ergonomics)\n// ---------------------------------------------------------------------------\n\n/**\n * Generate an Ed25519 keypair suitable for `TenantConfig.signingKey`.\n *\n * Convenience for adopters spiking the SDK before standing up KMS or\n * publishing brand.json. AdCP 3.x treats request signing as optional, so\n * `TenantConfig.signingKey` is itself optional — but adopters who DO\n * want to exercise the signing path (storyboards, signed-requests\n * grader, end-to-end tests) need a working key without the operational\n * lift of a real KMS.\n *\n * Pair with `createNoopJwksValidator()` to skip the brand.json\n * roundtrip in dev/test, OR publish the returned `publicJwk` at\n * `{agentUrl}/.well-known/brand.json` (under `jwks.keys[]`) and use the\n * default validator unchanged.\n *\n * **Production**: don't generate signing material in-process. Adopt a\n * KMS-backed loader (HashiCorp Vault, AWS KMS, GCP Secret Manager) — a\n * process compromise leaks an in-memory privateJwk and the only remedy\n * is rotation across every counterparty cache.\n *\n * @param opts.keyId Optional `kid` for the key. Defaults to a\n * timestamped value (`self-signed-{ISO date}`). Stable across restarts\n * only if you pass a stable `keyId`.\n */\nexport async function createSelfSignedTenantKey(opts?: { keyId?: string }): Promise<TenantSigningKey> {\n // Lazy import — `jose` is a runtime dep, but the import-cost is real\n // and most adopters never call this. Keep the registry's hot path\n // (register / resolve) free of jose.\n const { generateKeyPair, exportJWK } = await import('jose');\n const { publicKey, privateKey } = await generateKeyPair('EdDSA', { crv: 'Ed25519', extractable: true });\n const rawPublic = (await exportJWK(publicKey)) as JsonWebKey;\n const rawPrivate = (await exportJWK(privateKey)) as JsonWebKey;\n const keyId = opts?.keyId ?? `self-signed-${new Date().toISOString().slice(0, 10)}`;\n // Tag both halves with `adcp_use: \"webhook-signing\"`. The registry's\n // auto-wire path requires this tag (assertWebhookSigningUse below);\n // setting it on the helper output means adopters get a working\n // signing posture out of the box.\n const publicJwk: JsonWebKey = { ...rawPublic, adcp_use: 'webhook-signing' } as JsonWebKey;\n const privateJwk: JsonWebKey = { ...rawPrivate, adcp_use: 'webhook-signing' } as JsonWebKey;\n return { keyId, publicJwk, privateJwk };\n}\n\n/**\n * No-op JWKS validator that always returns `{ ok: true }`. Use ONLY in\n * dev/test when you've set `signingKey` (e.g., via\n * `createSelfSignedTenantKey()`) but haven't published brand.json yet —\n * this skips the JWKS roundtrip so tenants reach `healthy` without a\n * real `/.well-known/brand.json` endpoint.\n *\n * **Refuses to construct outside `NODE_ENV` ∈ {`'test'`, `'development'`}**\n * unless the operator sets `ADCP_NOOP_JWKS_ACK=1` to explicitly\n * acknowledge the risk. Mirrors the `idempotency: 'disabled'` allowlist\n * pattern — `NODE_ENV` defaults to unset in raw Lambda / custom\n * containers / many K8s deployments, so a `=== 'production'` check\n * would no-op in exactly the environments where a silent skip-validation\n * start is most dangerous.\n *\n * The ack value MUST be the literal string `'1'`. Truthy lookalikes\n * (`'true'`, `'yes'`) intentionally don't satisfy the gate to prevent\n * copy-paste typos.\n *\n * In production, leave the registry's default validator wired and\n * publish brand.json. Or omit `signingKey` entirely (`TenantConfig`\n * makes it optional in 3.x), which skips JWKS validation without\n * needing this helper.\n */\nexport function createNoopJwksValidator(): JwksValidator {\n const env = process.env.NODE_ENV;\n const acknowledged = process.env.ADCP_NOOP_JWKS_ACK === '1';\n const isAllowlistedDevEnv = env === 'test' || env === 'development';\n if (!isAllowlistedDevEnv && !acknowledged) {\n throw new Error(\n 'createNoopJwksValidator: refuses to construct with NODE_ENV=' +\n (env === undefined ? '<unset>' : JSON.stringify(env)) +\n '. The no-op validator skips JWKS verification, so a tenant whose published brand.json does not actually ' +\n 'contain the configured signingKey would reach `healthy` and serve unverifiable signed responses. ' +\n 'The SDK only allows it under NODE_ENV=test or NODE_ENV=development by default. Either: ' +\n '(a) use the default validator (createDefaultJwksValidator) and publish a real brand.json, ' +\n '(b) omit signingKey from TenantConfig — JWKS validation is skipped entirely for unsigned tenants in 3.x, ' +\n '(c) set NODE_ENV=test or NODE_ENV=development if this is a dev-only environment, or ' +\n '(d) set ADCP_NOOP_JWKS_ACK=1 to explicitly acknowledge the risk for non-standard environments.'\n );\n }\n // eslint-disable-next-line no-console\n console.warn(\n '[adcp] createNoopJwksValidator: JWKS validation is DISABLED for this registry. ' +\n 'Tenants will reach `healthy` without a brand.json roundtrip. Use only in dev/test.'\n );\n return {\n async validate(): Promise<JwksValidationResult> {\n return { ok: true };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Webhook auto-wire — convert TenantSigningKey to a SignerKey the framework's\n// webhook emitter understands. Strict on `adcp_use` per AdCP key-purpose\n// discriminator (adcp#2423).\n// ---------------------------------------------------------------------------\n\n/**\n * Derive the RFC 9421 signing algorithm from JWK shape. AdCP webhook\n * signing supports Ed25519 (kty=OKP, crv=Ed25519) and ECDSA P-256\n * (kty=EC, crv=P-256). Anything else throws — RSA / EC P-384 / etc.\n * are not in the AdCP signing-algorithm set.\n */\nfunction deriveSigningAlg(jwk: JsonWebKey): 'ed25519' | 'ecdsa-p256-sha256' {\n if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') return 'ed25519';\n if (jwk.kty === 'EC' && jwk.crv === 'P-256') return 'ecdsa-p256-sha256';\n throw new Error(\n `TenantConfig.signingKey: unsupported JWK shape kty=${JSON.stringify(jwk.kty)} crv=${JSON.stringify(jwk.crv)}. ` +\n 'AdCP RFC 9421 webhook signing requires Ed25519 (kty=OKP, crv=Ed25519) or ECDSA P-256 (kty=EC, crv=P-256). ' +\n 'See `docs/guides/SIGNING-GUIDE.md` for key-generation recipes.'\n );\n}\n\n/**\n * Enforce the AdCP key-purpose discriminator on the webhook auto-wire path.\n * Webhooks are signed with the agent's `adcp_use: \"request-signing\"` key\n * (the deprecated `\"webhook-signing\"` value is still accepted —\n * adcontextprotocol/adcp#5555); any other purpose is rejected. Called only\n * when auto-wiring fires (signingKey set + serverOptions.webhooks.signerKey\n * unset); adopters who want different keys per purpose wire them explicitly\n * and bypass this check.\n */\nconst WEBHOOK_WIRE_PURPOSES = ['request-signing', 'webhook-signing'];\nfunction assertWebhookSigningUse(key: TenantSigningKey): void {\n const publicUse = (key.publicJwk as Record<string, unknown>).adcp_use;\n const privateUse = (key.privateJwk as Record<string, unknown>).adcp_use;\n if (typeof publicUse !== 'string' || !WEBHOOK_WIRE_PURPOSES.includes(publicUse)) {\n throw new Error(\n `TenantConfig.signingKey: publicJwk.adcp_use must be 'request-signing' (or the deprecated 'webhook-signing') for the registry's webhook auto-wire path. ` +\n `Got ${publicUse === undefined ? '<unset>' : JSON.stringify(publicUse)}. ` +\n 'Webhooks are signed with the request-signing key; domain separation is carried by the RFC 9421 tag, not the key purpose. ' +\n 'Either: (a) tag this key with `adcp_use: \"request-signing\"`, ' +\n '(b) mint a separate request-signing key under a distinct kid for webhook isolation, or ' +\n '(c) wire `serverOptions.webhooks.signerKey` explicitly — the explicit config bypasses auto-wiring.'\n );\n }\n if (publicUse !== privateUse) {\n throw new Error(\n `TenantConfig.signingKey: privateJwk.adcp_use must match publicJwk.adcp_use ('${publicUse}'). ` +\n `Got ${privateUse === undefined ? '<unset>' : JSON.stringify(privateUse)}.`\n );\n }\n}\n\n/**\n * Convert `TenantSigningKey` to the framework-internal `SignerKey`\n * shape consumed by the webhook emitter. Field-name shift: the\n * `TenantSigningKey` surface uses `keyId` (camelCase, adopter-facing);\n * `SignerKey` uses `keyid` (lowercase, RFC 9421 wire term).\n */\nfunction tenantKeyToSignerKey(key: TenantSigningKey): SignerKey {\n const alg = deriveSigningAlg(key.publicJwk);\n return {\n keyid: key.keyId,\n alg,\n privateKey: key.privateJwk as AdcpJsonWebKey,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\ninterface TenantEntry {\n config: TenantConfig;\n server: DecisioningAdcpServer;\n status: TenantStatus;\n /**\n * Parsed (host, pathPrefix) routes for every URL on the tenant. One\n * entry per `agentUrls` element; single-element array for the\n * single-URL case. Resolution iterates this list and picks the longest\n * matching path prefix across all hosts.\n */\n routes: ReadonlyArray<{ host: string; pathPrefix: string }>;\n /** Pending revalidation; consulted by `recheck` to dedupe in-flight work. */\n pending?: Promise<TenantStatus>;\n}\n\n/**\n * Resolve the canonical agent URL + the full URL list from a TenantConfig.\n *\n * - `agentUrls` (when set) wins. First element is canonical (used for\n * JWKS resolution and status reporting).\n * - `agentUrl` (single-URL form) maps to a one-element list.\n * - Setting both is a programmer error — refuse explicitly so the\n * ambiguity doesn't silently drop one of them.\n *\n * Returns `[canonical, allUrls]`. `allUrls` is the routing surface;\n * `canonical` is what JWKS / status reports use.\n */\nfunction resolveTenantUrls(config: TenantConfig): readonly [string, ReadonlyArray<string>] {\n const list = config.agentUrls;\n const single = config.agentUrl;\n if (list !== undefined && single !== undefined) {\n throw new Error(\n 'TenantConfig: set exactly one of `agentUrl` (single URL) or `agentUrls` (multi-URL). Setting both is ambiguous.'\n );\n }\n if (list !== undefined) {\n if (list.length === 0) {\n throw new Error('TenantConfig: `agentUrls` must contain at least one URL when provided.');\n }\n return [list[0]!, list];\n }\n if (single !== undefined) {\n return [single, [single]];\n }\n throw new Error('TenantConfig: must provide either `agentUrl` or `agentUrls`.');\n}\n\n/**\n * Parse host + path prefix from an agent URL. Normalizes the path:\n * always starts with `/`; trailing `/` stripped unless the prefix is\n * itself `/` (root, the subdomain-routing case).\n */\nfunction parseHostAndPrefix(agentUrl: string): { host: string; pathPrefix: string } {\n const url = new URL(agentUrl);\n const host = url.host.toLowerCase();\n let pathPrefix = url.pathname || '/';\n if (pathPrefix.length > 1 && pathPrefix.endsWith('/')) {\n pathPrefix = pathPrefix.slice(0, -1);\n }\n return { host, pathPrefix };\n}\n\n/**\n * Does `requestPath` fall under tenant's `pathPrefix`?\n *\n * - Tenant prefix `/` matches any path (subdomain-routing case).\n * - Tenant prefix `/sales` matches `/sales`, `/sales/mcp`, `/sales/a2a`, etc.\n * Does NOT match `/sales-broadcast` (no boundary).\n *\n * **Caller contract.** `requestPath` MUST be a normalized URL pathname:\n * no query string, no fragment, no leading scheme/authority. Express's\n * `req.path`, Node's `new URL(req.url, base).pathname`, or any\n * properly-decoded equivalent. Raw `req.url` includes the query string\n * (`/sales/mcp?token=abc`) which fails the boundary check at the `?`\n * char. The framework strips query/fragment defensively (see\n * `stripQueryAndFragment`) but downstream URL normalization (`..`\n * resolution, percent-decoding) is the caller's responsibility.\n */\nfunction pathPrefixMatches(pathPrefix: string, requestPath: string): boolean {\n if (pathPrefix === '/') return true;\n if (!requestPath.startsWith(pathPrefix)) return false;\n // Boundary check: char immediately after the prefix must be `/` or\n // end-of-string. Prevents `/sales` from matching `/sales-broadcast`.\n const next = requestPath.charAt(pathPrefix.length);\n return next === '' || next === '/';\n}\n\n/**\n * Defensive normalization for callers who hand us `req.url` (raw HTTP\n * path with query / fragment). Strips `?...` and `#...` so the boundary\n * check in `pathPrefixMatches` works. Idempotent on already-normalized\n * paths.\n */\nfunction stripQueryAndFragment(pathname: string): string {\n const queryIdx = pathname.indexOf('?');\n const fragmentIdx = pathname.indexOf('#');\n let cut = pathname.length;\n if (queryIdx !== -1 && queryIdx < cut) cut = queryIdx;\n if (fragmentIdx !== -1 && fragmentIdx < cut) cut = fragmentIdx;\n return cut === pathname.length ? pathname : pathname.slice(0, cut);\n}\n\nexport function createTenantRegistry(opts: TenantRegistryOptions): TenantRegistry {\n const validator = opts.jwksValidator ?? createDefaultJwksValidator();\n const autoValidate = opts.autoValidate ?? true;\n // One-shot footgun guard: developers reaching for `autoValidate: false`\n // typically expect \"skip the validation cost,\" but the actual semantics\n // are \"every tenant lands in `pending` and `resolveByRequest` silently\n // refuses traffic until the operator calls `recheck()` on each one.\"\n // That divergence is hard to debug without a clue. Surface it once at\n // construction so the test/dev path stays usable but the production\n // misuse is visible.\n if (opts.autoValidate === false) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[adcp] TenantRegistry created with autoValidate: false — every register() lands tenants in 'pending' \" +\n 'health and resolveByRequest will refuse all traffic until you call recheck(tenantId) on each one. ' +\n 'If you wanted to skip validation cost, REMOVE the flag — the default (true) validates in the ' +\n 'background and traffic is served as soon as the first validation succeeds. autoValidate: false ' +\n 'only suits tests that drive recheck() manually.'\n );\n }\n const tenants = new Map<string, TenantEntry>();\n\n function buildServer(config: TenantConfig): DecisioningAdcpServer {\n const merged: CreateAdcpServerFromPlatformOptions = {\n ...opts.defaultServerOptions,\n ...config.serverOptions,\n };\n // Auto-wire `signingKey` into webhook emission. Adopters set the key\n // once on TenantConfig and outbound webhook deliveries are RFC\n // 9421-signed by default. Skip when:\n // - signingKey is omitted (3.x unsigned path), or\n // - the adopter has already wired their own webhook signer on\n // serverOptions.webhooks (explicit config wins — adopters with\n // KMS-backed signing or distinct webhook keys per tenant pass\n // through unaffected).\n // Strict on `adcp_use`: the JWK MUST carry `adcp_use:\n // \"webhook-signing\"` per AdCP key-purpose discriminator (adcp#2423).\n // Throws at register() time with a remediation-pointing error rather\n // than silently no-op'ing the auto-wire.\n if (\n config.signingKey &&\n merged.webhooks?.signerKey === undefined &&\n merged.webhooks?.signerProvider === undefined\n ) {\n assertWebhookSigningUse(config.signingKey);\n merged.webhooks = {\n ...(merged.webhooks ?? {}),\n signerKey: tenantKeyToSignerKey(config.signingKey),\n };\n }\n return createAdcpServerFromPlatform(config.platform, merged);\n }\n\n async function runValidation(tenantId: string): Promise<TenantStatus> {\n const entry = tenants.get(tenantId);\n if (!entry) {\n throw new Error(`runValidation: tenant '${tenantId}' not registered`);\n }\n const [canonicalUrl, allUrls] = resolveTenantUrls(entry.config);\n // Unsigned tenant — adopter chose to ship without signing in 3.x.\n // Skip JWKS validation entirely; tenant goes straight to healthy.\n // `buildServer` short-circuits the webhook auto-wire on the same\n // condition (signingKey is undefined), so the tenant emits unsigned\n // webhooks too — consistent posture across JWKS and signing.\n if (!entry.config.signingKey) {\n const status: TenantStatus = {\n tenantId,\n agentUrl: canonicalUrl,\n agentUrls: allUrls,\n health: 'healthy',\n reason: 'unsigned (no signingKey)',\n lastCheckedAt: new Date().toISOString(),\n };\n entry.status = status;\n return status;\n }\n // Multi-URL tenants validate every URL independently. Aliases share the\n // signing key; if an alias publishes a brand.json that doesn't include\n // the key (DNS hijack, operator misconfig, stale mirror), traffic to\n // that alias would receive responses no buyer can verify. Aggregate\n // failures: tenant is healthy iff ALL URLs validate; first permanent\n // failure → disabled; transient-only → pending/unverified per existing\n // policy. The explicit `jwksUrl` override applies to all URLs (the\n // documented contract for sub-routed deployments).\n const perUrlResults: Array<{ url: string; res: JwksValidationResult }> = [];\n for (const url of allUrls) {\n let res: JwksValidationResult;\n try {\n res = await validator.validate({\n agentUrl: url,\n ...(entry.config.jwksUrl !== undefined && { jwksUrl: entry.config.jwksUrl }),\n signingKey: entry.config.signingKey,\n });\n } catch (err) {\n res = {\n ok: false,\n recovery: 'transient',\n reason: redactCredentialPatterns(\n `validator threw on '${url}': ${err instanceof Error ? err.message : String(err)}`\n ) as string,\n };\n }\n perUrlResults.push({ url, res });\n // First permanent failure short-circuits — no point hitting the rest;\n // the tenant is going to disabled regardless.\n if (!res.ok && res.recovery === 'permanent') break;\n }\n // Aggregate: pick the worst outcome across the urls we validated.\n let result: JwksValidationResult;\n const firstPermanent = perUrlResults.find(r => !r.res.ok && r.res.recovery === 'permanent');\n const firstTransient = perUrlResults.find(r => !r.res.ok && r.res.recovery === 'transient');\n if (firstPermanent) {\n result = {\n ok: false,\n recovery: 'permanent',\n reason: `${firstPermanent.url}: ${firstPermanent.res.reason}`,\n };\n } else if (firstTransient) {\n result = {\n ok: false,\n recovery: 'transient',\n reason: `${firstTransient.url}: ${firstTransient.res.reason}`,\n };\n } else {\n result = { ok: true };\n }\n const now = new Date().toISOString();\n const wasFirstValidation = entry.status.health === 'pending';\n const baseStatus = { tenantId, agentUrl: canonicalUrl, agentUrls: allUrls, lastCheckedAt: now };\n let status: TenantStatus;\n if (result.ok) {\n status = { ...baseStatus, health: 'healthy' };\n } else if (result.recovery === 'transient') {\n // Transient failure on FIRST validation → stay `pending` (refuse\n // traffic). Transient failure AFTER first success → `unverified`\n // (graceful degradation — the tenant's known good).\n status = {\n ...baseStatus,\n health: wasFirstValidation ? 'pending' : 'unverified',\n reason: result.reason,\n };\n } else {\n // Permanent failure → `disabled` regardless of prior state. The\n // signing-key material doesn't match what's published; refusing\n // traffic is the safe default.\n status = { ...baseStatus, health: 'disabled', reason: result.reason };\n }\n entry.status = status;\n return status;\n }\n\n return {\n register<P extends DecisioningPlatform>(\n tenantId: string,\n config: TenantConfig<P>,\n opts?: { awaitFirstValidation?: boolean }\n ): Promise<TenantStatus> | void {\n if (tenants.has(tenantId)) {\n throw new Error(`tenant '${tenantId}' already registered; unregister first`);\n }\n const [canonicalUrl, allUrls] = resolveTenantUrls(config as unknown as TenantConfig);\n const routes = allUrls.map(url => parseHostAndPrefix(url));\n // Reject overlapping (host, pathPrefix) routes against already-registered\n // tenants. Without this, two tenants can claim the same alias host\n // silently — `resolveByRequest` picks the first-inserted (deterministic\n // per-process via Map insertion order, but cross-process flaky on\n // restart-order changes). Surface the collision now rather than\n // discover it in production. Round-1 expert review (security-medium).\n for (const route of routes) {\n for (const [otherId, otherEntry] of tenants) {\n for (const otherRoute of otherEntry.routes) {\n if (otherRoute.host === route.host && otherRoute.pathPrefix === route.pathPrefix) {\n throw new Error(\n `tenant '${tenantId}' route ${route.host}${route.pathPrefix} collides with tenant '${otherId}'; ` +\n `register them under distinct hosts or path prefixes`\n );\n }\n }\n }\n }\n const server = buildServer(config as unknown as TenantConfig);\n const initialStatus: TenantStatus = {\n tenantId,\n agentUrl: canonicalUrl,\n agentUrls: allUrls,\n // `pending` (NOT `unverified`) — first validation hasn't run.\n // resolveByHost refuses traffic until validation succeeds at\n // least once. Closes the register-then-serve race window.\n health: 'pending',\n reason: 'awaiting initial JWKS validation',\n lastCheckedAt: new Date().toISOString(),\n };\n const entry: TenantEntry = {\n config: config as unknown as TenantConfig,\n server,\n status: initialStatus,\n routes,\n };\n tenants.set(tenantId, entry);\n // Operability: log when an explicit jwksUrl points somewhere\n // OTHER than the spec-canonical agentUrl-relative location. An\n // admin who pasted a typo into config gets a visible audit trail\n // before the tenant goes live; a sub-routed deployment that\n // intentionally overrode sees the configured URL confirmed.\n // One-shot per register() — not per validate() — so periodic\n // rechecks don't spam logs.\n if (config.jwksUrl && config.jwksUrl.length > 0) {\n let canonical: string;\n try {\n canonical = new URL('/.well-known/brand.json', canonicalUrl).toString();\n } catch {\n canonical = '<invalid agentUrl>';\n }\n if (config.jwksUrl !== canonical) {\n // eslint-disable-next-line no-console\n console.info(\n `[adcp] tenant '${tenantId}' jwksUrl override: '${config.jwksUrl}' (spec-canonical from agentUrl: '${canonical}')`\n );\n }\n }\n if (!autoValidate) return;\n const validation = runValidation(tenantId);\n entry.pending = validation;\n // Always clear pending on settle so subsequent recheck() doesn't\n // dedupe against a settled promise.\n validation.finally(() => {\n if (entry.pending === validation) entry.pending = undefined;\n });\n if (opts?.awaitFirstValidation) {\n return validation;\n }\n // Background fire — log throws so they don't surface as\n // UnhandledPromiseRejection. (runValidation now catches inside;\n // belt-and-suspenders.)\n validation.catch((err: unknown) => {\n // eslint-disable-next-line no-console\n console.warn(`[adcp] tenant '${tenantId}' validation threw:`, err);\n });\n },\n\n unregister(tenantId: string): void {\n tenants.delete(tenantId);\n },\n\n resolveByHost(host: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null {\n // Convenience for subdomain-routed deployments — request path is\n // implicitly `/`, which only matches root-prefix tenants.\n return this.resolveByRequest(host, '/');\n },\n\n resolveByRequest(\n host: string,\n pathname: string\n ): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null {\n const lowered = host.toLowerCase();\n // Strip query/fragment defensively — adopters wiring `req.url`\n // (Node raw HTTP) instead of `req.path` (Express normalized)\n // would otherwise fail the boundary check on `/sales/mcp?x=1`.\n const cleanPath = stripQueryAndFragment(pathname);\n let best: { tenantId: string; entry: TenantEntry; prefixLength: number } | null = null;\n for (const [tenantId, entry] of tenants) {\n // Refuse traffic for pending (first validation hasn't succeeded)\n // and disabled (permanent validation failure). `unverified` —\n // previously healthy, latest recheck failed transiently — still\n // resolves; operators choose graceful degradation here.\n if (entry.status.health === 'pending' || entry.status.health === 'disabled') continue;\n // Multi-URL tenants register one route per `agentUrls[]` entry.\n // Longest-prefix match across ALL routes on ALL tenants wins —\n // a tenant with `/sales-broadcast` on alias-host beats a tenant\n // with `/sales` on canonical-host for `/sales-broadcast/mcp`.\n for (const route of entry.routes) {\n if (route.host !== lowered) continue;\n if (!pathPrefixMatches(route.pathPrefix, cleanPath)) continue;\n const prefixLength = route.pathPrefix === '/' ? 0 : route.pathPrefix.length;\n if (best === null || prefixLength > best.prefixLength) {\n best = { tenantId, entry, prefixLength };\n }\n }\n }\n if (best === null) return null;\n return { tenantId: best.tenantId, config: best.entry.config, server: best.entry.server };\n },\n\n get(tenantId: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null {\n const entry = tenants.get(tenantId);\n if (!entry) return null;\n // Same health gate as resolveByRequest — pending tenants haven't\n // been validated yet, disabled tenants permanently failed.\n // unverified (post-healthy transient failure) resolves normally.\n if (entry.status.health === 'pending' || entry.status.health === 'disabled') return null;\n return { tenantId, config: entry.config, server: entry.server };\n },\n\n getStatus(tenantId: string): TenantStatus | null {\n const entry = tenants.get(tenantId);\n return entry?.status ?? null;\n },\n\n list(): readonly TenantStatus[] {\n return Array.from(tenants.values()).map(e => e.status);\n },\n\n async recheck(tenantId: string): Promise<TenantStatus> {\n const entry = tenants.get(tenantId);\n if (!entry) {\n throw new Error(`recheck: tenant '${tenantId}' not registered`);\n }\n // Dedupe concurrent rechecks against the same tenant.\n if (entry.pending) {\n try {\n await entry.pending;\n } catch {\n // ignore — fall through to fresh recheck\n }\n }\n entry.pending = runValidation(tenantId);\n try {\n return await entry.pending;\n } finally {\n entry.pending = undefined;\n }\n },\n };\n}\n"],"mappings":"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","names":["status","opts"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/server/decisioning/tenant-registry.ts"],"sourcesContent":["/**\n * TenantRegistry — multi-tenant deployment helper for the v6.0 decisioning\n * runtime.\n *\n * Holds a map of `agentUrl → TenantConfig` and tracks per-tenant health.\n * Composes with the existing `serve()` host-routing surface: the registry\n * returns an `AdcpServer` factory that dispatches based on `ctx.host`.\n *\n * **Three health states**, never startup-fatal:\n *\n * - `healthy` — JWKS validated, accepting traffic normally.\n * - `unverified` — validation hasn't completed (server starting, or\n * JWKS unreachable transiently). Tenant accepts traffic with a\n * warning header; framework periodically re-validates.\n * - `disabled` — validation failed deterministically (signing key not\n * in published JWKS, brand.json malformed). Tenant returns\n * `SERVICE_UNAVAILABLE` until an admin recheck succeeds.\n *\n * One bad tenant doesn't take down others — health is per-tenant, the\n * other tenants keep serving.\n *\n * Status: Preview / 6.0.\n *\n * @public\n */\n\nimport type { DecisioningPlatform, RequiredPlatformsFor, RequiredCapabilitiesFor } from './platform';\nimport type { DecisioningAdcpServer, CreateAdcpServerFromPlatformOptions } from './runtime/from-platform';\nimport { createAdcpServerFromPlatform } from './runtime/from-platform';\nimport type { SignerKey } from '../../signing/signer';\nimport type { AdcpJsonWebKey } from '../../signing/types';\nimport { redactCredentialPatterns } from '../redact';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface TenantSigningKey {\n /** Stable key identifier — appears in the `Signature-Input` header. */\n keyId: string;\n /**\n * JWK form of the public key. MUST appear in the JWKS at\n * `{agentUrl}/.well-known/brand.json` for the tenant to validate.\n *\n * **`adcp_use` requirement.** The JWK MUST carry\n * `adcp_use: \"webhook-signing\"` for the registry to auto-wire this\n * key into outbound webhook signing. Per AdCP, request-signing and\n * webhook-signing keys MUST be distinct (key-purpose discriminator,\n * adcp#2423) — a key intended for inbound request-signature\n * verification CANNOT double as a webhook-signing key. If you have\n * both purposes, register two tenants OR wire the request-signing\n * verifier separately on `serverOptions.signedRequests` and put only\n * the webhook-signing key on `signingKey`.\n *\n * Set with: `publicJwk: { ...jwk, adcp_use: 'webhook-signing' }`.\n * `createSelfSignedTenantKey()` sets it for you.\n */\n publicJwk: JsonWebKey;\n /**\n * Private JWK used to sign outbound webhooks (RFC 9421). MUST carry\n * `adcp_use: \"webhook-signing\"`. The registry's `buildServer` plumbs\n * this into `serverOptions.webhooks.signerKey` automatically — set\n * the key once on `signingKey` and outbound webhook deliveries are\n * RFC 9421-signed by default. Adopters who want a different webhook\n * key (or don't want auto-wiring) explicitly set\n * `serverOptions.webhooks.signerKey` / `signerProvider`; the explicit\n * config wins and auto-wiring is skipped.\n */\n privateJwk: JsonWebKey;\n}\n\nexport interface TenantConfig<P extends DecisioningPlatform = DecisioningPlatform> {\n /**\n * Public URL the tenant accepts traffic on (e.g.,\n * `https://acme-tv.example.com`). Used for host-route matching and —\n * unless `jwksUrl` overrides — as the JWKS fetch base (the default\n * validator computes `{host}/.well-known/brand.json` from this URL).\n *\n * For deployments that accept traffic on multiple URLs simultaneously\n * (DNS cutover, vanity domains, internal + public hostname), use\n * {@link agentUrls} instead — `agentUrl` is the single-URL convenience\n * form. When both are set, `agentUrls` wins and `agentUrl` is ignored.\n */\n agentUrl?: string;\n /**\n * Multiple public URLs this tenant accepts traffic on. Use for cutover\n * windows where `old.example.com/mcp` and `new.example.com/mcp` must\n * both resolve to the same tenant for a window before DNS or buyer\n * caches catch up. The first URL is the **canonical** one — JWKS\n * validation uses it (and any `jwksUrl` override applies to all URLs).\n * Additional URLs are aliases; they share the same brand and signing\n * key.\n *\n * Adopters with two truly distinct brands serving from one platform\n * should register separate tenants — each brand has its own JWKS and\n * signing identity. `agentUrls` is for the single-brand-multiple-URLs\n * case.\n *\n * MUST contain at least one URL when set. Exactly one of `agentUrl` or\n * `agentUrls` must be provided; supplying both is a register() error.\n */\n agentUrls?: string[];\n /**\n * Override the JWKS fetch URL for this tenant. Use this when the\n * tenant's brand.json doesn't sit at the host root — i.e., a single\n * host serves multiple agents under path prefixes\n * (`https://shared.example.com/api/agent-a`,\n * `https://shared.example.com/api/agent-b`) and each prefix has its\n * own brand identity. Without this override, the default validator\n * resolves `/.well-known/brand.json` against the host root, which\n * collapses both agents onto the same brand.\n *\n * Spec convention is host-root, so the override is only needed for\n * sub-routed multi-tenant deployments. Custom validators that take a\n * different shape entirely (e.g., reading from a vault) read this\n * field via the `jwksUrl` argument on `JwksValidator.validate`.\n */\n jwksUrl?: string;\n /**\n * Tenant's webhook-signing identity (RFC 9421). **Optional in 3.x;\n * mandated in 4.0.**\n *\n * **Scope is webhooks only.** AdCP 3.x defines exactly one\n * outbound seller-→buyer signing surface: webhook deliveries. The\n * synchronous tools/call reply is **not** signed at the body level\n * — TLS provides session-scoped integrity for the immediate response,\n * and signed webhooks carry durable at-rest integrity for async\n * artifacts. This is the deliberate two-surface design, not a\n * coverage gap; see `docs/building/understanding/security-model.mdx`\n * § \"What gets signed — and what doesn't\" (adcp#3737, resolved by\n * adcp#3742). Adopters who need attestable artifacts for\n * synchronous flows structure the tool to emit a signed webhook\n * (the \"request-the-webhook\" pattern in the same doc).\n *\n * When set, the registry does two things:\n *\n * 1. **JWKS validation.** Confirms `publicJwk` appears in the\n * tenant's published JWKS at `{agentUrl}/.well-known/brand.json`\n * (or `jwksUrl` if overridden) before transitioning the tenant\n * to `healthy`.\n * 2. **Webhook auto-wire.** Plumbs the privateJwk into\n * `serverOptions.webhooks.signerKey` automatically, so outbound\n * webhook deliveries are signed without the adopter wiring the\n * key twice. Strict on `adcp_use: \"webhook-signing\"` per AdCP\n * key-purpose discriminator (adcp#2423). Adopters wiring a\n * KMS-backed signer or a distinct webhook key per tenant set\n * `serverOptions.webhooks.signerKey` / `signerProvider`\n * explicitly — the explicit config wins and auto-wiring is\n * skipped.\n *\n * When omitted, both behaviors short-circuit: JWKS validation is\n * skipped, the tenant transitions directly from `pending` to\n * `healthy` on register() with `reason: 'unsigned (no signingKey)'`,\n * and outbound webhooks are emitted unsigned (or, in 4.0, would be\n * a registration error). AdCP 3.x treats signing as optional, so\n * adopters spiking the SDK before standing up KMS or publishing\n * brand.json can ship without signing material. Buyers MUST NOT\n * break when an agent doesn't sign in 3.x — that's covered by the\n * \"tolerate Signature headers\" baseline regardless of whether the\n * agent itself signs.\n *\n * For local dev with signing enabled, pair `createSelfSignedTenantKey()`\n * (generates an Ed25519 keypair already tagged\n * `adcp_use: \"webhook-signing\"`) with `createNoopJwksValidator()`\n * (skips the brand.json roundtrip in dev/test). Production adopters\n * keep the default validator and publish the public half via\n * brand.json. See `docs/guides/SIGNING-GUIDE.md` § \"Self-signed dev\n * path\" for the worked recipe.\n */\n signingKey?: TenantSigningKey;\n /** The DecisioningPlatform impl for this tenant. */\n platform: P &\n RequiredPlatformsFor<P['capabilities']['specialisms'][number]> &\n RequiredCapabilitiesFor<P['capabilities']['specialisms'][number]>;\n /** Display label for admin / logs. Optional. */\n label?: string;\n /** Per-tenant `createAdcpServerFromPlatform` options override. */\n serverOptions?: Partial<CreateAdcpServerFromPlatformOptions>;\n}\n\n/**\n * Tenant health lifecycle:\n * - `'pending'` — first JWKS validation hasn't succeeded yet. Brand-new\n * tenants land here. `resolveByHost` REFUSES TRAFFIC for `pending`\n * tenants — host transport should respond 503 + Retry-After. This\n * closes the register-then-serve race window where a tenant\n * registered with a wrong signing key would have served signed\n * responses no buyer can verify until the first refresh detected\n * the mismatch (60+ seconds).\n * - `'healthy'` — JWKS validation has succeeded at least once.\n * Periodic rechecks confirm.\n * - `'unverified'` — was previously `healthy`; latest recheck failed\n * transiently (network error, 5xx, etc.). `resolveByHost` still\n * returns the tenant — graceful degradation for known-good tenants\n * when brand.json is briefly unreachable. Distinct from `'pending'`\n * where we've never validated.\n * - `'disabled'` — permanent validation failure (key not in published\n * JWKS, brand.json malformed, etc.). `resolveByHost` returns null;\n * operator must fix and call `recheck()` to revive.\n */\nexport type TenantHealth = 'pending' | 'healthy' | 'unverified' | 'disabled';\n\nexport interface TenantStatus {\n tenantId: string;\n /**\n * Canonical URL — the first entry in the tenant's `agentUrls` (or the\n * single `agentUrl` value). JWKS validates against this URL; ops\n * dashboards page on this field for the primary identity.\n */\n agentUrl: string;\n /**\n * Full URL list when the tenant was registered with `agentUrls[]`.\n * Single-URL tenants get a one-element array. Ops dashboards iterate\n * this field to show every URL serving the tenant; required so admins\n * can detect stale aliases or accidental host overlap with another\n * tenant (#1097 follow-up — collision check below errors at register\n * time, but the operator still wants visibility into what's live).\n */\n agentUrls: readonly string[];\n health: TenantHealth;\n /** Reason for unverified/disabled state. */\n reason?: string;\n /** ISO timestamp of the last health check. */\n lastCheckedAt: string;\n}\n\nexport interface JwksValidationResult {\n ok: boolean;\n /** Recovery classification when `ok === false`. */\n recovery?: 'transient' | 'permanent';\n reason?: string;\n}\n\nexport interface JwksValidator {\n /**\n * Validate that the tenant's signing key appears in the published JWKS.\n *\n * - `agentUrl` is the tenant's public URL (used for host-relative URL\n * computation by the default validator).\n * - `jwksUrl` is the explicit JWKS fetch URL when the tenant's\n * `TenantConfig.jwksUrl` was set; absent for the spec-default\n * host-root resolution.\n * - `signingKey` is what to look for in the published JWKS.\n */\n validate(opts: { agentUrl: string; jwksUrl?: string; signingKey: TenantSigningKey }): Promise<JwksValidationResult>;\n}\n\nexport interface TenantRegistryOptions {\n /**\n * JWKS validator. Defaults to a fetch-based validator that hits\n * `{agentUrl}/.well-known/brand.json`. Tests can pass a fake.\n */\n jwksValidator?: JwksValidator;\n /**\n * Per-tenant `createAdcpServerFromPlatform` options applied to every\n * tenant unless overridden by `TenantConfig.serverOptions`.\n */\n defaultServerOptions: CreateAdcpServerFromPlatformOptions;\n /**\n * Auto-validate tenants when they're registered. Defaults to `true`.\n *\n * Disable ONLY for tests that drive validation manually via `recheck` —\n * with this off, every `register()` leaves the tenant in `'pending'`\n * health and `resolveByRequest` silently refuses traffic until the\n * caller recheck()s each tenant. Production deployments should leave\n * this at the default; the framework emits a one-shot console.warn at\n * registry construction when `autoValidate: false` is set, to surface\n * the \"all traffic blocked\" consequence.\n */\n autoValidate?: boolean;\n}\n\nexport interface TenantRegistry {\n /**\n * Register a tenant. Tenant lands in `'pending'` health initially —\n * `resolveByHost` refuses traffic until the first JWKS validation\n * succeeds. Pass `{ awaitFirstValidation: true }` to block on the\n * synchronous validation outcome (returns the resulting status; throws\n * if registration is incompatible). Without the flag, register fires\n * validation in the background and returns immediately; the caller\n * polls `getStatus(tenantId).health === 'healthy'` if needed.\n *\n * **Admin-API security.** This method is the privileged surface — any\n * caller invoking `register` can introduce a tenant that will sign\n * outbound webhooks. Hosts wiring an HTTP/RPC endpoint in front of\n * `register` MUST gate it with operator-level auth (mTLS, signed\n * admin tokens, network ACL). The framework doesn't ship admin-HTTP\n * scaffolding because the right auth shape varies by deployment;\n * adopters who want a vetted shape can layer Express middleware\n * around their `registry.register(...)` route handler.\n *\n * **Make construction errors observable.** `register` builds the\n * inner `createAdcpServerFromPlatform()` synchronously — config\n * errors (`customTools` collision with framework-promoted tools,\n * missing required handlers, invalid `signingKey` shape) fire\n * inside this call. The cleanest path is eager registration at\n * process boot, where those errors fail the deploy visibly. Lazy\n * shapes are legitimate (autoscale replicas avoiding JWKS-storm,\n * multi-tenant SaaS with mutable tenant tables, serverless\n * warm-start) — if you defer, catch the throw at the registration\n * site and route it through your error pipeline. Otherwise the\n * host framework's default error handler renders the throw as\n * HTTP 500 HTML, MCP clients (correctly) classify the body as a\n * non-MCP response, and the failure surfaces as `discovery_failed`\n * on every probe — looking like a transport bug instead of the\n * config bug it is.\n */\n register<P extends DecisioningPlatform>(\n tenantId: string,\n config: TenantConfig<P>,\n opts?: { awaitFirstValidation?: boolean }\n ): Promise<TenantStatus> | void;\n unregister(tenantId: string): void;\n /**\n * Resolve a tenant by host alone (the lowercased authority of the\n * request). Convenience wrapper around `resolveByRequest(host, '/')` —\n * works for the canonical subdomain-routing pattern (e.g.,\n * `sales.training.example.com`) where each tenant has its own host.\n *\n * For path-based routing (`training.example.com/sales`,\n * `training.example.com/creative` on a single host), use\n * `resolveByRequest(host, pathname)` instead.\n *\n * Returns null if no tenant matches, the tenant is `pending` (first\n * validation hasn't succeeded), or the tenant is `disabled`.\n * `unverified` tenants resolve normally — graceful degradation for\n * known-good tenants whose latest recheck failed transiently.\n */\n resolveByHost(host: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null;\n /**\n * Resolve a tenant by host AND request path. The framework matches\n * tenants whose `agentUrl` host equals the request host AND whose\n * `agentUrl` path is a prefix of the request `pathname`. When multiple\n * tenants share a host, the LONGEST matching path prefix wins (so\n * `/sales-broadcast` is preferred over `/sales` for a request to\n * `/sales-broadcast/mcp`).\n *\n * Use this for path-routed multi-tenant deployments where adopters\n * don't want per-tenant subdomain DNS / TLS overhead. Each tenant's\n * `agentUrl` carries the path: `https://training.example.com/sales`.\n * Subdomain-routed tenants (`https://sales.training.example.com`)\n * keep working — their path prefix is `/`, which matches any pathname.\n *\n * Returns null on no match, `pending`, or `disabled`. `unverified`\n * tenants resolve normally (graceful degradation).\n */\n resolveByRequest(\n host: string,\n pathname: string\n ): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null;\n /**\n * Direct tenant lookup by `tenantId`. Use this when your route layer\n * has already resolved the tenant from session / path / header and\n * needs the registry's `{ config, server }` entry without re-running\n * URL parsing. Same `pending` / `disabled` health gate as the\n * `resolveByXxx` helpers — returns `null` when the tenant isn't\n * accepting traffic so callers don't have to re-check status.\n *\n * Adopters who decouple tenant lookup from URL routing (e.g., bind\n * tenantId at their own Express middleware before calling into the\n * registry) should prefer this over `resolveByRequest(host, '/<id>/mcp')`\n * tricks.\n */\n get(tenantId: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null;\n getStatus(tenantId: string): TenantStatus | null;\n list(): readonly TenantStatus[];\n /**\n * Trigger a JWKS recheck for the tenant. Admin UI calls this after\n * fixing a brand.json mismatch.\n */\n recheck(tenantId: string): Promise<TenantStatus>;\n}\n\n// ---------------------------------------------------------------------------\n// Default JWKS validator\n// ---------------------------------------------------------------------------\n\n/**\n * Default JWKS validator. Fetches `{agentUrl}/.well-known/brand.json` and\n * checks that `signingKey.publicJwk` appears in the JWKS keys array.\n *\n * Uses the global `fetch`. Network errors classify as `transient`;\n * 4xx / parse-error / key-not-in-JWKS classify as `permanent`.\n */\nexport function createDefaultJwksValidator(opts?: { fetchImpl?: typeof fetch; timeoutMs?: number }): JwksValidator {\n const fetchImpl = opts?.fetchImpl ?? fetch;\n // 10-second default timeout. A slow brand.json server (or a malicious\n // one dribbling bytes) would otherwise pin the tenant in `pending`\n // for the lifetime of the fetch — and `register({ awaitFirstValidation: true })`\n // would block the booting host indefinitely. Adopters with strict SLAs\n // pass a tighter value; adopters fronting brand.json behind a slow\n // CDN can extend it. Round-6 Sec-M1.\n const timeoutMs = opts?.timeoutMs ?? 10_000;\n return {\n async validate({ agentUrl, jwksUrl, signingKey }): Promise<JwksValidationResult> {\n // Explicit jwksUrl wins (sub-routed deployments where brand.json\n // lives under a path prefix). Default falls back to the spec-\n // canonical host-root location — `new URL('/.well-known/brand.json',\n // agentUrl)` REPLACES the path because the second arg starts with\n // '/'. That's correct for host-level brand identity (one brand per\n // host), wrong for multi-tenant sub-routed deployments — adopters\n // there set `TenantConfig.jwksUrl` to point at the per-tenant\n // brand.json.\n // Reject empty-string jwksUrl as defense-in-depth — `??` only\n // catches null/undefined, so an empty string would otherwise\n // reach `fetch('')` and produce an opaque error. Treat empty as\n // \"use default.\"\n const url = jwksUrl && jwksUrl.length > 0 ? jwksUrl : new URL('/.well-known/brand.json', agentUrl).toString();\n let response: Response;\n try {\n response = await fetchImpl(url, { method: 'GET', signal: AbortSignal.timeout(timeoutMs) });\n } catch (err) {\n return {\n ok: false,\n recovery: 'transient',\n // Redact credential patterns from the upstream error message\n // before it surfaces on the admin-router wire (#1330). A fetch\n // failure can echo basic-auth-bearing URLs or upstream-library\n // diagnostics that include credential bytes.\n reason: redactCredentialPatterns(\n `JWKS fetch failed: ${err instanceof Error ? err.message : String(err)}`\n ) as string,\n };\n }\n if (!response.ok) {\n return {\n ok: false,\n recovery: response.status >= 500 ? 'transient' : 'permanent',\n reason: `JWKS fetch returned ${response.status} ${response.statusText}`,\n };\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n return {\n ok: false,\n recovery: 'permanent',\n reason: redactCredentialPatterns(\n `JWKS body not JSON: ${err instanceof Error ? err.message : String(err)}`\n ) as string,\n };\n }\n const jwks = (body as { jwks?: { keys?: unknown[] } }).jwks;\n if (!jwks || !Array.isArray(jwks.keys)) {\n return {\n ok: false,\n recovery: 'permanent',\n reason: '`brand.json` has no `jwks.keys` array',\n };\n }\n const matched = jwks.keys.find(k => isMatchingKey(k, signingKey.publicJwk, signingKey.keyId));\n if (!matched) {\n return {\n ok: false,\n recovery: 'permanent',\n reason: `signingKey.keyId='${signingKey.keyId}' not present in published JWKS`,\n };\n }\n return { ok: true };\n },\n };\n}\n\nfunction isMatchingKey(jwk: unknown, expected: JsonWebKey, expectedKid: string): boolean {\n if (!jwk || typeof jwk !== 'object') return false;\n const k = jwk as Record<string, unknown>;\n // Both `kid` AND public-key material must match. `kid` alone is NOT\n // sufficient — RFC 7517 § 4.5 explicitly notes that `kid` is just a\n // hint and clashes are possible. An attacker who can publish a JWKS\n // with a colliding `kid` would otherwise bypass verification.\n if (typeof k.kid === 'string' && k.kid !== expectedKid) return false;\n if (k.kty !== expected.kty) return false;\n // Structural equality on the public-half fields (n/e for RSA, x/y for\n // EC, x for OKP). A full JWK thumbprint comparison (RFC 7638) would be\n // more robust but this catches the typical \"wrong key\" case without\n // pulling crypto deps in. Asymmetric pubkeys are public — comparing\n // the components here doesn't need constant-time semantics.\n if (expected.kty === 'RSA') {\n return k.n === expected.n && k.e === expected.e;\n }\n if (expected.kty === 'EC') {\n return k.crv === expected.crv && k.x === expected.x && k.y === expected.y;\n }\n if (expected.kty === 'OKP') {\n return k.crv === expected.crv && k.x === expected.x;\n }\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Self-signed key + no-op validator helpers (3.x adoption ergonomics)\n// ---------------------------------------------------------------------------\n\n/**\n * Generate an Ed25519 keypair suitable for `TenantConfig.signingKey`.\n *\n * Convenience for adopters spiking the SDK before standing up KMS or\n * publishing brand.json. AdCP 3.x treats request signing as optional, so\n * `TenantConfig.signingKey` is itself optional — but adopters who DO\n * want to exercise the signing path (storyboards, signed-requests\n * grader, end-to-end tests) need a working key without the operational\n * lift of a real KMS.\n *\n * Pair with `createNoopJwksValidator()` to skip the brand.json\n * roundtrip in dev/test, OR publish the returned `publicJwk` at\n * `{agentUrl}/.well-known/brand.json` (under `jwks.keys[]`) and use the\n * default validator unchanged.\n *\n * **Production**: don't generate signing material in-process. Adopt a\n * KMS-backed loader (HashiCorp Vault, AWS KMS, GCP Secret Manager) — a\n * process compromise leaks an in-memory privateJwk and the only remedy\n * is rotation across every counterparty cache.\n *\n * @param opts.keyId Optional `kid` for the key. Defaults to a\n * timestamped value (`self-signed-{ISO date}`). Stable across restarts\n * only if you pass a stable `keyId`.\n */\nexport async function createSelfSignedTenantKey(opts?: { keyId?: string }): Promise<TenantSigningKey> {\n // Lazy import — `jose` is a runtime dep, but the import-cost is real\n // and most adopters never call this. Keep the registry's hot path\n // (register / resolve) free of jose.\n const { generateKeyPair, exportJWK } = await import('jose');\n const { publicKey, privateKey } = await generateKeyPair('EdDSA', { crv: 'Ed25519', extractable: true });\n const rawPublic = (await exportJWK(publicKey)) as JsonWebKey;\n const rawPrivate = (await exportJWK(privateKey)) as JsonWebKey;\n const keyId = opts?.keyId ?? `self-signed-${new Date().toISOString().slice(0, 10)}`;\n // Tag both halves with `adcp_use: \"webhook-signing\"`. The registry's\n // auto-wire path requires this tag (assertWebhookSigningUse below);\n // setting it on the helper output means adopters get a working\n // signing posture out of the box.\n const publicJwk: JsonWebKey = { ...rawPublic, adcp_use: 'webhook-signing' } as JsonWebKey;\n const privateJwk: JsonWebKey = { ...rawPrivate, adcp_use: 'webhook-signing' } as JsonWebKey;\n return { keyId, publicJwk, privateJwk };\n}\n\n/**\n * No-op JWKS validator that always returns `{ ok: true }`. Use ONLY in\n * dev/test when you've set `signingKey` (e.g., via\n * `createSelfSignedTenantKey()`) but haven't published brand.json yet —\n * this skips the JWKS roundtrip so tenants reach `healthy` without a\n * real `/.well-known/brand.json` endpoint.\n *\n * **Refuses to construct outside `NODE_ENV` ∈ {`'test'`, `'development'`}**\n * unless the operator sets `ADCP_NOOP_JWKS_ACK=1` to explicitly\n * acknowledge the risk. Mirrors the `idempotency: 'disabled'` allowlist\n * pattern — `NODE_ENV` defaults to unset in raw Lambda / custom\n * containers / many K8s deployments, so a `=== 'production'` check\n * would no-op in exactly the environments where a silent skip-validation\n * start is most dangerous.\n *\n * The ack value MUST be the literal string `'1'`. Truthy lookalikes\n * (`'true'`, `'yes'`) intentionally don't satisfy the gate to prevent\n * copy-paste typos.\n *\n * In production, leave the registry's default validator wired and\n * publish brand.json. Or omit `signingKey` entirely (`TenantConfig`\n * makes it optional in 3.x), which skips JWKS validation without\n * needing this helper.\n */\nexport function createNoopJwksValidator(): JwksValidator {\n const env = process.env.NODE_ENV;\n const acknowledged = process.env.ADCP_NOOP_JWKS_ACK === '1';\n const isAllowlistedDevEnv = env === 'test' || env === 'development';\n if (!isAllowlistedDevEnv && !acknowledged) {\n throw new Error(\n 'createNoopJwksValidator: refuses to construct with NODE_ENV=' +\n (env === undefined ? '<unset>' : JSON.stringify(env)) +\n '. The no-op validator skips JWKS verification, so a tenant whose published brand.json does not actually ' +\n 'contain the configured signingKey would reach `healthy` and serve unverifiable signed responses. ' +\n 'The SDK only allows it under NODE_ENV=test or NODE_ENV=development by default. Either: ' +\n '(a) use the default validator (createDefaultJwksValidator) and publish a real brand.json, ' +\n '(b) omit signingKey from TenantConfig — JWKS validation is skipped entirely for unsigned tenants in 3.x, ' +\n '(c) set NODE_ENV=test or NODE_ENV=development if this is a dev-only environment, or ' +\n '(d) set ADCP_NOOP_JWKS_ACK=1 to explicitly acknowledge the risk for non-standard environments.'\n );\n }\n // eslint-disable-next-line no-console\n console.warn(\n '[adcp] createNoopJwksValidator: JWKS validation is DISABLED for this registry. ' +\n 'Tenants will reach `healthy` without a brand.json roundtrip. Use only in dev/test.'\n );\n return {\n async validate(): Promise<JwksValidationResult> {\n return { ok: true };\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Webhook auto-wire — convert TenantSigningKey to a SignerKey the framework's\n// webhook emitter understands. Strict on `adcp_use` per AdCP key-purpose\n// discriminator (adcp#2423).\n// ---------------------------------------------------------------------------\n\n/**\n * Derive the RFC 9421 signing algorithm from JWK shape. AdCP webhook\n * signing supports Ed25519 (kty=OKP, crv=Ed25519) and ECDSA P-256\n * (kty=EC, crv=P-256). Anything else throws — RSA / EC P-384 / etc.\n * are not in the AdCP signing-algorithm set.\n */\nfunction deriveSigningAlg(jwk: JsonWebKey): 'ed25519' | 'ecdsa-p256-sha256' {\n if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519') return 'ed25519';\n if (jwk.kty === 'EC' && jwk.crv === 'P-256') return 'ecdsa-p256-sha256';\n throw new Error(\n `TenantConfig.signingKey: unsupported JWK shape kty=${JSON.stringify(jwk.kty)} crv=${JSON.stringify(jwk.crv)}. ` +\n 'AdCP RFC 9421 webhook signing requires Ed25519 (kty=OKP, crv=Ed25519) or ECDSA P-256 (kty=EC, crv=P-256). ' +\n 'See `docs/guides/SIGNING-GUIDE.md` for key-generation recipes.'\n );\n}\n\n/**\n * Enforce the AdCP key-purpose discriminator on the webhook auto-wire path.\n * Webhooks are signed with the agent's `adcp_use: \"request-signing\"` key\n * (the deprecated `\"webhook-signing\"` value is still accepted —\n * adcontextprotocol/adcp#5555); any other purpose is rejected. Called only\n * when auto-wiring fires (signingKey set + serverOptions.webhooks.signerKey\n * unset); adopters who want different keys per purpose wire them explicitly\n * and bypass this check.\n */\nconst WEBHOOK_WIRE_PURPOSES = ['request-signing', 'webhook-signing'];\nfunction assertWebhookSigningUse(key: TenantSigningKey): void {\n const publicUse = (key.publicJwk as Record<string, unknown>).adcp_use;\n const privateUse = (key.privateJwk as Record<string, unknown>).adcp_use;\n if (typeof publicUse !== 'string' || !WEBHOOK_WIRE_PURPOSES.includes(publicUse)) {\n throw new Error(\n `TenantConfig.signingKey: publicJwk.adcp_use must be 'request-signing' (or the deprecated 'webhook-signing') for the registry's webhook auto-wire path. ` +\n `Got ${publicUse === undefined ? '<unset>' : JSON.stringify(publicUse)}. ` +\n 'Webhooks are signed with the request-signing key; domain separation is carried by the RFC 9421 tag, not the key purpose. ' +\n 'Either: (a) tag this key with `adcp_use: \"request-signing\"`, ' +\n '(b) mint a separate request-signing key under a distinct kid for webhook isolation, or ' +\n '(c) wire `serverOptions.webhooks.signerKey` explicitly — the explicit config bypasses auto-wiring.'\n );\n }\n if (publicUse !== privateUse) {\n throw new Error(\n `TenantConfig.signingKey: privateJwk.adcp_use must match publicJwk.adcp_use ('${publicUse}'). ` +\n `Got ${privateUse === undefined ? '<unset>' : JSON.stringify(privateUse)}.`\n );\n }\n}\n\n/**\n * Convert `TenantSigningKey` to the framework-internal `SignerKey`\n * shape consumed by the webhook emitter. Field-name shift: the\n * `TenantSigningKey` surface uses `keyId` (camelCase, adopter-facing);\n * `SignerKey` uses `keyid` (lowercase, RFC 9421 wire term).\n */\nfunction tenantKeyToSignerKey(key: TenantSigningKey): SignerKey {\n const alg = deriveSigningAlg(key.publicJwk);\n return {\n keyid: key.keyId,\n alg,\n privateKey: key.privateJwk as AdcpJsonWebKey,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\ninterface TenantEntry {\n config: TenantConfig;\n server: DecisioningAdcpServer;\n status: TenantStatus;\n /**\n * Parsed (host, pathPrefix) routes for every URL on the tenant. One\n * entry per `agentUrls` element; single-element array for the\n * single-URL case. Resolution iterates this list and picks the longest\n * matching path prefix across all hosts.\n */\n routes: ReadonlyArray<{ host: string; pathPrefix: string }>;\n /** Pending revalidation; consulted by `recheck` to dedupe in-flight work. */\n pending?: Promise<TenantStatus>;\n}\n\n/**\n * Resolve the canonical agent URL + the full URL list from a TenantConfig.\n *\n * - `agentUrls` (when set) wins. First element is canonical (used for\n * JWKS resolution and status reporting).\n * - `agentUrl` (single-URL form) maps to a one-element list.\n * - Setting both is a programmer error — refuse explicitly so the\n * ambiguity doesn't silently drop one of them.\n *\n * Returns `[canonical, allUrls]`. `allUrls` is the routing surface;\n * `canonical` is what JWKS / status reports use.\n */\nfunction resolveTenantUrls(config: TenantConfig): readonly [string, ReadonlyArray<string>] {\n const list = config.agentUrls;\n const single = config.agentUrl;\n if (list !== undefined && single !== undefined) {\n throw new Error(\n 'TenantConfig: set exactly one of `agentUrl` (single URL) or `agentUrls` (multi-URL). Setting both is ambiguous.'\n );\n }\n if (list !== undefined) {\n if (list.length === 0) {\n throw new Error('TenantConfig: `agentUrls` must contain at least one URL when provided.');\n }\n return [list[0]!, list];\n }\n if (single !== undefined) {\n return [single, [single]];\n }\n throw new Error('TenantConfig: must provide either `agentUrl` or `agentUrls`.');\n}\n\n/**\n * Parse host + path prefix from an agent URL. Normalizes the path:\n * always starts with `/`; trailing `/` stripped unless the prefix is\n * itself `/` (root, the subdomain-routing case).\n */\nfunction parseHostAndPrefix(agentUrl: string): { host: string; pathPrefix: string } {\n const url = new URL(agentUrl);\n const host = url.host.toLowerCase();\n let pathPrefix = url.pathname || '/';\n if (pathPrefix.length > 1 && pathPrefix.endsWith('/')) {\n pathPrefix = pathPrefix.slice(0, -1);\n }\n return { host, pathPrefix };\n}\n\n/**\n * Does `requestPath` fall under tenant's `pathPrefix`?\n *\n * - Tenant prefix `/` matches any path (subdomain-routing case).\n * - Tenant prefix `/sales` matches `/sales`, `/sales/mcp`, `/sales/a2a`, etc.\n * Does NOT match `/sales-broadcast` (no boundary).\n *\n * **Caller contract.** `requestPath` MUST be a normalized URL pathname:\n * no query string, no fragment, no leading scheme/authority. Express's\n * `req.path`, Node's `new URL(req.url, base).pathname`, or any\n * properly-decoded equivalent. Raw `req.url` includes the query string\n * (`/sales/mcp?token=abc`) which fails the boundary check at the `?`\n * char. The framework strips query/fragment defensively (see\n * `stripQueryAndFragment`) but downstream URL normalization (`..`\n * resolution, percent-decoding) is the caller's responsibility.\n */\nfunction pathPrefixMatches(pathPrefix: string, requestPath: string): boolean {\n if (pathPrefix === '/') return true;\n if (!requestPath.startsWith(pathPrefix)) return false;\n // Boundary check: char immediately after the prefix must be `/` or\n // end-of-string. Prevents `/sales` from matching `/sales-broadcast`.\n const next = requestPath.charAt(pathPrefix.length);\n return next === '' || next === '/';\n}\n\n/**\n * Defensive normalization for callers who hand us `req.url` (raw HTTP\n * path with query / fragment). Strips `?...` and `#...` so the boundary\n * check in `pathPrefixMatches` works. Idempotent on already-normalized\n * paths.\n */\nfunction stripQueryAndFragment(pathname: string): string {\n const queryIdx = pathname.indexOf('?');\n const fragmentIdx = pathname.indexOf('#');\n let cut = pathname.length;\n if (queryIdx !== -1 && queryIdx < cut) cut = queryIdx;\n if (fragmentIdx !== -1 && fragmentIdx < cut) cut = fragmentIdx;\n return cut === pathname.length ? pathname : pathname.slice(0, cut);\n}\n\nexport function createTenantRegistry(opts: TenantRegistryOptions): TenantRegistry {\n const validator = opts.jwksValidator ?? createDefaultJwksValidator();\n const autoValidate = opts.autoValidate ?? true;\n // One-shot footgun guard: developers reaching for `autoValidate: false`\n // typically expect \"skip the validation cost,\" but the actual semantics\n // are \"every tenant lands in `pending` and `resolveByRequest` silently\n // refuses traffic until the operator calls `recheck()` on each one.\"\n // That divergence is hard to debug without a clue. Surface it once at\n // construction so the test/dev path stays usable but the production\n // misuse is visible.\n if (opts.autoValidate === false) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[adcp] TenantRegistry created with autoValidate: false — every register() lands tenants in 'pending' \" +\n 'health and resolveByRequest will refuse all traffic until you call recheck(tenantId) on each one. ' +\n 'If you wanted to skip validation cost, REMOVE the flag — the default (true) validates in the ' +\n 'background and traffic is served as soon as the first validation succeeds. autoValidate: false ' +\n 'only suits tests that drive recheck() manually.'\n );\n }\n const tenants = new Map<string, TenantEntry>();\n\n function buildServer(config: TenantConfig): DecisioningAdcpServer {\n const merged: CreateAdcpServerFromPlatformOptions = {\n ...opts.defaultServerOptions,\n ...config.serverOptions,\n };\n // Auto-wire `signingKey` into webhook emission. Adopters set the key\n // once on TenantConfig and outbound webhook deliveries are RFC\n // 9421-signed by default. Skip when:\n // - signingKey is omitted (3.x unsigned path), or\n // - the adopter has already wired their own webhook signer on\n // serverOptions.webhooks (explicit config wins — adopters with\n // KMS-backed signing or distinct webhook keys per tenant pass\n // through unaffected).\n // Strict on `adcp_use`: the JWK MUST carry `adcp_use:\n // \"webhook-signing\"` per AdCP key-purpose discriminator (adcp#2423).\n // Throws at register() time with a remediation-pointing error rather\n // than silently no-op'ing the auto-wire.\n if (\n config.signingKey &&\n merged.webhooks?.signerKey === undefined &&\n merged.webhooks?.signerProvider === undefined\n ) {\n assertWebhookSigningUse(config.signingKey);\n merged.webhooks = {\n ...(merged.webhooks ?? {}),\n signerKey: tenantKeyToSignerKey(config.signingKey),\n };\n }\n return createAdcpServerFromPlatform(config.platform, merged);\n }\n\n async function runValidation(tenantId: string): Promise<TenantStatus> {\n const entry = tenants.get(tenantId);\n if (!entry) {\n throw new Error(`runValidation: tenant '${tenantId}' not registered`);\n }\n const [canonicalUrl, allUrls] = resolveTenantUrls(entry.config);\n // Unsigned tenant — adopter chose to ship without signing in 3.x.\n // Skip JWKS validation entirely; tenant goes straight to healthy.\n // `buildServer` short-circuits the webhook auto-wire on the same\n // condition (signingKey is undefined), so the tenant emits unsigned\n // webhooks too — consistent posture across JWKS and signing.\n if (!entry.config.signingKey) {\n const status: TenantStatus = {\n tenantId,\n agentUrl: canonicalUrl,\n agentUrls: allUrls,\n health: 'healthy',\n reason: 'unsigned (no signingKey)',\n lastCheckedAt: new Date().toISOString(),\n };\n entry.status = status;\n return status;\n }\n // Multi-URL tenants validate every URL independently. Aliases share the\n // signing key; if an alias publishes a brand.json that doesn't include\n // the key (DNS hijack, operator misconfig, stale mirror), traffic to\n // that alias would receive responses no buyer can verify. Aggregate\n // failures: tenant is healthy iff ALL URLs validate; first permanent\n // failure → disabled; transient-only → pending/unverified per existing\n // policy. The explicit `jwksUrl` override applies to all URLs (the\n // documented contract for sub-routed deployments).\n const perUrlResults: Array<{ url: string; res: JwksValidationResult }> = [];\n for (const url of allUrls) {\n let res: JwksValidationResult;\n try {\n res = await validator.validate({\n agentUrl: url,\n ...(entry.config.jwksUrl !== undefined && { jwksUrl: entry.config.jwksUrl }),\n signingKey: entry.config.signingKey,\n });\n } catch (err) {\n res = {\n ok: false,\n recovery: 'transient',\n reason: redactCredentialPatterns(\n `validator threw on '${url}': ${err instanceof Error ? err.message : String(err)}`\n ) as string,\n };\n }\n perUrlResults.push({ url, res });\n // First permanent failure short-circuits — no point hitting the rest;\n // the tenant is going to disabled regardless.\n if (!res.ok && res.recovery === 'permanent') break;\n }\n // Aggregate: pick the worst outcome across the urls we validated.\n let result: JwksValidationResult;\n const firstPermanent = perUrlResults.find(r => !r.res.ok && r.res.recovery === 'permanent');\n const firstTransient = perUrlResults.find(r => !r.res.ok && r.res.recovery === 'transient');\n if (firstPermanent) {\n result = {\n ok: false,\n recovery: 'permanent',\n reason: `${firstPermanent.url}: ${firstPermanent.res.reason}`,\n };\n } else if (firstTransient) {\n result = {\n ok: false,\n recovery: 'transient',\n reason: `${firstTransient.url}: ${firstTransient.res.reason}`,\n };\n } else {\n result = { ok: true };\n }\n const now = new Date().toISOString();\n const wasFirstValidation = entry.status.health === 'pending';\n const baseStatus = { tenantId, agentUrl: canonicalUrl, agentUrls: allUrls, lastCheckedAt: now };\n let status: TenantStatus;\n if (result.ok) {\n status = { ...baseStatus, health: 'healthy' };\n } else if (result.recovery === 'transient') {\n // Transient failure on FIRST validation → stay `pending` (refuse\n // traffic). Transient failure AFTER first success → `unverified`\n // (graceful degradation — the tenant's known good).\n status = {\n ...baseStatus,\n health: wasFirstValidation ? 'pending' : 'unverified',\n reason: result.reason,\n };\n } else {\n // Permanent failure → `disabled` regardless of prior state. The\n // signing-key material doesn't match what's published; refusing\n // traffic is the safe default.\n status = { ...baseStatus, health: 'disabled', reason: result.reason };\n }\n entry.status = status;\n return status;\n }\n\n return {\n register<P extends DecisioningPlatform>(\n tenantId: string,\n config: TenantConfig<P>,\n opts?: { awaitFirstValidation?: boolean }\n ): Promise<TenantStatus> | void {\n if (tenants.has(tenantId)) {\n throw new Error(`tenant '${tenantId}' already registered; unregister first`);\n }\n const [canonicalUrl, allUrls] = resolveTenantUrls(config as unknown as TenantConfig);\n const routes = allUrls.map(url => parseHostAndPrefix(url));\n // Reject overlapping (host, pathPrefix) routes against already-registered\n // tenants. Without this, two tenants can claim the same alias host\n // silently — `resolveByRequest` picks the first-inserted (deterministic\n // per-process via Map insertion order, but cross-process flaky on\n // restart-order changes). Surface the collision now rather than\n // discover it in production. Round-1 expert review (security-medium).\n for (const route of routes) {\n for (const [otherId, otherEntry] of tenants) {\n for (const otherRoute of otherEntry.routes) {\n if (otherRoute.host === route.host && otherRoute.pathPrefix === route.pathPrefix) {\n throw new Error(\n `tenant '${tenantId}' route ${route.host}${route.pathPrefix} collides with tenant '${otherId}'; ` +\n `register them under distinct hosts or path prefixes`\n );\n }\n }\n }\n }\n const server = buildServer(config as unknown as TenantConfig);\n const initialStatus: TenantStatus = {\n tenantId,\n agentUrl: canonicalUrl,\n agentUrls: allUrls,\n // `pending` (NOT `unverified`) — first validation hasn't run.\n // resolveByHost refuses traffic until validation succeeds at\n // least once. Closes the register-then-serve race window.\n health: 'pending',\n reason: 'awaiting initial JWKS validation',\n lastCheckedAt: new Date().toISOString(),\n };\n const entry: TenantEntry = {\n config: config as unknown as TenantConfig,\n server,\n status: initialStatus,\n routes,\n };\n tenants.set(tenantId, entry);\n // Operability: log when an explicit jwksUrl points somewhere\n // OTHER than the spec-canonical agentUrl-relative location. An\n // admin who pasted a typo into config gets a visible audit trail\n // before the tenant goes live; a sub-routed deployment that\n // intentionally overrode sees the configured URL confirmed.\n // One-shot per register() — not per validate() — so periodic\n // rechecks don't spam logs.\n if (config.jwksUrl && config.jwksUrl.length > 0) {\n let canonical: string;\n try {\n canonical = new URL('/.well-known/brand.json', canonicalUrl).toString();\n } catch {\n canonical = '<invalid agentUrl>';\n }\n if (config.jwksUrl !== canonical) {\n // eslint-disable-next-line no-console\n console.info(\n `[adcp] tenant '${tenantId}' jwksUrl override: '${config.jwksUrl}' (spec-canonical from agentUrl: '${canonical}')`\n );\n }\n }\n if (!autoValidate) return;\n const validation = runValidation(tenantId);\n entry.pending = validation;\n // Always clear pending on settle so subsequent recheck() doesn't\n // dedupe against a settled promise.\n validation.finally(() => {\n if (entry.pending === validation) entry.pending = undefined;\n });\n if (opts?.awaitFirstValidation) {\n return validation;\n }\n // Background fire — log throws so they don't surface as\n // UnhandledPromiseRejection. (runValidation now catches inside;\n // belt-and-suspenders.)\n validation.catch((err: unknown) => {\n // eslint-disable-next-line no-console\n console.warn(`[adcp] tenant '${tenantId}' validation threw:`, err);\n });\n },\n\n unregister(tenantId: string): void {\n tenants.delete(tenantId);\n },\n\n resolveByHost(host: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null {\n // Convenience for subdomain-routed deployments — request path is\n // implicitly `/`, which only matches root-prefix tenants.\n return this.resolveByRequest(host, '/');\n },\n\n resolveByRequest(\n host: string,\n pathname: string\n ): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null {\n const lowered = host.toLowerCase();\n // Strip query/fragment defensively — adopters wiring `req.url`\n // (Node raw HTTP) instead of `req.path` (Express normalized)\n // would otherwise fail the boundary check on `/sales/mcp?x=1`.\n const cleanPath = stripQueryAndFragment(pathname);\n let best: { tenantId: string; entry: TenantEntry; prefixLength: number } | null = null;\n for (const [tenantId, entry] of tenants) {\n // Refuse traffic for pending (first validation hasn't succeeded)\n // and disabled (permanent validation failure). `unverified` —\n // previously healthy, latest recheck failed transiently — still\n // resolves; operators choose graceful degradation here.\n if (entry.status.health === 'pending' || entry.status.health === 'disabled') continue;\n // Multi-URL tenants register one route per `agentUrls[]` entry.\n // Longest-prefix match across ALL routes on ALL tenants wins —\n // a tenant with `/sales-broadcast` on alias-host beats a tenant\n // with `/sales` on canonical-host for `/sales-broadcast/mcp`.\n for (const route of entry.routes) {\n if (route.host !== lowered) continue;\n if (!pathPrefixMatches(route.pathPrefix, cleanPath)) continue;\n const prefixLength = route.pathPrefix === '/' ? 0 : route.pathPrefix.length;\n if (best === null || prefixLength > best.prefixLength) {\n best = { tenantId, entry, prefixLength };\n }\n }\n }\n if (best === null) return null;\n return { tenantId: best.tenantId, config: best.entry.config, server: best.entry.server };\n },\n\n get(tenantId: string): { tenantId: string; config: TenantConfig; server: DecisioningAdcpServer } | null {\n const entry = tenants.get(tenantId);\n if (!entry) return null;\n // Same health gate as resolveByRequest — pending tenants haven't\n // been validated yet, disabled tenants permanently failed.\n // unverified (post-healthy transient failure) resolves normally.\n if (entry.status.health === 'pending' || entry.status.health === 'disabled') return null;\n return { tenantId, config: entry.config, server: entry.server };\n },\n\n getStatus(tenantId: string): TenantStatus | null {\n const entry = tenants.get(tenantId);\n return entry?.status ?? null;\n },\n\n list(): readonly TenantStatus[] {\n return Array.from(tenants.values()).map(e => e.status);\n },\n\n async recheck(tenantId: string): Promise<TenantStatus> {\n const entry = tenants.get(tenantId);\n if (!entry) {\n throw new Error(`recheck: tenant '${tenantId}' not registered`);\n }\n // Dedupe concurrent rechecks against the same tenant.\n if (entry.pending) {\n try {\n await entry.pending;\n } catch {\n // ignore — fall through to fresh recheck\n }\n }\n entry.pending = runValidation(tenantId);\n try {\n return await entry.pending;\n } finally {\n entry.pending = undefined;\n }\n },\n };\n}\n"],"mappings":"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","names":["status","opts"]}
|
|
@@ -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
|
function createTenantStore(config) {
|
|
8
2
|
const store = {
|
|
9
3
|
resolve: async (ref, ctx) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/server/decisioning/tenant-store.ts"],"sourcesContent":["/**\n * `createTenantStore` — opinionated `AccountStore` builder for multi-tenant\n * adapters. Canonicalizes the two-path resolution shape (operator-routed\n * for tools that carry `account` on the wire; auth-derived for tools that\n * don't) and bakes in the per-entry tenant-isolation gate on the mutating\n * account-sync tools (`sync_accounts` / `sync_governance`) that adopters\n * historically had to write — and silently fail to write — by hand.\n *\n * NOTE: `accounts.resolve` is NOT gated by default (`refAccess: 'ref-routed'`)\n * — it returns whatever tenant the buyer's ref points at, which is correct\n * for the agency-hub model where one credential spans tenants. Deployments\n * where a credential must NOT reach another tenant's account set\n * `refAccess: 'auth-scoped'` (gates `resolve` fail-closed) or compose a\n * `resolve-presets` guard. See {@link TenantStoreConfig.refAccess}.\n *\n * Full walkthrough with same-tenant invariant + production caveats:\n * `skills/build-holdco-agent/SKILL.md`. Worked example:\n * `examples/hello_seller_adapter_multi_tenant.ts`.\n *\n * Status: Preview / 6.x.\n *\n * @public\n */\n\nimport type { ResolveContext, Account, AccountStore, SyncAccountsResultRow } from './account';\nimport type { AccountReference, SyncGovernanceRequest, SyncGovernanceSuccess } from '../../types/tools.generated';\n\ntype SyncGovernanceEntry = SyncGovernanceRequest['accounts'][number];\ntype SyncGovernanceRow = SyncGovernanceSuccess['accounts'][number];\n\n/**\n * Adopter contract for `createTenantStore`. Every callback is sync OR async;\n * the helper awaits.\n *\n * Tenant isolation is enforced by comparing `tenantId(authTenant)` against\n * `tenantId(entryTenant)` per-entry on `sync_accounts` / `sync_governance`.\n * Mismatches produce a `'failed'` row with `code: 'PERMISSION_DENIED'` —\n * the adopter's `upsertRow` / `syncGovernanceRow` callbacks NEVER see a\n * cross-tenant entry. Fail-closed when `resolveFromAuth` returns null\n * (unknown principal): every entry fails `PERMISSION_DENIED` regardless of\n * its operator. Don't fork this around to fail-open — adopters who copied\n * the prior fail-open shape (`if (homeTenantId && tenantId !== homeTenantId)`)\n * silently disabled isolation when a credential lacked a tenant binding.\n *\n * @template TTenant Adopter's tenant model (e.g., `TenantState`, a row\n * from a `tenants` table). Compared via `tenantId`,\n * not by reference.\n * @template TCtxMeta Shape of `Account.ctx_metadata`. Threads through to\n * every specialism handler via `ctx.account.ctx_metadata`.\n */\nexport interface TenantStoreConfig<TTenant, TCtxMeta = Record<string, unknown>> {\n /**\n * Controls whether a buyer-supplied account ref on `accounts.resolve` is\n * gated against the authenticated principal's tenant.\n *\n * - `'ref-routed'` (default) — `resolve` returns whatever tenant the ref\n * points at, WITHOUT checking the caller. This is correct for the\n * agency-hub / account-routed model where one credential legitimately\n * spans tenants (see `examples/hello_seller_adapter_multi_tenant.ts`).\n * In this mode `resolve` performs NO isolation check — any authenticated\n * caller can resolve any tenant's account by naming its `account_id` /\n * `operator`. Isolation for such deployments must be layered on top with\n * a `resolve-presets` guard (`requireAccountMatch` /\n * `requireAdvertiserMatch` / `requireOrgScope`) via `composeMethod`.\n * - `'auth-scoped'` — `resolve` fails closed: a ref that resolves to a\n * tenant other than `resolveFromAuth(ctx)` (or an unresolvable auth\n * principal) returns `null` (framework emits `ACCOUNT_NOT_FOUND`). Use\n * this when a credential must NOT be able to reach another tenant's\n * account by supplying its ref.\n *\n * This flag governs ONLY `resolve`. `upsert` / `syncGovernance` always\n * enforce the tenant gate regardless of this setting.\n */\n refAccess?: 'ref-routed' | 'auth-scoped';\n\n /**\n * Path 1: account ref carries `account_id` OR `(brand, operator)`.\n * Resolve to the tenant the ref points at. Return null if the ref is\n * unknown (helper emits `ACCOUNT_NOT_FOUND` for that row).\n *\n * By default (`refAccess: 'ref-routed'`) this resolves independent of who\n * the caller is. Set `refAccess: 'auth-scoped'` to make `resolve` reject a\n * ref that points at a tenant other than the caller's.\n *\n * Receives the full `AccountReference` so adopters can route on\n * `ref.sandbox` (Pattern 2: separate sandbox tenant) or read the\n * `account_id` arm of the discriminated union.\n */\n resolveByRef(ref: AccountReference): TTenant | null | Promise<TTenant | null>;\n\n /**\n * Path 2: no account ref on the wire (`get_brand_identity`, `get_rights`,\n * `provide_performance_feedback`, `list_creative_formats`). Derive the\n * tenant from the auth principal. Return null if no principal is\n * resolvable (no auth, no `agentRegistry`, principal not registered).\n *\n * Used by both `accounts.resolve(undefined, ctx)` (no-account tools)\n * AND the tenant-isolation gate on per-entry tools — `null` here\n * means EVERY entry on `sync_accounts` / `sync_governance` fails\n * `PERMISSION_DENIED` (fail-closed).\n */\n resolveFromAuth(ctx: ResolveContext): TTenant | null | Promise<TTenant | null>;\n\n /**\n * Stable identity for tenant-equality checks. The helper compares\n * `tenantId(authTenant) === tenantId(entryTenant)` to enforce isolation.\n * Reference equality is fragile (Postgres-backed stores hand back fresh\n * objects each fetch); a stable string id closes that gap.\n */\n tenantId(tenant: TTenant): string;\n\n /**\n * Project `(tenant, ref)` to the framework `Account<TCtxMeta>`. Called by\n * `accounts.resolve` after tenant resolution. Adopters thread sandbox\n * routing here (`sandbox: ref?.sandbox`), pin `ctx_metadata` for\n * downstream handlers, and shape `name` / `operator` / `brand` to match\n * the wire echo conventions their buyers expect.\n *\n * `ref` is `undefined` on Path-2 (no-account tools) — adopters returning\n * a synthetic publisher-wide singleton omit `ref?.sandbox` (no sandbox\n * boundary applies).\n */\n tenantToAccount(\n tenant: TTenant,\n ref: AccountReference | undefined,\n ctx: ResolveContext\n ): Account<TCtxMeta> | Promise<Account<TCtxMeta>>;\n\n /**\n * Per-entry `sync_accounts` storage callback. Called for each entry whose\n * `(authTenant === entryTenant)` check passed. Receives the resolved\n * tenant + the original ref + ctx. Returns a `SyncAccountsResultRow`.\n *\n * Cross-tenant entries and unknown-ref entries never reach this callback\n * — the helper builds `PERMISSION_DENIED` / `ACCOUNT_NOT_FOUND` rows for\n * those before invoking your code. The adopter's only job is the actual\n * upsert.\n *\n * Optional. Omit if your platform doesn't claim `sync_accounts`; the\n * helper leaves `accounts.upsert` undefined and the framework returns\n * `UNSUPPORTED_FEATURE`.\n */\n upsertRow?(\n tenant: TTenant,\n ref: AccountReference,\n ctx: ResolveContext\n ): SyncAccountsResultRow | Promise<SyncAccountsResultRow>;\n\n /**\n * Per-entry `sync_governance` storage callback. Same gating rules as\n * `upsertRow` — cross-tenant entries are rejected before reaching this\n * code. Adopters persist the buyer's governance-agent binding (including\n * write-only `authentication.credentials`, which the framework strips\n * from the response automatically — see `toWireSyncGovernanceRow`).\n *\n * Optional. Omit if your platform doesn't claim `sync_governance`.\n */\n syncGovernanceRow?(\n tenant: TTenant,\n entry: SyncGovernanceEntry,\n ctx: ResolveContext\n ): SyncGovernanceRow | Promise<SyncGovernanceRow>;\n}\n\n/**\n * Build an `AccountStore<TCtxMeta>` whose `resolve` / `upsert` /\n * `syncGovernance` methods enforce tenant isolation.\n *\n * The helper produces:\n *\n * - `accounts.resolve(ref, ctx)` — calls `resolveByRef(ref)` when `ref` is\n * set, otherwise `resolveFromAuth(ctx)`. Projects via `tenantToAccount`.\n * Returns `null` if the resolver returned `null` (framework emits\n * `ACCOUNT_NOT_FOUND` for tools that require an account, or treats\n * absence as \"no tenant\" for tools that don't). By default this path does\n * NOT check the caller against the ref's tenant — set\n * `refAccess: 'auth-scoped'` to fail closed on a cross-tenant ref.\n *\n * - `accounts.upsert(refs, ctx)` — for each ref:\n * 1. Resolve the entry's tenant via `resolveByRef`.\n * 2. Resolve the auth principal's tenant via `resolveFromAuth(ctx)`\n * (computed once per request).\n * 3. If the entry tenant is unknown, emit `ACCOUNT_NOT_FOUND`.\n * 4. If the auth tenant is unknown OR differs from the entry tenant,\n * emit `PERMISSION_DENIED` (fail-closed).\n * 5. Otherwise, invoke the adopter's `upsertRow`.\n *\n * - `accounts.syncGovernance(entries, ctx)` — same gating as `upsert`,\n * shaped for the `SyncGovernanceResponseRow` arm (`status: 'failed'`\n * with per-entry `errors`).\n *\n * `accounts.list` and `accounts.reportUsage` / `accounts.getAccountFinancials`\n * are NOT generated by this helper — those tools have shapes (cursor\n * pagination; per-row account refs spanning multiple tenants in\n * `report_usage`) that don't fit the per-entry-then-row pattern. Adopters\n * who claim those capabilities extend the returned store with\n * `Object.assign`:\n *\n * ```ts\n * const accounts = Object.assign(\n * createTenantStore<TenantState, TenantMeta>({...}),\n * { list: async (filter, ctx) => { ... } }\n * );\n * ```\n *\n * **Direct mutation of `upsert` / `syncGovernance` is locked.** The helper\n * makes those properties non-writable (`Object.defineProperty`) so an\n * adopter who writes `accounts.upsert = customHandler` after construction\n * gets a TypeError instead of silently bypassing the tenant gate. If you\n * really need a different `upsert`, don't use the helper — write a plain\n * `AccountStore` and own the gate.\n */\nexport function createTenantStore<TTenant, TCtxMeta = Record<string, unknown>>(\n config: TenantStoreConfig<TTenant, TCtxMeta>\n): AccountStore<TCtxMeta> {\n const store: AccountStore<TCtxMeta> = {\n resolve: async (ref, ctx) => {\n const resolveCtx = ctx ?? {};\n if (!ref) {\n const authTenant = await config.resolveFromAuth(resolveCtx);\n if (authTenant == null) return null;\n return await config.tenantToAccount(authTenant, ref, resolveCtx);\n }\n const entryTenant = await config.resolveByRef(ref);\n if (entryTenant == null) return null;\n if ((config.refAccess ?? 'ref-routed') === 'auth-scoped') {\n const authTenant = await config.resolveFromAuth(resolveCtx);\n // Fail-closed: an unresolvable principal OR a ref pointing at a\n // different tenant is treated as not found — a caller cannot reach\n // another tenant's account by naming its ref.\n if (authTenant == null || config.tenantId(authTenant) !== config.tenantId(entryTenant)) {\n return null;\n }\n }\n return await config.tenantToAccount(entryTenant, ref, resolveCtx);\n },\n };\n\n if (config.upsertRow) {\n const upsertRow = config.upsertRow;\n const upsert: NonNullable<AccountStore<TCtxMeta>['upsert']> = async (refs, ctx) => {\n const resolveCtx = ctx ?? {};\n const authTenant = await config.resolveFromAuth(resolveCtx);\n const authTenantKey = authTenant != null ? config.tenantId(authTenant) : undefined;\n // Sequential, not Promise.all: adopter `upsertRow` callbacks\n // commonly mutate shared tenant state (the multi-tenant adapter's\n // `tenant.accounts.set(...)` is the canonical example). Concurrent\n // invocations against the same tenant are an entropy source the\n // helper shouldn't introduce. Adopters who want parallel writes\n // can fan out inside their callback against an upstream that\n // tolerates it.\n const rows: SyncAccountsResultRow[] = [];\n for (const ref of refs) {\n const entryTenant = await config.resolveByRef(ref);\n if (entryTenant == null) {\n rows.push(buildSyncAccountsFailedRow(ref, 'ACCOUNT_NOT_FOUND', accountNotFoundMessage(ref)));\n continue;\n }\n const entryKey = config.tenantId(entryTenant);\n if (authTenantKey == null || authTenantKey !== entryKey) {\n rows.push(buildSyncAccountsFailedRow(ref, 'PERMISSION_DENIED', permissionDeniedMessage(ref)));\n continue;\n }\n rows.push(await upsertRow(entryTenant, ref, resolveCtx));\n }\n return rows;\n };\n Object.defineProperty(store, 'upsert', { value: upsert, writable: false, configurable: false, enumerable: true });\n }\n\n if (config.syncGovernanceRow) {\n const syncGovernanceRow = config.syncGovernanceRow;\n const syncGovernance: NonNullable<AccountStore<TCtxMeta>['syncGovernance']> = async (entries, ctx) => {\n const resolveCtx = ctx ?? {};\n const authTenant = await config.resolveFromAuth(resolveCtx);\n const authTenantKey = authTenant != null ? config.tenantId(authTenant) : undefined;\n const rows: SyncGovernanceRow[] = [];\n for (const entry of entries) {\n const entryTenant = await config.resolveByRef(entry.account);\n if (entryTenant == null) {\n rows.push(buildSyncGovernanceFailedRow(entry, 'ACCOUNT_NOT_FOUND', accountNotFoundMessage(entry.account)));\n continue;\n }\n const entryKey = config.tenantId(entryTenant);\n if (authTenantKey == null || authTenantKey !== entryKey) {\n rows.push(buildSyncGovernanceFailedRow(entry, 'PERMISSION_DENIED', permissionDeniedMessage(entry.account)));\n continue;\n }\n rows.push(await syncGovernanceRow(entryTenant, entry, resolveCtx));\n }\n return rows;\n };\n Object.defineProperty(store, 'syncGovernance', {\n value: syncGovernance,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n }\n\n return store;\n}\n\n/**\n * Read fields from an `AccountReference` without per-arm narrowing. The wire\n * type is a discriminated union (`{account_id} | {brand, operator}`) — schema\n * validation upstream guarantees one arm is populated, so widening to an\n * all-optional record is safe and saves a `'in' ref ? ref.x : fallback` dance\n * inside `tenantToAccount` / `resolveByRef` / failed-row builders.\n *\n * Use inside adopter `createTenantStore({...})` callbacks to read\n * `ref.operator`, `ref.brand?.domain`, or `ref.account_id` without inline\n * casts. This is the same helper the framework uses internally for its\n * `PERMISSION_DENIED` / `ACCOUNT_NOT_FOUND` row construction.\n *\n * ```ts\n * tenantToAccount: (tenant, ref, ctx) => {\n * const r = narrowAccountRef(ref);\n * return {\n * id: tenant.id,\n * operator: r?.operator ?? ctx?.agent?.agent_url ?? 'derived',\n * ...(r?.brand?.domain && { brand: { domain: r.brand.domain } }),\n *\n * };\n * }\n * ```\n *\n * Returns `undefined` on `undefined` input — the no-account-tool path.\n */\nexport function narrowAccountRef(ref: AccountReference): {\n account_id?: string;\n operator?: string;\n brand?: { domain?: string };\n sandbox?: boolean;\n};\nexport function narrowAccountRef(ref: undefined): undefined;\nexport function narrowAccountRef(ref: AccountReference | undefined):\n | {\n account_id?: string;\n operator?: string;\n brand?: { domain?: string };\n sandbox?: boolean;\n }\n | undefined;\nexport function narrowAccountRef(\n ref: AccountReference | undefined\n): { account_id?: string; operator?: string; brand?: { domain?: string }; sandbox?: boolean } | undefined {\n if (ref === undefined) return undefined;\n return ref as { account_id?: string; operator?: string; brand?: { domain?: string }; sandbox?: boolean };\n}\n\n/**\n * Build a failed `sync_accounts` row when the helper rejects an entry\n * (cross-tenant or unknown ref). The wire schema requires `brand` +\n * `operator` on every row, so when the input ref is `account_id`-only\n * we synthesize `'unknown'` placeholders — the buyer's `errors[0].code`\n * is the actionable signal; `brand` / `operator` here are wire-required\n * scaffolding, not authoritative echoes.\n */\nfunction buildSyncAccountsFailedRow(\n ref: AccountReference,\n code: 'ACCOUNT_NOT_FOUND' | 'PERMISSION_DENIED',\n message: string\n): SyncAccountsResultRow {\n const r = narrowAccountRef(ref);\n return {\n brand: { domain: r.brand?.domain ?? 'unknown.example' },\n operator: r.operator ?? 'unknown',\n action: 'failed',\n status: 'rejected',\n errors: [{ code, message }],\n ...(r.account_id != null && { account_id: r.account_id }),\n };\n}\n\nfunction buildSyncGovernanceFailedRow(\n entry: SyncGovernanceEntry,\n code: 'ACCOUNT_NOT_FOUND' | 'PERMISSION_DENIED',\n message: string\n): SyncGovernanceRow {\n return {\n account: entry.account,\n status: 'failed',\n errors: [{ code, message }],\n };\n}\n\nfunction accountNotFoundMessage(ref: AccountReference): string {\n const r = narrowAccountRef(ref);\n if (r.account_id) return `Unknown account_id: ${r.account_id}`;\n if (r.operator) return `Unknown operator: ${r.operator}`;\n return 'Unknown account reference';\n}\n\nfunction permissionDeniedMessage(ref: AccountReference): string {\n const r = narrowAccountRef(ref);\n const subject = r.operator ?? r.account_id ?? 'this account';\n return `Buyer agent has no authority over '${subject}' (tenant mismatch or auth principal not registered).`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/server/decisioning/tenant-store.ts"],"sourcesContent":["/**\n * `createTenantStore` — opinionated `AccountStore` builder for multi-tenant\n * adapters. Canonicalizes the two-path resolution shape (operator-routed\n * for tools that carry `account` on the wire; auth-derived for tools that\n * don't) and bakes in the per-entry tenant-isolation gate on the mutating\n * account-sync tools (`sync_accounts` / `sync_governance`) that adopters\n * historically had to write — and silently fail to write — by hand.\n *\n * NOTE: `accounts.resolve` is NOT gated by default (`refAccess: 'ref-routed'`)\n * — it returns whatever tenant the buyer's ref points at, which is correct\n * for the agency-hub model where one credential spans tenants. Deployments\n * where a credential must NOT reach another tenant's account set\n * `refAccess: 'auth-scoped'` (gates `resolve` fail-closed) or compose a\n * `resolve-presets` guard. See {@link TenantStoreConfig.refAccess}.\n *\n * Full walkthrough with same-tenant invariant + production caveats:\n * `skills/build-holdco-agent/SKILL.md`. Worked example:\n * `examples/hello_seller_adapter_multi_tenant.ts`.\n *\n * Status: Preview / 6.x.\n *\n * @public\n */\n\nimport type { ResolveContext, Account, AccountStore, SyncAccountsResultRow } from './account';\nimport type { AccountReference, SyncGovernanceRequest, SyncGovernanceSuccess } from '../../types/tools.generated';\n\ntype SyncGovernanceEntry = SyncGovernanceRequest['accounts'][number];\ntype SyncGovernanceRow = SyncGovernanceSuccess['accounts'][number];\n\n/**\n * Adopter contract for `createTenantStore`. Every callback is sync OR async;\n * the helper awaits.\n *\n * Tenant isolation is enforced by comparing `tenantId(authTenant)` against\n * `tenantId(entryTenant)` per-entry on `sync_accounts` / `sync_governance`.\n * Mismatches produce a `'failed'` row with `code: 'PERMISSION_DENIED'` —\n * the adopter's `upsertRow` / `syncGovernanceRow` callbacks NEVER see a\n * cross-tenant entry. Fail-closed when `resolveFromAuth` returns null\n * (unknown principal): every entry fails `PERMISSION_DENIED` regardless of\n * its operator. Don't fork this around to fail-open — adopters who copied\n * the prior fail-open shape (`if (homeTenantId && tenantId !== homeTenantId)`)\n * silently disabled isolation when a credential lacked a tenant binding.\n *\n * @template TTenant Adopter's tenant model (e.g., `TenantState`, a row\n * from a `tenants` table). Compared via `tenantId`,\n * not by reference.\n * @template TCtxMeta Shape of `Account.ctx_metadata`. Threads through to\n * every specialism handler via `ctx.account.ctx_metadata`.\n */\nexport interface TenantStoreConfig<TTenant, TCtxMeta = Record<string, unknown>> {\n /**\n * Controls whether a buyer-supplied account ref on `accounts.resolve` is\n * gated against the authenticated principal's tenant.\n *\n * - `'ref-routed'` (default) — `resolve` returns whatever tenant the ref\n * points at, WITHOUT checking the caller. This is correct for the\n * agency-hub / account-routed model where one credential legitimately\n * spans tenants (see `examples/hello_seller_adapter_multi_tenant.ts`).\n * In this mode `resolve` performs NO isolation check — any authenticated\n * caller can resolve any tenant's account by naming its `account_id` /\n * `operator`. Isolation for such deployments must be layered on top with\n * a `resolve-presets` guard (`requireAccountMatch` /\n * `requireAdvertiserMatch` / `requireOrgScope`) via `composeMethod`.\n * - `'auth-scoped'` — `resolve` fails closed: a ref that resolves to a\n * tenant other than `resolveFromAuth(ctx)` (or an unresolvable auth\n * principal) returns `null` (framework emits `ACCOUNT_NOT_FOUND`). Use\n * this when a credential must NOT be able to reach another tenant's\n * account by supplying its ref.\n *\n * This flag governs ONLY `resolve`. `upsert` / `syncGovernance` always\n * enforce the tenant gate regardless of this setting.\n */\n refAccess?: 'ref-routed' | 'auth-scoped';\n\n /**\n * Path 1: account ref carries `account_id` OR `(brand, operator)`.\n * Resolve to the tenant the ref points at. Return null if the ref is\n * unknown (helper emits `ACCOUNT_NOT_FOUND` for that row).\n *\n * By default (`refAccess: 'ref-routed'`) this resolves independent of who\n * the caller is. Set `refAccess: 'auth-scoped'` to make `resolve` reject a\n * ref that points at a tenant other than the caller's.\n *\n * Receives the full `AccountReference` so adopters can route on\n * `ref.sandbox` (Pattern 2: separate sandbox tenant) or read the\n * `account_id` arm of the discriminated union.\n */\n resolveByRef(ref: AccountReference): TTenant | null | Promise<TTenant | null>;\n\n /**\n * Path 2: no account ref on the wire (`get_brand_identity`, `get_rights`,\n * `provide_performance_feedback`, `list_creative_formats`). Derive the\n * tenant from the auth principal. Return null if no principal is\n * resolvable (no auth, no `agentRegistry`, principal not registered).\n *\n * Used by both `accounts.resolve(undefined, ctx)` (no-account tools)\n * AND the tenant-isolation gate on per-entry tools — `null` here\n * means EVERY entry on `sync_accounts` / `sync_governance` fails\n * `PERMISSION_DENIED` (fail-closed).\n */\n resolveFromAuth(ctx: ResolveContext): TTenant | null | Promise<TTenant | null>;\n\n /**\n * Stable identity for tenant-equality checks. The helper compares\n * `tenantId(authTenant) === tenantId(entryTenant)` to enforce isolation.\n * Reference equality is fragile (Postgres-backed stores hand back fresh\n * objects each fetch); a stable string id closes that gap.\n */\n tenantId(tenant: TTenant): string;\n\n /**\n * Project `(tenant, ref)` to the framework `Account<TCtxMeta>`. Called by\n * `accounts.resolve` after tenant resolution. Adopters thread sandbox\n * routing here (`sandbox: ref?.sandbox`), pin `ctx_metadata` for\n * downstream handlers, and shape `name` / `operator` / `brand` to match\n * the wire echo conventions their buyers expect.\n *\n * `ref` is `undefined` on Path-2 (no-account tools) — adopters returning\n * a synthetic publisher-wide singleton omit `ref?.sandbox` (no sandbox\n * boundary applies).\n */\n tenantToAccount(\n tenant: TTenant,\n ref: AccountReference | undefined,\n ctx: ResolveContext\n ): Account<TCtxMeta> | Promise<Account<TCtxMeta>>;\n\n /**\n * Per-entry `sync_accounts` storage callback. Called for each entry whose\n * `(authTenant === entryTenant)` check passed. Receives the resolved\n * tenant + the original ref + ctx. Returns a `SyncAccountsResultRow`.\n *\n * Cross-tenant entries and unknown-ref entries never reach this callback\n * — the helper builds `PERMISSION_DENIED` / `ACCOUNT_NOT_FOUND` rows for\n * those before invoking your code. The adopter's only job is the actual\n * upsert.\n *\n * Optional. Omit if your platform doesn't claim `sync_accounts`; the\n * helper leaves `accounts.upsert` undefined and the framework returns\n * `UNSUPPORTED_FEATURE`.\n */\n upsertRow?(\n tenant: TTenant,\n ref: AccountReference,\n ctx: ResolveContext\n ): SyncAccountsResultRow | Promise<SyncAccountsResultRow>;\n\n /**\n * Per-entry `sync_governance` storage callback. Same gating rules as\n * `upsertRow` — cross-tenant entries are rejected before reaching this\n * code. Adopters persist the buyer's governance-agent binding (including\n * write-only `authentication.credentials`, which the framework strips\n * from the response automatically — see `toWireSyncGovernanceRow`).\n *\n * Optional. Omit if your platform doesn't claim `sync_governance`.\n */\n syncGovernanceRow?(\n tenant: TTenant,\n entry: SyncGovernanceEntry,\n ctx: ResolveContext\n ): SyncGovernanceRow | Promise<SyncGovernanceRow>;\n}\n\n/**\n * Build an `AccountStore<TCtxMeta>` whose `resolve` / `upsert` /\n * `syncGovernance` methods enforce tenant isolation.\n *\n * The helper produces:\n *\n * - `accounts.resolve(ref, ctx)` — calls `resolveByRef(ref)` when `ref` is\n * set, otherwise `resolveFromAuth(ctx)`. Projects via `tenantToAccount`.\n * Returns `null` if the resolver returned `null` (framework emits\n * `ACCOUNT_NOT_FOUND` for tools that require an account, or treats\n * absence as \"no tenant\" for tools that don't). By default this path does\n * NOT check the caller against the ref's tenant — set\n * `refAccess: 'auth-scoped'` to fail closed on a cross-tenant ref.\n *\n * - `accounts.upsert(refs, ctx)` — for each ref:\n * 1. Resolve the entry's tenant via `resolveByRef`.\n * 2. Resolve the auth principal's tenant via `resolveFromAuth(ctx)`\n * (computed once per request).\n * 3. If the entry tenant is unknown, emit `ACCOUNT_NOT_FOUND`.\n * 4. If the auth tenant is unknown OR differs from the entry tenant,\n * emit `PERMISSION_DENIED` (fail-closed).\n * 5. Otherwise, invoke the adopter's `upsertRow`.\n *\n * - `accounts.syncGovernance(entries, ctx)` — same gating as `upsert`,\n * shaped for the `SyncGovernanceResponseRow` arm (`status: 'failed'`\n * with per-entry `errors`).\n *\n * `accounts.list` and `accounts.reportUsage` / `accounts.getAccountFinancials`\n * are NOT generated by this helper — those tools have shapes (cursor\n * pagination; per-row account refs spanning multiple tenants in\n * `report_usage`) that don't fit the per-entry-then-row pattern. Adopters\n * who claim those capabilities extend the returned store with\n * `Object.assign`:\n *\n * ```ts\n * const accounts = Object.assign(\n * createTenantStore<TenantState, TenantMeta>({...}),\n * { list: async (filter, ctx) => { ... } }\n * );\n * ```\n *\n * **Direct mutation of `upsert` / `syncGovernance` is locked.** The helper\n * makes those properties non-writable (`Object.defineProperty`) so an\n * adopter who writes `accounts.upsert = customHandler` after construction\n * gets a TypeError instead of silently bypassing the tenant gate. If you\n * really need a different `upsert`, don't use the helper — write a plain\n * `AccountStore` and own the gate.\n */\nexport function createTenantStore<TTenant, TCtxMeta = Record<string, unknown>>(\n config: TenantStoreConfig<TTenant, TCtxMeta>\n): AccountStore<TCtxMeta> {\n const store: AccountStore<TCtxMeta> = {\n resolve: async (ref, ctx) => {\n const resolveCtx = ctx ?? {};\n if (!ref) {\n const authTenant = await config.resolveFromAuth(resolveCtx);\n if (authTenant == null) return null;\n return await config.tenantToAccount(authTenant, ref, resolveCtx);\n }\n const entryTenant = await config.resolveByRef(ref);\n if (entryTenant == null) return null;\n if ((config.refAccess ?? 'ref-routed') === 'auth-scoped') {\n const authTenant = await config.resolveFromAuth(resolveCtx);\n // Fail-closed: an unresolvable principal OR a ref pointing at a\n // different tenant is treated as not found — a caller cannot reach\n // another tenant's account by naming its ref.\n if (authTenant == null || config.tenantId(authTenant) !== config.tenantId(entryTenant)) {\n return null;\n }\n }\n return await config.tenantToAccount(entryTenant, ref, resolveCtx);\n },\n };\n\n if (config.upsertRow) {\n const upsertRow = config.upsertRow;\n const upsert: NonNullable<AccountStore<TCtxMeta>['upsert']> = async (refs, ctx) => {\n const resolveCtx = ctx ?? {};\n const authTenant = await config.resolveFromAuth(resolveCtx);\n const authTenantKey = authTenant != null ? config.tenantId(authTenant) : undefined;\n // Sequential, not Promise.all: adopter `upsertRow` callbacks\n // commonly mutate shared tenant state (the multi-tenant adapter's\n // `tenant.accounts.set(...)` is the canonical example). Concurrent\n // invocations against the same tenant are an entropy source the\n // helper shouldn't introduce. Adopters who want parallel writes\n // can fan out inside their callback against an upstream that\n // tolerates it.\n const rows: SyncAccountsResultRow[] = [];\n for (const ref of refs) {\n const entryTenant = await config.resolveByRef(ref);\n if (entryTenant == null) {\n rows.push(buildSyncAccountsFailedRow(ref, 'ACCOUNT_NOT_FOUND', accountNotFoundMessage(ref)));\n continue;\n }\n const entryKey = config.tenantId(entryTenant);\n if (authTenantKey == null || authTenantKey !== entryKey) {\n rows.push(buildSyncAccountsFailedRow(ref, 'PERMISSION_DENIED', permissionDeniedMessage(ref)));\n continue;\n }\n rows.push(await upsertRow(entryTenant, ref, resolveCtx));\n }\n return rows;\n };\n Object.defineProperty(store, 'upsert', { value: upsert, writable: false, configurable: false, enumerable: true });\n }\n\n if (config.syncGovernanceRow) {\n const syncGovernanceRow = config.syncGovernanceRow;\n const syncGovernance: NonNullable<AccountStore<TCtxMeta>['syncGovernance']> = async (entries, ctx) => {\n const resolveCtx = ctx ?? {};\n const authTenant = await config.resolveFromAuth(resolveCtx);\n const authTenantKey = authTenant != null ? config.tenantId(authTenant) : undefined;\n const rows: SyncGovernanceRow[] = [];\n for (const entry of entries) {\n const entryTenant = await config.resolveByRef(entry.account);\n if (entryTenant == null) {\n rows.push(buildSyncGovernanceFailedRow(entry, 'ACCOUNT_NOT_FOUND', accountNotFoundMessage(entry.account)));\n continue;\n }\n const entryKey = config.tenantId(entryTenant);\n if (authTenantKey == null || authTenantKey !== entryKey) {\n rows.push(buildSyncGovernanceFailedRow(entry, 'PERMISSION_DENIED', permissionDeniedMessage(entry.account)));\n continue;\n }\n rows.push(await syncGovernanceRow(entryTenant, entry, resolveCtx));\n }\n return rows;\n };\n Object.defineProperty(store, 'syncGovernance', {\n value: syncGovernance,\n writable: false,\n configurable: false,\n enumerable: true,\n });\n }\n\n return store;\n}\n\n/**\n * Read fields from an `AccountReference` without per-arm narrowing. The wire\n * type is a discriminated union (`{account_id} | {brand, operator}`) — schema\n * validation upstream guarantees one arm is populated, so widening to an\n * all-optional record is safe and saves a `'in' ref ? ref.x : fallback` dance\n * inside `tenantToAccount` / `resolveByRef` / failed-row builders.\n *\n * Use inside adopter `createTenantStore({...})` callbacks to read\n * `ref.operator`, `ref.brand?.domain`, or `ref.account_id` without inline\n * casts. This is the same helper the framework uses internally for its\n * `PERMISSION_DENIED` / `ACCOUNT_NOT_FOUND` row construction.\n *\n * ```ts\n * tenantToAccount: (tenant, ref, ctx) => {\n * const r = narrowAccountRef(ref);\n * return {\n * id: tenant.id,\n * operator: r?.operator ?? ctx?.agent?.agent_url ?? 'derived',\n * ...(r?.brand?.domain && { brand: { domain: r.brand.domain } }),\n *\n * };\n * }\n * ```\n *\n * Returns `undefined` on `undefined` input — the no-account-tool path.\n */\nexport function narrowAccountRef(ref: AccountReference): {\n account_id?: string;\n operator?: string;\n brand?: { domain?: string };\n sandbox?: boolean;\n};\nexport function narrowAccountRef(ref: undefined): undefined;\nexport function narrowAccountRef(ref: AccountReference | undefined):\n | {\n account_id?: string;\n operator?: string;\n brand?: { domain?: string };\n sandbox?: boolean;\n }\n | undefined;\nexport function narrowAccountRef(\n ref: AccountReference | undefined\n): { account_id?: string; operator?: string; brand?: { domain?: string }; sandbox?: boolean } | undefined {\n if (ref === undefined) return undefined;\n return ref as { account_id?: string; operator?: string; brand?: { domain?: string }; sandbox?: boolean };\n}\n\n/**\n * Build a failed `sync_accounts` row when the helper rejects an entry\n * (cross-tenant or unknown ref). The wire schema requires `brand` +\n * `operator` on every row, so when the input ref is `account_id`-only\n * we synthesize `'unknown'` placeholders — the buyer's `errors[0].code`\n * is the actionable signal; `brand` / `operator` here are wire-required\n * scaffolding, not authoritative echoes.\n */\nfunction buildSyncAccountsFailedRow(\n ref: AccountReference,\n code: 'ACCOUNT_NOT_FOUND' | 'PERMISSION_DENIED',\n message: string\n): SyncAccountsResultRow {\n const r = narrowAccountRef(ref);\n return {\n brand: { domain: r.brand?.domain ?? 'unknown.example' },\n operator: r.operator ?? 'unknown',\n action: 'failed',\n status: 'rejected',\n errors: [{ code, message }],\n ...(r.account_id != null && { account_id: r.account_id }),\n };\n}\n\nfunction buildSyncGovernanceFailedRow(\n entry: SyncGovernanceEntry,\n code: 'ACCOUNT_NOT_FOUND' | 'PERMISSION_DENIED',\n message: string\n): SyncGovernanceRow {\n return {\n account: entry.account,\n status: 'failed',\n errors: [{ code, message }],\n };\n}\n\nfunction accountNotFoundMessage(ref: AccountReference): string {\n const r = narrowAccountRef(ref);\n if (r.account_id) return `Unknown account_id: ${r.account_id}`;\n if (r.operator) return `Unknown operator: ${r.operator}`;\n return 'Unknown account reference';\n}\n\nfunction permissionDeniedMessage(ref: AccountReference): string {\n const r = narrowAccountRef(ref);\n const subject = r.operator ?? r.account_id ?? 'this account';\n return `Buyer agent has no authority over '${subject}' (tenant mismatch or auth principal not registered).`;\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 { SPECIALISM_REQUIRED_TOOLS } from "../../types/manifest.generated.mjs";
|
|
8
2
|
function toolNameToMethodName(tool) {
|
|
9
3
|
return tool.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/lib/server/decisioning/validate-specialisms.ts"],"sourcesContent":["/**\n * Runtime validation for specialism→required-tools coverage.\n *\n * When an adopter declares a specialism in `capabilities.specialisms[]`, the\n * AdCP spec implies the agent supports every tool in that specialism's\n * required-tool list (per `manifest.specialisms[*].required_tools` —\n * `SPECIALISM_REQUIRED_TOOLS` in `manifest.generated.ts`). This module checks\n * that the adopter's platform object exposes a method matching every required\n * tool, and emits actionable warnings (or throws under strict mode) when\n * something's missing.\n *\n * The check is **method-presence anywhere on the platform**, not method-on-\n * specific-field. Required tools can span platform fields (`sync_accounts`\n * lives on `accounts`, not on the specialism's primary platform field), and\n * the SDK doesn't enforce a 1:1 specialism→field mapping. The looser check\n * catches the common adopter mistake (forgot to implement) without\n * false-positives on legitimate layout choices.\n *\n * Tracked: adcp-client#1192 (manifest adoption) → #1299 (stage 3).\n *\n * Companion to the build-time type-check fixture at\n * `src/lib/server/decisioning/specialism-required-tools.type-checks.ts`,\n * which catches the inverse failure mode (SDK's hand-maintained platform\n * interfaces drifting away from manifest's required-tools contract).\n */\n\nimport { SPECIALISM_REQUIRED_TOOLS } from '../../types/manifest.generated';\n\n/**\n * Convert a snake_case tool name to its expected camelCase platform method\n * name. The transform is mechanical — every required tool in 3.0.4 follows\n * this convention. If a future spec introduces a tool whose canonical method\n * name diverges (e.g., the kebab segment maps to an acronym), add an\n * override here rather than reaching for a more elaborate mapping table.\n */\nexport function toolNameToMethodName(tool: string): string {\n return tool.replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n}\n\ninterface ValidatorIssue {\n specialism: string;\n tool: string;\n method: string;\n}\n\n/**\n * Walk the platform object's top-level fields and check whether any field\n * exposes a callable property matching `methodName`. Returns true on first\n * match. The platform layout is adopter-controlled — we look across every\n * field rather than mapping tool→field upfront.\n *\n * **Top-level functions on the platform root are NOT searched.** The SDK\n * convention is sub-platform fields (`{ sales: SalesPlatform, accounts:\n * AccountsPlatform, ... }`), and a future adopter who tries\n * `{ getProducts: () => ... }` directly on the root would get a confusing\n * warning. If that pattern needs supporting, extend the loop to also check\n * `typeof (platform as any)[methodName] === 'function'` — but the\n * non-conventional layout test (`single mega-platform exposes all methods`\n * in `validate-specialisms.test.js`) already covers the alternative\n * single-field-of-everything pattern, which is the legitimate flat-layout\n * case adopters might choose.\n */\nfunction hasMethodAnywhere(platform: unknown, methodName: string): boolean {\n if (!platform || typeof platform !== 'object') return false;\n for (const value of Object.values(platform as Record<string, unknown>)) {\n if (value && typeof value === 'object') {\n const method = (value as Record<string, unknown>)[methodName];\n if (typeof method === 'function') return true;\n }\n }\n return false;\n}\n\n/**\n * Validate that `platform` exposes a method for every required tool of every\n * declared specialism. Returns the list of missing (specialism, tool, method)\n * triples. Callers decide whether to warn or throw — `createAdcpServer`\n * defaults to console.warn for ergonomic dev feedback; opt in via the\n * `strictSpecialismValidation` config flag to escalate.\n *\n * Specialisms not present in `SPECIALISM_REQUIRED_TOOLS` (preview-only or\n * not yet enumerated by the manifest) are silently passed — the manifest is\n * the source of truth, and a missing entry there means the spec hasn't yet\n * formalized the required tools for that specialism.\n *\n * The optional `requiredToolsLookup` parameter exists for testing — production\n * callers omit it and get the manifest-derived `SPECIALISM_REQUIRED_TOOLS`.\n * Tests can inject synthetic data so the validator's behavior can be exercised\n * even when the manifest's `required_tools` field is empty (3.0.4 ships every\n * specialism with `required_tools: []` per spec; coverage activates when the\n * spec authors populate the field).\n */\nexport function validateSpecialismRequiredTools(\n platform: unknown,\n specialisms: readonly string[] | undefined,\n requiredToolsLookup: Record<string, readonly string[] | undefined> = SPECIALISM_REQUIRED_TOOLS\n): ValidatorIssue[] {\n if (!specialisms || specialisms.length === 0) return [];\n const issues: ValidatorIssue[] = [];\n for (const specialism of specialisms) {\n const required = requiredToolsLookup[specialism];\n if (!required) continue;\n for (const tool of required) {\n const method = toolNameToMethodName(tool);\n if (!hasMethodAnywhere(platform, method)) {\n issues.push({ specialism, tool, method });\n }\n }\n }\n return issues;\n}\n\n/**\n * Format a single issue as a human-readable warning line. The format names\n * the specialism, the missing tool, and the method an adopter should\n * implement, so the dev fix is obvious from the log.\n */\nexport function formatSpecialismIssue(issue: ValidatorIssue): string {\n return (\n `[adcp/server] specialism '${issue.specialism}' requires tool '${issue.tool}' ` +\n `but no platform field exposes a '${issue.method}' method. ` +\n `Either implement the method, drop the specialism from capabilities.specialisms, ` +\n `or pass strictSpecialismValidation: false to silence (not recommended — ` +\n `naive AdCP buyers will hit runtime errors when they call the unsupported tool).`\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../../src/lib/server/decisioning/validate-specialisms.ts"],"sourcesContent":["/**\n * Runtime validation for specialism→required-tools coverage.\n *\n * When an adopter declares a specialism in `capabilities.specialisms[]`, the\n * AdCP spec implies the agent supports every tool in that specialism's\n * required-tool list (per `manifest.specialisms[*].required_tools` —\n * `SPECIALISM_REQUIRED_TOOLS` in `manifest.generated.ts`). This module checks\n * that the adopter's platform object exposes a method matching every required\n * tool, and emits actionable warnings (or throws under strict mode) when\n * something's missing.\n *\n * The check is **method-presence anywhere on the platform**, not method-on-\n * specific-field. Required tools can span platform fields (`sync_accounts`\n * lives on `accounts`, not on the specialism's primary platform field), and\n * the SDK doesn't enforce a 1:1 specialism→field mapping. The looser check\n * catches the common adopter mistake (forgot to implement) without\n * false-positives on legitimate layout choices.\n *\n * Tracked: adcp-client#1192 (manifest adoption) → #1299 (stage 3).\n *\n * Companion to the build-time type-check fixture at\n * `src/lib/server/decisioning/specialism-required-tools.type-checks.ts`,\n * which catches the inverse failure mode (SDK's hand-maintained platform\n * interfaces drifting away from manifest's required-tools contract).\n */\n\nimport { SPECIALISM_REQUIRED_TOOLS } from '../../types/manifest.generated';\n\n/**\n * Convert a snake_case tool name to its expected camelCase platform method\n * name. The transform is mechanical — every required tool in 3.0.4 follows\n * this convention. If a future spec introduces a tool whose canonical method\n * name diverges (e.g., the kebab segment maps to an acronym), add an\n * override here rather than reaching for a more elaborate mapping table.\n */\nexport function toolNameToMethodName(tool: string): string {\n return tool.replace(/_([a-z])/g, (_, c) => c.toUpperCase());\n}\n\ninterface ValidatorIssue {\n specialism: string;\n tool: string;\n method: string;\n}\n\n/**\n * Walk the platform object's top-level fields and check whether any field\n * exposes a callable property matching `methodName`. Returns true on first\n * match. The platform layout is adopter-controlled — we look across every\n * field rather than mapping tool→field upfront.\n *\n * **Top-level functions on the platform root are NOT searched.** The SDK\n * convention is sub-platform fields (`{ sales: SalesPlatform, accounts:\n * AccountsPlatform, ... }`), and a future adopter who tries\n * `{ getProducts: () => ... }` directly on the root would get a confusing\n * warning. If that pattern needs supporting, extend the loop to also check\n * `typeof (platform as any)[methodName] === 'function'` — but the\n * non-conventional layout test (`single mega-platform exposes all methods`\n * in `validate-specialisms.test.js`) already covers the alternative\n * single-field-of-everything pattern, which is the legitimate flat-layout\n * case adopters might choose.\n */\nfunction hasMethodAnywhere(platform: unknown, methodName: string): boolean {\n if (!platform || typeof platform !== 'object') return false;\n for (const value of Object.values(platform as Record<string, unknown>)) {\n if (value && typeof value === 'object') {\n const method = (value as Record<string, unknown>)[methodName];\n if (typeof method === 'function') return true;\n }\n }\n return false;\n}\n\n/**\n * Validate that `platform` exposes a method for every required tool of every\n * declared specialism. Returns the list of missing (specialism, tool, method)\n * triples. Callers decide whether to warn or throw — `createAdcpServer`\n * defaults to console.warn for ergonomic dev feedback; opt in via the\n * `strictSpecialismValidation` config flag to escalate.\n *\n * Specialisms not present in `SPECIALISM_REQUIRED_TOOLS` (preview-only or\n * not yet enumerated by the manifest) are silently passed — the manifest is\n * the source of truth, and a missing entry there means the spec hasn't yet\n * formalized the required tools for that specialism.\n *\n * The optional `requiredToolsLookup` parameter exists for testing — production\n * callers omit it and get the manifest-derived `SPECIALISM_REQUIRED_TOOLS`.\n * Tests can inject synthetic data so the validator's behavior can be exercised\n * even when the manifest's `required_tools` field is empty (3.0.4 ships every\n * specialism with `required_tools: []` per spec; coverage activates when the\n * spec authors populate the field).\n */\nexport function validateSpecialismRequiredTools(\n platform: unknown,\n specialisms: readonly string[] | undefined,\n requiredToolsLookup: Record<string, readonly string[] | undefined> = SPECIALISM_REQUIRED_TOOLS\n): ValidatorIssue[] {\n if (!specialisms || specialisms.length === 0) return [];\n const issues: ValidatorIssue[] = [];\n for (const specialism of specialisms) {\n const required = requiredToolsLookup[specialism];\n if (!required) continue;\n for (const tool of required) {\n const method = toolNameToMethodName(tool);\n if (!hasMethodAnywhere(platform, method)) {\n issues.push({ specialism, tool, method });\n }\n }\n }\n return issues;\n}\n\n/**\n * Format a single issue as a human-readable warning line. The format names\n * the specialism, the missing tool, and the method an adopter should\n * implement, so the dev fix is obvious from the log.\n */\nexport function formatSpecialismIssue(issue: ValidatorIssue): string {\n return (\n `[adcp/server] specialism '${issue.specialism}' requires tool '${issue.tool}' ` +\n `but no platform field exposes a '${issue.method}' method. ` +\n `Either implement the method, drop the specialism from capabilities.specialisms, ` +\n `or pass strictSpecialismValidation: false to silence (not recommended — ` +\n `naive AdCP buyers will hit runtime errors when they call the unsupported tool).`\n );\n}\n"],"mappings":"AA0BA,SAAS,iCAAiC;AASnC,SAAS,qBAAqB,MAAsB;AACzD,SAAO,KAAK,QAAQ,aAAa,CAAC,GAAG,MAAM,EAAE,YAAY,CAAC;AAC5D;AAyBA,SAAS,kBAAkB,UAAmB,YAA6B;AACzE,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AACtD,aAAW,SAAS,OAAO,OAAO,QAAmC,GAAG;AACtE,QAAI,SAAS,OAAO,UAAU,UAAU;AACtC,YAAM,SAAU,MAAkC,UAAU;AAC5D,UAAI,OAAO,WAAW,WAAY,QAAO;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAqBO,SAAS,gCACd,UACA,aACA,sBAAqE,2BACnD;AAClB,MAAI,CAAC,eAAe,YAAY,WAAW,EAAG,QAAO,CAAC;AACtD,QAAM,SAA2B,CAAC;AAClC,aAAW,cAAc,aAAa;AACpC,UAAM,WAAW,oBAAoB,UAAU;AAC/C,QAAI,CAAC,SAAU;AACf,eAAW,QAAQ,UAAU;AAC3B,YAAM,SAAS,qBAAqB,IAAI;AACxC,UAAI,CAAC,kBAAkB,UAAU,MAAM,GAAG;AACxC,eAAO,KAAK,EAAE,YAAY,MAAM,OAAO,CAAC;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,sBAAsB,OAA+B;AACnE,SACE,6BAA6B,MAAM,UAAU,oBAAoB,MAAM,IAAI,sCACvC,MAAM,MAAM;AAKpD;","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
|
function createDynamicRegistry(config) {
|
|
8
2
|
const names = [...config.registries];
|
|
9
3
|
if (names.length === 0) {
|