@cosmicdrift/kumiko-framework 0.2.3 → 0.3.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 (167) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +124 -39
  3. package/src/__tests__/full-stack.integration.ts +2 -2
  4. package/src/api/auth-routes.ts +5 -5
  5. package/src/api/jwt.ts +2 -2
  6. package/src/api/route-registrars.ts +1 -1
  7. package/src/api/routes.ts +3 -3
  8. package/src/api/server.ts +6 -7
  9. package/src/compliance/profiles.ts +8 -8
  10. package/src/db/assert-exists-in.ts +2 -2
  11. package/src/db/cursor.ts +3 -3
  12. package/src/db/event-store-executor.ts +19 -13
  13. package/src/db/located-timestamp.ts +1 -1
  14. package/src/db/money.ts +12 -2
  15. package/src/db/pg-error.ts +1 -1
  16. package/src/db/row-helpers.ts +1 -1
  17. package/src/db/table-builder.ts +3 -5
  18. package/src/db/tenant-db.ts +9 -9
  19. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  20. package/src/engine/__tests__/build-target.test.ts +135 -0
  21. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  22. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  23. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  24. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  25. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  26. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  27. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  28. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  29. package/src/engine/__tests__/raw-table.test.ts +2 -2
  30. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  31. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  32. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  33. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  34. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  35. package/src/engine/__tests__/steps-read.test.ts +142 -0
  36. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  37. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  38. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  39. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  40. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  41. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  42. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  43. package/src/engine/boot-validator/api-ext.ts +77 -0
  44. package/src/engine/boot-validator/config-deps.ts +163 -0
  45. package/src/engine/boot-validator/entity-handler.ts +466 -0
  46. package/src/engine/boot-validator/index.ts +159 -0
  47. package/src/engine/boot-validator/ownership.ts +198 -0
  48. package/src/engine/boot-validator/pii-retention.ts +155 -0
  49. package/src/engine/boot-validator/screens-nav.ts +624 -0
  50. package/src/engine/boot-validator.ts +1 -1804
  51. package/src/engine/build-app-schema.ts +1 -1
  52. package/src/engine/build-target.ts +99 -0
  53. package/src/engine/codemod/index.ts +15 -0
  54. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  55. package/src/engine/config-helpers.ts +9 -19
  56. package/src/engine/constants.ts +1 -1
  57. package/src/engine/define-feature.ts +88 -9
  58. package/src/engine/define-handler.ts +89 -3
  59. package/src/engine/define-roles.ts +2 -2
  60. package/src/engine/define-step.ts +28 -0
  61. package/src/engine/define-workflow.ts +110 -0
  62. package/src/engine/entity-handlers.ts +10 -9
  63. package/src/engine/event-helpers.ts +4 -4
  64. package/src/engine/factories.ts +12 -12
  65. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  66. package/src/engine/feature-ast/extractors/index.ts +74 -0
  67. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  68. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  69. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  70. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  71. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  72. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  73. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  74. package/src/engine/feature-ast/parse.ts +7 -0
  75. package/src/engine/feature-ast/patch.ts +9 -1
  76. package/src/engine/feature-ast/patcher.ts +10 -3
  77. package/src/engine/feature-ast/patterns.ts +49 -1
  78. package/src/engine/feature-ast/render.ts +17 -1
  79. package/src/engine/index.ts +45 -2
  80. package/src/engine/pattern-library/__tests__/library.test.ts +6 -0
  81. package/src/engine/pattern-library/library.ts +42 -2
  82. package/src/engine/pipeline.ts +88 -0
  83. package/src/engine/projection-helpers.ts +1 -1
  84. package/src/engine/read-claim.ts +1 -1
  85. package/src/engine/registry.ts +30 -2
  86. package/src/engine/resolve-config-or-param.ts +4 -0
  87. package/src/engine/run-pipeline.ts +162 -0
  88. package/src/engine/schema-builder.ts +2 -4
  89. package/src/engine/state-machine.ts +1 -1
  90. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  91. package/src/engine/steps/_duration-utils.ts +33 -0
  92. package/src/engine/steps/_no-return-guard.ts +21 -0
  93. package/src/engine/steps/_resolver-utils.ts +42 -0
  94. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  95. package/src/engine/steps/aggregate-append-event.ts +56 -0
  96. package/src/engine/steps/aggregate-create.ts +56 -0
  97. package/src/engine/steps/aggregate-update.ts +68 -0
  98. package/src/engine/steps/branch.ts +84 -0
  99. package/src/engine/steps/call-feature.ts +49 -0
  100. package/src/engine/steps/compute.ts +41 -0
  101. package/src/engine/steps/for-each.ts +111 -0
  102. package/src/engine/steps/mail-send.ts +44 -0
  103. package/src/engine/steps/read-find-many.ts +51 -0
  104. package/src/engine/steps/read-find-one.ts +58 -0
  105. package/src/engine/steps/retry.ts +87 -0
  106. package/src/engine/steps/return.ts +34 -0
  107. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  108. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  109. package/src/engine/steps/wait-for-event.ts +71 -0
  110. package/src/engine/steps/wait.ts +69 -0
  111. package/src/engine/steps/webhook-send.ts +71 -0
  112. package/src/engine/system-user.ts +1 -1
  113. package/src/engine/types/feature.ts +92 -1
  114. package/src/engine/types/handlers.ts +18 -10
  115. package/src/engine/types/identifiers.ts +1 -0
  116. package/src/engine/types/index.ts +12 -1
  117. package/src/engine/types/step.ts +334 -0
  118. package/src/engine/types/target-ref.ts +21 -0
  119. package/src/engine/types/tree-node.ts +130 -0
  120. package/src/engine/types/workspace.ts +7 -0
  121. package/src/engine/validate-projection-allowlist.ts +161 -0
  122. package/src/event-store/snapshot.ts +1 -1
  123. package/src/event-store/upcaster-dead-letter.ts +1 -1
  124. package/src/event-store/upcaster.ts +1 -1
  125. package/src/files/file-routes.ts +1 -1
  126. package/src/files/types.ts +2 -2
  127. package/src/jobs/job-runner.ts +10 -10
  128. package/src/lifecycle/lifecycle.ts +0 -3
  129. package/src/logging/index.ts +1 -0
  130. package/src/logging/pino-logger.ts +11 -7
  131. package/src/logging/utils.ts +24 -0
  132. package/src/observability/prometheus-meter.ts +7 -5
  133. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  134. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  135. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  136. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  137. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  138. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  139. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  140. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  141. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  142. package/src/pipeline/append-event-core.ts +22 -6
  143. package/src/pipeline/dispatcher-utils.ts +188 -0
  144. package/src/pipeline/dispatcher.ts +63 -283
  145. package/src/pipeline/distributed-lock.ts +1 -1
  146. package/src/pipeline/entity-cache.ts +2 -2
  147. package/src/pipeline/event-consumer-state.ts +0 -13
  148. package/src/pipeline/event-dispatcher.ts +4 -4
  149. package/src/pipeline/index.ts +0 -2
  150. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  151. package/src/pipeline/msp-rebuild.ts +5 -5
  152. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  153. package/src/pipeline/projection-rebuild.ts +2 -2
  154. package/src/pipeline/projection-state.ts +0 -12
  155. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  156. package/src/rate-limit/resolver.ts +1 -1
  157. package/src/search/in-memory-adapter.ts +1 -1
  158. package/src/search/meilisearch-adapter.ts +3 -3
  159. package/src/search/types.ts +1 -1
  160. package/src/secrets/leak-guard.ts +2 -2
  161. package/src/stack/request-helper.ts +9 -5
  162. package/src/stack/test-stack.ts +1 -1
  163. package/src/testing/handler-context.ts +4 -4
  164. package/src/testing/http-cookies.ts +1 -1
  165. package/src/time/tz-context.ts +1 -2
  166. package/src/ui-types/index.ts +4 -0
  167. package/src/engine/feature-ast/extractors.ts +0 -2602
