@k2b/cloud 0.40.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -5
- package/src/_internal/bounded-json.ts +14 -2
- package/src/_internal/capabilities.ts +87 -8
- package/src/_internal/capability-reviews.ts +6 -0
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/capability-timeout.ts +71 -0
- package/src/_internal/define-app.ts +158 -92
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/http-drain.ts +56 -0
- package/src/_internal/registry-validation.ts +3 -0
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +16 -17
- package/src/access/PrincipalPicker.tsx +45 -10
- package/src/access/messages.ts +8 -0
- package/src/access/principal-label.ts +18 -0
- package/src/access/ui.ts +1 -0
- package/src/account/EntitySearch.tsx +5 -4
- package/src/ai/action-review.ts +2 -1
- package/src/ai/app-skill-store.ts +1 -1
- package/src/ai/capabilities.ts +21 -3
- package/src/ai/capability-execution.ts +14 -12
- package/src/ai/chat/action-card.ts +76 -4
- package/src/ai/chat/action-messages.ts +14 -0
- package/src/ai/chat/action-question.browser-harness.tsx +12 -0
- package/src/ai/chat/action-question.tsx +214 -23
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/chat-tasks.ts +2 -1
- package/src/ai/client/controller.ts +150 -32
- package/src/ai/client/transport.ts +14 -2
- package/src/ai/code-capability-routes.ts +27 -23
- package/src/ai/code-runtime-tools.ts +14 -16
- package/src/ai/code-source-tools.ts +16 -18
- package/src/ai/executor.ts +64 -21
- package/src/ai/fetch-file-tool.ts +67 -76
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/http.ts +1 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +28 -0
- package/src/ai/open-questions.ts +168 -0
- package/src/ai/projects.ts +1 -2
- package/src/ai/protocol.ts +2 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +12 -5
- package/src/ai/review-content.ts +77 -0
- package/src/ai/routes.ts +119 -35
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skill-tool.ts +17 -6
- package/src/ai/skills-routes.ts +72 -27
- package/src/ai/skills.ts +139 -60
- package/src/ai/store.ts +184 -31
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +25 -4
- package/src/ai/usage.ts +2 -2
- package/src/api/admin-core-settings.ts +15 -2
- package/src/api/admin-geo.ts +54 -0
- package/src/api/admin-linux-identities.ts +6 -3
- package/src/api/admin-outgoing-mail.ts +29 -15
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/approvals-loader.ts +3 -3
- package/src/api/approvals.ts +51 -0
- package/src/api/apps.ts +4 -2
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/capabilities.ts +258 -181
- package/src/api/capability-streams.ts +21 -13
- package/src/api/cli-plugins.ts +4 -1
- package/src/api/geo-glyphs.ts +11 -0
- package/src/api/geo.ts +128 -0
- package/src/api/help.ts +12 -4
- package/src/api/index.ts +5 -0
- package/src/api/mcp.ts +73 -41
- package/src/api/me-panel-consent.ts +8 -5
- package/src/api/me.ts +79 -3
- package/src/api/search.ts +21 -14
- package/src/api/sync-ops.ts +19 -13
- package/src/api/widgets.ts +42 -18
- package/src/approvals/Approvals.tsx +30 -66
- package/src/approvals/approval-card.ts +68 -0
- package/src/browser/CloudResourceSearch.browser-harness.tsx +53 -1
- package/src/browser/CloudResourceSearch.tsx +3 -13
- package/src/browser/PanelLeaveNotice.tsx +18 -0
- package/src/browser/navigation-search.ts +78 -25
- package/src/browser/panel-leave.ts +69 -0
- package/src/browser/panel-messages.ts +12 -0
- package/src/browser/panel-slot.ts +173 -24
- package/src/browser/panel.ts +3 -0
- package/src/browser/resource-search-messages.ts +2 -0
- package/src/capabilities/client.ts +28 -1
- package/src/capabilities/executions.ts +22 -3
- package/src/capabilities/geo-client.ts +57 -0
- package/src/capabilities/response.ts +2 -1
- package/src/capabilities/review-storage.ts +24 -0
- package/src/capabilities/reviews.ts +17 -3
- package/src/capabilities/server.ts +36 -16
- package/src/cli/access.ts +15 -23
- package/src/cli/admin/linux.ts +13 -2
- package/src/cli/admin/outgoing-mail.ts +26 -4
- package/src/cli/capabilities.ts +19 -0
- package/src/config/env.ts +0 -6
- package/src/contracts/app-access.ts +48 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/app.ts +4 -1
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/avatar.ts +186 -0
- package/src/contracts/capabilities.ts +149 -55
- package/src/contracts/geo-admin.ts +127 -0
- package/src/contracts/geo-map.ts +19 -0
- package/src/contracts/geo-route.ts +168 -0
- package/src/contracts/geo.ts +248 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/outgoing-mail.ts +17 -4
- package/src/contracts/registry.ts +2 -0
- package/src/contracts/shared.ts +3 -0
- package/src/events/app-access.ts +68 -0
- package/src/events/live-delivery.ts +128 -0
- package/src/events/live.ts +14 -130
- package/src/geo/CloudMap.tsx +95 -0
- package/src/geo/map-messages.ts +17 -0
- package/src/geo/ui.ts +1 -0
- package/src/index.ts +2 -0
- package/src/server/client-ip.ts +36 -0
- package/src/server/index.ts +3 -4
- package/src/server/middleware/app-access.ts +312 -0
- package/src/server/middleware/auth.ts +5 -3
- package/src/server/middleware/invocation.ts +11 -0
- package/src/server/middleware/rate-limit.ts +4 -4
- package/src/server/services/access.ts +13 -9
- package/src/server/services/index.ts +0 -2
- package/src/server/services/services.ts +0 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/posix.ts +21 -7
- package/src/services/accounts/users.ts +12 -1
- package/src/services/allowed-network-fetch.ts +16 -10
- package/src/services/app-access-metrics.ts +24 -0
- package/src/services/app-access.ts +123 -0
- package/src/services/app-discovery.ts +27 -0
- package/src/services/approvals/content.ts +40 -0
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +15 -6
- package/src/services/approvals/list.ts +6 -11
- package/src/services/approvals/question.ts +72 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/password-reset.ts +32 -6
- package/src/services/downstream-budget.ts +87 -0
- package/src/services/geo/admin.ts +262 -0
- package/src/services/geo/bucket.ts +67 -0
- package/src/services/geo/cache.ts +57 -0
- package/src/services/geo/costs.ts +5 -0
- package/src/services/geo/errors.ts +41 -0
- package/src/services/geo/fixtures/README.md +17 -0
- package/src/services/geo/fixtures/along.json +355 -0
- package/src/services/geo/fixtures/autocomplete.json +218 -0
- package/src/services/geo/fixtures/geocode.json +129 -0
- package/src/services/geo/fixtures/places.json +355 -0
- package/src/services/geo/fixtures/reverse.json +61 -0
- package/src/services/geo/fixtures/routing.json +57 -0
- package/src/services/geo/geoapify-route.ts +93 -0
- package/src/services/geo/geoapify.ts +281 -0
- package/src/services/geo/ledger.ts +122 -0
- package/src/services/geo/map.ts +160 -0
- package/src/services/geo/migrate.ts +25 -0
- package/src/services/geo/polyline.ts +184 -0
- package/src/services/geo/route-chain.ts +145 -0
- package/src/services/geo/route-costs.ts +9 -0
- package/src/services/geo/route-geometry.ts +39 -0
- package/src/services/geo/route-plan.ts +172 -0
- package/src/services/geo/route-service.ts +433 -0
- package/src/services/geo/service.ts +300 -0
- package/src/services/geo/settings.ts +49 -0
- package/src/services/help/index.ts +16 -7
- package/src/services/help/types.ts +6 -1
- package/src/services/identity/invocation-actor.ts +20 -2
- package/src/services/identity/issue-invocation.ts +67 -0
- package/src/services/identity/key-ring.ts +5 -3
- package/src/services/identity/runtime-config.ts +23 -0
- package/src/services/index.ts +11 -0
- package/src/services/mandates/index.ts +40 -3
- package/src/services/network-security.ts +6 -3
- package/src/services/network-transport.ts +99 -0
- package/src/services/notifications/catalog.ts +3 -2
- package/src/services/notifications/runtime.ts +1 -0
- package/src/services/notifications/web-push-transport.ts +45 -38
- package/src/services/outgoing-mail/dispatcher.ts +6 -1
- package/src/services/outgoing-mail/index.ts +55 -1
- package/src/services/outgoing-mail/messages.ts +82 -3
- package/src/services/outgoing-mail/resolve-host.ts +2 -2
- package/src/services/outgoing-mail/retention.ts +13 -1
- package/src/services/outgoing-mail/store.ts +36 -6
- package/src/services/outgoing-mail/test-send.ts +34 -4
- package/src/services/outgoing-mail/transport.ts +1 -1
- package/src/services/pdf/gotenberg.ts +11 -38
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/pdf/template-preview.ts +1 -1
- package/src/services/public-http.ts +28 -40
- package/src/services/session/index.ts +203 -29
- package/src/services/settings/app.ts +4 -1
- package/src/services/settings/core-settings.ts +88 -0
- package/src/services/settings/defaults.ts +8 -2
- package/src/services/settings/errors.ts +11 -0
- package/src/services/settings/geo-budget.ts +40 -0
- package/src/services/settings/index.ts +4 -3
- package/src/services/settings/store.ts +25 -17
- package/src/services/weather/geo.ts +29 -111
- package/src/services/weather/index.ts +4 -3
- package/src/services/weather/locations.ts +8 -5
- package/src/services/weather/migrate.ts +1 -0
- package/src/services/weather/settings.ts +0 -16
- package/src/shared/__snapshots__/avatar-art.test.ts.snap +5 -0
- package/src/shared/avatar-art.ts +400 -0
- package/src/shared/index.ts +2 -0
- package/src/shared/network-address.ts +22 -3
- package/src/shared/template-rendering.ts +36 -4
- package/src/shared-text/documents.ts +1 -1
- package/src/shared-text/index.ts +2 -0
- package/src/shared-text/log.ts +51 -3
- package/src/shared-text/snapshots.ts +340 -0
- package/src/ssr/AdminLayout.tsx +4 -2
- package/src/ssr/CloudShell.island.tsx +6 -0
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +65 -15
- package/src/ssr/LayoutHelp.browser-harness.tsx +1 -1
- package/src/ssr/LayoutHelp.tsx +185 -542
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/PanelRuntime.island.tsx +11 -7
- package/src/ssr/PwaLayout.tsx +2 -2
- package/src/ssr/RailApps.tsx +6 -0
- package/src/ssr/ShellRuntime.island.tsx +23 -7
- package/src/ssr/__snapshots__/Layout.unchanged.render.test.tsx.snap +151 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/admin-navigation.ts +1 -0
- package/src/ssr/app-badges.ts +6 -1
- package/src/ssr/compact-shell.browser-harness.ts +44 -5
- package/src/ssr/help.ts +7 -5
- package/src/ssr/index.ts +2 -1
- package/src/ssr/islands/index.ts +3 -4
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/mobile-menu-history.ts +4 -0
- package/src/ssr/page-swap-fixture/Column.island.tsx +15 -0
- package/src/ssr/page-swap-fixture/ProviderProbe.island.tsx +31 -0
- package/src/ssr/page-swap.browser-harness.tsx +177 -0
- package/src/ssr/page-swap.ts +28 -0
- package/src/ssr/page-trail-runtime.ts +32 -13
- package/src/ssr/panel-consent.ts +2 -2
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +3 -0
- package/src/ssr/platform-messages.ts +8 -0
- package/src/ssr/runtime.ts +17 -0
- package/src/ssr/shell-data.tsx +10 -2
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
- package/src/styles/global.css +70 -6
- package/src/server/services/geo.ts +0 -215
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type AppSkillDefinition, appSkillManifestHash, registerAppSkills, validateAppSkillCatalog } from "../ai/app-skills";
|
|
2
|
+
import { type AppAccessDeclaration, compileAppAccess } from "../contracts/app-access";
|
|
2
3
|
import { startLiveOutbox } from "../events/live";
|
|
3
4
|
import { registerHelp } from "../services/help";
|
|
4
5
|
import { preloadLayoutHelp } from "../ssr/help";
|
|
6
|
+
import { registerPageSwap } from "../ssr/page-swap";
|
|
5
7
|
import { preloadPanelConsent } from "../ssr/panel-consent";
|
|
6
8
|
import { invokeCapabilityStream } from "./capability-streams";
|
|
9
|
+
import { withCapabilityRequestTimeout } from "./capability-timeout";
|
|
7
10
|
import { bindProcessApplicationId, clearProcessApplicationId } from "./process-identity";
|
|
8
11
|
/**
|
|
9
12
|
* defineApp() — The single entry point for every cloud app.
|
|
@@ -49,8 +52,10 @@ import type { AppSettingsMap, KindToType } from "../contracts/settings-types";
|
|
|
49
52
|
import type { Role } from "../contracts/shared";
|
|
50
53
|
import type { HelpDefinition } from "../server/help";
|
|
51
54
|
import { getLocale, resolveLocale } from "../server/locale";
|
|
55
|
+
import { requireAppAccess, stopAppAccessConnections } from "../server/middleware/app-access";
|
|
52
56
|
import { type AuthContext, auth } from "../server/middleware/auth";
|
|
53
57
|
import { requireInvocation } from "../server/middleware/invocation";
|
|
58
|
+
import "../events/app-access";
|
|
54
59
|
import { matchedRouteTemplate, routeTemplate } from "../server/middleware/route-template";
|
|
55
60
|
import { runtime as runtimeMiddleware } from "../server/middleware/runtime";
|
|
56
61
|
import { preloadLayoutAnnouncements, settings as settingsMiddleware } from "../server/middleware/settings";
|
|
@@ -84,15 +89,17 @@ import { appRuntimeMetadata, buildMetadata } from "./build-metadata";
|
|
|
84
89
|
import { compileCapabilities, invokeCompiledCapability, reviewCompiledCapability, serializeCapabilityProviderResult } from "./capabilities";
|
|
85
90
|
import { cliPluginRoutePrefixes, createCliPluginRoutes, validateAppCliModules } from "./cli-plugins";
|
|
86
91
|
import { FONT_PRELOAD_LINKS } from "./font-preloads";
|
|
87
|
-
import { createHeartbeat } from "./heartbeat";
|
|
92
|
+
import { createHeartbeat, createRegistryMembership } from "./heartbeat";
|
|
88
93
|
import { compileHelp } from "./help";
|
|
94
|
+
import { createHttpDrain } from "./http-drain";
|
|
89
95
|
import { createPageResponses } from "./page-responses";
|
|
90
96
|
import { configurePostgresApplicationName } from "./postgres-application-name";
|
|
91
97
|
import { getProcessSync, startProcessSync } from "./process-sync";
|
|
92
98
|
import { APP_READINESS_PATH, appReadinessResponse } from "./readiness";
|
|
93
|
-
import { appRegistry, type CapabilityRegistryRecord, capabilityRegistry } from "./registry";
|
|
99
|
+
import { appInstanceRegistry, appMembershipLock, appRegistry, type CapabilityRegistryRecord, capabilityRegistry } from "./registry";
|
|
94
100
|
import { canonicalRoute, isOwnAppPath } from "./registry-validation";
|
|
95
101
|
import { ensureRuntimeWatcher, getCurrentRuntime, registeredCoreRelease, stopRuntimeWatcher } from "./runtime-watcher";
|
|
102
|
+
import { runSetupExclusively } from "./setup-lock";
|
|
96
103
|
import { publicFileVersion, servePublicAsset, sharedStylesheetVersion, versionedPublicHref } from "./static-assets";
|
|
97
104
|
import { createStatusPreservingSsrHandler } from "./status-preserving-ssr";
|
|
98
105
|
import { WEB_VITALS_ASSET_HREF } from "./web-vitals-asset";
|
|
@@ -128,6 +135,7 @@ type ResolvedPageOptions = PageOptions & { performanceRoute?: string; themePrefe
|
|
|
128
135
|
*/
|
|
129
136
|
export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefinitionMap = {}, AppId extends string = string> = {
|
|
130
137
|
id: AppId;
|
|
138
|
+
access?: AppAccessDeclaration;
|
|
131
139
|
name: string;
|
|
132
140
|
icon: string;
|
|
133
141
|
description: string;
|
|
@@ -136,6 +144,8 @@ export type AppOptions<S extends AppSettingsMap = {}, N extends NotificationDefi
|
|
|
136
144
|
appearance?: AppAppearance;
|
|
137
145
|
/** URL prefix for SSR asset isolation. Omit for the global `/_ssr/` path (core). */
|
|
138
146
|
basePath?: string;
|
|
147
|
+
/** Swap Layout content within this app while keeping the rail, tab bar and panel document. Default: false; focusMode pages still load a document. */
|
|
148
|
+
pageSwap?: boolean;
|
|
139
149
|
/** Base URL as seen by other containers (e.g. "http://app-notebooks:3000"). */
|
|
140
150
|
baseUrl: string;
|
|
141
151
|
adminHref?: string;
|
|
@@ -529,6 +539,8 @@ export const defineApp = <
|
|
|
529
539
|
// cannot override.
|
|
530
540
|
const ssr = Object.assign(
|
|
531
541
|
createStatusPreservingSsrHandler<ResolvedPageOptions>(html, async (c) => {
|
|
542
|
+
if (opts.pageSwap)
|
|
543
|
+
registerPageSwap(c, isDevelopment ? String(developmentStamp) : `${buildMetadata.version}:${buildMetadata.release}`);
|
|
532
544
|
c.get("page").lang = getLocale(c);
|
|
533
545
|
c.get("page").themePreference = readThemePreferenceFromCookieHeader(c.req.header("Cookie"));
|
|
534
546
|
const user = c.get("user");
|
|
@@ -577,13 +589,19 @@ export const defineApp = <
|
|
|
577
589
|
openapi: opts.openapi,
|
|
578
590
|
cli: opts.cli,
|
|
579
591
|
};
|
|
580
|
-
const meta: AppMeta = {
|
|
592
|
+
const meta: AppMeta = {
|
|
593
|
+
...baseMeta,
|
|
594
|
+
access: compileAppAccess(opts.access),
|
|
595
|
+
presentation: compileAppPresentation(baseMeta, opts.presentation),
|
|
596
|
+
};
|
|
581
597
|
|
|
582
598
|
// ── 3. start() — builds and boots the Hono server ────────────────────
|
|
583
599
|
const start = async (startOpts: StartOptions): Promise<StartResult> => {
|
|
584
600
|
const port = startOpts.port ?? 3000;
|
|
585
601
|
const baseUrl = opts.baseUrl;
|
|
586
602
|
const log = logger("app");
|
|
603
|
+
const http = createHttpDrain();
|
|
604
|
+
if (!meta.access) log.warn("Application has no access declaration; legacy entry and discovery rules remain active", { appId: meta.id });
|
|
587
605
|
|
|
588
606
|
// Settings are encrypted at rest, so every container needs the secret. It
|
|
589
607
|
// is only read on the first decrypt, which meant a container without it
|
|
@@ -631,6 +649,7 @@ export const defineApp = <
|
|
|
631
649
|
// Registry entry
|
|
632
650
|
const entry: AppRegistryEntry = {
|
|
633
651
|
id: meta.id,
|
|
652
|
+
access: meta.access,
|
|
634
653
|
name: meta.name,
|
|
635
654
|
icon: meta.icon,
|
|
636
655
|
description: meta.description,
|
|
@@ -686,9 +705,16 @@ export const defineApp = <
|
|
|
686
705
|
cliModules: cliModules.length > 0 ? cliModules : undefined,
|
|
687
706
|
};
|
|
688
707
|
|
|
708
|
+
// Both shared entries follow one process membership; creating it touches no Sync resources.
|
|
709
|
+
const membership = createRegistryMembership(meta.id, {
|
|
710
|
+
registry: appInstanceRegistry,
|
|
711
|
+
lock: appMembershipLock,
|
|
712
|
+
startedAt: entry.startedAt,
|
|
713
|
+
});
|
|
689
714
|
// Heartbeat
|
|
690
715
|
const heartbeat = createHeartbeat(meta.id, entry, {
|
|
691
716
|
registry: appRegistry(),
|
|
717
|
+
membership,
|
|
692
718
|
beforeWrite: async () => {
|
|
693
719
|
if (compiledHelp) await registerHelp(compiledHelp.corpus);
|
|
694
720
|
if (appSkills && skillsManifestHash) await registerAppSkills(meta.id, appSkills, skillsManifestHash);
|
|
@@ -717,6 +743,7 @@ export const defineApp = <
|
|
|
717
743
|
? createHeartbeat(meta.id, capabilityEntry, {
|
|
718
744
|
key: `capabilities/${meta.id}`,
|
|
719
745
|
registry: capabilityRegistry(),
|
|
746
|
+
membership,
|
|
720
747
|
onError: (error) =>
|
|
721
748
|
log.error("Capability registry heartbeat failed", {
|
|
722
749
|
appId: meta.id,
|
|
@@ -757,6 +784,7 @@ export const defineApp = <
|
|
|
757
784
|
// Framework-owned mounts answer before the app's router ever runs, so the
|
|
758
785
|
// app middleware cannot report their template. Without this, every hashed
|
|
759
786
|
// island chunk would land in telemetry as its own route.
|
|
787
|
+
const invocationApp = { getApp: async (id: string) => (id === meta.id ? meta : null) };
|
|
760
788
|
const server = new Hono<AuthContext>()
|
|
761
789
|
.use("*", routeTemplate)
|
|
762
790
|
.get(APP_READINESS_PATH, () => appReadinessResponse(meta.id))
|
|
@@ -765,11 +793,14 @@ export const defineApp = <
|
|
|
765
793
|
.all("/public/*", servePublicAsset(isDevelopment, sharedStylesheetVersion(buildMetadata.release)))
|
|
766
794
|
.use(
|
|
767
795
|
"/_internal/sync/*",
|
|
768
|
-
requireInvocation(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
796
|
+
requireInvocation(
|
|
797
|
+
(c) => ({
|
|
798
|
+
targetAppId: meta.id,
|
|
799
|
+
operation: syncInvocationOperation(c.req.method, new URL(c.req.url).pathname.slice("/_internal/sync".length)),
|
|
800
|
+
schemaHash: null,
|
|
801
|
+
}),
|
|
802
|
+
invocationApp,
|
|
803
|
+
),
|
|
773
804
|
)
|
|
774
805
|
.use("/_internal/sync/*", auth.requireOAuthScope("admin"))
|
|
775
806
|
.use("/_internal/sync/*", async (c, next) => {
|
|
@@ -778,79 +809,73 @@ export const defineApp = <
|
|
|
778
809
|
})
|
|
779
810
|
.route("/_internal/sync", createSyncOpsRoutes(getProcessSync));
|
|
780
811
|
|
|
781
|
-
if (opts.cli && cliModules.length > 0) server.route(CLOUD_CLI_PLUGIN_ROUTE, createCliPluginRoutes(meta.id, opts.cli));
|
|
782
|
-
|
|
783
|
-
if (compiledHelp) {
|
|
784
|
-
const pageBase = compiledHelp.summary.pageBase;
|
|
785
|
-
const centralPageBase = `/help/apps/${encodeURIComponent(meta.id)}`;
|
|
786
|
-
server.get(pageBase, (c) => c.redirect(centralPageBase, 302));
|
|
787
|
-
server.get(`${pageBase}/:topic`, (c) => c.redirect(`${centralPageBase}/${encodeURIComponent(c.req.param("topic"))}`, 302));
|
|
788
|
-
}
|
|
789
|
-
|
|
790
812
|
if (compiledCapabilities) {
|
|
791
|
-
const invoke = async (c: Context<AuthContext>, kind: "query" | "action" | "review") =>
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
813
|
+
const invoke = async (c: Context<AuthContext>, kind: "query" | "action" | "review") =>
|
|
814
|
+
withCapabilityRequestTimeout(c, kind, async () => {
|
|
815
|
+
// Framework-owned endpoints run before the app's settings middleware,
|
|
816
|
+
// so the operator default is read directly instead of via a snapshot.
|
|
817
|
+
const requestLocale = resolveLocale(c.req.raw.headers, await get<string>("app.locale"));
|
|
818
|
+
const messages = capabilityMessages(requestLocale);
|
|
819
|
+
const parsedBody = await readBoundedJson(c.req.raw, CAPABILITY_MAX_REQUEST_BYTES);
|
|
820
|
+
if (!parsedBody.ok) {
|
|
821
|
+
const message = parsedBody.reason === "too_large" ? messages.requestTooLarge : messages.requestBodyJson;
|
|
822
|
+
return c.json({ code: CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed, message }, 400);
|
|
823
|
+
}
|
|
824
|
+
if (
|
|
825
|
+
typeof parsedBody.data !== "object" ||
|
|
826
|
+
parsedBody.data === null ||
|
|
827
|
+
Array.isArray(parsedBody.data) ||
|
|
828
|
+
!Object.hasOwn(parsedBody.data, "input") ||
|
|
829
|
+
Object.keys(parsedBody.data).length !== 1
|
|
830
|
+
) {
|
|
831
|
+
return c.json(
|
|
832
|
+
{
|
|
833
|
+
code: CAPABILITY_FRAMEWORK_ERROR_CODES.validationFailed,
|
|
834
|
+
message: messages.requestInputOnly,
|
|
835
|
+
},
|
|
836
|
+
400,
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const body = parsedBody.data as { input: unknown };
|
|
840
|
+
const actor = c.get("actor");
|
|
841
|
+
const user = actor.kind === "user" ? actor.user : actor.delegatedUser;
|
|
842
|
+
const idempotencyKey = c.req.header("idempotency-key") || undefined;
|
|
843
|
+
const invocation = {
|
|
844
|
+
compiled: compiledCapabilities,
|
|
845
|
+
localId: c.req.param("capabilityId") ?? "",
|
|
846
|
+
input: body.input,
|
|
847
|
+
expectedSchemaHash: c.req.header("x-cloud-capability-schema-hash") ?? null,
|
|
848
|
+
context: {
|
|
849
|
+
authority: auth.getAuthority(c),
|
|
850
|
+
actor,
|
|
851
|
+
accessSubject: c.get("accessSubject"),
|
|
852
|
+
user,
|
|
853
|
+
idempotencyKey,
|
|
854
|
+
locale: requestLocale,
|
|
855
|
+
requestId: normalizeInvocationRequestId(c.req.header("x-request-id")) ?? crypto.randomUUID(),
|
|
856
|
+
origin: CapabilityOriginSchema.catch("http").parse(c.req.header(CAPABILITY_ORIGIN_HEADER)),
|
|
857
|
+
signal: c.req.raw.signal,
|
|
812
858
|
},
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
origin: CapabilityOriginSchema.catch("http").parse(c.req.header(CAPABILITY_ORIGIN_HEADER)),
|
|
833
|
-
signal: c.req.raw.signal,
|
|
834
|
-
},
|
|
835
|
-
onUnexpectedError: (error: unknown) =>
|
|
836
|
-
log.error(kind === "review" ? "Capability review failed" : "Capability execution failed", {
|
|
837
|
-
appId: meta.id,
|
|
838
|
-
kind,
|
|
839
|
-
capabilityId: c.req.param("capabilityId") ?? "",
|
|
840
|
-
error: error instanceof Error ? error.message : String(error),
|
|
841
|
-
}),
|
|
842
|
-
};
|
|
843
|
-
const result =
|
|
844
|
-
kind === "review" ? await reviewCompiledCapability(invocation) : await invokeCompiledCapability({ ...invocation, kind });
|
|
845
|
-
const operation = kind === "action" ? compiledCapabilities.actions.get(invocation.localId) : undefined;
|
|
846
|
-
const serialized = serializeCapabilityProviderResult(result, {
|
|
847
|
-
nonIdempotentAction: kind === "action" && operation?.manifest.idempotency === "none",
|
|
848
|
-
});
|
|
849
|
-
return new Response(serialized.body, {
|
|
850
|
-
status: serialized.status,
|
|
851
|
-
headers: { "content-type": "application/json" },
|
|
859
|
+
onUnexpectedError: (error: unknown) =>
|
|
860
|
+
log.error(kind === "review" ? "Capability review failed" : "Capability execution failed", {
|
|
861
|
+
appId: meta.id,
|
|
862
|
+
kind,
|
|
863
|
+
capabilityId: c.req.param("capabilityId") ?? "",
|
|
864
|
+
error: error instanceof Error ? error.message : String(error),
|
|
865
|
+
}),
|
|
866
|
+
};
|
|
867
|
+
const result =
|
|
868
|
+
kind === "review" ? await reviewCompiledCapability(invocation) : await invokeCompiledCapability({ ...invocation, kind });
|
|
869
|
+
const operation = kind === "action" ? compiledCapabilities.actions.get(invocation.localId) : undefined;
|
|
870
|
+
const serialized = serializeCapabilityProviderResult(result, {
|
|
871
|
+
review: kind === "review",
|
|
872
|
+
nonIdempotentAction: kind === "action" && operation?.manifest.idempotency === "none",
|
|
873
|
+
});
|
|
874
|
+
return new Response(serialized.body, {
|
|
875
|
+
status: serialized.status,
|
|
876
|
+
headers: { "content-type": "application/json" },
|
|
877
|
+
});
|
|
852
878
|
});
|
|
853
|
-
};
|
|
854
879
|
const capabilityAuth = (kind: "queries" | "actions", review = false) =>
|
|
855
880
|
requireInvocation((c) => {
|
|
856
881
|
const localId = c.req.param("capabilityId") ?? "";
|
|
@@ -869,7 +894,7 @@ export const defineApp = <
|
|
|
869
894
|
operation: invocationOperation,
|
|
870
895
|
schemaHash: operation.manifest.schemaHash,
|
|
871
896
|
};
|
|
872
|
-
});
|
|
897
|
+
}, invocationApp);
|
|
873
898
|
for (const kind of ["queries", "actions"] as const) {
|
|
874
899
|
server.post(
|
|
875
900
|
`/api/_internal/capabilities/v1/streams/${kind}/:capabilityId/:verb`,
|
|
@@ -884,6 +909,7 @@ export const defineApp = <
|
|
|
884
909
|
verb: c.req.param("verb") ?? "",
|
|
885
910
|
request: c.req.raw,
|
|
886
911
|
context: {
|
|
912
|
+
authority: auth.getAuthority(c),
|
|
887
913
|
actor,
|
|
888
914
|
accessSubject: c.get("accessSubject"),
|
|
889
915
|
user: actor.kind === "user" ? actor.user : actor.delegatedUser,
|
|
@@ -923,7 +949,7 @@ export const defineApp = <
|
|
|
923
949
|
const widgetId = c.req.param("widgetId") ?? "";
|
|
924
950
|
if (!declaredWidgetIds.has(widgetId) || !startOpts.widgets?.[widgetId]) return null;
|
|
925
951
|
return { targetAppId: meta.id, operation: widgetInvocationOperation(widgetId), schemaHash: null };
|
|
926
|
-
}),
|
|
952
|
+
}, invocationApp),
|
|
927
953
|
auth.requireOAuthScope("read", "admin"),
|
|
928
954
|
runtimeMiddleware(),
|
|
929
955
|
settingsMiddleware(),
|
|
@@ -938,10 +964,31 @@ export const defineApp = <
|
|
|
938
964
|
);
|
|
939
965
|
}
|
|
940
966
|
|
|
967
|
+
cleanup.push(() => stopAppAccessConnections());
|
|
968
|
+
server.use(
|
|
969
|
+
"*",
|
|
970
|
+
requireAppAccess(meta, (c, message) =>
|
|
971
|
+
ssr.error(c, 403, {
|
|
972
|
+
title: "No access",
|
|
973
|
+
description: message,
|
|
974
|
+
layout: "minimal",
|
|
975
|
+
}),
|
|
976
|
+
),
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
if (opts.cli && cliModules.length > 0) server.route(CLOUD_CLI_PLUGIN_ROUTE, createCliPluginRoutes(meta.id, opts.cli));
|
|
980
|
+
|
|
981
|
+
if (compiledHelp) {
|
|
982
|
+
const pageBase = compiledHelp.summary.pageBase;
|
|
983
|
+
const centralPageBase = `/help/apps/${encodeURIComponent(meta.id)}`;
|
|
984
|
+
server.get(pageBase, (c) => c.redirect(centralPageBase, 302));
|
|
985
|
+
server.get(`${pageBase}/:topic`, (c) => c.redirect(`${centralPageBase}/${encodeURIComponent(c.req.param("topic"))}`, 302));
|
|
986
|
+
}
|
|
987
|
+
|
|
941
988
|
// OpenAPI spec mount. Registered on the framework server (before the
|
|
942
989
|
// user-fetch catch-all below) so it bypasses any auth / rate-limit
|
|
943
990
|
// middleware on the api router — the spec must stay reachable without
|
|
944
|
-
// a session for
|
|
991
|
+
// a session for legacy apps. Declared app access is enforced above.
|
|
945
992
|
//
|
|
946
993
|
// The `servers` override is load-bearing: hono-openapi walks the
|
|
947
994
|
// BARE api router and emits paths relative to its own root (e.g.
|
|
@@ -997,7 +1044,8 @@ export const defineApp = <
|
|
|
997
1044
|
|
|
998
1045
|
if (!startOpts.skipSetup && startOpts.lifecycle?.setup) {
|
|
999
1046
|
log.info(`Setup: ${meta.id}`);
|
|
1000
|
-
|
|
1047
|
+
const setup = startOpts.lifecycle.setup;
|
|
1048
|
+
await runSetupExclusively(meta.id, () => setup(cloudCtx));
|
|
1001
1049
|
}
|
|
1002
1050
|
if (meta.id === "core") await startOutbox();
|
|
1003
1051
|
|
|
@@ -1020,18 +1068,36 @@ export const defineApp = <
|
|
|
1020
1068
|
log.info(`Registered "${meta.id}"`, { baseUrl });
|
|
1021
1069
|
|
|
1022
1070
|
// Graceful shutdown
|
|
1023
|
-
let
|
|
1024
|
-
const shutdown =
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1071
|
+
let shutdownPromise: Promise<void> | undefined;
|
|
1072
|
+
const shutdown = () =>
|
|
1073
|
+
(shutdownPromise ??= (async () => {
|
|
1074
|
+
const drained = http.stop();
|
|
1075
|
+
log.info(`Stopping: ${meta.id}`);
|
|
1076
|
+
try {
|
|
1077
|
+
await drained;
|
|
1078
|
+
} finally {
|
|
1079
|
+
await stopStartedResources();
|
|
1080
|
+
}
|
|
1081
|
+
})());
|
|
1030
1082
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1083
|
+
const stopAndExit = () =>
|
|
1084
|
+
void shutdown().then(
|
|
1085
|
+
() => process.exit(0),
|
|
1086
|
+
(error: unknown) => {
|
|
1087
|
+
log.error("HTTP shutdown failed", { appId: meta.id, error: error instanceof Error ? error.message : String(error) });
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
},
|
|
1090
|
+
);
|
|
1091
|
+
process.on("SIGTERM", stopAndExit);
|
|
1092
|
+
process.on("SIGINT", stopAndExit);
|
|
1033
1093
|
|
|
1034
|
-
return {
|
|
1094
|
+
return {
|
|
1095
|
+
hostname: "0.0.0.0",
|
|
1096
|
+
port,
|
|
1097
|
+
development: isDevelopment,
|
|
1098
|
+
idleTimeout: REQUEST_IDLE_TIMEOUT_SECONDS,
|
|
1099
|
+
fetch: (request, bunServer) => http.fetch(request, bunServer, (request) => server.fetch(request, bunServer)),
|
|
1100
|
+
};
|
|
1035
1101
|
} catch (error) {
|
|
1036
1102
|
await stopStartedResources();
|
|
1037
1103
|
throw error;
|