@cosmicdrift/kumiko-bundled-features 0.354.1 → 0.355.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 (34) hide show
  1. package/dist/agent-tools/agent-manifest.js +2 -0
  2. package/dist/agent-tools/types.d.ts +2 -0
  3. package/dist/audit/escape-hatch-audit-sink.d.ts +1 -0
  4. package/dist/audit/escape-hatch-audit-sink.js +2 -0
  5. package/dist/audit/handlers/resolve-audit-tenant.js +8 -4
  6. package/dist/billing-foundation/consumer-protection/register-consent.js +1 -2
  7. package/dist/billing-foundation/consumer-protection/register-termination.d.ts +1 -1
  8. package/dist/billing-foundation/consumer-protection/register-termination.js +1 -2
  9. package/dist/cap-overview/handlers/caps-usage.query.js +8 -5
  10. package/dist/compliance-profiles/handlers/set-profile.write.js +1 -4
  11. package/dist/delivery/delivery-service.js +1 -1
  12. package/dist/managed-pages/handlers/by-tenant-published.query.js +1 -4
  13. package/dist/managed-pages/handlers/set.write.js +1 -5
  14. package/dist/template-resolver/handlers/by-slug.query.js +1 -4
  15. package/dist/template-resolver/handlers/by-tenant.query.js +1 -4
  16. package/dist/template-resolver/handlers/collection-item.query.js +1 -4
  17. package/dist/template-resolver/handlers/collection-list.query.js +1 -4
  18. package/dist/template-resolver/handlers/collection-set.write.js +1 -5
  19. package/dist/template-resolver/handlers/set.write.js +1 -5
  20. package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -4
  21. package/dist/template-resolver/seeding.js +1 -2
  22. package/dist/tenant-lifecycle/feature.js +1 -0
  23. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
  24. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
  25. package/dist/tenant-lifecycle/stages.d.ts +2 -0
  26. package/dist/tenant-lifecycle/stages.js +2 -0
  27. package/dist/user-data-rights/feature.js +2 -0
  28. package/dist/user-data-rights/run-export-jobs.d.ts +2 -0
  29. package/dist/user-data-rights/run-export-jobs.js +2 -0
  30. package/dist/user-data-rights/run-forget-cleanup.d.ts +2 -0
  31. package/dist/user-data-rights/run-forget-cleanup.js +4 -0
  32. package/dist/user-data-rights/run-user-export.d.ts +2 -0
  33. package/dist/user-data-rights/run-user-export.js +2 -0
  34. package/package.json +9 -9
@@ -163,6 +163,7 @@ function buildHandlerEntry(qn, kind, def, roles, getHandlerEntity, denyQns) {
163
163
  return undefined;
164
164
  }
165
165
  const entity = getHandlerEntity(qn);
166
+ const excludedFields = "excludedFields" in def ? def.excludedFields : undefined;
166
167
  return {
167
168
  qn,
168
169
  kind,
@@ -170,6 +171,7 @@ function buildHandlerEntry(qn, kind, def, roles, getHandlerEntity, denyQns) {
170
171
  risk: exposure.risk,
171
172
  inputSchema,
172
173
  ...(entity !== undefined && { entity }),
174
+ ...(excludedFields !== undefined && { excludedFields }),
173
175
  };
174
176
  }
175
177
  function buildHandlers(queryHandlers, writeHandlers, roles, getHandlerEntity, denyQns) {
@@ -130,6 +130,8 @@ export type AgentManifestHandler = {
130
130
  readonly risk: AgentRisk;
131
131
  readonly inputSchema: Readonly<Record<string, unknown>>;
132
132
  readonly entity?: string;
133
+ /** Fields this generic create/update handler never writes; sending one fails validation. */
134
+ readonly excludedFields?: readonly string[];
133
135
  };
134
136
  export type AgentManifestScreen = {
135
137
  readonly id: string;
@@ -15,6 +15,7 @@ export declare const escapeHatchUsedSchema: z.ZodObject<{
15
15
  }>;
16
16
  reason: z.ZodString;
17
17
  actor: z.ZodString;
18
+ caller: z.ZodOptional<z.ZodString>;
18
19
  targetUserId: z.ZodOptional<z.ZodString>;
19
20
  targetTenantId: z.ZodOptional<z.ZodString>;
20
21
  }, z.core.$strip>;
@@ -18,6 +18,7 @@ export const escapeHatchUsedSchema = z.object({
18
18
  kind: z.enum(escapeHatchKinds),
19
19
  reason: z.string(),
20
20
  actor: z.string(),
21
+ caller: z.string().optional(),
21
22
  targetUserId: z.string().optional(),
22
23
  targetTenantId: z.string().optional(),
23
24
  });
@@ -28,6 +29,7 @@ export function createEscapeHatchAuditSink(opts) {
28
29
  kind: event.kind,
29
30
  reason: event.reason,
30
31
  actor: event.actor,
32
+ ...(event.caller !== undefined && { caller: event.caller }),
31
33
  ...(event.target && { targetUserId: event.target.id, targetTenantId: event.target.tenantId }),
32
34
  });
