@cosmicdrift/kumiko-bundled-features 0.309.0 → 0.311.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.309.0",
3
+ "version": "0.311.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>",
@@ -133,12 +133,12 @@
133
133
  "./workflow-runner": "./src/workflow-runner/index.ts"
134
134
  },
135
135
  "dependencies": {
136
- "@cosmicdrift/kumiko-dispatcher-live": "0.309.0",
137
- "@cosmicdrift/kumiko-framework": "0.309.0",
138
- "@cosmicdrift/kumiko-headless": "0.309.0",
139
- "@cosmicdrift/kumiko-renderer": "0.309.0",
140
- "@cosmicdrift/kumiko-renderer-web": "0.309.0",
141
- "@cosmicdrift/kumiko-types": "0.309.0",
136
+ "@cosmicdrift/kumiko-dispatcher-live": "0.311.0",
137
+ "@cosmicdrift/kumiko-framework": "0.311.0",
138
+ "@cosmicdrift/kumiko-headless": "0.311.0",
139
+ "@cosmicdrift/kumiko-renderer": "0.311.0",
140
+ "@cosmicdrift/kumiko-renderer-web": "0.311.0",
141
+ "@cosmicdrift/kumiko-types": "0.311.0",
142
142
  "@mollie/api-client": "^4.5.0",
143
143
  "@node-rs/argon2": "^2.0.2",
144
144
  "@types/mailparser": "^3.4.6",
@@ -167,8 +167,8 @@
167
167
  ],
168
168
  "devDependencies": {
169
169
  "@testing-library/user-event": "^14.6.1",
170
- "@cosmicdrift/kumiko-locale-de": "0.309.0",
171
- "@cosmicdrift/kumiko-locale-es": "0.309.0",
170
+ "@cosmicdrift/kumiko-locale-de": "0.311.0",
171
+ "@cosmicdrift/kumiko-locale-es": "0.311.0",
172
172
  "jsqr": "^1.4.0"
173
173
  }
174
174
  }
@@ -12,6 +12,7 @@ import {
12
12
  type TestStack,
13
13
  testTenantId,
14
14
  } from "@cosmicdrift/kumiko-framework/stack";
15
+ import { waitFor } from "@cosmicdrift/kumiko-framework/testing";
15
16
  import * as z from "zod";
16
17
  import { createConfigFeature } from "../../config";
17
18
  import { createTenantFeature } from "../../tenant";
