@cosmicdrift/kumiko-types 0.351.0 → 0.353.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import type { AgentExposure, AgentHandlerHints } from "./handlers.js";
1
+ import type { AgentExposure, AgentHandlerHints, AgentScreenHints } from "./handlers.js";
2
2
  /** Fail-closed: a handler without a `description` stays invisible to the agent
3
3
  * unless it opts in explicitly. `kind` is a parameter because write and query
4
4
  * handler definitions are structurally indistinguishable at runtime. */
@@ -11,5 +11,5 @@ export declare function resolveAgentExposure(def: {
11
11
  * Reusing `resolveAgentExposure`'s fail-closed default here would silently
12
12
  * drop every screen without a description from the agent's view. */
13
13
  export declare function isAgentVisibleScreen(screen: {
14
- readonly agent?: AgentHandlerHints;
14
+ readonly agent?: AgentScreenHints;
15
15
  }): boolean;
@@ -43,6 +43,9 @@ export type RegisterEntityCrudOptions = {
43
43
  * manifest exposes only the verbs described here (fail-closed, same rule as
44
44
  * hand-written handlers). Falls back to `write.description`/`read.description`. */
45
45
  readonly descriptions?: Partial<Record<EntityCrudVerb, string>>;
46
+ /** Per-verb agent hints; win over `write.agent`/`read.agent`, so one irreversible
47
+ * verb (e.g. a hard delete) can be `risk: "high"` without raising create/update. */
48
+ readonly agents?: Partial<Record<EntityCrudVerb, AgentHandlerHints>>;
46
49
  /** Per-verb `excludeFields` for the generic create/update handlers — see
47
50
  * EntityWriteHandlerOptions. */
48
51
  readonly excludeFields?: Partial<Record<"create" | "update", readonly string[]>>;
package/dist/fields.d.ts CHANGED
@@ -80,6 +80,9 @@ export type TextFieldDef = {
80
80
  * the i18n label is what users see. */
81
81
  readonly description?: string;
82
82
  readonly maxLength?: number;
83
+ /** Lower bound enforced by the generated write schema, so short input is
84
+ * rejected by validation instead of reaching the handler. */
85
+ readonly minLength?: number;
83
86
  readonly required?: boolean;
84
87
  readonly searchable?: boolean;
85
88
  readonly sortable?: boolean;
@@ -171,7 +174,8 @@ export type BooleanFieldDef = {
171
174
  readonly default?: boolean;
172
175
  readonly access?: FieldAccess;
173
176
  };
174
- export type SelectOptionTone = "ok" | "warn" | "bad" | "neutral";
177
+ export declare const SELECT_OPTION_TONES: readonly ["ok", "warn", "bad", "neutral"];
178
+ export type SelectOptionTone = (typeof SELECT_OPTION_TONES)[number];
175
179
  /** One `optionsQueryPayload` entry: a literal, or `{ field }` = the current value of
176
180
  * a sibling field of the same form (config keys: another key of the owner feature). */
177
181
  export type OptionsQueryPayloadValue = string | number | boolean | {
@@ -213,6 +217,14 @@ export type SelectFieldDef<TOptions extends readonly string[] = readonly string[
213
217
  /** Payload sent with every `optionsQuery` call. `{ field }` entries re-load the
214
218
  * options when that sibling field changes. */
215
219
  readonly optionsQueryPayload?: OptionsQueryPayload;
220
+ /** Marks static options as unavailable per tenant (e.g. by tier). Contract:
221
+ * `{ rows: { value: string; disabled?: boolean; hint?: string }[] }`. The renderer
222
+ * loads it (with `optionsQueryPayload` resolved like for `optionsQuery`, `{}` when
223
+ * absent) and merges it onto `options`: `disabled` disables the option, `hint` becomes
224
+ * its description. The currently stored value always stays enabled; unknown values
225
+ * are ignored. Static `options` stay authoritative, and the write path must enforce
226
+ * the same rule (see `withTierOptionGate`), the renderer only mirrors it. */
227
+ readonly optionsAvailabilityQuery?: string;
216
228
  } & ResolvedPiiFlags;
217
229
  export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly string[]> = {
218
230
  readonly type: "multiSelect";
package/dist/fields.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // --- Field Types ---
2
+ export const SELECT_OPTION_TONES = ["ok", "warn", "bad", "neutral"];
2
3
  export function isOptionsQueryFieldRef(value) {
3
4
  return typeof value === "object" && value !== null;
4
5
  }
@@ -2,7 +2,7 @@ import type { Redis } from "ioredis";
2
2
  import type { ZodType } from "zod";
3
3
  import type { CacheSyncBus } from "./cache-sync-types.js";
4
4
  import type { ConfigAccessor, ConfigAccessorFactory, ConfigResolver } from "./config.js";
5
- import type { DbConnection } from "./db-connection.js";
5
+ import type { DbConnection, DbTx } from "./db-connection.js";
6
6
  import type { DerivativesContext } from "./derivatives-types.js";
7
7
  import type { EntityCache } from "./entity-cache.js";
8
8
  import type { EventMetadata, WriteOrigin } from "./event-store-types.js";
@@ -404,7 +404,7 @@ export type EventDef<TPayload = unknown, TName extends string = string> = {
404
404
  readonly name: TName;
405
405
  readonly schema: ZodType<TPayload>;
406
406
  readonly version: number;
407
- readonly piiFields?: EventPiiStance;
407
+ readonly piiFields: EventPiiStance;
408
408
  };
409
409
  export type AppendEventArgs = {
410
410
  readonly aggregateId: string;
@@ -499,18 +499,32 @@ export type AgentHandlerHints = {
499
499
  readonly expose?: boolean;
500
500
  readonly risk?: AgentRisk;
501
501
  };
502
+ /** Screens only read `expose`; `risk` ranks handler tool calls, so it is not offered here. */
503
+ export type AgentScreenHints = Pick<AgentHandlerHints, "expose">;
502
504
  export type AgentExposure = {
503
505
  readonly expose: boolean;
504
506
  readonly risk: AgentRisk;
505
507
  };
506
508
  /** Undoes a reservation made before the handler transaction. */
507
509
  export type ReservationRelease = () => Promise<void>;
508
- /** Runs in the dispatcher after access and schema checks but before the
509
- * handler transaction opens, so it must commit its own short write instead
510
- * of holding a second connection while the transaction is open. The returned
511
- * release runs when the transaction does not commit (failure result, throw,
512
- * rollback, failed commit). Throwing a KumikoError rejects the write. */
513
- export type PreTransactionReservation = (event: WriteEvent, ctx: HandlerContext) => Promise<ReservationRelease | undefined>;
510
+ /** The dispatcher's own open handler transaction: a reservation must settle on it, not on
511
+ * HandlerContext, whose db is fail-closed for r.systemScope() handlers. */
512
+ export type ReservationConfirmContext = {
513
+ readonly tx: DbTx;
514
+ readonly tenantId: TenantId;
515
+ };
516
+ /** A reservation that also settles inside the handler transaction. `confirmInTransaction` runs
517
+ * after the handler succeeded and before COMMIT; a throw fails the write and rolls it back. */
518
+ export type ReservationHandle = {
519
+ readonly release: ReservationRelease;
520
+ readonly confirmInTransaction: (confirm: ReservationConfirmContext) => Promise<void>;
521
+ };
522
+ /** Runs in the dispatcher after every pre-handler gate (feature, rate limit, access, schema,
523
+ * validation, field roles) passed but before the handler transaction opens, so it must commit
524
+ * its own short write instead of holding a second connection while the transaction is open.
525
+ * The returned release (or handle.release) runs when the transaction does not commit (failure
526
+ * result, throw, rollback, failed commit). Throwing a KumikoError rejects the write. */
527
+ export type PreTransactionReservation = (event: WriteEvent, ctx: HandlerContext) => Promise<ReservationRelease | ReservationHandle | undefined>;
514
528
  export type WriteHandlerDef = {
515
529
  readonly name: string;
516
530
  readonly schema: ZodType;
@@ -13,6 +13,7 @@ export type SubjectId = {
13
13
  export type SubjectKey = string;
14
14
  export declare function subjectKeyForUser(userId: string): SubjectKey;
15
15
  export declare function subjectKeyForTenant(tenantId: TenantId): SubjectKey;
16
+ export declare const PII_ERASED_SENTINEL = "[[erased]]";
16
17
  export declare const RECORD_ENTITY_PATTERN: RegExp;
17
18
  export declare function subjectKeyForRecord(entity: string, id: string): SubjectKey;
18
19
  export declare function subjectIdToKey(subject: SubjectId): SubjectKey;
@@ -4,6 +4,9 @@ export function subjectKeyForUser(userId) {
4
4
  export function subjectKeyForTenant(tenantId) {
5
5
  return `tenant:${tenantId}`;
6
6
  }
7
+ // Stands in for a PII value whose subject key was erased; clients map it to a
8
+ // translated label instead of showing it.
9
+ export const PII_ERASED_SENTINEL = "[[erased]]";
7
10
  // Registry entity names are identifier-shaped; a free-form aggregate_type
8
11
  // (events-schema.ts is a plain text column) could otherwise mint a record
9
12
  // key that subjectIdSchema (forget-subject.write.ts) later refuses to
package/dist/screen.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { FieldIconKey } from "./field-icon.js";
2
2
  import type { FieldDefinition, FormFieldDefinition, SelectOptionTone } from "./fields.js";
3
- import type { AccessRule, AgentHandlerHints } from "./handlers.js";
3
+ import type { AccessRule, AgentScreenHints } from "./handlers.js";
4
4
  import type { IconKey, NavIconKey } from "./nav-icon.js";
5
5
  export type { FieldIconKey } from "./field-icon.js";
6
6
  export type PlatformComponent = {
@@ -395,7 +395,7 @@ export type EntityListScreenDefinition = {
395
395
  readonly nav?: ScreenNavSugar;
396
396
  readonly detailFor?: string;
397
397
  readonly description?: string;
398
- readonly agent?: AgentHandlerHints;
398
+ readonly agent?: AgentScreenHints;
399
399
  /** Screen has no nav entry by design (opened via link or navved by the
400
400
  * app); exempts it from the nav-area boot check. */
401
401
  readonly dormant?: boolean;
@@ -504,7 +504,7 @@ export type ProjectionListScreenDefinition = {
504
504
  readonly nav?: ScreenNavSugar;
505
505
  readonly detailFor?: string;
506
506
  readonly description?: string;
507
- readonly agent?: AgentHandlerHints;
507
+ readonly agent?: AgentScreenHints;
508
508
  /** Screen has no nav entry by design (opened via link or navved by the
509
509
  * app); exempts it from the nav-area boot check. */
510
510
  readonly dormant?: boolean;
@@ -598,7 +598,7 @@ export type ProjectionDetailScreenDefinition = {
598
598
  readonly nav?: ScreenNavSugar;
599
599
  readonly detailFor?: string;
600
600
  readonly description?: string;
601
- readonly agent?: AgentHandlerHints;
601
+ readonly agent?: AgentScreenHints;
602
602
  /** Screen has no nav entry by design (opened via link or navved by the
603
603
  * app); exempts it from the nav-area boot check. */
604
604
  readonly dormant?: boolean;
@@ -734,7 +734,10 @@ export type DashboardStatPanel = DashboardPanelQueryOptions & DashboardPanelGate
734
734
  /** Static CSS color value (e.g. "var(--color-debt)") for the icon chip,
735
735
  * passed straight to the tile without a registry lookup. Applies ONLY when
736
736
  * `icon` is set (StatCard renders the chip only together with an icon);
737
- * without an icon the value is silently dropped. */
737
+ * without an icon the value is silently dropped. Without `accentColor` the
738
+ * chip follows the tone, including a `toneField` value delivered per result.
739
+ * An accent that should change with the result (e.g. by sign) goes through
740
+ * `toneField`; leave `accentColor` unset then. */
738
741
  readonly accentColor?: string;
739
742
  };
740
743
  export type DashboardChartKind = "timeseries" | "stacked-bars" | "segment-bars" | "stacked-area";
@@ -782,6 +785,8 @@ export type DashboardChartPanel = DashboardPanelQueryOptions & DashboardPanelEmp
782
785
  readonly legendTotals?: boolean;
783
786
  /** Only for chart "stacked-area": range switch in the panel header. */
784
787
  readonly ranges?: DashboardChartRanges;
788
+ /** Only for chart "stacked-area": scrubber under the plot to drag the visible window; starts at today. */
789
+ readonly brush?: boolean;
785
790
  };
786
791
  export type DashboardListPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
787
792
  readonly kind: "list";
@@ -796,6 +801,7 @@ export type DashboardStatGroupPanel = DashboardPanelGate & {
796
801
  readonly label?: string;
797
802
  /** i18n key, a line under `label`; the boot validator requires `label`. */
798
803
  readonly subtitle?: string;
804
+ readonly span?: DashboardPanelSpan;
799
805
  readonly stats: readonly DashboardStatPanel[];
800
806
  };
801
807
  export type DashboardFeedPanel = DashboardPanelQueryOptions & DashboardPanelEmptyState & DashboardPanelGate & {
@@ -867,7 +873,7 @@ export type DashboardScreenDefinition = {
867
873
  readonly nav?: ScreenNavSugar;
868
874
  readonly detailFor?: string;
869
875
  readonly description?: string;
870
- readonly agent?: AgentHandlerHints;
876
+ readonly agent?: AgentScreenHints;
871
877
  /** Screen has no nav entry by design (opened via link or navved by the
872
878
  * app); exempts it from the nav-area boot check. */
873
879
  readonly dormant?: boolean;
@@ -1201,7 +1207,7 @@ export type EntityEditScreenDefinition = {
1201
1207
  readonly nav?: ScreenNavSugar;
1202
1208
  readonly detailFor?: string;
1203
1209
  readonly description?: string;
1204
- readonly agent?: AgentHandlerHints;
1210
+ readonly agent?: AgentScreenHints;
1205
1211
  /** Screen has no nav entry by design (opened via link or navved by the
1206
1212
  * app); exempts it from the nav-area boot check. */
1207
1213
  readonly dormant?: boolean;
@@ -1297,7 +1303,7 @@ export type ActionFormScreenDefinition = {
1297
1303
  readonly nav?: ScreenNavSugar;
1298
1304
  readonly detailFor?: string;
1299
1305
  readonly description?: string;
1300
- readonly agent?: AgentHandlerHints;
1306
+ readonly agent?: AgentScreenHints;
1301
1307
  /** Screen has no nav entry by design (opened via link or navved by the
1302
1308
  * app); exempts it from the nav-area boot check. */
1303
1309
  readonly dormant?: boolean;
@@ -1465,7 +1471,7 @@ export type SecretMintScreenDefinition = {
1465
1471
  readonly nav?: ScreenNavSugar;
1466
1472
  readonly detailFor?: string;
1467
1473
  readonly description?: string;
1468
- readonly agent?: AgentHandlerHints;
1474
+ readonly agent?: AgentScreenHints;
1469
1475
  /** Screen has no nav entry by design (opened via link or navved by the
1470
1476
  * app); exempts it from the nav-area boot check. */
1471
1477
  readonly dormant?: boolean;
@@ -1504,8 +1510,7 @@ export type CustomScreenDefinition = {
1504
1510
  readonly nav?: ScreenNavSugar;
1505
1511
  readonly detailFor?: string;
1506
1512
  readonly description?: string;
1507
- /** Only `expose` is read for screens; `risk` ranks handler tool calls. */
1508
- readonly agent?: AgentHandlerHints;
1513
+ readonly agent?: AgentScreenHints;
1509
1514
  readonly renderer: PlatformComponent;
1510
1515
  readonly routes?: readonly CustomScreenRoute[];
1511
1516
  /** Parent list screen for breadcrumb when this detail is not in nav. */
@@ -1536,7 +1541,7 @@ export type ConfigEditScreenDefinition = {
1536
1541
  readonly nav?: ScreenNavSugar;
1537
1542
  readonly detailFor?: string;
1538
1543
  readonly description?: string;
1539
- readonly agent?: AgentHandlerHints;
1544
+ readonly agent?: AgentScreenHints;
1540
1545
  /** Screen has no nav entry by design (opened via link or navved by the
1541
1546
  * app); exempts it from the nav-area boot check. */
1542
1547
  readonly dormant?: boolean;
@@ -1586,7 +1591,7 @@ export type SecretsEditScreenDefinition = {
1586
1591
  readonly nav?: ScreenNavSugar;
1587
1592
  readonly detailFor?: string;
1588
1593
  readonly description?: string;
1589
- readonly agent?: AgentHandlerHints;
1594
+ readonly agent?: AgentScreenHints;
1590
1595
  /** Screen has no nav entry by design (opened via link or navved by the
1591
1596
  * app); exempts it from the nav-area boot check. */
1592
1597
  readonly dormant?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.351.0",
3
+ "version": "0.353.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,17 @@
1
1
  [
2
+ {
3
+ "version": "0.352.0",
4
+ "type": "improvement",
5
+ "title": "SelectFieldDef.optionsAvailabilityQuery marks static select options as unavailable per tenant",
6
+ "detail": "The query returns { rows: { value, disabled?, hint? }[] }; the renderer merges it onto options and keeps the stored value enabled."
7
+ },
8
+ {
9
+ "version": "0.352.0",
10
+ "type": "breaking",
11
+ "title": "Apps must import Temporal from @cosmicdrift/kumiko-types/temporal instead of temporal-polyfill (affects every app since 0.351.0)",
12
+ "detail": "Since 0.351.0 the native globalThis.Temporal wins over the polyfill. A Temporal imported from \"temporal-polyfill\" is a second implementation, so instanceof and z.instanceof(Temporal.Instant) reject values the framework creates. This applies whether you upgrade from before 0.351.0 or are already on it.",
13
+ "migration": "Replace every value import from \"temporal-polyfill\" in app code and tests, e.g. `import { Temporal } from \"temporal-polyfill\"`, with `import { Temporal } from \"@cosmicdrift/kumiko-types/temporal\"`, and drop side-effect imports of \"temporal-polyfill/global\" (importing @cosmicdrift/kumiko-types/temporal already installs the global when the runtime has none). `import type` and `/// <reference types=\"temporal-polyfill/global\" />` may stay. Then run `kumiko check`; the No-Temporal-Polyfill-Import guard lists anything left. Close the step with `kumiko-upgrade --resolve <id> --reason \"<what you changed>\"`, or add `--not-applicable` when the repo never imported temporal-polyfill."
14
+ },
2
15
  {
3
16
  "version": "0.351.0",
4
17
  "type": "fix",