@k2b/cloud 0.45.0 → 0.47.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.
Files changed (131) hide show
  1. package/package.json +11 -4
  2. package/scripts/build.ts +5 -1
  3. package/scripts/preload.ts +5 -1
  4. package/src/_internal/app-queries.ts +73 -0
  5. package/src/_internal/capabilities.ts +137 -9
  6. package/src/_internal/define-app.ts +40 -0
  7. package/src/_internal/heartbeat.ts +19 -0
  8. package/src/_internal/postgres-application-name.ts +11 -0
  9. package/src/_internal/process-sync.ts +9 -0
  10. package/src/_internal/readiness.ts +6 -1
  11. package/src/_internal/registry-validation.ts +17 -4
  12. package/src/_internal/registry.ts +32 -4
  13. package/src/_internal/resource-principals.ts +6 -0
  14. package/src/_internal/workload-credential.ts +153 -0
  15. package/src/_internal/workload-protocol.ts +36 -0
  16. package/src/ai/application-tasks.ts +5 -2
  17. package/src/ai/capabilities.ts +4 -2
  18. package/src/ai/chart-block.ts +36 -711
  19. package/src/ai/chat/map-messages.ts +270 -0
  20. package/src/ai/chat/map-notebook.browser-harness.tsx +28 -0
  21. package/src/ai/chat/map-notebook.tsx +301 -0
  22. package/src/ai/chat/map-widget.browser-harness.tsx +71 -0
  23. package/src/ai/chat/map-widget.css +107 -0
  24. package/src/ai/chat/map-widget.tsx +1279 -0
  25. package/src/ai/chat/message-utils.ts +1 -0
  26. package/src/ai/chat/tool-names.ts +2 -0
  27. package/src/ai/chat/turn-layout.ts +2 -1
  28. package/src/ai/chat/turn-view.tsx +17 -1
  29. package/src/ai/default-tool-contracts.ts +21 -259
  30. package/src/ai/default-tools.ts +3 -0
  31. package/src/ai/executor.ts +13 -1
  32. package/src/ai/map-contracts.ts +209 -0
  33. package/src/ai/map-execution.ts +14 -0
  34. package/src/ai/map-notebook-save.ts +276 -0
  35. package/src/ai/map-notebook.ts +32 -0
  36. package/src/ai/map-opening-hours.ts +40 -0
  37. package/src/ai/map-routes.ts +84 -0
  38. package/src/ai/map-service.ts +288 -0
  39. package/src/ai/map-store.ts +137 -0
  40. package/src/ai/map-tool.ts +73 -0
  41. package/src/ai/migrate.ts +3 -0
  42. package/src/ai/protocol.ts +3 -1
  43. package/src/ai/routes.ts +2 -0
  44. package/src/ai/store.ts +19 -6
  45. package/src/ai/tools.ts +14 -0
  46. package/src/ai/types.ts +3 -0
  47. package/src/api/admin-app-keys.ts +66 -0
  48. package/src/api/admin-core-settings.ts +2 -0
  49. package/src/api/admin-identity.ts +27 -1
  50. package/src/api/admin-settings-env.ts +120 -0
  51. package/src/api/capabilities.ts +99 -15
  52. package/src/api/index.ts +3 -0
  53. package/src/api/mcp.ts +4 -2
  54. package/src/api/settings-ops.ts +35 -0
  55. package/src/app-key.ts +125 -0
  56. package/src/browser/CloudResourceSearch.tsx +48 -41
  57. package/src/browser/notifications.ts +25 -8
  58. package/src/capabilities/discussion-providers.ts +100 -0
  59. package/src/capabilities/executions.ts +2 -2
  60. package/src/capabilities/migrate.ts +10 -1
  61. package/src/capabilities/resource-readers.ts +69 -0
  62. package/src/capabilities/response.ts +4 -1
  63. package/src/capabilities/server-invoke.ts +64 -36
  64. package/src/capabilities/server.ts +17 -6
  65. package/src/capabilities/testing.ts +13 -0
  66. package/src/capabilities/types.ts +1 -1
  67. package/src/cli/admin/app-credentials.ts +32 -7
  68. package/src/cli/admin/app-keys.ts +226 -0
  69. package/src/cli/admin/index.ts +7 -1
  70. package/src/cli/admin/settings.ts +62 -0
  71. package/src/config/define-env.ts +63 -8
  72. package/src/config/env.ts +27 -21
  73. package/src/config/index.ts +12 -1
  74. package/src/config/workflow-ai.ts +28 -0
  75. package/src/contracts/capabilities.ts +137 -4
  76. package/src/contracts/discussions.ts +91 -0
  77. package/src/contracts/geo-document.ts +60 -0
  78. package/src/contracts/geo-snapshot.ts +86 -0
  79. package/src/contracts/geo.ts +14 -0
  80. package/src/contracts/index.ts +2 -0
  81. package/src/contracts/outgoing-mail.ts +1 -1
  82. package/src/contracts/resource-types.ts +21 -0
  83. package/src/contracts/settings-env.ts +21 -0
  84. package/src/contracts/settings-types.ts +6 -0
  85. package/src/geo/AddressAutocomplete.tsx +213 -0
  86. package/src/geo/messages.ts +29 -0
  87. package/src/geo/navigation.ts +25 -0
  88. package/src/geo/ui.ts +2 -0
  89. package/src/index.ts +9 -0
  90. package/src/runtime/clients.ts +72 -0
  91. package/src/runtime/connections.ts +37 -0
  92. package/src/runtime/resolve-connections.ts +91 -0
  93. package/src/server/client-ip.ts +1 -1
  94. package/src/services/accounts/app.ts +28 -6
  95. package/src/services/accounts/entities.ts +11 -4
  96. package/src/services/accounts/groups.ts +10 -4
  97. package/src/services/accounts/local-groups.ts +87 -38
  98. package/src/services/accounts/machine-group-management.ts +94 -0
  99. package/src/services/app-keys.ts +298 -0
  100. package/src/services/gateway.ts +6 -0
  101. package/src/services/geo/cache.ts +2 -1
  102. package/src/services/geo/geoapify.ts +3 -0
  103. package/src/services/identity/index.ts +3 -0
  104. package/src/services/identity/invocation-token.ts +110 -0
  105. package/src/services/identity/resource-readers.ts +80 -0
  106. package/src/services/identity/runtime-config.ts +14 -6
  107. package/src/services/identity/workload-runtime.ts +253 -0
  108. package/src/services/index.ts +4 -1
  109. package/src/services/outgoing-mail/resolve-host.ts +5 -4
  110. package/src/services/pdf/geo-snapshot.ts +107 -0
  111. package/src/services/pdf/index.ts +2 -0
  112. package/src/services/service-account-credentials.ts +63 -21
  113. package/src/services/settings/api.ts +3 -3
  114. package/src/services/settings/binding.ts +45 -0
  115. package/src/services/settings/core-settings.ts +99 -1
  116. package/src/services/settings/defaults.ts +16 -2
  117. package/src/services/settings/env-status.ts +69 -0
  118. package/src/services/settings/index.ts +69 -26
  119. package/src/services/settings/store.ts +73 -8
  120. package/src/services/settings/validation.ts +40 -0
  121. package/src/shared/index.ts +11 -10
  122. package/src/shared/markdown/extensions/tables.ts +10 -1
  123. package/src/ssr/AdminArea.tsx +2 -2
  124. package/src/ssr/AdminLayout.tsx +1 -1
  125. package/src/ssr/AdminSidebar.tsx +1 -1
  126. package/src/start.ts +36 -0
  127. package/src/styles/global.css +2 -0
  128. package/src/workflows/ai/index.ts +1 -0
  129. package/src/workflows/ai/runtime.ts +16 -8
  130. package/src/workflows/ai/settings.ts +5 -0
  131. package/src/shared/markdown/formula.ts +0 -1283