33
35
  // Appends on the unbound pool, never the handler tx — an audit entry must
@@ -1,4 +1,5 @@
1
- import { crossTenantOverrideDenied } from "@cosmicdrift/kumiko-framework/engine";
1
+ import { mayOverrideTenant } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { AccessDeniedError, FrameworkReasons } from "@cosmicdrift/kumiko-framework/errors";
2
3
  import { APP_INSTANCE_STREAM_TYPE } from "@cosmicdrift/kumiko-framework/event-store";
3
4
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-types/identifiers";
4
5
  import { AuditScopes } from "../constants.js";
@@ -7,8 +8,11 @@ import { AuditScopes } from "../constants.js";
7
8
  export function resolveAuditScopeFilter(user, scope) {
8
9
  if (scope !== AuditScopes.system)
9
10
  return { tenantId: user.tenantId };
10
- const denied = crossTenantOverrideDenied(user, SYSTEM_TENANT_ID, "audit.errors.systemScopeRequiresSystemAdmin");
11
- if (denied)
12
- throw denied;
11
+ if (!mayOverrideTenant(user)) {
12
+ throw new AccessDeniedError({
13
+ i18nKey: "audit.errors.systemScopeRequiresSystemAdmin",
14
+ details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
15
+ });
16
+ }
13
17
  return { tenantId: SYSTEM_TENANT_ID, aggregateType: APP_INSTANCE_STREAM_TYPE };
14
18
  }
@@ -1,4 +1,3 @@
1
- import { createSystemUser } from "@cosmicdrift/kumiko-framework/engine";
2
1
  import { paymentAggregateId, subscriptionAggregateId } from "../aggregate-id.js";
3
2
  import { SubscriptionFoundationHandlers, SubscriptionStatuses } from "../constants.js";
4
3
  import { CHECKOUT_CONSENT_RECORDED_EVENT_SHORT, CONTRACT_CONFIRMATION_ISSUED_EVENT_SHORT, checkoutConsentRecordedPayloadSchema, contractConfirmationIssuedPayloadSchema, INVOICE_PAID_EVENT_QN, PAYMENT_RECEIVED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_QN, } from "../events.js";
