@cosmicdrift/kumiko-types 0.337.1 → 0.339.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/dist/config.d.ts CHANGED
@@ -25,6 +25,14 @@ export type ConfigComputedContext = {
25
25
  readonly db: DbConnection | TenantDb;
26
26
  };
27
27
  export type ConfigComputedFn<T extends ConfigKeyType = ConfigKeyType> = (ctx: ConfigComputedContext) => Promise<ConfigValue<T>>;
28
+ export type ConfigValidateContext = {
29
+ readonly key: string;
30
+ readonly scope: ConfigScope;
31
+ readonly tenantId: TenantId;
32
+ readonly userId: string | null;
33
+ readonly db: DbConnection | TenantDb;
34
+ };
35
+ export type ConfigValidateFn<T extends ConfigKeyType = ConfigKeyType> = (value: ConfigValue<T>, ctx: ConfigValidateContext) => Promise<void>;
28
36
  export type ConfigBacking = "config" | "secrets";
29
37
  export type ConfigSecretsReader = {
30
38
  get(tenantId: TenantId, key: string): Promise<{
@@ -60,6 +68,7 @@ export type ConfigKeyDefinition<T extends ConfigKeyType = ConfigKeyType> = {
60
68
  readonly flags?: string;
61
69
  };
62
70
  readonly computed?: ConfigComputedFn<T>;
71
+ readonly validate?: ConfigValidateFn<T>;
63
72
  readonly allowPerRequest?: boolean;
64
73
  readonly required?: boolean;
65
74
  readonly env?: string;
@@ -12,6 +12,7 @@ export type WriteHandlerDefinition<TName extends string = string, TSchema extend
12
12
  readonly unsafeSkipTransitionGuard?: boolean;
13
13
  readonly rateLimit?: RateLimitDeclaration;
14
14
  readonly escapeHatch?: EscapeHatchDeclaration;
15
+ readonly tenantlessAnonymous?: boolean;
15
16
  readonly handler: (event: WriteEvent<z.infer<TSchema>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
16
17
  readonly perform?: PipelineDef<z.infer<TSchema>, TData>;
17
18
  };
@@ -24,6 +25,7 @@ export type WriteHandlerInput<TName extends string = string, TSchema extends Zod
24
25
  readonly unsafeSkipTransitionGuard?: boolean;
25
26
  readonly rateLimit?: RateLimitDeclaration;
26
27
  readonly escapeHatch?: EscapeHatchDeclaration;
28
+ readonly tenantlessAnonymous?: boolean;
27
29
  } & ({
28
30
  readonly handler: (event: WriteEvent<z.infer<TSchema>>, context: HandlerContext<TMap>) => Promise<WriteResult<TData>>;
29
31
  readonly perform?: never;
@@ -28,9 +28,18 @@ export type RoleAccessRule = {
28
28
  readonly personalData?: RoleAccessPersonalData;
29
29
  };
30
30
  export type AccessRule = RoleAccessRule | OpenToAllAccessRule;
31
+ export declare const ESCAPE_HATCH_GRANTS: readonly ["systemIdentity", "globalWrites", "unsafeRaw"];
32
+ export type EscapeHatchGrant = (typeof ESCAPE_HATCH_GRANTS)[number];
31
33
  export type EscapeHatchDeclaration = {
32
34
  readonly reason: string;
33
- };
35
+ readonly grants?: readonly EscapeHatchGrant[];
36
+ };
37
+ export declare function isEscapeHatchGrant(value: unknown): value is EscapeHatchGrant;
38
+ export declare function escapeHatchAllows(declaration: EscapeHatchDeclaration | undefined, grant: EscapeHatchGrant): boolean;
39
+ export declare function escapeHatchFor(declaration: EscapeHatchDeclaration | undefined, grant: EscapeHatchGrant): EscapeHatchDeclaration | undefined;
40
+ export declare function escapeHatchGrantsProblem(declaration: {
41
+ readonly grants?: unknown;
42
+ }): string | undefined;
34
43
  export declare function isOpenToAllGranted(rule: AccessRule): boolean;
35
44
  export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
36
45
  export type SessionUserOrigin = "member-resolution";
@@ -149,6 +158,14 @@ type SharedContextFields = {
149
158
  readonly jobRunner?: JobRunnerRef;
150
159
  readonly configResolver?: ConfigResolver;
151
160
  readonly config?: ConfigAccessor;
161
+ /**
162
+ * Config accessor resolved for ANOTHER tenant, identity = that tenant's system
163
+ * user (the caller's user-scope config never leaks in). Cross-tenant calls
164
+ * throw AccessDeniedError unless the caller is a system identity or has the
165
+ * SystemAdmin role; the caller's own tenant needs no privilege. Present when
166
+ * the config feature wired its accessor factory.
167
+ */
168
+ readonly configFor?: (tenantId: TenantId) => ConfigAccessor;
152
169
  readonly _configAccessorFactory?: ConfigAccessorFactory;
153
170
  readonly configEncryption?: import("./envelope-cipher-types.js").EnvelopeCipher;
154
171
  readonly rateLimit?: import("./rate-limit-types.js").RateLimitResolver;
@@ -456,6 +473,10 @@ export type WriteHandlerDef = {
456
473
  readonly unsafeSkipTransitionGuard?: boolean;
457
474
  readonly rateLimit?: RateLimitDeclaration;
458
475
  readonly escapeHatch?: EscapeHatchDeclaration;
476
+ /** Lets an anonymous `POST /api/write` for this handler proceed under
477
+ * SYSTEM_TENANT_ID when the host resolves no tenant. Boot requires
478
+ * `access.roles: ["anonymous"]` and a real `rateLimit`. */
479
+ readonly tenantlessAnonymous?: boolean;
459
480
  readonly perform?: import("./step.js").PipelineDef;
460
481
  };
461
482
  export declare const ENTITY_CONVENTION_QUERY_BRAND = "__kumikoEntityConventionQuery";
package/dist/handlers.js CHANGED
@@ -1,3 +1,31 @@
1
+ export const ESCAPE_HATCH_GRANTS = ["systemIdentity", "globalWrites", "unsafeRaw"];
2
+ export function isEscapeHatchGrant(value) {
3
+ return ESCAPE_HATCH_GRANTS.some((grant) => grant === value);
4
+ }
5
+ export function escapeHatchAllows(declaration, grant) {
6
+ if (declaration === undefined)
7
+ return false;
8
+ return declaration.grants === undefined || declaration.grants.includes(grant);
9
+ }
10
+ // Returns the declaration only when it unlocks `grant`, so call sites keep passing
11
+ // "a declaration or nothing" into grant-gated code.
12
+ export function escapeHatchFor(declaration, grant) {
13
+ return escapeHatchAllows(declaration, grant) ? declaration : undefined;
14
+ }
15
+ // Boot-time check of `grants` (untyped sources can carry anything): must be a non-empty list of known grants.
16
+ export function escapeHatchGrantsProblem(declaration) {
17
+ const { grants } = declaration;
18
+ if (grants === undefined)
19
+ return undefined;
20
+ if (!Array.isArray(grants) || grants.length === 0) {
21
+ return `grants must be a non-empty list of: ${ESCAPE_HATCH_GRANTS.join(", ")}`;
22
+ }
23
+ const unknownGrant = grants.find((grant) => !isEscapeHatchGrant(grant));
24
+ if (unknownGrant !== undefined) {
25
+ return `unknown grant ${JSON.stringify(unknownGrant)} — allowed: ${ESCAPE_HATCH_GRANTS.join(", ")}`;
26
+ }
27
+ return undefined;
28
+ }
1
29
  // AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
2
30
  // where `openToAll` doesn't actually match the declared union — narrow via
3
31
  // `unknown` instead of trusting the static type, deny on anything malformed.
@@ -34,9 +34,9 @@ export type TenantDb = {
34
34
  readonly mode: TenantDbMode;
35
35
  /**
36
36
  * Unfiltered DbRunner escape hatch for handlers/hooks that declare `escapeHatch: { reason }`.
37
- * Throws `AccessDeniedError` when ungranted, or `Error` when `reason` is empty.
37
+ * Throws `AccessDeniedError` when ungranted. The audit entry carries the declared escapeHatch reason.
38
38
  */
39
- unsafeRaw(reason: string): DbRunner;
39
+ unsafeRaw(): DbRunner;
40
40
  /**
41
41
  * Reach a "global" table with the tenant filter lifted — reads always work; writes
42
42
  * reject unless the write handler declared `escapeHatch: { reason }`. "tenant"-tenancy is a compile error here.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.337.1",
3
+ "version": "0.339.0",
4
4
  "description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
package/src/changes.json CHANGED
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.338.0",
4
+ "type": "improvement",
5
+ "title": "ctx.configFor on the handler context types",
6
+ "detail": "`configFor?: (tenantId: TenantId) => ConfigAccessor` is part of the shared handler context fields, optional like `config`.",
7
+ "migration": "No code change needed."
8
+ },
2
9
  {
3
10
  "version": "0.337.0",
4
11
  "type": "improvement",