@cosmicdrift/kumiko-framework 0.399.0 → 0.401.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 (144) hide show
  1. package/AGENTS.md +4 -3
  2. package/agents/api/framework-engine.md +16 -8
  3. package/agents/api/framework-ui-types.md +10 -5
  4. package/agents/api/renderer-web.md +2 -1
  5. package/agents/api/testing-stack.md +1 -0
  6. package/agents/api/types.md +256 -80
  7. package/agents/api.md +3 -3
  8. package/agents/examples/derived-fields-sql.md +251 -0
  9. package/agents/examples/documents.md +161 -0
  10. package/agents/examples/inspection-checklist.md +807 -0
  11. package/agents/examples/relations.md +13 -3
  12. package/agents/examples.md +3 -0
  13. package/agents/features-index.md +2 -0
  14. package/agents/features.md +8 -0
  15. package/dist/api/entity-read-access.d.ts +2 -0
  16. package/dist/api/entity-read-access.js +46 -2
  17. package/dist/api/server-boot-checks.js +2 -2
  18. package/dist/api/server-routes.js +3 -1
  19. package/dist/config/index.d.ts +1 -1
  20. package/dist/db/derived-field-sql.d.ts +23 -0
  21. package/dist/db/derived-field-sql.js +115 -0
  22. package/dist/db/derived-related-sql.d.ts +31 -0
  23. package/dist/db/derived-related-sql.js +137 -0
  24. package/dist/db/driver-row-coercion.d.ts +1 -0
  25. package/dist/db/driver-row-coercion.js +13 -0
  26. package/dist/db/eagerload.d.ts +3 -1
  27. package/dist/db/eagerload.js +4 -6
  28. package/dist/db/event-store-executor-context.js +1 -1
  29. package/dist/db/event-store-executor-read-paging.js +1 -1
  30. package/dist/db/event-store-executor-read-search.d.ts +3 -0
  31. package/dist/db/event-store-executor-read-search.js +17 -9
  32. package/dist/db/event-store-executor-read-sort.d.ts +9 -2
  33. package/dist/db/event-store-executor-read-sort.js +27 -14
  34. package/dist/db/event-store-executor-read-where.d.ts +0 -5
  35. package/dist/db/event-store-executor-read-where.js +3 -9
  36. package/dist/db/event-store-executor-read.js +23 -4
  37. package/dist/db/event-store-executor-write-steps.d.ts +6 -1
  38. package/dist/db/event-store-executor-write-steps.js +20 -1
  39. package/dist/db/event-store-executor-write.js +8 -8
  40. package/dist/db/money.d.ts +4 -0
  41. package/dist/db/money.js +34 -29
  42. package/dist/db/parent-ref-clause.d.ts +1 -0
  43. package/dist/db/parent-ref-clause.js +18 -8
  44. package/dist/db/read-gate-clause.d.ts +7 -0
  45. package/dist/db/read-gate-clause.js +201 -0
  46. package/dist/db/read-ownership.d.ts +9 -0
  47. package/dist/db/read-ownership.js +12 -0
  48. package/dist/engine/boot-validator/boot-check.d.ts +3 -1
  49. package/dist/engine/boot-validator/boot-check.js +7 -11
  50. package/dist/engine/boot-validator/boot-context.d.ts +26 -0
  51. package/dist/engine/boot-validator/boot-context.js +57 -0
  52. package/dist/engine/boot-validator/boot-options.d.ts +38 -0
  53. package/dist/engine/boot-validator/boot-options.js +1 -0
  54. package/dist/engine/boot-validator/boot-phases-app.d.ts +5 -0
  55. package/dist/engine/boot-validator/boot-phases-app.js +107 -0
  56. package/dist/engine/boot-validator/boot-phases.d.ts +11 -0
  57. package/dist/engine/boot-validator/boot-phases.js +140 -0
  58. package/dist/engine/boot-validator/boot-report.d.ts +6 -0
  59. package/dist/engine/boot-validator/boot-report.js +47 -0
  60. package/dist/engine/boot-validator/boot-rule.d.ts +12 -0
  61. package/dist/engine/boot-validator/boot-rule.js +14 -0
  62. package/dist/engine/boot-validator/boot-warnings.d.ts +2 -0
  63. package/dist/engine/boot-validator/boot-warnings.js +20 -0
  64. package/dist/engine/boot-validator/derived-sql-fields.d.ts +4 -0
  65. package/dist/engine/boot-validator/derived-sql-fields.js +184 -0
  66. package/dist/engine/boot-validator/entity-handler.d.ts +3 -0
  67. package/dist/engine/boot-validator/entity-handler.js +6 -3
  68. package/dist/engine/boot-validator/entity-list-screens.js +3 -1
  69. package/dist/engine/boot-validator/field-conditions.d.ts +2 -0
  70. package/dist/engine/boot-validator/field-conditions.js +196 -0
  71. package/dist/engine/boot-validator/i18n-keys.d.ts +3 -2
  72. package/dist/engine/boot-validator/i18n-keys.js +18 -4
  73. package/dist/engine/boot-validator/index.d.ts +5 -34
  74. package/dist/engine/boot-validator/index.js +24 -204
  75. package/dist/engine/boot-validator/pii-retention.js +15 -9
  76. package/dist/engine/boot-validator/projection-list-screens.js +19 -1
  77. package/dist/engine/boot-validator/read-gate.d.ts +2 -0
  78. package/dist/engine/boot-validator/read-gate.js +59 -0
  79. package/dist/engine/boot-validator/screens-forms.js +19 -6
  80. package/dist/engine/boot-validator/screens-kind-entity-list.js +8 -6
  81. package/dist/engine/boot-validator/screens-lists.js +8 -0
  82. package/dist/engine/boot-validator/zod-shape.js +4 -2
  83. package/dist/engine/build-app-schema.js +11 -5
  84. package/dist/engine/embedded-derived.d.ts +8 -6
  85. package/dist/engine/embedded-derived.js +25 -7
  86. package/dist/engine/entity-handlers.d.ts +13 -6
  87. package/dist/engine/entity-handlers.js +17 -18
  88. package/dist/engine/entity-list-runtime-options.d.ts +2 -1
  89. package/dist/engine/entity-list-runtime-options.js +86 -5
  90. package/dist/engine/factories.d.ts +18 -6
  91. package/dist/engine/factories.js +21 -5
  92. package/dist/engine/field-access.d.ts +2 -0
  93. package/dist/engine/field-access.js +48 -1
  94. package/dist/engine/field-state.d.ts +12 -0
  95. package/dist/engine/field-state.js +67 -0
  96. package/dist/engine/handler-helpers.d.ts +3 -0
  97. package/dist/engine/handler-helpers.js +7 -1
  98. package/dist/engine/index.d.ts +3 -3
  99. package/dist/engine/index.js +3 -3
  100. package/dist/engine/ownership.d.ts +1 -1
  101. package/dist/engine/ownership.js +9 -5
  102. package/dist/engine/registry-state.d.ts +1 -0
  103. package/dist/engine/registry-state.js +1 -1
  104. package/dist/engine/schema-builder.js +3 -0
  105. package/dist/engine/strip-unreadable-related-lists.d.ts +4 -0
  106. package/dist/engine/strip-unreadable-related-lists.js +80 -0
  107. package/dist/engine/user-entity-reference.d.ts +2 -0
  108. package/dist/engine/user-entity-reference.js +7 -0
  109. package/dist/files/assert-file-refs-belong-to-tenant.js +26 -6
  110. package/dist/files/bind-pending-file-refs.d.ts +3 -2
  111. package/dist/files/bind-pending-file-refs.js +11 -12
  112. package/dist/files/bind-referenced-files.write.js +5 -5
  113. package/dist/files/create-file-ref-from-bytes.d.ts +2 -0
  114. package/dist/files/create-file-ref-from-bytes.js +2 -0
  115. package/dist/files/create-file-ref.d.ts +2 -0
  116. package/dist/files/create-file-ref.js +39 -22
  117. package/dist/files/dispose-removed-file-refs.js +5 -8
  118. package/dist/files/file-field-slots.d.ts +10 -0
  119. package/dist/files/file-field-slots.js +53 -0
  120. package/dist/files/file-ref-entity.d.ts +1 -1
  121. package/dist/files/file-ref-table.d.ts +1 -1
  122. package/dist/files/file-routes.js +12 -3
  123. package/dist/files/index.d.ts +3 -0
  124. package/dist/files/index.js +2 -0
  125. package/dist/files/load-save-file-refs.js +3 -4
  126. package/dist/files/rebind-file-ref.d.ts +41 -0
  127. package/dist/files/rebind-file-ref.js +170 -0
  128. package/dist/files/record-read-check.js +2 -30
  129. package/dist/files/record-write-check.d.ts +2 -0
  130. package/dist/files/record-write-check.js +18 -3
  131. package/dist/files/upload-field-resolution.d.ts +2 -1
  132. package/dist/files/upload-field-resolution.js +33 -13
  133. package/dist/i18n/required-surface-keys.d.ts +6 -0
  134. package/dist/i18n/required-surface-keys.js +61 -0
  135. package/dist/pipeline/dispatch-batch.js +6 -5
  136. package/dist/pipeline/dispatch-write.js +2 -1
  137. package/dist/pipeline/write-result-masking.js +2 -1
  138. package/dist/schema-cli/validate-boot-input.js +2 -0
  139. package/dist/schema-cli/validate-command.js +46 -12
  140. package/dist/schema-cli.js +1 -1
  141. package/dist/ui-types/index.d.ts +2 -1
  142. package/dist/ui-types/index.js +2 -1
  143. package/package.json +3 -3
  144. package/src/changes.json +68 -0
