@betterportal/framework 10.1.61 → 10.1.62

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 (40) hide show
  1. package/lib/adapters/h3.d.ts.map +1 -1
  2. package/lib/adapters/h3.js +48 -28
  3. package/lib/adapters/h3.js.map +1 -1
  4. package/lib/cli/client.d.ts +1 -1
  5. package/lib/cli/client.d.ts.map +1 -1
  6. package/lib/cli/client.js +8 -8
  7. package/lib/cli/client.js.map +1 -1
  8. package/lib/codegen/emitter.d.ts.map +1 -1
  9. package/lib/codegen/emitter.js +22 -48
  10. package/lib/codegen/emitter.js.map +1 -1
  11. package/lib/codegen/scanner.d.ts +2 -1
  12. package/lib/codegen/scanner.d.ts.map +1 -1
  13. package/lib/codegen/scanner.js +22 -13
  14. package/lib/codegen/scanner.js.map +1 -1
  15. package/lib/codegen/validate.d.ts.map +1 -1
  16. package/lib/codegen/validate.js +70 -0
  17. package/lib/codegen/validate.js.map +1 -1
  18. package/lib/contracts/manifest.d.ts +166 -150
  19. package/lib/contracts/manifest.d.ts.map +1 -1
  20. package/lib/contracts/manifest.js +5 -2
  21. package/lib/contracts/manifest.js.map +1 -1
  22. package/lib/contracts/platformConfig.d.ts +85 -65
  23. package/lib/contracts/platformConfig.d.ts.map +1 -1
  24. package/lib/contracts/platformConfig.js +37 -28
  25. package/lib/contracts/platformConfig.js.map +1 -1
  26. package/lib/contracts/registry.d.ts +12 -17
  27. package/lib/contracts/registry.d.ts.map +1 -1
  28. package/lib/contracts/view.d.ts +97 -6
  29. package/lib/contracts/view.d.ts.map +1 -1
  30. package/lib/contracts/view.js +12 -6
  31. package/lib/contracts/view.js.map +1 -1
  32. package/lib/runtime/manifest.js +1 -1
  33. package/lib/runtime/registry.d.ts.map +1 -1
  34. package/lib/runtime/registry.js +99 -65
  35. package/lib/runtime/registry.js.map +1 -1
  36. package/lib/runtime/view.d.ts +2 -1
  37. package/lib/runtime/view.d.ts.map +1 -1
  38. package/lib/runtime/view.js +18 -13
  39. package/lib/runtime/view.js.map +1 -1
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"h3.d.ts","sourceRoot":"","sources":["../../src/adapters/h3.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxG,OAAO,KAAK,EAAsB,oBAAoB,EAAE,eAAe,EAAwD,MAAM,0BAA0B,CAAC;AAChK,OAAO,KAAK,EAGV,WAAW,EAIX,mBAAmB,EACnB,oBAAoB,EAIrB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,sBAAsB,CAAC;AAErE,OAAO,EASL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AA0B1B,MAAM,WAAW,4BAA4B;IAC3C,0BAA0B,CAAC,EAAE,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,uBAAuB,KAChC,yBAAyB,CAAC;IAC/B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yGAAyG;IACzG,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,GAAG,aAAa,GAAG,SAAS,CAAC;IACnI;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,sBAAsB,EAAE,mBAAmB,CAAC,GAAG,OAAO,sBAAsB,EAAE,mBAAmB,CAAC;IAC1K,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC7I;AAQD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,oBAAoB,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE;QACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AA+KD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAKvE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExG;AA2CD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,oBAAoB,EAC9B,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,4BAAiC,GACzC,IAAI,CAqGN;AAyhDD;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,iBAAiB,EACtB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAC;CAChC,GACL,IAAI,CAuDN"}
1
+ {"version":3,"file":"h3.d.ts","sourceRoot":"","sources":["../../src/adapters/h3.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,KAAK,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxG,OAAO,KAAK,EAAsB,oBAAoB,EAAE,eAAe,EAAwD,MAAM,0BAA0B,CAAC;AAChK,OAAO,KAAK,EAGV,WAAW,EAIX,mBAAmB,EACnB,oBAAoB,EAIrB,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,sBAAsB,CAAC;AAErE,OAAO,EASL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAC;AA0B1B,MAAM,WAAW,4BAA4B;IAC3C,0BAA0B,CAAC,EAAE,CAC3B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,uBAAuB,KAChC,yBAAyB,CAAC;IAC/B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yGAAyG;IACzG,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,GAAG,aAAa,GAAG,SAAS,CAAC;IACnI;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,sBAAsB,EAAE,mBAAmB,CAAC,GAAG,OAAO,sBAAsB,EAAE,mBAAmB,CAAC;IAC1K,qEAAqE;IACrE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;CAC7I;AAQD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,oBAAoB,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE;QACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AA+KD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAKvE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAExG;AA2CD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,oBAAoB,EAC9B,GAAG,EAAE,iBAAiB,EACtB,OAAO,GAAE,4BAAiC,GACzC,IAAI,CAqGN;AA8iDD;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,iBAAiB,EACtB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,cAAc,EACxB,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAC;CAChC,GACL,IAAI,CAuDN"}
@@ -142,7 +142,7 @@ async function resolveRequiredHandlerContext(event, routerOptions, route) {
142
142
  : null;
143
143
  }
144
144
  export function isBpManagementAuthRoute(route) {
145
- return route.auth.required && (route.path === "/.well-known/bp"
145
+ return Object.values(route.methodRoutes ?? {}).some((operation) => operation?.auth.required) && (route.path === "/.well-known/bp"
146
146
  || route.path.startsWith("/.well-known/bp/"));
147
147
  }
148
148
  export function isBpManagementAuthPath(routes, pathname) {
@@ -288,6 +288,9 @@ function requestAttributes(event, route, method, extra = {}) {
288
288
  "network.protocol.name": requestUrl.protocol.replace(":", ""),
289
289
  "bp.route.path": route.path,
290
290
  "bp.route.view_id": route.viewId,
291
+ ...route.methodRoutes?.[method]?.operationId
292
+ ? { "bp.route.operation_id": route.methodRoutes[method].operationId }
293
+ : {},
291
294
  ...(requestIp ? { "client.address": requestIp } : {}),
292
295
  ...extra
293
296
  };
@@ -326,6 +329,7 @@ function logNegotiationFailure(obs, route, method, reason, attributes = {}) {
326
329
  status: 406,
327
330
  reason,
328
331
  "bp.route.view_id": route.viewId,
332
+ "bp.route.operation_id": route.methodRoutes?.[method]?.operationId ?? "",
329
333
  ...attributes
330
334
  });
331
335
  }
@@ -347,19 +351,20 @@ function routePathsMatch(left, right) {
347
351
  function routeMountServicePath(routeMount) {
348
352
  return routeMount.resolvedServicePath ?? routeMount.servicePathVariant ?? routeMount.targetPath;
349
353
  }
350
- function methodAllowed(methods, method) {
351
- return (methods?.length ? methods : ["GET"]).some((candidate) => candidate.toUpperCase() === method);
352
- }
353
354
  function appAllowsRoute(app, route, method, url, acceptHeader) {
354
355
  // Well-known routes are control-plane/service endpoints. Their access policy
355
356
  // is declared by the route and must not depend on being mounted as an app page.
356
357
  if (route.path.startsWith("/.well-known/"))
357
358
  return { allowed: true };
359
+ const operationId = route.methodRoutes?.[method]?.operationId;
360
+ if (!operationId)
361
+ return { allowed: false, reason: "operation_not_registered" };
362
+ const legacyOperationId = `legacy:${route.viewId}:${method}`;
358
363
  const appRoute = app.routes.find((candidate) => {
359
364
  const servicePath = routeMountServicePath(candidate);
360
365
  return candidate.enabled !== false
361
366
  && candidate.viewId === route.viewId
362
- && methodAllowed(candidate.methods, method)
367
+ && (candidate.operations.includes(operationId) || candidate.operations.includes(legacyOperationId))
363
368
  && (!servicePath || routePathsMatch(servicePath, route.path));
364
369
  });
365
370
  if (appRoute)
@@ -622,7 +627,7 @@ function createUiRouteUrlBuilder(event, extraContext, dependencyAliases, current
622
627
  const serviceIds = serviceReferenceIds(extraContext, targetServiceId, dependencyAliases);
623
628
  const routes = appRouteIndex(extraContext.app).filter((candidate) => candidate.enabled !== false
624
629
  && (candidate.kind ?? "page") === "page"
625
- && methodAllowed(candidate.methods, "GET")
630
+ && (candidate.resolvedMethods?.includes("GET") ?? false)
626
631
  && candidate.viewId === viewId
627
632
  && serviceIds.has(candidate.serviceId));
628
633
  const resolved = routes.flatMap((route) => {
@@ -649,6 +654,7 @@ function rejectUnallowedAppRoute(obs, route, method, extraContext, reason) {
649
654
  method,
650
655
  reason,
651
656
  "bp.route.view_id": route.viewId,
657
+ "bp.route.operation_id": route.methodRoutes?.[method]?.operationId ?? "",
652
658
  "bp.route.path": route.path,
653
659
  "bp.app.id": extraContext.app.id,
654
660
  "bp.tenant.id": extraContext.tenant.id,
@@ -656,6 +662,7 @@ function rejectUnallowedAppRoute(obs, route, method, extraContext, reason) {
656
662
  });
657
663
  return coreJsonResponse({ error: "Route not found" }, 404, "route.not_mounted", reason, {
658
664
  "bp.route.view_id": route.viewId,
665
+ "bp.route.operation_id": route.methodRoutes?.[method]?.operationId ?? "",
659
666
  "bp.route.path": route.path,
660
667
  "bp.route_allowlist.reason": reason
661
668
  });
@@ -713,13 +720,13 @@ async function withSpan(obs, name, attributes, handler) {
713
720
  }
714
721
  async function handleRouteRequest(registryRoutes, dependencyAliases, route, method, event, obs, routerOptions = {}) {
715
722
  const methodRoute = route.methodRoutes?.[method];
716
- const handler = methodRoute?.handler ?? route.handlers[method];
723
+ const handler = methodRoute?.handler;
717
724
  const schemas = {
718
725
  ...route.schemas,
719
726
  ...(methodRoute?.schemas ?? {}),
720
727
  params: methodRoute?.schemas.params ?? route.schemas.params
721
728
  };
722
- if (!handler) {
729
+ if (!handler || !methodRoute) {
723
730
  return coreJsonResponse({ error: `No handler for ${method} ${route.path}` }, 405, "route.handler_missing", `No handler for ${method} ${route.path}`);
724
731
  }
725
732
  // -- Parse inputs -------------------------------------------------
@@ -810,11 +817,11 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
810
817
  }, dependencyAliases, renderer, "page", "page", undefined, status) : undefined;
811
818
  };
812
819
  // -- Auth resolution (per spec section 0.5) ----------------------
813
- const apiAuth = route.auth;
820
+ const apiAuth = methodRoute.auth;
814
821
  const authResolved = await loadAuthContext(event, route, routerOptions, obs);
815
822
  const authResult = await resolveRequestAuth(apiAuth, event, authResolved, route, method, obs);
816
823
  if (authResult.error) {
817
- return renderAuthError(route, event, authResult.status, authResult.code ?? "auth.unclassified", authResult.error, authResult.requiredPermissions, earlyRenderContext(authResult.status));
824
+ return renderAuthError(route, event, method, authResult.status, authResult.code ?? "auth.unclassified", authResult.error, authResult.requiredPermissions, earlyRenderContext(authResult.status));
818
825
  }
819
826
  // -- Tenant/app activation check (validateTenantApp hook -> 426) -----
820
827
  const tenantApp = readTenantAppFromEvent(event);
@@ -827,13 +834,13 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
827
834
  appId: tenantApp.appId,
828
835
  reason: validation.reason ?? "(unspecified)"
829
836
  });
830
- return renderUpgradeRequired(route, event, validation, earlyRenderContext(426));
837
+ return renderUpgradeRequired(route, event, method, validation, earlyRenderContext(426));
831
838
  }
832
839
  }
833
840
  catch (err) {
834
841
  obs?.logger.warn("validateTenantApp threw: {msg}", { msg: err.message });
835
842
  // Fail-open: validation error treated as block.
836
- return renderUpgradeRequired(route, event, {
843
+ return renderUpgradeRequired(route, event, method, {
837
844
  allowed: false,
838
845
  reason: "Tenant-app validation error"
839
846
  }, earlyRenderContext(426));
@@ -877,6 +884,7 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
877
884
  }
878
885
  rawData = await withCoreFailure(event, "handler.exception", () => withSpan(obs, "bp.route.handler", {
879
886
  "bp.route.view_id": route.viewId,
887
+ "bp.route.operation_id": methodRoute.operationId,
880
888
  "bp.route.path": route.path,
881
889
  "http.request.method": method,
882
890
  "bp.route.stream_buffered": true
@@ -885,6 +893,7 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
885
893
  else {
886
894
  rawData = await withCoreFailure(event, "handler.exception", () => withSpan(obs, "bp.route.handler", {
887
895
  "bp.route.view_id": route.viewId,
896
+ "bp.route.operation_id": methodRoute.operationId,
888
897
  "bp.route.path": route.path,
889
898
  "http.request.method": method
890
899
  }, () => handler(ctx)));
@@ -912,9 +921,10 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
912
921
  title: route.title,
913
922
  description: route.description,
914
923
  path: route.path,
915
- methods: [...route.methods],
916
- auth: { ...route.auth, callers: [...(route.auth.callers ?? ["user"])] },
917
- cacheHints: route.cacheHints
924
+ operationId: methodRoute.operationId,
925
+ method,
926
+ auth: { ...methodRoute.auth, callers: [...(methodRoute.auth.callers ?? ["user"])] },
927
+ cacheHints: methodRoute.cacheHints
918
928
  }, 200, {
919
929
  "content-type": "application/vnd.betterportal.metadata+json; charset=utf-8"
920
930
  });
@@ -976,11 +986,12 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
976
986
  }
977
987
  const html = await withCoreFailure(event, "render.status_failed", () => withSpan(obs, "bp.view.render", {
978
988
  "bp.route.view_id": route.viewId,
989
+ "bp.route.operation_id": methodRoute.operationId,
979
990
  "bp.view.renderer": renderer,
980
991
  "bp.view.kind": requestedKind,
981
992
  "bp.view.status": handlerStatus
982
993
  }, () => statusRenderer.render(data, renderContext)));
983
- return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("status", route.chrome));
994
+ return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("status", methodRoute.chrome));
984
995
  }
985
996
  // No specific renderer found.
986
997
  if (!shouldFallThroughToDefaultRenderer(handlerStatus)) {
@@ -1009,11 +1020,12 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
1009
1020
  }
1010
1021
  const html = await withCoreFailure(event, "render.fragment_failed", () => withSpan(obs, "bp.view.render", {
1011
1022
  "bp.route.view_id": route.viewId,
1023
+ "bp.route.operation_id": methodRoute.operationId,
1012
1024
  "bp.view.renderer": renderer,
1013
1025
  "bp.view.kind": "fragment",
1014
1026
  "bp.view.key": fragmentKey
1015
1027
  }, () => resolved.renderer.render(data, renderContext)));
1016
- return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("fragment", route.chrome));
1028
+ return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("fragment", methodRoute.chrome));
1017
1029
  }