@@ -48,7 +47,7 @@ export function registerConsumerProtection(r, options, consumerProtection) {
48
47
  // user), never from payload data.
49
48
  const tenantId = ctx.systemUser.tenantId;
50
49
  const currentPeriodEndIso = payload["currentPeriodEndIso"];
51
- const result = await ctx.writeAs(createSystemUser(tenantId), SubscriptionFoundationHandlers.issueContractConfirmation, {
50
+ const result = await ctx.write(SubscriptionFoundationHandlers.issueContractConfirmation, {
52
51
  consentId,
53
52
  sourceAggregateId: isSubscriptionEvent
54
53
  ? subscriptionAggregateId(tenantId)
@@ -1,3 +1,3 @@
1
- import { type FeatureRegistrar } from "@cosmicdrift/kumiko-framework/engine";
1
+ import type { FeatureRegistrar } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.js";
3
3
  export declare function registerContractTermination(r: FeatureRegistrar, options: ResolvedBillingFoundationOptions, consumerProtection: ConsumerProtectionOptions): void;
@@ -1,4 +1,3 @@
1
- import { createSystemUser } from "@cosmicdrift/kumiko-framework/engine";
2
1
  import { SubscriptionCancelTimings, SubscriptionFoundationHandlers } from "../constants.js";
3
2
  import { CONTRACT_TERMINATION_DECLARED_EVENT_QN, CONTRACT_TERMINATION_DECLARED_EVENT_SHORT, CONTRACT_TERMINATION_REQUESTED_EVENT_SHORT, CONTRACT_TERMINATION_UNMATCHED_EVENT_SHORT, contractTerminationDeclaredPayloadSchema, contractTerminationRequestedPayloadSchema, contractTerminationUnmatchedPayloadSchema, } from "../events.js";
4
3
  import { createDeclareContractTerminationHandler, createRecordContractTerminationHandler, createRecordUnmatchedContractTerminationHandler, createTerminableSubscriptionQuery, } from "./termination-record.js";
@@ -22,7 +21,7 @@ export function registerContractTermination(r, options, consumerProtection) {
22
21
  // skip: trigger event carries no usable declaration
23
22
  if (!declared.success)
24
23
  return;
25
- const result = await ctx.writeAs(createSystemUser(ctx.systemUser.tenantId), SubscriptionFoundationHandlers.recordContractTermination, {
24
+ const result = await ctx.write(SubscriptionFoundationHandlers.recordContractTermination, {
26
25
  requestId: declared.data.requestId,
27
26
  declarationType: declared.data.declarationType,
28
27
  terminationKind: declared.data.terminationKind,
@@ -1,7 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
3
- import { crossTenantOverrideDenied, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
4
- import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
3
+ import { defineQueryHandler, mayOverrideTenant, } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { AccessDeniedError, FrameworkReasons, InternalError, } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import * as z from "zod";
6
6
  import { tierAssignmentEntity } from "../../tier-engine/index.js";
7
7
  import { computeFraction, computeTone, computeUnclampedFraction } from "../usage-math.js";
@@ -24,9 +24,12 @@ export function createCapsUsageQuery(caps, usageRoles) {
24
24
  });
25
25
  }
26
26
  const override = query.payload.tenantId;
27
- const overrideDenied = crossTenantOverrideDenied(query.user, override, "cap-overview.errors.tenantOverrideRequiresSystemAdmin");
28
- if (overrideDenied)
29
- throw overrideDenied;
27
+ if (override !== undefined && !mayOverrideTenant(query.user)) {
28
+ throw new AccessDeniedError({
29
+ i18nKey: "cap-overview.errors.tenantOverrideRequiresSystemAdmin",
30
+ details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
31
+ });
32
+ }
30
33
  const targetTenantId = override ?? query.user.tenantId;
31
34
  // Both branches return the SAME unfiltered system-mode db —
32
35
  // assertTenantMatch is a self-check on the caller, not a query
@@ -1,7 +1,7 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { complianceProfileOverrideSchema, SELECTABLE_PROFILE_KEYS, } from "@cosmicdrift/kumiko-framework/compliance";
3
3
  import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
4
- import { access, crossTenantOverrideDenied, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
5
5
  import { UnprocessableError, validationErrorFromZod, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import * as z from "zod";
7
7
  import { tenantComplianceProfileEntity, tenantComplianceProfileTable, } from "../schema/profile-selection.js";
@@ -44,9 +44,6 @@ export const setProfileWrite = defineWriteHandler({
44
44
  description: "Sets or replaces a tenant's compliance profile key plus an optional JSON override, for onboarding or a later region change; a system admin may target a different tenant through tenantIdOverride.",
45
45
  handler: async (event, ctx) => {
46
46
  const tenantOverride = event.payload.tenantIdOverride;
47
- const overrideDenied = crossTenantOverrideDenied(event.user, tenantOverride, "complianceProfiles.errors.tenantOverrideRequiresSystemAdmin");
48
- if (overrideDenied)
49
- return writeFailure(overrideDenied);
50
47
  const tenantId = (tenantOverride ?? event.user.tenantId); // @cast-boundary engine-payload
51
48
  const executorUser = tenantOverride !== undefined ? { ...event.user, tenantId } : event.user;
52
49
  // Override-Validation: muss parseables JSON-Object sein UND dem
@@ -92,7 +92,7 @@ export function createDeliveryService(options) {
92
92
  tenantId,
93
93
  actor: systemUser.id,
94
94
  sink: escapeHatchAuditSink,
95
- log,
95
+ log: logError,
96
96
  });
97
97
  // ctx.db/dbOutsideTransaction are tenant-filtered even for a systemScope handler:
98
98
  // the dispatcher closes them off entirely there, so "system" mode would hand this
@@ -1,6 +1,6 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
3
- import { crossTenantOverrideDenied, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import * as z from "zod";
5
5
  import { pagesTable } from "../table.js";
6
6
  export const byTenantPublishedQuery = defineQueryHandler({
@@ -15,9 +15,6 @@ export const byTenantPublishedQuery = defineQueryHandler({
15
15
  description: "Lists every published page of a tenant with slug, language, title and last-change time but no body; use it to enumerate the public pages for sitemap.xml or llms.txt, rather than by-slug which fetches one page's content.",
16
16
  handler: async (query, ctx) => {
17
17
  const override = query.payload.tenantIdOverride;
18
- const overrideDenied = crossTenantOverrideDenied(query.user, override, "managedPages.errors.tenantOverrideRequiresSystemAdmin");
19
- if (overrideDenied)
20
- throw overrideDenied;
21
18
  const tenantId = override ?? query.user.tenantId;
22
19
  const rows = castTenantRows(await selectMany(ctx.db, pagesTable, { tenantId: tenantId, published: true }));
23
20
  return {
@@ -1,7 +1,6 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
3
- import { crossTenantOverrideDenied, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
4
- import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
5
4
  import * as z from "zod";
6
5
  import { pageEntity, pagesTable } from "../table.js";
7
6
  const slugSchema = z
@@ -44,9 +43,6 @@ export const setWrite = defineWriteHandler({
44
43
  handler: async (event, ctx) => {
45
44
  const db = ctx.db;
46
45
  const override = event.payload.tenantIdOverride;
47
- const overrideDenied = crossTenantOverrideDenied(event.user, override, "managedPages.errors.tenantOverrideRequiresSystemAdmin");
48
- if (overrideDenied)
49
- return writeFailure(overrideDenied);
50
46
  const tenantId = override ?? event.user.tenantId;
51
47
  // override: point the executor context at the target tenant, else getStreamVersion runs against user.tenantId → version_conflict.
52
48
  const executorUser = override !== undefined ? { ...event.user, tenantId: override } : event.user; // @cast-boundary engine-bridge
@@ -1,5 +1,5 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { crossTenantOverrideDenied, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import * as z from "zod";
4
4
  import { TEXT_BLOCK_KIND } from "../constants.js";
5
5
  import { templateResourcesTable } from "../table.js";
@@ -28,9 +28,6 @@ export const bySlugQuery = defineQueryHandler({
28
28
  description: "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.",
29
29
  handler: async (query, ctx) => {
30
30
  const override = query.payload.tenantIdOverride;
31
- const overrideDenied = crossTenantOverrideDenied(query.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
32
- if (overrideDenied)
33
- throw overrideDenied;
34
31
  const tenantId = override ?? query.user.tenantId;
35
32
  const row = await fetchOne(ctx.db, templateResourcesTable, {
36
33
  tenantId,
@@ -1,6 +1,6 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
3
- import { crossTenantOverrideDenied, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import * as z from "zod";
5
5
  import { TEXT_BLOCK_KIND } from "../constants.js";
6
6
  import { templateResourcesTable } from "../table.js";
@@ -15,9 +15,6 @@ export const byTenantQuery = defineQueryHandler({
15
15
  description: "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.",
16
16
  handler: async (query, ctx) => {
17
17
  const override = query.payload.tenantIdOverride;
18
- const overrideDenied = crossTenantOverrideDenied(query.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
19
- if (overrideDenied)
20
- throw overrideDenied;
21
18
  const tenantId = override ?? query.user.tenantId;
22
19
  const rows = castTenantRows(await selectMany(ctx.db, templateResourcesTable, { tenantId, kind: TEXT_BLOCK_KIND }));
23
20
  return {
@@ -1,5 +1,5 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { crossTenantOverrideDenied, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import * as z from "zod";
4
4
  import { collectionStore, DEFAULT_COLLECTION_ACCESS, toCollectionEntry, } from "./collection-shared.js";
5
5
  // Single entry of one collection, for the editor behind a tree node. Same
@@ -20,9 +20,6 @@ export function makeCollectionItemQuery(collection) {
20
20
  : `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.`,
21
21
  handler: async (query, ctx) => {
22
22
  const override = query.payload.tenantIdOverride;
23
- const overrideDenied = crossTenantOverrideDenied(query.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
24
- if (overrideDenied)
25
- throw overrideDenied;
26
23
  const tenantId = override ?? query.user.tenantId;
27
24
  const row = await fetchOne(ctx.db, store.table, {
28
25
  tenantId,
@@ -1,6 +1,6 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
3
- import { crossTenantOverrideDenied, defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { defineQueryHandler, } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import * as z from "zod";
5
5
  import { collectionStore, DEFAULT_COLLECTION_ACCESS, toCollectionEntry, } from "./collection-shared.js";
6
6
  // One list handler per declared collection: `kind`, `ownership` and `access`
@@ -25,9 +25,6 @@ export function makeCollectionListQuery(collection) {
25
25
  : `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.`,
26
26
  handler: async (query, ctx) => {
27
27
  const override = query.payload.tenantIdOverride;
28
- const overrideDenied = crossTenantOverrideDenied(query.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
29
- if (overrideDenied)
30
- throw overrideDenied;
31
28
  const tenantId = override ?? query.user.tenantId;
32
29
  const rows = castTenantRows(await selectMany(ctx.db, store.table, {
33
30
  tenantId,
@@ -1,6 +1,5 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { crossTenantOverrideDenied, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
3
- import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
+ import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
4
3
  import * as z from "zod";
5
4
  import { collectionStore, DEFAULT_COLLECTION_ACCESS, } from "./collection-shared.js";
6
5
  import { contentFormatSchema, folderSchema, localeSchema, slugSchema } from "./shared.js";
@@ -32,9 +31,6 @@ export function makeCollectionSetWrite(collection) {
32
31
  handler: async (event, ctx) => {
33
32
  const db = ctx.db;
34
33
  const override = event.payload.tenantIdOverride;
35
- const overrideDenied = crossTenantOverrideDenied(event.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
36
- if (overrideDenied)
37
- return writeFailure(overrideDenied);
38
34
  // @cast-boundary engine-payload — override is a zod-validated string, the
39
35
  // user's own tenantId is already TenantId-branded.
40
36
  const tenantId = (override ?? event.user.tenantId);
@@ -1,6 +1,5 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { crossTenantOverrideDenied, defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
- import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
+ import { defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
4
3
  import * as z from "zod";
5
4
  import { TEMPLATE_KINDS, TEXT_BLOCK_KIND } from "../constants.js";
6
5
  import { templateResourcesTable } from "../table.js";
@@ -45,9 +44,6 @@ export const setWrite = defineWriteHandler({
45
44
  handler: async (event, ctx) => {
46
45
  const db = ctx.db;
47
46
  const override = event.payload.tenantIdOverride;
48
- const overrideDenied = crossTenantOverrideDenied(event.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
49
- if (overrideDenied)
50
- return writeFailure(overrideDenied);
51
47
  // @cast-boundary engine-payload — override is a zod-validated string, the
52
48
  // user's own tenantId is already TenantId-branded.
53
49
  const tenantId = (override ?? event.user.tenantId);
@@ -1,5 +1,5 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { crossTenantOverrideDenied, defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { defineWriteHandler, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { AccessDeniedError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import * as z from "zod";
5
5
  import { templateResourcesTable } from "../table.js";
@@ -20,9 +20,6 @@ export const upsertTenantWrite = defineWriteHandler({
20
20
  handler: async (event, ctx) => {
21
21
  const db = ctx.db;
22
22
  const override = event.payload.tenantIdOverride;
23
- const overrideDenied = crossTenantOverrideDenied(event.user, override, "templateResolver.errors.tenantOverrideRequiresSystemAdmin");
24
- if (overrideDenied)
25
- return writeFailure(overrideDenied);
26
23
  // upsertTenant erzeugt scope='tenant'. SYSTEM_TENANT_ID-Override würde
27
24
  // scope='tenant' unter SYSTEM_TENANT_ID schreiben → inkonsistenter Zustand
28
25
  // (Resolver-Logik trennt sauber zwischen system+tenant). SystemAdmin muss
@@ -29,8 +29,7 @@ async function resolveExistingForEventStoreSeed(db, tdb, existingRow, tenantId)
29
29
  if (unscoped !== 0) {
30
30
  throw new Error(`seed orphan check: projection ${id} has no events for tenant ${tenantId} but unscoped stream version is ${unscoped} — refusing delete/recreate to avoid wiping a live stream`);
31
31
  }
32
- // @cast-boundary orphan projection cleanup: empty stream, no executor verb;
33
- // plain table ident trips guard-direct-entity-writes (infra EXCLUDE follow-up).
32
+ // The cast strips the executor-only brand that WritableTable rejects; the orphan row has no events, so no executor verb applies.
34
33
  await deleteMany(tdb, templateResourcesTable, { id, tenantId });
35
34
  return null;
36
35
  }
@@ -89,6 +89,7 @@ export function createTenantLifecycleFeature() {
89
89
  fileProviderResolver: ctx._fileProviderResolver,
90
90
  searchAdapter: ctx.searchAdapter,
91
91
  escapeHatchAuditSink: ctx._escapeHatchAuditSink,
92
+ escapeHatchAuditLog: ctx.log,
92
93
  actor: ctx.systemUser.id,
93
94
  });
94
95
  },
@@ -2,6 +2,7 @@
2
2
  import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { type EscapeHatchAuditSink, type Registry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
5
+ import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
5
6
  import type { SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
6
7
  type StageTickResult = {
7
8
  readonly done: boolean;
@@ -17,6 +18,7 @@ export declare function runNextDestructionStage(args: {
17
18
  readonly fileProviderResolver?: FileProviderResolver;
18
19
  readonly searchAdapter?: SearchAdapter;
19
20
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
21
+ readonly escapeHatchAuditLog?: Logger;
20
22
  readonly actor?: string;
21
23
  }): Promise<StageTickResult>;
22
24
  export declare function hasTenantDestructionWork(reads: Pick<TenantDb, "count">, now: Temporal.Instant): Promise<boolean>;
@@ -29,6 +31,7 @@ export declare function runTenantDestructionSweep(args: {
29
31
  readonly fileProviderResolver?: FileProviderResolver;
30
32
  readonly searchAdapter?: SearchAdapter;
31
33
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
34
+ readonly escapeHatchAuditLog?: Logger;
32
35
  readonly actor?: string;
33
36
  }): Promise<{
34
37
  readonly triggered: number;
@@ -162,6 +162,7 @@ export async function runNextDestructionStage(args) {
162
162
  fileProviderResolver: args.fileProviderResolver,
163
163
  searchAdapter: args.searchAdapter,
164
164
  escapeHatchAuditSink: args.escapeHatchAuditSink,
165
+ escapeHatchAuditLog: args.escapeHatchAuditLog,
165
166
  actor: args.actor,
166
167
  deadlineAt: Date.now() + DESTRUCTION_STAGE_TICK_BUDGET_MS,
167
168
  };
@@ -258,6 +259,7 @@ export async function runTenantDestructionSweep(args) {
258
259
  fileProviderResolver: args.fileProviderResolver,
259
260
  searchAdapter: args.searchAdapter,
260
261
  escapeHatchAuditSink: args.escapeHatchAuditSink,
262
+ escapeHatchAuditLog: args.escapeHatchAuditLog,
261
263
  actor: args.actor,
262
264
  });
263
265
  if (!result.error && !result.halted)
@@ -1,6 +1,7 @@
1
1
  import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { type EscapeHatchAuditSink, type Registry, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
4
+ import { type Logger } from "@cosmicdrift/kumiko-framework/logging";
4
5
  import { type SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
5
6
  import type { TenantDestructionStageName } from "./constants.js";
6
7
  export type DestructionStageCtx = {
@@ -11,6 +12,7 @@ export type DestructionStageCtx = {
11
12
  readonly fileProviderResolver?: FileProviderResolver;
12
13
  readonly searchAdapter?: SearchAdapter;
13
14
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
15
+ readonly escapeHatchAuditLog?: Logger;
14
16
  readonly actor?: string;
15
17
  readonly deadlineAt: number;
16
18
  };
@@ -2,6 +2,7 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { configuredPiiSubjectKms, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
3
3
  import { createEventStoreExecutor, createTenantDb, } from "@cosmicdrift/kumiko-framework/db";
4
4
  import { createSystemUser, EXT_EXTERNAL_RESOURCE, EXT_INFRA_RESOURCE, EXT_SEARCH_ADAPTER, EXT_STORAGE_PROVIDER, EXT_TENANT_DATA, extensionUsageEscapeHatchReason, isTenantDataExtensionHooks, isTenantResourceExtensionHooks, } from "@cosmicdrift/kumiko-framework/engine";
5
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
5
6
  import { createEscapeHatchReporter, UNATTRIBUTED_ACTOR, } from "@cosmicdrift/kumiko-framework/pipeline";
6
7
  import { purgeSearchDocumentsForSubject, } from "@cosmicdrift/kumiko-framework/search";
7
8
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
@@ -51,6 +52,7 @@ async function runTenantDataHooks(ctx) {
51
52
  tenantId: ctx.tenantId,
52
53
  actor: ctx.actor ?? UNATTRIBUTED_ACTOR,
53
54
  sink: ctx.escapeHatchAuditSink,
55
+ log: ctx.escapeHatchAuditLog ?? createFallbackLogger("tenant-lifecycle"),
54
56
  });
55
57
  const hookCtx = {
56
58
  db: createTenantDb(ctx.db, ctx.tenantId, "tenant", undefined, undefined, undefined, {
@@ -477,6 +477,7 @@ export function createUserDataRightsFeature(opts = {}) {
477
477
  appExportDownloadUrl: opts.appExportDownloadUrl,
478
478
  }),
479
479
  escapeHatchAuditSink: ctx._escapeHatchAuditSink,
480
+ escapeHatchAuditLog: ctx.log,
480
481
  actor: ctx.systemUser.id,
481
482
  metrics: exportMetrics,
482
483
  });
@@ -541,6 +542,7 @@ export function createUserDataRightsFeature(opts = {}) {
541
542
  }),
542
543
  ...(sendDeletionExecutedEmail && { sendDeletionExecutedEmail }),
543
544
  escapeHatchAuditSink: ctx._escapeHatchAuditSink,
545
+ escapeHatchAuditLog: ctx.log,
544
546
  actor: ctx.systemUser.id,
545
547
  });
546
548
  // Persist each incomplete report on the job-run log (#1572). The write
@@ -1,3 +1,4 @@
1
+ import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
1
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
3
  import type { EscapeHatchAuditSink, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import { type FileStorageProvider } from "@cosmicdrift/kumiko-framework/files";
@@ -75,6 +76,7 @@ export interface RunExportJobsArgs {
75
76
  * Callback durch. Required wenn sendExportReadyEmail gesetzt. */
76
77
  readonly appExportDownloadUrl?: string;
77
78
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
79
+ readonly escapeHatchAuditLog?: Logger;
78
80
  readonly actor?: string;
79
81
  /**
80
82
  * Reports EXPORT_CLEANUP_BACKLOG_AGE_METRIC after the storage-cleanup
@@ -106,6 +106,7 @@ export async function runExportJobs(args) {
106
106
  now,
107
107
  job,
108
108
  escapeHatchAuditSink: args.escapeHatchAuditSink,
109
+ escapeHatchAuditLog: args.escapeHatchAuditLog,
109
110
  actor: args.actor,
110
111
  });
111
112
  if (outcome.kind === "done") {
@@ -230,6 +231,7 @@ async function processJob(args) {
230
231
  userId: job.userId,
231
232
  now,
232
233
  escapeHatchAuditSink: args.escapeHatchAuditSink,
234
+ escapeHatchAuditLog: args.escapeHatchAuditLog,
233
235
  actor: args.actor,
234
236
  });
235
237
  const profile = await resolveProfileForTenant({
@@ -1,3 +1,4 @@
1
+ import { type Logger } from "@cosmicdrift/kumiko-framework/logging";
1
2
  import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
2
3
  import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
4
  import { type EscapeHatchAuditSink, type Registry, type TenantId, type TenantUserModel, type UserDataDeleteStrategy, type UserDataStorageProvider } from "@cosmicdrift/kumiko-framework/engine";
@@ -69,6 +70,7 @@ export interface RunForgetCleanupArgs {
69
70
  */
70
71
  readonly tenantModel?: TenantUserModel;
71
72
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
73
+ readonly escapeHatchAuditLog?: Logger;
72
74
  readonly actor?: string;
73
75
  }
74
76
  export interface ForgetCleanupError {
@@ -1,3 +1,4 @@
1
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
1
2
  // Forget-Cleanup-Runner (S2.U5b) — pure-Function Pipeline.
2
3
  //
3
4
  // After the grace period (S2.U5a sets gracePeriodEnd) this runner iterates
@@ -51,6 +52,7 @@ function buildHookDb(db, tenantId, entry, args) {
51
52
  tenantId,
52
53
  actor: args.actor ?? UNATTRIBUTED_ACTOR,
53
54
  sink: args.escapeHatchAuditSink,
55
+ log: args.escapeHatchAuditLog ?? createFallbackLogger("user-data-rights"),
54
56
  });
55
57
  return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
56
58
  unsafeRaw: entry.escapeHatchReason !== undefined ? { reason: entry.escapeHatchReason } : undefined,
@@ -117,6 +119,7 @@ export async function runForgetCleanup(args) {
117
119
  kms,
118
120
  searchAdapter: args.searchAdapter,
119
121
  escapeHatchAuditSink: args.escapeHatchAuditSink,
122
+ escapeHatchAuditLog: args.escapeHatchAuditLog,
120
123
  actor: args.actor,
121
124
  });
122
125
  hookCallsAttempted += userResult.hookCallsAttempted;
@@ -235,6 +238,7 @@ async function processUser(args) {
235
238
  const hookResult = await entry.deleteHook({
236
239
  db: buildHookDb(tx, tenantId, entry, {
237
240
  escapeHatchAuditSink: args.escapeHatchAuditSink,
241
+ escapeHatchAuditLog: args.escapeHatchAuditLog,
238
242
  actor: args.actor,
239
243
  }),
240
244
  registry,
@@ -1,3 +1,4 @@
1
+ import { type Logger } from "@cosmicdrift/kumiko-framework/logging";
1
2
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
3
  import { type EscapeHatchAuditSink, type Registry, type TenantId, type UserDataExportSnippet } from "@cosmicdrift/kumiko-framework/engine";
3
4
  import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
@@ -8,6 +9,7 @@ export interface RunUserExportArgs {
8
9
  readonly userId: string;
9
10
  readonly now: Instant;
10
11
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
12
+ readonly escapeHatchAuditLog?: Logger;
11
13
  readonly actor?: string;
12
14
  }
13
15
  export interface UserExportFileRef {
@@ -1,3 +1,4 @@
1
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
1
2
  // User-Data-Export-Pipeline (S2.U3) — DSGVO Art. 15 (Auskunft) +
2
3
  // Art. 20 (Datenportabilität).
3
4
  //
@@ -32,6 +33,7 @@ function buildHookDb(db, tenantId, entry, args) {
32
33
  tenantId,
33
34
  actor: args.actor ?? UNATTRIBUTED_ACTOR,
34
35
  sink: args.escapeHatchAuditSink,
36
+ log: args.escapeHatchAuditLog ?? createFallbackLogger("user-data-rights"),
35
37
  });
36
38
  return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
37
39
  unsafeRaw: entry.escapeHatchReason !== undefined ? { reason: entry.escapeHatchReason } : undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.354.1",
3
+ "version": "0.355.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -516,12 +516,12 @@
516
516
  }
517
517
  },
518
518
  "dependencies": {
519
- "@cosmicdrift/kumiko-dispatcher-live": "0.354.1",
520
- "@cosmicdrift/kumiko-framework": "0.354.1",
521
- "@cosmicdrift/kumiko-headless": "0.354.1",
522
- "@cosmicdrift/kumiko-renderer": "0.354.1",
523
- "@cosmicdrift/kumiko-renderer-web": "0.354.1",
524
- "@cosmicdrift/kumiko-types": "0.354.1",
519
+ "@cosmicdrift/kumiko-dispatcher-live": "0.355.0",
520
+ "@cosmicdrift/kumiko-framework": "0.355.0",
521
+ "@cosmicdrift/kumiko-headless": "0.355.0",
522
+ "@cosmicdrift/kumiko-renderer": "0.355.0",
523
+ "@cosmicdrift/kumiko-renderer-web": "0.355.0",
524
+ "@cosmicdrift/kumiko-types": "0.355.0",
525
525
  "@mollie/api-client": "^4.5.0",
526
526
  "@node-rs/argon2": "^2.0.2",
527
527
  "@types/mailparser": "^3.4.6",
@@ -1050,8 +1050,8 @@
1050
1050
  ],
1051
1051
  "devDependencies": {
1052
1052
  "@testing-library/user-event": "^14.6.1",
1053
- "@cosmicdrift/kumiko-locale-de": "0.354.1",
1054
- "@cosmicdrift/kumiko-locale-es": "0.354.1",
1053
+ "@cosmicdrift/kumiko-locale-de": "0.355.0",
1054
+ "@cosmicdrift/kumiko-locale-es": "0.355.0",
1055
1055
  "jsqr": "^1.4.0"
1056
1056
  }
1057
1057
  }