package/AGENTS.md CHANGED
@@ -113,8 +113,9 @@ the field types reject a text field without that stance.
113
113
  `personal: false, reason: "is_business_data"` (a snake_case reason) for
114
114
  business data. For personal data name the subject, `personal: "self"`,
115
115
  `personal: { of: "<ownerField>" }` or `personal: "tenant"`, plus
116
- `find: "exact" | "fuzzy" | "none" | "secret"`. A plain foreign key to a user
117
- is `personal: "ref"`. `personal: "self"` is for the row that is the user.
116
+ `find: "exact" | "fuzzy" | "none" | "secret"`. A foreign key to a user
117
+ is `createReferenceField({ entity: "user:user" })`, which defaults to
118
+ `personal: "ref"`; `personal: false` on it fails at boot. `personal: "self"` is for the row that is the user.
118
119
  When the record itself is the person, such as a contact or a customer that
119
120
  is not a user, use `personal: { of: "id" }`; the entity then needs a uuid
120
121
  `id`. The framework encrypts personal fields per subject and
@@ -306,7 +307,7 @@ as bundled features.
306
307
  | Business logic beyond CRUD | `r.writeHandler` / `r.queryHandler` with `createEntityExecutor` | `agents/examples/custom-handlers.md` |
307
308
  | Filtered lists and counts | Executor `list` with `filter` and `totalCount` | `agents/examples/custom-handlers.md` |