1018
1030
  // Component request via `_c` query param
1019
1031
  if (componentId) {
@@ -1031,11 +1043,12 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
1031
1043
  }
1032
1044
  const html = await withCoreFailure(event, "render.component_failed", () => withSpan(obs, "bp.view.render", {
1033
1045
  "bp.route.view_id": route.viewId,
1046
+ "bp.route.operation_id": methodRoute.operationId,
1034
1047
  "bp.view.renderer": renderer,
1035
1048
  "bp.view.kind": "component",
1036
1049
  "bp.view.key": componentId
1037
1050
  }, () => resolved.renderer.render(data, renderContext)));
1038
- return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("fragment", route.chrome));
1051
+ return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType("fragment", methodRoute.chrome));
1039
1052
  }
1040
1053
  // Page request - only page renderers allowed
1041
1054
  const resolved = resolveRenderer(route, renderer, "page", method);
@@ -1051,11 +1064,12 @@ async function handleRouteRequest(registryRoutes, dependencyAliases, route, meth
1051
1064
  }
1052
1065
  const html = await withCoreFailure(event, "render.page_failed", () => withSpan(obs, "bp.view.render", {
1053
1066
  "bp.route.view_id": route.viewId,
1067
+ "bp.route.operation_id": methodRoute.operationId,
1054
1068
  "bp.view.renderer": renderer,
1055
1069
  "bp.view.kind": "page"
1056
1070
  }, () => resolved.renderer.render(data, renderContext)));
