@webpieces/core-util 0.4.863 → 0.4.865

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.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/src/ContextKey.d.ts +3 -3
  3. package/src/ContextKey.js +3 -3
  4. package/src/ContextKey.js.map +1 -1
  5. package/src/http/ContextMgr.d.ts +3 -3
  6. package/src/http/ContextMgr.js +3 -3
  7. package/src/http/ContextMgr.js.map +1 -1
  8. package/src/http/DeletedAuthCompileAssertions.d.ts +4 -0
  9. package/src/http/DeletedAuthCompileAssertions.js +30 -0
  10. package/src/http/DeletedAuthCompileAssertions.js.map +1 -0
  11. package/src/http/DestinationTrust.d.ts +6 -6
  12. package/src/http/DestinationTrust.js +12 -13
  13. package/src/http/DestinationTrust.js.map +1 -1
  14. package/src/http/HttpEndpointOptions.d.ts +2 -2
  15. package/src/http/HttpEndpointOptions.js.map +1 -1
  16. package/src/http/RouteMetadata.d.ts +6 -1
  17. package/src/http/RouteMetadata.js +14 -1
  18. package/src/http/RouteMetadata.js.map +1 -1
  19. package/src/http/RouteMetadataFactory.js +9 -3
  20. package/src/http/RouteMetadataFactory.js.map +1 -1
  21. package/src/http/RuntimeLocality.d.ts +3 -3
  22. package/src/http/RuntimeLocality.js +1 -1
  23. package/src/http/RuntimeLocality.js.map +1 -1
  24. package/src/http/Secrets.d.ts +2 -2
  25. package/src/http/Secrets.js +2 -2
  26. package/src/http/Secrets.js.map +1 -1
  27. package/src/http/Surface.d.ts +4 -4
  28. package/src/http/Surface.js.map +1 -1
  29. package/src/http/WebpiecesCoreHeaders.d.ts +3 -3
  30. package/src/http/WebpiecesCoreHeaders.js +3 -3
  31. package/src/http/WebpiecesCoreHeaders.js.map +1 -1
  32. package/src/http/WpAuthApiKeyCompileAssertions.d.ts +4 -8
  33. package/src/http/WpAuthApiKeyCompileAssertions.js +8 -13
  34. package/src/http/WpAuthApiKeyCompileAssertions.js.map +1 -1
  35. package/src/http/WpAuthJwtCompileAssertions.d.ts +1 -27
  36. package/src/http/WpAuthJwtCompileAssertions.js +13 -48
  37. package/src/http/WpAuthJwtCompileAssertions.js.map +1 -1
  38. package/src/http/WpAuthorizationCompileAssertions.d.ts +1 -0
  39. package/src/http/WpAuthorizationCompileAssertions.js +26 -0
  40. package/src/http/WpAuthorizationCompileAssertions.js.map +1 -0
  41. package/src/http/auth-mode.d.ts +22 -130
  42. package/src/http/auth-mode.js +100 -26
  43. package/src/http/auth-mode.js.map +1 -1
  44. package/src/http/authorization.d.ts +38 -0
  45. package/src/http/authorization.js +71 -0
  46. package/src/http/authorization.js.map +1 -0
  47. package/src/http/decorators.d.ts +11 -114
  48. package/src/http/decorators.js +47 -157
  49. package/src/http/decorators.js.map +1 -1
  50. package/src/index.d.ts +5 -4
  51. package/src/index.js +21 -19
  52. package/src/index.js.map +1 -1
  53. package/src/mcp/McpAuthMetadata.d.ts +0 -9
  54. package/src/mcp/McpAuthMetadata.js +0 -31
  55. package/src/mcp/McpAuthMetadata.js.map +0 -1
@@ -6,6 +6,8 @@ const http_parameter_decorators_1 = require("./http-parameter-decorators");
6
6
  const HttpContract_1 = require("./HttpContract");
7
7
  const RouteMetadata_1 = require("./RouteMetadata");
8
8
  const StreamingContract_1 = require("./StreamingContract");
9
+ const authorization_1 = require("./authorization");
10
+ const decorators_2 = require("./decorators");
9
11
  const StreamingContract_2 = require("./StreamingContract");