308
309
  | Key figures on a dashboard | `dashboard` screen with `stat` / `stat-group` panels and a query handler | `agents/examples/dashboard-stats.md` |
309
- | Relations between entities | `r.relation(entity, name, { type, target, foreignKey, onDelete })` | `agents/examples/relations.md` |
310
+ | Relations between entities | `createReferenceField({ entity })` for the foreign key (user ids: `entity: "user:user"`), `r.relation(entity, name, { type, target, foreignKey, onDelete })` | `agents/examples/relations.md` |
310
311
  | Structured sub-objects in a field | Embedded fields | `agents/examples/embedded.md` |
311
312
  | List, edit, custom screens and navs | `r.screen`, `r.nav` | `agents/examples/screens-nav.md` |
312
313
  | A record page with header, metrics, tabs | `projectionDetail` with `header`, `metrics`, `layout.mode: "tabs"` | `agents/examples/record-detail-layout.md` |
@@ -30,10 +30,10 @@ function createBigIntField<R extends true | false = false>(overrides?: Partial<O
30
30
  function createBooleanField<R extends true | false = false>(overrides?: Partial<Omit<BooleanFieldDef, "type" | "required">> & { required?: R; }): BooleanFieldDef & { required: R; default: boolean; };
31
31
  function createDateField<R extends true | false = false>(overrides?: Partial<Omit<DateFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): DateFieldDef & { required: R; };
32
32
  function createDecimalField<R extends true | false = false>(config: { precision: number; scale: number; } & Partial<Omit<DecimalFieldDef, "type" | "precision" | "scale" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): DecimalFieldDef & { required: R; };
33
- function createDerivedField(spec: DerivedFieldDef): DerivedFieldDef; // Read-time computed field for `EntityDefinition.derivedFields` (NOT `fields`).
33
+ function createDerivedField(spec: DerivedFieldDef): DerivedFieldDef; // Read-time computed field for `EntityDefinition.derivedFields` (NOT `fields`), never persisted.
34
34
  function createEmbeddedField(schema: EmbeddedFieldDef["schema"], overrides?: Partial<Omit<EmbeddedFieldDef, "type" | "schema" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): EmbeddedFieldDef;
35
35
  function createEmbeddedListField(schema: EmbeddedFieldDef["schema"], overrides?: Partial<Omit<EmbeddedFieldDef, "type" | "schema" | "multiple" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): EmbeddedFieldDef & { multiple: true; };
36
- function createEntity<const F, const T extends EntityTenancy = "tenant">(def: { table?: string; fields: F; description?: string; softDelete?: boolean; systemStream?: boolean; eventVersion?: number; eventMigrations?: EntityDefinition["eventMigrations"]; searchWeight?: number; defaultCurrency?: string; transitions?: Readonly<Record<string, Readonly<Record<string, string[]>>>>; lockedWhen?: EntityDefinition["lockedWhen"]; indexes?: EntityIndexDef[]; idType?: "serial" | "uuid"; access?: EntityDefinition["access"]; parentRef?: EntityDefinition["parentRef"]; retention?: RetentionDef; derivedFields?: EntityDefinition["derivedFields"]; tenancy?: T; transferable?: boolean; attachedFileAccess?: EntityDefinition["attachedFileAccess"]; }): F extends FieldsMap ? EntityDefinition<F, T> : never;
36
+ function createEntity<const F, const T extends EntityTenancy = "tenant">(def: { table?: string; fields: F; description?: string; softDelete?: boolean; systemStream?: boolean; eventVersion?: number; eventMigrations?: EntityDefinition["eventMigrations"]; searchWeight?: number; defaultCurrency?: string; transitions?: Readonly<Record<string, Readonly<Record<string, string[]>>>>; lockedWhen?: EntityDefinition["lockedWhen"]; indexes?: EntityIndexDef[]; idType?: "serial" | "uuid"; access?: EntityDefinition["access"]; parentRef?: EntityDefinition["parentRef"]; readGate?: EntityDefinition["readGate"]; retention?: RetentionDef; derivedFields?: EntityDefinition["derivedFields"]; tenancy?: T; transferable?: boolean; attachedFileAccess?: EntityDefinition["attachedFileAccess"]; }): F extends FieldsMap ? EntityDefinition<F, T> : never;
37
37
  function createEntityExecutor<E extends EntityDefinition>(entityName: string, entity: E): { table: EntityTable; executor: EventStoreExecutor<EntityRow<E>>; };
38
38
  function createFileField(overrides?: Partial<Omit<FileFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): FileFieldDef;
39
39
  function createFilesField(overrides?: Partial<Omit<FilesFieldDef, "type" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): FilesFieldDef;
@@ -45,6 +45,7 @@ function createLongTextField<R extends true | false = false>(overrides: Partial<
45
45
  function createMoneyField<R extends true | false = false>(overrides?: Partial<Omit<MoneyFieldDef, "type" | "required">> & { required?: R; }): MoneyFieldDef & { required: R; };
46
46
  function createMultiSelectField<const TOptions extends string[]>(opts: { options: TOptions; } & Partial<Omit<MultiSelectFieldDef<TOptions>, "type" | "options" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind): MultiSelectFieldDef<TOptions>; // Multi-select field — N values from a fixed option list.
47
47
  function createNumberField<R extends true | false = false>(overrides?: Partial<Omit<NumberFieldDef, "type" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): NumberFieldDef & { required: R; }; // Numeric field — `double precision` column by default (fractional values allowed end to end).
48
+ function createReferenceField<R extends true | false = false>(opts: { entity: string; } & Partial<Omit<ReferenceFieldDef, "type" | "entity" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): ReferenceFieldDef & { required: R; }; // Foreign key to another entity (`"customer"` or `"feature:entity"`), stored as a UUID.
48
49
  function createRegistry(rawFeatures: FeatureDefinition[]): Registry;
49
50
  function createSeed(opts: CreateSeedOptions): ConfigSeedDef;
50
51
  function createSelectField<const TOptions extends string[], R extends true | false = false>(opts: { options: TOptions; } & Partial<Omit<SelectFieldDef<NoInfer<TOptions>>, "type" | "options" | "required" | keyof ResolvedPiiFlags>> & PersonalAnnotationsNoFind & { required?: R; }): SelectFieldDef<TOptions> & { required: R; };
@@ -60,14 +61,14 @@ function createUserConfig<T extends ConfigKeyType>(type: T, opts?: ConfigKeyOpti
60
61
  function declareEscapeHatch(declaration: EscapeHatchDeclaration): void;
61
62
  function dedupeFeatures(features: FeatureDefinition[]): FeatureDefinition[];
62
63
  function defineApply<TPayload = Record<string, unknown>>(fn: (event: StoredEvent<TPayload>, tx: ExecutorManagedRunner, table: ProjectionTable) => Promise<void>): SingleStreamApplyFn;
63
- function defineEntityCreateHandler(entityName: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
64
- function defineEntityDeleteHandler(entityName: string, entity: EntityDefinition, options: EntityHandlerOptions): WriteHandlerDef;
64
+ function defineEntityCreateHandler(entityName: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): EntityWriteHandlerDef;
65
+ function defineEntityDeleteHandler(entityName: string, entity: EntityDefinition, options: EntityHandlerOptions): EntityWriteHandlerDef;
65
66
  function defineEntityDetailHandler(entityName: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
66
67
  function defineEntityListHandler(entityName: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
67
68
  function defineEntityQueryHandler(name: string, entity: EntityDefinition, options: EntityQueryHandlerOptions): QueryHandlerDef;
68
- function defineEntityRestoreHandler(entityName: string, entity: EntityDefinition, options: EntityHandlerOptions): WriteHandlerDef;
69
- function defineEntityUpdateHandler(entityName: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
70
- function defineEntityWriteHandler(name: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): WriteHandlerDef;
69
+ function defineEntityRestoreHandler(entityName: string, entity: EntityDefinition, options: EntityHandlerOptions): EntityWriteHandlerDef;
70
+ function defineEntityUpdateHandler(entityName: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): EntityWriteHandlerDef;
71
+ function defineEntityWriteHandler(name: string, entity: EntityDefinition, options: EntityWriteHandlerOptions): EntityWriteHandlerDef;
71
72
  function defineFeature<const TName extends string, TExports = undefined>(name: TName, setup: (r: FeatureRegistrar<TName>) => TExports, settings?: DefineFeatureSettings): FeatureDefinition & { exports: TExports; };
72
73
  function defineMspApply<TPayload = Record<string, unknown>>(fn: (event: StoredEvent<TPayload>, tx: ExecutorManagedRunner, ctx: MultiStreamApplyContext) => Promise<void>): MultiStreamApplyFn;
73
74
  function definePagedQueryHandler<const TName extends string, TSchema extends ZodType, TRow = unknown, TMap extends object = KumikoEventTypeMap>(def: QueryHandlerDefinition<TName, TSchema, CursorResult<TRow>, TMap>, ..._noSecretInResponse: true extends ContainsSecret<CursorResult<TRow>> ? [ secretLeak: "A handler response must not contain a Secret<> — call .reveal() and return the plaintext, or drop the field." ] : [ ]): PagedQueryHandlerDefinition<TName, TSchema, TRow, TMap>;
@@ -120,6 +121,7 @@ function normalizeEditField(f: EditFieldSpec): Exclude<EditFieldSpec, string>;
120
121
  function normalizeListColumn(c: ListColumnSpec): Exclude<ListColumnSpec, string>;
121
122
  function pageRows<TRow extends Readonly<Record<TSortField, PagedListRowValue>> & { id: string; }, TSortField extends string>(rows: TRow[], request: PagedListRequest<TSortField>, options: { searchFields: TSortField[]; defaultSort: TSortField; }): CursorResult<TRow>;
122
123
  function pagedListSchema<const TSortField extends string>(sortableFields: [ TSortField, ...TSortField[] ]): …;
124
+ function parentVisibilityFor(ctx: HandlerContext, user: SessionUser): ParentVisibilityOption;
123
125
  function parseChangesetChanges(markdown: string, source: string): PendingChange[]; // Parse structured upgrade metadata embedded in a Changeset body.
124
126
  function parseFeatureChangelog(raw: string, featureName: string): FeatureChangelog | null; // Parse a changes.json body.
125
127
  function parseQn(value: string): ParsedQn;
@@ -145,6 +147,7 @@ function tenantChannel(tenantId: TenantId): string;
145
147
  function toKebab(input: string): string;
146
148
  function typedPayload<TPayload>(event: { type: string; payload: Record<string, unknown>; }, eventDef: EventDef<TPayload>): TPayload;
147
149
  function unwrapWorkflowStepError(error: unknown): unknown;
150
+ function userCanWriteFieldRow(user: SessionUser, accessMap: OwnershipMap | undefined, oldRow: Readonly<Record<string, unknown>>, newRow: Readonly<Record<string, unknown>>): boolean;
148
151
  function validateAppCustomScreenWriteQns(appRoot: string, knownQns: ReadonlySet<string>): void; // Boot-time scan of app `src/**` for `dispatcher.write("…")` string literals.
149
152
  function validateBoot(rawFeatures: FeatureDefinition[], options?: ValidateBootOptions): void; // Validates all feature configurations at boot time.
150
153
  function validateBundledListsOptions(options: unknown): void;
@@ -482,6 +485,7 @@ type EditRelatedListSection = {
482
485
  id?: string; // Kebab-case tab id, used as the `?tab=` value.
483
486
  kind: "relatedList";
484
487
  title: string;
488
+ readOnly?: boolean; // Rows are aggregates or other values with no record of their own, so they lead nowhere on purpose.
485
489
  query: string; // Fully qualified query QN.
486
490
  parentParam?: string; // Query-payload key the parent record's id is passed under.
487
491
  parentFilter?: {
@@ -506,6 +510,7 @@ type EditRelatedListSection = {
506
510
  rowTone?: RelatedListRowTone; // Tints rows whose field matches — a declarative rule, not a function, so the screen definition stays serializable.
507
511
  toolbarActions?: RelatedListToolbarAction[]; // Toolbar actions above the table — same type and dispatch semantics as `entityList`/`projectionList`'s `toolbarActions` ("+ Anlegen" etc.), plus `visible`/`params` evaluated against the parent record (see RelatedListToolbarAction).
508
512
  countField?: string; // Record field rendered as a count badge in the tab label when the enclosing `EditLayout.mode` is "tabs" (e.g. an open-items counter).
513
+ count?: true; // Tab badge counted from this section's own query (`limit: 1, totalCount: true` → `total`), so it matches the tab's rows and stays live.
509
514
  actions?: RowAction[]; // Rendered top-right in the section's Card title row — same navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`, evaluated against the parent record.
510
515
  emptyState?: RelatedListEmptyStateSpec; // Rendered inside the section's Card in place of the table when this section's query returns zero rows.
511
516
  };
@@ -532,6 +537,7 @@ type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTenancy =
532
537
  write?: OwnershipMap;
533
538
  }; // Row-level ownership rules (H.2).
534
539
  parentRef?: ParentRefDef; // Join-row carrier reference.
540
+ readGate?: ReadGateDef; // Declarative read gate for rows readable only through other rows (a link to a visible host, a file whose owner is readable).
535
541
  retention?: RetentionDef; // Default retention policy for this entity.
536
542
  derivedFields?: DerivedFieldsMap; // Read-time computed fields, keyed by name.
537
543
  description?: string; // Optional free-text semantics for the AI agent manifest — see TextFieldDef.description.
@@ -942,7 +948,7 @@ type JobTrigger =
942
948
  manual: true;
943
949
  }
944
950
  ;
945
- type JsonbFieldDef = ResolvedPiiFlags & {
951
+ type JsonbFieldDef = PiiAndFieldState & {
946
952
  type: "jsonb";
947
953
  description?: string;
948
954
  emptyValue?: JsonbEmptyValue; // `"array"`: the field holds a JSON array, so a row that never wrote it reads `[]` and writes take an array.
@@ -1708,6 +1714,7 @@ type ValidateBootOptions = {
1708
1714
  env?: Readonly<Record<string, string | undefined>>; // Env the encrypted-field master-key probe reads.
1709
1715
  entityFieldCipher?: EnvelopeCipher; // The cipher the runner injects after validation (built from the app's `masterKey` or the env keyring).
1710
1716
  requiredLocales?: string[]; // Locales every translation key must carry across all mounted features (locale packages count).
1717
+ requireFieldValueTranslations?: boolean; // Require translations for the values that entityList columns and entityEdit fields display: select/multiSelect option labels and the column headers and select options of embedded lists.
1711
1718
  };
1712
1719
  type ValidationError = {
1713
1720
  field: string;
@@ -1798,6 +1805,7 @@ type WriteResult<TData = unknown> =
1798
1805
  | {
1799
1806
  isSuccess: true;
1800
1807
  data: TData;
1808
+ lifecycle?: LifecycleResult; // The executor result behind a handler that answers with its own `data`.
1801
1809
  }
1802
1810
  | WriteFailure
1803
1811
  ;
@@ -10,13 +10,17 @@ Import from `@cosmicdrift/kumiko-framework/ui-types`. Types used here but not ex
10
10
  function applyFormDerivations(values: unknown, screen: FormDerivationScreen): unknown; // Recomputes every derived head field of an action form from its inputs.
11
11
  function availableSelectOptions(field: SelectOptionRules, values: Record<string, unknown>): string[]; // Options of a select field that are offered for the given row values, in declaration order.
12
12
  function cardColumnRoles<C extends CardRoleColumn>(columns: C[]): CardColumnRoles<C>;
13
- function computeDerivedCellValue(op: EmbeddedDerivedCellDef["op"], values: (number | undefined)[]): number | undefined; // Computes a derived cell from its source values.
13
+ function computeDerivedCellValue(op: NumericDerivedOp, values: (number | undefined)[]): number | undefined; // Computes a derived cell from its source values.
14
14
  function evalFieldCondition(cond: FieldCondition, values: Record<string, unknown>): boolean; // Evaluates a declarative FieldCondition against the current row/form values.
15
+ function findMissingConditionalRequirements(fields: Readonly<Record<string, FieldDefinition>>, values: Record<string, unknown>, previous?: Record<string, unknown>): string[]; // Paths of fields (and embedded sub-fields) whose `requiredWhen` holds while the value is missing.
15
16
  function findUnavailableSelectOptions(fields: Readonly<Record<string, FieldDefinition>>, values: Record<string, unknown>, previous?: Record<string, unknown>): UnavailableSelectOption[]; // Select values that violate a `conditionalOptions` rule.
16
17
  function formalLocaleTag<Locale extends string>(locale: Locale): `${Locale}-x-formal`;
17
18
  function isExtensionEditSection(section: EditSectionSpec): section is EditExtensionSection;
19
+ function isFieldRequired(def: RequiredState, values: Record<string, unknown>): boolean;
20
+ function isFieldVisible(def: FieldStateConditions, values: Record<string, unknown>): boolean;
18
21
  function isFormDerivationInputMode(screen: FormDerivationScreen, values: unknown, field: string): boolean; // True when `field` is a `listSum` without `whenEmpty` whose list has no rows: the sum has nothing to add up, so the user types the value.
19
22
  function isFormatSpec(r: unknown): r is FormatSpec;
23
+ function isMissingRequiredValue(value: unknown): boolean;
20
24
  function isRecordLocked(lockedWhen: FieldCondition | undefined, row: Readonly<Record<string, unknown>>): boolean;
21
25
  function isUiAccessGranted(access: AccessRule | undefined, userRoles: string[] | undefined): boolean;
22
26
  function isUnchangedValue(a: unknown, b: unknown): boolean;
@@ -26,6 +30,7 @@ function resolveNavParentScreen(screens: ScreenDefinition[], detail: ScreenDefin
26
30
  function resolveTranslationValue(value: TranslationValue, locale: string, params?: Readonly<Record<string, unknown>>): string; // Resolve a TranslationValue to display text: a plural entry picks its CLDR category via `Intl.PluralRules` from `params.count`, falling back to `other` when `count` is missing, non-finite, or `locale` is invalid.
27
31
  function roundDerivedCellValue(value: number, target: DerivedCellRoundingTarget): number; // Rounds a derived cell's computed value to the precision its target sub-field declares — commercial rounding (round-half-away-from-zero, correct for signed minor-unit money).
28
32
  function translationValueOtherText(value: TranslationValue): string; // Text-only reading of a TranslationValue — the CLDR `other` form for a plural entry, or the string as-is.
33
+ function withDerivedCells(row: unknown, derived: Readonly<Record<string, EmbeddedDerivedCellDef>>, targets: Readonly<Record<string, DerivedCellRoundingTarget>>): unknown; // Recomputes every derived cell of an embedded-list row from its raw values, overwriting whatever the client sent instead of merely checking it — the server is the authority for derived cells.
29
34
  ```
30
35
 
31
36
  ## Constants
@@ -331,12 +336,12 @@ type EditWriteFormSection = {
331
336
  submitLabel?: string; // i18n-key for the submit button.
332
337
  actions?: RowAction[]; // Rendered top-right in the section's Card title row — same navigate/drawer/writeHandler dispatch as `ProjectionDetailScreenDefinition.actions`, evaluated against the same record.
333
338
  };
334
- type EmbeddedDerivedCellDef = { // A cell computed from other cells of the same row.
335
- op: "multiply" | "sum" | "subtract" | "percent"; // `percent` = `from[0] * from[1] / 100` (exactly two sources, e.g. amount and rate).
336
- from: string[];
339
+ type EmbeddedDerivedCellDef = ({ op: "multiply" | "sum" | "subtract" | "percent"; from: string[]; } | { op: "inRange"; from: string[]; inside: string; outside: string; }) & { // A cell computed from other cells of the same row.
340
+ when?: Exclude<FieldCondition, boolean>; // Row-level condition on a sibling sub-field.
337
341
  };
338
342
  type EmbeddedListEditSpec = {
339
343
  readOnlyCells?: string[]; // Sub-fields shown as read-only cells; every other cell stays editable.
344
+ cardHiddenCells?: string[]; // Sub-fields that only steer a row (e.g. the kind that decides which controls appear) and are not shown on phone cards; the desktop table still shows them.
340
345
  fixedRows?: boolean; // `true` keeps the rows as they are: no add, remove, duplicate, move or paste-appended rows.
341
346
  };
342
347
  type EntityEditAction = RowAction & {
@@ -507,7 +512,7 @@ type SecretsEditScreenDefinition = {
507
512
  access?: AccessRule;
508
513
  };
509
514
  type SelectOptionTone = (typeof SELECT_OPTION_TONES)[number];
510
- type TextFieldDef = ResolvedPiiFlags & {
515
+ type TextFieldDef = PiiAndFieldState & {
511
516
  type: "text";
512
517
  description?: string; // Optional free-text semantics for the AI agent manifest — never rendered; the i18n label is what users see.
513
518
  maxLength?: number;
@@ -499,7 +499,7 @@ interface FileFieldProps extends FieldBase {
499
499
  entityType?: string; // Binds the upload to a specific entity field; the `/api/files` endpoint then validates `accept`/`maxSize` server-side against that field's definition.
500
500
  fieldName?: string;
501
501
  }
502
- type FileRefTarget = Pick<FileUploadFormDataInput, "entityType" | "entityId" | "fieldName" | "actionForm">;
502
+ type FileRefTarget = Pick<FileUploadFormDataInput, "entityType" | "entityId" | "fieldName" | "subField" | "actionForm">;
503
503
  type FloatingPanelGeometry = {
504
504
  x: number;
505
505
  y: number;
@@ -776,6 +776,7 @@ type InputProps =
776
776
  hasError?: boolean;
777
777
  radioVariant?: "list" | "card"; // `"card"` renders the radio group as bordered cards with label plus optional description per option and always uses the radio group, regardless of option count or `display`.
778
778
  display?: "radio" | "dropdown"; // Requested presentation.
779
+ optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>; // Tone per option value; the selected segment of a radio presentation takes its colors.
779
780
  }
780
781
  | ({ kind: "combobox"; id: string; name: string; options: { value: string; label: string; }[]; disabled?: boolean; required?: boolean; hasError?: boolean; placeholder?: string; searchPlaceholder?: string; emptyText?: string; onSearchChange?: (q: string) => void; loading?: boolean; onCreate?: () => void; createLabel?: string; } & ({ multiple?: false; value: string; onChange: (v: string) => void; } | { multiple: true; value: string[]; onChange: (v: string[]) => void; }))
781
782
  | {
@@ -13,6 +13,7 @@ function createRequestHelper(app: Hono, jwt: JwtHelper, options?: RequestHelperO
13
13
  function createTestDb(arg?: string | CreateTestDbOptions): Promise<TestDb>;
14
14
  function createTestRedis(opts?: CreateTestRedisOptions): Promise<TestRedis>;
15
15
  function drainEventConsumers(stack: Pick<TestStack, "db" | "eventDispatcher">, consumerNames: [ string, ...string[] ], options?: { maxPasses?: number; }): Promise<void>;
16
+ function dropTestDb(baseUrl: string, dbName: string): Promise<void>;
16
17
  function pushEntityProjectionTables(stack: DbHolder, registry: Registry): Promise<void>; // Push all implicit-projection tables — one per `r.entity()` — that the registry knows about.
17
18
  function resetEventStore(stack: { db: unknown; eventDispatcher?: EventDispatcher; }, extraTables?: (unknown | string)[]): Promise<void>; // Wipes event store + framework-state + the given feature read-models in one TRUNCATE, then re-registers the event-consumer state rows.
18
19
  function setupTestStack(options: TestStackOptions): Promise<TestStack>;