@@ -71,18 +72,21 @@ type AuditRow = {
71
72
  type AuditResponse = { rows: AuditRow[]; nextBefore: string | null };
72
73
 
73
74
  async function pollForEscapeHatchRow(): Promise<AuditRow> {
74
- const deadline = Date.now() + 2000;
75
- while (Date.now() < deadline) {
76
- const res = await stack.http.queryOk<AuditResponse>(
77
- AuditQueries.list,
78
- { eventType: ESCAPE_HATCH_USED_EVENT },
79
- adminOfSameTenant,
80
- );
81
- const row = res.rows[0];
82
- if (row) return row;
83
- await new Promise((resolve) => setTimeout(resolve, 50));
84
- }
85
- throw new Error("escape-hatch-used audit row did not appear within 2s");
75
+ let row: AuditRow | undefined;
76
+ await waitFor(
77
+ async () => {
78
+ const res = await stack.http.queryOk<AuditResponse>(
79
+ AuditQueries.list,
80
+ { eventType: ESCAPE_HATCH_USED_EVENT },
81
+ adminOfSameTenant,
82
+ );
83
+ row = res.rows[0];
84
+ return row !== undefined;
85
+ },
86
+ { delays: Array(40).fill(50) },
87
+ );
88
+ if (row === undefined) throw new Error("escape-hatch-used audit row did not appear within 2s");
89
+ return row;
86
90
  }
87
91
 
88
92
  describe("createEscapeHatchAuditSink — persists audit:event:escape-hatch-used", () => {
@@ -26,6 +26,7 @@ import {
26
26
  testTenantId,
27
27
  unsafePushTables,
28
28
  } from "@cosmicdrift/kumiko-framework/stack";
29
+ import { waitFor } from "@cosmicdrift/kumiko-framework/testing";
29
30
  import * as z from "zod";
30
31
  import { createChannelEmailFeature } from "../../channel-email/feature";
31
32
  import { createInMemoryTransport, type EmailMessage } from "../../channel-email/types";
@@ -1502,21 +1503,6 @@ describe("flow 16: repeated unsubscribe clicks are idempotent", () => {
1502
1503
  // non-string `to` as a broadcast target ("tenant" in to).
1503
1504
  const asyncRecipient = "7";
1504
1505
 
1505
- async function waitFor(check: () => Promise<void>, timeoutMs = 10000): Promise<void> {
1506
- const start = Date.now();
1507
- let lastErr: unknown;
1508
- for (;;) {
1509
- try {
1510
- await check();
1511
- return;
1512
- } catch (err) {
1513
- lastErr = err;
1514
- if (Date.now() - start > timeoutMs) throw lastErr;
1515
- await new Promise((resolve) => setTimeout(resolve, 50));
1516
- }
1517
- }
1518
- }
1519
-
1520
1506
  function makeStubRunner(): {
1521
1507
  runner: JobRunner;
1522
1508
  dispatched: Array<{
@@ -1580,13 +1566,16 @@ describe("flow 17: async render→send pipeline", () => {
1580
1566
 
1581
1567
  // Email: queued → render job → send job → sent. Proves the framework
1582
1568
  // jobRunner-into-job-ctx injection (render dispatches send) end-to-end.
1583
- await waitFor(async () => {
1584
- const rows = await selectMany(db, deliveryAttemptsTable, {
1585
- notificationType: "app:notify:async-e2e",
1586
- channel: "email",
1587
- });
1588
- expect(rows.some((r) => r["status"] === "sent")).toBe(true);
1589
- });
1569
+ await waitFor(
1570
+ async () => {
1571
+ const rows = await selectMany(db, deliveryAttemptsTable, {
1572
+ notificationType: "app:notify:async-e2e",
1573
+ channel: "email",
1574
+ });
1575
+ expect(rows.some((r) => r["status"] === "sent")).toBe(true);
1576
+ },
1577
+ { delays: Array(40).fill(250) },
1578
+ );
1590
1579
  const email = emailTransport.sent.find((m) => m.to === testEmail(asyncRecipient));
1591
1580
  expect(email).toBeDefined();
1592
1581
  expect(email?.subject).toBe("Async Subject");
@@ -1594,13 +1583,16 @@ describe("flow 17: async render→send pipeline", () => {
1594
1583
  expect(email?.html).toContain("<!DOCTYPE html>");
1595
1584
 
1596
1585
  // Push: no render step → dispatched straight to delivery.send.
1597
- await waitFor(async () => {
1598
- const rows = await selectMany(db, deliveryAttemptsTable, {
1599
- notificationType: "app:notify:async-e2e",
1600
- channel: "push",
1601
- });
1602
- expect(rows.some((r) => r["status"] === "sent")).toBe(true);
1603
- });
1586
+ await waitFor(
1587
+ async () => {
1588
+ const rows = await selectMany(db, deliveryAttemptsTable, {
1589
+ notificationType: "app:notify:async-e2e",
1590
+ channel: "push",
1591
+ });
1592
+ expect(rows.some((r) => r["status"] === "sent")).toBe(true);
1593
+ },
1594
+ { delays: Array(40).fill(250) },
1595
+ );
1604
1596
  expect(
1605
1597
  pushTransport.sent.find((m) => m.token === testPushToken(asyncRecipient)),
1606
1598
  ).toBeDefined();
@@ -0,0 +1,224 @@
1
+ // Proves kumiko-framework#3255: `publicTenantResolution: "fileRef"` lets a
2
+ // single shared platform host serve every tenant's public variants — the
3
+ // variant is read from the FileRef row's own tenant, not the host's, while
4
+ // resolveApexTenant still gates the host and the FileRef-tenant's own
5
+ // `isPublic` predicate remains the default-deny gate. Default ("host")
6
+ // mode is unchanged: a tenant B file requested through tenant A's host
7
+ // still 404s, identically to any other unknown FileRef.
8
+
9
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
10
+ import {
11
+ createEntity,
12
+ createImageField,
13
+ defineFeature,
14
+ EXT_DERIVATIVE_PUBLIC_PREDICATE,
15
+ EXT_DERIVATIVE_RENDERER,
16
+ } from "@cosmicdrift/kumiko-framework/engine";
17
+ import {
18
+ createFilesFeature,
19
+ createInMemoryFileProvider,
20
+ } from "@cosmicdrift/kumiko-framework/files";
21
+ import {
22
+ createTestUser,
23
+ setupTestStack,
24
+ type TestStack,
25
+ testTenantId,
26
+ } from "@cosmicdrift/kumiko-framework/stack";
27
+ import {
28
+ buildMultipartBody,
29
+ patchFileInstanceofForBunTest,
30
+ } from "@cosmicdrift/kumiko-framework/testing";
31
+ import type { DerivativeRendererPlugin } from "@cosmicdrift/kumiko-types/derivatives-types";
32
+ import { createConfigFeature } from "../../config";
33
+ import { fileFoundationFeature } from "../../file-foundation";
34
+ import { createFileDerivativesFeature } from "../feature";
35
+ import type { DerivativePublicPredicateArgs } from "../handlers/public-variant.query";
36
+ import { PUBLIC_VARIANT_BY_FILE_REF_QN } from "../handlers/public-variant-by-file-ref.query";
37
+
38
+ const VARIANT_BYTES = new Uint8Array([4, 2, 4, 2]);
39
+ const fakeRender: DerivativeRendererPlugin["render"] = async () => VARIANT_BYTES;
40
+
41
+ const TENANT_A = testTenantId(11);
42
+ const TENANT_B = testTenantId(12);
43
+ const HOST_A = "cross-tenant-a.example.com";
44
+
45
+ let receivedArgs: DerivativePublicPredicateArgs[] = [];
46
+ const gadgetEntity = createEntity({
47
+ table: "cross_tenant_public_variant_gadgets",
48
+ fields: {
49
+ img: createImageField({ variants: { thumb: { maxEdge: 160, format: "webp" } } }),
50
+ },
51
+ });
52
+
53
+ const gadgetPredicateFeature = defineFeature("crosstenantpublicvarianttest", (r) => {
54
+ r.entity("gadget", gadgetEntity);
55
+ r.useExtension(EXT_DERIVATIVE_PUBLIC_PREDICATE, "gadget", {
56
+ isPublic: (args: DerivativePublicPredicateArgs) => {
57
+ receivedArgs.push(args);
58
+ return args.entityId === "public-1";
59
+ },
60
+ });
61
+ r.useExtension(EXT_DERIVATIVE_RENDERER, "image/*", { render: fakeRender });
62
+ });
63
+
64
+ const userA = createTestUser({ id: 1, tenantId: TENANT_A, roles: ["Admin"] });
65
+ const userB = createTestUser({ id: 2, tenantId: TENANT_B, roles: ["Admin"] });
66
+
67
+ async function uploadImage(
68
+ stack: TestStack,
69
+ asUser: typeof userA,
70
+ entityId: string,
71
+ ): Promise<string> {
72
+ const token = await stack.jwt.sign(asUser);
73
+ const fd = new FormData();
74
+ fd.append("file", new File([Buffer.from([1, 2, 3])], "img.jpg", { type: "image/jpeg" }));
75
+ fd.append("entityType", "gadget");
76
+ fd.append("entityId", entityId);
77
+ fd.append("fieldName", "img");
78
+ const { body, contentType } = await buildMultipartBody(fd);
79
+ const res = await stack.app.request("/api/files", {
80
+ method: "POST",
81
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
82
+ body,
83
+ });
84
+ expect(res.status).toBe(201);
85
+ const json = (await res.json()) as { id: string };
86
+ return json.id;
87
+ }
88
+
89
+ describe("file-derivatives :: publicTenantResolution 'fileRef' — cross-tenant shared host", () => {
90
+ let stack: TestStack;
91
+
92
+ beforeAll(async () => {
93
+ patchFileInstanceofForBunTest();
94
+ stack = await setupTestStack({
95
+ features: [
96
+ createConfigFeature(),
97
+ fileFoundationFeature,
98
+ createFilesFeature(),
99
+ createFileDerivativesFeature({
100
+ resolveApexTenant: (host) => (host === HOST_A ? TENANT_A : null),
101
+ publicTenantResolution: "fileRef",
102
+ }),
103
+ gadgetPredicateFeature,
104
+ ],
105
+ files: { storageProvider: createInMemoryFileProvider() },
106
+ });
107
+ });
108
+
109
+ afterAll(async () => {
110
+ await stack.cleanup();
111
+ });
112
+
113
+ beforeEach(async () => {
114
+ receivedArgs = [];
115
+ await stack.redis.flushNamespace();
116
+ });
117
+
118
+ test("a tenant B public file is served through tenant A's shared host, isPublic runs against tenant B", async () => {
119
+ const fileId = await uploadImage(stack, userB, "public-1");
120
+
121
+ const res = await stack.app.request(`http://${HOST_A}/media/${fileId}/thumb`);
122
+
123
+ expect(res.status).toBe(200);
124
+ expect(new Uint8Array(await res.arrayBuffer())).toEqual(VARIANT_BYTES);
125
+ expect(receivedArgs).toHaveLength(1);
126
+ expect(receivedArgs[0]?.tenantId).toBe(TENANT_B);
127
+ });
128
+
129
+ test("a tenant A public file is also served through tenant A's host", async () => {
130
+ const fileId = await uploadImage(stack, userA, "public-1");
131
+
132
+ const res = await stack.app.request(`http://${HOST_A}/media/${fileId}/thumb`);
133
+
134
+ expect(res.status).toBe(200);
135
+ expect(receivedArgs[0]?.tenantId).toBe(TENANT_A);
136
+ });
137
+
138
+ test("a non-public tenant B file, an unknown fileRefId, and an unknown host all 404 with the same body", async () => {
139
+ const privateFileId = await uploadImage(stack, userB, "private-1");
140
+
141
+ const privateRes = await stack.app.request(`http://${HOST_A}/media/${privateFileId}/thumb`);
142
+ const unknownFileRefRes = await stack.app.request(
143
+ `http://${HOST_A}/media/00000000-0000-4000-8000-000000000000/thumb`,
144
+ );
145
+
146
+ expect(privateRes.status).toBe(404);
147
+ expect(unknownFileRefRes.status).toBe(404);
148
+
149
+ const unknownFileRefBody = await unknownFileRefRes.text();
150
+ expect(await privateRes.text()).toBe(unknownFileRefBody);
151
+ });
152
+
153
+ test("an unknown host 404s even for a public file, before isPublic runs", async () => {
154
+ const publicFileId = await uploadImage(stack, userB, "public-1");
155
+ const unknownFileRefRes = await stack.app.request(
156
+ `http://${HOST_A}/media/00000000-0000-4000-8000-000000000000/thumb`,
157
+ );
158
+ receivedArgs = [];
159
+
160
+ const unknownHostRes = await stack.app.request(
161
+ `http://unknown-host.example.com/media/${publicFileId}/thumb`,
162
+ );
163
+
164
+ expect(unknownHostRes.status).toBe(404);
165
+ expect(await unknownHostRes.text()).toBe(await unknownFileRefRes.text());
166
+ expect(receivedArgs).toHaveLength(0);
167
+ });
168
+
169
+ test("PUBLIC_VARIANT_BY_FILE_REF_QN is registered on the stack in 'fileRef' mode", () => {
170
+ expect(stack.registry.getQueryHandler(PUBLIC_VARIANT_BY_FILE_REF_QN)).toBeDefined();
171
+ });
172
+ });
173
+
174
+ describe("file-derivatives :: default ('host') mode is unchanged", () => {
175
+ let stack: TestStack;
176
+
177
+ beforeAll(async () => {
178
+ patchFileInstanceofForBunTest();
179
+ stack = await setupTestStack({
180
+ features: [
181
+ createConfigFeature(),
182
+ fileFoundationFeature,
183
+ createFilesFeature(),
184
+ createFileDerivativesFeature({
185
+ resolveApexTenant: (host) => (host === HOST_A ? TENANT_A : null),
186
+ }),
187
+ gadgetPredicateFeature,
188
+ ],
189
+ files: { storageProvider: createInMemoryFileProvider() },
190
+ });
191
+ });
192
+
193
+ afterAll(async () => {
194
+ await stack.cleanup();
195
+ });
196
+
197
+ beforeEach(async () => {
198
+ receivedArgs = [];
199
+ await stack.redis.flushNamespace();
200
+ });
201
+
202
+ test("a tenant B public file requested through tenant A's host 404s, identical body to an unknown FileRef", async () => {
203
+ const fileId = await uploadImage(stack, userB, "public-1");
204
+
205
+ const crossTenantRes = await stack.app.request(`http://${HOST_A}/media/${fileId}/thumb`);
206
+ const unknownFileRefRes = await stack.app.request(
207
+ `http://${HOST_A}/media/00000000-0000-4000-8000-000000000000/thumb`,
208
+ );
209
+
210
+ expect(crossTenantRes.status).toBe(404);
211
+ expect(unknownFileRefRes.status).toBe(404);
212
+ expect(await crossTenantRes.text()).toBe(await unknownFileRefRes.text());
213
+ });
214
+
215
+ test("PUBLIC_VARIANT_BY_FILE_REF_QN is not registered in default mode — not reachable via the generic /api/query dispatch", () => {
216
+ expect(stack.registry.getQueryHandler(PUBLIC_VARIANT_BY_FILE_REF_QN)).toBeUndefined();
217
+ });
218
+ });
219
+
220
+ describe("createFileDerivativesFeature :: publicTenantResolution requires resolveApexTenant", () => {
221
+ test("throws when 'fileRef' is passed without resolveApexTenant", () => {
222
+ expect(() => createFileDerivativesFeature({ publicTenantResolution: "fileRef" })).toThrow();
223
+ });
224
+ });
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.310.0",
4
+ "type": "improvement",
5
+ "title": "publicTenantResolution \"fileRef\" reads a public variant from its FileRef's own tenant",
6
+ "detail": "`createFileDerivativesFeature({ resolveApexTenant, publicTenantResolution: \"fileRef\" })`\nkeeps resolveApexTenant as the host gate (an unknown host still 404s), but\nreads the variant in the tenant that owns the FileRef, so one shared\nplatform host serves `/media/:fileRefId/:variant` for every tenant. The\nFileRef tenant's `isPublic` predicate stays the default-deny gate; unknown\nand non-public FileRefs answer the identical 404. Default \"host\" keeps the\nprevious behavior. In \"fileRef\" mode the anonymous\n`file-derivatives:query:public-variant-by-file-ref` query is also reachable\nover `/api`, independent of the host, and returns only variants the\npredicate allows. Passing publicTenantResolution without resolveApexTenant\nthrows at construction."
7
+ },
2
8
  {
3
9
  "version": "0.287.0",
4
10
  "type": "improvement",
@@ -18,6 +18,10 @@ import {
18
18
  } from "@cosmicdrift/kumiko-framework/engine";
19
19
  import { RateLimitError } from "@cosmicdrift/kumiko-framework/errors";
20
20
  import { PUBLIC_VARIANT_QN, publicVariantQuery } from "./handlers/public-variant.query";
21
+ import {
22
+ PUBLIC_VARIANT_BY_FILE_REF_QN,
23
+ publicVariantByFileRefQuery,
24
+ } from "./handlers/public-variant-by-file-ref.query";
21
25
 
22
26
  const FEATURE_NAME = "file-derivatives";
23
27
 
@@ -42,6 +46,8 @@ export type PublicVariantResolveApexTenant = (
42
46
  host: string,
43
47
  ) => Promise<TenantId | null> | TenantId | null;
44
48
 
49
+ export type PublicVariantTenantResolution = "host" | "fileRef";
50
+
45
51
  export type FileDerivativesOptions = {
46
52
  /** Host → tenantId for the anonymous `/media/:fileRefId/:variant` route.
47
53
  * Without this option, neither the httpRoute NOR the `publicVariant`
@@ -52,6 +58,14 @@ export type FileDerivativesOptions = {
52
58
  readonly resolveApexTenant?: PublicVariantResolveApexTenant;
53
59
  /** Base path of the public variant route. Default "/media". */
54
60
  readonly basePath?: string;
61
+ /** How the route picks the tenant a variant is read from. "host" (default):
62
+ * tenant = resolveApexTenant(host). "fileRef": resolveApexTenant still
63
+ * gates the host (an unknown host still 404s), but the variant itself is
64
+ * read from the FileRef row's own tenant — a single shared platform host
65
+ * then serves every tenant's public variants. The FileRef-tenant's own
66
+ * `isPublic` predicate remains the default-deny gate either way; requires
67
+ * `resolveApexTenant`. */
68
+ readonly publicTenantResolution?: PublicVariantTenantResolution;
55
69
  };
56
70
 
57
71
  // Raw handler-return of publicVariantQuery — systemQuery dispatches
@@ -73,13 +87,22 @@ type PublicVariantQueryResult = {
73
87
  // — only a name, resolved against the field's own `variants` declaration —
74
88
  // and only after the app's registered `isPublic` predicate for the FileRef's
75
89
  // entityType says yes. tenantId is resolved from the request Host via
76
- // `resolveApexTenant`, NEVER read from the request payload.
90
+ // `resolveApexTenant`, NEVER read from the request payload — in
91
+ // `publicTenantResolution: "fileRef"` mode the tenant instead comes from the
92
+ // FileRef row matching `fileRefId` (never from the payload either), and the
93
+ // by-file-ref handler is reachable over `/api` independently of any Host,
94
+ // but still only returns a variant its FileRef-tenant's `isPublic` allows.
77
95
  export function createFileDerivativesFeature(opts: FileDerivativesOptions = {}): FeatureDefinition {
78
96
  const basePath = opts.basePath ?? "/media";
97
+ if (opts.publicTenantResolution !== undefined && !opts.resolveApexTenant) {
98
+ throw new Error(
99
+ "createFileDerivativesFeature: publicTenantResolution requires resolveApexTenant — it only changes which tenant a variant is read from, resolveApexTenant still gates the host.",
100
+ );
101
+ }
79
102
 
80
103
  return defineFeature(FEATURE_NAME, (r) => {
81
104
  r.describe(
82
- "Declares the `derivativeRenderer` extension point. `ctx.derivatives.variant(fileRefId, spec, name)` derives a variant of a tracked FileRef the first time it's requested and reuses the stored result afterwards (derive-on-first-use, keyed by a hash of the spec). Mount at least one `derivatives-*` renderer feature alongside this one — without a registered renderer for the FileRef's MIME type, every `variant(...)` call throws. Also declares the `derivativePublicPredicate` extension point (`r.useExtension(EXT_DERIVATIVE_PUBLIC_PREDICATE, '<entityType>', { isPublic })`) and, when `createFileDerivativesFeature({resolveApexTenant})` is passed a host-resolver, mounts an anonymous `GET {basePath}/:fileRefId/:variant` route that serves any variant name the FileRef's field declared in its `variants` for a FileRef whose entityType has a registered predicate returning true — default-deny (404) otherwise, same as an unknown FileRef or an undeclared variant name. The route's only rate-limit (`per: \"ip\"`) trusts the first `x-forwarded-for` hop — deployers must ensure their ingress overwrites rather than appends to that header, or the throttle is bypassable by rotating it. Also declares the `derivativeOverlayResolver` extension point (`r.useExtension(EXT_DERIVATIVE_OVERLAY_RESOLVER, '<entityType>', { resolve })`), used to turn a variant's `overlays[].dataToken` into the actual QR payload for that FileRef's entityType before the variant is rendered — a variant declaring a `qr` overlay throws at request-time if no resolver is registered for the FileRef's entityType.",
105
+ "Declares the `derivativeRenderer` extension point. `ctx.derivatives.variant(fileRefId, spec, name)` derives a variant of a tracked FileRef the first time it's requested and reuses the stored result afterwards (derive-on-first-use, keyed by a hash of the spec). Mount at least one `derivatives-*` renderer feature alongside this one — without a registered renderer for the FileRef's MIME type, every `variant(...)` call throws. Also declares the `derivativePublicPredicate` extension point (`r.useExtension(EXT_DERIVATIVE_PUBLIC_PREDICATE, '<entityType>', { isPublic })`) and, when `createFileDerivativesFeature({resolveApexTenant})` is passed a host-resolver, mounts an anonymous `GET {basePath}/:fileRefId/:variant` route that serves any variant name the FileRef's field declared in its `variants` for a FileRef whose entityType has a registered predicate returning true — default-deny (404) otherwise, same as an unknown FileRef or an undeclared variant name. The route's only rate-limit (`per: \"ip\"`) trusts the first `x-forwarded-for` hop — deployers must ensure their ingress overwrites rather than appends to that header, or the throttle is bypassable by rotating it. `publicTenantResolution: \"fileRef\"` keeps resolveApexTenant as the host gate but reads the variant from the FileRef row's own tenant instead of the host's, so one shared platform host serves every tenant's public variants — each FileRef-tenant's own `isPublic` predicate still default-denies. Also declares the `derivativeOverlayResolver` extension point (`r.useExtension(EXT_DERIVATIVE_OVERLAY_RESOLVER, '<entityType>', { resolve })`), used to turn a variant's `overlays[].dataToken` into the actual QR payload for that FileRef's entityType before the variant is rendered — a variant declaring a `qr` overlay throws at request-time if no resolver is registered for the FileRef's entityType.",
83
106
  );
84
107
  r.uiHints({
85
108
  displayLabel: "File Derivatives",
@@ -127,8 +150,18 @@ export function createFileDerivativesFeature(opts: FileDerivativesOptions = {}):
127
150
  // `resolveApexTenant` enforces.
128
151
  if (opts.resolveApexTenant) {
129
152
  const resolveApexTenant = opts.resolveApexTenant;
153
+ const byFileRef = opts.publicTenantResolution === "fileRef";
154
+ const routeQn = byFileRef ? PUBLIC_VARIANT_BY_FILE_REF_QN : PUBLIC_VARIANT_QN;
130
155
 
131
156
  r.queryHandler(publicVariantQuery);
157
+ // publicVariantByFileRefQuery is the ONLY way "fileRef" mode is
158
+ // reachable — registered ONLY in that mode for the same reason
159
+ // publicVariantQuery is gated above: registering it unconditionally
160
+ // would expose its unsafeRaw, cross-tenant FileRef lookup via the
161
+ // generic `/api` query dispatch even in default "host" mode.
162
+ if (byFileRef) {
163
+ r.queryHandler(publicVariantByFileRefQuery);
164
+ }
132
165
 
133
166
  r.httpRoute({
134
167
  method: "GET",
@@ -156,9 +189,10 @@ export function createFileDerivativesFeature(opts: FileDerivativesOptions = {}):
156
189
  let result: PublicVariantQueryResult;
157
190
  try {
158
191
  // @cast-boundary engine-payload — shape comes from
159
- // publicVariantQuery's return type.
192
+ // publicVariantQuery's return type (publicVariantByFileRefQuery
193
+ // returns exactly that, or null, never its own shape).
160
194
  result = (await systemQuery(
161
- PUBLIC_VARIANT_QN,
195
+ routeQn,
162
196
  { fileRefId, variant },
163
197
  tenantId,
164
198
  )) as PublicVariantQueryResult;
@@ -0,0 +1,40 @@
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import {
3
+ createAnonymousUser,
4
+ defineQueryHandler,
5
+ isUuid,
6
+ type TenantId,
7
+ } from "@cosmicdrift/kumiko-framework/engine";
8
+ import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
9
+ import { PUBLIC_VARIANT_QN, publicVariantPayloadSchema } from "./public-variant.query";
10
+
11
+ type FileRefTenantRow = {
12
+ readonly tenantId: TenantId;
13
+ };
14
+
15
+ const BY_FILE_REF_ESCAPE_HATCH_REASON =
16
+ "shared public host serves every tenant's public variants; the FileRef row names the tenant, and publicVariantQuery's isPublic gate still runs inside that tenant via queryAs";
17
+
18
+ // Full QN this handler is registered under, mirrors PUBLIC_VARIANT_QN.
19
+ export const PUBLIC_VARIANT_BY_FILE_REF_QN = "file-derivatives:query:public-variant-by-file-ref";
20
+
21
+ export const publicVariantByFileRefQuery = defineQueryHandler({
22
+ name: "public-variant-by-file-ref",
23
+ schema: publicVariantPayloadSchema,
24
+ access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
25
+ agent: { expose: false },
26
+ rateLimit: { per: "ip", limit: 60, windowSeconds: 60 },
27
+ escapeHatch: {
28
+ reason: BY_FILE_REF_ESCAPE_HATCH_REASON,
29
+ },
30
+ handler: async (query, ctx) => {
31
+ const row = await fetchOne<FileRefTenantRow>(
32
+ ctx.db.unsafeRaw(BY_FILE_REF_ESCAPE_HATCH_REASON),
33
+ fileRefsTable,
34
+ { id: query.payload.fileRefId, isDeleted: false },
35
+ );
36
+ if (!row || !isUuid(row.tenantId)) return null;
37
+
38
+ return ctx.queryAs(createAnonymousUser(row.tenantId), PUBLIC_VARIANT_QN, query.payload);
39
+ },
40
+ });
@@ -16,6 +16,11 @@
16
16
  // feature.ts), `ctx.user.tenantId` instead comes from that dispatcher's own
17
17
  // `anonymousAccess` resolution — tenant provenance there depends on the
18
18
  // consumer's `resolverTrust`/anonymousAccess setup, not the host.
19
+ //
20
+ // In `publicTenantResolution: "fileRef"` mode, this handler is also the
21
+ // `ctx.queryAs(createAnonymousUser(fileRefTenantId), ...)` target of
22
+ // publicVariantByFileRefQuery — tenantId there comes from the FileRef row,
23
+ // but this handler's own isPublic gate runs exactly the same either way.
19
24
 
20
25
  import { computeRevisionEtag } from "@cosmicdrift/kumiko-framework/api";
21
26
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
@@ -72,15 +77,19 @@ declare module "@cosmicdrift/kumiko-framework/engine" {
72
77
  // def itself — mirrors managed-pages' BY_SLUG_QN.
73
78
  export const PUBLIC_VARIANT_QN = "file-derivatives:query:public-variant";
74
79
 
80
+ // Shared with publicVariantByFileRefQuery — same payload shape, both routes
81
+ // forward `{ fileRefId, variant }` unchanged.
82
+ export const publicVariantPayloadSchema = z.object({
83
+ // Loose, version-agnostic UUID shape (same as isUuid/TENANT_ID_REGEX in
84
+ // packages/types) — not zod's `.uuid()`, which is stricter than this
85
+ // repo's convention and would reject valid v7/nil ids the DB accepts.
86
+ fileRefId: z.string().refine(isUuid, "invalid fileRefId"),
87
+ variant: z.string().min(1).max(64),
88
+ });
89
+
75
90
  export const publicVariantQuery = defineQueryHandler({
76
91
  name: "public-variant",
77
- schema: z.object({
78
- // Loose, version-agnostic UUID shape (same as isUuid/TENANT_ID_REGEX in
79
- // packages/types) — not zod's `.uuid()`, which is stricter than this
80
- // repo's convention and would reject valid v7/nil ids the DB accepts.
81
- fileRefId: z.string().refine(isUuid, "invalid fileRefId"),
82
- variant: z.string().min(1).max(64),
83
- }),
92
+ schema: publicVariantPayloadSchema,
84
93
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
85
94
  agent: { expose: false },
86
95
  // ponytail: "ip" trusts the first x-forwarded-for hop (buildRequestContextData
@@ -1,4 +1,8 @@
1
- export type { FileDerivativesOptions, PublicVariantResolveApexTenant } from "./feature";
1
+ export type {
2
+ FileDerivativesOptions,
3
+ PublicVariantResolveApexTenant,
4
+ PublicVariantTenantResolution,
5
+ } from "./feature";
2
6
  export { createFileDerivativesFeature, fileDerivativesFeature } from "./feature";
3
7
  export type {
4
8
  DerivativePublicPredicateArgs,
@@ -5,6 +5,7 @@
5
5
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
6
6
  import { EventEmitter } from "node:events";
7
7
  import { createSecret } from "@cosmicdrift/kumiko-framework/secrets";
8
+ import { sleep, waitFor } from "@cosmicdrift/kumiko-framework/testing";
8
9
  import {
9
10
  type InboundMailContext,
10
11
  isInboundAuthError,
@@ -152,13 +153,6 @@ const account: MailAccountRecord = {
152
153
  watchState: "idle",
153
154
  };
154
155
 
155
- async function waitFor(predicate: () => boolean, timeoutMs = 2000): Promise<void> {
156
- const t0 = Date.now();
157
- while (!predicate() && Date.now() - t0 < timeoutMs) {
158
- await Bun.sleep(5);
159
- }
160
- }
161
-
162
156
  function ctxWithDoc(doc: string | null): InboundMailContext {
163
157
  return {
164
158
  _userId: "imap-plugin-mocked",
@@ -332,7 +326,9 @@ describe("imapInboundMailPlugin — mocked imapflow", () => {
332
326
 
333
327
  // drainNew() does MIME-parsing before onMessages fires — a fixed sleep
334
328
  // flakes on a loaded CI runner; poll instead.
335
- await waitFor(() => received.some((batch) => batch.some((m) => m.subject === "pushed")));
329
+ await waitFor(() => received.some((batch) => batch.some((m) => m.subject === "pushed")), {
330
+ delays: Array(40).fill(5),
331
+ });
336
332
  expect(received.some((batch) => batch.some((m) => m.subject === "pushed"))).toBe(true);
337
333
 
338
334
  await stop();
@@ -347,11 +343,13 @@ describe("imapInboundMailPlugin — mocked imapflow", () => {
347
343
  },
348
344
  });
349
345
  lastIdleClient?.emit("error", new Error("socket hang up"));
350
- await waitFor(() => errors >= 1);
346
+ await waitFor(() => errors >= 1, { delays: Array(40).fill(5) });
351
347
  lastIdleClient?.emit("error", new Error("second"));
352
- // Confirms onError stays unsubscribed after the first error — polls the
353
- // same bounded window rather than betting on a fixed sleep outlasting it.
354
- await waitFor(() => errors >= 2, 100);
348
+ // Confirms onError stays unsubscribed after the first error. This is a
349
+ // grace period for a negative outcome, not a wait-for-true condition, so
350
+ // it's a single sleep (not a poll loop) rather than waitFor, which would
351
+ // throw once errors never reaches 2.
352
+ await sleep(100);
355
353
  expect(errors).toBe(1);
356
354
  await stop().catch(() => {});
357
355
  });
@@ -40,7 +40,7 @@ import {
40
40
  unsafeCreateEntityTable,
41
41
  unsafePushTables,
42
42
  } from "@cosmicdrift/kumiko-framework/stack";
43
- import { sleep } from "@cosmicdrift/kumiko-framework/testing";
43
+ import { waitFor } from "@cosmicdrift/kumiko-framework/testing";
44
44
  import { createJobsFeature } from "../feature";
45
45
  import { createJobRunLogger } from "../job-run-logger";
46
46
  import { jobRunLogsTable, jobRunsTable } from "../job-run-table";
@@ -148,7 +148,7 @@ describe("projection-rebuild job (jobs feature composed)", () => {
148
148
  }
149
149
 
150
150
  // Poll until the worker drained the queue and the rebuild refilled.
151
- for (let i = 0; i < 40 && (await getCount()) !== 2; i++) await sleep(200);
151
+ await waitFor(async () => (await getCount()) === 2, { delays: Array(40).fill(200) });
152
152
  expect(await getCount()).toBe(2);
153
153
 
154
154
  // getCount()==2 only proves rebuildProjection's own writes landed — the
@@ -156,13 +156,15 @@ describe("projection-rebuild job (jobs feature composed)", () => {
156
156
  // async append that starts only after the handler returns, so it can
157
157
  // still be in flight here. Poll status too instead of racing it.
158
158
  let runs: readonly { jobName: string; status: string }[] = [];
159
- for (let i = 0; i < 40; i++) {
160
- runs = await selectMany<{ jobName: string; status: string }>(db, jobRunsTable, {
161
- jobName: PROJECTION_REBUILD_JOB,
162
- });
163
- if (runs.some((r) => r.status === "completed")) break;
164
- await sleep(200);
165
- }
159
+ await waitFor(
160
+ async () => {
161
+ runs = await selectMany<{ jobName: string; status: string }>(db, jobRunsTable, {
162
+ jobName: PROJECTION_REBUILD_JOB,
163
+ });
164
+ return runs.some((r) => r.status === "completed");
165
+ },
166
+ { delays: Array(40).fill(200) },
167
+ );
166
168
  expect(runs.length).toBeGreaterThanOrEqual(1);
167
169
  expect(runs.some((r) => r.status === "completed")).toBe(true);
168
170
  }, 30000);