10
12
  /**
11
13
  * Build the one runtime route model consumed by incoming routing, in-process clients, and both
@@ -15,8 +17,8 @@ const StreamingContract_2 = require("./StreamingContract");
15
17
  class RouteMetadataFactory {
16
18
  // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes
17
19
  static create(apiClass, methodName, controllerClassName) {
18
- const endpointPath = this.endpointPath(apiClass, methodName);
19
- const fullPath = this.joinPath((0, decorators_1.getApiPath)(apiClass) ?? '', endpointPath);
20
+ (0, decorators_2.assertEveryEndpointHasAuthMode)(apiClass);
21
+ const fullPath = this.joinPath((0, decorators_1.getApiPath)(apiClass) ?? '', this.endpointPath(apiClass, methodName));
20
22
  const options = (0, decorators_1.getEndpointOptions)(apiClass, methodName);
21
23
  const streaming = (0, StreamingContract_1.getStreamingEndpoint)(apiClass, methodName);
22
24
  const httpMethod = (0, decorators_1.getEndpointHttpMethod)(apiClass, methodName);
@@ -30,7 +32,11 @@ class RouteMetadataFactory {
30
32
  const bindings = declarations
31
33
  .map((declaration) => new HttpContract_1.HttpParameterBinding(declaration.index, declaration.source, declaration.wireName, this.valueType(parameterTypes[declaration.index])))
32
34
  .sort((a, b) => a.index - b.index);
33
- return new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, operation, controllerClassName, (0, decorators_1.getAuthMeta)(apiClass, methodName), apiClass.name || 'UnknownApi', options.formPost === true, (0, decorators_1.getMaskSpec)(apiClass, methodName), options.rawBody === true, bindings, bodyParameterIndex, this.responseType(options.responseType, apiClass, methodName), streaming, options.background === true);
35
+ const route = new RouteMetadata_1.RouteMetadata(httpMethod, fullPath, methodName, operation, controllerClassName, (0, decorators_1.getAuthMeta)(apiClass, methodName), apiClass.name || 'UnknownApi', options.formPost === true, (0, decorators_1.getMaskSpec)(apiClass, methodName), options.rawBody === true, bindings, bodyParameterIndex, this.responseType(options.responseType, apiClass, methodName), streaming, options.background === true);
36
+ route.authorization = (0, authorization_1.getAuthorization)(apiClass, methodName);
37
+ route.apiClass = apiClass;
38
+ route.localOnly = (0, decorators_2.isLocalOnly)(apiClass, methodName);
39
+ return route;
34
40
  }
35
41
  // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction
36
42
  static endpointPath(apiClass, methodName) {
@@ -1 +1 @@
1
- {"version":3,"file":"RouteMetadataFactory.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadataFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpointHttpMethod,\n getEndpointOperation,\n getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n HttpMethod,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { StreamDirection, StreamingEndpointMetadata } from './StreamingContract';\n\n/**\n * Build the one runtime route model consumed by incoming routing, in-process clients, and both\n * generated HTTP clients. Keeping this join/validation here prevents four subtly different\n * interpretations of the same decorators.\n */\nexport class RouteMetadataFactory {\n // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes\n static create(\n apiClass: Function,\n methodName: string,\n controllerClassName?: string,\n ): RouteMetadata {\n const endpointPath = this.endpointPath(apiClass, methodName);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', endpointPath);\n const options = getEndpointOptions(apiClass, methodName);\n const streaming = getStreamingEndpoint(apiClass, methodName);\n const httpMethod = getEndpointHttpMethod(apiClass, methodName);\n const operation = getEndpointOperation(apiClass, methodName);\n this.validateEndpointOptions(\n apiClass,\n methodName,\n httpMethod,\n options.formPost,\n options.rawBody,\n );\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n const bodyParameterIndex = streaming\n ? this.validateStreamingParameters(\n apiClass,\n methodName,\n httpMethod,\n parameterTypes,\n declarations,\n streaming,\n )\n : this.validateParameters(\n apiClass,\n methodName,\n fullPath,\n httpMethod,\n parameterTypes,\n declarations,\n );\n const bindings = declarations\n .map(\n (declaration: HttpParameterDeclaration) =>\n new HttpParameterBinding(\n declaration.index,\n declaration.source,\n declaration.wireName,\n this.valueType(parameterTypes[declaration.index]),\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\n\n return new RouteMetadata(\n httpMethod,\n fullPath,\n methodName,\n operation,\n controllerClassName,\n getAuthMeta(apiClass, methodName),\n apiClass.name || 'UnknownApi',\n options.formPost === true,\n getMaskSpec(apiClass, methodName),\n options.rawBody === true,\n bindings,\n bodyParameterIndex,\n this.responseType(options.responseType, apiClass, methodName),\n streaming,\n options.background === true,\n );\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static endpointPath(apiClass: Function, methodName: string): string {\n const path = (getEndpoints(apiClass) ?? {})[methodName];\n if (path !== undefined) return path;\n throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit\n private static validateEndpointOptions(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n formPost?: boolean,\n rawBody?: boolean,\n ): void {\n if (httpMethod === 'GET' && formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare formPost:true because GET has no request body.`,\n );\n }\n if (httpMethod === 'GET' && rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare rawBody:true because GET has no request body.`,\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators\n private static validateStreamingParameters(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n streaming: StreamingEndpointMetadata,\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (\n !streaming.initialRequestSchema ||\n !streaming.initialResponseSchema ||\n (streaming.direction !== StreamDirection.RESPONSE && !streaming.requestSchema) ||\n (streaming.direction !== StreamDirection.REQUEST && !streaming.responseSchema)\n ) {\n throw new Error(\n `${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`,\n );\n }\n if (httpMethod !== 'POST') throw new Error(`${label} is streaming and must use POST.`);\n if (declarations.length > 0)\n throw new Error(`${label} is streaming and cannot declare path/query parameters.`);\n const expected = streaming.direction === StreamDirection.REQUEST ? 1 : 2;\n if (parameterTypes.length !== expected) {\n const form =\n streaming.direction === StreamDirection.REQUEST\n ? '(InitialRequest) => Promise<RequestStream<InitialResponse, RequestEvent>>'\n : streaming.direction === StreamDirection.RESPONSE\n ? '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<InitialResponse>'\n : '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<RequestStream<InitialResponse, RequestEvent>>';\n throw new Error(\n `Invalid @WpStream(StreamDirection.${streaming.direction.toUpperCase()}) method ${label}: ` +\n `${streaming.direction.toUpperCase()} accepts exactly ${form}.`,\n );\n }\n return 0;\n }\n\n /**\n * Refuse a contract in which two endpoints resolve to the same HTTP method + path.\n *\n * Empty paths are LEGAL (`@ApiPath('')` + `@Endpoint(POST, '', ...)` is how a contract says \"the whole\n * destination arrives per call\"), so the thing actually worth rejecting is ambiguity: two methods\n * that land on one route. On the server the second registration would silently shadow the first\n * in the route map, and on the client both would dial the same URL — neither is ever what the\n * author meant. Placeholder NAMES are ignored when comparing (`/a/{id}` and `/a/{key}` are the\n * same route to a router), `''` and `'/'` compare equal (the server serves an empty path at the\n * root), and the error names BOTH methods so the fix is obvious.\n *\n * Called by every transport that binds a whole contract (the generated clients and the server's\n * route registration), so a duplicate dies at startup however the contract is consumed.\n *\n * @throws Error naming both colliding methods and the shared route.\n */\n // webpieces-disable no-function-outside-class -- deterministic contract validation shared by DI-free browser and server runtimes\n static assertNoDuplicateRoutes(apiClass: Function): void {\n const label = apiClass.name || 'Unknown';\n const seen = new Map<string, string>();\n for (const methodName of Object.keys(getEndpoints(apiClass) ?? {})) {\n const route = this.create(apiClass, methodName);\n // '' and '/' are one route to a server (every request path starts with '/').\n const shape = (route.path === '' ? '/' : route.path).replace(/\\{[^{}]+\\}/g, '{}');\n const key = `${route.httpMethod} ${shape}`;\n const previous = seen.get(key);\n if (previous !== undefined) {\n throw new Error(\n `${label}.${previous} and ${label}.${methodName} both resolve to ` +\n `${route.httpMethod} '${route.path}'. Two endpoints of one contract cannot share ` +\n `an HTTP method + path; give one of them a distinct @Endpoint path or HTTP method.`,\n );\n }\n seen.set(key, methodName);\n }\n }\n\n /**\n * Join `@ApiPath` and `@Endpoint` into the route path.\n *\n * Both empty joins to `''`, NOT `'/'`. A contract declaring `@ApiPath('')` + `@Endpoint(POST, '', ...)`\n * is saying \"this route adds nothing to the base URL\", and a client whose base URL is a full\n * destination (host + path + query) must send it byte for byte. Before #926 the client joined with\n * plain concatenation (`'' + ''`), and forcing a `/` here broke exactly that (#944).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static joinPath(basePath: string, endpointPath: string): string {\n if (basePath === '' && endpointPath === '') return '';\n const joined = `${basePath}/${endpointPath}`.replace(/\\/{2,}/g, '/');\n return joined.startsWith('/') ? joined : `/${joined}`;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static responseType(\n value: EndpointResponseType | undefined,\n apiClass: Function,\n methodName: string,\n ): EndpointResponseType {\n const responseType = value ?? 'body';\n if (responseType === 'body' || responseType === 'full') return responseType;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`,\n );\n }\n\n // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency\n private static parameterTypes(apiClass: Function, methodName: string): Function[] {\n return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];\n }\n\n // webpieces-disable no-function-outside-class -- private deterministic contract validation helper\n private static validateParameters(\n apiClass: Function,\n methodName: string,\n fullPath: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n const placeholders = [...fullPath.matchAll(/\\{([^{}]+)\\}/g)].map(\n (match: RegExpMatchArray) => match[1],\n );\n const pathNames = declarations\n .filter((declaration: HttpParameterDeclaration) => declaration.source === 'path')\n .map((declaration: HttpParameterDeclaration) => declaration.wireName);\n for (const placeholder of new Set(placeholders)) {\n if (!pathNames.includes(placeholder)) {\n throw new Error(\n `${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`,\n );\n }\n }\n for (const pathName of pathNames) {\n if (!placeholders.includes(pathName)) {\n throw new Error(\n `${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`,\n );\n }\n }\n\n const seenWire = new Set<string>();\n const mappedIndexes = new Set<number>();\n for (const declaration of declarations) {\n const wireKey = `${declaration.source}:${declaration.wireName}`;\n if (seenWire.has(wireKey)) {\n throw new Error(`${label} maps '${wireKey}' more than once.`);\n }\n seenWire.add(wireKey);\n mappedIndexes.add(declaration.index);\n }\n\n const declaredCount = Math.max(\n parameterTypes.length,\n declarations.reduce(\n (highest: number, declaration: HttpParameterDeclaration) =>\n Math.max(highest, declaration.index + 1),\n 0,\n ),\n );\n const unbound: number[] = [];\n for (let index = 0; index < declaredCount; index++) {\n if (!mappedIndexes.has(index)) unbound.push(index);\n }\n if (httpMethod === 'GET' && unbound.length > 0) {\n throw new Error(\n `${label} is GET, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`,\n );\n }\n if (unbound.length > 1) {\n throw new Error(\n `${label} has ${unbound.length} unmapped parameters. POST permits exactly one unannotated request body; map every other parameter explicitly.`,\n );\n }\n return unbound[0];\n }\n\n // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types\n private static valueType(type: Function | undefined): HttpParameterValueType {\n if (type === String) return 'string';\n if (type === Number) return 'number';\n if (type === Boolean) return 'boolean';\n if (type === Array) return 'array';\n return 'unknown';\n }\n}\n"]}
1
+ {"version":3,"file":"RouteMetadataFactory.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RouteMetadataFactory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getApiPath,\n getAuthMeta,\n getEndpointHttpMethod,\n getEndpointOperation,\n getEndpointOptions,\n getEndpoints,\n getMaskSpec,\n} from './decorators';\nimport { getHttpParameterDeclarations } from './http-parameter-decorators';\nimport {\n EndpointResponseType,\n HttpParameterBinding,\n HttpParameterDeclaration,\n HttpParameterValueType,\n HttpMethod,\n} from './HttpContract';\nimport { RouteMetadata } from './RouteMetadata';\nimport { getStreamingEndpoint } from './StreamingContract';\nimport { getAuthorization } from './authorization';\nimport { assertEveryEndpointHasAuthMode, isLocalOnly } from './decorators';\nimport { StreamDirection, StreamingEndpointMetadata } from './StreamingContract';\n\n/**\n * Build the one runtime route model consumed by incoming routing, in-process clients, and both\n * generated HTTP clients. Keeping this join/validation here prevents four subtly different\n * interpretations of the same decorators.\n */\nexport class RouteMetadataFactory {\n // webpieces-disable no-function-outside-class -- deterministic decorator metadata factory shared by DI-free browser and server runtimes\n static create(\n apiClass: Function,\n methodName: string,\n controllerClassName?: string,\n ): RouteMetadata {\n assertEveryEndpointHasAuthMode(apiClass);\n const fullPath = this.joinPath(getApiPath(apiClass) ?? '', this.endpointPath(apiClass, methodName));\n const options = getEndpointOptions(apiClass, methodName);\n const streaming = getStreamingEndpoint(apiClass, methodName);\n const httpMethod = getEndpointHttpMethod(apiClass, methodName);\n const operation = getEndpointOperation(apiClass, methodName);\n this.validateEndpointOptions(\n apiClass,\n methodName,\n httpMethod,\n options.formPost,\n options.rawBody,\n );\n const parameterTypes = this.parameterTypes(apiClass, methodName);\n const declarations = getHttpParameterDeclarations(apiClass, methodName);\n const bodyParameterIndex = streaming\n ? this.validateStreamingParameters(\n apiClass,\n methodName,\n httpMethod,\n parameterTypes,\n declarations,\n streaming,\n )\n : this.validateParameters(\n apiClass,\n methodName,\n fullPath,\n httpMethod,\n parameterTypes,\n declarations,\n );\n const bindings = declarations\n .map(\n (declaration: HttpParameterDeclaration) =>\n new HttpParameterBinding(\n declaration.index,\n declaration.source,\n declaration.wireName,\n this.valueType(parameterTypes[declaration.index]),\n ),\n )\n .sort((a: HttpParameterBinding, b: HttpParameterBinding) => a.index - b.index);\n const route = new RouteMetadata(\n httpMethod,\n fullPath,\n methodName,\n operation,\n controllerClassName,\n getAuthMeta(apiClass, methodName),\n apiClass.name || 'UnknownApi',\n options.formPost === true,\n getMaskSpec(apiClass, methodName),\n options.rawBody === true,\n bindings,\n bodyParameterIndex,\n this.responseType(options.responseType, apiClass, methodName),\n streaming,\n options.background === true,\n );\n route.authorization = getAuthorization(apiClass, methodName);\n route.apiClass = apiClass;\n route.localOnly = isLocalOnly(apiClass, methodName);\n return route;\n }\n // webpieces-disable no-function-outside-class -- private pure helper for static route metadata construction\n private static endpointPath(apiClass: Function, methodName: string): string {\n const path = (getEndpoints(apiClass) ?? {})[methodName];\n if (path !== undefined) return path;\n throw new Error(`No @Endpoint metadata for ${apiClass.name || 'Unknown'}.${methodName}.`);\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper keeps the existing static metadata factory below the method-size limit\n private static validateEndpointOptions(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n formPost?: boolean,\n rawBody?: boolean,\n ): void {\n if (httpMethod === 'GET' && formPost === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare formPost:true because GET has no request body.`,\n );\n }\n if (httpMethod === 'GET' && rawBody === true) {\n throw new Error(\n `${apiClass.name}.${methodName} is GET and cannot declare rawBody:true because GET has no request body.`,\n );\n }\n }\n\n // webpieces-disable no-function-outside-class -- private pure validator alongside existing contract validators\n private static validateStreamingParameters(\n apiClass: Function,\n methodName: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n streaming: StreamingEndpointMetadata,\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n if (\n !streaming.initialRequestSchema ||\n !streaming.initialResponseSchema ||\n (streaming.direction !== StreamDirection.RESPONSE && !streaming.requestSchema) ||\n (streaming.direction !== StreamDirection.REQUEST && !streaming.responseSchema)\n ) {\n throw new Error(\n `${label} requires generated streaming schemas. Run wp-openapi and registerStreamingCatalog(${apiClass.name}, catalog) before binding.`,\n );\n }\n if (httpMethod !== 'POST') throw new Error(`${label} is streaming and must use POST.`);\n if (declarations.length > 0)\n throw new Error(`${label} is streaming and cannot declare path/query parameters.`);\n const expected = streaming.direction === StreamDirection.REQUEST ? 1 : 2;\n if (parameterTypes.length !== expected) {\n const form =\n streaming.direction === StreamDirection.REQUEST\n ? '(InitialRequest) => Promise<RequestStream<InitialResponse, RequestEvent>>'\n : streaming.direction === StreamDirection.RESPONSE\n ? '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<InitialResponse>'\n : '(InitialRequest, ResponseStream<ResponseEvent>) => Promise<RequestStream<InitialResponse, RequestEvent>>';\n throw new Error(\n `Invalid @WpStream(StreamDirection.${streaming.direction.toUpperCase()}) method ${label}: ` +\n `${streaming.direction.toUpperCase()} accepts exactly ${form}.`,\n );\n }\n return 0;\n }\n\n /**\n * Refuse a contract in which two endpoints resolve to the same HTTP method + path.\n *\n * Empty paths are LEGAL (`@ApiPath('')` + `@Endpoint(POST, '', ...)` is how a contract says \"the whole\n * destination arrives per call\"), so the thing actually worth rejecting is ambiguity: two methods\n * that land on one route. On the server the second registration would silently shadow the first\n * in the route map, and on the client both would dial the same URL — neither is ever what the\n * author meant. Placeholder NAMES are ignored when comparing (`/a/{id}` and `/a/{key}` are the\n * same route to a router), `''` and `'/'` compare equal (the server serves an empty path at the\n * root), and the error names BOTH methods so the fix is obvious.\n *\n * Called by every transport that binds a whole contract (the generated clients and the server's\n * route registration), so a duplicate dies at startup however the contract is consumed.\n *\n * @throws Error naming both colliding methods and the shared route.\n */\n // webpieces-disable no-function-outside-class -- deterministic contract validation shared by DI-free browser and server runtimes\n static assertNoDuplicateRoutes(apiClass: Function): void {\n const label = apiClass.name || 'Unknown';\n const seen = new Map<string, string>();\n for (const methodName of Object.keys(getEndpoints(apiClass) ?? {})) {\n const route = this.create(apiClass, methodName);\n // '' and '/' are one route to a server (every request path starts with '/').\n const shape = (route.path === '' ? '/' : route.path).replace(/\\{[^{}]+\\}/g, '{}');\n const key = `${route.httpMethod} ${shape}`;\n const previous = seen.get(key);\n if (previous !== undefined) {\n throw new Error(\n `${label}.${previous} and ${label}.${methodName} both resolve to ` +\n `${route.httpMethod} '${route.path}'. Two endpoints of one contract cannot share ` +\n `an HTTP method + path; give one of them a distinct @Endpoint path or HTTP method.`,\n );\n }\n seen.set(key, methodName);\n }\n }\n\n /**\n * Join `@ApiPath` and `@Endpoint` into the route path.\n *\n * Both empty joins to `''`, NOT `'/'`. A contract declaring `@ApiPath('')` + `@Endpoint(POST, '', ...)`\n * is saying \"this route adds nothing to the base URL\", and a client whose base URL is a full\n * destination (host + path + query) must send it byte for byte. Before #926 the client joined with\n * plain concatenation (`'' + ''`), and forcing a `/` here broke exactly that (#944).\n */\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static joinPath(basePath: string, endpointPath: string): string {\n if (basePath === '' && endpointPath === '') return '';\n const joined = `${basePath}/${endpointPath}`.replace(/\\/{2,}/g, '/');\n return joined.startsWith('/') ? joined : `/${joined}`;\n }\n\n // webpieces-disable no-function-outside-class -- private pure helper for the static metadata factory\n private static responseType(\n value: EndpointResponseType | undefined,\n apiClass: Function,\n methodName: string,\n ): EndpointResponseType {\n const responseType = value ?? 'body';\n if (responseType === 'body' || responseType === 'full') return responseType;\n throw new Error(\n `${apiClass.name}.${methodName} declares unsupported responseType '${String(responseType)}'; use body or full.`,\n );\n }\n\n // webpieces-disable no-function-outside-class -- Reflect metadata lookup has no injectable dependency\n private static parameterTypes(apiClass: Function, methodName: string): Function[] {\n return Reflect.getMetadata('design:paramtypes', apiClass.prototype, methodName) ?? [];\n }\n\n // webpieces-disable no-function-outside-class -- private deterministic contract validation helper\n private static validateParameters(\n apiClass: Function,\n methodName: string,\n fullPath: string,\n httpMethod: HttpMethod,\n parameterTypes: readonly Function[],\n declarations: readonly HttpParameterDeclaration[],\n ): number | undefined {\n const label = `${apiClass.name || 'Unknown'}.${methodName}`;\n const placeholders = [...fullPath.matchAll(/\\{([^{}]+)\\}/g)].map(\n (match: RegExpMatchArray) => match[1],\n );\n const pathNames = declarations\n .filter((declaration: HttpParameterDeclaration) => declaration.source === 'path')\n .map((declaration: HttpParameterDeclaration) => declaration.wireName);\n for (const placeholder of new Set(placeholders)) {\n if (!pathNames.includes(placeholder)) {\n throw new Error(\n `${label} path '${fullPath}' contains {${placeholder}} but no @PathParam('${placeholder}') parameter.`,\n );\n }\n }\n for (const pathName of pathNames) {\n if (!placeholders.includes(pathName)) {\n throw new Error(\n `${label} declares @PathParam('${pathName}') but '${fullPath}' has no {${pathName}} placeholder.`,\n );\n }\n }\n\n const seenWire = new Set<string>();\n const mappedIndexes = new Set<number>();\n for (const declaration of declarations) {\n const wireKey = `${declaration.source}:${declaration.wireName}`;\n if (seenWire.has(wireKey)) {\n throw new Error(`${label} maps '${wireKey}' more than once.`);\n }\n seenWire.add(wireKey);\n mappedIndexes.add(declaration.index);\n }\n\n const declaredCount = Math.max(\n parameterTypes.length,\n declarations.reduce(\n (highest: number, declaration: HttpParameterDeclaration) =>\n Math.max(highest, declaration.index + 1),\n 0,\n ),\n );\n const unbound: number[] = [];\n for (let index = 0; index < declaredCount; index++) {\n if (!mappedIndexes.has(index)) unbound.push(index);\n }\n if (httpMethod === 'GET' && unbound.length > 0) {\n throw new Error(\n `${label} is GET, so every parameter must use @PathParam(...) or @QueryParam(...); argument ${unbound[0]} is unmapped.`,\n );\n }\n if (unbound.length > 1) {\n throw new Error(\n `${label} has ${unbound.length} unmapped parameters. POST permits exactly one unannotated request body; map every other parameter explicitly.`,\n );\n }\n return unbound[0];\n }\n\n // webpieces-disable no-function-outside-class -- private conversion of reflected constructors to wire types\n private static valueType(type: Function | undefined): HttpParameterValueType {\n if (type === String) return 'string';\n if (type === Number) return 'number';\n if (type === Boolean) return 'boolean';\n if (type === Array) return 'array';\n return 'unknown';\n }\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Where this process is running, as a NAMED token rather than a boolean:
3
3
  *
4
- * - `'local'` — a developer's machine. `@WpAuthLocalOnly` endpoints exist and serve.
5
- * - `'deployed'` — anywhere else (staging, prod, CI, a container). `@WpAuthLocalOnly` endpoints are
4
+ * - `'local'` — a developer's machine. `@WpLocalOnly` endpoints exist and serve.
5
+ * - `'deployed'` — anywhere else (staging, prod, CI, a container). `@WpLocalOnly` endpoints are
6
6
  * not registered and, if reached anyway, 404.
7
7
  *
8
8
  * A `boolean` would have made the DANGEROUS half (`true`) unnameable and ungreppable — see
@@ -12,7 +12,7 @@
12
12
  export type Locality = 'local' | 'deployed';
13
13
  /**
14
14
  * RuntimeLocality - the ONE answer to "am I running on a developer's machine?", for the one part of
15
- * webpieces that needs it: {@link WpAuthLocalOnly}.
15
+ * webpieces that needs it: {@link WpLocalOnly}.
16
16
  *
17
17
  * ## Why this is a seam and not a `process.env` read
18
18
  *
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RuntimeLocality = void 0;
4
4
  /**
5
5
  * RuntimeLocality - the ONE answer to "am I running on a developer's machine?", for the one part of
6
- * webpieces that needs it: {@link WpAuthLocalOnly}.
6
+ * webpieces that needs it: {@link WpLocalOnly}.
7
7
  *
8
8
  * ## Why this is a seam and not a `process.env` read
9
9
  *
@@ -1 +1 @@
1
- {"version":3,"file":"RuntimeLocality.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RuntimeLocality.ts"],"names":[],"mappings":";;;AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAa,eAAe;IACxB,+FAA+F;IACvF,MAAM,CAAC,QAAQ,CAAuB;IAE9C;;;;;;OAMG;IACH,yJAAyJ;IACzJ,MAAM,CAAC,OAAO,CAAC,QAAkB;QAC7B,eAAe,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,yJAAyJ;IACzJ,MAAM,CAAC,kBAAkB;QACrB,OAAO,eAAe,CAAC,QAAQ,KAAK,OAAO,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,yJAAyJ;IACzJ,MAAM,CAAC,UAAU;QACb,OAAO,eAAe,CAAC,QAAQ,KAAK,SAAS,CAAC;IAClD,CAAC;IAED,8DAA8D;IAC9D,yJAAyJ;IACzJ,MAAM,CAAC,KAAK;QACR,eAAe,CAAC,QAAQ,GAAG,SAAS,CAAC;IACzC,CAAC;CACJ;AAzCD,0CAyCC","sourcesContent":["/**\n * Where this process is running, as a NAMED token rather than a boolean:\n *\n * - `'local'` — a developer's machine. `@WpAuthLocalOnly` endpoints exist and serve.\n * - `'deployed'` — anywhere else (staging, prod, CI, a container). `@WpAuthLocalOnly` endpoints are\n * not registered and, if reached anyway, 404.\n *\n * A `boolean` would have made the DANGEROUS half (`true`) unnameable and ungreppable — see\n * `.claude/rules/no-backwards-compat.md` shim shape #5. `grep -rn \"'local'\" ` over a repo's startup now lists every place that claims to be a\n * developer's machine.\n */\nexport type Locality = 'local' | 'deployed';\n\n/**\n * RuntimeLocality - the ONE answer to \"am I running on a developer's machine?\", for the one part of\n * webpieces that needs it: {@link WpAuthLocalOnly}.\n *\n * ## Why this is a seam and not a `process.env` read\n *\n * The framework cannot compute this itself and must not try. \"Local\" is a fact about the DEPLOYMENT\n * PLATFORM: Cloud Run derives it from `K_SERVICE`, ECS from `ECS_CONTAINER_METADATA_URI`, a laptop\n * from the absence of both. Baking any one of those into core-util would hardcode a cloud vendor into\n * the framework core, and core-util is browser-safe (it may not read `process.env` at all). So the\n * ENVIRONMENT tells the framework, exactly as it tells it the logging backend\n * ({@link LogManager.setFactory}), the header set ({@link HeaderRegistry.configure}) and its own\n * identity ({@link ServiceInfo.setInfo}). (The {@link ApiCallContext} seam is NOT in that list any\n * more: it is a constructor argument to {@link LogApiCallImpl}, not a startup install.)\n *\n * It is a VALUE holder rather than an interface-plus-impl (the `ApiCallContext` shape) because there\n * is no behavior to plug in — the answer is one token fixed at startup. Per CLAUDE.md, data is a\n * class; only behavior is an interface.\n *\n * ## Where it is declared\n *\n * `RuntimeSetupOptions` takes it as a REQUIRED, positional constructor argument, so `setupRuntime`\n * declares it on every server and no server can boot without having stated it. That is the same\n * forcing function `@Endpoint(path, kind)` uses: a required positional argument turns \"we forgot\" into\n * a compile error instead of a runtime guess.\n *\n * ## FAIL SAFE when nothing declared it\n *\n * {@link isLocalDevelopment} returns `false` until {@link declare} is called. An undeclared process is\n * treated as DEPLOYED, so the failure mode of a forgotten wiring call is \"my local-only endpoint 404s\n * on my laptop\" — annoying and instantly visible — never \"my local-only endpoint is live in\n * production\". The permissive answer is never the one you get by not typing anything.\n */\nexport class RuntimeLocality {\n /** Process-global; set once at startup. `undefined` = never declared = treated as deployed. */\n private static locality: Locality | undefined;\n\n /**\n * State where this process is running. Call it at startup — `setupRuntime` does it for you from\n * `RuntimeSetupOptions.locality`.\n *\n * LAST CALL WINS, mirroring {@link ServiceInfo.setInfo}: an in-process test can legitimately boot\n * two servers back-to-back.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static declare(locality: Locality): void {\n RuntimeLocality.locality = locality;\n }\n\n /**\n * True ONLY when a startup explicitly declared `'local'`. Undeclared reads as deployed — see the\n * fail-safe note on the class. Does not throw: a wrong answer here must refuse an endpoint, never\n * 500 unrelated traffic.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static isLocalDevelopment(): boolean {\n return RuntimeLocality.locality === 'local';\n }\n\n /**\n * Whether anything declared a locality at all. Used ONLY to make the refusal log say which of the\n * two reasons applies — \"you are deployed\" vs \"nobody ever told me\" — because those have very\n * different fixes and a developer staring at a 404 on their own laptop needs to know which.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static isDeclared(): boolean {\n return RuntimeLocality.locality !== undefined;\n }\n\n /** Reset — for tests, mirroring {@link ServiceInfo.clear}. */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static clear(): void {\n RuntimeLocality.locality = undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"RuntimeLocality.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/RuntimeLocality.ts"],"names":[],"mappings":";;;AAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAa,eAAe;IACxB,+FAA+F;IACvF,MAAM,CAAC,QAAQ,CAAuB;IAE9C;;;;;;OAMG;IACH,yJAAyJ;IACzJ,MAAM,CAAC,OAAO,CAAC,QAAkB;QAC7B,eAAe,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,yJAAyJ;IACzJ,MAAM,CAAC,kBAAkB;QACrB,OAAO,eAAe,CAAC,QAAQ,KAAK,OAAO,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACH,yJAAyJ;IACzJ,MAAM,CAAC,UAAU;QACb,OAAO,eAAe,CAAC,QAAQ,KAAK,SAAS,CAAC;IAClD,CAAC;IAED,8DAA8D;IAC9D,yJAAyJ;IACzJ,MAAM,CAAC,KAAK;QACR,eAAe,CAAC,QAAQ,GAAG,SAAS,CAAC;IACzC,CAAC;CACJ;AAzCD,0CAyCC","sourcesContent":["/**\n * Where this process is running, as a NAMED token rather than a boolean:\n *\n * - `'local'` — a developer's machine. `@WpLocalOnly` endpoints exist and serve.\n * - `'deployed'` — anywhere else (staging, prod, CI, a container). `@WpLocalOnly` endpoints are\n * not registered and, if reached anyway, 404.\n *\n * A `boolean` would have made the DANGEROUS half (`true`) unnameable and ungreppable — see\n * `.claude/rules/no-backwards-compat.md` shim shape #5. `grep -rn \"'local'\" ` over a repo's startup now lists every place that claims to be a\n * developer's machine.\n */\nexport type Locality = 'local' | 'deployed';\n\n/**\n * RuntimeLocality - the ONE answer to \"am I running on a developer's machine?\", for the one part of\n * webpieces that needs it: {@link WpLocalOnly}.\n *\n * ## Why this is a seam and not a `process.env` read\n *\n * The framework cannot compute this itself and must not try. \"Local\" is a fact about the DEPLOYMENT\n * PLATFORM: Cloud Run derives it from `K_SERVICE`, ECS from `ECS_CONTAINER_METADATA_URI`, a laptop\n * from the absence of both. Baking any one of those into core-util would hardcode a cloud vendor into\n * the framework core, and core-util is browser-safe (it may not read `process.env` at all). So the\n * ENVIRONMENT tells the framework, exactly as it tells it the logging backend\n * ({@link LogManager.setFactory}), the header set ({@link HeaderRegistry.configure}) and its own\n * identity ({@link ServiceInfo.setInfo}). (The {@link ApiCallContext} seam is NOT in that list any\n * more: it is a constructor argument to {@link LogApiCallImpl}, not a startup install.)\n *\n * It is a VALUE holder rather than an interface-plus-impl (the `ApiCallContext` shape) because there\n * is no behavior to plug in — the answer is one token fixed at startup. Per CLAUDE.md, data is a\n * class; only behavior is an interface.\n *\n * ## Where it is declared\n *\n * `RuntimeSetupOptions` takes it as a REQUIRED, positional constructor argument, so `setupRuntime`\n * declares it on every server and no server can boot without having stated it. That is the same\n * forcing function `@Endpoint(path, kind)` uses: a required positional argument turns \"we forgot\" into\n * a compile error instead of a runtime guess.\n *\n * ## FAIL SAFE when nothing declared it\n *\n * {@link isLocalDevelopment} returns `false` until {@link declare} is called. An undeclared process is\n * treated as DEPLOYED, so the failure mode of a forgotten wiring call is \"my local-only endpoint 404s\n * on my laptop\" — annoying and instantly visible — never \"my local-only endpoint is live in\n * production\". The permissive answer is never the one you get by not typing anything.\n */\nexport class RuntimeLocality {\n /** Process-global; set once at startup. `undefined` = never declared = treated as deployed. */\n private static locality: Locality | undefined;\n\n /**\n * State where this process is running. Call it at startup — `setupRuntime` does it for you from\n * `RuntimeSetupOptions.locality`.\n *\n * LAST CALL WINS, mirroring {@link ServiceInfo.setInfo}: an in-process test can legitimately boot\n * two servers back-to-back.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static declare(locality: Locality): void {\n RuntimeLocality.locality = locality;\n }\n\n /**\n * True ONLY when a startup explicitly declared `'local'`. Undeclared reads as deployed — see the\n * fail-safe note on the class. Does not throw: a wrong answer here must refuse an endpoint, never\n * 500 unrelated traffic.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static isLocalDevelopment(): boolean {\n return RuntimeLocality.locality === 'local';\n }\n\n /**\n * Whether anything declared a locality at all. Used ONLY to make the refusal log say which of the\n * two reasons applies — \"you are deployed\" vs \"nobody ever told me\" — because those have very\n * different fixes and a developer staring at a 404 on their own laptop needs to know which.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static isDeclared(): boolean {\n return RuntimeLocality.locality !== undefined;\n }\n\n /** Reset — for tests, mirroring {@link ServiceInfo.clear}. */\n // webpieces-disable no-function-outside-class -- static global singleton (like ServiceInfo/HeaderRegistry); populated once at startup, never DI-injected\n static clear(): void {\n RuntimeLocality.locality = undefined;\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Secrets - the CLIENT-side shared-secret store: the ONE value THIS service currently SENDS per
3
- * `@WpAuthSharedSecret(key)` name. Bound once (from config; tests pass literals) and used by every
3
+ * `sharedSecret(key)` name. Bound once (from config; tests pass literals) and used by every
4
4
  * outbound client (the RPC http-client + the Cloud Tasks invokers) — NEVER read from process.env in
5
5
  * the send path, so tests stay parallel-safe.
6
6
  *
@@ -11,7 +11,7 @@
11
11
  export declare class Secrets {
12
12
  private readonly values;
13
13
  constructor(values?: Record<string, string | undefined>);
14
- /** The secret this client sends for the given `@WpAuthSharedSecret(key)`, or undefined if unset. */
14
+ /** The secret this client sends for the given `sharedSecret(key)`, or undefined if unset. */
15
15
  get(key: string): string | undefined;
16
16
  }
17
17
  /**
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SECRETS = exports.Secrets = void 0;
4
4
  /**
5
5
  * Secrets - the CLIENT-side shared-secret store: the ONE value THIS service currently SENDS per
6
- * `@WpAuthSharedSecret(key)` name. Bound once (from config; tests pass literals) and used by every
6
+ * `sharedSecret(key)` name. Bound once (from config; tests pass literals) and used by every
7
7
  * outbound client (the RPC http-client + the Cloud Tasks invokers) — NEVER read from process.env in
8
8
  * the send path, so tests stay parallel-safe.
9
9
  *
@@ -18,7 +18,7 @@ class Secrets {
18
18
  constructor(values = {}) {
19
19
  this.values = values;
20
20
  }
21
- /** The secret this client sends for the given `@WpAuthSharedSecret(key)`, or undefined if unset. */
21
+ /** The secret this client sends for the given `sharedSecret(key)`, or undefined if unset. */
22
22
  get(key) {
23
23
  return this.values[key];
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Secrets.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/Secrets.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,OAAO;IAGa;IAF7B,iGAAiG;IACjG,wGAAwG;IACxG,YAA6B,SAA6C,EAAE;QAA/C,WAAM,GAAN,MAAM,CAAyC;IAAG,CAAC;IAEhF,oGAAoG;IACpG,GAAG,CAAC,GAAW;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;CACJ;AATD,0BASC;AAED;;;;GAIG;AACU,QAAA,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC","sourcesContent":["/**\n * Secrets - the CLIENT-side shared-secret store: the ONE value THIS service currently SENDS per\n * `@WpAuthSharedSecret(key)` name. Bound once (from config; tests pass literals) and used by every\n * outbound client (the RPC http-client + the Cloud Tasks invokers) — NEVER read from process.env in\n * the send path, so tests stay parallel-safe.\n *\n * The SERVER counterpart accepts TWO values per key ({@link SharedSecrets}) for zero-downtime\n * rotation; a client sends ONE and is migrated by changing its value here. Data-only structure (a\n * class, per the webpieces guidelines).\n */\nexport class Secrets {\n // Values are `string | undefined` so a process.env slot drops straight in with no `?? ''` noise:\n // new Secrets({ INTERNAL_API_SECRET: process.env['INTERNAL_API_SECRET'], KEY2: process.env['KEY2'] })\n constructor(private readonly values: Record<string, string | undefined> = {}) {}\n\n /** The secret this client sends for the given `@WpAuthSharedSecret(key)`, or undefined if unset. */\n get(key: string): string | undefined {\n return this.values[key];\n }\n}\n\n/**\n * DI identifier for the optional {@link Secrets} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(SECRETS)`\n * correct — undefined when unbound. The Secrets class stays the TYPE.\n */\nexport const SECRETS = Symbol.for('Secrets');\n"]}
1
+ {"version":3,"file":"Secrets.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/Secrets.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,OAAO;IAGa;IAF7B,iGAAiG;IACjG,wGAAwG;IACxG,YAA6B,SAA6C,EAAE;QAA/C,WAAM,GAAN,MAAM,CAAyC;IAAG,CAAC;IAEhF,6FAA6F;IAC7F,GAAG,CAAC,GAAW;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;CACJ;AATD,0BASC;AAED;;;;GAIG;AACU,QAAA,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC","sourcesContent":["/**\n * Secrets - the CLIENT-side shared-secret store: the ONE value THIS service currently SENDS per\n * `sharedSecret(key)` name. Bound once (from config; tests pass literals) and used by every\n * outbound client (the RPC http-client + the Cloud Tasks invokers) — NEVER read from process.env in\n * the send path, so tests stay parallel-safe.\n *\n * The SERVER counterpart accepts TWO values per key ({@link SharedSecrets}) for zero-downtime\n * rotation; a client sends ONE and is migrated by changing its value here. Data-only structure (a\n * class, per the webpieces guidelines).\n */\nexport class Secrets {\n // Values are `string | undefined` so a process.env slot drops straight in with no `?? ''` noise:\n // new Secrets({ INTERNAL_API_SECRET: process.env['INTERNAL_API_SECRET'], KEY2: process.env['KEY2'] })\n constructor(private readonly values: Record<string, string | undefined> = {}) {}\n\n /** The secret this client sends for the given `sharedSecret(key)`, or undefined if unset. */\n get(key: string): string | undefined {\n return this.values[key];\n }\n}\n\n/**\n * DI identifier for the optional {@link Secrets} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(SECRETS)`\n * correct — undefined when unbound. The Secrets class stays the TYPE.\n */\nexport const SECRETS = Symbol.for('Secrets');\n"]}
@@ -10,12 +10,12 @@
10
10
  *
11
11
  * | how the request authenticated | surface |
12
12
  * |-------------------------------|--------------|
13
- * | `@WpAuthJwt` | `gui` |
14
- * | `@WpMcpAuthJwt` (MCP bridge) | `llm` |
15
- * | `@WpAuthApiKey` | `public-api` |
13
+ * | `jwt()` | `gui` |
14
+ * | `@WpAuthorization` (MCP bridge) | `llm` |
15
+ * | `apiKey(...)` | `public-api` |
16
16
  *
17
17
  * It is set at the EDGE ONLY and then PROPAGATES: the surface is the ORIGINAL caller's, so a hop that
18
- * already received one INHERITS it unchanged. `@WpAuthOidc` is an internal service-to-service hop, not
18
+ * already received one INHERITS it unchanged. `oidc(...)` is an internal service-to-service hop, not
19
19
  * a surface of its own, and must never overwrite a propagated value — `GUI -> api1 -> api2` leaves
20
20
  * api2 seeing `gui`, which is the whole point of carrying it.
21
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Surface.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/Surface.ts"],"names":[],"mappings":";;;AAuBA;;;;;;;;;;;;;GAaG;AACH,MAAa,oBAAoB;IAC7B,oGAAoG;IACpG,MAAM,CAAC,SAAS,CAAC,OAA4B,EAAE,cAAkC;QAC7E,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;YAC3B,OAAO,cAAc,IAAI,GAAG,CAAC;QACjC,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;CACJ;AARD,oDAQC","sourcesContent":["/**\n * WHICH KIND OF CALLER is driving this request, all the way from the edge.\n *\n * The same endpoint can be reached by a browser GUI, by an LLM through the MCP bridge, and by an\n * external partner against a published REST contract. \"Does an end-user error render as 266 or as a\n * real 4xx\" is therefore a property of the CALLER, not of the server — which is why the per-router\n * `setEndUserStatus` this replaced could never express it: one router serves all three.\n *\n * webpieces already knows which, from the auth mode that matched the incoming request:\n *\n * | how the request authenticated | surface |\n * |-------------------------------|--------------|\n * | `@WpAuthJwt` | `gui` |\n * | `@WpMcpAuthJwt` (MCP bridge) | `llm` |\n * | `@WpAuthApiKey` | `public-api` |\n *\n * It is set at the EDGE ONLY and then PROPAGATES: the surface is the ORIGINAL caller's, so a hop that\n * already received one INHERITS it unchanged. `@WpAuthOidc` is an internal service-to-service hop, not\n * a surface of its own, and must never overwrite a propagated value — `GUI -> api1 -> api2` leaves\n * api2 seeing `gui`, which is the whole point of carrying it.\n */\nexport type Surface = 'gui' | 'llm' | 'public-api';\n\n/**\n * The ONE derivation of \"what HTTP status does an `ApiEndUserError` publish on this request\" — the\n * per-request replacement for the deleted per-router `EndUserStatus`.\n *\n * - `gui` and `llm` -> **266**. Both are webpieces clients that DECODE the body: a browser client\n * reconstructs the typed `ApiEndUserError`, and the MCP bridge calls through `ApiFactory` and\n * renders the result for the model itself. A real 4xx would throw away the message either one is\n * there to show.\n * - `public-api` -> the status the THROW SITE published (`ApiEndUserError.edgeHttpStatus`, else 400),\n * because a partner's REST contract promises real statuses and has no webpieces client to decode\n * anything.\n * - **absent** -> 266, the historical default, so a public endpoint or a non-webpieces caller behaves\n * exactly as it did before this key existed.\n */\nexport class SurfaceEndUserStatus {\n // webpieces-disable no-function-outside-class -- stateless per-request derivation, no state to hold\n static statusFor(surface: Surface | undefined, edgeHttpStatus: number | undefined): number {\n if (surface === 'public-api') {\n return edgeHttpStatus ?? 400;\n }\n return 266;\n }\n}\n"]}
1
+ {"version":3,"file":"Surface.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/Surface.ts"],"names":[],"mappings":";;;AAuBA;;;;;;;;;;;;;GAaG;AACH,MAAa,oBAAoB;IAC7B,oGAAoG;IACpG,MAAM,CAAC,SAAS,CAAC,OAA4B,EAAE,cAAkC;QAC7E,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;YAC3B,OAAO,cAAc,IAAI,GAAG,CAAC;QACjC,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;CACJ;AARD,oDAQC","sourcesContent":["/**\n * WHICH KIND OF CALLER is driving this request, all the way from the edge.\n *\n * The same endpoint can be reached by a browser GUI, by an LLM through the MCP bridge, and by an\n * external partner against a published REST contract. \"Does an end-user error render as 266 or as a\n * real 4xx\" is therefore a property of the CALLER, not of the server — which is why the per-router\n * `setEndUserStatus` this replaced could never express it: one router serves all three.\n *\n * webpieces already knows which, from the auth mode that matched the incoming request:\n *\n * | how the request authenticated | surface |\n * |-------------------------------|--------------|\n * | `jwt()` | `gui` |\n * | `@WpAuthorization` (MCP bridge) | `llm` |\n * | `apiKey(...)` | `public-api` |\n *\n * It is set at the EDGE ONLY and then PROPAGATES: the surface is the ORIGINAL caller's, so a hop that\n * already received one INHERITS it unchanged. `oidc(...)` is an internal service-to-service hop, not\n * a surface of its own, and must never overwrite a propagated value — `GUI -> api1 -> api2` leaves\n * api2 seeing `gui`, which is the whole point of carrying it.\n */\nexport type Surface = 'gui' | 'llm' | 'public-api';\n\n/**\n * The ONE derivation of \"what HTTP status does an `ApiEndUserError` publish on this request\" — the\n * per-request replacement for the deleted per-router `EndUserStatus`.\n *\n * - `gui` and `llm` -> **266**. Both are webpieces clients that DECODE the body: a browser client\n * reconstructs the typed `ApiEndUserError`, and the MCP bridge calls through `ApiFactory` and\n * renders the result for the model itself. A real 4xx would throw away the message either one is\n * there to show.\n * - `public-api` -> the status the THROW SITE published (`ApiEndUserError.edgeHttpStatus`, else 400),\n * because a partner's REST contract promises real statuses and has no webpieces client to decode\n * anything.\n * - **absent** -> 266, the historical default, so a public endpoint or a non-webpieces caller behaves\n * exactly as it did before this key existed.\n */\nexport class SurfaceEndUserStatus {\n // webpieces-disable no-function-outside-class -- stateless per-request derivation, no state to hold\n static statusFor(surface: Surface | undefined, edgeHttpStatus: number | undefined): number {\n if (surface === 'public-api') {\n return edgeHttpStatus ?? 400;\n }\n return 266;\n }\n}\n"]}
@@ -99,8 +99,8 @@ export declare class WebpiecesCoreHeaders {
99
99
  * service is the point. What makes that safe is not the header being absent, it is WHO is
100
100
  * allowed to have set it: an inbound value is held PENDING by
101
101
  * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that
102
- * verified its CALLER (`@WpAuthOidc` / `@WpAuthSharedSecret`). On a browser-reachable route
103
- * (`@WpAuthJwt` / public) the value must match what the authenticator itself derived, or the
102
+ * verified its CALLER (`oidc(...)` / `sharedSecret(...)`). On a browser-reachable route
103
+ * (`jwt()` / public) the value must match what the authenticator itself derived, or the
104
104
  * request is rejected.
105
105
  *
106
106
  * `provenance` says "an app-bound JwtHook" rather than naming one hook, because the framework
@@ -124,7 +124,7 @@ export declare class WebpiecesCoreHeaders {
124
124
  * every other unvouched trusted header.
125
125
  *
126
126
  * It keeps its `httpHeader` because PROPAGATION is the point: the surface belongs to the ORIGINAL
127
- * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `@WpAuthOidc` hop
127
+ * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `oidc(...)` hop
128
128
  * verifies its caller, so the propagated value is admitted verbatim — and the auth filter only
129
129
  * ever SETS a surface when none arrived, so an inherited one is never overwritten.
130
130
  */
@@ -105,8 +105,8 @@ class WebpiecesCoreHeaders {
105
105
  * service is the point. What makes that safe is not the header being absent, it is WHO is
106
106
  * allowed to have set it: an inbound value is held PENDING by
107
107
  * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that
108
- * verified its CALLER (`@WpAuthOidc` / `@WpAuthSharedSecret`). On a browser-reachable route
109
- * (`@WpAuthJwt` / public) the value must match what the authenticator itself derived, or the
108
+ * verified its CALLER (`oidc(...)` / `sharedSecret(...)`). On a browser-reachable route
109
+ * (`jwt()` / public) the value must match what the authenticator itself derived, or the
110
110
  * request is rejected.
111
111
  *
112
112
  * `provenance` says "an app-bound JwtHook" rather than naming one hook, because the framework
@@ -130,7 +130,7 @@ class WebpiecesCoreHeaders {
130
130
  * every other unvouched trusted header.
131
131
  *
132
132
  * It keeps its `httpHeader` because PROPAGATION is the point: the surface belongs to the ORIGINAL
133
- * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `@WpAuthOidc` hop
133
+ * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `oidc(...)` hop
134
134
  * verifies its caller, so the propagated value is admitted verbatim — and the auth filter only
135
135
  * ever SETS a surface when none arrived, so an inherited one is never overwritten.
136
136
  */
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { ApiCallInfo } from './ApiCallInfo';\nimport { Surface } from './Surface';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n *\n * - `httpHeader` `x-request-id` → travels DOWN the call tree on every outbound request.\n * - `responseHeader` `x-request-id` → and back UP on every response, which is the header a\n * support desk quotes. This used to be a hard-coded `res.setHeader` in `ExpressWrapper`\n * (`stampTransactionId`), reachable by no other key. It is an ordinary registry entry now: the\n * server's ONE response choke point writes every key in\n * {@link HeaderRegistry.getResponseTxfrKeys}, so a second key joins with no framework edit.\n * - `responseMerge` `first` → and NOT `collect`. A callee echoes OUR id back at us, so the\n * caller's context must KEEP what it already has. `collect` would turn `requestId` into a\n * growing array on every client call and break every log line of the request; `last` would\n * rewrite it with an echo. `first` makes reading it back a provable no-op for the normal case\n * and still adopts an id when a caller somehow had none.\n */\n static readonly REQUEST_ID = ContextKey.untrusted<string>(\n 'requestId',\n 'x-request-id',\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n /*responseHeader*/ 'x-request-id',\n /*responseMerge*/ 'first',\n );\n\n /** Framework-minted identity for one inbound streaming event; never accepted from HTTP. */\n static readonly STREAM_EVENT_ID = ContextKey.untrusted<string>(\n 'streamEventId',\n undefined,\n false,\n true,\n );\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = ContextKey.untrusted<string>(\n 'requestIdSource',\n /*httpHeader*/ undefined,\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = ContextKey.untrusted<string>(\n 'clientVersion',\n 'x-webpieces-client-version',\n );\n\n /**\n * A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.\n *\n * An \"action\" is a single thing the user did in the GUI — a CLICK on a button/link, or TYPING in a\n * field — or a background poller tick: anything that may fan out into MULTIPLE remote calls. That one\n * action fires 1..N browser HTTP calls, each of which gets its own framework-minted {@link REQUEST_ID}\n * (one per HTTP call, shared within that call's server→server subtree). `actionId` sits ABOVE\n * `requestId` and is what stitches those N requests back to the single action that caused them:\n *\n * actionId (app-minted, ONE per user action, rides EVERY call of that action)\n * └── 1..N requestId (framework-minted, ONE per HTTP call)\n *\n * Grep one `actionId` in the logs → every `requestId` it spawned, and every log line of the whole\n * action. Minted and refreshed by the app (a UI concern), carried under `x-webpieces-actionid`.\n *\n * Browser/app-minted ONLY: unlike {@link REQUEST_ID}, the framework transfers and logs it but must\n * NOT auto-mint one server-side. Absent `actionId` ⇒ a non-action flow (system / cron / task), which\n * is the correct signal.\n *\n * - `httpHeader` SET → transferred: copied off the inbound request into context and re-emitted on\n * outbound hops, so the id follows the action across services.\n * - `isLogged` TRUE → emitted as a plain string on every log line of the request.\n */\n static readonly ACTION_ID = ContextKey.untrusted<string>('actionId', 'x-webpieces-actionid');\n\n /**\n * WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the\n * inputs to authorization decisions, so a reader must be able to tell \"the framework proved\n * this\" from \"the caller typed this\" — see the trust section of the {@link ContextKey} doc.\n *\n * They keep their `httpHeader`, because propagating a verified identity to the next internal\n * service is the point. What makes that safe is not the header being absent, it is WHO is\n * allowed to have set it: an inbound value is held PENDING by\n * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that\n * verified its CALLER (`@WpAuthOidc` / `@WpAuthSharedSecret`). On a browser-reachable route\n * (`@WpAuthJwt` / public) the value must match what the authenticator itself derived, or the\n * request is rejected.\n *\n * `provenance` says \"an app-bound JwtHook\" rather than naming one hook, because the framework\n * default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that\n * returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's\n * hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud\n * outcome: nothing is vouching for it.\n */\n static readonly ORG_ID = ContextKey.trusted<string>(\n 'orgId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-org-id',\n );\n\n static readonly USER_ID = ContextKey.trusted<string>(\n 'userId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-user-id',\n );\n\n static readonly USER_ROLES = ContextKey.trusted<string>(\n 'roles',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-webpieces-roles',\n );\n\n /**\n * WHICH KIND OF CALLER is driving this request — `gui`, `llm` or `public-api`. See\n * {@link Surface} for the table and for why it is a property of the CALLER and not of the server.\n *\n * TRUSTED, and that is load-bearing twice over. It is what makes an app safe to branch on it in\n * its OWN authorization logic (\"this endpoint is not exposed to partners\"), and it is what makes\n * the value in {@link SurfaceEndUserStatus} believable: webpieces sets it itself, from the auth\n * mode that matched, never from caller input. A caller that simply SENDS `x-wp-surface` at an\n * endpoint whose auth did not establish one is rejected by `AuthFilter.reconcileWireTrust`, like\n * every other unvouched trusted header.\n *\n * It keeps its `httpHeader` because PROPAGATION is the point: the surface belongs to the ORIGINAL\n * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `@WpAuthOidc` hop\n * verifies its caller, so the propagated value is admitted verbatim — and the auth filter only\n * ever SETS a surface when none arrived, so an inherited one is never overwritten.\n */\n static readonly SURFACE = ContextKey.trusted<Surface>(\n 'surface',\n 'set by the webpieces AuthFilter from the auth mode that matched the incoming request, or inherited from the edge hop that did',\n 'x-wp-surface',\n );\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = ContextKey.untrusted<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCallImpl} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = ContextKey.untrusted<ApiCallInfo>(\n 'api',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = ContextKey.untrusted<string>(\n 'httpMethod',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly REQUEST_PATH = ContextKey.untrusted<string>(\n 'requestPath',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The routed endpoint's IMPLEMENTATION identity: the concrete controller class name\n * ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME\n * ({@link RouteMetadata.methodName}, e.g. `login`), stamped once per request by {@link LogApiFilter}\n * after route matching so every subsequent log line of the request carries them.\n *\n * These say WHICH CODE ran, which is what you actually grep for — far more useful than the raw\n * `requestPath`. They are the top-level, filterable twin of what previously only lived nested in\n * {@link ApiCallInfo} (`api.method.controllerName` / `api.method.methodName`). The local console\n * formatters render them together as a compact `[Controller.method]` bracket; GCP keeps them as two\n * separate `jsonPayload.controller` / `jsonPayload.method` fields.\n *\n * NOTE: `method` here is the CODE method name (e.g. `login`), NOT the HTTP verb — that is\n * {@link HTTP_METHOD} (`httpMethod`).\n *\n * - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly CONTROLLER = ContextKey.untrusted<string>(\n 'controller',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly METHOD = ContextKey.untrusted<string>(\n 'method',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The base URL ONE outbound call should go to, overriding whatever the client's `ClientConfig`\n * bound at construction — the answer to \"POST our published contract to a URL the PARTNER\n * registered at runtime\" (an `OrganizationWebhook.url` column, an OAuth callback, a per-tenant\n * or self-hosted host). The destination is DATA, not deployment, so it cannot be a svcName and\n * there is nothing to register in {@link ClientRegistry}.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * The contract's path IS appended to it (base + `@ApiPath` + `@Endpoint`). When the stored value\n * is a complete url — the usual partner-webhook row — use {@link OVERRIDE_FULL_URL} instead.\n *\n * ONLY a client carrying a `ContextBaseUrlFilter` reads it. Every other client IGNORES this key\n * entirely, which is what stops an ambient value re-pointing every other client in the same\n * fan-out loop at a partner's server. Installing that ONE filter IS the opt-in, so\n * `grep -rn ContextBaseUrlFilter` enumerates every client that can be re-pointed at all.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is load-bearing. This value\n * names where THIS hop goes. If it travelled, the callee would inherit it and re-point ITS\n * own outbound calls at the same host — one partner-supplied URL turning into an SSRF pivot\n * across the whole call tree. It is per-hop, always.\n * - `isLogged` TRUE → the destination of a partner delivery is exactly what you want in the log\n * line when one fails.\n *\n * UNTRUSTED, necessarily: it comes from a database column a partner edited. That is precisely\n * why re-pointing a request ARMS the framework's SSRF guard, automatically, rather than trusting\n * it — the guard sits beneath every app filter and reads the fact that the request was moved.\n */\n static readonly OVERRIDE_BASE_URL = ContextKey.untrusted<string>(\n 'overrideBaseUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The COMPLETE url — host, path AND query — ONE outbound call should be sent to, verbatim. The\n * sibling of {@link OVERRIDE_BASE_URL} for the other shape of runtime destination: a partner\n * that does not implement our contract at their own host, but hands us one opaque url to POST\n * to (`https://hooks.partner.example/in/abc?token=xyz`, straight out of a webhook row). With a\n * base-URL override the contract's path would be appended to it and the url would be wrong; with\n * this one the contract's path is NOT appended and nothing is inserted.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_FULL_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * ONLY a client carrying a `ContextFullUrlFilter` reads it, for exactly the reasons given on\n * {@link OVERRIDE_BASE_URL}: installing that filter IS the opt-in, and every other client ignores\n * the key. Not transferred over the wire (per-hop, or it becomes an SSRF pivot across the call\n * tree), logged (it is what you want in a failed-delivery log line), and UNTRUSTED (a partner\n * edited it) — which is why re-pointing at it arms the framework's SSRF guard automatically.\n */\n static readonly OVERRIDE_FULL_URL = ContextKey.untrusted<string>(\n 'overrideFullUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * All core context keys (the platform DEFAULT_HEADERS set). A `static readonly` CONSTANT, not a\n * method: it is compile-time data — a list of the key definitions above — read once at the startup\n * composition root (`HeaderRegistry.configure` / `HeaderRegistry.DEFAULT_HEADERS`). A method here\n * would be un-injectable behavior the DI design graph can't reach; a constant is honest data.\n */\n static readonly ALL_HEADERS: AnyContextKey[] = [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.STREAM_EVENT_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.ACTION_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.SURFACE,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n WebpiecesCoreHeaders.CONTROLLER,\n WebpiecesCoreHeaders.METHOD,\n WebpiecesCoreHeaders.OVERRIDE_BASE_URL,\n WebpiecesCoreHeaders.OVERRIDE_FULL_URL,\n ];\n}\n"]}
1
+ {"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA0D;AAI1D;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,SAAS,CAC7C,WAAW,EACX,cAAc;IACd,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI;IACjB,kBAAkB,CAAC,cAAc;IACjC,iBAAiB,CAAC,OAAO,CAC5B,CAAC;IAEF,2FAA2F;IAC3F,MAAM,CAAU,eAAe,GAAG,uBAAU,CAAC,SAAS,CAClD,eAAe,EACf,SAAS,EACT,KAAK,EACL,IAAI,CACP,CAAC;IAEF;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS,CAC3B,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,CAAU,cAAc,GAAG,uBAAU,CAAC,SAAS,CACjD,eAAe,EACf,4BAA4B,CAC/B,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAU,SAAS,GAAG,uBAAU,CAAC,SAAS,CAAS,UAAU,EAAE,sBAAsB,CAAC,CAAC;IAE7F;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,OAAO,CACvC,OAAO,EACP,oGAAoG,EACpG,UAAU,CACb,CAAC;IAEF,MAAM,CAAU,OAAO,GAAG,uBAAU,CAAC,OAAO,CACxC,QAAQ,EACR,oGAAoG,EACpG,WAAW,CACd,CAAC;IAEF,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,OAAO,CAC3C,OAAO,EACP,oGAAoG,EACpG,mBAAmB,CACtB,CAAC;IAEF;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAU,OAAO,GAAG,uBAAU,CAAC,OAAO,CACxC,SAAS,EACT,+HAA+H,EAC/H,cAAc,CACjB,CAAC;IAEF;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,uBAAU,CAAC,SAAS,CAAS,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAE/F;;;;;;;;;;;OAWG;IACH,MAAM,CAAU,aAAa,GAAG,uBAAU,CAAC,SAAS,CAChD,KAAK;IACL,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;OAUG;IACH,MAAM,CAAU,WAAW,GAAG,uBAAU,CAAC,SAAS,CAC9C,YAAY;IACZ,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF,MAAM,CAAU,YAAY,GAAG,uBAAU,CAAC,SAAS,CAC/C,aAAa;IACb,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,SAAS,CAC7C,YAAY;IACZ,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,SAAS,CACzC,QAAQ;IACR,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS;IACxB,cAAc,CAAC,KAAK;IACpB,YAAY,CAAC,IAAI,CACpB,CAAC;IAEF;;;;;;;;;;;;;;;OAeG;IAEH;;;;;OAKG;IACH,MAAM,CAAU,WAAW,GAAoB;QAC3C,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,eAAe;QACpC,oBAAoB,CAAC,iBAAiB;QACtC,oBAAoB,CAAC,cAAc;QACnC,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,OAAO;QAC5B,oBAAoB,CAAC,MAAM;QAC3B,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,OAAO;QAC5B,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,aAAa;QAClC,oBAAoB,CAAC,WAAW;QAChC,oBAAoB,CAAC,YAAY;QACjC,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,MAAM;QAC3B,oBAAoB,CAAC,iBAAiB;QACtC,oBAAoB,CAAC,iBAAiB;KACzC,CAAC;;AA1VN,oDA2VC","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { ApiCallInfo } from './ApiCallInfo';\nimport { Surface } from './Surface';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n *\n * - `httpHeader` `x-request-id` → travels DOWN the call tree on every outbound request.\n * - `responseHeader` `x-request-id` → and back UP on every response, which is the header a\n * support desk quotes. This used to be a hard-coded `res.setHeader` in `ExpressWrapper`\n * (`stampTransactionId`), reachable by no other key. It is an ordinary registry entry now: the\n * server's ONE response choke point writes every key in\n * {@link HeaderRegistry.getResponseTxfrKeys}, so a second key joins with no framework edit.\n * - `responseMerge` `first` → and NOT `collect`. A callee echoes OUR id back at us, so the\n * caller's context must KEEP what it already has. `collect` would turn `requestId` into a\n * growing array on every client call and break every log line of the request; `last` would\n * rewrite it with an echo. `first` makes reading it back a provable no-op for the normal case\n * and still adopts an id when a caller somehow had none.\n */\n static readonly REQUEST_ID = ContextKey.untrusted<string>(\n 'requestId',\n 'x-request-id',\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n /*responseHeader*/ 'x-request-id',\n /*responseMerge*/ 'first',\n );\n\n /** Framework-minted identity for one inbound streaming event; never accepted from HTTP. */\n static readonly STREAM_EVENT_ID = ContextKey.untrusted<string>(\n 'streamEventId',\n undefined,\n false,\n true,\n );\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = ContextKey.untrusted<string>(\n 'requestIdSource',\n /*httpHeader*/ undefined,\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = ContextKey.untrusted<string>(\n 'clientVersion',\n 'x-webpieces-client-version',\n );\n\n /**\n * A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.\n *\n * An \"action\" is a single thing the user did in the GUI — a CLICK on a button/link, or TYPING in a\n * field — or a background poller tick: anything that may fan out into MULTIPLE remote calls. That one\n * action fires 1..N browser HTTP calls, each of which gets its own framework-minted {@link REQUEST_ID}\n * (one per HTTP call, shared within that call's server→server subtree). `actionId` sits ABOVE\n * `requestId` and is what stitches those N requests back to the single action that caused them:\n *\n * actionId (app-minted, ONE per user action, rides EVERY call of that action)\n * └── 1..N requestId (framework-minted, ONE per HTTP call)\n *\n * Grep one `actionId` in the logs → every `requestId` it spawned, and every log line of the whole\n * action. Minted and refreshed by the app (a UI concern), carried under `x-webpieces-actionid`.\n *\n * Browser/app-minted ONLY: unlike {@link REQUEST_ID}, the framework transfers and logs it but must\n * NOT auto-mint one server-side. Absent `actionId` ⇒ a non-action flow (system / cron / task), which\n * is the correct signal.\n *\n * - `httpHeader` SET → transferred: copied off the inbound request into context and re-emitted on\n * outbound hops, so the id follows the action across services.\n * - `isLogged` TRUE → emitted as a plain string on every log line of the request.\n */\n static readonly ACTION_ID = ContextKey.untrusted<string>('actionId', 'x-webpieces-actionid');\n\n /**\n * WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the\n * inputs to authorization decisions, so a reader must be able to tell \"the framework proved\n * this\" from \"the caller typed this\" — see the trust section of the {@link ContextKey} doc.\n *\n * They keep their `httpHeader`, because propagating a verified identity to the next internal\n * service is the point. What makes that safe is not the header being absent, it is WHO is\n * allowed to have set it: an inbound value is held PENDING by\n * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that\n * verified its CALLER (`oidc(...)` / `sharedSecret(...)`). On a browser-reachable route\n * (`jwt()` / public) the value must match what the authenticator itself derived, or the\n * request is rejected.\n *\n * `provenance` says \"an app-bound JwtHook\" rather than naming one hook, because the framework\n * default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that\n * returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's\n * hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud\n * outcome: nothing is vouching for it.\n */\n static readonly ORG_ID = ContextKey.trusted<string>(\n 'orgId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-org-id',\n );\n\n static readonly USER_ID = ContextKey.trusted<string>(\n 'userId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-user-id',\n );\n\n static readonly USER_ROLES = ContextKey.trusted<string>(\n 'roles',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-webpieces-roles',\n );\n\n /**\n * WHICH KIND OF CALLER is driving this request — `gui`, `llm` or `public-api`. See\n * {@link Surface} for the table and for why it is a property of the CALLER and not of the server.\n *\n * TRUSTED, and that is load-bearing twice over. It is what makes an app safe to branch on it in\n * its OWN authorization logic (\"this endpoint is not exposed to partners\"), and it is what makes\n * the value in {@link SurfaceEndUserStatus} believable: webpieces sets it itself, from the auth\n * mode that matched, never from caller input. A caller that simply SENDS `x-wp-surface` at an\n * endpoint whose auth did not establish one is rejected by `AuthFilter.reconcileWireTrust`, like\n * every other unvouched trusted header.\n *\n * It keeps its `httpHeader` because PROPAGATION is the point: the surface belongs to the ORIGINAL\n * caller, so `GUI -> api1 -> api2` must leave api2 seeing `gui`. An internal `oidc(...)` hop\n * verifies its caller, so the propagated value is admitted verbatim — and the auth filter only\n * ever SETS a surface when none arrived, so an inherited one is never overwritten.\n */\n static readonly SURFACE = ContextKey.trusted<Surface>(\n 'surface',\n 'set by the webpieces AuthFilter from the auth mode that matched the incoming request, or inherited from the edge hop that did',\n 'x-wp-surface',\n );\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = ContextKey.untrusted<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCallImpl} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = ContextKey.untrusted<ApiCallInfo>(\n 'api',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = ContextKey.untrusted<string>(\n 'httpMethod',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly REQUEST_PATH = ContextKey.untrusted<string>(\n 'requestPath',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The routed endpoint's IMPLEMENTATION identity: the concrete controller class name\n * ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME\n * ({@link RouteMetadata.methodName}, e.g. `login`), stamped once per request by {@link LogApiFilter}\n * after route matching so every subsequent log line of the request carries them.\n *\n * These say WHICH CODE ran, which is what you actually grep for — far more useful than the raw\n * `requestPath`. They are the top-level, filterable twin of what previously only lived nested in\n * {@link ApiCallInfo} (`api.method.controllerName` / `api.method.methodName`). The local console\n * formatters render them together as a compact `[Controller.method]` bracket; GCP keeps them as two\n * separate `jsonPayload.controller` / `jsonPayload.method` fields.\n *\n * NOTE: `method` here is the CODE method name (e.g. `login`), NOT the HTTP verb — that is\n * {@link HTTP_METHOD} (`httpMethod`).\n *\n * - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly CONTROLLER = ContextKey.untrusted<string>(\n 'controller',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly METHOD = ContextKey.untrusted<string>(\n 'method',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The base URL ONE outbound call should go to, overriding whatever the client's `ClientConfig`\n * bound at construction — the answer to \"POST our published contract to a URL the PARTNER\n * registered at runtime\" (an `OrganizationWebhook.url` column, an OAuth callback, a per-tenant\n * or self-hosted host). The destination is DATA, not deployment, so it cannot be a svcName and\n * there is nothing to register in {@link ClientRegistry}.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * The contract's path IS appended to it (base + `@ApiPath` + `@Endpoint`). When the stored value\n * is a complete url — the usual partner-webhook row — use {@link OVERRIDE_FULL_URL} instead.\n *\n * ONLY a client carrying a `ContextBaseUrlFilter` reads it. Every other client IGNORES this key\n * entirely, which is what stops an ambient value re-pointing every other client in the same\n * fan-out loop at a partner's server. Installing that ONE filter IS the opt-in, so\n * `grep -rn ContextBaseUrlFilter` enumerates every client that can be re-pointed at all.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is load-bearing. This value\n * names where THIS hop goes. If it travelled, the callee would inherit it and re-point ITS\n * own outbound calls at the same host — one partner-supplied URL turning into an SSRF pivot\n * across the whole call tree. It is per-hop, always.\n * - `isLogged` TRUE → the destination of a partner delivery is exactly what you want in the log\n * line when one fails.\n *\n * UNTRUSTED, necessarily: it comes from a database column a partner edited. That is precisely\n * why re-pointing a request ARMS the framework's SSRF guard, automatically, rather than trusting\n * it — the guard sits beneath every app filter and reads the fact that the request was moved.\n */\n static readonly OVERRIDE_BASE_URL = ContextKey.untrusted<string>(\n 'overrideBaseUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The COMPLETE url — host, path AND query — ONE outbound call should be sent to, verbatim. The\n * sibling of {@link OVERRIDE_BASE_URL} for the other shape of runtime destination: a partner\n * that does not implement our contract at their own host, but hands us one opaque url to POST\n * to (`https://hooks.partner.example/in/abc?token=xyz`, straight out of a webhook row). With a\n * base-URL override the contract's path would be appended to it and the url would be wrong; with\n * this one the contract's path is NOT appended and nothing is inserted.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_FULL_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * ONLY a client carrying a `ContextFullUrlFilter` reads it, for exactly the reasons given on\n * {@link OVERRIDE_BASE_URL}: installing that filter IS the opt-in, and every other client ignores\n * the key. Not transferred over the wire (per-hop, or it becomes an SSRF pivot across the call\n * tree), logged (it is what you want in a failed-delivery log line), and UNTRUSTED (a partner\n * edited it) — which is why re-pointing at it arms the framework's SSRF guard automatically.\n */\n static readonly OVERRIDE_FULL_URL = ContextKey.untrusted<string>(\n 'overrideFullUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * All core context keys (the platform DEFAULT_HEADERS set). A `static readonly` CONSTANT, not a\n * method: it is compile-time data — a list of the key definitions above — read once at the startup\n * composition root (`HeaderRegistry.configure` / `HeaderRegistry.DEFAULT_HEADERS`). A method here\n * would be un-injectable behavior the DI design graph can't reach; a constant is honest data.\n */\n static readonly ALL_HEADERS: AnyContextKey[] = [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.STREAM_EVENT_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.ACTION_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.SURFACE,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n WebpiecesCoreHeaders.CONTROLLER,\n WebpiecesCoreHeaders.METHOD,\n WebpiecesCoreHeaders.OVERRIDE_BASE_URL,\n WebpiecesCoreHeaders.OVERRIDE_FULL_URL,\n ];\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { ApiKeyCredential, AuthMode } from './auth-mode';
2
2
  /**
3
3
  * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the
4
- * {@link WpAuthApiKey} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, "unused
4
+ * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, "unused
5
5
  * '@ts-expect-error' directive") if the line it guards ever starts compiling.
6
6
  *
7
7
  * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:
@@ -20,7 +20,7 @@ import { ApiKeyCredential, AuthMode } from './auth-mode';
20
20
  * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential
21
21
  * must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,
22
22
  * so a `name` beside it would be a lie a generator has to guess about.
23
- * 4. The ONE-ARGUMENT `@WpAuthApiKey('regime')` form is GONE. It could declare a key regime while
23
+ * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while
24
24
  * saying nothing about where the credential rides, which is the whole defect; per the
25
25
  * no-backwards-compatibility rule there is no overload and no optional second parameter left
26
26
  * behind, and this directive is what proves it.
@@ -35,15 +35,11 @@ export declare class AuthApiKeyCompileAssertions {
35
35
  legitimate(): AuthMode;
36
36
  /** The bearer branch, likewise asserted by the ABSENCE of an error. */
37
37
  legitimateBearer(): ApiKeyCredential;
38
- /** The decorator's TWO-argument form is the only one; asserted by the ABSENCE of an error. */
38
+ /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */
39
39
  legitimateDecorator(): MethodDecorator;
40
40
  /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */
41
41
  rejected(): void;
42
- /**
43
- * The DELETED one-argument form. `@WpAuthApiKey('onetablet-partner')` used to compile and is now a
44
- * compile error naming the missing `credentials` argument — the delivery mechanism for the
45
- * migration, and the reason no `@deprecated` overload survives.
46
- */
42
+ /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */
47
43
  oneArgumentFormIsGone(): void;
48
44
  /**
49
45
  * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthApiKeyCompileAssertions = void 0;
4
+ const auth_mode_1 = require("./auth-mode");
4
5
  const decorators_1 = require("./decorators");
5
6
  /**
6
7
  * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the
7
- * {@link WpAuthApiKey} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, "unused
8
+ * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, "unused
8
9
  * '@ts-expect-error' directive") if the line it guards ever starts compiling.
9
10
  *
10
11
  * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:
@@ -23,7 +24,7 @@ const decorators_1 = require("./decorators");
23
24
  * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential
24
25
  * must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,
25
26
  * so a `name` beside it would be a lie a generator has to guess about.
26
- * 4. The ONE-ARGUMENT `@WpAuthApiKey('regime')` form is GONE. It could declare a key regime while
27
+ * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while
27
28
  * saying nothing about where the credential rides, which is the whole defect; per the
28
29
  * no-backwards-compatibility rule there is no overload and no optional second parameter left
29
30
  * behind, and this directive is what proves it.
@@ -58,9 +59,9 @@ class AuthApiKeyCompileAssertions {
58
59
  };
59
60
  return credential;
60
61
  }
61
- /** The decorator's TWO-argument form is the only one; asserted by the ABSENCE of an error. */
62
+ /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */
62
63
  legitimateDecorator() {
63
- return (0, decorators_1.WpAuthApiKey)('onetablet-partner', [{ in: 'header', name: 'x-api-key' }]);
64
+ return (0, decorators_1.WpAuth)([(0, auth_mode_1.apiKey)('onetablet-partner', [{ in: 'header', name: 'x-api-key' }])]);
64
65
  }
65
66
  /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */
66
67
  rejected() {
@@ -93,14 +94,10 @@ class AuthApiKeyCompileAssertions {
93
94
  const unknownLocation = { in: 'query', name: 'api_key' };
94
95
  void unknownLocation;
95
96
  }
96
- /**
97
- * The DELETED one-argument form. `@WpAuthApiKey('onetablet-partner')` used to compile and is now a
98
- * compile error naming the missing `credentials` argument — the delivery mechanism for the
99
- * migration, and the reason no `@deprecated` overload survives.
100
- */
97
+ /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */
101
98
  oneArgumentFormIsGone() {
102
- // @ts-expect-error the one-argument form is DELETED; pass the credential list as well
103
- const legacy = (0, decorators_1.WpAuthApiKey)('onetablet-partner');
99
+ // @ts-expect-error apiKey requires regime and credentials
100
+ const legacy = (0, auth_mode_1.apiKey)('onetablet-partner');
104
101
  void legacy;
105
102
  }
106
103
  /**
@@ -122,8 +119,6 @@ class AuthApiKeyCompileAssertions {
122
119
  return 'webhook';
123
120
  case 'apikey':
124
121
  return 'apikey';
125
- case 'local-only':
126
- return 'local-only';
127
122
  }
128
123
  }
129
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuthApiKey } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link WpAuthApiKey} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,\n * so a `name` beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@WpAuthApiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n name: 'x-api-key',\n description: 'The key issued to your integration.',\n },\n { in: 'header', name: 'x-organization-id' },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** The decorator's TWO-argument form is the only one; asserted by the ABSENCE of an error. */\n legitimateDecorator(): MethodDecorator {\n return WpAuthApiKey('onetablet-partner', [{ in: 'header', name: 'x-api-key' }]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', name: 'x-api-key' }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', name: 'x-api-key' }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a header name beside it is a lie\n const bearerWithName: ApiKeyCredential = { in: 'bearer', name: 'x-api-key' };\n void bearerWithName;\n // @ts-expect-error a header credential with no header name cannot become a securityScheme\n const headerWithNoName: ApiKeyCredential = { in: 'header' };\n void headerWithNoName;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', name: 'api_key' };\n void unknownLocation;\n }\n\n /**\n * The DELETED one-argument form. `@WpAuthApiKey('onetablet-partner')` used to compile and is now a\n * compile error naming the missing `credentials` argument — the delivery mechanism for the\n * migration, and the reason no `@deprecated` overload survives.\n */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error the one-argument form is DELETED; pass the credential list as well\n const legacy = WpAuthApiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'webhook':\n return 'webhook';\n case 'apikey':\n return 'apikey';\n case 'local-only':\n return 'local-only';\n }\n }\n}\n"]}
1
+ {"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { apiKey, ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuth } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link apiKey} descriptor and {@link WpAuth} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,\n * so a `name` beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@apiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n name: 'x-api-key',\n description: 'The key issued to your integration.',\n },\n { in: 'header', name: 'x-organization-id' },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** apiKey takes regime and credentials; WpAuth wraps the descriptor in a nonempty list. */\n legitimateDecorator(): MethodDecorator {\n return WpAuth([apiKey('onetablet-partner', [{ in: 'header', name: 'x-api-key' }])]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', name: 'x-api-key' }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', name: 'x-api-key' }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a header name beside it is a lie\n const bearerWithName: ApiKeyCredential = { in: 'bearer', name: 'x-api-key' };\n void bearerWithName;\n // @ts-expect-error a header credential with no header name cannot become a securityScheme\n const headerWithNoName: ApiKeyCredential = { in: 'header' };\n void headerWithNoName;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', name: 'api_key' };\n void unknownLocation;\n }\n\n /** The apiKey descriptor requires an explicit credential list; no one-argument form exists. */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error apiKey requires regime and credentials\n const legacy = apiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'webhook':\n return 'webhook';\n case 'apikey':\n return 'apikey';\n }\n }\n}\n"]}