create-ailk 0.14.0 → 0.16.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 (184) hide show
  1. package/README.md +124 -0
  2. package/component-catalog.json +3 -3
  3. package/dist/add-module.d.ts +7 -0
  4. package/dist/add-module.js +80 -3
  5. package/dist/add-route.js +26 -2
  6. package/dist/adopt.d.ts +74 -0
  7. package/dist/adopt.js +314 -0
  8. package/dist/apply-design.d.ts +12 -1
  9. package/dist/apply-design.js +36 -4
  10. package/dist/cli.js +103 -3
  11. package/dist/derive-architecture.d.ts +37 -2
  12. package/dist/derive-architecture.js +92 -7
  13. package/dist/design-package.js +3 -2
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/lib/chrome-readers.d.ts +21 -0
  17. package/dist/lib/chrome-readers.js +100 -0
  18. package/dist/lib/declared-env.d.ts +142 -0
  19. package/dist/lib/declared-env.js +985 -0
  20. package/dist/lib/env-catalog.d.ts +17 -0
  21. package/dist/lib/env-catalog.js +234 -0
  22. package/dist/lib/extract-module-bundle.d.ts +20 -0
  23. package/dist/lib/extract-module-bundle.js +53 -6
  24. package/dist/lib/license-key-files.d.ts +6 -1
  25. package/dist/lib/license-key-files.js +9 -4
  26. package/dist/lib/module-tier.d.ts +2 -0
  27. package/dist/lib/module-tier.js +4 -0
  28. package/dist/module-architecture.d.ts +9 -0
  29. package/dist/module-architecture.js +82 -0
  30. package/dist/parse-args.d.ts +27 -0
  31. package/dist/parse-args.js +89 -0
  32. package/dist/programmatic.d.ts +28 -0
  33. package/dist/programmatic.js +181 -11
  34. package/dist/seed-flows.d.ts +65 -0
  35. package/dist/seed-flows.js +350 -0
  36. package/dist/site-chrome.d.ts +4 -0
  37. package/dist/site-chrome.js +34 -3
  38. package/dist/site-manifest.d.ts +55 -5
  39. package/dist/site-manifest.js +121 -12
  40. package/dist/site-roster.d.ts +13 -0
  41. package/dist/site-roster.js +22 -8
  42. package/dist/spoke-checkout.d.ts +42 -0
  43. package/dist/spoke-checkout.js +359 -0
  44. package/dist/surfaces.js +7 -0
  45. package/dist/sync-routes.js +29 -5
  46. package/dist/topology.d.ts +246 -0
  47. package/dist/topology.js +942 -0
  48. package/package.json +3 -3
  49. package/site-templates/designs/minimalism/DESIGN.md +3 -3
  50. package/templates/.env.example +10 -0
  51. package/templates/apps/api/CLAUDE.md +21 -4
  52. package/templates/apps/api/package.json +1 -0
  53. package/templates/apps/api/src/__tests__/factories/agent-caller.ts +5 -1
  54. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +95 -16
  55. package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +122 -0
  56. package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +362 -0
  57. package/templates/apps/api/src/bin/spokes.ts +62 -1
  58. package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +75 -0
  59. package/templates/apps/api/src/config/index.ts +35 -0
  60. package/templates/apps/api/src/lib/__mocks__/prisma.ts +99 -0
  61. package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +114 -1
  62. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +66 -5
  63. package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +209 -0
  64. package/templates/apps/api/src/lib/__tests__/site.test.ts +54 -0
  65. package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +20 -0
  66. package/templates/apps/api/src/lib/agent-store.ts +95 -3
  67. package/templates/apps/api/src/lib/deliverable-asset-store.ts +24 -5
  68. package/templates/apps/api/src/lib/hub-sign-in-store.ts +419 -0
  69. package/templates/apps/api/src/lib/site.ts +37 -0
  70. package/templates/apps/api/src/lib/spoke-store.ts +41 -3
  71. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +4 -0
  72. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +310 -0
  73. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +22 -0
  74. package/templates/apps/api/src/middleware/auth.ts +129 -0
  75. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +23 -0
  76. package/templates/apps/api/src/openapi/spec.ts +38 -2
  77. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +483 -0
  78. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +201 -0
  79. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +50 -2
  80. package/templates/apps/api/src/routes/checkout/sessions.ts +7 -0
  81. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +282 -0
  82. package/templates/apps/api/src/routes/leads/index.ts +69 -6
  83. package/templates/apps/api/src/routes/webhooks/README.md +39 -0
  84. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +105 -0
  85. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +366 -0
  86. package/templates/apps/api/src/routes/webhooks/stripe.ts +133 -2
  87. package/templates/apps/api/src/server.ts +105 -0
  88. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +4 -4
  89. package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +854 -0
  90. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +492 -0
  91. package/templates/apps/api/src/services/__tests__/person.test.ts +553 -0
  92. package/templates/apps/api/src/services/default-tenant.ts +41 -7
  93. package/templates/apps/api/src/services/hub-sign-in.ts +471 -0
  94. package/templates/apps/api/src/services/lead-routing.ts +278 -30
  95. package/templates/apps/api/src/services/person.ts +541 -0
  96. package/templates/apps/api/src/services/public-site-resolver.ts +48 -0
  97. package/templates/apps/mcp/__tests__/index.test.ts +5 -5
  98. package/templates/apps/mcp/src/index.ts +6 -4
  99. package/templates/apps/mcp/src/lib/content-adapter.ts +2 -2
  100. package/templates/apps/mcp/src/lib/topology.generated.ts +24 -0
  101. package/templates/apps/web/.env.example +58 -5
  102. package/templates/apps/web/CLAUDE.md +1 -0
  103. package/templates/apps/web/app/[locale]/(auth)/sign-in/next-path.ts +0 -0
  104. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +206 -0
  105. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +256 -3
  106. package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +5 -8
  107. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +205 -0
  108. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +82 -0
  109. package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +98 -29
  110. package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +6 -3
  111. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +19 -101
  112. package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +90 -0
  113. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +3 -0
  114. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +3 -0
  115. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +66 -14
  116. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +105 -11
  117. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +12 -8
  118. package/templates/apps/web/app/[locale]/layout.tsx +83 -18
  119. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +3 -0
  120. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +5 -2
  121. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +3 -0
  122. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +295 -0
  123. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +156 -0
  124. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +341 -0
  125. package/templates/apps/web/app/api/hub-session/callback/route.ts +168 -0
  126. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +76 -0
  127. package/templates/apps/web/app/api/hub-session/sign-out/route.ts +63 -0
  128. package/templates/apps/web/app/openapi.json/__tests__/route.test.ts +104 -1
  129. package/templates/apps/web/app/openapi.json/route.ts +72 -7
  130. package/templates/apps/web/auth.ts +29 -2
  131. package/templates/apps/web/components/sign-out-trigger.tsx +20 -1
  132. package/templates/apps/web/components/signed-in-nav.tsx +17 -3
  133. package/templates/apps/web/instrumentation.ts +7 -4
  134. package/templates/apps/web/lib/__tests__/account-config.test.ts +5 -2
  135. package/templates/apps/web/lib/__tests__/closing-footer-path.test.ts +96 -0
  136. package/templates/apps/web/lib/__tests__/listings-config.test.ts +83 -0
  137. package/templates/apps/web/lib/__tests__/listings.test.tsx +49 -0
  138. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +49 -1
  139. package/templates/apps/web/lib/__tests__/relay.test.ts +317 -0
  140. package/templates/apps/web/lib/__tests__/site-brand.test.ts +52 -0
  141. package/templates/apps/web/lib/account-config.ts +130 -1
  142. package/templates/apps/web/lib/api-origin.generated.ts +11 -0
  143. package/templates/apps/web/lib/closing-footer-path.ts +50 -0
  144. package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +262 -0
  145. package/templates/apps/web/lib/hub-session/config.ts +104 -0
  146. package/templates/apps/web/lib/hub-session/index.ts +23 -0
  147. package/templates/apps/web/lib/hub-session/seal.ts +82 -0
  148. package/templates/apps/web/lib/hub-session/session.ts +180 -0
  149. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +79 -0
  150. package/templates/apps/web/lib/listings-config.ts +31 -0
  151. package/templates/apps/web/lib/listings.tsx +7 -3
  152. package/templates/apps/web/lib/meeting-slugs.ts +11 -1
  153. package/templates/apps/web/lib/next-path.ts +35 -0
  154. package/templates/apps/web/lib/relay.ts +209 -3
  155. package/templates/apps/web/lib/server-api.ts +11 -1
  156. package/templates/apps/web/lib/site-brand.tsx +22 -1
  157. package/templates/apps/web/lib/site-chrome.ts +3 -0
  158. package/templates/apps/web/lib/topology.generated.ts +33 -0
  159. package/templates/apps/web/messages/en.json +32 -3
  160. package/templates/apps/web/package.json +1 -1
  161. package/templates/database/CHANGELOG.md +51 -0
  162. package/templates/database/__tests__/agents.db.test.ts +85 -0
  163. package/templates/database/__tests__/fixtures/person-backfill.json +264 -0
  164. package/templates/database/__tests__/lead-idempotency-key.db.test.ts +92 -0
  165. package/templates/database/__tests__/member-area.db.test.ts +245 -0
  166. package/templates/database/__tests__/person.db.test.ts +584 -0
  167. package/templates/database/auth/schema.prisma +24 -0
  168. package/templates/database/config/schema.prisma +13 -0
  169. package/templates/database/inbox/schema.prisma +111 -0
  170. package/templates/database/jest.config.cjs +7 -11
  171. package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +35 -0
  172. package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +20 -0
  173. package/templates/database/migrations/20261009120000_agent_grants/migration.sql +33 -0
  174. package/templates/database/migrations/20261010120000_person/migration.sql +106 -0
  175. package/templates/database/migrations/20261010121000_person_backfill/migration.sql +132 -0
  176. package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +20 -0
  177. package/templates/database/migrations/20261011120000_member_area/migration.sql +158 -0
  178. package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +21 -0
  179. package/templates/database/package.json +1 -1
  180. package/templates/database/scripts/person-backfill.sql +132 -0
  181. package/templates/database/tenancy.ts +53 -0
  182. package/templates/tools/check-declared-env.ts +37 -0
  183. package/templates/tools/declared-env.ts +1163 -0
  184. package/versions.json +35 -34