@@ -13,6 +13,7 @@ import type {
13
13
  SaveContext,
14
14
  } from "../engine/types";
15
15
  import { HookPhases } from "../engine/types";
16
+ import { createFallbackLogger } from "../logging/utils";
16
17
  import { getFallbackTracer, type Tracer } from "../observability";
17
18
  import type { EventDedup } from "./event-dedup";
18
19
 
@@ -253,14 +254,9 @@ export function createLifecycleHooks(
253
254
  }
254
255
 
255
256
  if (errors.length > 0) {
256
- const log = opts.context.log;
257
+ const logError = createFallbackLogger("lifecycle", opts.context.log);
257
258
  const msg = `${opts.phaseLabel} errors for ${opts.handlerName}`;
258
- const details = errors.map((e) => `${e.name}: ${e.error}`);
259
- if (log) {
260
- log.error(msg, { errors: details });
261
- } else {
262
- console.error(`[lifecycle] ${msg}:`, details);
263
- }
259
+ logError.error(msg, { details: errors.map((e) => `${e.name}: ${e.error}`) });
264
260
  }
265
261
  }
266
262
 
@@ -397,11 +393,9 @@ export function createLifecycleHooks(
397
393
  // skip: all batch hooks succeeded, nothing to log
398
394
  if (failures.length === 0) return;
399
395
 
400
- const log = opts.context.log;
396
+ const logError = createFallbackLogger("lifecycle", opts.context.log);
401
397
  const msg = `${opts.phaseLabel} errors`;
402
- const details = failures.map((f) => `${f.name}: ${f.outcome.reason}`);
403
- if (log) log.error(msg, { errors: details });
404
- else console.error(`[lifecycle] ${msg}:`, details);
398
+ logError.error(msg, { details: failures.map((f) => `${f.name}: ${f.outcome.reason}`) });
405
399
  }