1057
1071
  const mode = representation.mode ?? "page";
1058
- return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(mode, route.chrome));
1072
+ return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), handlerStatus, htmlContentType(mode, methodRoute.chrome));
1059
1073
  }
1060
1074
  /**
1061
1075
  * Handle representations that stream, returning null for buffered ones so the
@@ -1093,7 +1107,7 @@ async function handleStreamRepresentation(route, handler, ctx, event, url, metho
1093
1107
  "bp.view.renderer": renderer,
1094
1108
  "bp.view.kind": "stream-shell"
1095
1109
  }, () => streamSet.renderShell(shellCtx)));
1096
- return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), 200, htmlContentType("fragment", route.chrome));
1110
+ return htmlResponse(rewriteServiceRouteTokens(toHtmlString(html), ctx.routeUrl, obs), 200, htmlContentType("fragment", route.methodRoutes?.[method]?.chrome));
1097
1111
  }
1098
1112
  /**
1099
1113
  * SSE delivery of the frame stream at `{path}/__sse`. With a theme context and
@@ -1117,8 +1131,12 @@ async function handleStreamSse(registryRoutes, dependencyAliases, route, handler
1117
1131
  return params;
1118
1132
  // The frame stream carries the same data as the view route - enforce the
1119
1133
  // same auth requirement.
1134
+ const operation = route.methodRoutes?.GET;
1135
+ if (!operation) {
1136
+ return coreJsonResponse({ error: "Streaming GET operation metadata is missing" }, 500, "route.operation_missing", "Streaming GET operation metadata is missing");
1137
+ }
1120
1138
  const authResolved = await loadAuthContext(event, route, routerOptions, obs);
1121
- const authResult = await resolveRequestAuth(route.auth, event, authResolved, route, "GET", obs);
1139
+ const authResult = await resolveRequestAuth(operation.auth, event, authResolved, route, "GET", obs);
1122
1140
  if (authResult.error) {
1123
1141
  return coreJsonResponse({ error: authResult.error, status: authResult.status }, authResult.status, authResult.code ?? "auth.unclassified", authResult.error);
1124
1142
  }
@@ -1428,11 +1446,12 @@ function formatRequiredPermissions(requiredPermissions = []) {
1428
1446
  .map((requirement) => `${requirement.serviceId} / ${requirement.viewId} / ${requirement.permissions.join("+")}`)
1429
1447
  .join("; ");
1430
1448
  }
1431
- function renderAuthError(route, event, status, code, message, requiredPermissions = [], context) {
1449
+ function renderAuthError(route, event, method, status, code, message, requiredPermissions = [], context) {
1432
1450
  const renderer = rendererFromEvent(event);
1433
1451
  const acceptHeader = acceptHeaderFromEvent(event);
1434
1452
  const representation = resolveRequestedRepresentation(acceptHeader);
1435
1453
  const corsHeaders = corsHeadersFromEvent(event);
1454
+ const chrome = route.methodRoutes?.[method]?.chrome;
1436
1455
  // Auth errors NEVER emit navigation headers (HX-Location / HX-Redirect). A
1437
1456
  // service has no reliable knowledge of where the auth provider lives - it only
1438
1457
  // knows the JWKS for token *validation*, not a URL the browser should navigate
@@ -1442,13 +1461,13 @@ function renderAuthError(route, event, status, code, message, requiredPermission
1442
1461
  // Prefer a route/theme status view so the body swaps cleanly into the htmx
1443
1462
  // target as a fragment rather than replacing the shell.
1444
1463
  if (renderer && (representation.kind === "html")) {
1445
- const statusRenderer = resolveStatusRenderer(route, renderer, status, "page", undefined, "GET");
1464
+ const statusRenderer = resolveStatusRenderer(route, renderer, status, "page", undefined, method);
1446
1465
  if (statusRenderer && context) {
1447
1466
  try {
1448
1467
  const html = statusRenderer.render({ error: message, status, requiredPermissions }, context);
1449
1468
  return coreResponse(new Response(toHtmlString(html), {
1450
1469
  status,
1451
- headers: { ...corsHeaders, "content-type": htmlContentType("status", route.chrome) }
1470
+ headers: { ...corsHeaders, "content-type": htmlContentType("status", chrome) }
1452
1471
  }), code, message);
1453
1472
  }
1454
1473
  catch {
@@ -1469,7 +1488,7 @@ function renderAuthError(route, event, status, code, message, requiredPermission
1469
1488
  `;
1470
1489
  return coreResponse(new Response(html, {
1471
1490
  status,
1472
- headers: { ...corsHeaders, "content-type": renderer ? htmlContentType("status", route.chrome) : "text/html; charset=utf-8" }
1491
+ headers: { ...corsHeaders, "content-type": renderer ? htmlContentType("status", chrome) : "text/html; charset=utf-8" }
1473
1492
  }), code, message);
1474
1493
  }
1475
1494
  return coreJsonResponse({ error: message, status, requiredPermissions }, status, code, message, undefined, corsHeaders);
@@ -1492,18 +1511,19 @@ function readTenantAppFromEvent(event) {
1492
1511
  return undefined;
1493
1512
  return { tenantId: ctx.__bpTenantId, appId: ctx.__bpAppId };
1494
1513
  }
1495
- function renderUpgradeRequired(route, event, validation, context) {
1514
+ function renderUpgradeRequired(route, event, method, validation, context) {
1496
1515
  const renderer = rendererFromEvent(event);
1497
1516
  const acceptHeader = acceptHeaderFromEvent(event);
1498
1517
  const representation = resolveRequestedRepresentation(acceptHeader);
1499
1518
  const status = 426;
1519
+ const chrome = route.methodRoutes?.[method]?.chrome;
1500
1520
  // Honor Retry-After if requested
1501
1521
  const extraHeaders = {};
1502
1522
  if (validation.retryAfterSeconds) {
1503
1523
  extraHeaders["retry-after"] = String(validation.retryAfterSeconds);
1504
1524
  }
1505
1525
  if (renderer && representation.kind === "html") {
1506
- const statusRenderer = resolveStatusRenderer(route, renderer, status, "page", undefined, "GET");
1526
+ const statusRenderer = resolveStatusRenderer(route, renderer, status, "page", undefined, method);
1507
1527
  if (statusRenderer && context) {
1508
1528
  try {
1509
1529
  const html = statusRenderer.render({
@@ -1511,7 +1531,7 @@ function renderUpgradeRequired(route, event, validation, context) {
1511
1531
  reason: validation.reason,
1512
1532
  upgradeUrl: validation.upgradeUrl
1513
1533
  }, context);
1514
- return coreResponse(htmlResponse(toHtmlString(html), status, htmlContentType("status", route.chrome)), "route.tenant_app_unavailable", validation.reason ?? "Tenant/app is not available for this service");
1534
+ return coreResponse(htmlResponse(toHtmlString(html), status, htmlContentType("status", chrome)), "route.tenant_app_unavailable", validation.reason ?? "Tenant/app is not available for this service");
1515
1535
  }
1516
1536
  catch {
1517
1537
  // fall through to JSON