@cosmicdrift/kumiko-bundled-features 0.352.0 → 0.353.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 (105) hide show
  1. package/dist/agent-tools/agent-manifest.js +8 -2
  2. package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
  3. package/dist/agent-tools/manifest-deny-qns.js +17 -0
  4. package/dist/agent-tools/tool-catalog.d.ts +2 -1
  5. package/dist/agent-tools/tool-catalog.js +4 -1
  6. package/dist/audit/handlers/list.query.js +13 -8
  7. package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -3
  8. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  9. package/dist/auth-mfa/screens.js +2 -2
  10. package/dist/billing-foundation/checkout-core.d.ts +7 -0
  11. package/dist/billing-foundation/checkout-core.js +7 -2
  12. package/dist/billing-foundation/entities.d.ts +8 -0
  13. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
  14. package/dist/billing-foundation/projection.d.ts +8 -0
  15. package/dist/cap-counter/entity.d.ts +1 -0
  16. package/dist/cap-overview/access.js +2 -9
  17. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
  18. package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
  19. package/dist/compliance-profiles/schema/profile-selection.d.ts +8 -8
  20. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
  21. package/dist/config/handlers/values.query.d.ts +3 -1
  22. package/dist/config/handlers/values.query.js +7 -2
  23. package/dist/config/table.d.ts +6 -0
  24. package/dist/custom-fields/entity.d.ts +4 -0
  25. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  26. package/dist/delivery/public-names.js +1 -1
  27. package/dist/delivery/tables.d.ts +6 -0
  28. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  29. package/dist/files-tenant-data/hooks.js +3 -2
  30. package/dist/folders/entity.d.ts +8 -0
  31. package/dist/folders/feature.js +2 -10
  32. package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
  33. package/dist/form-draft/db/queries/cleanup.js +4 -3
  34. package/dist/form-draft/entity.d.ts +2 -0
  35. package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
  36. package/dist/form-draft/handlers/cleanup.job.js +11 -5
  37. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  38. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  39. package/dist/jobs/handlers/list.query.d.ts +2 -1
  40. package/dist/jobs/handlers/list.query.js +39 -14
  41. package/dist/jobs/i18n.js +1 -0
  42. package/dist/jobs/job-run-table.d.ts +6 -0
  43. package/dist/ledger/entity.d.ts +21 -8
  44. package/dist/ledger/entity.js +4 -0
  45. package/dist/ledger/schemas.js +8 -2
  46. package/dist/managed-pages/feature.js +28 -10
  47. package/dist/managed-pages/table.d.ts +12 -0
  48. package/dist/notes-history/entity.d.ts +12 -0
  49. package/dist/notes-history/feature.js +2 -10
  50. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  51. package/dist/personal-access-tokens/screens.js +14 -3
  52. package/dist/secrets/table.d.ts +1 -0
  53. package/dist/seo/feature.js +28 -6
  54. package/dist/sessions/schema/user-session.d.ts +4 -0
  55. package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
  56. package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
  57. package/dist/shared/index.d.ts +2 -1
  58. package/dist/shared/index.js +2 -1
  59. package/dist/shared/rate-limited-text-response.d.ts +2 -0
  60. package/dist/shared/rate-limited-text-response.js +9 -0
  61. package/dist/tags/entity.d.ts +9 -0
  62. package/dist/tags/feature.js +2 -10
  63. package/dist/template-resolver/table.d.ts +40 -30
  64. package/dist/template-resolver/user-content-table.d.ts +30 -20
  65. package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
  66. package/dist/tenant/decrypt-tenant-names.js +10 -0
  67. package/dist/tenant/handlers/me.query.js +5 -1
  68. package/dist/tenant/handlers/memberships.query.js +3 -2
  69. package/dist/tenant/handlers/tenant-directory.query.js +9 -3
  70. package/dist/tenant/index.d.ts +1 -0
  71. package/dist/tenant/index.js +1 -0
  72. package/dist/tenant/invitation-table.d.ts +14 -8
  73. package/dist/tenant/membership-table.d.ts +4 -0
  74. package/dist/tenant/schema/tenant.d.ts +16 -10
  75. package/dist/tenant/schema/tenant.js +4 -6
  76. package/dist/tenant/screens.js +1 -1
  77. package/dist/tenant-handover/move-entity-graph.js +11 -18
  78. package/dist/tenant-lifecycle/feature.js +1 -0
  79. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
  80. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
  81. package/dist/tenant-lifecycle/stages.d.ts +2 -0
  82. package/dist/tenant-lifecycle/stages.js +7 -1
  83. package/dist/tier-engine/entity.d.ts +2 -0
  84. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  85. package/dist/user/handlers/list.query.js +2 -2
  86. package/dist/user/schema/user.d.ts +20 -8
  87. package/dist/user-data-rights/boot-checks.js +5 -0
  88. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
  89. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  90. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  91. package/dist/user-data-rights/schema/export-job.d.ts +14 -8
  92. package/dist/waitlist/entity.d.ts +20 -6
  93. package/dist/workflow-runner/event-trigger.js +6 -3
  94. package/dist/workflow-runner/handlers/resume-run.write.js +3 -0
  95. package/package.json +9 -9
  96. package/src/agent-tools/changes.json +12 -0
  97. package/src/audit/changes.json +6 -0
  98. package/src/form-draft/changes.json +7 -1
  99. package/src/managed-pages/changes.json +6 -0
  100. package/src/seo/changes.json +5 -0
  101. package/src/tenant/changes.json +7 -0
  102. package/src/tenant-handover/changes.json +6 -0
  103. package/src/workflow-runner/changes.json +5 -0
  104. package/dist/shared/has-where-rule.d.ts +0 -2
  105. package/dist/shared/has-where-rule.js +0 -10
