@cosmicdrift/kumiko-samples 0.354.0 → 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.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +1 -1
- package/packages/bundled-features/src/agent-tools/agent-manifest.ts +2 -0
- package/packages/bundled-features/src/agent-tools/types.ts +2 -0
- package/packages/bundled-features/src/audit/escape-hatch-audit-sink.ts +2 -0
- package/packages/bundled-features/src/audit/handlers/resolve-audit-tenant.ts +8 -7
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +7 -12
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +13 -17
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +12 -8
- package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +1 -12
- package/packages/bundled-features/src/delivery/delivery-service.ts +1 -1
- package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +1 -10
- package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +1 -12
- package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +1 -10
- package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +1 -10
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +0 -7
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +0 -7
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +0 -8
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +0 -8
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +0 -7
- package/packages/bundled-features/src/template-resolver/seeding.ts +1 -2
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -0
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +5 -0
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +3 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +2 -0
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +5 -0
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +11 -1
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-samples",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.355.0",
|
|
4
4
|
"description": "Source trees of the Kumiko sample recipes, sample apps and bundled features in repo layout, for tooling such as few-shot corpus builds.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
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>",
|
|
@@ -251,6 +251,7 @@ function buildHandlerEntry(
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
const entity = getHandlerEntity(qn);
|
|
254
|
+
const excludedFields = "excludedFields" in def ? def.excludedFields : undefined;
|
|
254
255
|
return {
|
|
255
256
|
qn,
|
|
256
257
|
kind,
|
|
@@ -258,6 +259,7 @@ function buildHandlerEntry(
|
|
|
258
259
|
risk: exposure.risk,
|
|
259
260
|
inputSchema,
|
|
260
261
|
...(entity !== undefined && { entity }),
|
|
262
|
+
...(excludedFields !== undefined && { excludedFields }),
|
|
261
263
|
};
|
|
262
264
|
}
|
|
263
265
|
|
|
@@ -152,6 +152,8 @@ export type AgentManifestHandler = {
|
|
|
152
152
|
readonly risk: AgentRisk;
|
|
153
153
|
readonly inputSchema: Readonly<Record<string, unknown>>;
|
|
154
154
|
readonly entity?: string;
|
|
155
|
+
/** Fields this generic create/update handler never writes; sending one fails validation. */
|
|
156
|
+
readonly excludedFields?: readonly string[];
|
|
155
157
|
};
|
|
156
158
|
|
|
157
159
|
export type AgentManifestScreen = {
|
|
@@ -23,6 +23,7 @@ export const escapeHatchUsedSchema = z.object({
|
|
|
23
23
|
kind: z.enum(escapeHatchKinds),
|
|
24
24
|
reason: z.string(),
|
|
25
25
|
actor: z.string(),
|
|
26
|
+
caller: z.string().optional(),
|
|
26
27
|
targetUserId: z.string().optional(),
|
|
27
28
|
targetTenantId: z.string().optional(),
|
|
28
29
|
});
|
|
@@ -36,6 +37,7 @@ export function createEscapeHatchAuditSink(opts: {
|
|
|
36
37
|
kind: event.kind,
|
|
37
38
|
reason: event.reason,
|
|
38
39
|
actor: event.actor,
|
|
40
|
+
...(event.caller !== undefined && { caller: event.caller }),
|
|
39
41
|
...(event.target && { targetUserId: event.target.id, targetTenantId: event.target.tenantId }),
|
|
40
42
|
});
|
|
41
43
|
// Appends on the unbound pool, never the handler tx — an audit entry must
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mayOverrideTenant, type SessionUser } 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 { type AuditScope, AuditScopes } from "../constants.js";
|
|
@@ -12,11 +13,11 @@ export function resolveAuditScopeFilter(
|
|
|
12
13
|
scope: AuditScope | undefined,
|
|
13
14
|
): AuditScopeFilter {
|
|
14
15
|
if (scope !== AuditScopes.system) return { tenantId: user.tenantId };
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
if (!mayOverrideTenant(user)) {
|
|
17
|
+
throw new AccessDeniedError({
|
|
18
|
+
i18nKey: "audit.errors.systemScopeRequiresSystemAdmin",
|
|
19
|
+
details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
21
22
|
return { tenantId: SYSTEM_TENANT_ID, aggregateType: APP_INSTANCE_STREAM_TYPE };
|
|
22
23
|
}
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { FeatureRegistrar } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import { createSystemUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
2
|
import { paymentAggregateId, subscriptionAggregateId } from "../aggregate-id.js";
|
|
4
3
|
import { SubscriptionFoundationHandlers, SubscriptionStatuses } from "../constants.js";
|
|
5
4
|
import {
|
|
@@ -75,17 +74,13 @@ export function registerConsumerProtection(
|
|
|
75
74
|
// user), never from payload data.
|
|
76
75
|
const tenantId = ctx.systemUser.tenantId;
|
|
77
76
|
const currentPeriodEndIso = payload["currentPeriodEndIso"];
|
|
78
|
-
const result = await ctx.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
: paymentAggregateId(tenantId),
|
|
86
|
-
...(typeof currentPeriodEndIso === "string" && { currentPeriodEndIso }),
|
|
87
|
-
},
|
|
88
|
-
);
|
|
77
|
+
const result = await ctx.write(SubscriptionFoundationHandlers.issueContractConfirmation, {
|
|
78
|
+
consentId,
|
|
79
|
+
sourceAggregateId: isSubscriptionEvent
|
|
80
|
+
? subscriptionAggregateId(tenantId)
|
|
81
|
+
: paymentAggregateId(tenantId),
|
|
82
|
+
...(typeof currentPeriodEndIso === "string" && { currentPeriodEndIso }),
|
|
83
|
+
});
|
|
89
84
|
if (!result.isSuccess) {
|
|
90
85
|
throw new Error(
|
|
91
86
|
`billing-foundation:${trigger.jobName}: issue-contract-confirmation failed: ${JSON.stringify(result.error)}`,
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FeatureRegistrar } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { SubscriptionCancelTimings, SubscriptionFoundationHandlers } from "../constants.js";
|
|
3
3
|
import {
|
|
4
4
|
CONTRACT_TERMINATION_DECLARED_EVENT_QN,
|
|
@@ -59,22 +59,18 @@ export function registerContractTermination(
|
|
|
59
59
|
const declared = contractTerminationDeclaredPayloadSchema.safeParse(payload);
|
|
60
60
|
// skip: trigger event carries no usable declaration
|
|
61
61
|
if (!declared.success) return;
|
|
62
|
-
const result = await ctx.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
receivedAtIso: declared.data.receivedAtIso,
|
|
75
|
-
locale: declared.data.locale,
|
|
76
|
-
},
|
|
77
|
-
);
|
|
62
|
+
const result = await ctx.write(SubscriptionFoundationHandlers.recordContractTermination, {
|
|
63
|
+
requestId: declared.data.requestId,
|
|
64
|
+
declarationType: declared.data.declarationType,
|
|
65
|
+
terminationKind: declared.data.terminationKind,
|
|
66
|
+
channel: "public",
|
|
67
|
+
when:
|
|
68
|
+
declared.data.declarationType === "withdrawal"
|
|
69
|
+
? "none"
|
|
70
|
+
: SubscriptionCancelTimings.periodEnd,
|
|
71
|
+
receivedAtIso: declared.data.receivedAtIso,
|
|
72
|
+
locale: declared.data.locale,
|
|
73
|
+
});
|
|
78
74
|
if (!result.isSuccess) {
|
|
79
75
|
throw new Error(
|
|
80
76
|
`billing-foundation:cancel-on-public-termination-declared: record-contract-termination failed: ${JSON.stringify(result.error)}`,
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import {
|
|
4
|
-
crossTenantOverrideDenied,
|
|
5
4
|
defineQueryHandler,
|
|
5
|
+
mayOverrideTenant,
|
|
6
6
|
type QueryHandlerDefinition,
|
|
7
7
|
type TenantId,
|
|
8
8
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AccessDeniedError,
|
|
11
|
+
FrameworkReasons,
|
|
12
|
+
InternalError,
|
|
13
|
+
} from "@cosmicdrift/kumiko-framework/errors";
|
|
10
14
|
import * as z from "zod";
|
|
11
15
|
import { tierAssignmentEntity } from "../../tier-engine/index.js";
|
|
12
16
|
import type { CapSpec, CapUsageWithMeta } from "../types.js";
|
|
@@ -39,12 +43,12 @@ export function createCapsUsageQuery(
|
|
|
39
43
|
});
|
|
40
44
|
}
|
|
41
45
|
const override = query.payload.tenantId;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
if (override !== undefined && !mayOverrideTenant(query.user)) {
|
|
47
|
+
throw new AccessDeniedError({
|
|
48
|
+
i18nKey: "cap-overview.errors.tenantOverrideRequiresSystemAdmin",
|
|
49
|
+
details: { reason: FrameworkReasons.tenantOverrideRequiresSystemAdmin },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
48
52
|
|
|
49
53
|
const targetTenantId = override ?? query.user.tenantId;
|
|
50
54
|
// Both branches return the SAME unfiltered system-mode db —
|
|
@@ -4,12 +4,7 @@ import {
|
|
|
4
4
|
SELECTABLE_PROFILE_KEYS,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/compliance";
|
|
6
6
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
7
|
-
import {
|
|
8
|
-
access,
|
|
9
|
-
crossTenantOverrideDenied,
|
|
10
|
-
defineWriteHandler,
|
|
11
|
-
type TenantId,
|
|
12
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { access, defineWriteHandler, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
13
8
|
import {
|
|
14
9
|
UnprocessableError,
|
|
15
10
|
validationErrorFromZod,
|
|
@@ -63,12 +58,6 @@ export const setProfileWrite = defineWriteHandler({
|
|
|
63
58
|
"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.",
|
|
64
59
|
handler: async (event, ctx) => {
|
|
65
60
|
const tenantOverride = event.payload.tenantIdOverride;
|
|
66
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
67
|
-
event.user,
|
|
68
|
-
tenantOverride,
|
|
69
|
-
"complianceProfiles.errors.tenantOverrideRequiresSystemAdmin",
|
|
70
|
-
);
|
|
71
|
-
if (overrideDenied) return writeFailure(overrideDenied);
|
|
72
61
|
const tenantId = (tenantOverride ?? event.user.tenantId) as TenantId; // @cast-boundary engine-payload
|
|
73
62
|
const executorUser = tenantOverride !== undefined ? { ...event.user, tenantId } : event.user;
|
|
74
63
|
|
|
@@ -190,7 +190,7 @@ export function createDeliveryService(options: DeliveryServiceOptions): Delivery
|
|
|
190
190
|
tenantId,
|
|
191
191
|
actor: systemUser.id,
|
|
192
192
|
sink: escapeHatchAuditSink,
|
|
193
|
-
log,
|
|
193
|
+
log: logError,
|
|
194
194
|
});
|
|
195
195
|
// ctx.db/dbOutsideTransaction are tenant-filtered even for a systemScope handler:
|
|
196
196
|
// the dispatcher closes them off entirely there, so "system" mode would hand this
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import {
|
|
4
|
-
crossTenantOverrideDenied,
|
|
5
|
-
defineQueryHandler,
|
|
6
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
4
|
import * as z from "zod";
|
|
8
5
|
import { type PageRow, pagesTable } from "../table.js";
|
|
9
6
|
|
|
@@ -33,12 +30,6 @@ export const byTenantPublishedQuery = defineQueryHandler({
|
|
|
33
30
|
"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.",
|
|
34
31
|
handler: async (query, ctx) => {
|
|
35
32
|
const override = query.payload.tenantIdOverride;
|
|
36
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
37
|
-
query.user,
|
|
38
|
-
override,
|
|
39
|
-
"managedPages.errors.tenantOverrideRequiresSystemAdmin",
|
|
40
|
-
);
|
|
41
|
-
if (overrideDenied) throw overrideDenied;
|
|
42
33
|
const tenantId = override ?? query.user.tenantId;
|
|
43
34
|
const rows = castTenantRows<PageRow>(
|
|
44
35
|
await selectMany(ctx.db, pagesTable, { tenantId: tenantId, published: true }),
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import {
|
|
4
|
-
crossTenantOverrideDenied,
|
|
5
|
-
defineWriteHandler,
|
|
6
|
-
type TenantId,
|
|
7
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
-
import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { defineWriteHandler, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
9
4
|
import * as z from "zod";
|
|
10
5
|
import { type PageRow, pageEntity, pagesTable } from "../table.js";
|
|
11
6
|
|
|
@@ -55,12 +50,6 @@ export const setWrite = defineWriteHandler({
|
|
|
55
50
|
handler: async (event, ctx) => {
|
|
56
51
|
const db = ctx.db;
|
|
57
52
|
const override = event.payload.tenantIdOverride;
|
|
58
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
59
|
-
event.user,
|
|
60
|
-
override,
|
|
61
|
-
"managedPages.errors.tenantOverrideRequiresSystemAdmin",
|
|
62
|
-
);
|
|
63
|
-
if (overrideDenied) return writeFailure(overrideDenied);
|
|
64
53
|
const tenantId = override ?? event.user.tenantId;
|
|
65
54
|
// override: point the executor context at the target tenant, else getStreamVersion runs against user.tenantId → version_conflict.
|
|
66
55
|
const executorUser =
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
-
import {
|
|
3
|
-
crossTenantOverrideDenied,
|
|
4
|
-
defineQueryHandler,
|
|
5
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
6
3
|
import * as z from "zod";
|
|
7
4
|
import { TEXT_BLOCK_KIND } from "../constants.js";
|
|
8
5
|
import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
|
|
@@ -33,12 +30,6 @@ export const bySlugQuery = defineQueryHandler({
|
|
|
33
30
|
"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.",
|
|
34
31
|
handler: async (query, ctx) => {
|
|
35
32
|
const override = query.payload.tenantIdOverride;
|
|
36
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
37
|
-
query.user,
|
|
38
|
-
override,
|
|
39
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
40
|
-
);
|
|
41
|
-
if (overrideDenied) throw overrideDenied;
|
|
42
33
|
const tenantId = override ?? query.user.tenantId;
|
|
43
34
|
const row = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
|
|
44
35
|
tenantId,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
-
import {
|
|
4
|
-
crossTenantOverrideDenied,
|
|
5
|
-
defineQueryHandler,
|
|
6
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
4
|
import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
8
5
|
import * as z from "zod";
|
|
9
6
|
import { TEXT_BLOCK_KIND } from "../constants.js";
|
|
@@ -39,12 +36,6 @@ export const byTenantQuery = defineQueryHandler({
|
|
|
39
36
|
"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.",
|
|
40
37
|
handler: async (query, ctx) => {
|
|
41
38
|
const override = query.payload.tenantIdOverride;
|
|
42
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
43
|
-
query.user,
|
|
44
|
-
override,
|
|
45
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
46
|
-
);
|
|
47
|
-
if (overrideDenied) throw overrideDenied;
|
|
48
39
|
const tenantId = override ?? query.user.tenantId;
|
|
49
40
|
const rows = castTenantRows<TemplateResourceRow>(
|
|
50
41
|
await selectMany(ctx.db, templateResourcesTable, { tenantId, kind: TEXT_BLOCK_KIND }),
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import {
|
|
3
3
|
type ContentCollectionDefinition,
|
|
4
|
-
crossTenantOverrideDenied,
|
|
5
4
|
defineQueryHandler,
|
|
6
5
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
7
6
|
import * as z from "zod";
|
|
@@ -31,12 +30,6 @@ export function makeCollectionItemQuery(collection: ContentCollectionDefinition)
|
|
|
31
30
|
: `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.`,
|
|
32
31
|
handler: async (query, ctx) => {
|
|
33
32
|
const override = query.payload.tenantIdOverride;
|
|
34
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
35
|
-
query.user,
|
|
36
|
-
override,
|
|
37
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
38
|
-
);
|
|
39
|
-
if (overrideDenied) throw overrideDenied;
|
|
40
33
|
const tenantId = override ?? query.user.tenantId;
|
|
41
34
|
const row = await fetchOne<CollectionEntryRow>(ctx.db, store.table, {
|
|
42
35
|
tenantId,
|
|
@@ -2,7 +2,6 @@ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
|
2
2
|
import { castTenantRows } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import {
|
|
4
4
|
type ContentCollectionDefinition,
|
|
5
|
-
crossTenantOverrideDenied,
|
|
6
5
|
defineQueryHandler,
|
|
7
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
7
|
import * as z from "zod";
|
|
@@ -36,12 +35,6 @@ export function makeCollectionListQuery(collection: ContentCollectionDefinition)
|
|
|
36
35
|
: `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.`,
|
|
37
36
|
handler: async (query, ctx) => {
|
|
38
37
|
const override = query.payload.tenantIdOverride;
|
|
39
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
40
|
-
query.user,
|
|
41
|
-
override,
|
|
42
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
43
|
-
);
|
|
44
|
-
if (overrideDenied) throw overrideDenied;
|
|
45
38
|
const tenantId = override ?? query.user.tenantId;
|
|
46
39
|
const rows = castTenantRows<CollectionEntryRow>(
|
|
47
40
|
await selectMany(ctx.db, store.table, {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import {
|
|
3
3
|
type ContentCollectionDefinition,
|
|
4
|
-
crossTenantOverrideDenied,
|
|
5
4
|
defineWriteHandler,
|
|
6
5
|
type TenantId,
|
|
7
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
-
import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
9
7
|
import * as z from "zod";
|
|
10
8
|
import {
|
|
11
9
|
type CollectionEntryRow,
|
|
@@ -43,12 +41,6 @@ export function makeCollectionSetWrite(collection: ContentCollectionDefinition)
|
|
|
43
41
|
handler: async (event, ctx) => {
|
|
44
42
|
const db = ctx.db;
|
|
45
43
|
const override = event.payload.tenantIdOverride;
|
|
46
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
47
|
-
event.user,
|
|
48
|
-
override,
|
|
49
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
50
|
-
);
|
|
51
|
-
if (overrideDenied) return writeFailure(overrideDenied);
|
|
52
44
|
// @cast-boundary engine-payload — override is a zod-validated string, the
|
|
53
45
|
// user's own tenantId is already TenantId-branded.
|
|
54
46
|
const tenantId = (override ?? event.user.tenantId) as TenantId;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import {
|
|
3
|
-
crossTenantOverrideDenied,
|
|
4
3
|
defineWriteHandler,
|
|
5
4
|
SYSTEM_TENANT_ID,
|
|
6
5
|
type TenantId,
|
|
7
6
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
-
import { writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
9
7
|
import * as z from "zod";
|
|
10
8
|
import { TEMPLATE_KINDS, TEXT_BLOCK_KIND } from "../constants.js";
|
|
11
9
|
import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
|
|
@@ -52,12 +50,6 @@ export const setWrite = defineWriteHandler({
|
|
|
52
50
|
handler: async (event, ctx) => {
|
|
53
51
|
const db = ctx.db;
|
|
54
52
|
const override = event.payload.tenantIdOverride;
|
|
55
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
56
|
-
event.user,
|
|
57
|
-
override,
|
|
58
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
59
|
-
);
|
|
60
|
-
if (overrideDenied) return writeFailure(overrideDenied);
|
|
61
53
|
// @cast-boundary engine-payload — override is a zod-validated string, the
|
|
62
54
|
// user's own tenantId is already TenantId-branded.
|
|
63
55
|
const tenantId = (override ?? event.user.tenantId) as TenantId;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import {
|
|
3
|
-
crossTenantOverrideDenied,
|
|
4
3
|
defineWriteHandler,
|
|
5
4
|
SYSTEM_TENANT_ID,
|
|
6
5
|
type TenantId,
|
|
@@ -28,12 +27,6 @@ export const upsertTenantWrite = defineWriteHandler({
|
|
|
28
27
|
handler: async (event, ctx) => {
|
|
29
28
|
const db = ctx.db;
|
|
30
29
|
const override = event.payload.tenantIdOverride;
|
|
31
|
-
const overrideDenied = crossTenantOverrideDenied(
|
|
32
|
-
event.user,
|
|
33
|
-
override,
|
|
34
|
-
"templateResolver.errors.tenantOverrideRequiresSystemAdmin",
|
|
35
|
-
);
|
|
36
|
-
if (overrideDenied) return writeFailure(overrideDenied);
|
|
37
30
|
// upsertTenant erzeugt scope='tenant'. SYSTEM_TENANT_ID-Override würde
|
|
38
31
|
// scope='tenant' unter SYSTEM_TENANT_ID schreiben → inkonsistenter Zustand
|
|
39
32
|
// (Resolver-Logik trennt sauber zwischen system+tenant). SystemAdmin muss
|
|
@@ -51,8 +51,7 @@ async function resolveExistingForEventStoreSeed(
|
|
|
51
51
|
`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`,
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
|
-
//
|
|
55
|
-
// plain table ident trips guard-direct-entity-writes (infra EXCLUDE follow-up).
|
|
54
|
+
// The cast strips the executor-only brand that WritableTable rejects; the orphan row has no events, so no executor verb applies.
|
|
56
55
|
await deleteMany(tdb, templateResourcesTable as EntityTableMeta, { id, tenantId });
|
|
57
56
|
return null;
|
|
58
57
|
}
|
|
@@ -155,6 +155,7 @@ export function createTenantLifecycleFeature(): FeatureDefinition {
|
|
|
155
155
|
fileProviderResolver: ctx._fileProviderResolver,
|
|
156
156
|
searchAdapter: ctx.searchAdapter,
|
|
157
157
|
escapeHatchAuditSink: ctx._escapeHatchAuditSink,
|
|
158
|
+
escapeHatchAuditLog: ctx.log,
|
|
158
159
|
actor: ctx.systemUser.id,
|
|
159
160
|
});
|
|
160
161
|
},
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
VersionConflictError,
|
|
15
15
|
} from "@cosmicdrift/kumiko-framework/event-store";
|
|
16
16
|
import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
|
|
17
|
+
import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
17
18
|
import type { SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
|
|
18
19
|
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
19
20
|
import { tenantEntity, tenantTable } from "../tenant/index.js";
|
|
@@ -283,6 +284,7 @@ export async function runNextDestructionStage(args: {
|
|
|
283
284
|
readonly fileProviderResolver?: FileProviderResolver;
|
|
284
285
|
readonly searchAdapter?: SearchAdapter;
|
|
285
286
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
287
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
286
288
|
readonly actor?: string;
|
|
287
289
|
}): Promise<StageTickResult> {
|
|
288
290
|
const events = await loadAggregate(args.db, args.tenantId, args.tenantId);
|
|
@@ -308,6 +310,7 @@ export async function runNextDestructionStage(args: {
|
|
|
308
310
|
fileProviderResolver: args.fileProviderResolver,
|
|
309
311
|
searchAdapter: args.searchAdapter,
|
|
310
312
|
escapeHatchAuditSink: args.escapeHatchAuditSink,
|
|
313
|
+
escapeHatchAuditLog: args.escapeHatchAuditLog,
|
|
311
314
|
actor: args.actor,
|
|
312
315
|
deadlineAt: Date.now() + DESTRUCTION_STAGE_TICK_BUDGET_MS,
|
|
313
316
|
};
|
|
@@ -410,6 +413,7 @@ export async function runTenantDestructionSweep(args: {
|
|
|
410
413
|
readonly fileProviderResolver?: FileProviderResolver;
|
|
411
414
|
readonly searchAdapter?: SearchAdapter;
|
|
412
415
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
416
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
413
417
|
readonly actor?: string;
|
|
414
418
|
}): Promise<{ readonly triggered: number; readonly advanced: number }> {
|
|
415
419
|
const T = getTemporal();
|
|
@@ -457,6 +461,7 @@ export async function runTenantDestructionSweep(args: {
|
|
|
457
461
|
fileProviderResolver: args.fileProviderResolver,
|
|
458
462
|
searchAdapter: args.searchAdapter,
|
|
459
463
|
escapeHatchAuditSink: args.escapeHatchAuditSink,
|
|
464
|
+
escapeHatchAuditLog: args.escapeHatchAuditLog,
|
|
460
465
|
actor: args.actor,
|
|
461
466
|
});
|
|
462
467
|
if (!result.error && !result.halted) advanced++;
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type TenantResourceExtensionName,
|
|
28
28
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
29
29
|
import type { FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
|
|
30
|
+
import { createFallbackLogger, type Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
30
31
|
import {
|
|
31
32
|
createEscapeHatchReporter,
|
|
32
33
|
UNATTRIBUTED_ACTOR,
|
|
@@ -61,6 +62,7 @@ export type DestructionStageCtx = {
|
|
|
61
62
|
// systemUser.id); runTenantDataHooks uses them to attribute+audit any
|
|
62
63
|
// EXT_TENANT_DATA usage's declared escapeHatch.
|
|
63
64
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
65
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
64
66
|
readonly actor?: string;
|
|
65
67
|
// Epoch ms the stage must hand back by returning { done: false }.
|
|
66
68
|
readonly deadlineAt: number;
|
|
@@ -136,6 +138,7 @@ async function runTenantDataHooks(ctx: DestructionStageCtx): Promise<StageRunOut
|
|
|
136
138
|
tenantId: ctx.tenantId,
|
|
137
139
|
actor: ctx.actor ?? UNATTRIBUTED_ACTOR,
|
|
138
140
|
sink: ctx.escapeHatchAuditSink,
|
|
141
|
+
log: ctx.escapeHatchAuditLog ?? createFallbackLogger("tenant-lifecycle"),
|
|
139
142
|
});
|
|
140
143
|
const hookCtx: TenantDataHookCtx = {
|
|
141
144
|
db: createTenantDb(ctx.db, ctx.tenantId, "tenant", undefined, undefined, undefined, {
|
|
@@ -663,6 +663,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
663
663
|
appExportDownloadUrl: opts.appExportDownloadUrl,
|
|
664
664
|
}),
|
|
665
665
|
escapeHatchAuditSink: ctx._escapeHatchAuditSink,
|
|
666
|
+
escapeHatchAuditLog: ctx.log,
|
|
666
667
|
actor: ctx.systemUser.id,
|
|
667
668
|
metrics: exportMetrics,
|
|
668
669
|
});
|
|
@@ -732,6 +733,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
|
|
|
732
733
|
}),
|
|
733
734
|
...(sendDeletionExecutedEmail && { sendDeletionExecutedEmail }),
|
|
734
735
|
escapeHatchAuditSink: ctx._escapeHatchAuditSink,
|
|
736
|
+
escapeHatchAuditLog: ctx.log,
|
|
735
737
|
actor: ctx.systemUser.id,
|
|
736
738
|
});
|
|
737
739
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
1
2
|
// Async Export-Job Worker (S2.U3 Atom 3b) — pure Pipeline-Function.
|
|
2
3
|
//
|
|
3
4
|
// Spec: docs/plans/architecture/user-data-rights.md "Async Export-Pipeline".
|
|
@@ -176,6 +177,7 @@ export interface RunExportJobsArgs {
|
|
|
176
177
|
// fw#2914 — sourced from the owning job's ctx (_escapeHatchAuditSink,
|
|
177
178
|
// systemUser.id); threaded into runUserExport's per-hook TenantDb.
|
|
178
179
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
180
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
179
181
|
readonly actor?: string;
|
|
180
182
|
|
|
181
183
|
/**
|
|
@@ -248,6 +250,7 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
248
250
|
now,
|
|
249
251
|
job,
|
|
250
252
|
escapeHatchAuditSink: args.escapeHatchAuditSink,
|
|
253
|
+
escapeHatchAuditLog: args.escapeHatchAuditLog,
|
|
251
254
|
actor: args.actor,
|
|
252
255
|
});
|
|
253
256
|
if (outcome.kind === "done") {
|
|
@@ -369,6 +372,7 @@ async function processJob(args: {
|
|
|
369
372
|
now: Instant;
|
|
370
373
|
job: JobRow;
|
|
371
374
|
escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
375
|
+
escapeHatchAuditLog?: Logger;
|
|
372
376
|
actor?: string;
|
|
373
377
|
}): Promise<ProcessOutcome> {
|
|
374
378
|
const { db, registry, buildStorageProvider, now, job } = args;
|
|
@@ -415,6 +419,7 @@ async function processJob(args: {
|
|
|
415
419
|
userId: job.userId,
|
|
416
420
|
now,
|
|
417
421
|
escapeHatchAuditSink: args.escapeHatchAuditSink,
|
|
422
|
+
escapeHatchAuditLog: args.escapeHatchAuditLog,
|
|
418
423
|
actor: args.actor,
|
|
419
424
|
});
|
|
420
425
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createFallbackLogger, type Logger } 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
|
|
@@ -157,6 +158,7 @@ export interface RunForgetCleanupArgs {
|
|
|
157
158
|
// systemUser.id); attributes+audits any EXT_USER_DATA usage's declared
|
|
158
159
|
// escapeHatch when its delete hook calls ctx.db.unsafeRaw().
|
|
159
160
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
161
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
160
162
|
readonly actor?: string;
|
|
161
163
|
}
|
|
162
164
|
|
|
@@ -209,13 +211,18 @@ function buildHookDb(
|
|
|
209
211
|
db: DbRunner,
|
|
210
212
|
tenantId: TenantId,
|
|
211
213
|
entry: HookEntry,
|
|
212
|
-
args: {
|
|
214
|
+
args: {
|
|
215
|
+
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
216
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
217
|
+
readonly actor?: string;
|
|
218
|
+
},
|
|
213
219
|
) {
|
|
214
220
|
const report = createEscapeHatchReporter({
|
|
215
221
|
handler: `${EXT_USER_DATA}:${entry.entityName}`,
|
|
216
222
|
tenantId,
|
|
217
223
|
actor: args.actor ?? UNATTRIBUTED_ACTOR,
|
|
218
224
|
sink: args.escapeHatchAuditSink,
|
|
225
|
+
log: args.escapeHatchAuditLog ?? createFallbackLogger("user-data-rights"),
|
|
219
226
|
});
|
|
220
227
|
return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
|
|
221
228
|
unsafeRaw:
|
|
@@ -296,6 +303,7 @@ export async function runForgetCleanup(
|
|
|
296
303
|
kms,
|
|
297
304
|
searchAdapter: args.searchAdapter,
|
|
298
305
|
escapeHatchAuditSink: args.escapeHatchAuditSink,
|
|
306
|
+
escapeHatchAuditLog: args.escapeHatchAuditLog,
|
|
299
307
|
actor: args.actor,
|
|
300
308
|
});
|
|
301
309
|
hookCallsAttempted += userResult.hookCallsAttempted;
|
|
@@ -362,6 +370,7 @@ async function processUser(args: {
|
|
|
362
370
|
kms?: KmsAdapter;
|
|
363
371
|
searchAdapter?: SearchAdapter;
|
|
364
372
|
escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
373
|
+
escapeHatchAuditLog?: Logger;
|
|
365
374
|
actor?: string;
|
|
366
375
|
}): Promise<ProcessUserResult> {
|
|
367
376
|
const { db, registry, userId, hookEntries, buildStorageProvider, appTenantModel, kms } = args;
|
|
@@ -460,6 +469,7 @@ async function processUser(args: {
|
|
|
460
469
|
{
|
|
461
470
|
db: buildHookDb(tx, tenantId, entry, {
|
|
462
471
|
escapeHatchAuditSink: args.escapeHatchAuditSink,
|
|
472
|
+
escapeHatchAuditLog: args.escapeHatchAuditLog,
|
|
463
473
|
actor: args.actor,
|
|
464
474
|
}),
|
|
465
475
|
registry,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createFallbackLogger, type Logger } 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
|
//
|
|
@@ -62,6 +63,7 @@ export interface RunUserExportArgs {
|
|
|
62
63
|
// systemUser.id); attributes+audits any EXT_USER_DATA usage's declared
|
|
63
64
|
// escapeHatch when its export hook calls ctx.db.unsafeRaw().
|
|
64
65
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
66
|
+
readonly escapeHatchAuditLog?: Logger;
|
|
65
67
|
readonly actor?: string;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -110,13 +112,14 @@ function buildHookDb(
|
|
|
110
112
|
db: DbRunner,
|
|
111
113
|
tenantId: TenantId,
|
|
112
114
|
entry: HookEntry,
|
|
113
|
-
args: Pick<RunUserExportArgs, "escapeHatchAuditSink" | "actor">,
|
|
115
|
+
args: Pick<RunUserExportArgs, "escapeHatchAuditSink" | "escapeHatchAuditLog" | "actor">,
|
|
114
116
|
) {
|
|
115
117
|
const report = createEscapeHatchReporter({
|
|
116
118
|
handler: `${EXT_USER_DATA}:${entry.entityName}`,
|
|
117
119
|
tenantId,
|
|
118
120
|
actor: args.actor ?? UNATTRIBUTED_ACTOR,
|
|
119
121
|
sink: args.escapeHatchAuditSink,
|
|
122
|
+
log: args.escapeHatchAuditLog ?? createFallbackLogger("user-data-rights"),
|
|
120
123
|
});
|
|
121
124
|
return createTenantDb(db, tenantId, "tenant", undefined, undefined, undefined, {
|
|
122
125
|
unsafeRaw:
|