@@ -48,6 +48,10 @@ import {
48
48
  } from "./openapi/spec.js";
49
49
  import type { RegisteredRoute } from "./openapi/surface-drift.js";
50
50
  import { exampleAeoRoute, scoreAeoRoute } from "./routes/aeo/index.js";
51
+ import {
52
+ hubSignInClientRoutes,
53
+ hubSignInSessionRoutes,
54
+ } from "./routes/auth/hub-sign-in.js";
51
55
  import { tokenRoutes } from "./routes/auth/tokens.js";
52
56
  import { transactionalEmailRoute } from "./routes/auth/transactional-email.js";
53
57
  import { userRoutes } from "./routes/auth/user.js";
@@ -217,6 +221,16 @@ const WORKSPACES_MESSAGES = "./routes/workspaces/messages.js";
217
221
  // WORKSPACES_MESSAGES in the same workspace/multi-tenant scope.
218
222
  const WORKSPACES_SUBMISSIONS = "./routes/workspaces/submissions.js";
219
223
  const WORKSPACES_SUBSCRIBERS = "./routes/workspaces/subscribers.js";
224
+ // #852 D8 — the People reads (the workspace's clients), the SAME scope.
225
+ const WORKSPACES_PEOPLE = "./routes/workspaces/people.js";
226
+ // #7877 D9 — a client's pieces, plan and outbound messages, and the
227
+ // workspace's piece stages: the operator's writes, the SAME scope.
228
+ const WORKSPACES_PIECES = "./routes/workspaces/pieces.js";
229
+ const WORKSPACES_PLAN = "./routes/workspaces/plan.js";
230
+ const WORKSPACES_MESSAGES_OUT = "./routes/workspaces/messages-out.js";
231
+ // #7877 D11 — the member reads: auth only, under the SAME module predicate.
232
+ const MEMBER_PIECES = "./routes/member/pieces.js";
233
+ const MEMBER_MESSAGES = "./routes/member/messages.js";
220
234
  // #3591 PR-B — the agent seat's management routes and an agent's own