406
400
  }
407
401
 
@@ -422,6 +416,6 @@ export function buildEventId(handlerName: string, payload: unknown, phase: strin
422
416
  if (typeof rawId !== "string" && typeof rawId !== "number") return null;
423
417
  if (rawId === 0 || rawId === "") return null;
424
418
  const data = p["data"] as Record<string, unknown> | undefined; // @cast-boundary engine-payload
425
- const version = data?.["version"] as number | undefined;
419
+ const version = data?.["version"] as number | undefined; // @cast-boundary engine-payload
426
420
  return `${handlerName}:${rawId}:${version ?? 0}:${phase}`;
427
421
  }
@@ -67,7 +67,7 @@ function createRebuildCtx(
67
67
  };
68
68
  return {
69
69
  appendEvent: refuseAppend as MultiStreamApplyContext["appendEvent"], // @cast-boundary engine-bridge
70
- appendEventUnsafe: refuseAppend,
70
+ unsafeAppendEvent: refuseAppend,
71
71
  loadAggregate: async (aggregateId, options) => {
72
72
  const events = options?.asOf
73
73
  ? await loadAggregateAsOf(db, aggregateId, tenantId, options.asOf)
@@ -142,7 +142,7 @@ export async function rebuildMultiStreamProjection(
142
142
  // msp.table is narrowed by the upfront guard; the assertion here is
143
143
  // for TS inside the async closure (narrowing doesn't cross the
144
144
  // transaction boundary).
145
- const tableName = getTableName(msp.table as NonNullable<typeof msp.table>);
145
+ const tableName = getTableName(msp.table as NonNullable<typeof msp.table>); // @cast-boundary db-operator
146
146
  await tx.execute(sql.raw(`TRUNCATE TABLE ${quoteIdent(tableName)}`));
147
147
 
148
148
  const subscribedTypes = Object.keys(msp.apply);
@@ -151,7 +151,7 @@ export async function rebuildMultiStreamProjection(
151
151
  .select()
152
152
  .from(eventsTable)
153
153
  .where(inArray(eventsTable.type, subscribedTypes))
154
- .orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>;
154
+ .orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>; // @cast-boundary db-row
155
155
 
156
156
  const upcasters = registry.getEventUpcasters();
157
157
  for (const row of events) {
@@ -170,11 +170,11 @@ export async function rebuildMultiStreamProjection(
170
170
  };
171
171
  const storedEvent = await upcastStoredEvent(raw, upcasters, {
172
172
  db: tx,
173
- tenantId: row.tenantId as TenantId,
173
+ tenantId: row.tenantId as TenantId, // @cast-boundary db-row
174
174
  });
175
175
  const applyFn = msp.apply[row.type];
176
176
  if (!applyFn) continue;
177
- const rebuildCtx = createRebuildCtx(registry, tx, row.tenantId as TenantId);
177
+ const rebuildCtx = createRebuildCtx(registry, tx, row.tenantId as TenantId); // @cast-boundary db-row
178
178
  await applyFn(storedEvent, tx, rebuildCtx);
179
179
  eventsProcessed++;
180
180
  lastProcessedEventId = row.id;
@@ -2,10 +2,10 @@ import type { DbRunner } from "../db/connection";
2
2
  import type {
3
3
  AppendEventArgs,
4
4
  AppendEventFn,
5
- AppendEventUnsafeFn,
6
5
  KumikoEventTypeMap,
7
6
  Registry,
8
7
  TenantId,
8
+ UnsafeAppendEventFn,
9
9
  } from "../engine/types";
10
10
  import { loadAggregate, loadAggregateAsOf, type StoredEvent } from "../event-store/event-store";
11
11
  import { upcastStoredEvents } from "../event-store/upcaster";
@@ -20,7 +20,7 @@ import { appendDomainEventCore } from "./append-event-core";
20
20
  //
21
21
  // TMap propagates the strict event-type-map (see HandlerContext). Default
22
22
  // matches the global KumikoEventTypeMap; runtime-pluggable callers route
23
- // through appendEventUnsafe.
23
+ // through unsafeAppendEvent.
24
24
  export type MultiStreamApplyContext<TMap extends object = KumikoEventTypeMap> = {
25
25
  // Append a domain event onto an aggregate stream in the CURRENT tx.
26
26
  // Schema-validated, archive-guarded, stream-version derived. Metadata
@@ -31,7 +31,7 @@ export type MultiStreamApplyContext<TMap extends object = KumikoEventTypeMap> =
31
31
  readonly appendEvent: AppendEventFn<TMap>;
32
32
  // Escape hatch for runtime-pluggable events without compile-time
33
33
  // augmentation. Same runtime semantics; type-surface is `payload: unknown`.
34
- readonly appendEventUnsafe: AppendEventUnsafeFn;
34
+ readonly unsafeAppendEvent: UnsafeAppendEventFn;
35
35
  // Read an aggregate stream — useful when a saga needs to inspect the
36
36
  // current state of a different aggregate before deciding what to emit.
37
37
  readonly loadAggregate: (
@@ -74,7 +74,6 @@ export function createMultiStreamApplyContext(
74
74
  ): MultiStreamApplyContext {
75
75
  return {
76
76
  ...(deps.files ? { files: deps.files } : {}),
77
- // @cast-boundary engine-bridge — concrete impl conforms to AppendEventFn overload
78
77
  appendEvent: (async (args: AppendEventArgs) => {
79
78
  await appendDomainEventCore(
80
79
  {
@@ -87,15 +86,15 @@ export function createMultiStreamApplyContext(
87
86
  },
88
87
  args,
89
88
  );
90
- }) as AppendEventFn,
91
- appendEventUnsafe: async (args) => {
89
+ }) as AppendEventFn, // @cast-boundary engine-bridge
90
+ unsafeAppendEvent: async (args) => {
92
91
  await appendDomainEventCore(
93
92
  {
94
93
  registry: deps.registry,
95
94
  db: deps.db,
96
95
  tenantId: deps.tenantId,
97
96
  userId: deps.userId,
98
- callSiteLabel: "MSP-apply ctx.appendEventUnsafe",
97
+ callSiteLabel: "MSP-apply ctx.unsafeAppendEvent",
99
98
  ...(deps.callerFeature && { callerFeature: deps.callerFeature }),
100
99
  },
101
100
  args,
@@ -127,7 +127,7 @@ export async function rebuildProjection(
127
127
  subscribed,
128
128
  )}`,
129
129
  )
130
- .orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>;
130
+ .orderBy(asc(eventsTable.id))) as ReadonlyArray<typeof eventsTable.$inferSelect>; // @cast-boundary db-row
131
131
 
132
132
  // Upcasters run at read time: older stored payloads get walked
133
133
  // through the registered r.eventMigration chain until their shape
@@ -151,7 +151,7 @@ export async function rebuildProjection(
151
151
  };
152
152
  const storedEvent = await upcastStoredEvent(raw, upcasters, {
153
153
  db: tx,
154
- tenantId: row.tenantId as TenantId,
154
+ tenantId: row.tenantId as TenantId, // @cast-boundary db-row
155
155
  });
156
156
  const applyFn = projection.apply[row.type];
157
157
  // skip: apply-key validation ensures every subscribed type has a
@@ -49,18 +49,6 @@ export const ProjectionStatuses = {
49
49
  } as const;
50
50
  export type ProjectionStatus = (typeof ProjectionStatuses)[keyof typeof ProjectionStatuses];
51
51
 
52
- /**
53
- * @deprecated Use `ProjectionStatuses` (object form) or the `ProjectionStatus`
54
- * union type. Tuple alias kept for back-compat with callers that relied on
55
- * the array form (`z.enum(...)`, runtime iteration) — scheduled for removal
56
- * after downstream migration.
57
- */
58
- export const PROJECTION_STATUSES = [
59
- "idle",
60
- "rebuilding",
61
- "failed",
62
- ] as const satisfies readonly ProjectionStatus[];
63
-
64
52
  // Idempotent table bootstrap. Called by setupTestStack (and createApp once
65
53
  // that wires it up) — same pattern as createEventsTable. If the table is
66
54
  // already there (second stack in same test DB, production boot after
@@ -1,7 +1,11 @@
1
1
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest";
2
2
  import { RateLimitError } from "../../errors";
3
3
  import { createTestRedis, type TestRedis } from "../../stack";
4
- import { createRateLimitResolver, type RateLimitResolver } from "../resolver";
4
+ import {
5
+ createRateLimitResolver,
6
+ type RateLimitDecision,
7
+ type RateLimitResolver,
8
+ } from "../resolver";
5
9
 
6
10
  let testRedis: TestRedis;
7
11
  let resolver: RateLimitResolver;
@@ -32,7 +36,7 @@ beforeEach(async () => {
32
36
  describe("createRateLimitResolver — token bucket basics", () => {
33
37
  test("first N requests within limit are allowed, N+1 is rejected", async () => {
34
38
  const config = { limit: 5, windowSeconds: 60 };
35
- const decisions = [];
39
+ const decisions: RateLimitDecision[] = [];
36
40
  for (let i = 0; i < 5; i++) {
37
41
  decisions.push(await resolver.check("user:42", config));
38
42
  }
@@ -66,7 +70,7 @@ describe("createRateLimitResolver — token bucket basics", () => {
66
70
 
67
71
  // Advance 5s = window/2 → ~5 tokens refilled
68
72
  mockNowMs += 5000;
69
- const decisions = [];
73
+ const decisions: RateLimitDecision[] = [];
70
74
  for (let i = 0; i < 5; i++) {
71
75
  decisions.push(await resolver.check("refill:user", config));
72
76
  }
@@ -84,7 +88,7 @@ describe("createRateLimitResolver — token bucket basics", () => {
84
88
  // Advance 10× the window → bucket would overflow without the cap.
85
89
  mockNowMs += 10 * 10 * 1000;
86
90
 
87
- const decisions = [];
91
+ const decisions: RateLimitDecision[] = [];
88
92
  for (let i = 0; i < 5; i++) {
89
93
  decisions.push(await resolver.check("idle:user", config));
90
94
  }
@@ -185,7 +185,7 @@ function ensureCommand(redis: Redis): CommandClient {
185
185
  });
186
186
  REGISTERED.add(redis);
187
187
  }
188
- return redis as CommandClient;
188
+ return redis as CommandClient; // @cast-boundary engine-bridge
189
189
  }
190
190
 
191
191
  export function createRateLimitResolver(opts: RateLimitResolverOptions): RateLimitResolver {
@@ -1,4 +1,4 @@
1
- import type { EntityId, TenantId } from "@cosmicdrift/kumiko-framework/engine";
1
+ import type { EntityId, TenantId } from "../engine/types/identifiers";
2
2
  import type { SearchAdapter, SearchAdapterConfig, SearchResult } from "./types";
3
3
 
4
4
  type StoredDoc = {
@@ -1,5 +1,5 @@
1
- import type { EntityId, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
1
  import { Meilisearch } from "meilisearch";
2
+ import type { EntityId, TenantId } from "../engine/types/identifiers";
3
3
  import type { SearchAdapter, SearchResult } from "./types";
4
4
 
5
5
  export type MeilisearchAdapterOptions = {
@@ -92,8 +92,8 @@ export function createMeilisearchAdapter(options: MeilisearchAdapterOptions): Se
92
92
 
93
93
  return results.hits.map(
94
94
  (hit: Record<string, unknown>): SearchResult => ({
95
- entityType: hit["_type"] as string,
96
- entityId: hit["_entityId"] as EntityId,
95
+ entityType: hit["_type"] as string, // @cast-boundary engine-bridge
96
+ entityId: hit["_entityId"] as EntityId, // @cast-boundary engine-bridge
97
97
  }),
98
98
  );
99
99
  },
@@ -1,4 +1,4 @@
1
- import type { EntityId, TenantId } from "@cosmicdrift/kumiko-framework/engine";
1
+ import type { EntityId, TenantId } from "../engine/types/identifiers";
2
2
 
3
3
  export type SearchAdapterConfig = {
4
4
  searchableFields: readonly string[];
@@ -80,8 +80,8 @@ export function assertNoSecretLeak(value: unknown, path = "$", depth = 0): void
80
80
  return;
81
81
  }
82
82
 
83
- for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
84
- // @cast-boundary recursive-walk
83
+ const obj = value as Record<string, unknown>; // @cast-boundary recursive-walk
84
+ for (const [k, v] of Object.entries(obj)) {
85
85
  assertNoSecretLeak(v, `${path}.${k}`, depth + 1);
86
86
  }
87
87
  }
@@ -112,7 +112,9 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
112
112
  const res = await writeRaw(type, payload, user, requestId);
113
113
  // wire-body shape direkt nach JSON.parse — Caller-Code prüft danach
114
114
  // selber ob isSuccess/error/data tatsächlich da sind.
115
- const body = (await res.json()) as {
115
+ const rawBody = await res.json();
116
+ const body = rawBody as {
117
+ // @cast-boundary engine-bridge
116
118
  isSuccess?: boolean;
117
119
  data?: unknown;
118
120
  error?: { code?: string } | string;
@@ -126,7 +128,7 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
126
128
  (typeof body.error === "string" ? body.error : "unknown");
127
129
  throw new Error(`Expected write "${type}" to succeed but got error: ${code}`);
128
130
  }
129
- return body.data as T;
131
+ return body.data as T; // @cast-boundary engine-bridge
130
132
  },
131
133
 
132
134
  async writeErr(
@@ -135,7 +137,9 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
135
137
  user: SessionUser,
136
138
  ): Promise<import("../errors").WriteErrorInfo> {
137
139
  const res = await writeRaw(type, payload, user);
138
- const body = (await res.json()) as {
140
+ const rawErrorBody = await res.json();
141
+ const body = rawErrorBody as {
142
+ // @cast-boundary engine-bridge
139
143
  isSuccess?: boolean;
140
144
  error?: Omit<import("../errors").WriteErrorInfo, "httpStatus">;
141
145
  };
@@ -156,8 +160,8 @@ export function createRequestHelper(app: Hono, jwt: JwtHelper): RequestHelper {
156
160
 
157
161
  async queryOk<T = unknown>(type: string, payload: unknown, user: SessionUser): Promise<T> {
158
162
  const res = await queryRaw(type, payload, user);
159
- const body = (await res.json()) as { data: unknown };
160
- return body.data as T;
163
+ const body = (await res.json()) as { data: unknown }; // @cast-boundary engine-bridge
164
+ return body.data as T; // @cast-boundary engine-bridge
161
165
  },
162
166
 
163
167
  async writeWithHeaders(type, payload, user, extraHeaders) {
@@ -209,7 +209,7 @@ export async function setupTestStack(options: TestStackOptions): Promise<TestSta
209
209
  const { getTableName } = await import("drizzle-orm");
210
210
  const missing: Record<string, unknown> = {};
211
211
  for (const [key, tbl] of Object.entries(projectionTables)) {
212
- const physical = getTableName(tbl as Parameters<typeof getTableName>[0]);
212
+ const physical = getTableName(tbl as Parameters<typeof getTableName>[0]); // @cast-boundary drizzle-bridge
213
213
  if (await tableExists(testDb.db, `public.${physical}`)) continue;
214
214
  missing[key] = tbl;
215
215
  }
@@ -45,7 +45,7 @@ export function bridgeStub(opts?: {
45
45
  | "write"
46
46
  | "writeAs"
47
47
  | "appendEvent"
48
- | "appendEventUnsafe"
48
+ | "unsafeAppendEvent"
49
49
  | "fetchForWriting"
50
50
  | "loadAggregate"
51
51
  | "archiveStream"
@@ -68,12 +68,12 @@ export function bridgeStub(opts?: {
68
68
  // SessionUser hier und bekommt ihn am ctx zurück.
69
69
  const stubUser: SessionUser = opts?.user ?? {
70
70
  id: "00000000-0000-0000-0000-000000000000",
71
- tenantId: "00000000-0000-0000-0000-000000000000" as SessionUser["tenantId"],
71
+ tenantId: "00000000-0000-0000-0000-000000000000" as SessionUser["tenantId"], // @cast-boundary engine-bridge
72
72
  roles: ["all"],
73
73
  };
74
74
  return {
75
75
  user: stubUser,
76
- query: notAvailable("query") as HandlerContext["query"],
76
+ query: notAvailable("query") as HandlerContext["query"], // @cast-boundary engine-bridge
77
77
  queryAs: notAvailable("queryAs") as unknown as (
78
78
  user: SessionUser,
79
79
  qn: string,
@@ -89,7 +89,7 @@ export function bridgeStub(opts?: {
89
89
  payload: unknown,
90
90
  ) => Promise<WriteResult>,
91
91
  appendEvent: notAvailable("appendEvent") as unknown as (args: AppendEventArgs) => Promise<void>,
92
- appendEventUnsafe: notAvailable("appendEventUnsafe") as unknown as (
92
+ unsafeAppendEvent: notAvailable("unsafeAppendEvent") as unknown as (
93
93
  args: AppendEventArgs,
94
94
  ) => Promise<void>,
95
95
  fetchForWriting: notAvailable("fetchForWriting") as unknown as (
@@ -22,7 +22,7 @@ export type ParsedSetCookie = {
22
22
  // is acceptable here because tests that set multiple cookies run on a
23
23
  // runtime that supports getSetCookie.
24
24
  export function getSetCookies(res: Response): Map<string, ParsedSetCookie> {
25
- const getter = (res.headers as { getSetCookie?: () => string[] }).getSetCookie;
25
+ const getter = (res.headers as { getSetCookie?: () => string[] }).getSetCookie; // @cast-boundary engine-bridge
26
26
  const raws = getter ? getter.call(res.headers) : [res.headers.get("set-cookie") ?? ""];
27
27
  const out = new Map<string, ParsedSetCookie>();
28
28
  for (const raw of raws) {
@@ -11,9 +11,8 @@
11
11
  // kommt in einer späteren Iteration, wenn alle existing usages migriert sind.
12
12
  //
13
13
  // `tenant` + `user` sind die TZ-Defaults für den aktuellen Request. Aktueller
14
- // Stand (Iteration 4): beide default auf "UTC" — sobald tenant.timezone +
14
+ // Stand: beide default auf "UTC" — sobald tenant.timezone +
15
15
  // user.timezone Felder existieren, lese ich sie aus dem Request-Context.
16
- // TODO(Iteration 6): aus tenant entity / user profile lesen.
17
16
 
18
17
  import { ensureTemporalPolyfill, getTemporal } from "./polyfill";
19
18
 
@@ -55,10 +55,14 @@ export type {
55
55
  FieldRenderer,
56
56
  ListColumnSpec,
57
57
  PlatformComponent,
58
+ RowAction,
59
+ RowActionNavigate,
60
+ RowActionWriteHandler,
58
61
  ScreenDefinition,
59
62
  ScreenFilter,
60
63
  ScreenFilterOp,
61
64
  ScreenSlots,
65
+ ToolbarAction,
62
66
  } from "../engine/types/screen";
63
67
  export { normalizeEditField, normalizeListColumn } from "../engine/types/screen";
64
68
  export type { WorkspaceDefinition } from "../engine/types/workspace";