@@ -1,6 +1,7 @@
1
1
  import { hasAccess, isAgentVisibleScreen, resolveAgentExposure, SYSTEM_ONLY_JSON_SCHEMA_KEY, } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { translationValueOtherText } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import * as z from "zod";
4
+ import { recordManifestDenyQns } from "./manifest-deny-qns.js";
4
5
  /** The manifest surfaces one string per locale — a plural entry reads as its
5
6
  * CLDR `other` form since the manifest has no `count` to select a category. */
6
7
  function entryAsLabels(entry) {
@@ -232,6 +233,9 @@ function lastSegment(qualifiedId) {
232
233
  function detailIdParam(screen) {
233
234
  return screen.type === "projectionDetail" ? (screen.idParam ?? "id") : "id";
234
235
  }
236
+ function screenTitles(translations, screenId, shortId) {
237
+ return entryAsLabels(translations[`${screenId}.title`] ?? translations[`screen:${shortId}.title`] ?? {});
238
+ }
235
239
  function buildScreens(screens, navs, workspaces, translations, roles, visibleNavIds) {
236
240
  const allNavs = [...navs.values()];
237
241
  const result = [];
@@ -257,7 +261,7 @@ function buildScreens(screens, navs, workspaces, translations, roles, visibleNav
257
261
  result.push({
258
262
  id: screen.id,
259
263
  type: screen.type,
260
- titles: labelsForSuffix(translations, `screen:${shortId}.title`),
264
+ titles: screenTitles(translations, screen.id, shortId),
261
265
  // `screen.description` may be a raw i18n key (`registry.getAllTranslations()`
262
266
  // double-prefixes it, see build-app-schema.ts:77-81) — match on suffix like
263
267
  // `labelsForSuffix` does for entity/field labels, else fall back to the
@@ -304,7 +308,7 @@ export function buildAgentManifest(registry, options) {
304
308
  const navs = buildNavs(navMap, workspaceMap, translations, roles, screenMap);
305
309
  const screens = buildScreens(screenMap, navMap, workspaceMap, translations, roles, new Set(navs.map((nav) => nav.id)));
306
310
  const workspaces = buildWorkspaces(workspaceMap, translations, roles);
307
- return {
311
+ const manifest = {
308
312
  builtForRoles: [...roles],
309
313
  features: sortedByKey(features, (f) => f.name),
310
314
  entities: sortedByKey(entities, (e) => e.name),
@@ -317,4 +321,6 @@ export function buildAgentManifest(registry, options) {
317
321
  ...(options.currency && { currency: options.currency }),
318
322
  },
319
323
  };
324
+ recordManifestDenyQns(manifest, new Set(options.denyQns ?? []));
325
+ return manifest;
320
326
  }
@@ -0,0 +1,3 @@
1
+ import type { AgentManifest } from "./types.js";
2
+ export declare function recordManifestDenyQns(manifest: AgentManifest, denyQns: ReadonlySet<string>): void;
3
+ export declare function assertCatalogMirrorsManifestDenyQns(manifest: AgentManifest, catalogDenyQns: ReadonlySet<string>): void;
@@ -0,0 +1,17 @@
1
+ // The deny list is deliberately not a manifest field (the manifest is prompt
2
+ // payload), so the catalog builder learns it through this side channel to
3
+ // detect a manifest cut that the catalog would silently not mirror.
4
+ const denyQnsByManifest = new WeakMap();
5
+ export function recordManifestDenyQns(manifest, denyQns) {
6
+ denyQnsByManifest.set(manifest, denyQns);
7
+ }
8
+ export function assertCatalogMirrorsManifestDenyQns(manifest, catalogDenyQns) {
9
+ const manifestDenyQns = denyQnsByManifest.get(manifest);
10
+ // skip: manifest has no registered deny list, nothing to mirror
11
+ if (!manifestDenyQns)
12
+ return;
13
+ const missing = [...manifestDenyQns].filter((qn) => !catalogDenyQns.has(qn));
14
+ if (missing.length > 0) {
15
+ throw new Error(`agent denyQns passed to buildAgentManifest but not to buildToolCatalog: ${missing.join(", ")} — the catalog would still expose their entity CRUD tools`);
16
+ }
17
+ }
@@ -24,5 +24,6 @@ export declare const OPEN_FORM_TOOL_NAME = "open_form";
24
24
  * to drop an entity CRUD tool, which no `agent.expose` on the manifest side can reach. It is
25
25
  * NOT read off the manifest on purpose — the manifest is prompt payload, and a list of the
26
26
  * handlers the model may not call has no business travelling to the provider. Pass the same
27
- * list to `buildAgentManifest` so the manifest stops describing what the catalog withholds. */
27
+ * list to `buildAgentManifest` so the manifest stops describing what the catalog withholds;
28
+ * a manifest built with `denyQns` that the catalog lacks throws. */
28
29
  export declare function buildToolCatalog(registry: RegistrySearchView, manifest: AgentManifest, options: ToolCatalogOptions): ToolCatalog;
@@ -1,5 +1,6 @@
1
1
  import { hasAccess, normalizeAccessEntry } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { FILTER_OPS, isRecord } from "./filter-ops.js";
3
+ import { assertCatalogMirrorsManifestDenyQns } from "./manifest-deny-qns.js";
3
4
  const MAX_TOOL_NAME_LENGTH = 64;
4
5
  /** Field types that declare `filterable` (per `packages/framework/src/engine/types/fields.ts`).
5
6
  * Mapped to the JSON-Schema type an LLM tool-call argument should use. `undefined` = field type
@@ -466,13 +467,15 @@ function addClientTools(manifest, mode, sink) {
466
467
  * to drop an entity CRUD tool, which no `agent.expose` on the manifest side can reach. It is
467
468
  * NOT read off the manifest on purpose — the manifest is prompt payload, and a list of the
468
469
  * handlers the model may not call has no business travelling to the provider. Pass the same
469
- * list to `buildAgentManifest` so the manifest stops describing what the catalog withholds. */
470
+ * list to `buildAgentManifest` so the manifest stops describing what the catalog withholds;
471
+ * a manifest built with `denyQns` that the catalog lacks throws. */
470
472
  export function buildToolCatalog(registry, manifest, options) {
471
473
  const sink = { tools: [], dispatchTable: new Map(), usedNames: new Set() };
472
474
  const roleFilter = { roles: manifest.builtForRoles };
473
475
  const locale = manifest.tenantSettings.locale;
474
476
  const entityByName = new Map(manifest.entities.map((entity) => [entity.name, entity]));
475
477
  const denyQns = new Set(options.denyQns ?? []);
478
+ assertCatalogMirrorsManifestDenyQns(manifest, denyQns);
476
479
  addRegistrySearchTools(registry, roleFilter, denyQns, sink);
477
480
  const { detailQnByEntity, listQnByEntity, entityListDetailQns } = collectEntityHandlerQns(registry, roleFilter, denyQns);
478
481
  addGetTools(registry, detailQnByEntity, entityByName, locale, sink);
@@ -34,7 +34,7 @@ function buildDateRange(from, to) {
34
34
  function escapeLikePattern(value) {
35
35
  return value.replace(/[\\%_]/g, (m) => `\\${m}`);
36
36
  }
37
- function buildAuditWhere(tenantId, p) {
37
+ function buildAuditWhere(tenantId, p, direction) {
38
38
  const where = { tenantId };
39
39
  if (p.aggregateType)
40
40
  where["aggregateType"] = p.aggregateType;
@@ -50,8 +50,10 @@ function buildAuditWhere(tenantId, p) {
50
50
  if (range)
51
51
  where["createdAt"] = range;
52
52
  const cursor = p.cursor ?? p.before;
53
+ // The cursor is an event id, so it only walks pages in id order: ascending
54
+ // pages continue past it, descending pages stop before it.
53
55
  if (cursor)
54
- where["id"] = { lt: BigInt(cursor) };
56
+ where["id"] = direction === "asc" ? { gt: BigInt(cursor) } : { lt: BigInt(cursor) };
55
57
  return where;
56
58
  }
57
59
  export const listQuery = defineQueryHandler({
@@ -81,15 +83,17 @@ export const listQuery = defineQueryHandler({
81
83
  handler: async (query, ctx) => {
82
84
  const p = query.payload;
83
85
  const scopeFilter = resolveAuditScopeFilter(query.user, p.scope);
86
+ const direction = p.sortDirection ?? "desc";
84
87
  const where = buildAuditWhere(scopeFilter.tenantId, {
85
88
  ...p,
86
89
  aggregateType: scopeFilter.aggregateType ?? p.aggregateType,
87
- });
90
+ }, direction);
88
91
  const rows = await selectMany(ctx.db, eventsTable, where, {
89
- orderBy: {
90
- col: query.payload.sort === "type" ? "type" : "createdAt",
91
- direction: query.payload.sortDirection ?? "desc",
92
- },
92
+ // `id` as tie-breaker keeps the order total, so pages never overlap.
93
+ orderBy: [
94
+ { col: p.sort === "type" ? "type" : "createdAt", direction },
95
+ { col: "id", direction },
96
+ ],
93
97
  limit: p.limit,
94
98
  });
95
99
  const serialised = rows.map((r) => ({
@@ -104,7 +108,8 @@ export const listQuery = defineQueryHandler({
104
108
  createdBy: r.createdBy,
105
109
  }));
106
110
  const last = serialised[serialised.length - 1];
107
- const nextId = serialised.length === p.limit && last ? last.id : null;
111
+ // An id cursor cannot resume a type-ordered listing, so that sort is one page.
112
+ const nextId = serialised.length === p.limit && last && p.sort !== "type" ? last.id : null;
108
113
  return {
109
114
  rows: serialised,
110
115
  nextCursor: nextId,
@@ -124,9 +124,8 @@ export function createSignupConfirmHandler(opts = {}) {
124
124
  displayName,
125
125
  tenantId,
126
126
  tenantKey,
127
- // Tenant-Display-Name als Default = Email. User wechselt das im
128
- // Settings-Screen. Konzept "Tenant" leakt nicht in die Signup-UI.
129
- tenantName: email,
127
+ // Generated slug as default name: no personal data (the email) in the tenant name.
128
+ tenantName: tenantKey,
130
129
  },
131
130
  // #1463: seedTenant's postSave hooks (tier-engine's auto-default-
132
131
  // tier, an app's auto-default-compliance) must fire on self-signup
@@ -3,6 +3,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -45,6 +47,7 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
45
47
  readonly type: "text";
46
48
  readonly description?: string;
47
49
  readonly maxLength?: number;
50
+ readonly minLength?: number;
48
51
  readonly required?: boolean;
49
52
  readonly searchable?: boolean;
50
53
  readonly sortable?: boolean;
@@ -98,6 +101,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
98
101
  readonly type: "text";
99
102
  readonly description?: string;
100
103
  readonly maxLength?: number;
104
+ readonly minLength?: number;
101
105
  readonly required?: boolean;
102
106
  readonly searchable?: boolean;
103
107
  readonly sortable?: boolean;
@@ -119,6 +123,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
119
123
  readonly type: "text";
120
124
  readonly description?: string;
121
125
  readonly maxLength?: number;
126
+ readonly minLength?: number;
122
127
  readonly required?: boolean;
123
128
  readonly searchable?: boolean;
124
129
  readonly sortable?: boolean;
@@ -140,6 +145,7 @@ export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").En
140
145
  readonly type: "text";
141
146
  readonly description?: string;
142
147
  readonly maxLength?: number;
148
+ readonly minLength?: number;
143
149
  readonly required?: boolean;
144
150
  readonly searchable?: boolean;
145
151
  readonly sortable?: boolean;
@@ -47,7 +47,7 @@ export const mfaEnableScreen = {
47
47
  confirm: {
48
48
  handler: AuthMfaHandlers.enableConfirm,
49
49
  fields: {
50
- code: { type: "text", required: true, maxLength: 6 },
50
+ code: { type: "text", required: true, minLength: 6, maxLength: 6 },
51
51
  },
52
52
  layout: { sections: [{ fields: ["code"] }] },
53
53
  carry: ["setupToken"],
@@ -57,7 +57,7 @@ export const mfaEnableScreen = {
57
57
  };
58
58
  // Either a 6-digit TOTP code or a 9-char recovery code proves possession —
59
59
  // same bounds as the disable/regenerate-recovery handler schemas.
60
- const possessionCodeField = { type: "text", required: true, maxLength: 9 };
60
+ const possessionCodeField = { type: "text", required: true, minLength: 6, maxLength: 9 };
61
61
  export const mfaDisableScreen = {
62
62
  id: MFA_DISABLE_SCREEN_ID,
63
63
  type: "actionForm",
@@ -80,6 +80,13 @@ export type OpenCheckoutOptions = {
80
80
  readonly baseUrl?: string;
81
81
  readonly catalog?: BillingPlanCatalog;
82
82
  readonly now: () => Temporal.Instant;
83
+ /** The caller already ran `assertBillingEnabled` under its own handler name
84
+ * (before a costly provider lookup), so the gate is not repeated here. */
85
+ readonly billingEnabledChecked?: boolean;
86
+ /** The caller already ran `assertNoActiveSubscription` and passes its result
87
+ * (null = tenant has none); mode:"subscription" skips the second read and
88
+ * the providerCustomerId check reuses it. */
89
+ readonly checkedSubscription?: Awaited<ReturnType<typeof getSubscriptionForTenant>>;
83
90
  };
84
91
  export type OpenCheckoutInput = {
85
92
  readonly providerName: string;
@@ -249,7 +249,9 @@ export async function assertCheckoutAllowed(ctx, options, input) {
249
249
  // Checked before the redirect-origin hardening for every mode — a
250
250
  // disabled provider must reject a mode:"payment" checkout just as much
251
251
  // as a mode:"subscription" one.
252
- await assertBillingEnabled(ctx, plugin, "create-checkout-session");
252
+ if (options.billingEnabledChecked !== true) {
253
+ await assertBillingEnabled(ctx, plugin, "create-checkout-session");
254
+ }
253
255
  assertRedirectOrigins([input.successUrl, input.cancelUrl], options.baseUrl);
254
256
  const mode = input.mode ?? "subscription";
255
257
  // Reused by the providerCustomerId check below to avoid a second lookup
@@ -257,7 +259,10 @@ export async function assertCheckoutAllowed(ctx, options, input) {
257
259
  let ownSubscription;
258
260
  if (mode === "subscription") {
259
261
  assertSubscriptionPriceAllowed(plugin, options.catalog, input);
260
- ownSubscription = await assertNoActiveSubscription(ctx, options.now());
262
+ ownSubscription =
263
+ options.checkedSubscription !== undefined
264
+ ? options.checkedSubscription
265
+ : await assertNoActiveSubscription(ctx, options.now());
261
266
  }
262
267
  else {
263
268
  assertOneOffPriceAllowed(plugin, input);
@@ -3,6 +3,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -45,6 +47,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
45
47
  readonly type: "text";
46
48
  readonly description?: string;
47
49
  readonly maxLength?: number;
50
+ readonly minLength?: number;
48
51
  readonly required?: boolean;
49
52
  readonly searchable?: boolean;
50
53
  readonly sortable?: boolean;
@@ -66,6 +69,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
66
69
  readonly type: "text";
67
70
  readonly description?: string;
68
71
  readonly maxLength?: number;
72
+ readonly minLength?: number;
69
73
  readonly required?: boolean;
70
74
  readonly searchable?: boolean;
71
75
  readonly sortable?: boolean;
@@ -87,6 +91,7 @@ export declare const subscriptionEntity: import("@cosmicdrift/kumiko-types/field
87
91
  readonly type: "text";
88
92
  readonly description?: string;
89
93
  readonly maxLength?: number;
94
+ readonly minLength?: number;
90
95
  readonly required?: boolean;
91
96
  readonly searchable?: boolean;
92
97
  readonly sortable?: boolean;
@@ -141,6 +146,7 @@ export declare const paymentEntity: import("@cosmicdrift/kumiko-types/fields").E
141
146
  readonly type: "text";
142
147
  readonly description?: string;
143
148
  readonly maxLength?: number;
149
+ readonly minLength?: number;
144
150
  readonly required?: boolean;
145
151
  readonly searchable?: boolean;
146
152
  readonly sortable?: boolean;
@@ -162,6 +168,7 @@ export declare const paymentEntity: import("@cosmicdrift/kumiko-types/fields").E
162
168
  readonly type: "text";
163
169
  readonly description?: string;
164
170
  readonly maxLength?: number;
171
+ readonly minLength?: number;
165
172
  readonly required?: boolean;
166
173
  readonly searchable?: boolean;
167
174
  readonly sortable?: boolean;
@@ -183,6 +190,7 @@ export declare const paymentEntity: import("@cosmicdrift/kumiko-types/fields").E
183
190
  readonly type: "text";
184
191
  readonly description?: string;
185
192
  readonly maxLength?: number;
193
+ readonly minLength?: number;
186
194
  readonly required?: boolean;
187
195
  readonly searchable?: boolean;
188
196
  readonly sortable?: boolean;
@@ -40,7 +40,13 @@ export function createStartPlanCheckoutHandler(options, catalog) {
40
40
  ? await prepareConsent(ctx, consumerProtection, payload.consent, "subscription")
41
41
  : undefined;
42
42
  const baseUrl = options.baseUrl ?? "";
43
- const result = await openCheckout(ctx, { baseUrl: options.baseUrl, catalog, now: options.now }, {
43
+ const result = await openCheckout(ctx, {
44
+ baseUrl: options.baseUrl,
45
+ catalog,
46
+ now: options.now,
47
+ billingEnabledChecked: true,
48
+ checkedSubscription: existing,
49
+ }, {
44
50
  providerName,
45
51
  priceId: resolved.priceId,
46
52
  successUrl: joinBaseUrl(baseUrl, catalog.successPath),
@@ -3,6 +3,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -45,6 +47,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
45
47
  readonly type: "text";
46
48
  readonly description?: string;
47
49
  readonly maxLength?: number;
50
+ readonly minLength?: number;
48
51
  readonly required?: boolean;
49
52
  readonly searchable?: boolean;
50
53
  readonly sortable?: boolean;
@@ -66,6 +69,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
66
69
  readonly type: "text";
67
70
  readonly description?: string;
68
71
  readonly maxLength?: number;
72
+ readonly minLength?: number;
69
73
  readonly required?: boolean;
70
74
  readonly searchable?: boolean;
71
75
  readonly sortable?: boolean;
@@ -87,6 +91,7 @@ export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-f
87
91
  readonly type: "text";
88
92
  readonly description?: string;
89
93
  readonly maxLength?: number;
94
+ readonly minLength?: number;
90
95
  readonly required?: boolean;
91
96
  readonly searchable?: boolean;
92
97
  readonly sortable?: boolean;
@@ -140,6 +145,7 @@ export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framew
140
145
  readonly type: "text";
141
146
  readonly description?: string;
142
147
  readonly maxLength?: number;
148
+ readonly minLength?: number;
143
149
  readonly required?: boolean;
144
150
  readonly searchable?: boolean;
145
151
  readonly sortable?: boolean;
@@ -161,6 +167,7 @@ export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framew
161
167
  readonly type: "text";
162
168
  readonly description?: string;
163
169
  readonly maxLength?: number;
170
+ readonly minLength?: number;
164
171
  readonly required?: boolean;
165
172
  readonly searchable?: boolean;
166
173
  readonly sortable?: boolean;
@@ -182,6 +189,7 @@ export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framew
182
189
  readonly type: "text";
183
190
  readonly description?: string;
184
191
  readonly maxLength?: number;
192
+ readonly minLength?: number;
185
193
  readonly required?: boolean;
186
194
  readonly searchable?: boolean;
187
195
  readonly sortable?: boolean;
@@ -3,6 +3,7 @@ export declare const capCounterEntity: import("@cosmicdrift/kumiko-types/fields"
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -1,4 +1,4 @@
1
- import { access } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { access, BUILT_IN_MEMBERSHIP_ROLES } from "@cosmicdrift/kumiko-framework/engine";
2
2
  // Usage numbers are an operator concern by default; wider visibility is an
3
3
  // explicit `usageVisibleTo` opt-in on createCapOverviewFeature. The my-caps
4
4
  // screen and the query that fills its cards must carry the SAME rule, or the
@@ -7,14 +7,7 @@ export const DEFAULT_CAP_USAGE_ROLES = access.roles(...access.admin);
7
7
  // Roles the engine itself knows (role-assignment.ts ranks). App-declared roles
8
8
  // (assignableRole) only exist once the registry is composed, which is after
9
9
  // this feature is defined, so they cannot be validated here.
10
- const BUILT_IN_USAGE_VISIBLE_ROLES = new Set([
11
- "User",
12
- "Member",
13
- "Editor",
14
- "Admin",
15
- "TenantAdmin",
16
- "SystemAdmin",
17
- ]);
10
+ const BUILT_IN_USAGE_VISIBLE_ROLES = new Set(BUILT_IN_MEMBERSHIP_ROLES);
18
11
  export function resolveCapUsageRoles(usageVisibleTo) {
19
12
  if (usageVisibleTo === undefined)
20
13
  return DEFAULT_CAP_USAGE_ROLES;
@@ -4,7 +4,7 @@ import { definePagedQueryHandler, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-fr
4
4
  import { InternalError, ValidationError } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import * as z from "zod";
6
6
  import { subscriptionsProjectionTable } from "../../billing-foundation/index.js";
7
- import { tenantTable } from "../../tenant/index.js";
7
+ import { decryptTenantNames, tenantTable } from "../../tenant/index.js";
8
8
  import { tierAssignmentEntity } from "../../tier-engine/index.js";
9
9
  import { capFieldName } from "../constants.js";
10
10
  import { computeFraction } from "../usage-math.js";
@@ -115,7 +115,7 @@ export function createTenantCapsListQuery(caps, listCaps) {
115
115
  });
116
116
  }
117
117
  const db = ctx.systemDb.acknowledgeCrossTenant("cap-overview:tenant-caps:list — SystemAdmin platform-wide tenant overview");
118
- const tenants = await selectMany(db, tenantTable, {});
118
+ const tenants = await decryptTenantNames(await selectMany(db, tenantTable, {}), "cap-overview:tenant-caps:list");
119
119
  const assignments = await selectMany(db, tierAssignmentTable, {});
120
120
  const subscriptions = await selectMany(db, subscriptionsProjectionTable, {});
121
121
  const assignmentByTenant = new Map(assignments.map((row) => [row.tenantId, row]));
@@ -2,7 +2,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
- import { tenantTable } from "../../tenant/index.js";
5
+ import { decryptTenantNames, tenantTable } from "../../tenant/index.js";
6
6
  export const tenantOptionsQuery = defineQueryHandler({
7
7
  name: "tenant-options",
8
8
  description: "Returns all tenants as id/name option pairs sorted by name; use it to populate the tenant picker of a platform-wide overview.",
@@ -15,9 +15,8 @@ export const tenantOptionsQuery = defineQueryHandler({
15
15
  });
16
16
  }
17
17
  const db = ctx.systemDb.acknowledgeCrossTenant("cap-overview:tenant-options — SystemAdmin dashboard tenant-filter options");
18
- const tenants = await selectMany(db, tenantTable, {}, {
19
- orderBy: { col: "name", direction: "asc" },
20
- });
21
- return { rows: tenants.map((tenant) => ({ value: tenant.id, label: tenant.name })) };
18
+ const tenants = await decryptTenantNames(await selectMany(db, tenantTable, {}), "cap-overview:tenant-options");
19
+ const sorted = [...tenants].sort((a, b) => a.name.localeCompare(b.name));
20
+ return { rows: sorted.map((tenant) => ({ value: tenant.id, label: tenant.name })) };
22
21
  },
23
22
  });
@@ -11,10 +11,10 @@ export declare const tenantComplianceProfileEntity: import("@cosmicdrift/kumiko-
11
11
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
12
12
  readonly display?: "radio" | "dropdown";
13
13
  readonly optionTones?: {
14
- readonly "de-hr-dsgvo-hgb"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
15
- readonly "eu-dsgvo"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
16
- readonly "minimal-no-region"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
17
- readonly "swiss-dsg"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
14
+ readonly "de-hr-dsgvo-hgb"?: "bad" | "neutral" | "ok" | "warn" | undefined;
15
+ readonly "eu-dsgvo"?: "bad" | "neutral" | "ok" | "warn" | undefined;
16
+ readonly "minimal-no-region"?: "bad" | "neutral" | "ok" | "warn" | undefined;
17
+ readonly "swiss-dsg"?: "bad" | "neutral" | "ok" | "warn" | undefined;
18
18
  } | undefined;
19
19
  readonly optionsQuery?: string;
20
20
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -52,10 +52,10 @@ export declare const tenantComplianceProfileTable: import("@cosmicdrift/kumiko-f
52
52
  readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
53
53
  readonly display?: "radio" | "dropdown";
54
54
  readonly optionTones?: {
55
- readonly "de-hr-dsgvo-hgb"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
56
- readonly "eu-dsgvo"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
57
- readonly "minimal-no-region"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
58
- readonly "swiss-dsg"?: import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined;
55
+ readonly "de-hr-dsgvo-hgb"?: "bad" | "neutral" | "ok" | "warn" | undefined;
56
+ readonly "eu-dsgvo"?: "bad" | "neutral" | "ok" | "warn" | undefined;
57
+ readonly "minimal-no-region"?: "bad" | "neutral" | "ok" | "warn" | undefined;
58
+ readonly "swiss-dsg"?: "bad" | "neutral" | "ok" | "warn" | undefined;
59
59
  } | undefined;
60
60
  readonly optionsQuery?: string;
61
61
  readonly optionsQueryPayload?: import("@cosmicdrift/kumiko-types/fields").OptionsQueryPayload;
@@ -3,7 +3,7 @@ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
5
  import { tenantComplianceProfileTable } from "../../compliance-profiles/index.js";
6
- import { tenantTable } from "../../tenant/index.js";
6
+ import { decryptTenantNames, tenantTable } from "../../tenant/index.js";
7
7
  // SystemAdmin platform-wide counterpart to needs-profile (#2089).
8
8
  //
9
9
  // #2084 stopped needs-profile from nagging a TenantAdmin who can no longer
@@ -28,9 +28,7 @@ export const tenantsMissingProfileQuery = defineQueryHandler({
28
28
  });
29
29
  }
30
30
  const db = ctx.systemDb.acknowledgeCrossTenant("platform operator scans every tenant for a missing compliance-profile selection");
31
- const tenants = await db.selectMany(tenantTable, {
32
- isEnabled: true,
33
- });
31
+ const tenants = await decryptTenantNames(await db.selectMany(tenantTable, { isEnabled: true }), "compliance-profiles-ops:tenants-missing-profile");
34
32
  const profileRows = await db.selectMany(tenantComplianceProfileTable, {});
35
33
  const tenantIdsWithProfile = new Set(profileRows.map((row) => row.tenantId));
36
34
  return {
@@ -1,6 +1,8 @@
1
1
  import { type ConfigScope, type ConfigValueSource } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import * as z from "zod";
3
- export declare const valuesQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"values", z.ZodObject<{}, z.core.$strip>, Record<string, {
3
+ export declare const valuesQuery: import("@cosmicdrift/kumiko-types/define-handler").QueryHandlerDefinition<"values", z.ZodObject<{
4
+ keys: z.ZodOptional<z.ZodArray<z.ZodString>>;
5
+ }, z.core.$strip>, Record<string, {
4
6
  value: string | number | boolean | undefined;
5
7
  scope: ConfigScope;
6
8
  source: ConfigValueSource;
@@ -5,8 +5,10 @@ import { MASKED, redactInheritedCascade, shouldRedactInherited } from "../read-r
5
5
  import { hasConfigAccess } from "../write-helpers.js";
6
6
  export const valuesQuery = defineQueryHandler({
7
7
  name: "values",
8
- description: "Returns the effective value, scope and winning source for every config key the caller may read, with encrypted and secret-backed values masked; use it to inspect the current settings.",
9
- schema: z.object({}),
8
+ description: "Returns the effective value, scope and winning source for every config key the caller may read (optionally narrowed to `keys`), with encrypted and secret-backed values masked; use it to inspect the current settings.",
9
+ schema: z.object({
10
+ keys: z.array(z.string()).optional(),
11
+ }),
10
12
  // Per-key read access enforced via hasConfigAccess inside the handler.
11
13
  access: {
12
14
  openToAll: {
@@ -26,7 +28,10 @@ export const valuesQuery = defineQueryHandler({
26
28
  const allKeys = registry.getAllConfigKeys();
27
29
  const keyDefs = new Map();
28
30
  const filteredKeys = [];
31
+ const requestedKeys = query.payload.keys === undefined ? undefined : new Set(query.payload.keys);
29
32
  for (const [qualifiedKey, keyDef] of allKeys) {
33
+ if (requestedKeys !== undefined && !requestedKeys.has(qualifiedKey))
34
+ continue;
30
35
  if (!hasConfigAccess(keyDef.access.read, query.user.roles))
31
36
  continue;
32
37
  keyDefs.set(qualifiedKey, keyDef);
@@ -3,6 +3,7 @@ export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -45,6 +47,7 @@ export declare const configValueEntity: import("@cosmicdrift/kumiko-types/fields
45
47
  readonly type: "text";
46
48
  readonly description?: string;
47
49
  readonly maxLength?: number;
50
+ readonly minLength?: number;
48
51
  readonly required?: boolean;
49
52
  readonly searchable?: boolean;
50
53
  readonly sortable?: boolean;
@@ -68,6 +71,7 @@ export declare const configValuesTable: import("@cosmicdrift/kumiko-framework/db
68
71
  readonly type: "text";
69
72
  readonly description?: string;
70
73
  readonly maxLength?: number;
74
+ readonly minLength?: number;
71
75
  readonly required?: boolean;
72
76
  readonly searchable?: boolean;
73
77
  readonly sortable?: boolean;
@@ -89,6 +93,7 @@ export declare const configValuesTable: import("@cosmicdrift/kumiko-framework/db
89
93
  readonly type: "text";
90
94
  readonly description?: string;
91
95
  readonly maxLength?: number;
96
+ readonly minLength?: number;
92
97
  readonly required?: boolean;
93
98
  readonly searchable?: boolean;
94
99
  readonly sortable?: boolean;
@@ -110,6 +115,7 @@ export declare const configValuesTable: import("@cosmicdrift/kumiko-framework/db
110
115
  readonly type: "text";
111
116
  readonly description?: string;
112
117
  readonly maxLength?: number;
118
+ readonly minLength?: number;
113
119
  readonly required?: boolean;
114
120
  readonly searchable?: boolean;
115
121
  readonly sortable?: boolean;