@adcp/sdk 12.0.0 → 12.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/adapters/content-standards-adapter.mjs +0 -6
- package/dist/lib/adapters/content-standards-adapter.mjs.map +1 -1
- package/dist/lib/adapters/derived-account-store.mjs +0 -6
- package/dist/lib/adapters/derived-account-store.mjs.map +1 -1
- package/dist/lib/adapters/governance-adapter.mjs +0 -6
- package/dist/lib/adapters/governance-adapter.mjs.map +1 -1
- package/dist/lib/adapters/implicit-account-store.mjs +0 -6
- package/dist/lib/adapters/implicit-account-store.mjs.map +1 -1
- package/dist/lib/adapters/index.mjs +0 -6
- package/dist/lib/adapters/index.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/get_products.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/get_products.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/index.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/index.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs.map +1 -1
- package/dist/lib/adapters/legacy/v2-5/types.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs +0 -6
- package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs.map +1 -1
- package/dist/lib/adapters/oauth-passthrough-resolver.mjs +0 -6
- package/dist/lib/adapters/oauth-passthrough-resolver.mjs.map +1 -1
- package/dist/lib/adapters/property-list-adapter.mjs +0 -6
- package/dist/lib/adapters/property-list-adapter.mjs.map +1 -1
- package/dist/lib/adapters/roster-account-store.mjs +0 -6
- package/dist/lib/adapters/roster-account-store.mjs.map +1 -1
- package/dist/lib/adapters/si-session-manager.mjs +0 -6
- package/dist/lib/adapters/si-session-manager.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/brand-fields.mjs +0 -6
- package/dist/lib/adapters/version/3.0/brand-fields.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/get-products.mjs +0 -6
- package/dist/lib/adapters/version/3.0/get-products.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/index.mjs +0 -6
- package/dist/lib/adapters/version/3.0/index.mjs.map +1 -1
- package/dist/lib/adapters/version/3.0/sync-accounts.mjs +0 -6
- package/dist/lib/adapters/version/3.0/sync-accounts.mjs.map +1 -1
- package/dist/lib/adapters/version/index.mjs +0 -6
- package/dist/lib/adapters/version/index.mjs.map +1 -1
- package/dist/lib/adapters/version/types.mjs +0 -6
- package/dist/lib/advanced.mjs +0 -6
- package/dist/lib/advanced.mjs.map +1 -1
- package/dist/lib/agents/index.generated.mjs +0 -6
- package/dist/lib/agents/index.generated.mjs.map +1 -1
- package/dist/lib/auth/index.mjs +0 -6
- package/dist/lib/auth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs +0 -6
- package/dist/lib/auth/oauth/CLIFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +0 -6
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs.map +1 -1
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +0 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs.map +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +0 -6
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs.map +1 -1
- package/dist/lib/auth/oauth/authorization-required.mjs +0 -6
- package/dist/lib/auth/oauth/authorization-required.mjs.map +1 -1
- package/dist/lib/auth/oauth/diagnose.mjs +0 -6
- package/dist/lib/auth/oauth/diagnose.mjs.map +1 -1
- package/dist/lib/auth/oauth/diagnostics.mjs +0 -6
- package/dist/lib/auth/oauth/diagnostics.mjs.map +1 -1
- package/dist/lib/auth/oauth/discovery.mjs +0 -6
- package/dist/lib/auth/oauth/discovery.mjs.map +1 -1
- package/dist/lib/auth/oauth/file-storage.mjs +0 -6
- package/dist/lib/auth/oauth/file-storage.mjs.map +1 -1
- package/dist/lib/auth/oauth/index.mjs +0 -6
- package/dist/lib/auth/oauth/index.mjs.map +1 -1
- package/dist/lib/auth/oauth/secret-resolver.mjs +0 -6
- package/dist/lib/auth/oauth/secret-resolver.mjs.map +1 -1
- package/dist/lib/auth/oauth/storage-registry.mjs +0 -6
- package/dist/lib/auth/oauth/storage-registry.mjs.map +1 -1
- package/dist/lib/auth/oauth/types.mjs +0 -6
- package/dist/lib/auth/oauth/types.mjs.map +1 -1
- package/dist/lib/auth/oauth/web-flow.mjs +0 -6
- package/dist/lib/auth/oauth/web-flow.mjs.map +1 -1
- package/dist/lib/brand/index.mjs +0 -6
- package/dist/lib/brand/index.mjs.map +1 -1
- package/dist/lib/canonical-references/index.mjs +6 -7
- package/dist/lib/canonical-references/index.mjs.map +1 -1
- package/dist/lib/client/index.mjs +0 -6
- package/dist/lib/client/index.mjs.map +1 -1
- package/dist/lib/compliance/index.mjs +0 -6
- package/dist/lib/compliance/index.mjs.map +1 -1
- package/dist/lib/compliance-fixtures/index.mjs +0 -6
- package/dist/lib/compliance-fixtures/index.mjs.map +1 -1
- package/dist/lib/compliance-fixtures/test-authorization-server.mjs +0 -6
- package/dist/lib/compliance-fixtures/test-authorization-server.mjs.map +1 -1
- package/dist/lib/conformance/index.mjs +0 -6
- package/dist/lib/conformance/index.mjs.map +1 -1
- package/dist/lib/conformance/invariants/uniformError.mjs +0 -6
- package/dist/lib/conformance/invariants/uniformError.mjs.map +1 -1
- package/dist/lib/conformance/invariants/uniformErrorComparator.mjs +0 -6
- package/dist/lib/conformance/invariants/uniformErrorComparator.mjs.map +1 -1
- package/dist/lib/conformance/oracle.mjs +0 -6
- package/dist/lib/conformance/oracle.mjs.map +1 -1
- package/dist/lib/conformance/runConformance.mjs +0 -6
- package/dist/lib/conformance/runConformance.mjs.map +1 -1
- package/dist/lib/conformance/runners.mjs +0 -6
- package/dist/lib/conformance/runners.mjs.map +1 -1
- package/dist/lib/conformance/schemaArbitrary.mjs +0 -6
- package/dist/lib/conformance/schemaArbitrary.mjs.map +1 -1
- package/dist/lib/conformance/schemaLoader.mjs +5 -6
- package/dist/lib/conformance/schemaLoader.mjs.map +1 -1
- package/dist/lib/conformance/seeder.mjs +0 -6
- package/dist/lib/conformance/seeder.mjs.map +1 -1
- package/dist/lib/conformance/types.mjs +0 -6
- package/dist/lib/conformance/types.mjs.map +1 -1
- package/dist/lib/conformance/webhook.mjs +0 -6
- package/dist/lib/conformance/webhook.mjs.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.mjs +0 -6
- package/dist/lib/core/ADCPMultiAgentClient.mjs.map +1 -1
- package/dist/lib/core/AgentClient.mjs +0 -6
- package/dist/lib/core/AgentClient.mjs.map +1 -1
- package/dist/lib/core/AsyncHandler.mjs +0 -6
- package/dist/lib/core/AsyncHandler.mjs.map +1 -1
- package/dist/lib/core/ConfigurationManager.mjs +0 -6
- package/dist/lib/core/ConfigurationManager.mjs.map +1 -1
- package/dist/lib/core/ConversationTypes.mjs +0 -6
- package/dist/lib/core/CreativeAgentClient.mjs +0 -6
- package/dist/lib/core/CreativeAgentClient.mjs.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.mjs +0 -6
- package/dist/lib/core/GovernanceMiddleware.mjs.map +1 -1
- package/dist/lib/core/GovernanceTypes.mjs +0 -6
- package/dist/lib/core/GovernanceTypes.mjs.map +1 -1
- package/dist/lib/core/ProtocolResponseParser.mjs +0 -6
- package/dist/lib/core/ProtocolResponseParser.mjs.map +1 -1
- package/dist/lib/core/ResponseValidator.mjs +0 -6
- package/dist/lib/core/ResponseValidator.mjs.map +1 -1
- package/dist/lib/core/SingleAgentClient.mjs +0 -6
- package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
- package/dist/lib/core/TaskEventTypes.mjs +0 -6
- package/dist/lib/core/TaskEventTypes.mjs.map +1 -1
- package/dist/lib/core/TaskExecutor.mjs +0 -6
- package/dist/lib/core/TaskExecutor.mjs.map +1 -1
- package/dist/lib/core/match.mjs +0 -6
- package/dist/lib/core/match.mjs.map +1 -1
- package/dist/lib/core/task-status.mjs +0 -6
- package/dist/lib/core/task-status.mjs.map +1 -1
- package/dist/lib/core/webhook-url.mjs +0 -6
- package/dist/lib/core/webhook-url.mjs.map +1 -1
- package/dist/lib/discovery/adagents-redirects.mjs +0 -6
- package/dist/lib/discovery/adagents-redirects.mjs.map +1 -1
- package/dist/lib/discovery/agent-directory.mjs +0 -6
- package/dist/lib/discovery/agent-directory.mjs.map +1 -1
- package/dist/lib/discovery/inline-publisher-properties.mjs +0 -6
- package/dist/lib/discovery/inline-publisher-properties.mjs.map +1 -1
- package/dist/lib/discovery/network-consistency-checker.mjs +0 -6
- package/dist/lib/discovery/network-consistency-checker.mjs.map +1 -1
- package/dist/lib/discovery/property-crawler.mjs +0 -6
- package/dist/lib/discovery/property-crawler.mjs.map +1 -1
- package/dist/lib/discovery/property-index.mjs +0 -6
- package/dist/lib/discovery/property-index.mjs.map +1 -1
- package/dist/lib/discovery/publisher-property-selector.mjs +0 -6
- package/dist/lib/discovery/publisher-property-selector.mjs.map +1 -1
- package/dist/lib/discovery/resolve-agent-properties.mjs +0 -6
- package/dist/lib/discovery/resolve-agent-properties.mjs.map +1 -1
- package/dist/lib/discovery/types.mjs +0 -6
- package/dist/lib/discovery/validate-adagents.mjs +0 -6
- package/dist/lib/discovery/validate-adagents.mjs.map +1 -1
- package/dist/lib/enums.mjs +0 -6
- package/dist/lib/enums.mjs.map +1 -1
- package/dist/lib/errors/index.mjs +0 -6
- package/dist/lib/errors/index.mjs.map +1 -1
- package/dist/lib/express-mcp/index.mjs +0 -6
- package/dist/lib/express-mcp/index.mjs.map +1 -1
- package/dist/lib/governance/index.mjs +0 -6
- package/dist/lib/governance/index.mjs.map +1 -1
- package/dist/lib/handlers/types.mjs +0 -6
- package/dist/lib/handlers/types.mjs.map +1 -1
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +0 -6
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/media-buy/available-actions.mjs +0 -6
- package/dist/lib/media-buy/available-actions.mjs.map +1 -1
- package/dist/lib/media-buy/index.mjs +0 -6
- package/dist/lib/media-buy/index.mjs.map +1 -1
- package/dist/lib/media-buy/preflight.mjs +0 -6
- package/dist/lib/media-buy/preflight.mjs.map +1 -1
- package/dist/lib/media-buy/property-policy.mjs +0 -6
- package/dist/lib/media-buy/property-policy.mjs.map +1 -1
- package/dist/lib/media-buy/types.mjs +0 -6
- package/dist/lib/media-buy/types.mjs.map +1 -1
- package/dist/lib/media-buy/update-fields.generated.mjs +0 -6
- package/dist/lib/media-buy/update-fields.generated.mjs.map +1 -1
- package/dist/lib/mock-server/creative-ad-server/seed-data.mjs +0 -6
- package/dist/lib/mock-server/creative-ad-server/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/creative-ad-server/server.mjs +0 -6
- package/dist/lib/mock-server/creative-ad-server/server.mjs.map +1 -1
- package/dist/lib/mock-server/creative-template/seed-data.mjs +0 -6
- package/dist/lib/mock-server/creative-template/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/creative-template/server.mjs +0 -6
- package/dist/lib/mock-server/creative-template/server.mjs.map +1 -1
- package/dist/lib/mock-server/index.mjs +0 -6
- package/dist/lib/mock-server/index.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/recipe.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/recipe.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-guaranteed/server.mjs +0 -6
- package/dist/lib/mock-server/sales-guaranteed/server.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-non-guaranteed/server.mjs +0 -6
- package/dist/lib/mock-server/sales-non-guaranteed/server.mjs.map +1 -1
- package/dist/lib/mock-server/sales-social/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sales-social/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sales-social/server.mjs +0 -6
- package/dist/lib/mock-server/sales-social/server.mjs.map +1 -1
- package/dist/lib/mock-server/scenario.mjs +0 -6
- package/dist/lib/mock-server/scenario.mjs.map +1 -1
- package/dist/lib/mock-server/signal-marketplace/seed-data.mjs +0 -6
- package/dist/lib/mock-server/signal-marketplace/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/signal-marketplace/server.mjs +0 -6
- package/dist/lib/mock-server/signal-marketplace/server.mjs.map +1 -1
- package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs +0 -6
- package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs.map +1 -1
- package/dist/lib/mock-server/sponsored-intelligence/server.mjs +0 -6
- package/dist/lib/mock-server/sponsored-intelligence/server.mjs.map +1 -1
- package/dist/lib/net/address-guards.mjs +0 -6
- package/dist/lib/net/address-guards.mjs.map +1 -1
- package/dist/lib/net/index.mjs +0 -6
- package/dist/lib/net/index.mjs.map +1 -1
- package/dist/lib/net/ssrf-fetch.mjs +0 -6
- package/dist/lib/net/ssrf-fetch.mjs.map +1 -1
- package/dist/lib/observability/index.mjs +0 -6
- package/dist/lib/observability/index.mjs.map +1 -1
- package/dist/lib/observability/tracing.mjs +6 -7
- package/dist/lib/observability/tracing.mjs.map +1 -1
- package/dist/lib/protocols/a2a.mjs +0 -6
- package/dist/lib/protocols/a2a.mjs.map +1 -1
- package/dist/lib/protocols/abort.mjs +0 -6
- package/dist/lib/protocols/abort.mjs.map +1 -1
- package/dist/lib/protocols/index.mjs +0 -6
- package/dist/lib/protocols/index.mjs.map +1 -1
- package/dist/lib/protocols/mcp-modern.mjs +0 -6
- package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
- package/dist/lib/protocols/mcp-tasks.mjs +0 -6
- package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
- package/dist/lib/protocols/mcp.mjs +0 -6
- package/dist/lib/protocols/mcp.mjs.map +1 -1
- package/dist/lib/protocols/rawResponseCapture.mjs +0 -6
- package/dist/lib/protocols/rawResponseCapture.mjs.map +1 -1
- package/dist/lib/protocols/responseSizeLimit.mjs +0 -6
- package/dist/lib/protocols/responseSizeLimit.mjs.map +1 -1
- package/dist/lib/protocols/transportDiagnostics.mjs +0 -6
- package/dist/lib/protocols/transportDiagnostics.mjs.map +1 -1
- package/dist/lib/registry/cursor-store.mjs +0 -6
- package/dist/lib/registry/cursor-store.mjs.map +1 -1
- package/dist/lib/registry/feed-stream.mjs +0 -6
- package/dist/lib/registry/feed-stream.mjs.map +1 -1
- package/dist/lib/registry/index.mjs +0 -6
- package/dist/lib/registry/index.mjs.map +1 -1
- package/dist/lib/registry/property-registry.mjs +0 -6
- package/dist/lib/registry/property-registry.mjs.map +1 -1
- package/dist/lib/registry/sync.mjs +0 -6
- package/dist/lib/registry/sync.mjs.map +1 -1
- package/dist/lib/registry/types.generated.mjs +0 -6
- package/dist/lib/registry/types.mjs +0 -6
- package/dist/lib/schemas/index.mjs +0 -6
- package/dist/lib/schemas/index.mjs.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.mjs +0 -6
- package/dist/lib/server/a2a-adapter.mjs.map +1 -1
- package/dist/lib/server/account-mode.mjs +0 -6
- package/dist/lib/server/account-mode.mjs.map +1 -1
- package/dist/lib/server/adcp-server.mjs +0 -6
- package/dist/lib/server/adcp-server.mjs.map +1 -1
- package/dist/lib/server/auth-introspection.mjs +0 -6
- package/dist/lib/server/auth-introspection.mjs.map +1 -1
- package/dist/lib/server/auth-signature.mjs +0 -6
- package/dist/lib/server/auth-signature.mjs.map +1 -1
- package/dist/lib/server/auth.mjs +0 -6
- package/dist/lib/server/auth.mjs.map +1 -1
- package/dist/lib/server/create-adcp-server.d.mts +13 -0
- package/dist/lib/server/create-adcp-server.d.ts +13 -0
- package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
- package/dist/lib/server/create-adcp-server.js +3 -2
- package/dist/lib/server/create-adcp-server.js.map +1 -1
- package/dist/lib/server/create-adcp-server.mjs +3 -8
- package/dist/lib/server/create-adcp-server.mjs.map +1 -1
- package/dist/lib/server/credential-policy.mjs +0 -6
- package/dist/lib/server/credential-policy.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/memory.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/memory.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/pg.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/pg.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/backends/redis.mjs +0 -6
- package/dist/lib/server/ctx-metadata/backends/redis.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/index.mjs +0 -6
- package/dist/lib/server/ctx-metadata/index.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/store.mjs +0 -6
- package/dist/lib/server/ctx-metadata/store.mjs.map +1 -1
- package/dist/lib/server/ctx-metadata/wire-shape.mjs +0 -6
- package/dist/lib/server/ctx-metadata/wire-shape.mjs.map +1 -1
- package/dist/lib/server/decisioning/account.mjs +0 -6
- package/dist/lib/server/decisioning/account.mjs.map +1 -1
- package/dist/lib/server/decisioning/admin-router.mjs +0 -6
- package/dist/lib/server/decisioning/admin-router.mjs.map +1 -1
- package/dist/lib/server/decisioning/assembly-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/assembly-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/async-outcome.mjs +0 -6
- package/dist/lib/server/decisioning/async-outcome.mjs.map +1 -1
- package/dist/lib/server/decisioning/buyer-agent.mjs +0 -6
- package/dist/lib/server/decisioning/buyer-agent.mjs.map +1 -1
- package/dist/lib/server/decisioning/capabilities.mjs +0 -6
- package/dist/lib/server/decisioning/capabilities.mjs.map +1 -1
- package/dist/lib/server/decisioning/compose.mjs +0 -6
- package/dist/lib/server/decisioning/compose.mjs.map +1 -1
- package/dist/lib/server/decisioning/context.mjs +0 -6
- package/dist/lib/server/decisioning/errors-typed.mjs +0 -6
- package/dist/lib/server/decisioning/errors-typed.mjs.map +1 -1
- package/dist/lib/server/decisioning/helpers.mjs +0 -6
- package/dist/lib/server/decisioning/helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/index.mjs +0 -6
- package/dist/lib/server/decisioning/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/list-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/list-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/manifest-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/manifest-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/pagination.mjs +0 -6
- package/dist/lib/server/decisioning/platform-helpers.mjs +0 -6
- package/dist/lib/server/decisioning/platform-helpers.mjs.map +1 -1
- package/dist/lib/server/decisioning/platform.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/dispatch.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/dispatch.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/index.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/index.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/lifecycle.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/lifecycle.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/mock-manager.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/mock-manager.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/store.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/store.mjs.map +1 -1
- package/dist/lib/server/decisioning/proposal/types.mjs +0 -6
- package/dist/lib/server/decisioning/proposal/types.mjs.map +1 -1
- package/dist/lib/server/decisioning/resolve-presets.mjs +0 -6
- package/dist/lib/server/decisioning/resolve-presets.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/observed-modes.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/observed-modes.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/task-registry.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/to-context.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
- package/dist/lib/server/decisioning/runtime/validate-platform.mjs +0 -6
- package/dist/lib/server/decisioning/runtime/validate-platform.mjs.map +1 -1
- package/dist/lib/server/decisioning/specialisms/audiences.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/brand-rights.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/campaign-governance.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/content-standards.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/creative-ad-server.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/creative.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/lists.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/sales.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/signals.mjs +0 -6
- package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.mjs +0 -6
- package/dist/lib/server/decisioning/start-time.mjs +0 -6
- package/dist/lib/server/decisioning/start-time.mjs.map +1 -1
- package/dist/lib/server/decisioning/status-changes.mjs +0 -6
- package/dist/lib/server/decisioning/status-changes.mjs.map +1 -1
- package/dist/lib/server/decisioning/status-mappers.mjs +0 -6
- package/dist/lib/server/decisioning/status-mappers.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-registry.mjs +0 -6
- package/dist/lib/server/decisioning/tenant-registry.mjs.map +1 -1
- package/dist/lib/server/decisioning/tenant-store.mjs +0 -6
- package/dist/lib/server/decisioning/tenant-store.mjs.map +1 -1
- package/dist/lib/server/decisioning/validate-specialisms.mjs +0 -6
- package/dist/lib/server/decisioning/validate-specialisms.mjs.map +1 -1
- package/dist/lib/server/dynamic-registry.mjs +0 -6
- package/dist/lib/server/dynamic-registry.mjs.map +1 -1
- package/dist/lib/server/envelope-allowlist.mjs +0 -6
- package/dist/lib/server/envelope-allowlist.mjs.map +1 -1
- package/dist/lib/server/error-arm-tools.mjs +5 -6
- package/dist/lib/server/error-arm-tools.mjs.map +1 -1
- package/dist/lib/server/errors.mjs +0 -6
- package/dist/lib/server/errors.mjs.map +1 -1
- package/dist/lib/server/example-tld-guard.mjs +0 -6
- package/dist/lib/server/example-tld-guard.mjs.map +1 -1
- package/dist/lib/server/express-adapter.mjs +0 -6
- package/dist/lib/server/express-adapter.mjs.map +1 -1
- package/dist/lib/server/governance.mjs +0 -6
- package/dist/lib/server/governance.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/lazy.mjs +0 -6
- package/dist/lib/server/idempotency/backends/lazy.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/memory.mjs +0 -6
- package/dist/lib/server/idempotency/backends/memory.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/pg.mjs +0 -6
- package/dist/lib/server/idempotency/backends/pg.mjs.map +1 -1
- package/dist/lib/server/idempotency/backends/redis.mjs +0 -6
- package/dist/lib/server/idempotency/backends/redis.mjs.map +1 -1
- package/dist/lib/server/idempotency/index.mjs +0 -6
- package/dist/lib/server/idempotency/index.mjs.map +1 -1
- package/dist/lib/server/idempotency/store.mjs +0 -6
- package/dist/lib/server/idempotency/store.mjs.map +1 -1
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/index.mjs +0 -6
- package/dist/lib/server/index.mjs.map +1 -1
- package/dist/lib/server/legacy/v5/index.d.mts +1 -1
- package/dist/lib/server/legacy/v5/index.d.ts +1 -1
- package/dist/lib/server/legacy/v5/index.d.ts.map +1 -1
- package/dist/lib/server/legacy/v5/index.js.map +1 -1
- package/dist/lib/server/legacy/v5/index.mjs +0 -6
- package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
- package/dist/lib/server/mcp-modern-server.mjs +0 -6
- package/dist/lib/server/mcp-modern-server.mjs.map +1 -1
- package/dist/lib/server/media-buy-actions.mjs +0 -6
- package/dist/lib/server/media-buy-actions.mjs.map +1 -1
- package/dist/lib/server/media-buy-helpers.mjs +0 -6
- package/dist/lib/server/media-buy-helpers.mjs.map +1 -1
- package/dist/lib/server/media-buy-store.mjs +0 -6
- package/dist/lib/server/media-buy-store.mjs.map +1 -1
- package/dist/lib/server/normalize-errors.mjs +0 -6
- package/dist/lib/server/normalize-errors.mjs.map +1 -1
- package/dist/lib/server/operational-platform.mjs +0 -6
- package/dist/lib/server/operational-platform.mjs.map +1 -1
- package/dist/lib/server/pick-safe-details.mjs +0 -6
- package/dist/lib/server/pick-safe-details.mjs.map +1 -1
- package/dist/lib/server/pin-and-bind-fetch.mjs +0 -6
- package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
- package/dist/lib/server/postgres-state-store.mjs +0 -6
- package/dist/lib/server/postgres-state-store.mjs.map +1 -1
- package/dist/lib/server/postgres-task-store.mjs +0 -6
- package/dist/lib/server/postgres-task-store.mjs.map +1 -1
- package/dist/lib/server/product-defaults.mjs +0 -6
- package/dist/lib/server/product-defaults.mjs.map +1 -1
- package/dist/lib/server/redact.mjs +0 -6
- package/dist/lib/server/redact.mjs.map +1 -1
- package/dist/lib/server/responses.mjs +0 -6
- package/dist/lib/server/responses.mjs.map +1 -1
- package/dist/lib/server/serve.mjs +0 -6
- package/dist/lib/server/serve.mjs.map +1 -1
- package/dist/lib/server/socket-mode/conformance-client.mjs +0 -6
- package/dist/lib/server/socket-mode/conformance-client.mjs.map +1 -1
- package/dist/lib/server/socket-mode/index.mjs +0 -6
- package/dist/lib/server/socket-mode/index.mjs.map +1 -1
- package/dist/lib/server/socket-mode/ws-transport.mjs +0 -6
- package/dist/lib/server/socket-mode/ws-transport.mjs.map +1 -1
- package/dist/lib/server/state-machine.mjs +0 -6
- package/dist/lib/server/state-machine.mjs.map +1 -1
- package/dist/lib/server/state-store.mjs +0 -6
- package/dist/lib/server/state-store.mjs.map +1 -1
- package/dist/lib/server/structured-serialize.mjs +0 -6
- package/dist/lib/server/structured-serialize.mjs.map +1 -1
- package/dist/lib/server/targeting-helpers.mjs +0 -6
- package/dist/lib/server/targeting-helpers.mjs.map +1 -1
- package/dist/lib/server/tasks.mjs +0 -6
- package/dist/lib/server/tasks.mjs.map +1 -1
- package/dist/lib/server/test-controller-bridge.mjs +0 -6
- package/dist/lib/server/test-controller-bridge.mjs.map +1 -1
- package/dist/lib/server/test-controller.mjs +0 -6
- package/dist/lib/server/test-controller.mjs.map +1 -1
- package/dist/lib/server/upstream-helpers.mjs +0 -6
- package/dist/lib/server/upstream-helpers.mjs.map +1 -1
- package/dist/lib/server/webhook-emitter.mjs +0 -6
- package/dist/lib/server/webhook-emitter.mjs.map +1 -1
- package/dist/lib/server/wire-safe.mjs +0 -6
- package/dist/lib/server/wire-safe.mjs.map +1 -1
- package/dist/lib/server/wire-spec-fields.generated.mjs +0 -6
- package/dist/lib/server/wire-spec-fields.generated.mjs.map +1 -1
- package/dist/lib/server/wrap-envelope.mjs +0 -6
- package/dist/lib/server/wrap-envelope.mjs.map +1 -1
- package/dist/lib/signing/agent-context.mjs +0 -6
- package/dist/lib/signing/agent-context.mjs.map +1 -1
- package/dist/lib/signing/agent-fetch.mjs +0 -6
- package/dist/lib/signing/agent-fetch.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/canonicalize.mjs +0 -6
- package/dist/lib/signing/agent-resolver/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs +0 -6
- package/dist/lib/signing/agent-resolver/capabilities-types.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -6
- package/dist/lib/signing/agent-resolver/consistency.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/errors.mjs +0 -6
- package/dist/lib/signing/agent-resolver/errors.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/etld.mjs +0 -6
- package/dist/lib/signing/agent-resolver/etld.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +0 -6
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/index.mjs +0 -6
- package/dist/lib/signing/agent-resolver/index.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +0 -6
- package/dist/lib/signing/agent-resolver/jwks-set.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/select-agent.mjs +0 -6
- package/dist/lib/signing/agent-resolver/select-agent.mjs.map +1 -1
- package/dist/lib/signing/agent-resolver/strict-json.mjs +0 -6
- package/dist/lib/signing/agent-resolver/strict-json.mjs.map +1 -1
- package/dist/lib/signing/brand-jwks.mjs +0 -6
- package/dist/lib/signing/brand-jwks.mjs.map +1 -1
- package/dist/lib/signing/canonicalize.mjs +0 -6
- package/dist/lib/signing/canonicalize.mjs.map +1 -1
- package/dist/lib/signing/capability-cache.mjs +0 -6
- package/dist/lib/signing/capability-cache.mjs.map +1 -1
- package/dist/lib/signing/capability-priming.mjs +0 -6
- package/dist/lib/signing/capability-priming.mjs.map +1 -1
- package/dist/lib/signing/client.mjs +0 -6
- package/dist/lib/signing/client.mjs.map +1 -1
- package/dist/lib/signing/content-digest.mjs +0 -6
- package/dist/lib/signing/content-digest.mjs.map +1 -1
- package/dist/lib/signing/crypto.mjs +0 -6
- package/dist/lib/signing/crypto.mjs.map +1 -1
- package/dist/lib/signing/ecdsa-encoding.mjs +0 -6
- package/dist/lib/signing/ecdsa-encoding.mjs.map +1 -1
- package/dist/lib/signing/errors.mjs +0 -6
- package/dist/lib/signing/errors.mjs.map +1 -1
- package/dist/lib/signing/fetch-async.mjs +0 -6
- package/dist/lib/signing/fetch-async.mjs.map +1 -1
- package/dist/lib/signing/fetch.mjs +0 -6
- package/dist/lib/signing/fetch.mjs.map +1 -1
- package/dist/lib/signing/index.mjs +0 -6
- package/dist/lib/signing/index.mjs.map +1 -1
- package/dist/lib/signing/jwks-helpers.mjs +0 -6
- package/dist/lib/signing/jwks-helpers.mjs.map +1 -1
- package/dist/lib/signing/jwks-https.mjs +0 -6
- package/dist/lib/signing/jwks-https.mjs.map +1 -1
- package/dist/lib/signing/jwks.mjs +0 -6
- package/dist/lib/signing/jwks.mjs.map +1 -1
- package/dist/lib/signing/middleware.mjs +0 -6
- package/dist/lib/signing/middleware.mjs.map +1 -1
- package/dist/lib/signing/parser.mjs +0 -6
- package/dist/lib/signing/parser.mjs.map +1 -1
- package/dist/lib/signing/postgres-replay-store.mjs +0 -6
- package/dist/lib/signing/postgres-replay-store.mjs.map +1 -1
- package/dist/lib/signing/protocol-response.mjs +0 -6
- package/dist/lib/signing/protocol-response.mjs.map +1 -1
- package/dist/lib/signing/provider.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs +0 -6
- package/dist/lib/signing/redis-replay-store.mjs.map +1 -1
- package/dist/lib/signing/replay.mjs +0 -6
- package/dist/lib/signing/replay.mjs.map +1 -1
- package/dist/lib/signing/request-context.mjs +0 -6
- package/dist/lib/signing/request-context.mjs.map +1 -1
- package/dist/lib/signing/revocation-https.mjs +0 -6
- package/dist/lib/signing/revocation-https.mjs.map +1 -1
- package/dist/lib/signing/revocation.mjs +0 -6
- package/dist/lib/signing/revocation.mjs.map +1 -1
- package/dist/lib/signing/server.mjs +0 -6
- package/dist/lib/signing/server.mjs.map +1 -1
- package/dist/lib/signing/signer-async.mjs +0 -6
- package/dist/lib/signing/signer-async.mjs.map +1 -1
- package/dist/lib/signing/signer.mjs +0 -6
- package/dist/lib/signing/signer.mjs.map +1 -1
- package/dist/lib/signing/testing.mjs +0 -6
- package/dist/lib/signing/testing.mjs.map +1 -1
- package/dist/lib/signing/types.mjs +0 -6
- package/dist/lib/signing/types.mjs.map +1 -1
- package/dist/lib/signing/verifier.mjs +0 -6
- package/dist/lib/signing/verifier.mjs.map +1 -1
- package/dist/lib/signing/webhook-auth-detection.mjs +0 -6
- package/dist/lib/signing/webhook-auth-detection.mjs.map +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +0 -6
- package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
- package/dist/lib/storage/MemoryStorage.mjs +0 -6
- package/dist/lib/storage/MemoryStorage.mjs.map +1 -1
- package/dist/lib/storage/interfaces.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs +0 -6
- package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs.map +1 -1
- package/dist/lib/substitution/encoder/index.mjs +0 -6
- package/dist/lib/substitution/encoder/index.mjs.map +1 -1
- package/dist/lib/substitution/index.mjs +0 -6
- package/dist/lib/substitution/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs +0 -6
- package/dist/lib/substitution/observer/SubstitutionObserver.mjs.map +1 -1
- package/dist/lib/substitution/observer/alignment.mjs +0 -6
- package/dist/lib/substitution/observer/alignment.mjs.map +1 -1
- package/dist/lib/substitution/observer/assertions.mjs +0 -6
- package/dist/lib/substitution/observer/assertions.mjs.map +1 -1
- package/dist/lib/substitution/observer/html-parser.mjs +0 -6
- package/dist/lib/substitution/observer/html-parser.mjs.map +1 -1
- package/dist/lib/substitution/observer/index.mjs +0 -6
- package/dist/lib/substitution/observer/index.mjs.map +1 -1
- package/dist/lib/substitution/observer/ssrf.mjs +0 -6
- package/dist/lib/substitution/observer/ssrf.mjs.map +1 -1
- package/dist/lib/substitution/rfc3986.mjs +0 -6
- package/dist/lib/substitution/rfc3986.mjs.map +1 -1
- package/dist/lib/substitution/translate.mjs +0 -6
- package/dist/lib/substitution/translate.mjs.map +1 -1
- package/dist/lib/substitution/types.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs +0 -6
- package/dist/lib/substitution/vectors.mjs.map +1 -1
- package/dist/lib/testing/agent-tester.mjs +0 -6
- package/dist/lib/testing/agent-tester.mjs.map +1 -1
- package/dist/lib/testing/client.mjs +0 -6
- package/dist/lib/testing/client.mjs.map +1 -1
- package/dist/lib/testing/compliance/briefs.mjs +0 -6
- package/dist/lib/testing/compliance/briefs.mjs.map +1 -1
- package/dist/lib/testing/compliance/comply.mjs +0 -6
- package/dist/lib/testing/compliance/comply.mjs.map +1 -1
- package/dist/lib/testing/compliance/index.mjs +0 -6
- package/dist/lib/testing/compliance/index.mjs.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.mjs +0 -6
- package/dist/lib/testing/compliance/spec-conformance.mjs.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.mjs +0 -6
- package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
- package/dist/lib/testing/compliance/summary.mjs +0 -6
- package/dist/lib/testing/compliance/summary.mjs.map +1 -1
- package/dist/lib/testing/compliance/types.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs +0 -6
- package/dist/lib/testing/comply-controller.mjs.map +1 -1
- package/dist/lib/testing/controller-assertions.mjs +0 -6
- package/dist/lib/testing/controller-assertions.mjs.map +1 -1
- package/dist/lib/testing/formatter.mjs +0 -6
- package/dist/lib/testing/formatter.mjs.map +1 -1
- package/dist/lib/testing/index.mjs +0 -6
- package/dist/lib/testing/index.mjs.map +1 -1
- package/dist/lib/testing/local-agent-runner.mjs +0 -6
- package/dist/lib/testing/local-agent-runner.mjs.map +1 -1
- package/dist/lib/testing/orchestrator.mjs +0 -6
- package/dist/lib/testing/orchestrator.mjs.map +1 -1
- package/dist/lib/testing/personas/index.mjs +0 -6
- package/dist/lib/testing/personas/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/brand-rights.mjs +0 -6
- package/dist/lib/testing/scenarios/brand-rights.mjs.map +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +0 -6
- package/dist/lib/testing/scenarios/capabilities.mjs.map +1 -1
- package/dist/lib/testing/scenarios/creative.mjs +0 -6
- package/dist/lib/testing/scenarios/creative.mjs.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.mjs +0 -6
- package/dist/lib/testing/scenarios/deterministic.mjs.map +1 -1
- package/dist/lib/testing/scenarios/discovery.mjs +0 -6
- package/dist/lib/testing/scenarios/discovery.mjs.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.mjs +0 -6
- package/dist/lib/testing/scenarios/edge-cases.mjs.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/error-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/governance.mjs +0 -6
- package/dist/lib/testing/scenarios/governance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/health.mjs +0 -6
- package/dist/lib/testing/scenarios/health.mjs.map +1 -1
- package/dist/lib/testing/scenarios/index.mjs +0 -6
- package/dist/lib/testing/scenarios/index.mjs.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.mjs +0 -6
- package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.mjs +0 -6
- package/dist/lib/testing/scenarios/schema-compliance.mjs.map +1 -1
- package/dist/lib/testing/scenarios/signals.mjs +0 -6
- package/dist/lib/testing/scenarios/signals.mjs.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs +0 -6
- package/dist/lib/testing/scenarios/sponsored-intelligence.mjs.map +1 -1
- package/dist/lib/testing/scenarios/trusted-match.mjs +0 -6
- package/dist/lib/testing/scenarios/trusted-match.mjs.map +1 -1
- package/dist/lib/testing/seed-merge.mjs +0 -6
- package/dist/lib/testing/seed-merge.mjs.map +1 -1
- package/dist/lib/testing/storyboard/agent-routing.mjs +0 -6
- package/dist/lib/testing/storyboard/agent-routing.mjs.map +1 -1
- package/dist/lib/testing/storyboard/assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs +0 -6
- package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs.map +1 -1
- package/dist/lib/testing/storyboard/compliance.mjs +5 -6
- package/dist/lib/testing/storyboard/compliance.mjs.map +1 -1
- package/dist/lib/testing/storyboard/context.mjs +0 -6
- package/dist/lib/testing/storyboard/context.mjs.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.mjs +0 -6
- package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
- package/dist/lib/testing/storyboard/index.mjs +0 -6
- package/dist/lib/testing/storyboard/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/junit.mjs +0 -6
- package/dist/lib/testing/storyboard/junit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/loader.mjs +0 -6
- package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/parallel-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/path.mjs +0 -6
- package/dist/lib/testing/storyboard/path.mjs.map +1 -1
- package/dist/lib/testing/storyboard/probes.mjs +0 -6
- package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs +0 -6
- package/dist/lib/testing/storyboard/rate-limit-trip.mjs.map +1 -1
- package/dist/lib/testing/storyboard/rejection-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/rejection-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/builder.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/index.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/probe.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/probe.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/synthesize.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/types.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +0 -6
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/runner.mjs +0 -6
- package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
- package/dist/lib/testing/storyboard/sandbox-entities.mjs +0 -6
- package/dist/lib/testing/storyboard/sandbox-entities.mjs.map +1 -1
- package/dist/lib/testing/storyboard/seeding.mjs +0 -6
- package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/shape-drift-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/index.mjs +0 -6
- package/dist/lib/testing/storyboard/signer-grader/index.mjs.map +1 -1
- package/dist/lib/testing/storyboard/signer-grader/types.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs +0 -6
- package/dist/lib/testing/storyboard/strict-validation-hints.mjs.map +1 -1
- package/dist/lib/testing/storyboard/task-map.mjs +0 -6
- package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
- package/dist/lib/testing/storyboard/test-kit.mjs +0 -6
- package/dist/lib/testing/storyboard/test-kit.mjs.map +1 -1
- package/dist/lib/testing/storyboard/types.mjs +0 -6
- package/dist/lib/testing/storyboard/types.mjs.map +1 -1
- package/dist/lib/testing/storyboard/validations.mjs +0 -6
- package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-assertions.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
- package/dist/lib/testing/storyboard/webhook-receiver.mjs +0 -6
- package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
- package/dist/lib/testing/stubs/governance-agent-stub.mjs +0 -6
- package/dist/lib/testing/stubs/governance-agent-stub.mjs.map +1 -1
- package/dist/lib/testing/stubs/index.mjs +0 -6
- package/dist/lib/testing/stubs/index.mjs.map +1 -1
- package/dist/lib/testing/test-controller.mjs +0 -6
- package/dist/lib/testing/test-controller.mjs.map +1 -1
- package/dist/lib/testing/test-helpers.mjs +0 -6
- package/dist/lib/testing/test-helpers.mjs.map +1 -1
- package/dist/lib/testing/types.mjs +0 -6
- package/dist/lib/types/adcp.mjs +0 -6
- package/dist/lib/types/adcp.mjs.map +1 -1
- package/dist/lib/types/asset-instances.mjs +0 -6
- package/dist/lib/types/compat.mjs +0 -6
- package/dist/lib/types/compat.mjs.map +1 -1
- package/dist/lib/types/core.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs +0 -6
- package/dist/lib/types/enums.generated.mjs.map +1 -1
- package/dist/lib/types/error-codes.mjs +0 -6
- package/dist/lib/types/error-codes.mjs.map +1 -1
- package/dist/lib/types/error-details.aliases.mjs +0 -6
- package/dist/lib/types/error-details.aliases.mjs.map +1 -1
- package/dist/lib/types/format-asset-slots.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs +0 -6
- package/dist/lib/types/forward-compat-error-codes.mjs.map +1 -1
- package/dist/lib/types/index.mjs +0 -6
- package/dist/lib/types/index.mjs.map +1 -1
- package/dist/lib/types/inline-enums.aliases.mjs +0 -6
- package/dist/lib/types/inline-enums.aliases.mjs.map +1 -1
- package/dist/lib/types/inline-enums.generated.mjs +0 -6
- package/dist/lib/types/inline-enums.generated.mjs.map +1 -1
- package/dist/lib/types/manifest.generated.mjs +0 -6
- package/dist/lib/types/manifest.generated.mjs.map +1 -1
- package/dist/lib/types/schemas.generated.mjs +0 -6
- package/dist/lib/types/schemas.generated.mjs.map +1 -1
- package/dist/lib/types/server-payload-aliases.mjs +0 -6
- package/dist/lib/types/server-payload.mjs +0 -6
- package/dist/lib/types/sync-rows.mjs +0 -6
- package/dist/lib/types/tools.generated.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs +0 -6
- package/dist/lib/types/v2-5/index.mjs.map +1 -1
- package/dist/lib/types/v2-5/tools.generated.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs +0 -6
- package/dist/lib/types/v3-1-beta/index.mjs.map +1 -1
- package/dist/lib/types/v3-1-beta/tools.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs +0 -6
- package/dist/lib/types/wellknown-schemas.generated.mjs.map +1 -1
- package/dist/lib/upstream-recorder/constants.mjs +0 -6
- package/dist/lib/upstream-recorder/constants.mjs.map +1 -1
- package/dist/lib/upstream-recorder/index.mjs +0 -6
- package/dist/lib/upstream-recorder/index.mjs.map +1 -1
- package/dist/lib/upstream-recorder/recorder.mjs +0 -6
- package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
- package/dist/lib/upstream-recorder/types.mjs +0 -6
- package/dist/lib/upstream-recorder/types.mjs.map +1 -1
- package/dist/lib/utils/a2a-artifacts.mjs +0 -6
- package/dist/lib/utils/a2a-artifacts.mjs.map +1 -1
- package/dist/lib/utils/a2a-discovery.mjs +0 -6
- package/dist/lib/utils/a2a-discovery.mjs.map +1 -1
- package/dist/lib/utils/activation-key-builders.mjs +0 -6
- package/dist/lib/utils/activation-key-builders.mjs.map +1 -1
- package/dist/lib/utils/adcp-version-config.mjs +0 -6
- package/dist/lib/utils/adcp-version-config.mjs.map +1 -1
- package/dist/lib/utils/asset-builders.mjs +0 -6
- package/dist/lib/utils/asset-builders.mjs.map +1 -1
- package/dist/lib/utils/build-creative-return-builders.mjs +0 -6
- package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
- package/dist/lib/utils/buyer-retry-policy.mjs +0 -6
- package/dist/lib/utils/buyer-retry-policy.mjs.map +1 -1
- package/dist/lib/utils/capabilities.mjs +0 -6
- package/dist/lib/utils/capabilities.mjs.map +1 -1
- package/dist/lib/utils/capability-rollups.mjs +0 -6
- package/dist/lib/utils/capability-rollups.mjs.map +1 -1
- package/dist/lib/utils/creative-adapter.mjs +0 -6
- package/dist/lib/utils/creative-adapter.mjs.map +1 -1
- package/dist/lib/utils/creative-delivery.mjs +0 -6
- package/dist/lib/utils/creative-delivery.mjs.map +1 -1
- package/dist/lib/utils/deprecation.mjs +0 -6
- package/dist/lib/utils/deprecation.mjs.map +1 -1
- package/dist/lib/utils/envelope-status-compat.mjs +0 -6
- package/dist/lib/utils/envelope-status-compat.mjs.map +1 -1
- package/dist/lib/utils/error-extraction.mjs +0 -6
- package/dist/lib/utils/error-extraction.mjs.map +1 -1
- package/dist/lib/utils/extract-result.mjs +0 -6
- package/dist/lib/utils/extract-result.mjs.map +1 -1
- package/dist/lib/utils/format-asset-slot-builders.mjs +0 -6
- package/dist/lib/utils/format-asset-slot-builders.mjs.map +1 -1
- package/dist/lib/utils/format-assets.mjs +0 -6
- package/dist/lib/utils/format-assets.mjs.map +1 -1
- package/dist/lib/utils/format-render-builders.mjs +0 -6
- package/dist/lib/utils/format-render-builders.mjs.map +1 -1
- package/dist/lib/utils/format-renders.mjs +0 -6
- package/dist/lib/utils/format-renders.mjs.map +1 -1
- package/dist/lib/utils/get-products-cache-scope.mjs +0 -6
- package/dist/lib/utils/get-products-cache-scope.mjs.map +1 -1
- package/dist/lib/utils/glob.mjs +0 -6
- package/dist/lib/utils/glob.mjs.map +1 -1
- package/dist/lib/utils/global-async-local-storage.mjs +0 -6
- package/dist/lib/utils/global-async-local-storage.mjs.map +1 -1
- package/dist/lib/utils/idempotency.mjs +0 -6
- package/dist/lib/utils/idempotency.mjs.map +1 -1
- package/dist/lib/utils/index.mjs +0 -6
- package/dist/lib/utils/index.mjs.map +1 -1
- package/dist/lib/utils/jcs.mjs +0 -6
- package/dist/lib/utils/jcs.mjs.map +1 -1
- package/dist/lib/utils/json-depth.mjs +0 -6
- package/dist/lib/utils/json-depth.mjs.map +1 -1
- package/dist/lib/utils/logger.mjs +0 -6
- package/dist/lib/utils/logger.mjs.map +1 -1
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs +0 -6
- package/dist/lib/utils/media-buy-delivery-notification-builders.mjs.map +1 -1
- package/dist/lib/utils/media-buy-status.mjs +0 -6
- package/dist/lib/utils/media-buy-status.mjs.map +1 -1
- package/dist/lib/utils/pagination.mjs +0 -6
- package/dist/lib/utils/pagination.mjs.map +1 -1
- package/dist/lib/utils/preview-creative-builders.mjs +0 -6
- package/dist/lib/utils/preview-creative-builders.mjs.map +1 -1
- package/dist/lib/utils/preview-normalizer.mjs +0 -6
- package/dist/lib/utils/preview-normalizer.mjs.map +1 -1
- package/dist/lib/utils/preview-utils.mjs +0 -6
- package/dist/lib/utils/preview-utils.mjs.map +1 -1
- package/dist/lib/utils/pricing-adapter.mjs +0 -6
- package/dist/lib/utils/pricing-adapter.mjs.map +1 -1
- package/dist/lib/utils/probe-policy.mjs +0 -6
- package/dist/lib/utils/probe-policy.mjs.map +1 -1
- package/dist/lib/utils/protocol-detection.mjs +0 -6
- package/dist/lib/utils/protocol-detection.mjs.map +1 -1
- package/dist/lib/utils/redact-secrets.mjs +0 -6
- package/dist/lib/utils/redact-secrets.mjs.map +1 -1
- package/dist/lib/utils/redis-default-prefix-warn.mjs +0 -6
- package/dist/lib/utils/redis-default-prefix-warn.mjs.map +1 -1
- package/dist/lib/utils/render-builders.mjs +0 -6
- package/dist/lib/utils/render-builders.mjs.map +1 -1
- package/dist/lib/utils/request-normalizer.mjs +0 -6
- package/dist/lib/utils/request-normalizer.mjs.map +1 -1
- package/dist/lib/utils/response-schemas.mjs +0 -6
- package/dist/lib/utils/response-schemas.mjs.map +1 -1
- package/dist/lib/utils/response-unwrapper.mjs +0 -6
- package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
- package/dist/lib/utils/retry.mjs +0 -6
- package/dist/lib/utils/retry.mjs.map +1 -1
- package/dist/lib/utils/signal-discovery-helpers.mjs +0 -6
- package/dist/lib/utils/signal-discovery-helpers.mjs.map +1 -1
- package/dist/lib/utils/signal-id-builders.mjs +0 -6
- package/dist/lib/utils/signal-id-builders.mjs.map +1 -1
- package/dist/lib/utils/sync-creatives-adapter.mjs +0 -6
- package/dist/lib/utils/sync-creatives-adapter.mjs.map +1 -1
- package/dist/lib/utils/task-state.mjs +0 -6
- package/dist/lib/utils/task-state.mjs.map +1 -1
- package/dist/lib/utils/tool-request-schemas.mjs +0 -6
- package/dist/lib/utils/tool-request-schemas.mjs.map +1 -1
- package/dist/lib/utils/typeGuards.mjs +0 -6
- package/dist/lib/utils/typeGuards.mjs.map +1 -1
- package/dist/lib/utils/union-errors.mjs +0 -6
- package/dist/lib/utils/union-errors.mjs.map +1 -1
- package/dist/lib/utils/validate-user-agent.mjs +0 -6
- package/dist/lib/utils/validate-user-agent.mjs.map +1 -1
- package/dist/lib/v2/format-schema/fetch.mjs +0 -6
- package/dist/lib/v2/format-schema/fetch.mjs.map +1 -1
- package/dist/lib/v2/format-schema/index.mjs +0 -6
- package/dist/lib/v2/format-schema/index.mjs.map +1 -1
- package/dist/lib/v2/format-schema/regex-safety.mjs +0 -6
- package/dist/lib/v2/format-schema/regex-safety.mjs.map +1 -1
- package/dist/lib/v2/format-schema/resolver.mjs +7 -8
- package/dist/lib/v2/format-schema/resolver.mjs.map +1 -1
- package/dist/lib/v2/format-schema/sandbox-refs.mjs +0 -6
- package/dist/lib/v2/format-schema/sandbox-refs.mjs.map +1 -1
- package/dist/lib/v2/projection/augment-response.mjs +0 -6
- package/dist/lib/v2/projection/augment-response.mjs.map +1 -1
- package/dist/lib/v2/projection/builders.mjs +0 -6
- package/dist/lib/v2/projection/builders.mjs.map +1 -1
- package/dist/lib/v2/projection/cache-versions.mjs +0 -6
- package/dist/lib/v2/projection/cache-versions.mjs.map +1 -1
- package/dist/lib/v2/projection/canonical-properties.mjs +5 -6
- package/dist/lib/v2/projection/canonical-properties.mjs.map +1 -1
- package/dist/lib/v2/projection/catalog.mjs +5 -6
- package/dist/lib/v2/projection/catalog.mjs.map +1 -1
- package/dist/lib/v2/projection/constants.mjs +0 -6
- package/dist/lib/v2/projection/constants.mjs.map +1 -1
- package/dist/lib/v2/projection/index.mjs +0 -6
- package/dist/lib/v2/projection/index.mjs.map +1 -1
- package/dist/lib/v2/projection/registry.mjs +5 -6
- package/dist/lib/v2/projection/registry.mjs.map +1 -1
- package/dist/lib/v2/projection/types.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs +0 -6
- package/dist/lib/v2/projection/v1-to-v2.mjs.map +1 -1
- package/dist/lib/v2/projection/v2-to-v1.mjs +0 -6
- package/dist/lib/v2/projection/v2-to-v1.mjs.map +1 -1
- package/dist/lib/v2/projection/write-side.mjs +0 -6
- package/dist/lib/v2/projection/write-side.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/formats.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/formats.mjs.map +1 -1
- package/dist/lib/v2/publisher-catalog/index.mjs +0 -6
- package/dist/lib/v2/publisher-catalog/index.mjs.map +1 -1
- package/dist/lib/validation/client-hooks.mjs +0 -6
- package/dist/lib/validation/client-hooks.mjs.map +1 -1
- package/dist/lib/validation/hints.mjs +0 -6
- package/dist/lib/validation/hints.mjs.map +1 -1
- package/dist/lib/validation/index.mjs +0 -6
- package/dist/lib/validation/index.mjs.map +1 -1
- package/dist/lib/validation/schema-errors.mjs +0 -6
- package/dist/lib/validation/schema-errors.mjs.map +1 -1
- package/dist/lib/validation/schema-loader.mjs +5 -6
- package/dist/lib/validation/schema-loader.mjs.map +1 -1
- package/dist/lib/validation/schema-validator.mjs +0 -6
- package/dist/lib/validation/schema-validator.mjs.map +1 -1
- package/dist/lib/validation/sync-creatives.mjs +0 -6
- package/dist/lib/validation/sync-creatives.mjs.map +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/version.mjs +3 -9
- package/dist/lib/version.mjs.map +1 -1
- package/dist/lib/webhooks/index.mjs +0 -6
- package/dist/lib/webhooks/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/index.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/index.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/sync.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/sync.mjs.map +1 -1
- package/dist/lib/wholesale-feed-sync/types.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +0 -6
- package/dist/lib/wholesale-feed-sync/webhook-notification.mjs.map +1 -1
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/dynamic-registry.ts"],"sourcesContent":["/**\n * Hot-reloadable multi-registry plumbing.\n *\n * `createDynamicRegistry` packages the multi-registry-atomicity idiom\n * every adopter that hot-reloads tenants from a database independently\n * rebuilds. The shim in scope3data/agentic-adapters built this three\n * times (v5 PlatformAdapter, v6 DecisioningPlatform, v6\n * OperationalPlatform) before the pattern crystallized — each\n * iteration with its own hard-won bug fixes.\n *\n * Lessons baked in:\n *\n * 1. **Single-pointer atomic swap.** The bundle of all registry Maps\n * is held behind one `let bundle` reference. A refresh builds the\n * new bundle to completion in a side struct and reassigns the\n * pointer in one statement. A concurrent reader (per-request\n * lookup) crossing an `await` mid-refresh sees the OLD bundle\n * until swap, the NEW bundle after — never a mix where one\n * registry has the new data and another doesn't.\n *\n * 2. **In-flight refresh guard.** Concurrent calls to `refresh()`\n * coalesce onto the same Promise. Sequential calls get fresh\n * work. Without this, two parallel refreshes race on\n * `store.list()` snapshots and last-writer-wins regardless of\n * which snapshot is fresher.\n *\n * 3. **Pinned-carry-forward.** Entries registered with\n * `{ pinned: true }` survive refresh without rebuild. Adopters\n * pin their built-in tenants once at startup and let `refresh()`\n * rebuild only the dynamic ones from config. Pin always wins\n * over `pending` writes — refresh callbacks cannot accidentally\n * overwrite a pinned id.\n *\n * 4. **Lock-step unregister.** `unregister(id)` removes from every\n * named registry in one operation. No \"removed from adapters but\n * still in operational\" half-state.\n *\n * 5. **Per-registry typed `get`.** Caller declares per-registry value\n * types via the generic; `get('adapters', id)` returns the right\n * type without casts.\n *\n * @example\n * ```ts\n * import { createDynamicRegistry } from '@adcp/sdk/server';\n *\n * interface MyRegistries {\n * adapters: PlatformIdentity;\n * v6: AnyDecisioningPlatform;\n * operational: OperationalPlatform;\n * }\n *\n * const registry = createDynamicRegistry<MyRegistries>({\n * refresh: async pending => {\n * const configs = await store.list();\n * for (const config of configs) {\n * pending.adapters.set(config.platformId, buildAdapter(config));\n * pending.v6.set(config.platformId, buildPlatform(config));\n * pending.operational.set(config.platformId, deriveOperational(config));\n * }\n * },\n * });\n *\n * // Static — survives refresh\n * registry.register('adapters', 'snap', snapAdapter, { pinned: true });\n * registry.register('v6', 'snap', snapPlatform, { pinned: true });\n *\n * // Hot-reload (overlapping calls coalesce automatically)\n * await registry.refresh();\n *\n * // Per-request lookups\n * registry.get('adapters', platformId);\n * registry.get('operational', platformId);\n * ```\n *\n * Status: 6.10. See adcontextprotocol/adcp-client#1531.\n *\n * @public\n */\n\n/**\n * Type-level mapping from registry name to its value type. Adopters\n * provide this as the type parameter to `createDynamicRegistry` so\n * `register`, `get`, and the `pending` argument to the refresh\n * callback are all typed per registry without casts.\n */\nexport type RegistryShape = Record<string, unknown>;\n\n/**\n * The `pending` argument passed to a refresh callback. One Map per\n * registry; values are typed per `TRegistries[K]`.\n */\nexport type PendingRegistries<TRegistries extends RegistryShape> = {\n [K in keyof TRegistries]: Map<string, TRegistries[K]>;\n};\n\nexport interface DynamicRegistryRegisterOptions {\n /**\n * Mark this entry as static. Survives every refresh. Pin always\n * wins: a refresh callback that writes the same id into `pending`\n * is silently overridden by the pinned value at swap time.\n *\n * Use for built-in tenants registered at startup. Dynamic tenants\n * loaded from config should NOT be pinned — they live in `pending`\n * and rebuild on every refresh.\n *\n * **Pinned values are immutable until `unregister`.** Refresh-source\n * updates do not propagate to pinned entries. Adopters who rotate\n * credentials for a pinned tenant must explicitly call\n * `unregister(id)` and `register()` again with the new value — a\n * `refresh()` cycle alone will not pick up the rotation. If you want\n * refresh-driven updates, do not pin.\n */\n pinned?: boolean;\n\n /**\n * Permit overwriting an existing entry with the same name + id.\n * Default `false` — duplicate registration throws so silent\n * tenant clobbering doesn't ship to production.\n */\n overwrite?: boolean;\n}\n\nexport interface DynamicRegistryConfig<TRegistries extends RegistryShape> {\n /**\n * Refresh callback. Invoked when `refresh()` is called. The\n * `pending` argument is a fresh empty bundle of Maps (one per\n * registry); the callback writes the new dynamic entries into\n * each. On callback resolve, the framework atomically swaps the\n * live bundle to `pending` ∪ pinned-carry-forward.\n *\n * Concurrent calls to `refresh()` coalesce onto a single\n * invocation of this callback. Sequential calls get fresh\n * invocations.\n *\n * Optional. Adopters with no dynamic tenants (only pinned static\n * registrations) skip this and never call `refresh()`.\n */\n refresh?: (pending: PendingRegistries<TRegistries>) => Promise<void>;\n}\n\nexport interface DynamicRegistry<TRegistries extends RegistryShape> {\n /**\n * The registry names declared by `TRegistries`. Useful for\n * diagnostics and iteration.\n */\n readonly registryNames: ReadonlyArray<keyof TRegistries & string>;\n\n /**\n * Register an entry under one named registry. Throws on duplicate\n * (same registry + same id) unless `{ overwrite: true }` is set.\n * Pinned entries survive every subsequent `refresh()`.\n *\n * Overwriting a previously-pinned entry without `{ pinned: true }`\n * **clears the pin** — the entry will be dropped on the next\n * `refresh()` unless re-registered with `{ pinned: true }`.\n */\n register<K extends keyof TRegistries & string>(\n name: K,\n id: string,\n value: TRegistries[K],\n options?: DynamicRegistryRegisterOptions\n ): void;\n\n /**\n * Remove `id` from every registered registry in lock-step. No-op\n * for ids that aren't present. Pinned entries are removed too —\n * `unregister` is the only way to drop a pin without restarting\n * the process.\n */\n unregister(id: string): void;\n\n /**\n * Check whether `id` is present in the named registry. Equivalent\n * to `get(name, id) !== undefined` but skips the value allocation\n * on the hot path.\n */\n has<K extends keyof TRegistries & string>(name: K, id: string): boolean;\n\n /**\n * Look up an entry by registry name + id. Returns `undefined` for\n * unknown ids or unknown registry names (the latter is also a TS\n * type error — `K` is constrained to known names).\n */\n get<K extends keyof TRegistries & string>(name: K, id: string): TRegistries[K] | undefined;\n\n /**\n * List the ids currently registered under `name`. Includes both\n * pinned and dynamic entries. Useful for diagnostics; not on the\n * hot path.\n */\n ids<K extends keyof TRegistries & string>(name: K): string[];\n\n /**\n * Trigger the refresh callback. Rebuilds the dynamic portion of\n * the bundle from scratch; pinned entries carry forward. Concurrent\n * calls coalesce onto the same Promise.\n *\n * Returns a rejected Promise if no `refresh` callback was supplied\n * at construction (does not throw synchronously — callers can `await`\n * uniformly).\n */\n refresh(): Promise<void>;\n}\n\ninterface InternalBundle<TRegistries extends RegistryShape> {\n maps: { [K in keyof TRegistries]: Map<string, TRegistries[K]> };\n pinned: { [K in keyof TRegistries]: Set<string> };\n}\n\n/**\n * Build a hot-reloadable multi-registry. Names of the registries and\n * their value types come from the `TRegistries` type parameter.\n *\n * @example\n * ```ts\n * interface MyRegistries {\n * adapters: PlatformIdentity;\n * v6: AnyDecisioningPlatform;\n * operational: OperationalPlatform;\n * }\n *\n * const registry = createDynamicRegistry<MyRegistries>({\n * refresh: async pending => { ... },\n * });\n * ```\n *\n * The runtime call needs to know which registry names exist, so the\n * caller passes them either implicitly (via the first `register()`\n * call) or — recommended — eagerly by registering each at construction\n * with no entries. To make the registry-name set declarative, prefer\n * the explicit-names overload below.\n */\nexport function createDynamicRegistry<TRegistries extends RegistryShape>(\n config: DynamicRegistryConfig<TRegistries> & {\n /**\n * The list of registry names. Required so the framework can\n * pre-allocate Maps and validate `register`/`get` calls before\n * any entry is added. Pass `as const` to preserve per-registry\n * value types in `get` and `register`.\n */\n registries: ReadonlyArray<keyof TRegistries & string>;\n }\n): DynamicRegistry<TRegistries> {\n const names = [...config.registries];\n\n // Validate construction: empty registries are useless; duplicate names\n // would shadow each other in the bundle Maps and produce confusing\n // \"unknown registry\" errors at lookup time. Surface both at\n // construction with specific messages.\n if (names.length === 0) {\n throw new Error('createDynamicRegistry: `registries` must contain at least one name.');\n }\n const dedupedNames = new Set(names);\n if (dedupedNames.size !== names.length) {\n const dupes = names.filter((n, i) => names.indexOf(n) !== i);\n throw new Error(\n `createDynamicRegistry: duplicate registry name(s): ${dupes.map(n => JSON.stringify(n)).join(', ')}.`\n );\n }\n\n function emptyBundle(): InternalBundle<TRegistries> {\n const maps = {} as InternalBundle<TRegistries>['maps'];\n const pinned = {} as InternalBundle<TRegistries>['pinned'];\n for (const name of names) {\n maps[name as keyof TRegistries] = new Map();\n pinned[name as keyof TRegistries] = new Set();\n }\n return { maps, pinned };\n }\n\n // Single-pointer atomic-swap target. All reads and writes go through\n // this reference. Refresh builds a new InternalBundle to completion\n // and reassigns the reference in one statement — atomic across\n // `await` for any concurrent reader.\n let bundle: InternalBundle<TRegistries> = emptyBundle();\n\n // In-flight refresh guard. Concurrent calls observe the same Promise\n // until it settles, then the next call gets a fresh one.\n let inflight: Promise<void> | null = null;\n\n function assertKnownRegistry(name: string): void {\n if (!dedupedNames.has(name)) {\n throw new Error(\n `createDynamicRegistry: unknown registry name ${JSON.stringify(name)}. ` +\n `Known names: ${names.map(n => JSON.stringify(n)).join(', ')}.`\n );\n }\n }\n\n return {\n registryNames: Object.freeze([...names]) as ReadonlyArray<keyof TRegistries & string>,\n\n register(name, id, value, options) {\n assertKnownRegistry(name);\n const map = bundle.maps[name];\n const allowOverwrite = options?.overwrite === true;\n if (map.has(id) && !allowOverwrite) {\n throw new Error(\n `createDynamicRegistry: duplicate registration for ${JSON.stringify(name)}/${JSON.stringify(id)}. ` +\n `Pass { overwrite: true } if the duplicate is intentional.`\n );\n }\n // Non-pinned register during in-flight refresh writes to the\n // LIVE bundle. The live bundle becomes `liveBundle` in the\n // refresh closure; at swap, the new bundle is built from\n // `pending` ∪ `liveBundle.pinned` — so a non-pinned mid-refresh\n // register is silently dropped at swap time. This is documented\n // semantics; callers who race `register` against `refresh`\n // either pin the entry (so it survives the swap) or serialize\n // the operations.\n map.set(id, value);\n if (options?.pinned === true) {\n bundle.pinned[name].add(id);\n } else {\n // Clear any stale pin: overwriting without { pinned: true }\n // means the caller no longer wants carry-forward semantics.\n bundle.pinned[name].delete(id);\n }\n },\n\n unregister(id) {\n for (const name of names) {\n bundle.maps[name].delete(id);\n bundle.pinned[name].delete(id);\n }\n },\n\n has(name, id) {\n assertKnownRegistry(name);\n return bundle.maps[name].has(id);\n },\n\n get(name, id) {\n assertKnownRegistry(name);\n return bundle.maps[name].get(id);\n },\n\n ids(name) {\n assertKnownRegistry(name);\n return [...bundle.maps[name].keys()];\n },\n\n // CRITICAL: do not mark `async`. The non-async signature is\n // load-bearing for the coalesce contract — `async refresh()` would\n // wrap the returned Promise, so two parallel callers each get a\n // NEW wrapper around the shared `inflight`, not `inflight` itself.\n // Adopters who `Promise.all([a, b])` rely on reference equality\n // so a single completion settles all waiters at once. Pinned by\n // the test \"concurrent refresh() calls coalesce\".\n refresh(): Promise<void> {\n if (!config.refresh) {\n return Promise.reject(\n new Error('createDynamicRegistry: refresh() called but no `refresh` callback was supplied at construction.')\n );\n }\n if (inflight) return inflight;\n\n const callback = config.refresh;\n inflight = (async () => {\n try {\n // Capture the live pinned snapshot BEFORE building pending.\n // Adopters who call `register({ pinned: true })` between the\n // refresh-start and the callback's first await point still\n // get carry-forward — the snapshot is the as-of-await-zero\n // bundle, plus any in-flight pin registrations.\n const liveBundle = bundle;\n\n // pending = fresh empty Maps. Callback fills them.\n const pending = {} as PendingRegistries<TRegistries>;\n for (const name of names) {\n pending[name as keyof TRegistries] = new Map() as PendingRegistries<TRegistries>[keyof TRegistries];\n }\n\n await callback(pending);\n\n // Build new bundle: pending values first, then pinned values\n // from the live bundle. Pin always wins — a callback that\n // wrote a pinned id into `pending` is silently overridden.\n const next = emptyBundle();\n for (const name of names) {\n const nextMap = next.maps[name];\n for (const [k, v] of pending[name]) nextMap.set(k, v);\n for (const pinnedId of liveBundle.pinned[name]) {\n const pinnedValue = liveBundle.maps[name].get(pinnedId);\n if (pinnedValue !== undefined) {\n nextMap.set(pinnedId, pinnedValue);\n next.pinned[name].add(pinnedId);\n }\n }\n }\n\n // Single-pointer reassignment. Atomic across `await` —\n // a concurrent reader sees liveBundle until this line and\n // `next` after.\n bundle = next;\n } finally {\n inflight = null;\n }\n })();\n\n return inflight;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/dynamic-registry.ts"],"sourcesContent":["/**\n * Hot-reloadable multi-registry plumbing.\n *\n * `createDynamicRegistry` packages the multi-registry-atomicity idiom\n * every adopter that hot-reloads tenants from a database independently\n * rebuilds. The shim in scope3data/agentic-adapters built this three\n * times (v5 PlatformAdapter, v6 DecisioningPlatform, v6\n * OperationalPlatform) before the pattern crystallized — each\n * iteration with its own hard-won bug fixes.\n *\n * Lessons baked in:\n *\n * 1. **Single-pointer atomic swap.** The bundle of all registry Maps\n * is held behind one `let bundle` reference. A refresh builds the\n * new bundle to completion in a side struct and reassigns the\n * pointer in one statement. A concurrent reader (per-request\n * lookup) crossing an `await` mid-refresh sees the OLD bundle\n * until swap, the NEW bundle after — never a mix where one\n * registry has the new data and another doesn't.\n *\n * 2. **In-flight refresh guard.** Concurrent calls to `refresh()`\n * coalesce onto the same Promise. Sequential calls get fresh\n * work. Without this, two parallel refreshes race on\n * `store.list()` snapshots and last-writer-wins regardless of\n * which snapshot is fresher.\n *\n * 3. **Pinned-carry-forward.** Entries registered with\n * `{ pinned: true }` survive refresh without rebuild. Adopters\n * pin their built-in tenants once at startup and let `refresh()`\n * rebuild only the dynamic ones from config. Pin always wins\n * over `pending` writes — refresh callbacks cannot accidentally\n * overwrite a pinned id.\n *\n * 4. **Lock-step unregister.** `unregister(id)` removes from every\n * named registry in one operation. No \"removed from adapters but\n * still in operational\" half-state.\n *\n * 5. **Per-registry typed `get`.** Caller declares per-registry value\n * types via the generic; `get('adapters', id)` returns the right\n * type without casts.\n *\n * @example\n * ```ts\n * import { createDynamicRegistry } from '@adcp/sdk/server';\n *\n * interface MyRegistries {\n * adapters: PlatformIdentity;\n * v6: AnyDecisioningPlatform;\n * operational: OperationalPlatform;\n * }\n *\n * const registry = createDynamicRegistry<MyRegistries>({\n * refresh: async pending => {\n * const configs = await store.list();\n * for (const config of configs) {\n * pending.adapters.set(config.platformId, buildAdapter(config));\n * pending.v6.set(config.platformId, buildPlatform(config));\n * pending.operational.set(config.platformId, deriveOperational(config));\n * }\n * },\n * });\n *\n * // Static — survives refresh\n * registry.register('adapters', 'snap', snapAdapter, { pinned: true });\n * registry.register('v6', 'snap', snapPlatform, { pinned: true });\n *\n * // Hot-reload (overlapping calls coalesce automatically)\n * await registry.refresh();\n *\n * // Per-request lookups\n * registry.get('adapters', platformId);\n * registry.get('operational', platformId);\n * ```\n *\n * Status: 6.10. See adcontextprotocol/adcp-client#1531.\n *\n * @public\n */\n\n/**\n * Type-level mapping from registry name to its value type. Adopters\n * provide this as the type parameter to `createDynamicRegistry` so\n * `register`, `get`, and the `pending` argument to the refresh\n * callback are all typed per registry without casts.\n */\nexport type RegistryShape = Record<string, unknown>;\n\n/**\n * The `pending` argument passed to a refresh callback. One Map per\n * registry; values are typed per `TRegistries[K]`.\n */\nexport type PendingRegistries<TRegistries extends RegistryShape> = {\n [K in keyof TRegistries]: Map<string, TRegistries[K]>;\n};\n\nexport interface DynamicRegistryRegisterOptions {\n /**\n * Mark this entry as static. Survives every refresh. Pin always\n * wins: a refresh callback that writes the same id into `pending`\n * is silently overridden by the pinned value at swap time.\n *\n * Use for built-in tenants registered at startup. Dynamic tenants\n * loaded from config should NOT be pinned — they live in `pending`\n * and rebuild on every refresh.\n *\n * **Pinned values are immutable until `unregister`.** Refresh-source\n * updates do not propagate to pinned entries. Adopters who rotate\n * credentials for a pinned tenant must explicitly call\n * `unregister(id)` and `register()` again with the new value — a\n * `refresh()` cycle alone will not pick up the rotation. If you want\n * refresh-driven updates, do not pin.\n */\n pinned?: boolean;\n\n /**\n * Permit overwriting an existing entry with the same name + id.\n * Default `false` — duplicate registration throws so silent\n * tenant clobbering doesn't ship to production.\n */\n overwrite?: boolean;\n}\n\nexport interface DynamicRegistryConfig<TRegistries extends RegistryShape> {\n /**\n * Refresh callback. Invoked when `refresh()` is called. The\n * `pending` argument is a fresh empty bundle of Maps (one per\n * registry); the callback writes the new dynamic entries into\n * each. On callback resolve, the framework atomically swaps the\n * live bundle to `pending` ∪ pinned-carry-forward.\n *\n * Concurrent calls to `refresh()` coalesce onto a single\n * invocation of this callback. Sequential calls get fresh\n * invocations.\n *\n * Optional. Adopters with no dynamic tenants (only pinned static\n * registrations) skip this and never call `refresh()`.\n */\n refresh?: (pending: PendingRegistries<TRegistries>) => Promise<void>;\n}\n\nexport interface DynamicRegistry<TRegistries extends RegistryShape> {\n /**\n * The registry names declared by `TRegistries`. Useful for\n * diagnostics and iteration.\n */\n readonly registryNames: ReadonlyArray<keyof TRegistries & string>;\n\n /**\n * Register an entry under one named registry. Throws on duplicate\n * (same registry + same id) unless `{ overwrite: true }` is set.\n * Pinned entries survive every subsequent `refresh()`.\n *\n * Overwriting a previously-pinned entry without `{ pinned: true }`\n * **clears the pin** — the entry will be dropped on the next\n * `refresh()` unless re-registered with `{ pinned: true }`.\n */\n register<K extends keyof TRegistries & string>(\n name: K,\n id: string,\n value: TRegistries[K],\n options?: DynamicRegistryRegisterOptions\n ): void;\n\n /**\n * Remove `id` from every registered registry in lock-step. No-op\n * for ids that aren't present. Pinned entries are removed too —\n * `unregister` is the only way to drop a pin without restarting\n * the process.\n */\n unregister(id: string): void;\n\n /**\n * Check whether `id` is present in the named registry. Equivalent\n * to `get(name, id) !== undefined` but skips the value allocation\n * on the hot path.\n */\n has<K extends keyof TRegistries & string>(name: K, id: string): boolean;\n\n /**\n * Look up an entry by registry name + id. Returns `undefined` for\n * unknown ids or unknown registry names (the latter is also a TS\n * type error — `K` is constrained to known names).\n */\n get<K extends keyof TRegistries & string>(name: K, id: string): TRegistries[K] | undefined;\n\n /**\n * List the ids currently registered under `name`. Includes both\n * pinned and dynamic entries. Useful for diagnostics; not on the\n * hot path.\n */\n ids<K extends keyof TRegistries & string>(name: K): string[];\n\n /**\n * Trigger the refresh callback. Rebuilds the dynamic portion of\n * the bundle from scratch; pinned entries carry forward. Concurrent\n * calls coalesce onto the same Promise.\n *\n * Returns a rejected Promise if no `refresh` callback was supplied\n * at construction (does not throw synchronously — callers can `await`\n * uniformly).\n */\n refresh(): Promise<void>;\n}\n\ninterface InternalBundle<TRegistries extends RegistryShape> {\n maps: { [K in keyof TRegistries]: Map<string, TRegistries[K]> };\n pinned: { [K in keyof TRegistries]: Set<string> };\n}\n\n/**\n * Build a hot-reloadable multi-registry. Names of the registries and\n * their value types come from the `TRegistries` type parameter.\n *\n * @example\n * ```ts\n * interface MyRegistries {\n * adapters: PlatformIdentity;\n * v6: AnyDecisioningPlatform;\n * operational: OperationalPlatform;\n * }\n *\n * const registry = createDynamicRegistry<MyRegistries>({\n * refresh: async pending => { ... },\n * });\n * ```\n *\n * The runtime call needs to know which registry names exist, so the\n * caller passes them either implicitly (via the first `register()`\n * call) or — recommended — eagerly by registering each at construction\n * with no entries. To make the registry-name set declarative, prefer\n * the explicit-names overload below.\n */\nexport function createDynamicRegistry<TRegistries extends RegistryShape>(\n config: DynamicRegistryConfig<TRegistries> & {\n /**\n * The list of registry names. Required so the framework can\n * pre-allocate Maps and validate `register`/`get` calls before\n * any entry is added. Pass `as const` to preserve per-registry\n * value types in `get` and `register`.\n */\n registries: ReadonlyArray<keyof TRegistries & string>;\n }\n): DynamicRegistry<TRegistries> {\n const names = [...config.registries];\n\n // Validate construction: empty registries are useless; duplicate names\n // would shadow each other in the bundle Maps and produce confusing\n // \"unknown registry\" errors at lookup time. Surface both at\n // construction with specific messages.\n if (names.length === 0) {\n throw new Error('createDynamicRegistry: `registries` must contain at least one name.');\n }\n const dedupedNames = new Set(names);\n if (dedupedNames.size !== names.length) {\n const dupes = names.filter((n, i) => names.indexOf(n) !== i);\n throw new Error(\n `createDynamicRegistry: duplicate registry name(s): ${dupes.map(n => JSON.stringify(n)).join(', ')}.`\n );\n }\n\n function emptyBundle(): InternalBundle<TRegistries> {\n const maps = {} as InternalBundle<TRegistries>['maps'];\n const pinned = {} as InternalBundle<TRegistries>['pinned'];\n for (const name of names) {\n maps[name as keyof TRegistries] = new Map();\n pinned[name as keyof TRegistries] = new Set();\n }\n return { maps, pinned };\n }\n\n // Single-pointer atomic-swap target. All reads and writes go through\n // this reference. Refresh builds a new InternalBundle to completion\n // and reassigns the reference in one statement — atomic across\n // `await` for any concurrent reader.\n let bundle: InternalBundle<TRegistries> = emptyBundle();\n\n // In-flight refresh guard. Concurrent calls observe the same Promise\n // until it settles, then the next call gets a fresh one.\n let inflight: Promise<void> | null = null;\n\n function assertKnownRegistry(name: string): void {\n if (!dedupedNames.has(name)) {\n throw new Error(\n `createDynamicRegistry: unknown registry name ${JSON.stringify(name)}. ` +\n `Known names: ${names.map(n => JSON.stringify(n)).join(', ')}.`\n );\n }\n }\n\n return {\n registryNames: Object.freeze([...names]) as ReadonlyArray<keyof TRegistries & string>,\n\n register(name, id, value, options) {\n assertKnownRegistry(name);\n const map = bundle.maps[name];\n const allowOverwrite = options?.overwrite === true;\n if (map.has(id) && !allowOverwrite) {\n throw new Error(\n `createDynamicRegistry: duplicate registration for ${JSON.stringify(name)}/${JSON.stringify(id)}. ` +\n `Pass { overwrite: true } if the duplicate is intentional.`\n );\n }\n // Non-pinned register during in-flight refresh writes to the\n // LIVE bundle. The live bundle becomes `liveBundle` in the\n // refresh closure; at swap, the new bundle is built from\n // `pending` ∪ `liveBundle.pinned` — so a non-pinned mid-refresh\n // register is silently dropped at swap time. This is documented\n // semantics; callers who race `register` against `refresh`\n // either pin the entry (so it survives the swap) or serialize\n // the operations.\n map.set(id, value);\n if (options?.pinned === true) {\n bundle.pinned[name].add(id);\n } else {\n // Clear any stale pin: overwriting without { pinned: true }\n // means the caller no longer wants carry-forward semantics.\n bundle.pinned[name].delete(id);\n }\n },\n\n unregister(id) {\n for (const name of names) {\n bundle.maps[name].delete(id);\n bundle.pinned[name].delete(id);\n }\n },\n\n has(name, id) {\n assertKnownRegistry(name);\n return bundle.maps[name].has(id);\n },\n\n get(name, id) {\n assertKnownRegistry(name);\n return bundle.maps[name].get(id);\n },\n\n ids(name) {\n assertKnownRegistry(name);\n return [...bundle.maps[name].keys()];\n },\n\n // CRITICAL: do not mark `async`. The non-async signature is\n // load-bearing for the coalesce contract — `async refresh()` would\n // wrap the returned Promise, so two parallel callers each get a\n // NEW wrapper around the shared `inflight`, not `inflight` itself.\n // Adopters who `Promise.all([a, b])` rely on reference equality\n // so a single completion settles all waiters at once. Pinned by\n // the test \"concurrent refresh() calls coalesce\".\n refresh(): Promise<void> {\n if (!config.refresh) {\n return Promise.reject(\n new Error('createDynamicRegistry: refresh() called but no `refresh` callback was supplied at construction.')\n );\n }\n if (inflight) return inflight;\n\n const callback = config.refresh;\n inflight = (async () => {\n try {\n // Capture the live pinned snapshot BEFORE building pending.\n // Adopters who call `register({ pinned: true })` between the\n // refresh-start and the callback's first await point still\n // get carry-forward — the snapshot is the as-of-await-zero\n // bundle, plus any in-flight pin registrations.\n const liveBundle = bundle;\n\n // pending = fresh empty Maps. Callback fills them.\n const pending = {} as PendingRegistries<TRegistries>;\n for (const name of names) {\n pending[name as keyof TRegistries] = new Map() as PendingRegistries<TRegistries>[keyof TRegistries];\n }\n\n await callback(pending);\n\n // Build new bundle: pending values first, then pinned values\n // from the live bundle. Pin always wins — a callback that\n // wrote a pinned id into `pending` is silently overridden.\n const next = emptyBundle();\n for (const name of names) {\n const nextMap = next.maps[name];\n for (const [k, v] of pending[name]) nextMap.set(k, v);\n for (const pinnedId of liveBundle.pinned[name]) {\n const pinnedValue = liveBundle.maps[name].get(pinnedId);\n if (pinnedValue !== undefined) {\n nextMap.set(pinnedId, pinnedValue);\n next.pinned[name].add(pinnedId);\n }\n }\n }\n\n // Single-pointer reassignment. Atomic across `await` —\n // a concurrent reader sees liveBundle until this line and\n // `next` after.\n bundle = next;\n } finally {\n inflight = null;\n }\n })();\n\n return inflight;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
const DEFAULT_ERROR_ENVELOPE_FIELDS = Object.freeze(/* @__PURE__ */ new Set(["context"]));
|
|
8
2
|
const ERROR_ENVELOPE_FIELD_ALLOWLIST = Object.freeze({
|
|
9
3
|
// Spec-defined: conflict is not a replay; `replayed` intentionally
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/envelope-allowlist.ts"],"sourcesContent":["/**\n * Envelope-allowlist governance for AdCP error responses.\n *\n * Two orthogonal concerns both govern what round-trips on an AdCP error\n * envelope. They live side-by-side here so that a change to one forces\n * the reviewer to weigh the other (security review M4 on #788 — the\n * pair is conceptually related and can drift in separate files).\n *\n * - `ERROR_ENVELOPE_FIELD_ALLOWLIST` governs keys that may appear as\n * SIBLINGS of `adcp_error` on the wire response. Enforced by\n * `wrapEnvelope` in `./wrap-envelope.ts`.\n * - `ADCP_ERROR_FIELD_ALLOWLIST` governs keys that may appear INSIDE\n * the `adcp_error` block, keyed by error code. Consumed by\n * `adcpError()` (which filters its output to the allowlisted set)\n * AND by the dispatcher in `create-adcp-server.ts` (re-applied to\n * every handler-returned envelope via `sanitizeAdcpErrorEnvelope`\n * — defence-in-depth for handlers that hand-roll an error envelope\n * instead of going through the builder). The same set powers the\n * `idempotency.conflict_no_payload_leak` invariant in\n * `../testing/storyboard/default-invariants.ts`. The legacy\n * `CONFLICT_ADCP_ERROR_ALLOWLIST` export is a convenience alias for\n * the `IDEMPOTENCY_CONFLICT` entry.\n *\n * Both sets apply to the same wire response but at different nesting\n * levels. A seller that needs to extend one should audit whether the\n * other also needs updating.\n *\n * Hand-rolling an error envelope? Prefer `adcpError(code, options)` —\n * it wraps both transport layers and applies this allowlist for you.\n * The dispatcher sanitizer is a belt-and-suspenders, not a primary API.\n */\n\n/**\n * Fail-closed default allowlist for unknown error codes.\n *\n * An error code without an explicit entry in\n * `ERROR_ENVELOPE_FIELD_ALLOWLIST` gets this set — only `context`\n * round-trips (matching the invariant that every allowlist permits\n * correlation tracing). Sellers that want `replayed` or `operation_id`\n * on a bespoke error code must register it explicitly.\n *\n * This is the fail-closed posture — sellers MUST opt in to any field\n * they want on an error envelope, rather than inheriting success-path\n * semantics by default. Security review M3 on #788.\n */\nexport const DEFAULT_ERROR_ENVELOPE_FIELDS: ReadonlySet<string> = Object.freeze(new Set(['context']));\n\n/**\n * Per-error-code envelope-field allowlist.\n *\n * A code listed here restricts which envelope fields `wrapEnvelope`\n * will attach on top of the error's inner payload. Codes NOT listed\n * here receive `DEFAULT_ERROR_ENVELOPE_FIELDS` (fail-closed —\n * `context` only).\n *\n * `IDEMPOTENCY_CONFLICT` deliberately excludes `replayed`: the\n * conflict path in `create-adcp-server.ts` builds its error via\n * `finalize()`, which only echoes `context` and never calls\n * `stampReplayed`. The framework's own behavior is preserved.\n *\n * **Invariant**: every allowlist set MUST include `'context'`.\n * `ensureContextEcho` enforces this at module load so new error codes\n * can't accidentally drop context echo (which sellers rely on for\n * correlation tracing across both success and error paths).\n */\nexport const ERROR_ENVELOPE_FIELD_ALLOWLIST: Readonly<Record<string, ReadonlySet<string>>> = Object.freeze({\n // Spec-defined: conflict is not a replay; `replayed` intentionally\n // dropped. `operation_id` round-trips for async-op correlation.\n IDEMPOTENCY_CONFLICT: new Set(['context', 'operation_id']),\n});\n\n/**\n * Per-error-code allowlist for keys permitted INSIDE the `adcp_error`\n * block. The `adcpError()` builder consults this table and drops any\n * field the caller passes that isn't allowlisted for the given code —\n * the allowlist IS the contract for what a framework-emitted error may\n * carry. Storyboard invariants enforce the same contract on the wire.\n *\n * A code without an explicit entry permits everything `adcpError()`\n * would otherwise emit — the default case is pass-through. Sellers who\n * want a bespoke code to be strict should register it here (or wrap\n * `adcpError()` with their own sanitizer).\n *\n * **Scope warning for future contributors:** only register a code here\n * if it belongs to the AdCP standard error vocabulary (`enums/error-code.json`).\n * Per `core/error.json`, clients \"MUST handle unknown codes gracefully\n * by falling back to the recovery classification\" — so dropping\n * `recovery` on a vendor / non-standard code makes the response\n * genuinely lossy for any buyer agent that doesn't have the code in\n * its local vocabulary.\n *\n * `IDEMPOTENCY_CONFLICT` is narrow on purpose: a conflict response MUST\n * NOT echo the prior request payload or cached response (stolen-key\n * read oracle defence). Standard recovery metadata is allowed because it\n * is normalized to the fixed classifier, not trusted as caller-provided\n * prior request or cached response data.\n * `retry_after` is excluded: no framework path currently emits it on conflict, and a seller\n * that computed `retry_after = cached_entry_age` would leak the prior\n * payload's creation time (a distinguisher between \"key never seen\" vs\n * \"key seen N seconds ago\"). Retry hints belong on transient codes\n * (`SERVICE_UNAVAILABLE`, `RATE_LIMITED`), not on a terminal conflict.\n * `adcpError()` and the dispatcher sanitizer both enforce this.\n */\nexport const ADCP_ERROR_FIELD_ALLOWLIST: Readonly<Record<string, ReadonlySet<string>>> = Object.freeze({\n IDEMPOTENCY_CONFLICT: new Set([\n 'code',\n 'message',\n 'recovery',\n 'status',\n 'correlation_id',\n 'request_id',\n 'operation_id',\n ]),\n // `IDEMPOTENCY_IN_FLIGHT` (AdCP 3.1) carries `recovery: transient` plus a\n // store-derived `retry_after` so transient-aware buyer SDKs can replay\n // shortly. Allowlist registration is defense-in-depth — `adcpError()` at\n // the in-flight call site only assembles framework-controlled keys today,\n // but the registration forces any future caller that tries to attach\n // payload fingerprints onto the in-flight envelope to land here in code\n // review first.\n IDEMPOTENCY_IN_FLIGHT: new Set([\n 'code',\n 'message',\n 'recovery',\n 'retry_after',\n 'status',\n 'correlation_id',\n 'request_id',\n 'operation_id',\n ]),\n});\n\n/**\n * Convenience alias for the `IDEMPOTENCY_CONFLICT` entry in\n * {@link ADCP_ERROR_FIELD_ALLOWLIST}. Kept as a named export because\n * the default `idempotency.conflict_no_payload_leak` invariant and a\n * handful of consumer tests reach for it by name; new code should use\n * `ADCP_ERROR_FIELD_ALLOWLIST.IDEMPOTENCY_CONFLICT` directly.\n */\nexport const CONFLICT_ADCP_ERROR_ALLOWLIST: ReadonlySet<string> = ADCP_ERROR_FIELD_ALLOWLIST.IDEMPOTENCY_CONFLICT!;\n\n/**\n * Sanity check: every registered allowlist must permit `context` echo.\n * Missing entries fail at module load, not at runtime request time —\n * correlation tracing breaks if any error path silently stops echoing it.\n */\nfunction ensureContextEcho(allowlist: Readonly<Record<string, ReadonlySet<string>>>): void {\n if (!DEFAULT_ERROR_ENVELOPE_FIELDS.has('context')) {\n throw new Error(\n \"DEFAULT_ERROR_ENVELOPE_FIELDS must include 'context' so correlation \" + 'ids can round-trip on error envelopes.'\n );\n }\n for (const [code, fields] of Object.entries(allowlist)) {\n if (!fields.has('context')) {\n throw new Error(\n `ERROR_ENVELOPE_FIELD_ALLOWLIST['${code}'] is missing 'context'. ` +\n `Every error-code allowlist must include 'context' so correlation ` +\n `ids can round-trip on error envelopes.`\n );\n }\n }\n}\nensureContextEcho(ERROR_ENVELOPE_FIELD_ALLOWLIST);\n\n/**\n * Sanity check: every inside-`adcp_error` allowlist MUST permit `code`\n * and `message` — they are the only two required fields in the AdCP\n * error schema and `adcpError()` can't produce a valid payload without\n * them. Fail at module load so new allowlist entries can't silently ship\n * a shape that would drop the fields every consumer expects.\n */\nfunction ensureCoreAdcpErrorFields(allowlist: Readonly<Record<string, ReadonlySet<string>>>): void {\n for (const [code, fields] of Object.entries(allowlist)) {\n for (const required of ['code', 'message']) {\n if (!fields.has(required)) {\n throw new Error(\n `ADCP_ERROR_FIELD_ALLOWLIST['${code}'] is missing '${required}'. ` +\n `Every inside-adcp_error allowlist must include 'code' and 'message' — ` +\n `they are the only two required fields on the AdCP error schema.`\n );\n }\n }\n }\n}\nensureCoreAdcpErrorFields(ADCP_ERROR_FIELD_ALLOWLIST);\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/envelope-allowlist.ts"],"sourcesContent":["/**\n * Envelope-allowlist governance for AdCP error responses.\n *\n * Two orthogonal concerns both govern what round-trips on an AdCP error\n * envelope. They live side-by-side here so that a change to one forces\n * the reviewer to weigh the other (security review M4 on #788 — the\n * pair is conceptually related and can drift in separate files).\n *\n * - `ERROR_ENVELOPE_FIELD_ALLOWLIST` governs keys that may appear as\n * SIBLINGS of `adcp_error` on the wire response. Enforced by\n * `wrapEnvelope` in `./wrap-envelope.ts`.\n * - `ADCP_ERROR_FIELD_ALLOWLIST` governs keys that may appear INSIDE\n * the `adcp_error` block, keyed by error code. Consumed by\n * `adcpError()` (which filters its output to the allowlisted set)\n * AND by the dispatcher in `create-adcp-server.ts` (re-applied to\n * every handler-returned envelope via `sanitizeAdcpErrorEnvelope`\n * — defence-in-depth for handlers that hand-roll an error envelope\n * instead of going through the builder). The same set powers the\n * `idempotency.conflict_no_payload_leak` invariant in\n * `../testing/storyboard/default-invariants.ts`. The legacy\n * `CONFLICT_ADCP_ERROR_ALLOWLIST` export is a convenience alias for\n * the `IDEMPOTENCY_CONFLICT` entry.\n *\n * Both sets apply to the same wire response but at different nesting\n * levels. A seller that needs to extend one should audit whether the\n * other also needs updating.\n *\n * Hand-rolling an error envelope? Prefer `adcpError(code, options)` —\n * it wraps both transport layers and applies this allowlist for you.\n * The dispatcher sanitizer is a belt-and-suspenders, not a primary API.\n */\n\n/**\n * Fail-closed default allowlist for unknown error codes.\n *\n * An error code without an explicit entry in\n * `ERROR_ENVELOPE_FIELD_ALLOWLIST` gets this set — only `context`\n * round-trips (matching the invariant that every allowlist permits\n * correlation tracing). Sellers that want `replayed` or `operation_id`\n * on a bespoke error code must register it explicitly.\n *\n * This is the fail-closed posture — sellers MUST opt in to any field\n * they want on an error envelope, rather than inheriting success-path\n * semantics by default. Security review M3 on #788.\n */\nexport const DEFAULT_ERROR_ENVELOPE_FIELDS: ReadonlySet<string> = Object.freeze(new Set(['context']));\n\n/**\n * Per-error-code envelope-field allowlist.\n *\n * A code listed here restricts which envelope fields `wrapEnvelope`\n * will attach on top of the error's inner payload. Codes NOT listed\n * here receive `DEFAULT_ERROR_ENVELOPE_FIELDS` (fail-closed —\n * `context` only).\n *\n * `IDEMPOTENCY_CONFLICT` deliberately excludes `replayed`: the\n * conflict path in `create-adcp-server.ts` builds its error via\n * `finalize()`, which only echoes `context` and never calls\n * `stampReplayed`. The framework's own behavior is preserved.\n *\n * **Invariant**: every allowlist set MUST include `'context'`.\n * `ensureContextEcho` enforces this at module load so new error codes\n * can't accidentally drop context echo (which sellers rely on for\n * correlation tracing across both success and error paths).\n */\nexport const ERROR_ENVELOPE_FIELD_ALLOWLIST: Readonly<Record<string, ReadonlySet<string>>> = Object.freeze({\n // Spec-defined: conflict is not a replay; `replayed` intentionally\n // dropped. `operation_id` round-trips for async-op correlation.\n IDEMPOTENCY_CONFLICT: new Set(['context', 'operation_id']),\n});\n\n/**\n * Per-error-code allowlist for keys permitted INSIDE the `adcp_error`\n * block. The `adcpError()` builder consults this table and drops any\n * field the caller passes that isn't allowlisted for the given code —\n * the allowlist IS the contract for what a framework-emitted error may\n * carry. Storyboard invariants enforce the same contract on the wire.\n *\n * A code without an explicit entry permits everything `adcpError()`\n * would otherwise emit — the default case is pass-through. Sellers who\n * want a bespoke code to be strict should register it here (or wrap\n * `adcpError()` with their own sanitizer).\n *\n * **Scope warning for future contributors:** only register a code here\n * if it belongs to the AdCP standard error vocabulary (`enums/error-code.json`).\n * Per `core/error.json`, clients \"MUST handle unknown codes gracefully\n * by falling back to the recovery classification\" — so dropping\n * `recovery` on a vendor / non-standard code makes the response\n * genuinely lossy for any buyer agent that doesn't have the code in\n * its local vocabulary.\n *\n * `IDEMPOTENCY_CONFLICT` is narrow on purpose: a conflict response MUST\n * NOT echo the prior request payload or cached response (stolen-key\n * read oracle defence). Standard recovery metadata is allowed because it\n * is normalized to the fixed classifier, not trusted as caller-provided\n * prior request or cached response data.\n * `retry_after` is excluded: no framework path currently emits it on conflict, and a seller\n * that computed `retry_after = cached_entry_age` would leak the prior\n * payload's creation time (a distinguisher between \"key never seen\" vs\n * \"key seen N seconds ago\"). Retry hints belong on transient codes\n * (`SERVICE_UNAVAILABLE`, `RATE_LIMITED`), not on a terminal conflict.\n * `adcpError()` and the dispatcher sanitizer both enforce this.\n */\nexport const ADCP_ERROR_FIELD_ALLOWLIST: Readonly<Record<string, ReadonlySet<string>>> = Object.freeze({\n IDEMPOTENCY_CONFLICT: new Set([\n 'code',\n 'message',\n 'recovery',\n 'status',\n 'correlation_id',\n 'request_id',\n 'operation_id',\n ]),\n // `IDEMPOTENCY_IN_FLIGHT` (AdCP 3.1) carries `recovery: transient` plus a\n // store-derived `retry_after` so transient-aware buyer SDKs can replay\n // shortly. Allowlist registration is defense-in-depth — `adcpError()` at\n // the in-flight call site only assembles framework-controlled keys today,\n // but the registration forces any future caller that tries to attach\n // payload fingerprints onto the in-flight envelope to land here in code\n // review first.\n IDEMPOTENCY_IN_FLIGHT: new Set([\n 'code',\n 'message',\n 'recovery',\n 'retry_after',\n 'status',\n 'correlation_id',\n 'request_id',\n 'operation_id',\n ]),\n});\n\n/**\n * Convenience alias for the `IDEMPOTENCY_CONFLICT` entry in\n * {@link ADCP_ERROR_FIELD_ALLOWLIST}. Kept as a named export because\n * the default `idempotency.conflict_no_payload_leak` invariant and a\n * handful of consumer tests reach for it by name; new code should use\n * `ADCP_ERROR_FIELD_ALLOWLIST.IDEMPOTENCY_CONFLICT` directly.\n */\nexport const CONFLICT_ADCP_ERROR_ALLOWLIST: ReadonlySet<string> = ADCP_ERROR_FIELD_ALLOWLIST.IDEMPOTENCY_CONFLICT!;\n\n/**\n * Sanity check: every registered allowlist must permit `context` echo.\n * Missing entries fail at module load, not at runtime request time —\n * correlation tracing breaks if any error path silently stops echoing it.\n */\nfunction ensureContextEcho(allowlist: Readonly<Record<string, ReadonlySet<string>>>): void {\n if (!DEFAULT_ERROR_ENVELOPE_FIELDS.has('context')) {\n throw new Error(\n \"DEFAULT_ERROR_ENVELOPE_FIELDS must include 'context' so correlation \" + 'ids can round-trip on error envelopes.'\n );\n }\n for (const [code, fields] of Object.entries(allowlist)) {\n if (!fields.has('context')) {\n throw new Error(\n `ERROR_ENVELOPE_FIELD_ALLOWLIST['${code}'] is missing 'context'. ` +\n `Every error-code allowlist must include 'context' so correlation ` +\n `ids can round-trip on error envelopes.`\n );\n }\n }\n}\nensureContextEcho(ERROR_ENVELOPE_FIELD_ALLOWLIST);\n\n/**\n * Sanity check: every inside-`adcp_error` allowlist MUST permit `code`\n * and `message` — they are the only two required fields in the AdCP\n * error schema and `adcpError()` can't produce a valid payload without\n * them. Fail at module load so new allowlist entries can't silently ship\n * a shape that would drop the fields every consumer expects.\n */\nfunction ensureCoreAdcpErrorFields(allowlist: Readonly<Record<string, ReadonlySet<string>>>): void {\n for (const [code, fields] of Object.entries(allowlist)) {\n for (const required of ['code', 'message']) {\n if (!fields.has(required)) {\n throw new Error(\n `ADCP_ERROR_FIELD_ALLOWLIST['${code}'] is missing '${required}'. ` +\n `Every inside-adcp_error allowlist must include 'code' and 'message' — ` +\n `they are the only two required fields on the AdCP error schema.`\n );\n }\n }\n }\n}\nensureCoreAdcpErrorFields(ADCP_ERROR_FIELD_ALLOWLIST);\n"],"mappings":"AA6CO,MAAM,gCAAqD,OAAO,OAAO,oBAAI,IAAI,CAAC,SAAS,CAAC,CAAC;AAoB7F,MAAM,iCAAgF,OAAO,OAAO;AAAA;AAAA;AAAA,EAGzG,sBAAsB,oBAAI,IAAI,CAAC,WAAW,cAAc,CAAC;AAC3D,CAAC;AAkCM,MAAM,6BAA4E,OAAO,OAAO;AAAA,EACrG,sBAAsB,oBAAI,IAAI;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQD,uBAAuB,oBAAI,IAAI;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH,CAAC;AASM,MAAM,gCAAqD,2BAA2B;AAO7F,SAAS,kBAAkB,WAAgE;AACzF,MAAI,CAAC,8BAA8B,IAAI,SAAS,GAAG;AACjD,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,SAAS,GAAG;AACtD,QAAI,CAAC,OAAO,IAAI,SAAS,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR,mCAAmC,IAAI;AAAA,MAGzC;AAAA,IACF;AAAA,EACF;AACF;AACA,kBAAkB,8BAA8B;AAShD,SAAS,0BAA0B,WAAgE;AACjG,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,SAAS,GAAG;AACtD,eAAW,YAAY,CAAC,QAAQ,SAAS,GAAG;AAC1C,UAAI,CAAC,OAAO,IAAI,QAAQ,GAAG;AACzB,cAAM,IAAI;AAAA,UACR,+BAA+B,IAAI,kBAAkB,QAAQ;AAAA,QAG/D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AACA,0BAA0B,0BAA0B;","names":[]}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from
|
|
2
|
-
import { dirname as __adcpDirname } from
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
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 __dirname = __adcpDirname(__adcpFileURLToPath(import.meta.url));
|
|
5
|
+
const require2 = __adcpCreateRequire(import.meta.url);
|
|
7
6
|
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
8
7
|
import path from "path";
|
|
9
8
|
import { ADCP_VERSION } from "../version.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/error-arm-tools.ts"],"sourcesContent":["/**\n * Per-AdCP-version registry of tool names whose response schema declares\n * a typed Error arm — i.e. an arm of the top-level `oneOf` / `anyOf`\n * whose `required` includes `\"errors\"`.\n *\n * The dispatcher uses this set to decide whether to auto-emit the\n * payload-layer `errors[]` array alongside the envelope-layer\n * `{adcp_error}` block on the failure path. Tools NOT in this set keep\n * the envelope-only shape — surfacing `errors[]` on a tool whose\n * response schema doesn't define it would add a key the schema\n * doesn't expect.\n *\n * Built lazily at first use from the bundled schema cache (the same\n * tree `schema-loader.ts` reads). The set is computed once per bundle\n * key (`resolveBundleKey('3.0.0')` → `'3.0'`) and memoised — the schema\n * tree is read-only at runtime, so a single scan is sufficient.\n *\n * Spec basis: `error-code.json#GOVERNANCE_DENIED` and\n * `#GOVERNANCE_UNAVAILABLE` both prescribe \"populate `errors[].code` in\n * the payload AND `adcp_error.code` on the envelope per the two-layer\n * model\" for tasks whose response defines a typed Error arm but no\n * structured rejection arm. RFC: `docs/proposals/adcperror-two-layer-emission.md`.\n */\n\nimport { readdirSync, readFileSync, existsSync } from 'fs';\nimport path from 'path';\nimport { ADCP_VERSION } from '../version';\nimport { resolveBundleKey } from '../validation/schema-loader';\n\n/**\n * Resolve the schema-cache root for a given AdCP version.\n *\n * Mirrors `schema-loader.ts:resolveSchemaRoot` so the two-layer scan\n * reads from the same directory the AJV validators compile against.\n * Kept as a private duplicate (rather than exporting from schema-loader)\n * to keep the schema-loader's public surface narrow — that module is\n * about validators, not generic cache traversal.\n *\n * Returns `undefined` when no bundle exists for the version. The\n * dispatcher gates on a non-empty set, so a missing bundle silently\n * disables the auto-wrap (callers without schemas can't have tools to\n * wrap anyway).\n */\nfunction resolveBundledRoot(version: string): string | undefined {\n const key = resolveBundleKey(version);\n // Built layout (dist): dist/lib/schemas-data/<bundle-key>/bundled\n const distCandidate = path.join(__dirname, '..', 'schemas-data', key, 'bundled');\n if (existsSync(distCandidate)) return distCandidate;\n\n // Source-tree layout (dev): schemas/cache/<exact-version>/bundled\n const cacheRoot = path.join(__dirname, '..', '..', '..', 'schemas', 'cache');\n const exactCandidate = path.join(cacheRoot, version, 'bundled');\n if (existsSync(exactCandidate)) return exactCandidate;\n\n // Latest-patch fallback for stable minor pins (matches schema-loader).\n const minorMatch = key.match(/^(\\d+)\\.(\\d+)$/);\n if (minorMatch && existsSync(cacheRoot)) {\n const [, major, minor] = minorMatch;\n const prefix = `${major}.${minor}.`;\n const cached = readdirSync(cacheRoot, { withFileTypes: true })\n .filter(\n e =>\n e.isDirectory() &&\n e.name.startsWith(prefix) &&\n !e.name.endsWith('.previous') &&\n /^\\d+\\.\\d+\\.\\d+$/.test(e.name)\n )\n .map(e => ({ name: e.name, patch: parseInt(e.name.slice(prefix.length), 10) }))\n .filter(c => Number.isFinite(c.patch))\n .sort((a, b) => b.patch - a.patch);\n if (cached.length > 0) {\n return path.join(cacheRoot, cached[0]!.name, 'bundled');\n }\n }\n return undefined;\n}\n\n/**\n * Per-tool descriptor for two-layer auto-emission.\n *\n * `extraRequired` carries any `oneOf`-discriminator constants the error\n * arm requires beyond `errors[]` itself — e.g. `update_content_standards`\n * declares `required: [\"success\", \"errors\"]` with `success: { const: false }`.\n * The dispatcher applies these as constants when synthesising the error\n * arm so the resulting payload satisfies its own response schema.\n *\n * The vast majority of Error-arm tools have `required: [\"errors\"]` only\n * (no discriminator), so `extraRequired` is empty and the wrap is a\n * trivial `{errors: [...]}` projection.\n */\nexport interface ErrorArmDescriptor {\n /** `{[fieldName]: constValue}` for each `const`-typed required field. */\n extraRequired: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Resolve a `oneOf`/`anyOf` arm one level of `$ref` indirection. Bundled\n * schemas inline most refs but a few domains keep an extra hop into\n * `$defs`/`definitions`.\n */\nfunction resolveBranch(branch: unknown, rootSchema: Record<string, unknown>): Record<string, unknown> | undefined {\n if (!branch || typeof branch !== 'object') return undefined;\n const obj = branch as Record<string, unknown>;\n const ref = obj.$ref;\n if (typeof ref === 'string') {\n const match = ref.match(/^#\\/(\\$defs|definitions)\\/(.+)$/);\n if (match && match[1] && match[2]) {\n const root = rootSchema[match[1]] as Record<string, unknown> | undefined;\n const target = root?.[match[2]];\n if (target && typeof target === 'object') return target as Record<string, unknown>;\n }\n return undefined;\n }\n return obj;\n}\n\n/**\n * Inspect a branch and, if it declares `required: [..., \"errors\", ...]`,\n * return its descriptor (capturing any sibling `const`-typed required\n * fields). Returns undefined for branches that don't require `errors[]`\n * or for rejection arms (`AcquireRightsRejected` / `CreativeRejected`)\n * which declare `not: { required: [\"errors\"] }` and explicitly forbid\n * the two-layer wire (RFC § 1.2).\n */\nfunction describeErrorArm(branch: unknown, rootSchema: Record<string, unknown>): ErrorArmDescriptor | undefined {\n const resolved = resolveBranch(branch, rootSchema);\n if (!resolved) return undefined;\n const required = resolved.required;\n if (!Array.isArray(required) || !required.includes('errors')) return undefined;\n\n const extraRequired: Record<string, unknown> = {};\n const properties = resolved.properties as Record<string, unknown> | undefined;\n if (properties) {\n for (const fieldName of required) {\n if (typeof fieldName !== 'string' || fieldName === 'errors') continue;\n const propSchema = properties[fieldName] as Record<string, unknown> | undefined;\n if (propSchema && 'const' in propSchema) {\n extraRequired[fieldName] = propSchema.const;\n }\n // Required fields without a `const` discriminator (e.g. dynamic\n // values) can't be auto-synthesised. The spec convention for\n // Error arms is `errors[]` plus optional `success`/`response_type`\n // constants — so far no tool requires anything else.\n }\n }\n return { extraRequired: Object.freeze(extraRequired) };\n}\n\n/**\n * Read every `*-response.json` under the bundled cache root and return\n * a tool-name → descriptor map for tools whose top-level `oneOf`/`anyOf`\n * declares an Error arm.\n *\n * Tool names are derived from the filename stem (`create-media-buy` →\n * `create_media_buy`) to match the dispatcher's `toolName` key.\n */\nfunction scanForErrorArmTools(bundledRoot: string): Map<string, ErrorArmDescriptor> {\n const map = new Map<string, ErrorArmDescriptor>();\n const files: string[] = [];\n function walk(dir: string): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile() && entry.name.endsWith('-response.json')) {\n files.push(full);\n }\n }\n }\n walk(bundledRoot);\n\n for (const file of files) {\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(readFileSync(file, 'utf-8')) as Record<string, unknown>;\n } catch {\n continue;\n }\n const branches = (schema.oneOf ?? schema.anyOf) as unknown[] | undefined;\n if (!Array.isArray(branches)) continue;\n let descriptor: ErrorArmDescriptor | undefined;\n for (const branch of branches) {\n const candidate = describeErrorArm(branch, schema);\n if (candidate) {\n descriptor = candidate;\n break;\n }\n }\n if (!descriptor) continue;\n const base = path.basename(file, '-response.json');\n const toolName = base.replace(/-/g, '_');\n map.set(toolName, descriptor);\n }\n return map;\n}\n\n/** Memoised per-bundle-key result. Empty map when the bundle is missing. */\nconst cachedMaps = new Map<string, ReadonlyMap<string, ErrorArmDescriptor>>();\n\n/**\n * Per-tool descriptors for tools whose response schema (at `version`)\n * declares a top-level Error arm requiring `errors[]`. Dispatcher-side\n * gate for two-layer auto-emission, plus the per-arm constants the\n * dispatcher needs to set when synthesising the wrap.\n *\n * Scans the bundled schema cache lazily on first call per bundle key\n * (one read per minor version per process). Returns a frozen, empty map\n * when no schema bundle is reachable — the dispatcher early-returns on\n * `map.size === 0`, so a missing bundle silently disables the wrap\n * rather than breaking server startup.\n */\nexport function getToolsWithErrorArm(version: string = ADCP_VERSION): ReadonlyMap<string, ErrorArmDescriptor> {\n const key = resolveBundleKey(version);\n const cached = cachedMaps.get(key);\n if (cached) return cached;\n const root = resolveBundledRoot(version);\n if (!root) {\n const empty: ReadonlyMap<string, ErrorArmDescriptor> = new Map();\n cachedMaps.set(key, empty);\n return empty;\n }\n const map = scanForErrorArmTools(root);\n cachedMaps.set(key, map);\n return map;\n}\n\n/** Test hook: clear the per-bundle-key cache. */\nexport function _resetErrorArmToolsCache(): void {\n cachedMaps.clear();\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/error-arm-tools.ts"],"sourcesContent":["import { fileURLToPath as __adcpFileURLToPath } from 'node:url';\nimport { dirname as __adcpDirname } from 'node:path';\nimport { createRequire as __adcpCreateRequire } from 'node:module';\nconst __dirname = __adcpDirname(__adcpFileURLToPath(import.meta.url));\nconst require = __adcpCreateRequire(import.meta.url);\n/**\n * Per-AdCP-version registry of tool names whose response schema declares\n * a typed Error arm — i.e. an arm of the top-level `oneOf` / `anyOf`\n * whose `required` includes `\"errors\"`.\n *\n * The dispatcher uses this set to decide whether to auto-emit the\n * payload-layer `errors[]` array alongside the envelope-layer\n * `{adcp_error}` block on the failure path. Tools NOT in this set keep\n * the envelope-only shape — surfacing `errors[]` on a tool whose\n * response schema doesn't define it would add a key the schema\n * doesn't expect.\n *\n * Built lazily at first use from the bundled schema cache (the same\n * tree `schema-loader.ts` reads). The set is computed once per bundle\n * key (`resolveBundleKey('3.0.0')` → `'3.0'`) and memoised — the schema\n * tree is read-only at runtime, so a single scan is sufficient.\n *\n * Spec basis: `error-code.json#GOVERNANCE_DENIED` and\n * `#GOVERNANCE_UNAVAILABLE` both prescribe \"populate `errors[].code` in\n * the payload AND `adcp_error.code` on the envelope per the two-layer\n * model\" for tasks whose response defines a typed Error arm but no\n * structured rejection arm. RFC: `docs/proposals/adcperror-two-layer-emission.md`.\n */\n\nimport { readdirSync, readFileSync, existsSync } from 'fs';\nimport path from 'path';\nimport { ADCP_VERSION } from '../version';\nimport { resolveBundleKey } from '../validation/schema-loader';\n\n/**\n * Resolve the schema-cache root for a given AdCP version.\n *\n * Mirrors `schema-loader.ts:resolveSchemaRoot` so the two-layer scan\n * reads from the same directory the AJV validators compile against.\n * Kept as a private duplicate (rather than exporting from schema-loader)\n * to keep the schema-loader's public surface narrow — that module is\n * about validators, not generic cache traversal.\n *\n * Returns `undefined` when no bundle exists for the version. The\n * dispatcher gates on a non-empty set, so a missing bundle silently\n * disables the auto-wrap (callers without schemas can't have tools to\n * wrap anyway).\n */\nfunction resolveBundledRoot(version: string): string | undefined {\n const key = resolveBundleKey(version);\n // Built layout (dist): dist/lib/schemas-data/<bundle-key>/bundled\n const distCandidate = path.join(__dirname, '..', 'schemas-data', key, 'bundled');\n if (existsSync(distCandidate)) return distCandidate;\n\n // Source-tree layout (dev): schemas/cache/<exact-version>/bundled\n const cacheRoot = path.join(__dirname, '..', '..', '..', 'schemas', 'cache');\n const exactCandidate = path.join(cacheRoot, version, 'bundled');\n if (existsSync(exactCandidate)) return exactCandidate;\n\n // Latest-patch fallback for stable minor pins (matches schema-loader).\n const minorMatch = key.match(/^(\\d+)\\.(\\d+)$/);\n if (minorMatch && existsSync(cacheRoot)) {\n const [, major, minor] = minorMatch;\n const prefix = `${major}.${minor}.`;\n const cached = readdirSync(cacheRoot, { withFileTypes: true })\n .filter(\n e =>\n e.isDirectory() &&\n e.name.startsWith(prefix) &&\n !e.name.endsWith('.previous') &&\n /^\\d+\\.\\d+\\.\\d+$/.test(e.name)\n )\n .map(e => ({ name: e.name, patch: parseInt(e.name.slice(prefix.length), 10) }))\n .filter(c => Number.isFinite(c.patch))\n .sort((a, b) => b.patch - a.patch);\n if (cached.length > 0) {\n return path.join(cacheRoot, cached[0]!.name, 'bundled');\n }\n }\n return undefined;\n}\n\n/**\n * Per-tool descriptor for two-layer auto-emission.\n *\n * `extraRequired` carries any `oneOf`-discriminator constants the error\n * arm requires beyond `errors[]` itself — e.g. `update_content_standards`\n * declares `required: [\"success\", \"errors\"]` with `success: { const: false }`.\n * The dispatcher applies these as constants when synthesising the error\n * arm so the resulting payload satisfies its own response schema.\n *\n * The vast majority of Error-arm tools have `required: [\"errors\"]` only\n * (no discriminator), so `extraRequired` is empty and the wrap is a\n * trivial `{errors: [...]}` projection.\n */\nexport interface ErrorArmDescriptor {\n /** `{[fieldName]: constValue}` for each `const`-typed required field. */\n extraRequired: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Resolve a `oneOf`/`anyOf` arm one level of `$ref` indirection. Bundled\n * schemas inline most refs but a few domains keep an extra hop into\n * `$defs`/`definitions`.\n */\nfunction resolveBranch(branch: unknown, rootSchema: Record<string, unknown>): Record<string, unknown> | undefined {\n if (!branch || typeof branch !== 'object') return undefined;\n const obj = branch as Record<string, unknown>;\n const ref = obj.$ref;\n if (typeof ref === 'string') {\n const match = ref.match(/^#\\/(\\$defs|definitions)\\/(.+)$/);\n if (match && match[1] && match[2]) {\n const root = rootSchema[match[1]] as Record<string, unknown> | undefined;\n const target = root?.[match[2]];\n if (target && typeof target === 'object') return target as Record<string, unknown>;\n }\n return undefined;\n }\n return obj;\n}\n\n/**\n * Inspect a branch and, if it declares `required: [..., \"errors\", ...]`,\n * return its descriptor (capturing any sibling `const`-typed required\n * fields). Returns undefined for branches that don't require `errors[]`\n * or for rejection arms (`AcquireRightsRejected` / `CreativeRejected`)\n * which declare `not: { required: [\"errors\"] }` and explicitly forbid\n * the two-layer wire (RFC § 1.2).\n */\nfunction describeErrorArm(branch: unknown, rootSchema: Record<string, unknown>): ErrorArmDescriptor | undefined {\n const resolved = resolveBranch(branch, rootSchema);\n if (!resolved) return undefined;\n const required = resolved.required;\n if (!Array.isArray(required) || !required.includes('errors')) return undefined;\n\n const extraRequired: Record<string, unknown> = {};\n const properties = resolved.properties as Record<string, unknown> | undefined;\n if (properties) {\n for (const fieldName of required) {\n if (typeof fieldName !== 'string' || fieldName === 'errors') continue;\n const propSchema = properties[fieldName] as Record<string, unknown> | undefined;\n if (propSchema && 'const' in propSchema) {\n extraRequired[fieldName] = propSchema.const;\n }\n // Required fields without a `const` discriminator (e.g. dynamic\n // values) can't be auto-synthesised. The spec convention for\n // Error arms is `errors[]` plus optional `success`/`response_type`\n // constants — so far no tool requires anything else.\n }\n }\n return { extraRequired: Object.freeze(extraRequired) };\n}\n\n/**\n * Read every `*-response.json` under the bundled cache root and return\n * a tool-name → descriptor map for tools whose top-level `oneOf`/`anyOf`\n * declares an Error arm.\n *\n * Tool names are derived from the filename stem (`create-media-buy` →\n * `create_media_buy`) to match the dispatcher's `toolName` key.\n */\nfunction scanForErrorArmTools(bundledRoot: string): Map<string, ErrorArmDescriptor> {\n const map = new Map<string, ErrorArmDescriptor>();\n const files: string[] = [];\n function walk(dir: string): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.isFile() && entry.name.endsWith('-response.json')) {\n files.push(full);\n }\n }\n }\n walk(bundledRoot);\n\n for (const file of files) {\n let schema: Record<string, unknown>;\n try {\n schema = JSON.parse(readFileSync(file, 'utf-8')) as Record<string, unknown>;\n } catch {\n continue;\n }\n const branches = (schema.oneOf ?? schema.anyOf) as unknown[] | undefined;\n if (!Array.isArray(branches)) continue;\n let descriptor: ErrorArmDescriptor | undefined;\n for (const branch of branches) {\n const candidate = describeErrorArm(branch, schema);\n if (candidate) {\n descriptor = candidate;\n break;\n }\n }\n if (!descriptor) continue;\n const base = path.basename(file, '-response.json');\n const toolName = base.replace(/-/g, '_');\n map.set(toolName, descriptor);\n }\n return map;\n}\n\n/** Memoised per-bundle-key result. Empty map when the bundle is missing. */\nconst cachedMaps = new Map<string, ReadonlyMap<string, ErrorArmDescriptor>>();\n\n/**\n * Per-tool descriptors for tools whose response schema (at `version`)\n * declares a top-level Error arm requiring `errors[]`. Dispatcher-side\n * gate for two-layer auto-emission, plus the per-arm constants the\n * dispatcher needs to set when synthesising the wrap.\n *\n * Scans the bundled schema cache lazily on first call per bundle key\n * (one read per minor version per process). Returns a frozen, empty map\n * when no schema bundle is reachable — the dispatcher early-returns on\n * `map.size === 0`, so a missing bundle silently disables the wrap\n * rather than breaking server startup.\n */\nexport function getToolsWithErrorArm(version: string = ADCP_VERSION): ReadonlyMap<string, ErrorArmDescriptor> {\n const key = resolveBundleKey(version);\n const cached = cachedMaps.get(key);\n if (cached) return cached;\n const root = resolveBundledRoot(version);\n if (!root) {\n const empty: ReadonlyMap<string, ErrorArmDescriptor> = new Map();\n cachedMaps.set(key, empty);\n return empty;\n }\n const map = scanForErrorArmTools(root);\n cachedMaps.set(key, map);\n return map;\n}\n\n/** Test hook: clear the per-bundle-key cache. */\nexport function _resetErrorArmToolsCache(): void {\n cachedMaps.clear();\n}\n"],"mappings":"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","names":["require"]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
import {
|
|
8
2
|
STANDARD_ERROR_CODES,
|
|
9
3
|
isStandardErrorCode
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/errors.ts"],"sourcesContent":["/**\n * Server-side helpers for producing L3-compliant AdCP error responses.\n *\n * Use `adcpError()` in MCP tool handlers to return structured errors\n * that clients can automatically detect, classify, and act on.\n */\n\nimport {\n STANDARD_ERROR_CODES,\n isStandardErrorCode,\n type StandardErrorCode,\n type ErrorRecovery,\n} from '../types/error-codes';\nimport type { ValidationIssue } from '../validation/schema-validator';\nimport { ADCP_ERROR_FIELD_ALLOWLIST } from './envelope-allowlist';\nimport { pickSafeDetails } from './pick-safe-details';\n\nexport interface AdcpErrorOptions {\n message: string;\n /**\n * Override the recovery classification. Defaults to\n * `STANDARD_ERROR_CODES[code].recovery` for known codes, `'terminal'`\n * otherwise. Dropped from the wire shape for codes whose entry in\n * `ADCP_ERROR_FIELD_ALLOWLIST` excludes it; normalized back to the\n * standard table value for allowlisted standard-code envelopes.\n */\n recovery?: ErrorRecovery;\n /**\n * Name of the request field the error applies to (validation /\n * constraint errors). Dropped from the wire shape for codes whose\n * `ADCP_ERROR_FIELD_ALLOWLIST` entry excludes it (e.g. `IDEMPOTENCY_CONFLICT`\n * — a conflict response MUST NOT echo prior payload state).\n */\n field?: string;\n /**\n * Human-readable remediation hint. Dropped from the wire shape for\n * codes whose `ADCP_ERROR_FIELD_ALLOWLIST` entry excludes it (e.g.\n * `IDEMPOTENCY_CONFLICT`).\n */\n suggestion?: string;\n /**\n * Seconds to wait before retrying a transient error. Only meaningful\n * on retryable codes (`RATE_LIMITED`, `SERVICE_UNAVAILABLE`); dropped\n * on terminal codes whose allowlist excludes it (`IDEMPOTENCY_CONFLICT`\n * — a computed `retry_after` on conflict would leak cached-entry age).\n */\n retry_after?: number;\n /**\n * Code-specific diagnostic payload. Dropped from the wire shape for\n * codes whose `ADCP_ERROR_FIELD_ALLOWLIST` entry excludes it\n * (`IDEMPOTENCY_CONFLICT` — a conflict response MUST NOT echo the\n * prior request payload or cached response body).\n */\n details?: Record<string, unknown>;\n /**\n * Schema validation issues surfaced at the top level of `adcp_error`\n * so operators see JSON Pointers on the first render. Primary use is\n * `VALIDATION_ERROR`; framework validation hooks populate this\n * automatically and also mirror the same array to `details.issues`\n * for buyers that already index into `details` per AdCP spec\n * convention.\n */\n issues?: Array<Omit<ValidationIssue, 'schemaPath'> & { schemaPath?: string }>;\n}\n\nexport interface AdcpErrorPayload {\n code: string;\n message: string;\n /**\n * Closed-enum classifier. Populated by `adcpError()` from\n * `STANDARD_ERROR_CODES[code].recovery` unless the caller provides an\n * override. Marked optional because per-code inside-`adcp_error`\n * allowlists may deliberately drop it from the wire shape — consumers\n * reading a payload parsed off the wire MUST tolerate `undefined`.\n */\n recovery?: ErrorRecovery;\n field?: string;\n suggestion?: string;\n retry_after?: number;\n details?: Record<string, unknown>;\n /**\n * Schema validation issues (`VALIDATION_ERROR`) exposed at the top\n * level so the list is the first thing a reader sees when inspecting\n * the envelope. Also mirrored at `details.issues` for spec-convention\n * compatibility.\n */\n issues?: Array<Omit<ValidationIssue, 'schemaPath'> & { schemaPath?: string }>;\n}\n\nexport interface AdcpErrorResponse {\n [key: string]: unknown;\n content: Array<{ type: 'text'; text: string }>;\n isError: true;\n structuredContent: {\n adcp_error: AdcpErrorPayload;\n };\n}\n\nconst AUTHORIZATION_REQUIRED_DETAIL_KEYS = [\n 'required_connections',\n 'missing_connections',\n 'provider',\n 'connection_type',\n 'required_for',\n 'scope',\n 'status',\n 'resource_ref',\n 'platform_account_id',\n 'identity_id',\n 'handle',\n 'profile_url',\n 'post_id',\n 'post_url',\n 'authorization_url',\n 'authorization_instructions',\n 'checked_at',\n 'expires_at',\n 'reference_authorization',\n] as const;\n\n/**\n * Build an L3-compliant MCP tool error response with all three transport layers:\n *\n * 1. `structuredContent.adcp_error` — programmatic extraction (L3)\n * 2. `content[0].text` — JSON text fallback (L2)\n * 3. `isError: true` — MCP error signal\n *\n * Recovery is auto-populated from the standard error code table when not provided.\n *\n * Before returning, any field NOT allowlisted for the given code in\n * {@link ADCP_ERROR_FIELD_ALLOWLIST} is dropped — sellers get the builder's\n * ergonomics for every code AND the strict wire shape for codes that have\n * a registered allowlist. `IDEMPOTENCY_CONFLICT` is the canonical case:\n * payload-shaped diagnostics like `field`, `suggestion`, and `details`\n * silently drop while standard `recovery` metadata is preserved as the\n * canonical standard-table value. Codes without a registered allowlist\n * pass through unchanged.\n *\n * **Two-layer wire shape.** This builder emits the envelope layer\n * (`structuredContent.adcp_error`) only. For tools whose response\n * schema declares a typed Error arm (`errors[]` required at the top\n * level), the framework dispatcher synthesises the payload-layer\n * `errors[]` from the same data at finalize time, so the wire carries\n * both the envelope marker and the typed Error arm together — no\n * adopter code change required. The list of affected tools is derived\n * at server build from the bundled schema cache. RFC:\n * `docs/proposals/adcperror-two-layer-emission.md`.\n *\n * @example\n * ```typescript\n * import { adcpError } from '@adcp/sdk';\n *\n * server.registerTool(\"get_products\", { inputSchema: schema }, async ({ query }) => {\n * if (!products.length) {\n * return adcpError('PRODUCT_NOT_FOUND', {\n * message: 'No products match query',\n * field: 'query',\n * suggestion: 'Try a broader search term',\n * });\n * }\n * return { content: [...], structuredContent: { products } };\n * });\n * ```\n */\nexport function adcpError(code: StandardErrorCode | (string & {}), options: AdcpErrorOptions): AdcpErrorResponse {\n const recovery = normalizeRecoveryForCode(code, options.recovery);\n\n const adcp_error: AdcpErrorPayload = {\n code,\n message: options.message,\n recovery,\n ...(options.field != null && { field: options.field }),\n ...(options.suggestion != null && { suggestion: options.suggestion }),\n ...(options.retry_after != null && { retry_after: options.retry_after }),\n ...(options.issues != null && { issues: options.issues }),\n ...(options.details != null && { details: options.details }),\n };\n\n const filtered = applyAdcpErrorAllowlist(code, adcp_error as unknown as Record<string, unknown>);\n\n return {\n content: [{ type: 'text', text: JSON.stringify({ adcp_error: filtered }) }],\n isError: true,\n structuredContent: { adcp_error: filtered },\n };\n}\n\n/**\n * Drop every field not in {@link ADCP_ERROR_FIELD_ALLOWLIST} for `code`.\n * When an allowlisted standard code carries `recovery`, normalize it to\n * the fixed classifier from `STANDARD_ERROR_CODES` instead of trusting a\n * caller-supplied value.\n * Codes without an entry pass through unchanged — the allowlist is\n * opt-in per code, not a global filter. The returned object is re-typed\n * as `AdcpErrorPayload` on the assumption that `code` and `message`\n * (the only required fields) are in every registered allowlist; that\n * invariant is re-asserted at runtime by the module-load check in\n * `envelope-allowlist.ts`.\n */\nexport function applyAdcpErrorAllowlist(code: string, payload: Record<string, unknown>): AdcpErrorPayload {\n payload = sanitizeAdcpErrorDetails(code, payload);\n const allowlist = ADCP_ERROR_FIELD_ALLOWLIST[code];\n if (!allowlist) return payload as unknown as AdcpErrorPayload;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(payload)) {\n if (!allowlist.has(key)) continue;\n out[key] = key === 'recovery' ? normalizeAllowlistedRecoveryForCode(code, value) : value;\n }\n return out as unknown as AdcpErrorPayload;\n}\n\nexport function sanitizeStructuredAdcpError<T extends { code: string; message: string }>(error: T): T {\n return applyAdcpErrorAllowlist(error.code, error as unknown as Record<string, unknown>) as unknown as T;\n}\n\nfunction sanitizeAdcpErrorDetails(code: string, payload: Record<string, unknown>): Record<string, unknown> {\n if (code !== 'AUTHORIZATION_REQUIRED' || payload.details === undefined) return payload;\n\n const sanitizedDetails = pickSafeDetails(payload.details, AUTHORIZATION_REQUIRED_DETAIL_KEYS, {\n maxDepth: 4,\n maxSizeBytes: 4096,\n });\n const { details: _details, ...rest } = payload;\n return sanitizedDetails === undefined ? rest : { ...rest, details: sanitizedDetails };\n}\n\nfunction isErrorRecovery(value: unknown): value is ErrorRecovery {\n return value === 'transient' || value === 'correctable' || value === 'terminal';\n}\n\nfunction normalizeRecoveryForCode(code: string, value: unknown): ErrorRecovery {\n return isErrorRecovery(value) ? value : isStandardErrorCode(code) ? STANDARD_ERROR_CODES[code].recovery : 'terminal';\n}\n\nfunction normalizeAllowlistedRecoveryForCode(code: string, value: unknown): ErrorRecovery {\n if (isStandardErrorCode(code)) {\n return STANDARD_ERROR_CODES[code].recovery;\n }\n return normalizeRecoveryForCode(code, value);\n}\n"],"mappings":";;;;;;AAOA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAEP,SAAS,kCAAkC;AAC3C,SAAS,uBAAuB;AAmFhC,MAAM,qCAAqC;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA8CO,SAAS,UAAU,MAAyC,SAA8C;AAC/G,QAAM,WAAW,yBAAyB,MAAM,QAAQ,QAAQ;AAEhE,QAAM,aAA+B;AAAA,IACnC;AAAA,IACA,SAAS,QAAQ;AAAA,IACjB;AAAA,IACA,GAAI,QAAQ,SAAS,QAAQ,EAAE,OAAO,QAAQ,MAAM;AAAA,IACpD,GAAI,QAAQ,cAAc,QAAQ,EAAE,YAAY,QAAQ,WAAW;AAAA,IACnE,GAAI,QAAQ,eAAe,QAAQ,EAAE,aAAa,QAAQ,YAAY;AAAA,IACtE,GAAI,QAAQ,UAAU,QAAQ,EAAE,QAAQ,QAAQ,OAAO;AAAA,IACvD,GAAI,QAAQ,WAAW,QAAQ,EAAE,SAAS,QAAQ,QAAQ;AAAA,EAC5D;AAEA,QAAM,WAAW,wBAAwB,MAAM,UAAgD;AAE/F,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,EAAE,YAAY,SAAS,CAAC,EAAE,CAAC;AAAA,IAC1E,SAAS;AAAA,IACT,mBAAmB,EAAE,YAAY,SAAS;AAAA,EAC5C;AACF;AAcO,SAAS,wBAAwB,MAAc,SAAoD;AACxG,YAAU,yBAAyB,MAAM,OAAO;AAChD,QAAM,YAAY,2BAA2B,IAAI;AACjD,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,CAAC,UAAU,IAAI,GAAG,EAAG;AACzB,QAAI,GAAG,IAAI,QAAQ,aAAa,oCAAoC,MAAM,KAAK,IAAI;AAAA,EACrF;AACA,SAAO;AACT;AAEO,SAAS,4BAAyE,OAAa;AACpG,SAAO,wBAAwB,MAAM,MAAM,KAA2C;AACxF;AAEA,SAAS,yBAAyB,MAAc,SAA2D;AACzG,MAAI,SAAS,4BAA4B,QAAQ,YAAY,OAAW,QAAO;AAE/E,QAAM,mBAAmB,gBAAgB,QAAQ,SAAS,oCAAoC;AAAA,IAC5F,UAAU;AAAA,IACV,cAAc;AAAA,EAChB,CAAC;AACD,QAAM,EAAE,SAAS,UAAU,GAAG,KAAK,IAAI;AACvC,SAAO,qBAAqB,SAAY,OAAO,EAAE,GAAG,MAAM,SAAS,iBAAiB;AACtF;AAEA,SAAS,gBAAgB,OAAwC;AAC/D,SAAO,UAAU,eAAe,UAAU,iBAAiB,UAAU;AACvE;AAEA,SAAS,yBAAyB,MAAc,OAA+B;AAC7E,SAAO,gBAAgB,KAAK,IAAI,QAAQ,oBAAoB,IAAI,IAAI,qBAAqB,IAAI,EAAE,WAAW;AAC5G;AAEA,SAAS,oCAAoC,MAAc,OAA+B;AACxF,MAAI,oBAAoB,IAAI,GAAG;AAC7B,WAAO,qBAAqB,IAAI,EAAE;AAAA,EACpC;AACA,SAAO,yBAAyB,MAAM,KAAK;AAC7C;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/errors.ts"],"sourcesContent":["/**\n * Server-side helpers for producing L3-compliant AdCP error responses.\n *\n * Use `adcpError()` in MCP tool handlers to return structured errors\n * that clients can automatically detect, classify, and act on.\n */\n\nimport {\n STANDARD_ERROR_CODES,\n isStandardErrorCode,\n type StandardErrorCode,\n type ErrorRecovery,\n} from '../types/error-codes';\nimport type { ValidationIssue } from '../validation/schema-validator';\nimport { ADCP_ERROR_FIELD_ALLOWLIST } from './envelope-allowlist';\nimport { pickSafeDetails } from './pick-safe-details';\n\nexport interface AdcpErrorOptions {\n message: string;\n /**\n * Override the recovery classification. Defaults to\n * `STANDARD_ERROR_CODES[code].recovery` for known codes, `'terminal'`\n * otherwise. Dropped from the wire shape for codes whose entry in\n * `ADCP_ERROR_FIELD_ALLOWLIST` excludes it; normalized back to the\n * standard table value for allowlisted standard-code envelopes.\n */\n recovery?: ErrorRecovery;\n /**\n * Name of the request field the error applies to (validation /\n * constraint errors). Dropped from the wire shape for codes whose\n * `ADCP_ERROR_FIELD_ALLOWLIST` entry excludes it (e.g. `IDEMPOTENCY_CONFLICT`\n * — a conflict response MUST NOT echo prior payload state).\n */\n field?: string;\n /**\n * Human-readable remediation hint. Dropped from the wire shape for\n * codes whose `ADCP_ERROR_FIELD_ALLOWLIST` entry excludes it (e.g.\n * `IDEMPOTENCY_CONFLICT`).\n */\n suggestion?: string;\n /**\n * Seconds to wait before retrying a transient error. Only meaningful\n * on retryable codes (`RATE_LIMITED`, `SERVICE_UNAVAILABLE`); dropped\n * on terminal codes whose allowlist excludes it (`IDEMPOTENCY_CONFLICT`\n * — a computed `retry_after` on conflict would leak cached-entry age).\n */\n retry_after?: number;\n /**\n * Code-specific diagnostic payload. Dropped from the wire shape for\n * codes whose `ADCP_ERROR_FIELD_ALLOWLIST` entry excludes it\n * (`IDEMPOTENCY_CONFLICT` — a conflict response MUST NOT echo the\n * prior request payload or cached response body).\n */\n details?: Record<string, unknown>;\n /**\n * Schema validation issues surfaced at the top level of `adcp_error`\n * so operators see JSON Pointers on the first render. Primary use is\n * `VALIDATION_ERROR`; framework validation hooks populate this\n * automatically and also mirror the same array to `details.issues`\n * for buyers that already index into `details` per AdCP spec\n * convention.\n */\n issues?: Array<Omit<ValidationIssue, 'schemaPath'> & { schemaPath?: string }>;\n}\n\nexport interface AdcpErrorPayload {\n code: string;\n message: string;\n /**\n * Closed-enum classifier. Populated by `adcpError()` from\n * `STANDARD_ERROR_CODES[code].recovery` unless the caller provides an\n * override. Marked optional because per-code inside-`adcp_error`\n * allowlists may deliberately drop it from the wire shape — consumers\n * reading a payload parsed off the wire MUST tolerate `undefined`.\n */\n recovery?: ErrorRecovery;\n field?: string;\n suggestion?: string;\n retry_after?: number;\n details?: Record<string, unknown>;\n /**\n * Schema validation issues (`VALIDATION_ERROR`) exposed at the top\n * level so the list is the first thing a reader sees when inspecting\n * the envelope. Also mirrored at `details.issues` for spec-convention\n * compatibility.\n */\n issues?: Array<Omit<ValidationIssue, 'schemaPath'> & { schemaPath?: string }>;\n}\n\nexport interface AdcpErrorResponse {\n [key: string]: unknown;\n content: Array<{ type: 'text'; text: string }>;\n isError: true;\n structuredContent: {\n adcp_error: AdcpErrorPayload;\n };\n}\n\nconst AUTHORIZATION_REQUIRED_DETAIL_KEYS = [\n 'required_connections',\n 'missing_connections',\n 'provider',\n 'connection_type',\n 'required_for',\n 'scope',\n 'status',\n 'resource_ref',\n 'platform_account_id',\n 'identity_id',\n 'handle',\n 'profile_url',\n 'post_id',\n 'post_url',\n 'authorization_url',\n 'authorization_instructions',\n 'checked_at',\n 'expires_at',\n 'reference_authorization',\n] as const;\n\n/**\n * Build an L3-compliant MCP tool error response with all three transport layers:\n *\n * 1. `structuredContent.adcp_error` — programmatic extraction (L3)\n * 2. `content[0].text` — JSON text fallback (L2)\n * 3. `isError: true` — MCP error signal\n *\n * Recovery is auto-populated from the standard error code table when not provided.\n *\n * Before returning, any field NOT allowlisted for the given code in\n * {@link ADCP_ERROR_FIELD_ALLOWLIST} is dropped — sellers get the builder's\n * ergonomics for every code AND the strict wire shape for codes that have\n * a registered allowlist. `IDEMPOTENCY_CONFLICT` is the canonical case:\n * payload-shaped diagnostics like `field`, `suggestion`, and `details`\n * silently drop while standard `recovery` metadata is preserved as the\n * canonical standard-table value. Codes without a registered allowlist\n * pass through unchanged.\n *\n * **Two-layer wire shape.** This builder emits the envelope layer\n * (`structuredContent.adcp_error`) only. For tools whose response\n * schema declares a typed Error arm (`errors[]` required at the top\n * level), the framework dispatcher synthesises the payload-layer\n * `errors[]` from the same data at finalize time, so the wire carries\n * both the envelope marker and the typed Error arm together — no\n * adopter code change required. The list of affected tools is derived\n * at server build from the bundled schema cache. RFC:\n * `docs/proposals/adcperror-two-layer-emission.md`.\n *\n * @example\n * ```typescript\n * import { adcpError } from '@adcp/sdk';\n *\n * server.registerTool(\"get_products\", { inputSchema: schema }, async ({ query }) => {\n * if (!products.length) {\n * return adcpError('PRODUCT_NOT_FOUND', {\n * message: 'No products match query',\n * field: 'query',\n * suggestion: 'Try a broader search term',\n * });\n * }\n * return { content: [...], structuredContent: { products } };\n * });\n * ```\n */\nexport function adcpError(code: StandardErrorCode | (string & {}), options: AdcpErrorOptions): AdcpErrorResponse {\n const recovery = normalizeRecoveryForCode(code, options.recovery);\n\n const adcp_error: AdcpErrorPayload = {\n code,\n message: options.message,\n recovery,\n ...(options.field != null && { field: options.field }),\n ...(options.suggestion != null && { suggestion: options.suggestion }),\n ...(options.retry_after != null && { retry_after: options.retry_after }),\n ...(options.issues != null && { issues: options.issues }),\n ...(options.details != null && { details: options.details }),\n };\n\n const filtered = applyAdcpErrorAllowlist(code, adcp_error as unknown as Record<string, unknown>);\n\n return {\n content: [{ type: 'text', text: JSON.stringify({ adcp_error: filtered }) }],\n isError: true,\n structuredContent: { adcp_error: filtered },\n };\n}\n\n/**\n * Drop every field not in {@link ADCP_ERROR_FIELD_ALLOWLIST} for `code`.\n * When an allowlisted standard code carries `recovery`, normalize it to\n * the fixed classifier from `STANDARD_ERROR_CODES` instead of trusting a\n * caller-supplied value.\n * Codes without an entry pass through unchanged — the allowlist is\n * opt-in per code, not a global filter. The returned object is re-typed\n * as `AdcpErrorPayload` on the assumption that `code` and `message`\n * (the only required fields) are in every registered allowlist; that\n * invariant is re-asserted at runtime by the module-load check in\n * `envelope-allowlist.ts`.\n */\nexport function applyAdcpErrorAllowlist(code: string, payload: Record<string, unknown>): AdcpErrorPayload {\n payload = sanitizeAdcpErrorDetails(code, payload);\n const allowlist = ADCP_ERROR_FIELD_ALLOWLIST[code];\n if (!allowlist) return payload as unknown as AdcpErrorPayload;\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(payload)) {\n if (!allowlist.has(key)) continue;\n out[key] = key === 'recovery' ? normalizeAllowlistedRecoveryForCode(code, value) : value;\n }\n return out as unknown as AdcpErrorPayload;\n}\n\nexport function sanitizeStructuredAdcpError<T extends { code: string; message: string }>(error: T): T {\n return applyAdcpErrorAllowlist(error.code, error as unknown as Record<string, unknown>) as unknown as T;\n}\n\nfunction sanitizeAdcpErrorDetails(code: string, payload: Record<string, unknown>): Record<string, unknown> {\n if (code !== 'AUTHORIZATION_REQUIRED' || payload.details === undefined) return payload;\n\n const sanitizedDetails = pickSafeDetails(payload.details, AUTHORIZATION_REQUIRED_DETAIL_KEYS, {\n maxDepth: 4,\n maxSizeBytes: 4096,\n });\n const { details: _details, ...rest } = payload;\n return sanitizedDetails === undefined ? rest : { ...rest, details: sanitizedDetails };\n}\n\nfunction isErrorRecovery(value: unknown): value is ErrorRecovery {\n return value === 'transient' || value === 'correctable' || value === 'terminal';\n}\n\nfunction normalizeRecoveryForCode(code: string, value: unknown): ErrorRecovery {\n return isErrorRecovery(value) ? value : isStandardErrorCode(code) ? STANDARD_ERROR_CODES[code].recovery : 'terminal';\n}\n\nfunction normalizeAllowlistedRecoveryForCode(code: string, value: unknown): ErrorRecovery {\n if (isStandardErrorCode(code)) {\n return STANDARD_ERROR_CODES[code].recovery;\n }\n return normalizeRecoveryForCode(code, value);\n}\n"],"mappings":"AAOA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAEP,SAAS,kCAAkC;AAC3C,SAAS,uBAAuB;AAmFhC,MAAM,qCAAqC;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA8CO,SAAS,UAAU,MAAyC,SAA8C;AAC/G,QAAM,WAAW,yBAAyB,MAAM,QAAQ,QAAQ;AAEhE,QAAM,aAA+B;AAAA,IACnC;AAAA,IACA,SAAS,QAAQ;AAAA,IACjB;AAAA,IACA,GAAI,QAAQ,SAAS,QAAQ,EAAE,OAAO,QAAQ,MAAM;AAAA,IACpD,GAAI,QAAQ,cAAc,QAAQ,EAAE,YAAY,QAAQ,WAAW;AAAA,IACnE,GAAI,QAAQ,eAAe,QAAQ,EAAE,aAAa,QAAQ,YAAY;AAAA,IACtE,GAAI,QAAQ,UAAU,QAAQ,EAAE,QAAQ,QAAQ,OAAO;AAAA,IACvD,GAAI,QAAQ,WAAW,QAAQ,EAAE,SAAS,QAAQ,QAAQ;AAAA,EAC5D;AAEA,QAAM,WAAW,wBAAwB,MAAM,UAAgD;AAE/F,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,EAAE,YAAY,SAAS,CAAC,EAAE,CAAC;AAAA,IAC1E,SAAS;AAAA,IACT,mBAAmB,EAAE,YAAY,SAAS;AAAA,EAC5C;AACF;AAcO,SAAS,wBAAwB,MAAc,SAAoD;AACxG,YAAU,yBAAyB,MAAM,OAAO;AAChD,QAAM,YAAY,2BAA2B,IAAI;AACjD,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,MAA+B,CAAC;AACtC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,QAAI,CAAC,UAAU,IAAI,GAAG,EAAG;AACzB,QAAI,GAAG,IAAI,QAAQ,aAAa,oCAAoC,MAAM,KAAK,IAAI;AAAA,EACrF;AACA,SAAO;AACT;AAEO,SAAS,4BAAyE,OAAa;AACpG,SAAO,wBAAwB,MAAM,MAAM,KAA2C;AACxF;AAEA,SAAS,yBAAyB,MAAc,SAA2D;AACzG,MAAI,SAAS,4BAA4B,QAAQ,YAAY,OAAW,QAAO;AAE/E,QAAM,mBAAmB,gBAAgB,QAAQ,SAAS,oCAAoC;AAAA,IAC5F,UAAU;AAAA,IACV,cAAc;AAAA,EAChB,CAAC;AACD,QAAM,EAAE,SAAS,UAAU,GAAG,KAAK,IAAI;AACvC,SAAO,qBAAqB,SAAY,OAAO,EAAE,GAAG,MAAM,SAAS,iBAAiB;AACtF;AAEA,SAAS,gBAAgB,OAAwC;AAC/D,SAAO,UAAU,eAAe,UAAU,iBAAiB,UAAU;AACvE;AAEA,SAAS,yBAAyB,MAAc,OAA+B;AAC7E,SAAO,gBAAgB,KAAK,IAAI,QAAQ,oBAAoB,IAAI,IAAI,qBAAqB,IAAI,EAAE,WAAW;AAC5G;AAEA,SAAS,oCAAoC,MAAc,OAA+B;AACxF,MAAI,oBAAoB,IAAI,GAAG;AAC7B,WAAO,qBAAqB,IAAI,EAAE;AAAA,EACpC;AACA,SAAO,yBAAyB,MAAM,KAAK;AAC7C;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
const DEFAULT_ALLOWED_ENVS = ["test", "development"];
|
|
8
2
|
const EXAMPLE_TLD_PATTERN = /(?:^|[/:@.\s])[\w-]+\.example(?=$|[/:?#\s"'`<>)\],;}])/i;
|
|
9
3
|
function valuesFor(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/example-tld-guard.ts"],"sourcesContent":["export interface AssertNoExampleTldsOptions {\n allowIn?: readonly string[];\n checklistPath?: string;\n env?: string;\n}\n\ntype ExampleTldConstants = Record<string, string | readonly string[] | null | undefined>;\n\nconst DEFAULT_ALLOWED_ENVS = ['test', 'development'] as const;\nconst EXAMPLE_TLD_PATTERN = /(?:^|[/:@.\\s])[\\w-]+\\.example(?=$|[/:?#\\s\"'`<>)\\],;}])/i;\n\nfunction valuesFor(value: string | readonly string[] | null | undefined): readonly string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) return value;\n return [];\n}\n\nexport function assertNoExampleTlds(constants: ExampleTldConstants, opts: AssertNoExampleTldsOptions = {}): void {\n const env = opts.env ?? process.env.NODE_ENV;\n const allowIn = opts.allowIn ?? DEFAULT_ALLOWED_ENVS;\n if (env !== undefined && allowIn.includes(env)) return;\n\n for (const [key, value] of Object.entries(constants)) {\n if (valuesFor(value).some(entry => EXAMPLE_TLD_PATTERN.test(entry))) {\n const checklist = opts.checklistPath ? ` in ${opts.checklistPath}` : '';\n throw new Error(`Adapter forked without flipping ${key}; see FORK CHECKLIST${checklist}`);\n }\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/example-tld-guard.ts"],"sourcesContent":["export interface AssertNoExampleTldsOptions {\n allowIn?: readonly string[];\n checklistPath?: string;\n env?: string;\n}\n\ntype ExampleTldConstants = Record<string, string | readonly string[] | null | undefined>;\n\nconst DEFAULT_ALLOWED_ENVS = ['test', 'development'] as const;\nconst EXAMPLE_TLD_PATTERN = /(?:^|[/:@.\\s])[\\w-]+\\.example(?=$|[/:?#\\s\"'`<>)\\],;}])/i;\n\nfunction valuesFor(value: string | readonly string[] | null | undefined): readonly string[] {\n if (typeof value === 'string') return [value];\n if (Array.isArray(value)) return value;\n return [];\n}\n\nexport function assertNoExampleTlds(constants: ExampleTldConstants, opts: AssertNoExampleTldsOptions = {}): void {\n const env = opts.env ?? process.env.NODE_ENV;\n const allowIn = opts.allowIn ?? DEFAULT_ALLOWED_ENVS;\n if (env !== undefined && allowIn.includes(env)) return;\n\n for (const [key, value] of Object.entries(constants)) {\n if (valuesFor(value).some(entry => EXAMPLE_TLD_PATTERN.test(entry))) {\n const checklist = opts.checklistPath ? ` in ${opts.checklistPath}` : '';\n throw new Error(`Adapter forked without flipping ${key}; see FORK CHECKLIST${checklist}`);\n }\n }\n}\n"],"mappings":"AAQA,MAAM,uBAAuB,CAAC,QAAQ,aAAa;AACnD,MAAM,sBAAsB;AAE5B,SAAS,UAAU,OAAyE;AAC1F,MAAI,OAAO,UAAU,SAAU,QAAO,CAAC,KAAK;AAC5C,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,SAAO,CAAC;AACV;AAEO,SAAS,oBAAoB,WAAgC,OAAmC,CAAC,GAAS;AAC/G,QAAM,MAAM,KAAK,OAAO,QAAQ,IAAI;AACpC,QAAM,UAAU,KAAK,WAAW;AAChC,MAAI,QAAQ,UAAa,QAAQ,SAAS,GAAG,EAAG;AAEhD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,SAAS,GAAG;AACpD,QAAI,UAAU,KAAK,EAAE,KAAK,WAAS,oBAAoB,KAAK,KAAK,CAAC,GAAG;AACnE,YAAM,YAAY,KAAK,gBAAgB,OAAO,KAAK,aAAa,KAAK;AACrE,YAAM,IAAI,MAAM,mCAAmC,GAAG,uBAAuB,SAAS,EAAE;AAAA,IAC1F;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
|
|
2
|
-
import { dirname as __adcpDirname } from 'node:path';
|
|
3
|
-
import { createRequire as __adcpCreateRequire } from 'node:module';
|
|
4
|
-
const __filename = __adcpFileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = __adcpDirname(__filename);
|
|
6
|
-
const require = __adcpCreateRequire(import.meta.url);
|
|
7
1
|
function createExpressAdapter(options) {
|
|
8
2
|
const mountPath = normalizeMountPath(options.mountPath);
|
|
9
3
|
const trustForwardedHost = options.trustForwardedHost === true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/express-adapter.ts"],"sourcesContent":["/**\n * Express / Connect adapter for AdCP MCP agents.\n *\n * Mounting an MCP agent inside an existing Express app — rather than using\n * {@link serve}'s standalone HTTP server — requires five specific pieces\n * of plumbing, none of which are obvious the first time you wire them:\n *\n * 1. `rawBody` capture: `express.json()` consumes the body stream before\n * the RFC 9421 signature verifier runs; the verifier needs the exact\n * signed bytes. The seller has to pass a custom `verify` callback to\n * `express.json` that stashes a copy on `req.rawBody`.\n * 2. Mount-path URL reconstruction: Express strips the router mount\n * prefix from `req.url`, so when the verifier reconstructs the\n * canonical URL from `req.url` it sees `/mcp` instead of\n * `/api/training-agent/mcp`. The signed base was the second; the\n * verifier sees the first; every signature fails.\n * 3. RFC 9728 protected-resource metadata at the ORIGIN root: the\n * OAuth graders probe `${origin}/.well-known/oauth-protected-resource${pathname}`,\n * NOT a path inside the router. The well-known route must be mounted\n * on `app`, not on the sub-router.\n * 4. Session reset between storyboards: conformance runners execute\n * many storyboards in sequence against one process; cached state\n * from storyboard N leaks into N+1.\n * 5. Presence-gated signature composition (closed by\n * {@link requireSignatureWhenPresent} / {@link requireAuthenticatedOrSigned}).\n *\n * {@link createExpressAdapter} returns all four of the remaining pieces\n * so the seller's wiring collapses to:\n *\n * ```ts\n * import express from 'express';\n * import { createAdcpServer, createExpressAdapter } from '@adcp/sdk/server/legacy/v5';\n *\n * const agent = createAdcpServer({ ...handlers });\n * const adapter = createExpressAdapter({\n * server: agent,\n * mountPath: '/api/training-agent',\n * publicUrl: 'https://agent.example.com/api/training-agent',\n * prm: { authorizationServers: ['https://auth.example.com'] },\n * });\n *\n * const app = express();\n * app.use(express.json({ limit: '5mb', verify: adapter.rawBodyVerify }));\n * app.use(adapter.protectedResourceMiddleware);\n * app.use('/api/training-agent', myRouter);\n *\n * // In a conformance runner:\n * for (const storyboard of storyboards) {\n * await adapter.resetHook();\n * await run(storyboard);\n * }\n * ```\n */\n\nimport type { IncomingMessage, ServerResponse } from 'http';\nimport type { AdcpServer } from './adcp-server';\nimport type { ProtectedResourceMetadata } from './serve';\n// `SeedComplianceFixturesOptions` is only a type import — zero runtime\n// cost for sellers who don't use `seedFixtures`. The runtime\n// `seedComplianceFixtures` is loaded lazily inside `resetHook` so\n// the fixture set's JS bytes stay out of the server barrel for\n// production sellers who never seed.\nimport type { SeedComplianceFixturesOptions } from '../compliance-fixtures';\n\nexport interface ExpressAdapterOptions {\n /**\n * Mount path the agent router is attached at (must match what you\n * pass to `app.use(mountPath, router)`). Used by {@link ExpressAdapter.getUrl}\n * to reconstruct the canonical request URL for signature verification.\n *\n * Leading slash required; no trailing slash. Example: `/api/training-agent`.\n */\n mountPath: string;\n /**\n * Canonical public URL of the MCP endpoint, origin + mount + `/mcp`.\n *\n * **Required for any deployment that pipes `getUrl` into an RFC 9421\n * signature verifier.** Without `publicUrl`, `getUrl` reconstructs\n * the URL from `x-forwarded-host`/`host` — which an attacker can set\n * to any value, letting them present a signature signed for a\n * different audience and have it verify against the wrong origin.\n * `getUrl` throws when neither `publicUrl` nor the explicit opt-in\n * `trustForwardedHost` is set, so accidental misconfiguration fails\n * closed.\n *\n * When omitted, {@link ExpressAdapter.protectedResourceMiddleware}\n * also refuses to start unless `trustForwardedHost: true` is set —\n * it can't advertise a stable `resource` URL without one of the two.\n */\n publicUrl?: string;\n /**\n * Explicit opt-in for header-derived URL reconstruction in\n * multi-host deployments where `publicUrl` can't be fixed at adapter\n * construction. Setting this acknowledges that `x-forwarded-host` /\n * `host` are trusted inputs (the upstream proxy sanitizes them).\n * Prefer `publicUrl` when possible — it's the only configuration\n * that closes the signed-payload audience-confusion attack.\n */\n trustForwardedHost?: boolean;\n /**\n * RFC 9728 Protected Resource Metadata body. Required unless you're\n * wiring the PRM response yourself; omitting this disables\n * {@link ExpressAdapter.protectedResourceMiddleware}.\n */\n prm?: ProtectedResourceMetadata;\n /**\n * When present, {@link ExpressAdapter.resetHook} dispatches to\n * `server.compliance.reset()`. Leave undefined if you've wired the\n * reset yourself (e.g., via a test-controller scenario).\n */\n server?: AdcpServer;\n /**\n * Pass `{ force: true }` to the underlying `server.compliance.reset()`\n * call. Use when your deployment wires a state store other than the\n * in-memory default but you've verified the flush is safe for this\n * environment (a disposable test DB, for example).\n */\n resetForce?: boolean;\n /**\n * When truthy, {@link ExpressAdapter.resetHook} re-seeds the AdCP\n * compliance fixtures (via `seedComplianceFixtures`) after\n * `compliance.reset()` flushes the state store. Without this, a\n * runner that resets between storyboards loses fixtures seeded\n * before storyboard #1, and every subsequent fixture lookup returns\n * `null` — surfacing as fixture-not-found 404s across the run.\n *\n * - `true` seeds the full canonical fixture set with default options.\n * - An object applies the matching `seedComplianceFixtures`\n * configuration (category filter, overrides, collection names).\n *\n * Leave undefined when handlers don't read from the compliance\n * state-store collections, or when you seed through a different\n * path (e.g., a `comply_test_controller` scenario).\n */\n seedFixtures?: boolean | SeedComplianceFixturesOptions;\n}\n\n/** Return shape of {@link createExpressAdapter}. */\nexport interface ExpressAdapter {\n /**\n * Pass as `express.json({ verify })` — populates `req.rawBody` with\n * the exact bytes signed, which is what the RFC 9421 verifier needs\n * for `Content-Digest` recompute. Safe no-op when the route doesn't\n * run behind `express.json`.\n */\n rawBodyVerify: (req: IncomingMessage, res: ServerResponse, buf: Buffer) => void;\n\n /**\n * Mount on the top-level `app` (NOT inside your agent router) with\n * `app.use(adapter.protectedResourceMiddleware)`. Responds to\n * `/.well-known/oauth-protected-resource/*` at the origin root —\n * which is where the OAuth graders probe. Forwards everything else\n * via `next()`.\n *\n * Undefined when {@link ExpressAdapterOptions.prm} is omitted.\n */\n protectedResourceMiddleware?: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;\n\n /**\n * Reconstruct the full request URL for an incoming request, including\n * the Express mount prefix that `req.url` strips. Pass this as the\n * `getUrl` option to {@link verifySignatureAsAuthenticator} so the\n * recomputed signature base matches what the signer signed.\n */\n getUrl: (req: IncomingMessage) => string;\n\n /**\n * Drop session state and the idempotency cache between storyboards.\n * Delegates to `server.compliance.reset()` when a server is\n * configured; returns a no-op otherwise.\n */\n resetHook: () => Promise<void>;\n}\n\n/**\n * Build all four Express-integration helpers an AdCP agent needs to run\n * behind a mounted router. See the module docstring for the full wiring\n * example.\n */\nexport function createExpressAdapter(options: ExpressAdapterOptions): ExpressAdapter {\n const mountPath = normalizeMountPath(options.mountPath);\n const trustForwardedHost = options.trustForwardedHost === true;\n\n let fixedOrigin: string | undefined;\n if (options.publicUrl) {\n let parsed: URL;\n try {\n parsed = new URL(options.publicUrl);\n } catch {\n throw new Error(`createExpressAdapter: \\`publicUrl\\` is not a valid URL: ${options.publicUrl}`);\n }\n fixedOrigin = parsed.origin;\n }\n\n const rawBodyVerify = (req: IncomingMessage, _res: ServerResponse, buf: Buffer): void => {\n // Attach the raw body as a string — that's what\n // `verifySignatureAsAuthenticator` expects on `req.rawBody`. The\n // verifier's Content-Digest recompute runs over these exact bytes\n // regardless of express.json's subsequent parsing, so the buffer\n // copy here is the source of truth for signature verification.\n (req as IncomingMessage & { rawBody?: string }).rawBody = buf.toString('utf8');\n };\n\n let protectedResourceMiddleware: ExpressAdapter['protectedResourceMiddleware'];\n if (options.prm) {\n if (!fixedOrigin && !trustForwardedHost) {\n throw new Error(\n 'createExpressAdapter: `prm` requires either `publicUrl` (fixed origin) or `trustForwardedHost: true` ' +\n '(acknowledging that upstream sanitizes `x-forwarded-host`/`host`). Header-derived origin reconstruction ' +\n 'without one of these lets an attacker pick the advertised OAuth resource URL.'\n );\n }\n protectedResourceMiddleware = buildProtectedResourceMiddleware(options.prm, options.publicUrl, trustForwardedHost);\n }\n\n const getUrl = (req: IncomingMessage): string => {\n // Express strips the mount prefix from `req.url`, but leaves the\n // pre-strip value on `req.originalUrl`. Prefer `originalUrl` when\n // it exists — that's what the signer signed. Fall back to a\n // mountPath + req.url composition for non-Express frameworks.\n const reqAny = req as IncomingMessage & { originalUrl?: string };\n const path = reqAny.originalUrl ?? joinPath(mountPath, req.url ?? '/');\n if (fixedOrigin) {\n // publicUrl wins unconditionally — ignoring `x-forwarded-host` /\n // `host` is exactly what closes the signed-payload audience-\n // confusion attack. Attacker-controlled headers can't influence\n // the origin the verifier sees.\n return `${fixedOrigin}${path}`;\n }\n if (!trustForwardedHost) {\n throw new Error(\n 'createExpressAdapter.getUrl: neither `publicUrl` nor `trustForwardedHost: true` is set. ' +\n 'Header-derived URL reconstruction lets an attacker pick the origin a signed payload verifies against — ' +\n 'the verifier would recompute the signature base against the spoofed host and accept a signature signed for a different audience. ' +\n 'Set `publicUrl` at adapter construction, or opt into `trustForwardedHost` with an upstream that sanitizes the headers.'\n );\n }\n const forwardedProto = firstHeader(req.headers['x-forwarded-proto']);\n const encrypted = (req.socket as { encrypted?: boolean } | undefined)?.encrypted === true;\n const proto = forwardedProto ?? (encrypted ? 'https' : 'http');\n const host = firstHeader(req.headers['x-forwarded-host']) ?? firstHeader(req.headers['host']);\n if (!host) {\n throw new Error('createExpressAdapter.getUrl: missing Host header under `trustForwardedHost`.');\n }\n return `${proto}://${host}${path}`;\n };\n\n const resetHook = async (): Promise<void> => {\n if (!options.server) return;\n await options.server.compliance.reset({\n ...(options.resetForce ? { force: true } : {}),\n });\n // Re-seed AFTER the flush so subsequent storyboards find fixtures\n // at the same IDs. Without this step the first storyboard consumes\n // the seed from pre-run setup, then every storyboard after N+1\n // misses because `reset()` wiped `compliance:*` collections.\n //\n // NOT atomic: if `compliance.reset()` succeeds and\n // `seedComplianceFixtures` throws mid-loop, the state store is\n // left with a partial seed. For a conformance-runner use case\n // that's fine — the next storyboard surfaces the error loudly.\n // Lazy-loaded so sellers who never set `seedFixtures` don't\n // carry the fixture set's bytes in their server bundle.\n if (options.seedFixtures) {\n const seedOptions: SeedComplianceFixturesOptions = options.seedFixtures === true ? {} : options.seedFixtures;\n const { seedComplianceFixtures } = await import('../compliance-fixtures');\n await seedComplianceFixtures(options.server, seedOptions);\n }\n };\n\n return {\n rawBodyVerify,\n ...(protectedResourceMiddleware ? { protectedResourceMiddleware } : {}),\n getUrl,\n resetHook,\n };\n}\n\nfunction buildProtectedResourceMiddleware(\n prm: ProtectedResourceMetadata,\n publicUrl: string | undefined,\n trustForwardedHost: boolean\n): (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void {\n // OAuth graders probe `/.well-known/oauth-protected-resource/<mount>`.\n // The `<mount>` suffix varies by agent, so match the well-known prefix\n // and return PRM for any suffix. Agents that host multiple MCP mounts\n // behind one origin should override this middleware — the default\n // advertises the same `resource` for every probed path, which is the\n // common single-mount case.\n const WELL_KNOWN = '/.well-known/oauth-protected-resource';\n return (req, res, next) => {\n const pathname = parsePathname(req);\n if (!pathname.startsWith(WELL_KNOWN)) {\n next();\n return;\n }\n let resource: string;\n if (publicUrl) {\n // Fixed origin — same security property as `getUrl`'s\n // publicUrl path: attacker-controlled headers can't influence\n // the advertised resource URL.\n resource = publicUrl;\n } else {\n // trustForwardedHost: true was checked at construction — if we\n // got here without publicUrl, upstream is responsible for\n // sanitizing the headers we read below.\n resource = reconstructResource(req, pathname.slice(WELL_KNOWN.length), trustForwardedHost);\n }\n const body = {\n resource,\n ...prm,\n bearer_methods_supported: prm.bearer_methods_supported ?? ['header'],\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n };\n}\n\nfunction reconstructResource(req: IncomingMessage, suffix: string, trustForwardedHost: boolean): string {\n const forwardedProto = firstHeader(req.headers['x-forwarded-proto']);\n const encrypted = (req.socket as { encrypted?: boolean } | undefined)?.encrypted === true;\n const proto = forwardedProto ?? (encrypted ? 'https' : 'http');\n const host = firstHeader(req.headers['x-forwarded-host']) ?? firstHeader(req.headers['host']);\n if (!host) {\n // Fail closed rather than advertising a placeholder: a PRM with a\n // garbage `resource` would silently mint audience-mismatched tokens\n // across the fleet. Returning 500 surfaces the misconfiguration to\n // the operator; the OAuth grader's probe fails loud.\n throw new Error(\n 'createExpressAdapter.protectedResourceMiddleware: upstream did not forward a Host header. ' +\n 'Set `publicUrl` at adapter construction so the advertised OAuth resource URL is stable.'\n );\n }\n if (!trustForwardedHost && req.headers['x-forwarded-host'] !== undefined) {\n // Defense-in-depth: reaching this path at all means `publicUrl`\n // wasn't set. If an upstream forwards `x-forwarded-host` without\n // the caller opting into trust, that's a misconfiguration.\n throw new Error(\n 'createExpressAdapter.protectedResourceMiddleware: x-forwarded-host present but trustForwardedHost is false.'\n );\n }\n const normalizedSuffix = suffix && !suffix.startsWith('/') ? `/${suffix}` : suffix || '/';\n return `${proto}://${host}${normalizedSuffix}`;\n}\n\nfunction parsePathname(req: IncomingMessage): string {\n try {\n return new URL(req.url ?? '', 'http://_').pathname;\n } catch {\n return req.url ?? '/';\n }\n}\n\nfunction normalizeMountPath(mountPath: string): string {\n if (!mountPath.startsWith('/')) {\n throw new Error(`createExpressAdapter: mountPath must start with '/'. Got: ${mountPath}`);\n }\n let end = mountPath.length;\n while (end > 1 && mountPath.charCodeAt(end - 1) === 47 /* '/' */) end--;\n return mountPath.slice(0, end);\n}\n\nfunction joinPath(mount: string, rest: string): string {\n const r = rest.startsWith('/') ? rest : `/${rest}`;\n return mount === '/' ? r : `${mount}${r}`;\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value) && value.length > 0) return value[0];\n return undefined;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/express-adapter.ts"],"sourcesContent":["/**\n * Express / Connect adapter for AdCP MCP agents.\n *\n * Mounting an MCP agent inside an existing Express app — rather than using\n * {@link serve}'s standalone HTTP server — requires five specific pieces\n * of plumbing, none of which are obvious the first time you wire them:\n *\n * 1. `rawBody` capture: `express.json()` consumes the body stream before\n * the RFC 9421 signature verifier runs; the verifier needs the exact\n * signed bytes. The seller has to pass a custom `verify` callback to\n * `express.json` that stashes a copy on `req.rawBody`.\n * 2. Mount-path URL reconstruction: Express strips the router mount\n * prefix from `req.url`, so when the verifier reconstructs the\n * canonical URL from `req.url` it sees `/mcp` instead of\n * `/api/training-agent/mcp`. The signed base was the second; the\n * verifier sees the first; every signature fails.\n * 3. RFC 9728 protected-resource metadata at the ORIGIN root: the\n * OAuth graders probe `${origin}/.well-known/oauth-protected-resource${pathname}`,\n * NOT a path inside the router. The well-known route must be mounted\n * on `app`, not on the sub-router.\n * 4. Session reset between storyboards: conformance runners execute\n * many storyboards in sequence against one process; cached state\n * from storyboard N leaks into N+1.\n * 5. Presence-gated signature composition (closed by\n * {@link requireSignatureWhenPresent} / {@link requireAuthenticatedOrSigned}).\n *\n * {@link createExpressAdapter} returns all four of the remaining pieces\n * so the seller's wiring collapses to:\n *\n * ```ts\n * import express from 'express';\n * import { createAdcpServer, createExpressAdapter } from '@adcp/sdk/server/legacy/v5';\n *\n * const agent = createAdcpServer({ ...handlers });\n * const adapter = createExpressAdapter({\n * server: agent,\n * mountPath: '/api/training-agent',\n * publicUrl: 'https://agent.example.com/api/training-agent',\n * prm: { authorizationServers: ['https://auth.example.com'] },\n * });\n *\n * const app = express();\n * app.use(express.json({ limit: '5mb', verify: adapter.rawBodyVerify }));\n * app.use(adapter.protectedResourceMiddleware);\n * app.use('/api/training-agent', myRouter);\n *\n * // In a conformance runner:\n * for (const storyboard of storyboards) {\n * await adapter.resetHook();\n * await run(storyboard);\n * }\n * ```\n */\n\nimport type { IncomingMessage, ServerResponse } from 'http';\nimport type { AdcpServer } from './adcp-server';\nimport type { ProtectedResourceMetadata } from './serve';\n// `SeedComplianceFixturesOptions` is only a type import — zero runtime\n// cost for sellers who don't use `seedFixtures`. The runtime\n// `seedComplianceFixtures` is loaded lazily inside `resetHook` so\n// the fixture set's JS bytes stay out of the server barrel for\n// production sellers who never seed.\nimport type { SeedComplianceFixturesOptions } from '../compliance-fixtures';\n\nexport interface ExpressAdapterOptions {\n /**\n * Mount path the agent router is attached at (must match what you\n * pass to `app.use(mountPath, router)`). Used by {@link ExpressAdapter.getUrl}\n * to reconstruct the canonical request URL for signature verification.\n *\n * Leading slash required; no trailing slash. Example: `/api/training-agent`.\n */\n mountPath: string;\n /**\n * Canonical public URL of the MCP endpoint, origin + mount + `/mcp`.\n *\n * **Required for any deployment that pipes `getUrl` into an RFC 9421\n * signature verifier.** Without `publicUrl`, `getUrl` reconstructs\n * the URL from `x-forwarded-host`/`host` — which an attacker can set\n * to any value, letting them present a signature signed for a\n * different audience and have it verify against the wrong origin.\n * `getUrl` throws when neither `publicUrl` nor the explicit opt-in\n * `trustForwardedHost` is set, so accidental misconfiguration fails\n * closed.\n *\n * When omitted, {@link ExpressAdapter.protectedResourceMiddleware}\n * also refuses to start unless `trustForwardedHost: true` is set —\n * it can't advertise a stable `resource` URL without one of the two.\n */\n publicUrl?: string;\n /**\n * Explicit opt-in for header-derived URL reconstruction in\n * multi-host deployments where `publicUrl` can't be fixed at adapter\n * construction. Setting this acknowledges that `x-forwarded-host` /\n * `host` are trusted inputs (the upstream proxy sanitizes them).\n * Prefer `publicUrl` when possible — it's the only configuration\n * that closes the signed-payload audience-confusion attack.\n */\n trustForwardedHost?: boolean;\n /**\n * RFC 9728 Protected Resource Metadata body. Required unless you're\n * wiring the PRM response yourself; omitting this disables\n * {@link ExpressAdapter.protectedResourceMiddleware}.\n */\n prm?: ProtectedResourceMetadata;\n /**\n * When present, {@link ExpressAdapter.resetHook} dispatches to\n * `server.compliance.reset()`. Leave undefined if you've wired the\n * reset yourself (e.g., via a test-controller scenario).\n */\n server?: AdcpServer;\n /**\n * Pass `{ force: true }` to the underlying `server.compliance.reset()`\n * call. Use when your deployment wires a state store other than the\n * in-memory default but you've verified the flush is safe for this\n * environment (a disposable test DB, for example).\n */\n resetForce?: boolean;\n /**\n * When truthy, {@link ExpressAdapter.resetHook} re-seeds the AdCP\n * compliance fixtures (via `seedComplianceFixtures`) after\n * `compliance.reset()` flushes the state store. Without this, a\n * runner that resets between storyboards loses fixtures seeded\n * before storyboard #1, and every subsequent fixture lookup returns\n * `null` — surfacing as fixture-not-found 404s across the run.\n *\n * - `true` seeds the full canonical fixture set with default options.\n * - An object applies the matching `seedComplianceFixtures`\n * configuration (category filter, overrides, collection names).\n *\n * Leave undefined when handlers don't read from the compliance\n * state-store collections, or when you seed through a different\n * path (e.g., a `comply_test_controller` scenario).\n */\n seedFixtures?: boolean | SeedComplianceFixturesOptions;\n}\n\n/** Return shape of {@link createExpressAdapter}. */\nexport interface ExpressAdapter {\n /**\n * Pass as `express.json({ verify })` — populates `req.rawBody` with\n * the exact bytes signed, which is what the RFC 9421 verifier needs\n * for `Content-Digest` recompute. Safe no-op when the route doesn't\n * run behind `express.json`.\n */\n rawBodyVerify: (req: IncomingMessage, res: ServerResponse, buf: Buffer) => void;\n\n /**\n * Mount on the top-level `app` (NOT inside your agent router) with\n * `app.use(adapter.protectedResourceMiddleware)`. Responds to\n * `/.well-known/oauth-protected-resource/*` at the origin root —\n * which is where the OAuth graders probe. Forwards everything else\n * via `next()`.\n *\n * Undefined when {@link ExpressAdapterOptions.prm} is omitted.\n */\n protectedResourceMiddleware?: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;\n\n /**\n * Reconstruct the full request URL for an incoming request, including\n * the Express mount prefix that `req.url` strips. Pass this as the\n * `getUrl` option to {@link verifySignatureAsAuthenticator} so the\n * recomputed signature base matches what the signer signed.\n */\n getUrl: (req: IncomingMessage) => string;\n\n /**\n * Drop session state and the idempotency cache between storyboards.\n * Delegates to `server.compliance.reset()` when a server is\n * configured; returns a no-op otherwise.\n */\n resetHook: () => Promise<void>;\n}\n\n/**\n * Build all four Express-integration helpers an AdCP agent needs to run\n * behind a mounted router. See the module docstring for the full wiring\n * example.\n */\nexport function createExpressAdapter(options: ExpressAdapterOptions): ExpressAdapter {\n const mountPath = normalizeMountPath(options.mountPath);\n const trustForwardedHost = options.trustForwardedHost === true;\n\n let fixedOrigin: string | undefined;\n if (options.publicUrl) {\n let parsed: URL;\n try {\n parsed = new URL(options.publicUrl);\n } catch {\n throw new Error(`createExpressAdapter: \\`publicUrl\\` is not a valid URL: ${options.publicUrl}`);\n }\n fixedOrigin = parsed.origin;\n }\n\n const rawBodyVerify = (req: IncomingMessage, _res: ServerResponse, buf: Buffer): void => {\n // Attach the raw body as a string — that's what\n // `verifySignatureAsAuthenticator` expects on `req.rawBody`. The\n // verifier's Content-Digest recompute runs over these exact bytes\n // regardless of express.json's subsequent parsing, so the buffer\n // copy here is the source of truth for signature verification.\n (req as IncomingMessage & { rawBody?: string }).rawBody = buf.toString('utf8');\n };\n\n let protectedResourceMiddleware: ExpressAdapter['protectedResourceMiddleware'];\n if (options.prm) {\n if (!fixedOrigin && !trustForwardedHost) {\n throw new Error(\n 'createExpressAdapter: `prm` requires either `publicUrl` (fixed origin) or `trustForwardedHost: true` ' +\n '(acknowledging that upstream sanitizes `x-forwarded-host`/`host`). Header-derived origin reconstruction ' +\n 'without one of these lets an attacker pick the advertised OAuth resource URL.'\n );\n }\n protectedResourceMiddleware = buildProtectedResourceMiddleware(options.prm, options.publicUrl, trustForwardedHost);\n }\n\n const getUrl = (req: IncomingMessage): string => {\n // Express strips the mount prefix from `req.url`, but leaves the\n // pre-strip value on `req.originalUrl`. Prefer `originalUrl` when\n // it exists — that's what the signer signed. Fall back to a\n // mountPath + req.url composition for non-Express frameworks.\n const reqAny = req as IncomingMessage & { originalUrl?: string };\n const path = reqAny.originalUrl ?? joinPath(mountPath, req.url ?? '/');\n if (fixedOrigin) {\n // publicUrl wins unconditionally — ignoring `x-forwarded-host` /\n // `host` is exactly what closes the signed-payload audience-\n // confusion attack. Attacker-controlled headers can't influence\n // the origin the verifier sees.\n return `${fixedOrigin}${path}`;\n }\n if (!trustForwardedHost) {\n throw new Error(\n 'createExpressAdapter.getUrl: neither `publicUrl` nor `trustForwardedHost: true` is set. ' +\n 'Header-derived URL reconstruction lets an attacker pick the origin a signed payload verifies against — ' +\n 'the verifier would recompute the signature base against the spoofed host and accept a signature signed for a different audience. ' +\n 'Set `publicUrl` at adapter construction, or opt into `trustForwardedHost` with an upstream that sanitizes the headers.'\n );\n }\n const forwardedProto = firstHeader(req.headers['x-forwarded-proto']);\n const encrypted = (req.socket as { encrypted?: boolean } | undefined)?.encrypted === true;\n const proto = forwardedProto ?? (encrypted ? 'https' : 'http');\n const host = firstHeader(req.headers['x-forwarded-host']) ?? firstHeader(req.headers['host']);\n if (!host) {\n throw new Error('createExpressAdapter.getUrl: missing Host header under `trustForwardedHost`.');\n }\n return `${proto}://${host}${path}`;\n };\n\n const resetHook = async (): Promise<void> => {\n if (!options.server) return;\n await options.server.compliance.reset({\n ...(options.resetForce ? { force: true } : {}),\n });\n // Re-seed AFTER the flush so subsequent storyboards find fixtures\n // at the same IDs. Without this step the first storyboard consumes\n // the seed from pre-run setup, then every storyboard after N+1\n // misses because `reset()` wiped `compliance:*` collections.\n //\n // NOT atomic: if `compliance.reset()` succeeds and\n // `seedComplianceFixtures` throws mid-loop, the state store is\n // left with a partial seed. For a conformance-runner use case\n // that's fine — the next storyboard surfaces the error loudly.\n // Lazy-loaded so sellers who never set `seedFixtures` don't\n // carry the fixture set's bytes in their server bundle.\n if (options.seedFixtures) {\n const seedOptions: SeedComplianceFixturesOptions = options.seedFixtures === true ? {} : options.seedFixtures;\n const { seedComplianceFixtures } = await import('../compliance-fixtures');\n await seedComplianceFixtures(options.server, seedOptions);\n }\n };\n\n return {\n rawBodyVerify,\n ...(protectedResourceMiddleware ? { protectedResourceMiddleware } : {}),\n getUrl,\n resetHook,\n };\n}\n\nfunction buildProtectedResourceMiddleware(\n prm: ProtectedResourceMetadata,\n publicUrl: string | undefined,\n trustForwardedHost: boolean\n): (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void {\n // OAuth graders probe `/.well-known/oauth-protected-resource/<mount>`.\n // The `<mount>` suffix varies by agent, so match the well-known prefix\n // and return PRM for any suffix. Agents that host multiple MCP mounts\n // behind one origin should override this middleware — the default\n // advertises the same `resource` for every probed path, which is the\n // common single-mount case.\n const WELL_KNOWN = '/.well-known/oauth-protected-resource';\n return (req, res, next) => {\n const pathname = parsePathname(req);\n if (!pathname.startsWith(WELL_KNOWN)) {\n next();\n return;\n }\n let resource: string;\n if (publicUrl) {\n // Fixed origin — same security property as `getUrl`'s\n // publicUrl path: attacker-controlled headers can't influence\n // the advertised resource URL.\n resource = publicUrl;\n } else {\n // trustForwardedHost: true was checked at construction — if we\n // got here without publicUrl, upstream is responsible for\n // sanitizing the headers we read below.\n resource = reconstructResource(req, pathname.slice(WELL_KNOWN.length), trustForwardedHost);\n }\n const body = {\n resource,\n ...prm,\n bearer_methods_supported: prm.bearer_methods_supported ?? ['header'],\n };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n };\n}\n\nfunction reconstructResource(req: IncomingMessage, suffix: string, trustForwardedHost: boolean): string {\n const forwardedProto = firstHeader(req.headers['x-forwarded-proto']);\n const encrypted = (req.socket as { encrypted?: boolean } | undefined)?.encrypted === true;\n const proto = forwardedProto ?? (encrypted ? 'https' : 'http');\n const host = firstHeader(req.headers['x-forwarded-host']) ?? firstHeader(req.headers['host']);\n if (!host) {\n // Fail closed rather than advertising a placeholder: a PRM with a\n // garbage `resource` would silently mint audience-mismatched tokens\n // across the fleet. Returning 500 surfaces the misconfiguration to\n // the operator; the OAuth grader's probe fails loud.\n throw new Error(\n 'createExpressAdapter.protectedResourceMiddleware: upstream did not forward a Host header. ' +\n 'Set `publicUrl` at adapter construction so the advertised OAuth resource URL is stable.'\n );\n }\n if (!trustForwardedHost && req.headers['x-forwarded-host'] !== undefined) {\n // Defense-in-depth: reaching this path at all means `publicUrl`\n // wasn't set. If an upstream forwards `x-forwarded-host` without\n // the caller opting into trust, that's a misconfiguration.\n throw new Error(\n 'createExpressAdapter.protectedResourceMiddleware: x-forwarded-host present but trustForwardedHost is false.'\n );\n }\n const normalizedSuffix = suffix && !suffix.startsWith('/') ? `/${suffix}` : suffix || '/';\n return `${proto}://${host}${normalizedSuffix}`;\n}\n\nfunction parsePathname(req: IncomingMessage): string {\n try {\n return new URL(req.url ?? '', 'http://_').pathname;\n } catch {\n return req.url ?? '/';\n }\n}\n\nfunction normalizeMountPath(mountPath: string): string {\n if (!mountPath.startsWith('/')) {\n throw new Error(`createExpressAdapter: mountPath must start with '/'. Got: ${mountPath}`);\n }\n let end = mountPath.length;\n while (end > 1 && mountPath.charCodeAt(end - 1) === 47 /* '/' */) end--;\n return mountPath.slice(0, end);\n}\n\nfunction joinPath(mount: string, rest: string): string {\n const r = rest.startsWith('/') ? rest : `/${rest}`;\n return mount === '/' ? r : `${mount}${r}`;\n}\n\nfunction firstHeader(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value) && value.length > 0) return value[0];\n return undefined;\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 { callMCPTool } from "../protocols/mcp.mjs";
|
|
8
2
|
import { adcpError } from "./errors.mjs";
|
|
9
3
|
function extractMcpPayload(raw) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/server/governance.ts"],"sourcesContent":["/**\n * Composable governance helper for AdCP server handlers.\n *\n * Call `checkGovernance()` inside handlers that have financial commitment\n * (create_media_buy, update_media_buy, activate_signal). The seller controls\n * when and how governance is checked — the framework doesn't intercept.\n *\n * ## Where the governance agent URL comes from\n *\n * Buyers register governance agents against their accounts via `sync_governance`.\n * The seller persists each entry (URL + write-only credentials + optional\n * categories) and reads it back on every lifecycle event. Account's public\n * shape (`Account.governance_agents`) carries `{ url, categories? }[]` —\n * credentials are write-only on the wire, so sellers pair each stored URL\n * with its stored auth token from their own storage when calling this helper.\n *\n * Multi-agent semantics for a single lifecycle event are currently\n * under-specified (see adcontextprotocol/adcp#3010). Until the spec\n * resolves, the recommended usage is one governance agent per\n * category/scope per plan — pick the agent whose `categories` matches the\n * lifecycle event and call it directly.\n *\n * @example\n * ```typescript\n * import { createAdcpServer, checkGovernance, governanceDeniedError } from '@adcp/sdk/server/legacy/v5';\n *\n * const server = createAdcpServer({\n * name: 'My Publisher', version: '1.0.0',\n * resolveAccount: async (ref) => db.findAccount(ref),\n * mediaBuy: {\n * createMediaBuy: async (params, ctx) => {\n * // Look up the governance agent (url + stored credentials) from your\n * // storage. The public `ctx.account.governance_agents` readback\n * // carries URL + categories; auth credentials live in your DB.\n * const agent = await db.governanceAgentForPlan(ctx.account.id, params.plan_id);\n * if (agent) {\n * const gov = await checkGovernance({\n * agentUrl: agent.url,\n * authToken: agent.authToken,\n * planId: params.plan_id!,\n * caller: 'https://my-publisher.com/mcp',\n * tool: 'create_media_buy',\n * payload: params,\n * });\n * if (!gov.approved) return governanceDeniedError(gov);\n * // Thread gov.governanceContext through the media buy lifecycle —\n * // persist it and forward verbatim on subsequent checks.\n * }\n * return { media_buy_id: '...' };\n * },\n * },\n * });\n * ```\n */\n\nimport { callMCPTool } from '../protocols/mcp';\nimport type { CheckGovernanceResponse } from '../types/tools.generated';\nimport type { McpToolResponse } from './responses';\nimport { adcpError } from './errors';\n\n/**\n * Extract a tool's JSON payload from an MCP `CallToolResult` envelope.\n *\n * Handles the three shapes the SDK might see in the wild:\n * - `structuredContent` (AdCP convention — preferred, always an object)\n * - `content[0]` with `type: 'text'` carrying a JSON string\n * - the payload already spread at top level (legacy / non-conformant)\n *\n * Returns `null` when no payload can be extracted.\n */\nfunction extractMcpPayload(raw: unknown): Record<string, unknown> | null {\n if (!raw || typeof raw !== 'object') return null;\n const envelope = raw as Record<string, unknown>;\n\n // Error envelopes can carry structuredContent with shapes that accidentally\n // include `status` — refuse to mis-type them as a successful response.\n if (envelope.isError === true) return null;\n\n if (envelope.structuredContent && typeof envelope.structuredContent === 'object') {\n return envelope.structuredContent as Record<string, unknown>;\n }\n\n const content = envelope.content;\n if (Array.isArray(content) && content.length > 0) {\n const first = content[0] as { type?: string; text?: unknown };\n if (first?.type === 'text' && typeof first.text === 'string') {\n try {\n const parsed = JSON.parse(first.text);\n if (parsed && typeof parsed === 'object') return parsed as Record<string, unknown>;\n } catch {\n // fall through\n }\n }\n }\n\n // Legacy: caller spread the payload at top level.\n if ('status' in envelope || 'check_id' in envelope) return envelope;\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface CheckGovernanceOptions {\n /** URL of the governance agent's MCP endpoint. */\n agentUrl: string;\n /** Campaign governance plan identifier. */\n planId: string;\n /** URL of the agent making the request (your seller agent). */\n caller: string;\n /** The AdCP tool being governed (e.g., 'create_media_buy'). */\n tool?: string;\n /** The full tool arguments as they would be sent to the seller. */\n payload?: Record<string, unknown>;\n /** Opaque governance context from a prior check_governance response. */\n governanceContext?: string;\n /** Purchase type for the governance check. */\n purchaseType?: string;\n /** Auth token for the governance agent. */\n authToken?: string;\n}\n\nexport interface GovernanceApproved {\n approved: true;\n checkId: string;\n explanation: string;\n governanceContext?: string;\n expiresAt?: string;\n nextCheck?: string;\n findings?: CheckGovernanceResponse['findings'];\n}\n\nexport interface GovernanceDenied {\n approved: false;\n checkId: string;\n explanation: string;\n findings?: CheckGovernanceResponse['findings'];\n conditions?: CheckGovernanceResponse['conditions'];\n}\n\nexport interface GovernanceConditions {\n approved: 'conditions';\n checkId: string;\n explanation: string;\n conditions: NonNullable<CheckGovernanceResponse['conditions']>;\n findings?: CheckGovernanceResponse['findings'];\n governanceContext?: string;\n}\n\nexport type GovernanceCallResult = GovernanceApproved | GovernanceDenied | GovernanceConditions;\n\n// ---------------------------------------------------------------------------\n// checkGovernance\n// ---------------------------------------------------------------------------\n\n/**\n * Call a governance agent's `check_governance` tool and return a typed result.\n *\n * Use this inside handlers for tools with financial commitment:\n * - `create_media_buy` — commits budget\n * - `update_media_buy` — modifies budget, adds packages\n * - `activate_signal` — activates paid signals\n *\n * The result includes `governanceContext` which must be threaded through\n * the media buy lifecycle (attach to the response, pass on subsequent checks).\n */\nexport async function checkGovernance(options: CheckGovernanceOptions): Promise<GovernanceCallResult> {\n const { agentUrl, planId, caller, tool, payload, governanceContext, purchaseType, authToken } = options;\n\n const args: Record<string, unknown> = {\n plan_id: planId,\n caller,\n };\n if (tool != null) args.tool = tool;\n if (payload != null) args.payload = payload;\n if (governanceContext != null) args.governance_context = governanceContext;\n if (purchaseType != null) args.purchase_type = purchaseType;\n\n const raw = await callMCPTool(agentUrl, 'check_governance', args, authToken);\n const extracted = extractMcpPayload(raw);\n\n if (!extracted || !('status' in extracted) || !('check_id' in extracted) || !('explanation' in extracted)) {\n throw new Error(\n `Invalid check_governance response from ${agentUrl}: ` +\n `missing required fields (status, check_id, explanation). Got: ${JSON.stringify(raw)?.slice(0, 200)}`\n );\n }\n const response = extracted as unknown as CheckGovernanceResponse;\n\n if (response.verdict === 'approved') {\n return {\n approved: true,\n checkId: response.check_id,\n explanation: response.explanation,\n governanceContext: response.governance_context ?? undefined,\n expiresAt: response.expires_at ?? undefined,\n nextCheck: response.next_check ?? undefined,\n findings: response.findings,\n };\n }\n\n if (response.verdict === 'conditions') {\n return {\n approved: 'conditions',\n checkId: response.check_id,\n explanation: response.explanation,\n conditions: response.conditions!,\n findings: response.findings,\n governanceContext: response.governance_context ?? undefined,\n };\n }\n\n return {\n approved: false,\n checkId: response.check_id,\n explanation: response.explanation,\n findings: response.findings,\n conditions: response.conditions,\n };\n}\n\n/**\n * Convert a governance denial into an adcpError response.\n *\n * Convenience for the common pattern:\n * ```typescript\n * const gov = await checkGovernance({ ... });\n * if (!gov.approved) return governanceDeniedError(gov);\n * ```\n */\nexport function governanceDeniedError(result: GovernanceDenied | GovernanceConditions): McpToolResponse {\n const details: Record<string, unknown> = {\n check_id: result.checkId,\n };\n if (result.findings?.length) {\n details.findings = result.findings;\n }\n if (result.conditions?.length) {\n details.conditions = result.conditions;\n }\n\n return adcpError('GOVERNANCE_DENIED', {\n message: result.explanation,\n details,\n });\n}\n"],"mappings":";;;;;;AAuDA,SAAS,mBAAmB;AAG5B,SAAS,iBAAiB;AAY1B,SAAS,kBAAkB,KAA8C;AACvE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,WAAW;AAIjB,MAAI,SAAS,YAAY,KAAM,QAAO;AAEtC,MAAI,SAAS,qBAAqB,OAAO,SAAS,sBAAsB,UAAU;AAChF,WAAO,SAAS;AAAA,EAClB;AAEA,QAAM,UAAU,SAAS;AACzB,MAAI,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,GAAG;AAChD,UAAM,QAAQ,QAAQ,CAAC;AACvB,QAAI,OAAO,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC5D,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,MAAM,IAAI;AACpC,YAAI,UAAU,OAAO,WAAW,SAAU,QAAO;AAAA,MACnD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAGA,MAAI,YAAY,YAAY,cAAc,SAAU,QAAO;AAE3D,SAAO;AACT;AAqEA,eAAsB,gBAAgB,SAAgE;AACpG,QAAM,EAAE,UAAU,QAAQ,QAAQ,MAAM,SAAS,mBAAmB,cAAc,UAAU,IAAI;AAEhG,QAAM,OAAgC;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,EACF;AACA,MAAI,QAAQ,KAAM,MAAK,OAAO;AAC9B,MAAI,WAAW,KAAM,MAAK,UAAU;AACpC,MAAI,qBAAqB,KAAM,MAAK,qBAAqB;AACzD,MAAI,gBAAgB,KAAM,MAAK,gBAAgB;AAE/C,QAAM,MAAM,MAAM,YAAY,UAAU,oBAAoB,MAAM,SAAS;AAC3E,QAAM,YAAY,kBAAkB,GAAG;AAEvC,MAAI,CAAC,aAAa,EAAE,YAAY,cAAc,EAAE,cAAc,cAAc,EAAE,iBAAiB,YAAY;AACzG,UAAM,IAAI;AAAA,MACR,0CAA0C,QAAQ,mEACiB,KAAK,UAAU,GAAG,GAAG,MAAM,GAAG,GAAG,CAAC;AAAA,IACvG;AAAA,EACF;AACA,QAAM,WAAW;AAEjB,MAAI,SAAS,YAAY,YAAY;AACnC,WAAO;AAAA,MACL,UAAU;AAAA,MACV,SAAS,SAAS;AAAA,MAClB,aAAa,SAAS;AAAA,MACtB,mBAAmB,SAAS,sBAAsB;AAAA,MAClD,WAAW,SAAS,cAAc;AAAA,MAClC,WAAW,SAAS,cAAc;AAAA,MAClC,UAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,SAAS,YAAY,cAAc;AACrC,WAAO;AAAA,MACL,UAAU;AAAA,MACV,SAAS,SAAS;AAAA,MAClB,aAAa,SAAS;AAAA,MACtB,YAAY,SAAS;AAAA,MACrB,UAAU,SAAS;AAAA,MACnB,mBAAmB,SAAS,sBAAsB;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,SAAS;AAAA,IAClB,aAAa,SAAS;AAAA,IACtB,UAAU,SAAS;AAAA,IACnB,YAAY,SAAS;AAAA,EACvB;AACF;AAWO,SAAS,sBAAsB,QAAkE;AACtG,QAAM,UAAmC;AAAA,IACvC,UAAU,OAAO;AAAA,EACnB;AACA,MAAI,OAAO,UAAU,QAAQ;AAC3B,YAAQ,WAAW,OAAO;AAAA,EAC5B;AACA,MAAI,OAAO,YAAY,QAAQ;AAC7B,YAAQ,aAAa,OAAO;AAAA,EAC9B;AAEA,SAAO,UAAU,qBAAqB;AAAA,IACpC,SAAS,OAAO;AAAA,IAChB;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/server/governance.ts"],"sourcesContent":["/**\n * Composable governance helper for AdCP server handlers.\n *\n * Call `checkGovernance()` inside handlers that have financial commitment\n * (create_media_buy, update_media_buy, activate_signal). The seller controls\n * when and how governance is checked — the framework doesn't intercept.\n *\n * ## Where the governance agent URL comes from\n *\n * Buyers register governance agents against their accounts via `sync_governance`.\n * The seller persists each entry (URL + write-only credentials + optional\n * categories) and reads it back on every lifecycle event. Account's public\n * shape (`Account.governance_agents`) carries `{ url, categories? }[]` —\n * credentials are write-only on the wire, so sellers pair each stored URL\n * with its stored auth token from their own storage when calling this helper.\n *\n * Multi-agent semantics for a single lifecycle event are currently\n * under-specified (see adcontextprotocol/adcp#3010). Until the spec\n * resolves, the recommended usage is one governance agent per\n * category/scope per plan — pick the agent whose `categories` matches the\n * lifecycle event and call it directly.\n *\n * @example\n * ```typescript\n * import { createAdcpServer, checkGovernance, governanceDeniedError } from '@adcp/sdk/server/legacy/v5';\n *\n * const server = createAdcpServer({\n * name: 'My Publisher', version: '1.0.0',\n * resolveAccount: async (ref) => db.findAccount(ref),\n * mediaBuy: {\n * createMediaBuy: async (params, ctx) => {\n * // Look up the governance agent (url + stored credentials) from your\n * // storage. The public `ctx.account.governance_agents` readback\n * // carries URL + categories; auth credentials live in your DB.\n * const agent = await db.governanceAgentForPlan(ctx.account.id, params.plan_id);\n * if (agent) {\n * const gov = await checkGovernance({\n * agentUrl: agent.url,\n * authToken: agent.authToken,\n * planId: params.plan_id!,\n * caller: 'https://my-publisher.com/mcp',\n * tool: 'create_media_buy',\n * payload: params,\n * });\n * if (!gov.approved) return governanceDeniedError(gov);\n * // Thread gov.governanceContext through the media buy lifecycle —\n * // persist it and forward verbatim on subsequent checks.\n * }\n * return { media_buy_id: '...' };\n * },\n * },\n * });\n * ```\n */\n\nimport { callMCPTool } from '../protocols/mcp';\nimport type { CheckGovernanceResponse } from '../types/tools.generated';\nimport type { McpToolResponse } from './responses';\nimport { adcpError } from './errors';\n\n/**\n * Extract a tool's JSON payload from an MCP `CallToolResult` envelope.\n *\n * Handles the three shapes the SDK might see in the wild:\n * - `structuredContent` (AdCP convention — preferred, always an object)\n * - `content[0]` with `type: 'text'` carrying a JSON string\n * - the payload already spread at top level (legacy / non-conformant)\n *\n * Returns `null` when no payload can be extracted.\n */\nfunction extractMcpPayload(raw: unknown): Record<string, unknown> | null {\n if (!raw || typeof raw !== 'object') return null;\n const envelope = raw as Record<string, unknown>;\n\n // Error envelopes can carry structuredContent with shapes that accidentally\n // include `status` — refuse to mis-type them as a successful response.\n if (envelope.isError === true) return null;\n\n if (envelope.structuredContent && typeof envelope.structuredContent === 'object') {\n return envelope.structuredContent as Record<string, unknown>;\n }\n\n const content = envelope.content;\n if (Array.isArray(content) && content.length > 0) {\n const first = content[0] as { type?: string; text?: unknown };\n if (first?.type === 'text' && typeof first.text === 'string') {\n try {\n const parsed = JSON.parse(first.text);\n if (parsed && typeof parsed === 'object') return parsed as Record<string, unknown>;\n } catch {\n // fall through\n }\n }\n }\n\n // Legacy: caller spread the payload at top level.\n if ('status' in envelope || 'check_id' in envelope) return envelope;\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface CheckGovernanceOptions {\n /** URL of the governance agent's MCP endpoint. */\n agentUrl: string;\n /** Campaign governance plan identifier. */\n planId: string;\n /** URL of the agent making the request (your seller agent). */\n caller: string;\n /** The AdCP tool being governed (e.g., 'create_media_buy'). */\n tool?: string;\n /** The full tool arguments as they would be sent to the seller. */\n payload?: Record<string, unknown>;\n /** Opaque governance context from a prior check_governance response. */\n governanceContext?: string;\n /** Purchase type for the governance check. */\n purchaseType?: string;\n /** Auth token for the governance agent. */\n authToken?: string;\n}\n\nexport interface GovernanceApproved {\n approved: true;\n checkId: string;\n explanation: string;\n governanceContext?: string;\n expiresAt?: string;\n nextCheck?: string;\n findings?: CheckGovernanceResponse['findings'];\n}\n\nexport interface GovernanceDenied {\n approved: false;\n checkId: string;\n explanation: string;\n findings?: CheckGovernanceResponse['findings'];\n conditions?: CheckGovernanceResponse['conditions'];\n}\n\nexport interface GovernanceConditions {\n approved: 'conditions';\n checkId: string;\n explanation: string;\n conditions: NonNullable<CheckGovernanceResponse['conditions']>;\n findings?: CheckGovernanceResponse['findings'];\n governanceContext?: string;\n}\n\nexport type GovernanceCallResult = GovernanceApproved | GovernanceDenied | GovernanceConditions;\n\n// ---------------------------------------------------------------------------\n// checkGovernance\n// ---------------------------------------------------------------------------\n\n/**\n * Call a governance agent's `check_governance` tool and return a typed result.\n *\n * Use this inside handlers for tools with financial commitment:\n * - `create_media_buy` — commits budget\n * - `update_media_buy` — modifies budget, adds packages\n * - `activate_signal` — activates paid signals\n *\n * The result includes `governanceContext` which must be threaded through\n * the media buy lifecycle (attach to the response, pass on subsequent checks).\n */\nexport async function checkGovernance(options: CheckGovernanceOptions): Promise<GovernanceCallResult> {\n const { agentUrl, planId, caller, tool, payload, governanceContext, purchaseType, authToken } = options;\n\n const args: Record<string, unknown> = {\n plan_id: planId,\n caller,\n };\n if (tool != null) args.tool = tool;\n if (payload != null) args.payload = payload;\n if (governanceContext != null) args.governance_context = governanceContext;\n if (purchaseType != null) args.purchase_type = purchaseType;\n\n const raw = await callMCPTool(agentUrl, 'check_governance', args, authToken);\n const extracted = extractMcpPayload(raw);\n\n if (!extracted || !('status' in extracted) || !('check_id' in extracted) || !('explanation' in extracted)) {\n throw new Error(\n `Invalid check_governance response from ${agentUrl}: ` +\n `missing required fields (status, check_id, explanation). Got: ${JSON.stringify(raw)?.slice(0, 200)}`\n );\n }\n const response = extracted as unknown as CheckGovernanceResponse;\n\n if (response.verdict === 'approved') {\n return {\n approved: true,\n checkId: response.check_id,\n explanation: response.explanation,\n governanceContext: response.governance_context ?? undefined,\n expiresAt: response.expires_at ?? undefined,\n nextCheck: response.next_check ?? undefined,\n findings: response.findings,\n };\n }\n\n if (response.verdict === 'conditions') {\n return {\n approved: 'conditions',\n checkId: response.check_id,\n explanation: response.explanation,\n conditions: response.conditions!,\n findings: response.findings,\n governanceContext: response.governance_context ?? undefined,\n };\n }\n\n return {\n approved: false,\n checkId: response.check_id,\n explanation: response.explanation,\n findings: response.findings,\n conditions: response.conditions,\n };\n}\n\n/**\n * Convert a governance denial into an adcpError response.\n *\n * Convenience for the common pattern:\n * ```typescript\n * const gov = await checkGovernance({ ... });\n * if (!gov.approved) return governanceDeniedError(gov);\n * ```\n */\nexport function governanceDeniedError(result: GovernanceDenied | GovernanceConditions): McpToolResponse {\n const details: Record<string, unknown> = {\n check_id: result.checkId,\n };\n if (result.findings?.length) {\n details.findings = result.findings;\n }\n if (result.conditions?.length) {\n details.conditions = result.conditions;\n }\n\n return adcpError('GOVERNANCE_DENIED', {\n message: result.explanation,\n details,\n });\n}\n"],"mappings":"AAuDA,SAAS,mBAAmB;AAG5B,SAAS,iBAAiB;AAY1B,SAAS,kBAAkB,KAA8C;AACvE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,WAAW;AAIjB,MAAI,SAAS,YAAY,KAAM,QAAO;AAEtC,MAAI,SAAS,qBAAqB,OAAO,SAAS,sBAAsB,UAAU;AAChF,WAAO,SAAS;AAAA,EAClB;AAEA,QAAM,UAAU,SAAS;AACzB,MAAI,MAAM,QAAQ,OAAO,KAAK,QAAQ,SAAS,GAAG;AAChD,UAAM,QAAQ,QAAQ,CAAC;AACvB,QAAI,OAAO,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC5D,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,MAAM,IAAI;AACpC,YAAI,UAAU,OAAO,WAAW,SAAU,QAAO;AAAA,MACnD,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAGA,MAAI,YAAY,YAAY,cAAc,SAAU,QAAO;AAE3D,SAAO;AACT;AAqEA,eAAsB,gBAAgB,SAAgE;AACpG,QAAM,EAAE,UAAU,QAAQ,QAAQ,MAAM,SAAS,mBAAmB,cAAc,UAAU,IAAI;AAEhG,QAAM,OAAgC;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,EACF;AACA,MAAI,QAAQ,KAAM,MAAK,OAAO;AAC9B,MAAI,WAAW,KAAM,MAAK,UAAU;AACpC,MAAI,qBAAqB,KAAM,MAAK,qBAAqB;AACzD,MAAI,gBAAgB,KAAM,MAAK,gBAAgB;AAE/C,QAAM,MAAM,MAAM,YAAY,UAAU,oBAAoB,MAAM,SAAS;AAC3E,QAAM,YAAY,kBAAkB,GAAG;AAEvC,MAAI,CAAC,aAAa,EAAE,YAAY,cAAc,EAAE,cAAc,cAAc,EAAE,iBAAiB,YAAY;AACzG,UAAM,IAAI;AAAA,MACR,0CAA0C,QAAQ,mEACiB,KAAK,UAAU,GAAG,GAAG,MAAM,GAAG,GAAG,CAAC;AAAA,IACvG;AAAA,EACF;AACA,QAAM,WAAW;AAEjB,MAAI,SAAS,YAAY,YAAY;AACnC,WAAO;AAAA,MACL,UAAU;AAAA,MACV,SAAS,SAAS;AAAA,MAClB,aAAa,SAAS;AAAA,MACtB,mBAAmB,SAAS,sBAAsB;AAAA,MAClD,WAAW,SAAS,cAAc;AAAA,MAClC,WAAW,SAAS,cAAc;AAAA,MAClC,UAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,MAAI,SAAS,YAAY,cAAc;AACrC,WAAO;AAAA,MACL,UAAU;AAAA,MACV,SAAS,SAAS;AAAA,MAClB,aAAa,SAAS;AAAA,MACtB,YAAY,SAAS;AAAA,MACrB,UAAU,SAAS;AAAA,MACnB,mBAAmB,SAAS,sBAAsB;AAAA,IACpD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS,SAAS;AAAA,IAClB,aAAa,SAAS;AAAA,IACtB,UAAU,SAAS;AAAA,IACnB,YAAY,SAAS;AAAA,EACvB;AACF;AAWO,SAAS,sBAAsB,QAAkE;AACtG,QAAM,UAAmC;AAAA,IACvC,UAAU,OAAO;AAAA,EACnB;AACA,MAAI,OAAO,UAAU,QAAQ;AAC3B,YAAQ,WAAW,OAAO;AAAA,EAC5B;AACA,MAAI,OAAO,YAAY,QAAQ;AAC7B,YAAQ,aAAa,OAAO;AAAA,EAC9B;AAEA,SAAO,UAAU,qBAAqB;AAAA,IACpC,SAAS,OAAO;AAAA,IAChB;AAAA,EACF,CAAC;AACH;","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 createLazyBackend(factory, options = {}) {
|
|
8
2
|
let backend;
|
|
9
3
|
let resolving;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/lib/server/idempotency/backends/lazy.ts"],"sourcesContent":["import type { IdempotencyBackend, IdempotencyCacheEntry } from '../store';\n\nexport type LazyBackendFactory = () => Promise<IdempotencyBackend>;\n\nexport interface LazyBackendOptions {\n /**\n * Expose `clearAll()` on the wrapper. Keep this disabled for production\n * backends that intentionally omit `clearAll()` (for example Redis on a\n * shared instance), because `createIdempotencyStore()` uses method\n * presence as the reset-safety contract.\n */\n clearAll?: boolean;\n}\n\n/**\n * Lazily resolve an idempotency backend on first use.\n *\n * Use this when the real backend depends on application infrastructure that is\n * resolved asynchronously after SDK server construction, for example:\n *\n * ```ts\n * const store = createIdempotencyStore({\n * backend: createLazyBackend(async () => redisBackend(await getRedisClient(), { keyPrefix })),\n * });\n * ```\n *\n * Concurrent first calls share a single factory invocation. If the factory\n * fails, the wrapper forgets that failed attempt so a later call can retry.\n *\n * `clearAll()` is not exposed by default because its presence is used by\n * compliance reset code as the backend's explicit \"safe to flush\" signal. Set\n * `{ clearAll: true }` only when every backend the factory can return supports\n * and safely permits bulk clearing.\n */\nexport function createLazyBackend(factory: LazyBackendFactory, options: LazyBackendOptions = {}): IdempotencyBackend {\n let backend: IdempotencyBackend | undefined;\n let resolving: Promise<IdempotencyBackend> | undefined;\n\n async function resolveBackend(): Promise<IdempotencyBackend> {\n if (backend) return backend;\n if (!resolving) {\n resolving = Promise.resolve()\n .then(factory)\n .then(resolved => {\n if (!resolved || typeof resolved !== 'object') {\n throw new Error('createLazyBackend: factory must resolve to an IdempotencyBackend.');\n }\n backend = resolved;\n return resolved;\n })\n .catch(err => {\n resolving = undefined;\n throw new Error('createLazyBackend: failed to resolve idempotency backend.', { cause: err });\n });\n }\n return resolving;\n }\n\n const lazyBackend: IdempotencyBackend = {\n async get(scopedKey: string): Promise<IdempotencyCacheEntry | null> {\n return (await resolveBackend()).get(scopedKey);\n },\n\n async putIfAbsent(scopedKey: string, entry: IdempotencyCacheEntry): Promise<boolean> {\n return (await resolveBackend()).putIfAbsent(scopedKey, entry);\n },\n\n async put(scopedKey: string, entry: IdempotencyCacheEntry): Promise<void> {\n await (await resolveBackend()).put(scopedKey, entry);\n },\n\n async delete(scopedKey: string): Promise<void> {\n await (await resolveBackend()).delete(scopedKey);\n },\n\n async probe(): Promise<void> {\n const resolved = await resolveBackend();\n if (resolved.probe) await resolved.probe();\n },\n\n async close(): Promise<void> {\n const resolved = backend ?? (resolving ? await resolving : undefined);\n if (resolved?.close) await resolved.close();\n },\n };\n\n if (options.clearAll) {\n lazyBackend.clearAll = async (): Promise<void> => {\n const resolved = await resolveBackend();\n if (!resolved.clearAll) {\n throw new Error('createLazyBackend: resolved backend does not support clearAll().');\n }\n await resolved.clearAll();\n };\n }\n\n return lazyBackend;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../../../../src/lib/server/idempotency/backends/lazy.ts"],"sourcesContent":["import type { IdempotencyBackend, IdempotencyCacheEntry } from '../store';\n\nexport type LazyBackendFactory = () => Promise<IdempotencyBackend>;\n\nexport interface LazyBackendOptions {\n /**\n * Expose `clearAll()` on the wrapper. Keep this disabled for production\n * backends that intentionally omit `clearAll()` (for example Redis on a\n * shared instance), because `createIdempotencyStore()` uses method\n * presence as the reset-safety contract.\n */\n clearAll?: boolean;\n}\n\n/**\n * Lazily resolve an idempotency backend on first use.\n *\n * Use this when the real backend depends on application infrastructure that is\n * resolved asynchronously after SDK server construction, for example:\n *\n * ```ts\n * const store = createIdempotencyStore({\n * backend: createLazyBackend(async () => redisBackend(await getRedisClient(), { keyPrefix })),\n * });\n * ```\n *\n * Concurrent first calls share a single factory invocation. If the factory\n * fails, the wrapper forgets that failed attempt so a later call can retry.\n *\n * `clearAll()` is not exposed by default because its presence is used by\n * compliance reset code as the backend's explicit \"safe to flush\" signal. Set\n * `{ clearAll: true }` only when every backend the factory can return supports\n * and safely permits bulk clearing.\n */\nexport function createLazyBackend(factory: LazyBackendFactory, options: LazyBackendOptions = {}): IdempotencyBackend {\n let backend: IdempotencyBackend | undefined;\n let resolving: Promise<IdempotencyBackend> | undefined;\n\n async function resolveBackend(): Promise<IdempotencyBackend> {\n if (backend) return backend;\n if (!resolving) {\n resolving = Promise.resolve()\n .then(factory)\n .then(resolved => {\n if (!resolved || typeof resolved !== 'object') {\n throw new Error('createLazyBackend: factory must resolve to an IdempotencyBackend.');\n }\n backend = resolved;\n return resolved;\n })\n .catch(err => {\n resolving = undefined;\n throw new Error('createLazyBackend: failed to resolve idempotency backend.', { cause: err });\n });\n }\n return resolving;\n }\n\n const lazyBackend: IdempotencyBackend = {\n async get(scopedKey: string): Promise<IdempotencyCacheEntry | null> {\n return (await resolveBackend()).get(scopedKey);\n },\n\n async putIfAbsent(scopedKey: string, entry: IdempotencyCacheEntry): Promise<boolean> {\n return (await resolveBackend()).putIfAbsent(scopedKey, entry);\n },\n\n async put(scopedKey: string, entry: IdempotencyCacheEntry): Promise<void> {\n await (await resolveBackend()).put(scopedKey, entry);\n },\n\n async delete(scopedKey: string): Promise<void> {\n await (await resolveBackend()).delete(scopedKey);\n },\n\n async probe(): Promise<void> {\n const resolved = await resolveBackend();\n if (resolved.probe) await resolved.probe();\n },\n\n async close(): Promise<void> {\n const resolved = backend ?? (resolving ? await resolving : undefined);\n if (resolved?.close) await resolved.close();\n },\n };\n\n if (options.clearAll) {\n lazyBackend.clearAll = async (): Promise<void> => {\n const resolved = await resolveBackend();\n if (!resolved.clearAll) {\n throw new Error('createLazyBackend: resolved backend does not support clearAll().');\n }\n await resolved.clearAll();\n };\n }\n\n return lazyBackend;\n}\n"],"mappings":"AAkCO,SAAS,kBAAkB,SAA6B,UAA8B,CAAC,GAAuB;AACnH,MAAI;AACJ,MAAI;AAEJ,iBAAe,iBAA8C;AAC3D,QAAI,QAAS,QAAO;AACpB,QAAI,CAAC,WAAW;AACd,kBAAY,QAAQ,QAAQ,EACzB,KAAK,OAAO,EACZ,KAAK,cAAY;AAChB,YAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,gBAAM,IAAI,MAAM,mEAAmE;AAAA,QACrF;AACA,kBAAU;AACV,eAAO;AAAA,MACT,CAAC,EACA,MAAM,SAAO;AACZ,oBAAY;AACZ,cAAM,IAAI,MAAM,6DAA6D,EAAE,OAAO,IAAI,CAAC;AAAA,MAC7F,CAAC;AAAA,IACL;AACA,WAAO;AAAA,EACT;AAEA,QAAM,cAAkC;AAAA,IACtC,MAAM,IAAI,WAA0D;AAClE,cAAQ,MAAM,eAAe,GAAG,IAAI,SAAS;AAAA,IAC/C;AAAA,IAEA,MAAM,YAAY,WAAmB,OAAgD;AACnF,cAAQ,MAAM,eAAe,GAAG,YAAY,WAAW,KAAK;AAAA,IAC9D;AAAA,IAEA,MAAM,IAAI,WAAmB,OAA6C;AACxE,aAAO,MAAM,eAAe,GAAG,IAAI,WAAW,KAAK;AAAA,IACrD;AAAA,IAEA,MAAM,OAAO,WAAkC;AAC7C,aAAO,MAAM,eAAe,GAAG,OAAO,SAAS;AAAA,IACjD;AAAA,IAEA,MAAM,QAAuB;AAC3B,YAAM,WAAW,MAAM,eAAe;AACtC,UAAI,SAAS,MAAO,OAAM,SAAS,MAAM;AAAA,IAC3C;AAAA,IAEA,MAAM,QAAuB;AAC3B,YAAM,WAAW,YAAY,YAAY,MAAM,YAAY;AAC3D,UAAI,UAAU,MAAO,OAAM,SAAS,MAAM;AAAA,IAC5C;AAAA,EACF;AAEA,MAAI,QAAQ,UAAU;AACpB,gBAAY,WAAW,YAA2B;AAChD,YAAM,WAAW,MAAM,eAAe;AACtC,UAAI,CAAC,SAAS,UAAU;AACtB,cAAM,IAAI,MAAM,kEAAkE;AAAA,MACpF;AACA,YAAM,SAAS,SAAS;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AACT;","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 memoryBackend(options = {}) {
|
|
8
2
|
const store = /* @__PURE__ */ new Map();
|
|
9
3
|
const sweepIntervalMs = options.sweepIntervalMs ?? 6e4;
|