221
235
  // context, siblings of WORKSPACES_MEMBERS in the same workspace/multi-tenant
222
236
  // scope.
@@ -292,6 +306,33 @@ const SLICE_PLUGINS = {
292
306
  specifier: WORKSPACES_SUBSCRIBERS,
293
307
  load: () => import(WORKSPACES_SUBSCRIBERS),
294
308
  },
309
+ // #852 D8 — the People list and Person record, the SAME scope.
310
+ workspacePeopleRoutes: {
311
+ specifier: WORKSPACES_PEOPLE,
312
+ load: () => import(WORKSPACES_PEOPLE),
313
+ },
314
+ // #7877 D9 — the member area's operator routes, the SAME scope.
315
+ workspacePieceRoutes: {
316
+ specifier: WORKSPACES_PIECES,
317
+ load: () => import(WORKSPACES_PIECES),
318
+ },
319
+ workspacePlanRoutes: {
320
+ specifier: WORKSPACES_PLAN,
321
+ load: () => import(WORKSPACES_PLAN),
322
+ },
323
+ workspaceOutboundMessageRoutes: {
324
+ specifier: WORKSPACES_MESSAGES_OUT,
325
+ load: () => import(WORKSPACES_MESSAGES_OUT),
326
+ },
327
+ // #7877 D11 — the member reads, in authScope under the same predicate.
328
+ memberPieceRoutes: {
329
+ specifier: MEMBER_PIECES,
330
+ load: () => import(MEMBER_PIECES),
331
+ },
332
+ memberMessageRoutes: {
333
+ specifier: MEMBER_MESSAGES,
334
+ load: () => import(MEMBER_MESSAGES),
335
+ },
295
336
  // #3591 PR-B — the agent seat, the SAME scope.
