@cosmicdrift/kumiko-bundled-features 0.236.0 → 0.237.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 (205) hide show
  1. package/package.json +9 -9
  2. package/src/admin-shell/feature.ts +4 -0
  3. package/src/agent-tools/__tests__/agent-doc-lint.test.ts +53 -1
  4. package/src/audit/feature.ts +4 -0
  5. package/src/audit/handlers/details.query.ts +2 -0
  6. package/src/audit/handlers/list.query.ts +2 -0
  7. package/src/auth-email-password/handlers/change-password.write.ts +2 -0
  8. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +1 -0
  9. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -0
  10. package/src/auth-email-password/handlers/invite-accept.write.ts +1 -0
  11. package/src/auth-email-password/handlers/invite-create.write.ts +2 -0
  12. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +1 -0
  13. package/src/auth-email-password/handlers/login.write.ts +2 -0
  14. package/src/auth-email-password/handlers/logout.write.ts +2 -0
  15. package/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -0
  16. package/src/auth-email-password/handlers/request-email-verification.write.ts +2 -0
  17. package/src/auth-email-password/handlers/request-password-reset.write.ts +2 -0
  18. package/src/auth-email-password/handlers/reset-password.write.ts +1 -0
  19. package/src/auth-email-password/handlers/self-registration-status.query.ts +2 -0
  20. package/src/auth-email-password/handlers/signup-confirm.write.ts +1 -0
  21. package/src/auth-email-password/handlers/signup-request.write.ts +2 -0
  22. package/src/auth-email-password/handlers/token-request-handler.ts +4 -0
  23. package/src/auth-email-password/handlers/verify-email.write.ts +1 -0
  24. package/src/auth-mfa/feature.ts +2 -0
  25. package/src/auth-mfa/handlers/disable.write.ts +3 -0
  26. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +2 -0
  27. package/src/auth-mfa/handlers/enable-confirm.write.ts +2 -0
  28. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +2 -0
  29. package/src/auth-mfa/handlers/enable-start.write.ts +2 -0
  30. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +3 -0
  31. package/src/auth-mfa/handlers/status.query.ts +2 -0
  32. package/src/auth-mfa/handlers/verify.write.ts +2 -0
  33. package/src/auth-mfa/schema/user-mfa.ts +2 -0
  34. package/src/billing-foundation/handlers/create-checkout-session.write.ts +2 -0
  35. package/src/billing-foundation/handlers/create-portal-session.write.ts +2 -0
  36. package/src/billing-foundation/handlers/list-subscriptions.query.ts +2 -0
  37. package/src/billing-foundation/handlers/process-event.write.ts +1 -0
  38. package/src/cap-counter/entity.ts +2 -0
  39. package/src/cap-counter/feature.ts +7 -1
  40. package/src/cap-counter/handlers/get-counter.query.ts +2 -0
  41. package/src/cap-counter/handlers/increment-rolling.write.ts +1 -0
  42. package/src/cap-counter/handlers/increment.write.ts +1 -0
  43. package/src/cap-counter/handlers/mark-soft-warned.write.ts +1 -0
  44. package/src/cap-overview/handlers/caps-usage.query.ts +2 -0
  45. package/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -0
  46. package/src/cap-overview/handlers/tenant-options.query.ts +2 -0
  47. package/src/channel-in-app/handlers/inbox.query.ts +2 -0
  48. package/src/channel-in-app/handlers/mark-all-read.write.ts +2 -0
  49. package/src/channel-in-app/handlers/mark-read.write.ts +2 -0
  50. package/src/channel-in-app/handlers/unread-count.query.ts +2 -0
  51. package/src/compliance-profiles/handlers/for-tenant.query.ts +2 -0
  52. package/src/compliance-profiles/handlers/list-profiles.query.ts +2 -0
  53. package/src/compliance-profiles/handlers/needs-profile.query.ts +2 -0
  54. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -0
  55. package/src/compliance-profiles/handlers/sub-processors.query.ts +2 -0
  56. package/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +2 -0
  57. package/src/config/handlers/cascade.query.ts +2 -0
  58. package/src/config/handlers/readiness.query.ts +2 -0
  59. package/src/config/handlers/reset.write.ts +3 -0
  60. package/src/config/handlers/schema.query.ts +2 -0
  61. package/src/config/handlers/set.write.ts +2 -0
  62. package/src/config/handlers/values.query.ts +2 -0
  63. package/src/crypto-shredding/handlers/forget-subject.write.ts +3 -0
  64. package/src/custom-fields/entity.ts +2 -0
  65. package/src/custom-fields/feature.ts +2 -0
  66. package/src/custom-fields/handlers/clear-custom-field.write.ts +2 -0
  67. package/src/custom-fields/handlers/define-system-field.write.ts +2 -0
  68. package/src/custom-fields/handlers/define-tenant-field.write.ts +2 -0
  69. package/src/custom-fields/handlers/delete-system-field.write.ts +3 -0
  70. package/src/custom-fields/handlers/delete-tenant-field.write.ts +3 -0
  71. package/src/custom-fields/handlers/set-custom-field.write.ts +2 -0
  72. package/src/custom-fields/handlers/update-tenant-field.write.ts +2 -0
  73. package/src/data-retention/handlers/policy-for.query.ts +2 -0
  74. package/src/delivery/handlers/log.query.ts +2 -0
  75. package/src/delivery/handlers/preferences.query.ts +2 -0
  76. package/src/delivery/handlers/set-preference.write.ts +2 -0
  77. package/src/feature-toggles/handlers/list.query.ts +2 -0
  78. package/src/feature-toggles/handlers/registered.query.ts +2 -0
  79. package/src/feature-toggles/handlers/set.write.ts +2 -0
  80. package/src/file-derivatives/handlers/public-variant.query.ts +1 -0
  81. package/src/folders/entity.ts +4 -0
  82. package/src/folders/feature.ts +35 -5
  83. package/src/folders/handlers/clear-folder.write.ts +2 -0
  84. package/src/folders/handlers/delete-folder.write.ts +3 -0
  85. package/src/folders/handlers/set-folder.write.ts +2 -0
  86. package/src/form-draft/handlers/discard.write.ts +3 -0
  87. package/src/form-draft/handlers/get.query.ts +2 -0
  88. package/src/form-draft/handlers/list.query.ts +2 -0
  89. package/src/form-draft/handlers/save.write.ts +2 -0
  90. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +2 -0
  91. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +3 -0
  92. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -0
  93. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +2 -0
  94. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +2 -0
  95. package/src/inbound-mail-foundation/handlers/update-account.write.ts +2 -0
  96. package/src/jobs/feature.ts +4 -0
  97. package/src/jobs/handlers/catalog.query.ts +2 -0
  98. package/src/jobs/handlers/detail.query.ts +2 -0
  99. package/src/jobs/handlers/list.query.ts +2 -0
  100. package/src/jobs/handlers/retry.write.ts +2 -0
  101. package/src/jobs/handlers/trigger.write.ts +2 -0
  102. package/src/ledger/entity.ts +6 -0
  103. package/src/ledger/feature.ts +70 -10
  104. package/src/ledger/handlers/confirm-schedule-period.write.ts +2 -0
  105. package/src/ledger/handlers/create-transaction.write.ts +2 -0
  106. package/src/ledger/handlers/reports.query.ts +6 -0
  107. package/src/ledger/handlers/reverse-transaction.write.ts +2 -0
  108. package/src/managed-pages/feature.ts +4 -23
  109. package/src/managed-pages/handlers/branding.query.ts +2 -0
  110. package/src/managed-pages/handlers/by-slug.query.ts +2 -0
  111. package/src/managed-pages/handlers/by-tenant-published.query.ts +2 -0
  112. package/src/managed-pages/handlers/page-crud.ts +49 -0
  113. package/src/managed-pages/handlers/set.write.ts +2 -0
  114. package/src/managed-pages/table.ts +2 -0
  115. package/src/notes-history/entity.ts +2 -0
  116. package/src/notes-history/feature.ts +7 -1
  117. package/src/notes-history/handlers/add-note.write.ts +2 -0
  118. package/src/personal-access-tokens/feature.ts +2 -0
  119. package/src/personal-access-tokens/handlers/available-scopes.query.ts +2 -0
  120. package/src/personal-access-tokens/handlers/create.write.ts +2 -0
  121. package/src/personal-access-tokens/handlers/list.query.ts +2 -0
  122. package/src/personal-access-tokens/handlers/revoke.write.ts +3 -0
  123. package/src/rate-limiting/handlers/status.query.ts +2 -0
  124. package/src/readiness/handlers/status.query.ts +2 -0
  125. package/src/secrets/handlers/delete.write.ts +3 -0
  126. package/src/secrets/handlers/list.query.ts +2 -0
  127. package/src/secrets/handlers/set.write.ts +2 -0
  128. package/src/secrets/table.ts +2 -0
  129. package/src/seo/handlers/seo-config.query.ts +2 -0
  130. package/src/sessions/handlers/detail.query.ts +2 -0
  131. package/src/sessions/handlers/list.query.ts +2 -0
  132. package/src/sessions/handlers/mine.query.ts +2 -0
  133. package/src/sessions/handlers/revoke-all-for-user.write.ts +3 -0
  134. package/src/sessions/handlers/revoke-all-others.write.ts +3 -0
  135. package/src/sessions/handlers/revoke.write.ts +3 -0
  136. package/src/tags/entity.ts +4 -0
  137. package/src/tags/feature.ts +39 -6
  138. package/src/tags/handlers/assign-tag.write.ts +2 -0
  139. package/src/tags/handlers/create-tag.write.ts +2 -0
  140. package/src/tags/handlers/delete-tag.write.ts +3 -0
  141. package/src/tags/handlers/remove-tag.write.ts +2 -0
  142. package/src/tags/handlers/update-tag.write.ts +2 -0
  143. package/src/template-resolver/handlers/by-slug.query.ts +2 -0
  144. package/src/template-resolver/handlers/by-tenant.query.ts +2 -0
  145. package/src/template-resolver/handlers/collection-item.query.ts +4 -0
  146. package/src/template-resolver/handlers/collection-list.query.ts +4 -0
  147. package/src/template-resolver/handlers/collection-set.write.ts +4 -0
  148. package/src/template-resolver/handlers/find-by-id.query.ts +2 -0
  149. package/src/template-resolver/handlers/list.query.ts +2 -0
  150. package/src/template-resolver/handlers/set.write.ts +2 -0
  151. package/src/template-resolver/handlers/toggle-status.write.ts +12 -3
  152. package/src/template-resolver/handlers/upsert-system.write.ts +2 -0
  153. package/src/template-resolver/handlers/upsert-tenant.write.ts +2 -0
  154. package/src/tenant/feature.ts +15 -3
  155. package/src/tenant/handlers/active-tenant-ids.query.ts +2 -0
  156. package/src/tenant/handlers/add-member.write.ts +2 -0
  157. package/src/tenant/handlers/cancel-invitation.write.ts +2 -0
  158. package/src/tenant/handlers/create.write.ts +2 -0
  159. package/src/tenant/handlers/invitations.query.ts +2 -0
  160. package/src/tenant/handlers/list.query.ts +2 -0
  161. package/src/tenant/handlers/me.query.ts +2 -0
  162. package/src/tenant/handlers/members.query.ts +2 -0
  163. package/src/tenant/handlers/memberships.query.ts +2 -0
  164. package/src/tenant/handlers/remove-member.write.ts +3 -0
  165. package/src/tenant/handlers/resolve-user-ids.query.ts +1 -0
  166. package/src/tenant/handlers/team-list.query.ts +2 -0
  167. package/src/tenant/handlers/toggle-enabled.write.ts +3 -0
  168. package/src/tenant/handlers/update-member-roles.write.ts +2 -0
  169. package/src/tenant/handlers/update.write.ts +2 -0
  170. package/src/tenant/schema/tenant.ts +2 -0
  171. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -0
  172. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +3 -0
  173. package/src/tier-engine/entity.ts +2 -0
  174. package/src/tier-engine/feature.ts +25 -3
  175. package/src/tier-engine/handlers/active-tier.query.ts +2 -0
  176. package/src/tier-engine/handlers/get-tenant-tier.query.ts +2 -0
  177. package/src/tier-engine/handlers/set-tenant-tier.write.ts +2 -0
  178. package/src/user/handlers/create.write.ts +2 -0
  179. package/src/user/handlers/detail.query.ts +2 -0
  180. package/src/user/handlers/find-for-auth.query.ts +1 -0
  181. package/src/user/handlers/list.query.ts +2 -0
  182. package/src/user/handlers/me.query.ts +2 -0
  183. package/src/user/handlers/update.write.ts +2 -0
  184. package/src/user/schema/user.ts +2 -0
  185. package/src/user-data-rights/feature.ts +2 -0
  186. package/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -0
  187. package/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -0
  188. package/src/user-data-rights/handlers/download-attempt-list.query.ts +6 -1
  189. package/src/user-data-rights/handlers/download-by-job.query.ts +2 -0
  190. package/src/user-data-rights/handlers/download-by-token.query.ts +1 -0
  191. package/src/user-data-rights/handlers/export-job-detail.query.ts +2 -0
  192. package/src/user-data-rights/handlers/export-job-list.query.ts +2 -0
  193. package/src/user-data-rights/handlers/export-status.query.ts +2 -0
  194. package/src/user-data-rights/handlers/lift-restriction.write.ts +2 -0
  195. package/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -0
  196. package/src/user-data-rights/handlers/my-audit-log.query.ts +2 -0
  197. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +2 -0
  198. package/src/user-data-rights/handlers/request-deletion.write.ts +3 -0
  199. package/src/user-data-rights/handlers/request-export.write.ts +2 -0
  200. package/src/user-data-rights/handlers/restrict-account.write.ts +3 -0
  201. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +1 -0
  202. package/src/user-data-rights/schema/download-attempt.ts +2 -0
  203. package/src/user-data-rights/schema/export-job.ts +2 -0
  204. package/src/user-profile/handlers/change-email.write.ts +2 -0
  205. package/src/workflow-runner/handlers/resume-run.write.ts +1 -0