@@ -19,6 +19,13 @@ import { withSyncBudgets } from "./sync-budget";
19
19
 
20
20
  let current: Sync | undefined;
21
21
  let starting = false;
22
+ let processNats: NatsConnection | undefined;
23
+
24
+ /** Existing non-retained NATS transport, owned and drained by the process lifecycle. */
25
+ export const getProcessNats = (): NatsConnection => {
26
+ if (!processNats) throw new Error("NATS is not available before app.start()");
27
+ return processNats;
28
+ };
22
29
 
23
30
  /** Bind the process instance. `startProcessSync()` does this; tests may bind their own. */
24
31
  export const bindProcessSync = (sync: Sync): void => {
@@ -173,6 +180,7 @@ export const startProcessSync = async ({ application }: { application: string })
173
180
  starting = true;
174
181
  try {
175
182
  const { sync: active, connection } = await waitForNats(() => connectReadySync(application), { application });
183
+ processNats = connection;
176
184
  let stopPromise: Promise<void> | undefined;
177
185
  return {
178
186
  sync: active,
@@ -185,6 +193,7 @@ export const startProcessSync = async ({ application }: { application: string })
185
193
  await flushSyncTraceEvents();
186
194
  } finally {
187
195
  if (current === active) unbindProcessSync();
196
+ if (processNats === connection) processNats = undefined;
188
197
  await connection.drain();
189
198
  }
190
199
  }
@@ -3,4 +3,9 @@ import { buildMetadata } from "./build-metadata";
3
3
  export const APP_READINESS_PATH = "/_cloud/ready";
4
4
 
5
5
  export const appReadinessResponse = (appId: string): Response =>
6
- Response.json({ status: "ready", appId, version: buildMetadata.version, release: buildMetadata.release });
6
+ Response.json({
7
+ status: "ready",
8
+ appId,
9
+ version: buildMetadata.version,
10
+ release: buildMetadata.release,
11
+ });
@@ -160,8 +160,8 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
160
160
  return invalid("widgets", "an array of valid widgets");
161
161
  }