296
337
  workspaceAgentRoutes: {
297
338
  specifier: WORKSPACES_AGENTS,
@@ -365,6 +406,12 @@ const SLICE_PLUGINS = {
365
406
  specifier: FLOWS_INDEX,
366
407
  load: () => import(FLOWS_INDEX),
367
408
  },
409
+ // #7876 D1 — a spoke seeds its declared flows; off until
410
+ // AILK_SPOKE_FLOW_SEED (the route answers 403 flow_seed_disabled).
411
+ putFlowDefinitionRoute: {
412
+ specifier: FLOWS_INDEX,
413
+ load: () => import(FLOWS_INDEX),
414
+ },
368
415
  // #6157 — the tenant-scoped magnet Admin authoring surface. ONE plugin,
369
416
  // ONE specifier (D2): all four routes register through this one entry.
370
417
  magnetRoutes: {
@@ -791,6 +838,14 @@ export async function build() {
791
838
  await authScope.register(tokenRoutes);
792
839
  await authScope.register(userRoutes);
793
840
 
841
+ // The hub signs in its spokes (#7894 D5) — `codes` and `me`. Auth only,
842
+ // NOT tenant-guarded: a person signing in to a spoke has no tenant here.
843
+ // Unconditional, like the leads inbox read below: a spoke's sign-in is not
844
+ // a module. `codes` answers a cookie session only; `me` the hub credential
845
+ // only (both enforced in the route). The credential's own admission is the
846
+ // preHandler's hub-credential branch (D6).
847
+ await authScope.register(hubSignInSessionRoutes);
848
+
794
849
  // Tenant-context routes (multi-tenant module) — auth only. They
795
850
  // establish/inspect the active workspace, so the tenant guard does not
796
851
  // apply to them; each enforces membership in the service layer.
@@ -862,6 +917,18 @@ export async function build() {
862
917
  await authScope.register(await sliceRoutePlugin("projectListingMeRoute"));
863
918
  }
864
919
 
920
+ // The member reads (#7877 D11) — auth only, NOT tenant-guarded: a client
921
+ // holds no membership (and an agent, which reaches only tenant-guarded
922
+ // routes, is refused 403 by the preHandler). Each handler resolves the
923
+ // caller through `memberCaller` and the workspace from the calling
924
+ // spoke's key, never a request value. Registered under the SAME module
925
+ // predicate as every routes/workspaces/* route, at the exact URLs the
926
+ // preHandler's `HUB_SESSION_ROUTES` admits a hub credential on.
927
+ if (isModuleEnabled("workspace") || isModuleEnabled("multi-tenant")) {
928
+ await authScope.register(await sliceRoutePlugin("memberPieceRoutes"));
929
+ await authScope.register(await sliceRoutePlugin("memberMessageRoutes"));
930
+ }
931
+
865
932
  // Tenant-guarded domain routes (multi-tenant module) — the tenant
866
933
  // preHandler (default-deny guard, see middleware/tenant.ts) runs after
867
934
  // auth for every route in this inner scope. No domain handler executes
@@ -1024,6 +1091,25 @@ export async function build() {
1024
1091
  await tenantScope.register(
1025
1092
  await sliceRoutePlugin("workspaceSubscriberRoutes"),
1026
1093
  );
1094
+ // The People reads (#852 D8) — the SAME scope and guard: the
1095
+ // workspace's clients and one Person with its derived timeline,
1096
+ // behind `people-read` (owner, admin, a granted agent).
1097
+ await tenantScope.register(
1098
+ await sliceRoutePlugin("workspacePeopleRoutes"),
1099
+ );
1100
+ // The member area's operator routes (#7877 D9) — the SAME scope and
1101
+ // guard: a client's pieces, plan and outbound messages, and the
1102
+ // workspace's declared piece stages. Writes need `people-write`
1103
+ // (owner, admin; never an agent).
1104
+ await tenantScope.register(
1105
+ await sliceRoutePlugin("workspacePieceRoutes"),
1106
+ );
1107
+ await tenantScope.register(
1108
+ await sliceRoutePlugin("workspacePlanRoutes"),
1109
+ );
1110
+ await tenantScope.register(
1111
+ await sliceRoutePlugin("workspaceOutboundMessageRoutes"),
1112
+ );
1027
1113
  // The agent seat (#3591 PR-B) — the SAME scope and guard: owners and
1028
1114
  // admins list, add, re-key and switch off agents (`agents-manage`),
1029
1115
  // and an agent reads its own context at GET /v1/agent/context.
@@ -1249,6 +1335,22 @@ export async function build() {
1249
1335
  await authEmailScope.register(transactionalEmailRoute);
1250
1336
  });
1251
1337
 
1338
+ // The hub signs in its spokes (#7894 D5) — `token` and `revoke`. Their OWN
1339
+ // scope, deliberately OUTSIDE `authScope`: the caller is a spoke's server,
1340
+ // authenticated by its client secret AND its spoke key inside the service,
1341
+ // not by a person's session — an auth preHandler in front would reject every
1342
+ // legitimate call. `scope: "hubsignin"` namespaces the buckets (the scope
1343
+ // name admits no hyphen). The per-IP limit keys on `req.ip` under
1344
+ // `trustProxy`, so it is a backstop only; the control is the per-client
1345
+ // bucket the service keys on the VERIFIED client (SEC-7894-8).
1346
+ await server.register(async (hubSignInScope) => {
1347
+ hubSignInScope.register(rateLimitMiddleware, {
1348
+ perIpPerMinute: 60,
1349
+ scope: "hubsignin",
1350
+ });
1351
+ await hubSignInScope.register(hubSignInClientRoutes);
1352
+ });
1353
+
1252
1354
  // Lead-capture status (#6730 D4) — a cheap read the web page makes before
1253
1355
  // choosing the form or the contact email. Top level, NOT in `leadsScope`:
1254
1356
  // the web server's single egress IP already spends that scope's strict
@@ -1360,6 +1462,9 @@ export async function build() {
1360
1462
  await flowsScope.register(
1361
1463
  await sliceRoutePlugin("getFlowAggregateRoute"),
1362
1464
  );
1465
+ await flowsScope.register(
1466
+ await sliceRoutePlugin("putFlowDefinitionRoute"),
1467
+ );
1363
1468
  }
1364
1469
  // The recommender's email-moment routes (#4841) — same anonymous,
1365
1470
  // rate-limited posture as the three above. Module-conditional (commerce,
@@ -354,7 +354,7 @@ describe("fulfillDeliverable", () => {
354
354
  (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
355
355
  workspaceId: WORKSPACE_ID,
356
356
  });
357
- (prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID });
357
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID, tags: [] });
358
358
 
359
359
  await fulfillDeliverable(baseInput());
360
360
 
@@ -366,7 +366,7 @@ describe("fulfillDeliverable", () => {
366
366
  );
367
367
  expect(prisma.asset.findFirst).toHaveBeenCalledWith({
368
368
  where: { workspaceId: WORKSPACE_ID, key: "guide-small" },
369
- select: { id: true },
369
+ select: { id: true, tags: true },
370
370
  });
371
371
  expect(prisma.deliverableAsset.findUnique).not.toHaveBeenCalled();
372
372
  expect(prisma.deliverableGrant.create).toHaveBeenCalledWith(
@@ -421,7 +421,7 @@ describe("fulfillDeliverable", () => {
421
421
  (prisma.flowDefinition.findUnique as jest.Mock).mockResolvedValue({
422
422
  workspaceId: WORKSPACE_ID,
423
423
  });
424
- (prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID });
424
+ (prisma.asset.findFirst as jest.Mock).mockResolvedValue({ id: NEW_ASSET_ID, tags: [] });
425
425
  // beforeEach's default deliverableAsset.findUnique mock already resolves
426
426
  // a row at this key — it must never even be consulted.
427
427
 
@@ -482,7 +482,7 @@ describe("fulfillDeliverable", () => {
482
482
 
483
483
  expect(prisma.asset.findFirst).toHaveBeenCalledWith({
484
484
  where: { workspaceId: WORKSPACE_ID, key: "guide-small" },
485
- select: { id: true },
485
+ select: { id: true, tags: true },
486
486
  });
487
487
  expect(prisma.asset.findFirst).not.toHaveBeenCalledWith(
488
488
  expect.objectContaining({