@@ -24,6 +24,9 @@ export function createDeleteTagHandler(
24
24
  name,
25
25
  schema: deleteTagPayloadSchema,
26
26
  access,
27
+ description:
28
+ "Deletes a catalog tag and cascades a detach over every assignment still carrying it, so the tag disappears from all entities at once; this is the legacy name the TagManager dispatches.",
29
+ agent: { risk: "high" },
27
30
  handler: async (event, ctx) => {
28
31
  const payload = event.payload as DeleteTagPayload; // @cast-boundary engine-payload
29
32
 
@@ -12,6 +12,8 @@ export function createRemoveTagHandler(access: AccessRule = DEFAULT_TAG_ACCESS):
12
12
  name: "remove-tag",
13
13
  schema: removeTagPayloadSchema,
14
14
  access,
15
+ description:
16
+ "Detaches one catalog tag from one host entity, reporting success when it was not attached; use it to untag a single record while the tag itself stays in the catalog.",
15
17
  handler: async (event, ctx) => {
16
18
  const payload = event.payload as RemoveTagPayload; // @cast-boundary engine-payload
17
19
  const id = tagAssignmentAggregateId(
@@ -16,6 +16,8 @@ export function createUpdateTagHandler(
16
16
  name,
17
17
  schema: updateTagPayloadSchema,
18
18
  access,
19
+ description:
20
+ "Renames, recolours or re-scopes a catalog tag from a flat optimistic-locked payload, leaving omitted fields untouched; this is the legacy name the TagManager dispatches, unlike the entity-edit alias that takes a changes envelope.",
19
21
  handler: async (event, ctx) => {
20
22
  const payload = event.payload as UpdateTagPayload; // @cast-boundary engine-payload
21
23
  const changes: Record<string, unknown> = {};
@@ -28,6 +28,8 @@ export const bySlugQuery = defineQueryHandler({
28
28
  tenantIdOverride: z.string().min(1).optional(),
29
29
  }),
30
30
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
31
+ description:
32
+ "Reads one text-block of a tenant by slug and locale together with its body and format; it is pinned to the text-block kind so mail templates and AI prompts in the same table stay unreachable through this anonymous-capable path.",
31
33
  handler: async (query, ctx) => {
32
34
  const override = query.payload.tenantIdOverride;
33
35
  const overrideDenied = crossTenantOverrideDenied(
@@ -33,6 +33,8 @@ export const byTenantQuery = defineQueryHandler({
33
33
  tenantIdOverride: z.string().min(1).optional(),
34
34
  }),
35
35
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
36
+ description:
37
+ "Lists every text-block of a tenant with slug, locale, title and body so a public content tree can be rendered in one call; use it for the whole sidebar, and by-slug when only one block is needed.",
36
38
  handler: async (query, ctx) => {
37
39
  const override = query.payload.tenantIdOverride;
38
40
  const overrideDenied = crossTenantOverrideDenied(
@@ -25,6 +25,10 @@ export function makeCollectionItemQuery(collection: ContentCollectionDefinition)
25
25
  tenantIdOverride: z.string().min(1).optional(),
26
26
  }),
27
27
  access: collection.access ?? DEFAULT_COLLECTION_ACCESS,
28
+ description:
29
+ collection.ownership === "user"
30
+ ? `Reads one of the caller's own "${collection.id}" entries by slug and locale, returning null when that user has none; use it to open a single entry in the editor rather than to list the collection.`
31
+ : `Reads one tenant-wide "${collection.id}" entry by slug and locale, returning null when it does not exist; use it to open a single entry in the editor rather than to list the collection.`,
28
32
  handler: async (query, ctx) => {
29
33
  const override = query.payload.tenantIdOverride;
30
34
  const overrideDenied = crossTenantOverrideDenied(
@@ -30,6 +30,10 @@ export function makeCollectionListQuery(collection: ContentCollectionDefinition)
30
30
  tenantIdOverride: z.string().min(1).optional(),
31
31
  }),
32
32
  access: collection.access ?? DEFAULT_COLLECTION_ACCESS,
33
+ description:
34
+ collection.ownership === "user"
35
+ ? `Lists the caller's own entries in the "${collection.id}" content collection (${collection.kind} content) with slug, locale, title and body; entries other users keep in this collection are never returned.`
36
+ : `Lists the tenant-wide entries of the "${collection.id}" content collection (${collection.kind} content) with slug, locale, title and body; every caller who may reach this collection sees the same shared set.`,
33
37
  handler: async (query, ctx) => {
34
38
  const override = query.payload.tenantIdOverride;
35
39
  const overrideDenied = crossTenantOverrideDenied(
@@ -36,6 +36,10 @@ export function makeCollectionSetWrite(collection: ContentCollectionDefinition)
36
36
  tenantIdOverride: z.string().min(1).optional(),
37
37
  }),
38
38
  access: collection.access ?? DEFAULT_COLLECTION_ACCESS,
39
+ description:
40
+ collection.ownership === "user"
41
+ ? `Creates or overwrites the caller's own "${collection.id}" entry at a slug and locale, live on save with no draft stage; it always writes the acting user's entry, never another user's.`
42
+ : `Creates or overwrites the tenant-wide "${collection.id}" entry at a slug and locale, live on save with no draft stage; the change is visible to everyone who may reach this shared collection.`,
39
43
  handler: async (event, ctx) => {
40
44
  const db = ctx.db;
41
45
  const override = event.payload.tenantIdOverride;
@@ -12,6 +12,8 @@ export const findByIdQuery = defineQueryHandler({
12
12
  name: "find-by-id",
13
13
  schema: z.object({ id: z.string().min(1) }),
14
14
  access: { roles: ["TenantAdmin", "SystemAdmin", "User"] },
15
+ description:
16
+ "Reads one template resource by row id with its content, variable schema, scope and draft/archived status, but only when it belongs to the caller's tenant or is a system default; use it to load a template into the admin editor.",
15
17
  handler: async (query, ctx) => {
16
18
  const row = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
17
19
  id: query.payload.id,
@@ -16,6 +16,8 @@ export const listQuery = defineQueryHandler({
16
16
  includeSystem: z.boolean().default(true),
17
17
  }),
18
18
  access: { roles: ["TenantAdmin", "SystemAdmin", "User"] },
19
+ description:
20
+ "Lists template resources of the caller's tenant alongside the system defaults as summaries without content, filterable by kind, locale and status; use it to browse the catalogue, then find-by-id to read one row.",
19
21
  handler: async (query, ctx) => {
20
22
  const where: Record<string, unknown> = {};
21
23
 
@@ -47,6 +47,8 @@ export const setWrite = defineWriteHandler({
47
47
  // SystemAdmin is a global role, TenantAdmin is per tenant-membership — both
48
48
  // paths are needed explicitly, otherwise nobody can set SYSTEM_TENANT texts.
49
49
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
50
+ description:
51
+ "Creates or overwrites one content-tree resource addressed by slug, kind and locale, carrying a title and folder and going live on save with no draft stage; a SystemAdmin may target the system tenant, where shared legal and marketing copy lives.",
50
52
  handler: async (event, ctx) => {
51
53
  const db = ctx.db;
52
54
  const override = event.payload.tenantIdOverride;
@@ -8,11 +8,12 @@ import { executor } from "./shared";
8
8
 
9
9
  type TemplateStatus = "active" | "archived";
10
10
 
11
- function createStatusUpdateHandler(name: string, status: TemplateStatus) {
11
+ function createStatusUpdateHandler(name: string, status: TemplateStatus, description: string) {
12
12
  return defineWriteHandler({
13
13
  name,
14
14
  schema: z.object({ id: z.string().min(1) }),
15
15
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
16
+ description,
16
17
  handler: async (event, ctx) => {
17
18
  // ctx.db is tenant-scoped: a foreign tenant's id reads as absent → NotFound.
18
19
  // Cross-tenant toggling needs SystemAdmin with tenantIdOverride.
@@ -33,5 +34,13 @@ function createStatusUpdateHandler(name: string, status: TemplateStatus) {
33
34
  });
34
35
  }
35
36
 
36
- export const archiveWrite = createStatusUpdateHandler("archive", "archived");
37
- export const publishWrite = createStatusUpdateHandler("publish", "active");
37
+ export const archiveWrite = createStatusUpdateHandler(
38
+ "archive",
39
+ "archived",
40
+ "Sets one template resource of the caller's tenant to archived so the resolver stops serving it while the row and its content survive; use it to retire a template reversibly instead of deleting it.",
41
+ );
42
+ export const publishWrite = createStatusUpdateHandler(
43
+ "publish",
44
+ "active",
45
+ "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant, or to bring an archived template back.",
46
+ );
@@ -16,6 +16,8 @@ export const upsertSystemWrite = defineWriteHandler({
16
16
  name: "upsert-system",
17
17
  schema: upsertPayloadSchema,
18
18
  access: { roles: ["SystemAdmin"] },
19
+ description:
20
+ "Creates or overwrites a system-default template under the system tenant with its full variable schema and linked resources, active straight away with no draft stage; SystemAdmin only, since tenants shadow these defaults through upsert-tenant instead.",
19
21
  handler: async (event, ctx) => {
20
22
  const db = ctx.db;
21
23
  // @cast-boundary engine-payload — SYSTEM_TENANT_ID is a UUID literal,
@@ -23,6 +23,8 @@ export const upsertTenantWrite = defineWriteHandler({
23
23
  status: z.enum(["draft", "active"]).default("draft"),
24
24
  }),
25
25
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
26
+ description:
27
+ "Creates or overwrites a tenant-scoped template override with its variable schema, landing as a draft that only publish makes live; it refuses a system-tenant target, which is what upsert-system is for.",
26
28
  handler: async (event, ctx) => {
27
29
  const db = ctx.db;
28
30
  const override = event.payload.tenantIdOverride;
@@ -144,13 +144,25 @@ export function createTenantFeature(options?: TenantFeatureOptions): FeatureDefi
144
144
  // update by convention, so these are added alongside (no rename = no break
145
145
  // for existing callers). Cross-tenant because the feature is systemScope.
146
146
  r.queryHandler(
147
- defineEntityListHandler("tenant", tenantEntity, { access: { roles: ["SystemAdmin"] } }),
147
+ defineEntityListHandler("tenant", tenantEntity, {
148
+ access: { roles: ["SystemAdmin"] },
149
+ description:
150
+ "Lists tenants under the entity-convention name the generated SystemAdmin tenant-list screen resolves; tenant:query:list is the hand-written equivalent.",
151
+ }),
148
152
  );
149
153
  r.queryHandler(
150
- defineEntityDetailHandler("tenant", tenantEntity, { access: { roles: ["SystemAdmin"] } }),
154
+ defineEntityDetailHandler("tenant", tenantEntity, {
155
+ access: { roles: ["SystemAdmin"] },
156
+ description:
157
+ "Loads a single tenant by id under the entity-convention name the generated SystemAdmin tenant-edit screen resolves.",
158
+ }),
151
159
  );
152
160
  r.writeHandler(
153
- defineEntityUpdateHandler("tenant", tenantEntity, { access: { roles: ["SystemAdmin"] } }),
161
+ defineEntityUpdateHandler("tenant", tenantEntity, {
162
+ access: { roles: ["SystemAdmin"] },
163
+ description:
164
+ "Saves edits to a tenant's own fields under the entity-convention name the generated SystemAdmin tenant-edit screen resolves; tenant:write:update is the hand-written equivalent.",
165
+ }),
154
166
  );
155
167
  r.screen(tenantListScreen);
156
168
  r.screen(tenantEditScreen);
@@ -12,6 +12,8 @@ export const activeTenantIdsQuery = defineQueryHandler({
12
12
  name: "activeTenantIds",
13
13
  schema: z.object({}),
14
14
  access: { roles: [SYSTEM_ROLE, "SystemAdmin"] },
15
+ description:
16
+ "Returns the ids of every tenant that is currently enabled, platform-wide; use it to drive work that has to fan out over all live tenants.",
15
17
  handler: async (_query, ctx) => {
16
18
  if (!ctx.systemDb) {
17
19
  throw new InternalError({
@@ -19,6 +19,8 @@ export const addMemberWrite = defineWriteHandler({
19
19
  roles: z.array(z.string()).min(1),
20
20
  }),
21
21
  access: { roles: ["SystemAdmin"] },
22
+ description:
23
+ "Grants an existing user membership in a tenant with the given roles, refusing reserved role names and a user who is already a member; use it to add someone to a workspace without going through an invitation.",
22
24
  handler: async (event, ctx) => {
23
25
  if (!ctx.systemDb) {
24
26
  throw new InternalError({
@@ -35,6 +35,8 @@ export const cancelInvitationWrite = defineWriteHandler({
35
35
  name: "cancel-invitation",
36
36
  schema: CancelInvitationSchema,
37
37
  access: { roles: access.admin },
38
+ description:
39
+ "Withdraws a still-pending invitation of the caller's own tenant and invalidates its link, answering successfully as a no-op when it was already accepted, cancelled or expired.",
38
40
  handler: async (event, ctx) => {
39
41
  if (!ctx.systemDb) {
40
42
  throw new InternalError({
@@ -22,6 +22,8 @@ export const createWrite = defineWriteHandler({
22
22
  // (roles=["system"]) as the executor; "SystemAdmin" stays the real
23
23
  // human-operator path via the UI.
24
24
  access: { roles: ["system", "SystemAdmin"] },
25
+ description:
26
+ "Creates a new tenant from a key and display name, optionally under a caller-chosen id for seeds or external provisioning; use it when onboarding a new customer or workspace.",
25
27
  handler: async (event, ctx) => {
26
28
  if (!ctx.systemDb) {
27
29
  throw new InternalError({
@@ -23,6 +23,8 @@ export const invitationsQuery = defineQueryHandler({
23
23
  name: "invitations",
24
24
  schema: z.object({}),
25
25
  access: { roles: access.admin },
26
+ description:
27
+ "Lists the still-pending invitations of the caller's own tenant with their email and inviter decrypted; accepted, cancelled and expired ones are left out.",
26
28
  handler: async (query, ctx) => {
27
29
  if (!ctx.systemDb) {
28
30
  throw new InternalError({
@@ -14,6 +14,8 @@ export const listQuery = defineQueryHandler({
14
14
  search: z.string().optional(),
15
15
  }),
16
16
  access: { roles: ["SystemAdmin"] },
17
+ description:
18
+ "Pages through every tenant on the platform with an optional search term, for a SystemAdmin browsing the tenant roster.",
17
19
  handler: async (query, ctx) => {
18
20
  if (!ctx.systemDb) {
19
21
  throw new InternalError({
@@ -10,6 +10,8 @@ export const meQuery = defineQueryHandler({
10
10
  name: "me",
11
11
  schema: z.object({}),
12
12
  access: { openToAll: true },
13
+ description:
14
+ "Returns the record of the tenant the caller is currently signed in to, or null if it is gone; use it whenever the active tenant's own name, key or settings are needed.",
13
15
  handler: async (query, ctx) => {
14
16
  if (!ctx.systemDb) {
15
17
  throw new InternalError({
@@ -24,6 +24,8 @@ export const membersQuery = defineQueryHandler({
24
24
  userId: z.string().min(1).optional(),
25
25
  }),
26
26
  access: { roles: access.admin },
27
+ description:
28
+ "Lists the memberships of the caller's own tenant with each member's decrypted email, display name and roles, optionally narrowed to a single user id.",
27
29
  handler: async (query, ctx) => {
28
30
  if (!ctx.systemDb) {
29
31
  throw new InternalError({
@@ -12,6 +12,8 @@ export const membershipsQuery = defineQueryHandler({
12
12
  // Called via ctx.queryAs(systemUser, ...) during login/switch-tenant, or
13
13
  // directly by tenant admins managing memberships in the admin UI.
14
14
  access: { roles: [SYSTEM_ROLE, "SystemAdmin"] },
15
+ description:
16
+ "Resolves which tenants a given user belongs to and with which roles, labelled by tenant name and key and skipping disabled tenants; sign-in and tenant switching build their options from it.",
15
17
  handler: async (query, ctx) => {
16
18
  if (!ctx.systemDb) {
17
19
  throw new InternalError({
@@ -24,6 +24,9 @@ export const removeMemberWrite = defineWriteHandler({
24
24
  name: "removeMember",
25
25
  schema: z.object({ userId: z.string(), tenantId: z.string() }),
26
26
  access: { roles: ["SystemAdmin"] },
27
+ description:
28
+ "Removes a user's membership in a tenant and signs their sessions in that tenant out, refusing to strip the tenant's last TenantAdmin; use it when someone should lose access to a workspace.",
29
+ agent: { risk: "high" },
27
30
  handler: async (event, ctx) => {
28
31
  if (!ctx.systemDb) {
29
32
  throw new InternalError({
@@ -14,6 +14,7 @@ export const resolveUserIdsQuery = defineQueryHandler({
14
14
  userId: z.string().optional(),
15
15
  }),
16
16
  access: { roles: [SYSTEM_ROLE] },
17
+ agent: { expose: false },
17
18
  handler: async (query, ctx) => {
18
19
  if (!ctx.systemDb) {
19
20
  throw new InternalError({
@@ -99,6 +99,8 @@ export const teamListQuery = definePagedQueryHandler({
99
99
  .optional(),
100
100
  }),
101
101
  access: { roles: access.admin },
102
+ description:
103
+ "Pages through the caller's tenant team as one list of active members and pending invitations, with email, display name, roles, last-seen time and a status of active or pending, sortable and filterable by status.",
102
104
  handler: async (query, ctx) => {
103
105
  if (!ctx.systemDb) {
104
106
  throw new InternalError({
@@ -14,6 +14,9 @@ function createToggleTenantHandler(enable: boolean) {
14
14
  name: verbName,
15
15
  schema: z.object({ id: z.uuid() }),
16
16
  access: { roles: ["SystemAdmin"] },
17
+ description: enable
18
+ ? "Re-enables a disabled tenant so its members can sign in and switch to it again; this is the undo for tenant:write:disable."
19
+ : "Marks a tenant disabled so it stops counting as a membership — its members can no longer sign in to it or switch to it — while leaving all of its data in place.",
17
20
  handler: async (event, ctx) => {
18
21
  if (!ctx.systemDb) {
19
22
  throw new InternalError({
@@ -54,6 +54,8 @@ export const updateMemberRolesWrite = defineWriteHandler({
54
54
  // manage memberships cross-tenant (payload.tenantId). TenantAdmin and Admin
55
55
  // are session-scoped (event.user.tenantId).
56
56
  access: { roles: ["system", ...access.admin] },
57
+ description:
58
+ "Replaces the roles a user holds in a tenant, rejecting reserved role names and any grant that would raise the target above the caller; admins act on their own tenant, SystemAdmins on any.",
57
59
  handler: async (event, ctx) => {
58
60
  if (!ctx.systemDb) {
59
61
  throw new InternalError({
@@ -22,6 +22,8 @@ export const updateWrite = defineWriteHandler({
22
22
  changes: z.object({ name: z.string().min(1).max(200).optional() }),
23
23
  }),
24
24
  access: { roles: ["Admin", "SystemAdmin"] },
25
+ description:
26
+ "Renames a tenant against the version the caller read; an Admin may only rename their own tenant, a SystemAdmin any of them.",
25
27
  handler: async (event, ctx) => {
26
28
  if (!ctx.systemDb) {
27
29
  throw new InternalError({
@@ -18,6 +18,8 @@ export const TENANT_LIFECYCLE_STATUSES = [
18
18
  export type TenantLifecycleStatus = (typeof TENANT_LIFECYCLE_STATUSES)[number];
19
19
 
20
20
  export const tenantEntity = createEntity({
21
+ description:
22
+ "A tenant, the isolated workspace every other record belongs to, holding its key and display name, whether it is enabled, and its destruction-lifecycle state.",
21
23
  table: "read_tenants",
22
24
  // tenant.id IS the tenantId-value that every other table references as FK.
23
25
  // Alle tenantId-Spalten sind UUID (Migration 2026-04-16) → tenant.id muss
@@ -19,6 +19,8 @@ export const cancelDestructionWrite = defineWriteHandler({
19
19
  name: "cancel-destruction",
20
20
  schema: z.object({}),
21
21
  access: { roles: ["TenantOwner", "Admin"] },
22
+ description:
23
+ "Returns a tenant whose destruction was requested back to active and clears the grace period; use it to undo a close-account request while the grace period is still running.",
22
24
  handler: async (event, ctx) => {
23
25
  const tenantId = event.user.tenantId;
24
26
  const row = await fetchOne<TenantLifecycleRow>(ctx.db.raw, tenantTable, { id: tenantId });
@@ -22,6 +22,9 @@ export const requestDestructionWrite = defineWriteHandler({
22
22
  name: "request-destruction",
23
23
  schema: z.object({}),
24
24
  access: { roles: ["TenantOwner", "Admin"] },
25
+ description:
26
+ "Puts the caller's own tenant into destroyRequested, starts the compliance-profile grace period after which its data is erased, and revokes every session in the tenant; use it when an account owner asks to close their account.",
27
+ agent: { risk: "high" },
25
28
  handler: async (event, ctx) => {
26
29
  const tenantId = event.user.tenantId;
27
30
  const row = await fetchOne<TenantLifecycleRow>(ctx.db.raw, tenantTable, { id: tenantId });
@@ -24,6 +24,8 @@ import { createEntity, createTextField } from "@cosmicdrift/kumiko-framework/eng
24
24
  // - Caps-Werte: pro-Tier-Cap-Definitionen leben in der TierMap der App.
25
25
  export const tierAssignmentEntity = createEntity({
26
26
  table: "read_tier_assignments",
27
+ description:
28
+ "The pricing tier currently assigned to a tenant, plus where the assignment came from (manual grant, billing sync or signup default) — one row per tenant.",
27
29
  fields: {
28
30
  tier: createTextField({ required: true, maxLength: 50 }),
29
31
  // Woher das Assignment stammt: "manual" (Admin-Grant via tier-admin-Screen),
@@ -200,11 +200,29 @@ export function createTierEngineFeature<
200
200
  r.entity("tier-assignment", tierAssignmentEntity);
201
201
 
202
202
  // Standard-CRUD via Helper.
203
- r.writeHandler(defineEntityCreateHandler("tier-assignment", tierAssignmentEntity, writeAccess));
204
- r.writeHandler(defineEntityUpdateHandler("tier-assignment", tierAssignmentEntity, writeAccess));
203
+ r.writeHandler(
204
+ defineEntityCreateHandler("tier-assignment", tierAssignmentEntity, {
205
+ ...writeAccess,
206
+ description:
207
+ "Creates the tier-assignment row of the caller's tenant with a tier name and its source; prefer `set-tenant-tier`, which upserts and refreshes the feature gate.",
208
+ }),
209
+ );
210
+ r.writeHandler(
211
+ defineEntityUpdateHandler("tier-assignment", tierAssignmentEntity, {
212
+ ...writeAccess,
213
+ description:
214
+ "Updates an existing tier-assignment row by id; prefer `set-tenant-tier`, which resolves the tenant's row itself and refreshes the feature gate.",
215
+ }),
216
+ );
205
217
 
206
218
  // Reads.
207
- r.queryHandler(defineEntityListHandler("tier-assignment", tierAssignmentEntity, adminAccess));
219
+ r.queryHandler(
220
+ defineEntityListHandler("tier-assignment", tierAssignmentEntity, {
221
+ ...adminAccess,
222
+ description:
223
+ "Lists the tier-assignment rows visible to the caller with tier name and assignment source; use it to see which plan is stored for a tenant.",
224
+ }),
225
+ );
208
226
  r.queryHandler(getActiveTierQuery);
209
227
 
210
228
  // \u2500\u2500 Manueller Tier-Grant (SystemAdmin, ohne Billing) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -226,6 +244,8 @@ export function createTierEngineFeature<
226
244
  r.queryHandler(
227
245
  defineQueryHandler({
228
246
  name: "tier-options",
247
+ description:
248
+ "Returns the tier names the application defines in its tier map; use it to find out which values `set-tenant-tier` accepts.",
229
249
  schema: z.object({}),
230
250
  access: { roles: ["SystemAdmin"] },
231
251
  handler: async () => ({ tiers: opts.tierMap ? Object.keys(opts.tierMap) : [] }),
@@ -242,6 +262,8 @@ export function createTierEngineFeature<
242
262
  id: TIER_ADMIN_SCREEN_ID,
243
263
  type: "custom",
244
264
  renderer: { react: { __component: "TierAdminScreen" } },
265
+ description:
266
+ "Operator form that picks a tenant, shows its current tier and assigns a new one as a manual grant without a billing purchase.",
245
267
  access: { roles: ["SystemAdmin"] },
246
268
  });
247
269
 
@@ -12,6 +12,8 @@ import { z } from "zod";
12
12
  // tier-assignment-Row.
13
13
  export const getActiveTierQuery: QueryHandlerDef = {
14
14
  name: "get-active-tier",
15
+ description:
16
+ "Returns the tier-assignment row currently active for the caller's own tenant, or null when none is assigned; use it to answer which plan the caller is on.",
15
17
  schema: z.object({}),
16
18
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
17
19
  handler: async (_query, ctx) => {
@@ -17,6 +17,8 @@ const tierAssignmentTable = buildEntityTable("tier-assignment", tierAssignmentEn
17
17
 
18
18
  export const getTenantTierQuery = defineQueryHandler({
19
19
  name: "get-tenant-tier",
20
+ description:
21
+ "Returns the tier-assignment of any given tenant including where it came from (manual grant, billing sync or signup default), or null; use it to look up another tenant's plan.",
20
22
  schema: z.object({ tenantId: z.string().min(1) }),
21
23
  access: { roles: ["SystemAdmin"] },
22
24
  handler: async (query, ctx) => {
@@ -55,6 +55,8 @@ export type SetTenantTierOptions = {
55
55
  export function createSetTenantTierWrite(opts: SetTenantTierOptions = {}) {
56
56
  return defineWriteHandler({
57
57
  name: "set-tenant-tier",
58
+ description:
59
+ "Assigns a tier to any tenant as a manual grant without a billing purchase, marking it so a later billing sync will not overwrite it, and applies the new feature set immediately.",
58
60
  schema: z.object({
59
61
  tenantId: z.string().min(1),
60
62
  tier: z
@@ -46,6 +46,8 @@ export const createWrite = defineWriteHandler({
46
46
  roles: rolesInputSchema.optional(),
47
47
  }),
48
48
  access: { roles: ["system", "SystemAdmin"] },
49
+ description:
50
+ "Creates a tenant-agnostic identity record from email, display name, optional locale, timezone and global roles, refusing an email that a live user already holds; the auth features and SystemAdmins use it to onboard a person.",
49
51
  handler: async (event, ctx) => {
50
52
  if (!ctx.systemDb) {
51
53
  throw new InternalError({ message: "user:create requires r.systemScope()" });
@@ -11,6 +11,8 @@ import { attachTenantLabels, dbForList } from "./list.query";
11
11
  // bound to their own tenant's memberships (served by the tenant feature).
12
12
  const baseDetail = defineEntityDetailHandler("user", userEntity, {
13
13
  access: { roles: access.systemAdmin },
14
+ description:
15
+ "Loads one identity record by id for a SystemAdmin, enriched with the same derived `tenants` membership label as the user list; tenant-level admins do not reach it.",
14
16
  });
15
17
 
16
18
  // Same tenants enrichment as user:list — derived field placeholder is "".
@@ -28,6 +28,7 @@ export const findForAuthQuery = defineQueryHandler({
28
28
  { message: "exactly one of email or id must be set" },
29
29
  ),
30
30
  access: { roles: access.system },
31
+ agent: { expose: false },
31
32
  handler: async (query, ctx) => {
32
33
  // Soft-deleted rows can now share an email with a live row (the partial
33
34
  // bidx unique index covers live rows only), so the email arm must resolve
@@ -13,6 +13,8 @@ import { userEntity } from "../schema/user";
13
13
 
14
14
  const baseList = defineEntityListHandler("user", userEntity, {
15
15
  access: { roles: access.systemAdmin },
16
+ description:
17
+ "Lists identity records across every tenant for a SystemAdmin roster, each row carrying a derived `tenants` label naming the tenants the user belongs to and their roles there.",
16
18
  });
17
19
 
18
20
  type MembershipRow = { userId: unknown; tenantId: unknown; roles?: unknown };
@@ -12,6 +12,8 @@ export const meQuery = defineQueryHandler({
12
12
  name: "user:me",
13
13
  schema: z.object({}),
14
14
  access: { openToAll: true },
15
+ description:
16
+ "Returns the signed-in caller's own identity record, with the password hash stripped by field-level read access; use it whenever the current user's own profile data is needed.",
15
17
  handler: async (query, ctx) => {
16
18
  if (!ctx.systemDb) {
17
19
  throw new InternalError({ message: "user:me requires r.systemScope()" });
@@ -41,6 +41,8 @@ export const updateWrite = defineWriteHandler({
41
41
  }),
42
42
  }),
43
43
  access: { openToAll: true },
44
+ description:
45
+ "Changes a user's display name, locale, timezone, email, verification flag, last active tenant or global roles against the version the caller read; callers may edit themselves, while editing someone else or granting roles needs a privileged actor.",
44
46
  handler: async (event, ctx) => {
45
47
  const isSelf = event.payload.id === event.user.id;
46
48
  const isPrivileged = hasAccess(event.user, { roles: access.privileged });
@@ -58,6 +58,8 @@ const USER_STATUS_OPTIONS = [
58
58
  // User entity — tenant-agnostic. A single user can belong to multiple tenants
59
59
  // via tenantMemberships. No tenantId column on this table.
60
60
  export const userEntity = createEntity({
61
+ description:
62
+ "A person's platform-wide identity: login email and credentials, display name, locale and time zone, account status and global roles, shared across every tenant they belong to.",
61
63
  table: "read_users",
62
64
  softDelete: true,
63
65
  // Tenant-independent identity aggregate — its event stream lives on
@@ -299,6 +299,8 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
299
299
  renderer: { react: { __component: "PrivacyCenterScreen" } },
300
300
  access: { openToAll: true },
301
301
  dormant: true,
302
+ description:
303
+ "Logged-in GDPR self-service page where a user requests and downloads a data export (Art. 20), restricts processing of their account (Art. 18) and requests its deletion (Art. 17).",
302
304
  });
303
305
 
304
306
  // Magic-link path (anonymous): the email link carries the token as a
@@ -21,6 +21,8 @@ export const cancelDeletionWrite = defineWriteHandler({
21
21
  name: "cancel-deletion",
22
22
  schema: z.object({}),
23
23
  access: { openToAll: true },
24
+ description:
25
+ "Withdraws the calling user's own pending account-deletion request and puts the account back to active, accepted only while the grace period is still running.",
24
26
  handler: async (event, ctx) => {
25
27
  // ctx.db.raw (kein TenantDb-Wrapper) weil User-Entity tenant-agnostisch
26
28
  // ist — siehe request-deletion.write.ts fuer die Begruendung. Cancel
@@ -50,6 +50,7 @@ export function createConfirmDeletionByTokenHandler(opts: ConfirmDeletionByToken
50
50
  name: "confirm-deletion-by-token",
51
51
  schema: z.object({ token: z.string().min(1) }),
52
52
  access: { roles: ["anonymous", "Member", "User", "TenantAdmin", "SystemAdmin"] },
53
+ agent: { expose: false },
53
54
  rateLimit: { per: "ip", limit: 10, windowSeconds: 60 },
54
55
  handler: async (event, ctx) => {
55
56
  if (!opts.deletionTokenSecret) return writeFailure(invalidToken());
@@ -7,5 +7,10 @@ import { downloadAttemptEntity } from "../schema/download-attempt";
7
7
  export const downloadAttemptListQuery = defineEntityListHandler(
8
8
  "download-attempt",
9
9
  downloadAttemptEntity,
10
- { access: { roles: access.systemAdmin }, crossTenant: true },
10
+ {
11
+ access: { roles: access.systemAdmin },
12
+ crossTenant: true,
13
+ description:
14
+ "Lists invalid export-download attempts across every tenant for the system-admin inspector screen; list-download-attempts is the filterable view scoped to a single tenant.",
15
+ },
11
16
  );
@@ -61,6 +61,8 @@ export const downloadByJobQuery = defineQueryHandler({
61
61
  jobId: z.string().min(1, "jobId required"),
62
62
  }),
63
63
  access: { openToAll: true }, // openToAll = auth-required, kein anonymous
64
+ description:
65
+ "Returns a short-lived signed download URL for the calling user's own finished data-export job named by job id, backing the download button in the privacy center once export-status reports the job done.",
64
66
  handler: async (query, ctx) => {
65
67
  const T = getTemporal();
66
68
  const now = T.Now.instant();
@@ -74,6 +74,7 @@ export const downloadByTokenQuery = defineQueryHandler({
74
74
  .optional(),
75
75
  }),
76
76
  access: { roles: ["anonymous", "Member", "User", "TenantAdmin", "SystemAdmin"] },
77
+ agent: { expose: false },
77
78
  // Brute-Force-Schutz fuer Token-Hash-Probing. Anonymous-Endpoint mit
78
79
  // 32-byte-Random-Token = 256 Bit Search-Space, aber rate-limit als
79
80
  // defense-in-depth + Schutz gegen Storm-Patterns die DB-Last erzeugen.