162
162
  }
163
- if (value.platformPermissions !== undefined && !PlatformPermissionSchema.array().safeParse(value.platformPermissions).success)
164
- return invalid("platformPermissions", "an array of platform permissions");
163
+ if (value.platformPermissions !== undefined && !isStringArray(value.platformPermissions))
164
+ return invalid("platformPermissions", "an array of permission IDs");
165
165
  if (value.settingKeys !== undefined && !isStringArray(value.settingKeys)) return invalid("settingKeys", "an array of strings");
166
166
  if (
167
167
  value.cliModules !== undefined &&
@@ -198,5 +198,18 @@ export const validateAppRegistryEntry = (value: unknown): string | null => {
198
198
  return null;
199
199
  };
200
200
 
201
- export const asAppRegistryEntry = (value: unknown): AppRegistryEntry | null =>
202
- validateAppRegistryEntry(value) === null ? (value as AppRegistryEntry) : null;
201
+ export const asAppRegistryEntry = (value: unknown): AppRegistryEntry | null => {
202
+ if (validateAppRegistryEntry(value) !== null) return null;
203
+ const entry = value as AppRegistryEntry;
204
+ return {
205
+ ...entry,
206
+ ...(entry.platformPermissions !== undefined
207
+ ? {
208
+ platformPermissions: entry.platformPermissions.flatMap((permission) => {
209
+ const parsed = PlatformPermissionSchema.safeParse(permission);
210
+ return parsed.success ? [parsed.data] : [];
211
+ }),
212
+ }
213
+ : {}),
214
+ };
215
+ };
@@ -3,6 +3,7 @@ import type { EphemeralConfig, MutexConfig } from "@k2b/sync";
3
3
  import type { AppAppearanceColor } from "../contracts/app";
4
4
  import { calendarSourceIssues } from "../contracts/calendar-source";
5
5
  import type { CapabilityManifest, CapabilityPresentationCatalog } from "../contracts/capabilities";
6
+ import { discussionsIssues } from "../contracts/discussions";
6
7
  import { fileProviderIssues } from "../contracts/file-provider";
7
8
  import type { AppRegistryEntry, CapabilityRegistryEntry } from "../contracts/registry";
8
9
  import { resourcePageIssue } from "../contracts/resource-pages";
@@ -18,7 +19,7 @@ import {
18
19
  import { resolveAppPresentations } from "../shared/app-presentation";
19
20
  import { compileCapabilityPresentation, parseCapabilityManifest } from "./capabilities";
20
21
  import { lazySync } from "./process-sync";
21
- import { validateAppRegistryEntry } from "./registry-validation";
22
+ import { asAppRegistryEntry, validateAppRegistryEntry } from "./registry-validation";
22
23
  import { watchRegistryChanges } from "./registry-watch";
23
24
  import { WidgetSettingsDeclarationSchema } from "./widget-settings";
24
25
 
@@ -150,7 +151,7 @@ export const readAppRegistrySnapshot = async (): Promise<AppRegistrySnapshot> =>
150
151
  continue;
151
152
  }
152
153
  loggedInvalidReasons.delete(entry.key);
153
- const value = entry.value as AppRegistryEntry;
154
+ const value = asAppRegistryEntry(entry.value)!;
154
155
  const updatedAt = entry.updatedAt.getTime();
155
156
  apps.push({
156
157
  ...value,
@@ -194,7 +195,7 @@ export const getApp = async (appId: string): Promise<AppRegistryEntry | null> =>
194
195
  : validateAppRegistryEntry(entry.value);
195
196
  if (!reason) {
196
197
  loggedInvalidReasons.delete(key);
197
- return entry.value;
198
+ return asAppRegistryEntry(entry.value);
198
199
  }
199
200
  reportInvalidEntry({ key, version: entry.revision, reason });
200
201
  return null;
@@ -237,6 +238,7 @@ const reportLeftOutEntries = (sent: unknown, manifest: CapabilityManifest): void
237
238
  });
238
239
  if (groups.calendarSource !== undefined && !manifest.calendarSource) leftOut.push("calendarSource");
239
240
  if (groups.fileProvider !== undefined && !manifest.fileProvider) leftOut.push("fileProvider");
241
+ if (groups.discussions !== undefined && !manifest.discussions) leftOut.push("discussions");
240
242
  if (leftOut.length === 0) return;
241
243
  console.warn(
242
244
  JSON.stringify({
@@ -320,6 +322,32 @@ const withValidCalendarSource = (manifest: CapabilityManifest): CapabilityManife
320
322
  return rest;
321
323
  };
322
324
 
325
+ const checkedDiscussions = new Map<string, { manifestHash: string; valid: boolean }>();
326
+ const withValidDiscussions = (manifest: CapabilityManifest): CapabilityManifest => {
327
+ if (!manifest.discussions) return manifest;
328
+ const checked = checkedDiscussions.get(manifest.appId);
329
+ let valid = checked?.manifestHash === manifest.manifestHash ? checked.valid : undefined;
330
+ if (valid === undefined) {
331
+ const issues = discussionsIssues(manifest);
332
+ valid = issues.length === 0;
333
+ checkedDiscussions.set(manifest.appId, { manifestHash: manifest.manifestHash, valid });
334
+ if (!valid)
335
+ console.error(
336
+ JSON.stringify({
337
+ level: "error",
338
+ source: "capability-registry",
339
+ message: "Ignored an incompatible discussions provider",
340
+ appId: manifest.appId,
341
+ manifestHash: manifest.manifestHash,
342
+ issues,
343
+ }),
344
+ );
345
+ }
346
+ if (valid) return manifest;
347
+ const { discussions: _ignored, ...rest } = manifest;
348
+ return rest;
349
+ };
350
+
323
351
  /** Ignores record fields from a newer release; the endpoint always comes from the live app registry. */
324
352
  export const resolveLiveCapabilityRegistryEntry = (
325
353
  key: string,
@@ -334,7 +362,7 @@ export const resolveLiveCapabilityRegistryEntry = (
334
362
  if (!endpoint) return null;
335
363
  try {
336
364
  const read = parseCapabilityManifest(record.manifest, app.id);
337
- const supported = withValidCalendarSource(withValidFileProvider(read));
365
+ const supported = withValidDiscussions(withValidCalendarSource(withValidFileProvider(read)));
338
366
  const manifest = supported.resourcePages
339
367
  ? {
340
368
  ...supported,
@@ -0,0 +1,6 @@
1
+ import type { ResourcePrincipal } from "../contracts/capabilities";
2
+ import { resolveResourceReaders } from "../services/identity/resource-readers";
3
+
4
+ /** Apply the same current-account rules as provider-owned readability checks. */
5
+ export const activeResourcePrincipals = async (principals: ResourcePrincipal[]): Promise<ResourcePrincipal[]> =>
6
+ (await resolveResourceReaders(principals)).map((reader) => reader.principal);
@@ -0,0 +1,153 @@
1
+ import { env } from "../config/env";
2
+ import { getProcessApplicationId } from "./process-identity";
3
+ import { getProcessNats } from "./process-sync";
4
+ import {
5
+ WORKLOAD_CHECK_INTERVAL_MS,
6
+ WORKLOAD_REQUEST_TIMEOUT_MS,
7
+ type WorkloadRequest,
8
+ WorkloadResponseSchema,
9
+ workloadSubject,
10
+ } from "./workload-protocol";
11
+
12
+ export type WorkloadCredentialHealth = { status: "ready"; source: "runtime" | "override" } | { status: "unavailable"; reason: string };
13
+ let active: { appId: string; token: string; expiresAt: number } | undefined;
14
+ let health: WorkloadCredentialHealth | undefined;
15
+ let refreshRuntime: { appId: string; refresh: () => Promise<void> } | undefined;
16
+
17
+ /** Refresh only this process's app authority; concurrent broker rejections share one request. */
18
+ export const refreshAppWorkloadCredential = async (appId: string): Promise<string | undefined> => {
19
+ if (appId !== getProcessApplicationId()) return undefined;
20
+ if (refreshRuntime?.appId === appId) await refreshRuntime.refresh();
21
+ return getAppWorkloadCredential();
22
+ };
23
+
24
+ /** One server-side accessor for workers and app-authority invocation clients. Never send to a target app. */
25
+ export const getAppWorkloadCredential = (): string | undefined => {
26
+ const override = env.CLOUD_APP_CREDENTIAL?.trim();
27
+ if (override) return override;
28
+ if (active && active.appId === getProcessApplicationId() && active.expiresAt > Date.now()) return active.token;
29
+ return undefined;
30
+ };
31
+
32
+ export const getAppWorkloadCredentialHealth = (): WorkloadCredentialHealth | undefined => {
33
+ if (env.CLOUD_APP_CREDENTIAL?.trim()) return { status: "ready", source: "override" };
34
+ if (active && active.expiresAt <= Date.now()) return { status: "unavailable", reason: "expired" };
35
+ return health;
36
+ };
37
+
38
+ export const createAppWorkloadCredential = (options: {
39
+ appId: string;
40
+ instanceId: string;
41
+ request?: (request: WorkloadRequest) => Promise<unknown>;
42
+ onUnavailable?: (reason: string) => void;
43
+ onRecovered?: () => void;
44
+ }) => {
45
+ let credentialId: string | null = null;
46
+ let running = false;
47
+ let refreshFailed = false;
48
+ let timer: Timer | undefined;
49
+ let inFlight: Promise<void> | undefined;
50
+ const request =
51
+ options.request ??
52
+ (async (input: WorkloadRequest) => {
53
+ const response = await getProcessNats().request(workloadSubject(env.SYNC_NAMESPACE), JSON.stringify(input), {
54
+ timeout: WORKLOAD_REQUEST_TIMEOUT_MS,
55
+ });
56
+ return response.json<unknown>();
57
+ });
58
+ const unavailable = (reason: string, preserve = false) => {
59
+ refreshFailed = true;
60
+ if (preserve && active && active.expiresAt > Date.now()) return;
61
+ if (!preserve) {
62
+ active = undefined;
63
+ credentialId = null;
64
+ }
65
+ const changed = health?.status !== "unavailable" || health.reason !== reason;
66
+ health = { status: "unavailable", reason };
67
+ if (changed) options.onUnavailable?.(reason);
68
+ };
69
+ const recovered = () => {
70
+ health = { status: "ready", source: "runtime" };
71
+ if (refreshFailed) options.onRecovered?.();
72
+ refreshFailed = false;
73
+ };
74
+ const refresh = async (): Promise<void> => {
75
+ if (env.CLOUD_APP_CREDENTIAL?.trim()) {
76
+ active = undefined;
77
+ health = { status: "ready", source: "override" };
78
+ return;
79
+ }
80
+ try {
81
+ const response = WorkloadResponseSchema.parse(
82
+ await request({ operation: "refresh", appId: options.appId, instanceId: options.instanceId, credentialId }),
83
+ );
84
+ if (!running) return;
85
+ if (response.status === "issued") {
86
+ if (
87
+ response.appId !== options.appId ||
88
+ response.instanceId !== options.instanceId ||
89
+ Date.parse(response.expiresAt) <= Date.now()
90
+ ) {
91
+ unavailable("invalid_response");
92
+ return;
93
+ }
94
+ credentialId = response.credentialId;
95
+ active = { appId: options.appId, token: response.token, expiresAt: Date.parse(response.expiresAt) };
96
+ recovered();
97
+ } else if (response.status === "current" && response.credentialId === credentialId && active && active.expiresAt > Date.now()) {
98
+ recovered();
99
+ } else {
100
+ unavailable(response.status === "current" ? "invalid_response" : response.status, response.status === "unavailable");
101
+ }
102
+ } catch {
103
+ if (running) {
104
+ unavailable("issuance_unavailable", true);
105
+ }
106
+ }
107
+ };
108
+ const refreshOnce = (): Promise<void> => {
109
+ if (inFlight) return inFlight;
110
+ const operation = refresh().finally(() => {
111
+ if (inFlight === operation) inFlight = undefined;
112
+ });
113
+ inFlight = operation;
114
+ return operation;
115
+ };
116
+ const schedule = () => {
117
+ if (!running) return;
118
+ timer = setTimeout(
119
+ () => {
120
+ void refreshOnce().finally(schedule);
121
+ },
122
+ refreshFailed || health?.status === "unavailable" ? 5_000 : WORKLOAD_CHECK_INTERVAL_MS,
123
+ );
124
+ };
125
+ return {
126
+ start: async () => {
127
+ if (running) {
128
+ await inFlight;
129
+ return;
130
+ }
131
+ running = true;
132
+ refreshRuntime = { appId: options.appId, refresh: refreshOnce };
133
+ health = { status: "unavailable", reason: "starting" };
134
+ await refreshOnce();
135
+ schedule();
136
+ },
137
+ refresh: refreshOnce,
138
+ stop: async () => {
139
+ running = false;
140
+ if (refreshRuntime?.refresh === refreshOnce) refreshRuntime = undefined;
141
+ clearTimeout(timer);
142
+ await inFlight;
143
+ if (!env.CLOUD_APP_CREDENTIAL?.trim()) {
144
+ await request({ operation: "release", appId: options.appId, instanceId: options.instanceId, credentialId }).catch(() => {
145
+ // Core's registry reconciliation revokes after disappearance even when this reply is lost.
146
+ options.onUnavailable?.("release_unavailable");
147
+ });
148
+ }
149
+ active = undefined;
150
+ health = undefined;
151
+ },
152
+ };
153
+ };
@@ -0,0 +1,36 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { CapabilityAppIdSchema } from "../contracts/capabilities";
4
+
5
+ // Core NATS request/reply only: no JetStream subject, KV value, or retained raw token.
6
+ export const workloadSubject = (namespace: string): string => `cloud.workload.${createHash("sha256").update(namespace).digest("hex")}.v1`;
7
+ export const WORKLOAD_CHECK_INTERVAL_MS = 60_000;
8
+ export const WORKLOAD_REQUEST_TIMEOUT_MS = 5_000;
9
+ export const WORKLOAD_LIFETIME_MS = 60 * 60_000;
10
+ export const WORKLOAD_ROTATE_BEFORE_MS = WORKLOAD_LIFETIME_MS / 2;
11
+
12
+ export const WorkloadRequestSchema = z
13
+ .object({
14
+ operation: z.enum(["refresh", "release"]),
15
+ appId: CapabilityAppIdSchema,
16
+ instanceId: z.string().regex(/^[A-Za-z0-9_-]{16}$/),
17
+ credentialId: z.uuid().nullable(),
18
+ })
19
+ .strict();
20
+ export type WorkloadRequest = z.infer<typeof WorkloadRequestSchema>;
21
+
22
+ export const WorkloadResponseSchema = z.discriminatedUnion("status", [
23
+ z
24
+ .object({
25
+ status: z.literal("issued"),
26
+ appId: CapabilityAppIdSchema,
27
+ instanceId: z.string(),
28
+ credentialId: z.uuid(),
29
+ token: z.string().regex(/^cld_[a-f0-9]{24}_[a-f0-9]{64}$/),
30
+ expiresAt: z.iso.datetime(),
31
+ })
32
+ .strict(),
33
+ z.object({ status: z.literal("current"), credentialId: z.uuid() }).strict(),
34
+ z.object({ status: z.enum(["unavailable", "revoked", "unregistered"]) }).strict(),
35
+ ]);
36
+ export type WorkloadResponse = z.infer<typeof WorkloadResponseSchema>;
@@ -1,6 +1,6 @@
1
1
  import type { SQL } from "bun";
2
- import { env } from "../config/env";
3
2
  import type { AccessSubject } from "../server/services/access";
3
+ import { coreSettings } from "../services/settings/api";
4
4
  import { WorkflowAiAdmissionError, type WorkflowAiScope, workflowAiHash, workflowAiModelHash } from "../workflows/ai/policy";
5
5
  import { submitWorkflowAiTask } from "../workflows/ai/runtime";
6
6
  import {
@@ -56,7 +56,10 @@ export const runApplicationAiTask = async (input: ApplicationAiTaskInput) => {
56
56
  let task = await getWorkflowAiTaskByEffectKey(effectKey);
57
57
  if (!task) {
58
58
  const resolved = await resolveApplicationAiTaskModel(input.usageSubject, input.request.model);
59
- const admissionSignal = AbortSignal.any([input.signal, AbortSignal.timeout(env.WORKFLOW_AI_QUEUE_TIMEOUT_MS)]);
59
+ const admissionSignal = AbortSignal.any([
60
+ input.signal,
61
+ AbortSignal.timeout(await coreSettings.get<number>("ai.workflow_queue_timeout_ms")),
62
+ ]);
60
63
  for (;;) {
61
64
  input.signal.throwIfAborted();
62
65
  if (admissionSignal.aborted) throw new WorkflowAiAdmissionError("WORKFLOW_AI_QUEUE_TIMEOUT");
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type { Tool, ToolContext, ToolResolver } from "@k2b/nessi";
3
3
  import { z } from "zod";
4
- import { resolveCapabilityActionWording, resolveCapabilityOperationTitle } from "../_internal/capabilities";
4
+ import { isPersonVisibleCapabilityQuery, resolveCapabilityActionWording, resolveCapabilityOperationTitle } from "../_internal/capabilities";
5
5
  import { capabilityReviewView } from "../_internal/capability-reviews";
6
6
  import {
7
7
  CAPABILITY_APPROVAL_REASON_FIELD,
@@ -164,7 +164,9 @@ export const buildAiCapabilityCatalog = (apps: CapabilityRegistryEntry[], locale
164
164
  : app.appName;
165
165
  return [
166
166
  ...app.manifest.actions.map((operation) => ({ app, appName, operation, kind: "action" as const })),
167
- ...app.manifest.queries.map((operation) => ({ app, appName, operation, kind: "query" as const })),
167
+ ...app.manifest.queries
168
+ .filter(isPersonVisibleCapabilityQuery)
169
+ .map((operation) => ({ app, appName, operation, kind: "query" as const })),
168
170
  ];
169
171
  })
170
172
  .map(