create-ailk 0.18.0 → 0.19.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/LICENSE +6 -5
  2. package/NOTICE +2 -0
  3. package/dist/add-module.js +4 -1
  4. package/dist/category-profile.d.ts +10 -0
  5. package/dist/category-profile.js +21 -0
  6. package/dist/derive-architecture.d.ts +5 -1
  7. package/dist/derive-architecture.js +8 -3
  8. package/dist/lib/license-key-files.d.ts +6 -3
  9. package/dist/lib/license-key-files.js +8 -2
  10. package/dist/lib/scaffold-render-sweep.d.ts +9 -0
  11. package/dist/lib/scaffold-render-sweep.js +24 -3
  12. package/dist/license-set.d.ts +1 -1
  13. package/dist/license-set.js +13 -8
  14. package/dist/module-architecture.js +119 -0
  15. package/dist/programmatic.js +3 -0
  16. package/dist/spoke-checkout.d.ts +11 -3
  17. package/dist/spoke-checkout.js +62 -12
  18. package/dist/starter-grid.d.ts +1 -1
  19. package/dist/surfaces.js +140 -1
  20. package/dist/template-manifest.d.ts +4 -3
  21. package/dist/topology.d.ts +7 -2
  22. package/dist/topology.js +60 -3
  23. package/package.json +5 -4
  24. package/templates/.env.example +3 -3
  25. package/templates/.github/workflows/ci.yml +5 -0
  26. package/templates/apps/api/CLAUDE.md +25 -7
  27. package/templates/apps/api/LICENSE +6 -5
  28. package/templates/apps/api/NOTICE +2 -0
  29. package/templates/apps/api/jest.config.cjs +4 -29
  30. package/templates/apps/api/package.json +1 -0
  31. package/templates/apps/api/src/lib/__mocks__/prisma.ts +9 -0
  32. package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +38 -0
  33. package/templates/apps/api/src/lib/deliverable-asset-store.ts +5 -4
  34. package/templates/apps/api/src/lib/stripe.ts +81 -0
  35. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +266 -39
  36. package/templates/apps/api/src/middleware/auth.ts +206 -46
  37. package/templates/apps/api/src/routes/leads/index.ts +4 -0
  38. package/templates/apps/api/src/server.ts +107 -0
  39. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +58 -0
  40. package/templates/apps/api/src/services/lead-routing.ts +30 -2
  41. package/templates/apps/api/vercel.json +2 -2
  42. package/templates/apps/mcp/.env.example +10 -0
  43. package/templates/apps/mcp/CLAUDE.md +9 -4
  44. package/templates/apps/mcp/LICENSE +6 -5
  45. package/templates/apps/mcp/NOTICE +2 -0
  46. package/templates/apps/mcp/__tests__/abuse/guard.test.ts +198 -0
  47. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +15 -3
  48. package/templates/apps/mcp/__tests__/boot.test.ts +78 -1
  49. package/templates/apps/mcp/__tests__/http-transport.test.ts +291 -2
  50. package/templates/apps/mcp/__tests__/request_callback.test.ts +69 -7
  51. package/templates/apps/mcp/__tests__/submit_lead.test.ts +69 -8
  52. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +69 -7
  53. package/templates/apps/mcp/jest.config.cjs +3 -44
  54. package/templates/apps/mcp/src/abuse/index.ts +117 -21
  55. package/templates/apps/mcp/src/boot.ts +5 -23
  56. package/templates/apps/mcp/src/http-transport.ts +93 -73
  57. package/templates/apps/mcp/src/index.ts +2 -0
  58. package/templates/apps/mcp/src/request-context.ts +52 -0
  59. package/templates/apps/mcp/src/tools/capture_lead.ts +9 -1
  60. package/templates/apps/mcp/src/tools/request_callback.ts +9 -1
  61. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +9 -1
  62. package/templates/apps/web/LICENSE +6 -5
  63. package/templates/apps/web/NOTICE +2 -0
  64. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +3 -3
  65. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +288 -0
  66. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/general.test.tsx +568 -0
  67. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/hub-mode.test.tsx +272 -42
  68. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/item.test.tsx +11 -13
  69. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +115 -16
  70. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +305 -0
  71. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-popups.test.tsx +82 -0
  72. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/notifications.test.tsx +126 -0
  73. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +163 -72
  74. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +451 -0
  75. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/security.test.tsx +139 -0
  76. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +20 -390
  77. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/support.test.tsx +16 -11
  78. package/templates/apps/web/app/[locale]/(authed)/account/account-data.ts +1 -1
  79. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +114 -50
  80. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +24 -45
  81. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/page.tsx +253 -0
  82. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/review-form.tsx +145 -0
  83. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +100 -0
  84. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +179 -0
  85. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/page.test.tsx +514 -0
  86. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/review.test.tsx +288 -0
  87. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-actions.ts +123 -0
  88. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/artifact-data.ts +82 -0
  89. package/templates/apps/web/app/[locale]/(authed)/account/artifacts/page.tsx +177 -0
  90. package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +172 -0
  91. package/templates/apps/web/app/[locale]/(authed)/account/billing-source.ts +147 -0
  92. package/templates/apps/web/app/[locale]/(authed)/account/components/request-quote.tsx +229 -0
  93. package/templates/apps/web/app/[locale]/(authed)/account/components/schedule-service.tsx +276 -0
  94. package/templates/apps/web/app/[locale]/(authed)/account/components/send-message.tsx +128 -0
  95. package/templates/apps/web/app/[locale]/(authed)/account/general/billing-anchor.tsx +27 -0
  96. package/templates/apps/web/app/[locale]/(authed)/account/general/deletion-request.tsx +132 -0
  97. package/templates/apps/web/app/[locale]/(authed)/account/general/page.tsx +131 -0
  98. package/templates/apps/web/app/[locale]/(authed)/account/general/profile-form.tsx +104 -0
  99. package/templates/apps/web/app/[locale]/(authed)/account/general/profile-source.ts +73 -0
  100. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/__tests__/route.test.ts +18 -8
  101. package/templates/apps/web/app/[locale]/(authed)/account/inbox/message/[id]/file/route.ts +4 -6
  102. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +4 -1
  103. package/templates/apps/web/app/[locale]/(authed)/account/new-actions.ts +219 -0
  104. package/templates/apps/web/app/[locale]/(authed)/account/new-menu.tsx +340 -0
  105. package/templates/apps/web/app/[locale]/(authed)/account/new-popups.tsx +107 -0
  106. package/templates/apps/web/app/[locale]/(authed)/account/notifications/page.tsx +57 -0
  107. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +85 -26
  108. package/templates/apps/web/app/[locale]/(authed)/account/profile-actions.ts +116 -0
  109. package/templates/apps/web/app/[locale]/(authed)/account/security/page.tsx +80 -0
  110. package/templates/apps/web/app/[locale]/(authed)/account/settings/page.tsx +13 -354
  111. package/templates/apps/web/app/[locale]/(authed)/account/settings-frame.tsx +108 -0
  112. package/templates/apps/web/app/[locale]/(authed)/account/sign-in-method.ts +37 -0
  113. package/templates/apps/web/app/[locale]/(authed)/account/support-actions.ts +9 -10
  114. package/templates/apps/web/app/[locale]/layout.tsx +23 -4
  115. package/templates/apps/web/app/api/auth/hub/__tests__/continuation.test.ts +143 -0
  116. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +62 -10
  117. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +22 -7
  118. package/templates/apps/web/app/api/auth/hub/continuation.ts +73 -0
  119. package/templates/apps/web/app/api/auth/hub/continue/[ref]/__tests__/route.test.ts +188 -0
  120. package/templates/apps/web/app/api/auth/hub/continue/[ref]/route.ts +53 -0
  121. package/templates/apps/web/app/robots.txt/route.ts +10 -4
  122. package/templates/apps/web/auth.ts +5 -1
  123. package/templates/apps/web/lib/__tests__/account-config.test.ts +258 -46
  124. package/templates/apps/web/lib/account-config.ts +188 -52
  125. package/templates/apps/web/lib/hub-session/__tests__/hub-session.test.ts +143 -0
  126. package/templates/apps/web/lib/hub-session/index.ts +1 -0
  127. package/templates/apps/web/lib/hub-session/reseal.ts +90 -0
  128. package/templates/apps/web/lib/hub-session/seal.ts +13 -9
  129. package/templates/apps/web/lib/topology.generated.ts +24 -0
  130. package/templates/apps/web/messages/en.json +156 -35
  131. package/templates/database/CHANGELOG.md +21 -0
  132. package/templates/database/LICENSE +6 -5
  133. package/templates/database/NOTICE +2 -0
  134. package/templates/database/README.md +1 -1
  135. package/templates/database/__tests__/member-area.db.test.ts +15 -3
  136. package/templates/database/__tests__/member-contract-migration.test.ts +85 -0
  137. package/templates/database/__tests__/member-contract.db.test.ts +413 -0
  138. package/templates/database/__tests__/seed.test.ts +54 -2
  139. package/templates/database/inbox/schema.prisma +90 -15
  140. package/templates/database/migrations/20261012120000_member_contract/migration.sql +192 -0
  141. package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +20 -0
  142. package/templates/database/package.json +3 -2
  143. package/templates/database/seed.ts +34 -2
  144. package/versions.json +36 -36
  145. package/templates/apps/api/src/__tests__/content-write-production-boot.test.ts +0 -104
  146. package/templates/apps/api/src/__tests__/cors.test.ts +0 -276
  147. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +0 -279
  148. package/templates/apps/api/src/__tests__/openapi-keyless-hub.test.ts +0 -122
  149. package/templates/apps/api/src/__tests__/review-surface-registration.test.ts +0 -167
  150. package/templates/apps/api/src/__tests__/root-modules.test.ts +0 -175
  151. package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +0 -117
  152. package/templates/apps/api/src/__tests__/server.test.ts +0 -591
  153. package/templates/apps/api/src/__tests__/workspace-module-registration.test.ts +0 -175
  154. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +0 -293
  155. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +0 -1620
  156. package/templates/apps/api/src/routes/project-listings/__tests__/directory.test.ts +0 -528
  157. package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +0 -198
  158. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +0 -652
  159. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -408
  160. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +0 -476
  161. package/templates/apps/mcp/__tests__/audit.test.ts +0 -346
  162. package/templates/apps/mcp/__tests__/parity.test.ts +0 -325
  163. package/templates/apps/mcp/__tests__/workspace-surface.test.ts +0 -364
  164. package/templates/apps/web/app/[locale]/(authed)/account/settings/color-mode.tsx +0 -68
  165. package/templates/apps/web/app/[locale]/(authed)/account/settings/deletion-request.tsx +0 -55
  166. package/templates/apps/web/app/[locale]/(authed)/account/settings/name-form.tsx +0 -118
  167. package/templates/database/__tests__/build.test.ts +0 -131
package/LICENSE CHANGED
@@ -1,3 +1,4 @@
1
+
1
2
  Apache License
2
3
  Version 2.0, January 2004
3
4
  http://www.apache.org/licenses/
@@ -137,8 +138,8 @@
137
138
 
138
139
  6. Trademarks. This License does not grant permission to use the trade
139
140
  names, trademarks, service marks, or product names of the Licensor,
140
- except as required for describing the origin of the Work and
141
- reproducing the content of the NOTICE file.
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
142
143
 
143
144
  7. Disclaimer of Warranty. Unless required by applicable law or
144
145
  agreed to in writing, Licensor provides the Work (and each
@@ -162,7 +163,7 @@
162
163
  other commercial damages or losses), even if such Contributor
163
164
  has been advised of the possibility of such damages.
164
165
 
165
- 9. Accepting Warranty or Support. While redistributing
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
167
  the Work or Derivative Works thereof, You may choose to offer,
167
168
  and charge a fee for, acceptance of support, warranty, indemnity,
168
169
  or other liability obligations and/or rights consistent with this
@@ -171,7 +172,7 @@
171
172
  of any other Contributor, and only if You agree to indemnify,
172
173
  defend, and hold each Contributor harmless for any liability
173
174
  incurred by, or claims asserted against, such Contributor by reason
174
- of your accepting any such warranty or support.
175
+ of your accepting any such warranty or additional liability.
175
176
 
176
177
  END OF TERMS AND CONDITIONS
177
178
 
@@ -186,7 +187,7 @@
186
187
  same "printed page" as the copyright notice for easier
187
188
  identification within third-party archives.
188
189
 
189
- Copyright 2026 Tailwind AI
190
+ Copyright [yyyy] [name of copyright owner]
190
191
 
191
192
  Licensed under the Apache License, Version 2.0 (the "License");
192
193
  you may not use this file except in compliance with the License.
package/NOTICE ADDED
@@ -0,0 +1,2 @@
1
+ AI Launch Kit
2
+ Copyright 2026 SalesSmyth LLC
@@ -165,12 +165,15 @@ export async function applyModuleToScaffold(opts) {
165
165
  });
166
166
  // #7838 D8 — commerce on a spoke brings the spoke's own checkout, stamped
167
167
  // from the site directory's name (the `projectName` scaffold() defaults to).
168
- // A file the site already has is left as it is.
168
+ // A file the site already has is left as it is. #8217 D8 — on a spoke that
169
+ // signs in through its hub, the billing action and its seam render too, so
170
+ // re-running add-module commerce gives an existing commerce spoke the action.
169
171
  const spokeCommerce = spoke !== undefined && opts.moduleName === "commerce";
170
172
  const checkoutRows = spokeCommerce
171
173
  ? writeSpokeCheckoutSlice(targetAbs, {
172
174
  site: spokeCheckoutSite(basename(targetAbs)),
173
175
  versions: opts.versions,
176
+ signIn: spoke.signIn,
174
177
  })
175
178
  : [];
176
179
  // #7876 D9 — a spoke that just gained the flow stepper says what its
@@ -50,6 +50,16 @@ export declare const CATEGORY_PROFILE_SHAPES: {
50
50
  * set-equal to the `schema-dts` closure, so it cannot drift silently.
51
51
  */
52
52
  export declare const LOCAL_BUSINESS_TYPES: ReadonlySet<string>;
53
+ /**
54
+ * The leaf category slugs, in registry order — create-ailk's own copy of the
55
+ * site-category registry's leaves (`SITE_CATEGORIES`, #5830). A literal, not
56
+ * a type import, so the emitted `.d.ts` of the public tarball names no
57
+ * `@working-theory/*` package (#8412); `category-slugs.test.ts` proves it
58
+ * equal to the registry, so it cannot drift silently.
59
+ */
60
+ export declare const CATEGORY_SLUGS: readonly ["accounting", "legal", "consulting", "hvac", "roofing", "plumbing", "electrical", "landscaping", "auto-repair", "real-estate", "technology", "medical-specialty"];
61
+ /** A leaf category slug — the only thing a template's `categories` names. */
62
+ export type CategorySlug = (typeof CATEGORY_SLUGS)[number];
53
63
  export declare const OFFERING_PRICING: readonly ["quote", "fixed", "hourly", "subscription"];
54
64
  export declare const PROOF_KINDS: readonly ["credential", "reviews", "before-after", "response-time", "guarantee", "experience"];
55
65
  export declare const CONTENT_POSTURES: readonly ["instructions", "sample"];
@@ -71,6 +71,27 @@ export const LOCAL_BUSINESS_TYPES = new Set([
71
71
  "TennisComplex", "TireShop", "TouristInformationCenter", "ToyStore",
72
72
  "TravelAgency", "VacationRental", "WholesaleStore", "Winery",
73
73
  ]);
74
+ /**
75
+ * The leaf category slugs, in registry order — create-ailk's own copy of the
76
+ * site-category registry's leaves (`SITE_CATEGORIES`, #5830). A literal, not
77
+ * a type import, so the emitted `.d.ts` of the public tarball names no
78
+ * `@working-theory/*` package (#8412); `category-slugs.test.ts` proves it
79
+ * equal to the registry, so it cannot drift silently.
80
+ */
81
+ export const CATEGORY_SLUGS = [
82
+ "accounting",
83
+ "legal",
84
+ "consulting",
85
+ "hvac",
86
+ "roofing",
87
+ "plumbing",
88
+ "electrical",
89
+ "landscaping",
90
+ "auto-repair",
91
+ "real-estate",
92
+ "technology",
93
+ "medical-specialty",
94
+ ];
74
95
  export const OFFERING_PRICING = ["quote", "fixed", "hourly", "subscription"];
75
96
  export const PROOF_KINDS = [
76
97
  "credential",
@@ -22,7 +22,11 @@ export interface DeriveArchitectureOptions {
22
22
  /** Spoke only: whether the spoke keeps `apps/mcp` (default true). */
23
23
  mcp?: boolean;
24
24
  }
25
- /** The only spoke files that may import @working-theory/spoke-checkout (#7873). */
25
+ /**
26
+ * The only spoke files that may import @working-theory/spoke-checkout (#7873):
27
+ * the buy route, the webhook route and — on a spoke that signs in through its
28
+ * hub — the billing action (#8217 D9, a reviewed change to the #7873 rule).
29
+ */
26
30
  export declare const SPOKE_CHECKOUT_ROUTE_FILES: readonly string[];
27
31
  /** The forbidden patterns a spoke's apps/web carries (#7873, brief Part 2 §2). */
28
32
  export declare const SPOKE_WEB_FORBIDDEN_PATTERNS: readonly ForbiddenPattern[];
@@ -27,10 +27,15 @@ const SPOKE_TIERS = {
27
27
  // import only to a workspace on disk, so the one way to ban an npm package is
28
28
  // a line-level forbidden pattern. These go under the spoke's own `apps.web`
29
29
  // and `apps.mcp` only — never top level, and never on a hub derivation.
30
- /** The only spoke files that may import @working-theory/spoke-checkout (#7873). */
30
+ /**
31
+ * The only spoke files that may import @working-theory/spoke-checkout (#7873):
32
+ * the buy route, the webhook route and — on a spoke that signs in through its
33
+ * hub — the billing action (#8217 D9, a reviewed change to the #7873 rule).
34
+ */
31
35
  export const SPOKE_CHECKOUT_ROUTE_FILES = Object.freeze([
32
36
  "apps/web/app/api/checkout/[offering]/route.ts",
33
37
  "apps/web/app/api/webhooks/stripe/route.ts",
38
+ "apps/web/app/[locale]/(authed)/account/billing/spoke-billing.action.ts",
34
39
  ]);
35
40
  /**
36
41
  * A line-level regex for an import of `specifier` or one of its subpaths, in
@@ -45,10 +50,10 @@ function specifierForm(specifier) {
45
50
  const literal = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
46
51
  return `\\b(?:from|import|require)\\s*\\(?\\s*${q}${literal}(?:/[^'"\`]*)?${q}`;
47
52
  }
48
- /** P1 with no exclusions — a kept apps/mcp's form; apps/web adds the two routes. */
53
+ /** P1 with no exclusions — a kept apps/mcp's form; apps/web adds the route files. */
49
54
  const SPOKE_CHECKOUT_PATTERN = Object.freeze({
50
55
  pattern: specifierForm("@working-theory/spoke-checkout"),
51
- reason: "Spoke checkout: only the buy route and the webhook route may import @working-theory/spoke-checkout (brief Part 2 §2, #7873)",
56
+ reason: "Spoke checkout: only the buy route, the webhook route and the billing action may import @working-theory/spoke-checkout (brief Part 2 §2, #7873, #8217)",
52
57
  });
53
58
  const STRIPE_CLIENT_PATTERN = Object.freeze({
54
59
  pattern: specifierForm("@working-theory/stripe-client"),
@@ -21,9 +21,12 @@ export declare function appendLine(text: string, line: string): string;
21
21
  * `ailk.key` when no existing line covers it. Idempotent — a second call with
22
22
  * the same key changes no byte.
23
23
  *
24
- * `apiEnv: false` skips the `apps/api/.env` line — a spoke (#7426 D6) has no
25
- * `apps/api`, so it keeps the key in `ailk.key` alone.
24
+ * `apiEnv: false` skips the `apps/api/.env` line — a spoke (#7426 D6) or any
25
+ * site without `apps/api` (#7779) keeps the key in `ailk.key` alone.
26
+ *
27
+ * Returns the key files it holds the key in, relative to `dir`
28
+ * (`LICENSE_KEY_FILE`, then `LICENSE_ENV_FILE` when written).
26
29
  */
27
30
  export declare function writeLicenseKeyFiles(dir: string, key: string, opts?: {
28
31
  apiEnv?: boolean;
29
- }): void;
32
+ }): string[];
@@ -86,16 +86,21 @@ function upsertEnvLine(existing, key) {
86
86
  * `ailk.key` when no existing line covers it. Idempotent — a second call with
87
87
  * the same key changes no byte.
88
88
  *
89
- * `apiEnv: false` skips the `apps/api/.env` line — a spoke (#7426 D6) has no
90
- * `apps/api`, so it keeps the key in `ailk.key` alone.
89
+ * `apiEnv: false` skips the `apps/api/.env` line — a spoke (#7426 D6) or any
90
+ * site without `apps/api` (#7779) keeps the key in `ailk.key` alone.
91
+ *
92
+ * Returns the key files it holds the key in, relative to `dir`
93
+ * (`LICENSE_KEY_FILE`, then `LICENSE_ENV_FILE` when written).
91
94
  */
92
95
  export function writeLicenseKeyFiles(dir, key, opts = {}) {
93
96
  assertLicenseKeyShape(key);
94
97
  writeOwnerOnly(join(dir, LICENSE_KEY_FILE), `${key}\n`);
98
+ const written = [LICENSE_KEY_FILE];
95
99
  if (opts.apiEnv !== false) {
96
100
  const envPath = join(dir, LICENSE_ENV_FILE);
97
101
  const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
98
102
  writeOwnerOnly(envPath, upsertEnvLine(env, key));
103
+ written.push(LICENSE_ENV_FILE);
99
104
  }
100
105
  const ignorePath = join(dir, ".gitignore");
101
106
  const ignore = existsSync(ignorePath) ? readFileSync(ignorePath, "utf8") : "";
@@ -104,4 +109,5 @@ export function writeLicenseKeyFiles(dir, key, opts = {}) {
104
109
  .some((l) => KEY_IGNORE_LINES.has(l.trim()));
105
110
  if (!covered)
106
111
  writeFileSync(ignorePath, appendLine(ignore, LICENSE_KEY_FILE));
112
+ return written;
107
113
  }
@@ -53,6 +53,15 @@ export declare function assessClaimGatedRoute(input: {
53
53
  hops: RedirectHop[];
54
54
  status: number | null;
55
55
  }): SweepVerdict;
56
+ /**
57
+ * A `redirect()` thrown by a Server Component after the shell has flushed is
58
+ * not an HTTP 3xx: the response is a 200 whose flight payload carries the
59
+ * digest `NEXT_REDIRECT;<replace|push>;<url>;<status>;`. The driver follows it
60
+ * as a hop exactly like a `Location` header (#8311 — `/account/settings`
61
+ * forwards to `/account/general` this way). Null when the HTML carries no
62
+ * such digest.
63
+ */
64
+ export declare function streamedRedirect(html: string): RedirectHop | null;
56
65
  /**
57
66
  * Decide whether a page route rendered, under the real locale, with its own
58
67
  * content. The first failed rule is the reason:
@@ -122,6 +122,24 @@ function htmlLang(html) {
122
122
  function flightDigest(html) {
123
123
  return /\\"digest\\":\\"([^"\\]*)\\"/.exec(html)?.[1] ?? null;
124
124
  }
125
+ /**
126
+ * A `redirect()` thrown by a Server Component after the shell has flushed is
127
+ * not an HTTP 3xx: the response is a 200 whose flight payload carries the
128
+ * digest `NEXT_REDIRECT;<replace|push>;<url>;<status>;`. The driver follows it
129
+ * as a hop exactly like a `Location` header (#8311 — `/account/settings`
130
+ * forwards to `/account/general` this way). Null when the HTML carries no
131
+ * such digest.
132
+ */
133
+ export function streamedRedirect(html) {
134
+ const digest = flightDigest(html);
135
+ if (digest === null || !digest.startsWith("NEXT_REDIRECT;"))
136
+ return null;
137
+ const [, , location = "", status = ""] = digest.split(";");
138
+ if (location === "")
139
+ return null;
140
+ const code = Number.parseInt(status, 10);
141
+ return { status: Number.isFinite(code) ? code : 307, location };
142
+ }
125
143
  /**
126
144
  * Whether an `<h1>`'s inner HTML carries a name: text once tags are stripped,
127
145
  * or an image with a non-empty `alt` (the Hero's lockup renders the wordmark
@@ -170,9 +188,12 @@ export function assessPageRender(input) {
170
188
  return { ok: false, reason: `${route} -> rendered under locale "${lang}"` };
171
189
  const digest = flightDigest(html);
172
190
  if (digest !== null) {
173
- const why = digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;404")
174
- ? "streamed not-found"
175
- : `streamed error (digest ${digest})`;
191
+ const redirect = streamedRedirect(html);
192
+ const why = redirect !== null
193
+ ? `streamed redirect to ${redirect.location} that was not followed`
194
+ : digest.startsWith("NEXT_HTTP_ERROR_FALLBACK;404")
195
+ ? "streamed not-found"
196
+ : `streamed error (digest ${digest})`;
176
197
  return { ok: false, reason: `${route} -> ${why}` };
177
198
  }
178
199
  const marker = ownContentMarker(html);
@@ -24,7 +24,7 @@ export interface LicenseSetIO {
24
24
  /** Defaults to `spawnSync` with `shell: false` (see `defaultRunner`). */
25
25
  runner?: LicenseSetRunner;
26
26
  }
27
- export declare const LICENSE_SET_HELP = "create-ailk license set \u2014 put your licence key into an existing site.\n\nUsage:\n pnpm create ailk@latest license set <key> [<target-dir>]\n pnpm create ailk@latest license set - [<target-dir>] < key.txt\n\nArguments:\n <key> Your licence key, or `-` to read it from stdin\n (keeps it out of your shell history).\n <target-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n\nWrites the key to ailk.key and apps/api/.env (owner-only, gitignored). When the\nsite (or apps/api, or apps/web) is linked to Vercel, it also replaces the\nproduction AILK_LICENSE_KEY variable and redeploys.\n";
27
+ export declare const LICENSE_SET_HELP = "create-ailk license set \u2014 put your licence key into an existing site.\n\nUsage:\n pnpm create ailk@latest license set <key> [<target-dir>]\n pnpm create ailk@latest license set - [<target-dir>] < key.txt\n\nArguments:\n <key> Your licence key, or `-` to read it from stdin\n (keeps it out of your shell history).\n <target-dir> An already-scaffolded repo (contains\n architecture.yaml). Defaults to the current dir.\n\nWrites the key to ailk.key, and to apps/api/.env when the site has apps/api\n(owner-only, gitignored). When the site (or apps/api, or apps/web) is linked to\nVercel, it also replaces the production AILK_LICENSE_KEY variable and redeploys.\n";
28
28
  /** `spawnSync`, never through a shell; stdin piped only when there is input. */
29
29
  export declare const defaultRunner: LicenseSetRunner;
30
30
  /**
@@ -7,8 +7,9 @@
7
7
  *
8
8
  * 1. The target (default: cwd) must contain `architecture.yaml` — the
9
9
  * `sync-routes` precondition — else exit 1 with nothing written.
10
- * 2. `writeLicenseKeyFiles` — `ailk.key` + `apps/api/.env`, both 0600 and
11
- * gitignored; the same writer `--license` uses.
10
+ * 2. `writeLicenseKeyFiles` — `ailk.key`, plus `apps/api/.env` only when
11
+ * `apps/api` already exists (#7779), both 0600 and gitignored; the same
12
+ * writer `--license` uses. The output names only the files written.
12
13
  * 3. For each of `<dir>`, `<dir>/apps/api`, `<dir>/apps/web` holding
13
14
  * `.vercel/project.json`, with that directory as `cwd`:
14
15
  * a. print the linked `projectId` about to change (SEC-13);
@@ -31,7 +32,7 @@
31
32
  import { spawnSync } from "node:child_process";
32
33
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
33
34
  import { join, relative, resolve } from "node:path";
34
- import { LICENSE_ENV_FILE, LICENSE_ENV_VAR, LICENSE_KEY_FILE, assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
35
+ import { LICENSE_ENV_VAR, LICENSE_KEY_FILE, assertLicenseKeyShape, writeLicenseKeyFiles, } from "./lib/license-key-files.js";
35
36
  export const LICENSE_SET_HELP = `create-ailk license set — put your licence key into an existing site.
36
37
 
37
38
  Usage:
@@ -44,9 +45,9 @@ Arguments:
44
45
  <target-dir> An already-scaffolded repo (contains
45
46
  architecture.yaml). Defaults to the current dir.
46
47
 
47
- Writes the key to ailk.key and apps/api/.env (owner-only, gitignored). When the
48
- site (or apps/api, or apps/web) is linked to Vercel, it also replaces the
49
- production AILK_LICENSE_KEY variable and redeploys.
48
+ Writes the key to ailk.key, and to apps/api/.env when the site has apps/api
49
+ (owner-only, gitignored). When the site (or apps/api, or apps/web) is linked to
50
+ Vercel, it also replaces the production AILK_LICENSE_KEY variable and redeploys.
50
51
  `;
51
52
  const LINK_DIRS = [".", join("apps", "api"), join("apps", "web")];
52
53
  const VERCELIGNORE_LINES = [LICENSE_KEY_FILE, ".env"];
@@ -151,9 +152,13 @@ export function runLicenseSet(argv, io) {
151
152
  if (!existsSync(join(target, "architecture.yaml"))) {
152
153
  return fail(`not a scaffolded repo: no architecture.yaml at ${target}. Run it from your site's root, or pass the site's directory.`);
153
154
  }
154
- writeLicenseKeyFiles(target, key);
155
+ // A site without apps/api (a web-only spoke) keeps the key in ailk.key
156
+ // alone — writing apps/api/.env would create an app directory it never had.
157
+ const files = writeLicenseKeyFiles(target, key, {
158
+ apiEnv: existsSync(join(target, "apps", "api")),
159
+ });
155
160
  const keyFile = join(target, LICENSE_KEY_FILE);
156
- const written = `Wrote ${LICENSE_KEY_FILE} and ${LICENSE_ENV_FILE.split("\\").join("/")} in ${target}`;
161
+ const written = `Wrote ${files.map((f) => f.split("\\").join("/")).join(" and ")} in ${target}`;
157
162
  io.stdout(`[create-ailk] ${written} (owner-only, gitignored).\n`);
158
163
  const linked = LINK_DIRS.map((d) => join(target, d)).filter((d) => existsSync(join(d, ".vercel", "project.json")));
159
164
  if (linked.length === 0) {
@@ -157,15 +157,41 @@ export const ARCHITECTURE_BASE = {
157
157
  * file behind it — the three operator route files, the two member route
158
158
  * files, the member resolution (`services/member-area.ts`) and the member
159
159
  * data boundary (`lib/member-store.ts`).
160
+ *
161
+ * #8169 D9 adds the five Phase 1 member reads registered under that same
162
+ * disjunction. Its two other reads — `routes/member/meeting-types.ts` and
163
+ * `availability.ts` — reach `services/scheduling/`, so they are the
164
+ * `scheduling` slice's, not listed here.
165
+ *
166
+ * #8170 D11 adds the four member writes registered under that same
167
+ * disjunction (the message POST rides `messages.ts`). The fifth —
168
+ * `routes/member/bookings.ts` — reaches `services/scheduling/`, so it is the
169
+ * `scheduling` slice's.
160
170
  */
161
171
  const MEMBER_AREA_FILES = [
162
172
  "apps/api/src/routes/workspaces/pieces.ts",
163
173
  "apps/api/src/routes/workspaces/plan.ts",
164
174
  "apps/api/src/routes/workspaces/messages-out.ts",
175
+ // #8171 D8 — the workspace Requests read; imports only pieces.ts,
176
+ // lib/tenant-db.ts and @working-theory/validation.
177
+ "apps/api/src/routes/workspaces/requests.ts",
165
178
  "apps/api/src/routes/member/pieces.ts",
166
179
  "apps/api/src/routes/member/messages.ts",
180
+ "apps/api/src/routes/member/purchases.ts",
181
+ "apps/api/src/routes/member/appointments.ts",
182
+ "apps/api/src/routes/member/artifacts.ts",
183
+ "apps/api/src/routes/member/request-form.ts",
184
+ "apps/api/src/routes/member/billing.ts",
185
+ "apps/api/src/routes/member/requests.ts",
186
+ "apps/api/src/routes/member/artifact-review.ts",
187
+ "apps/api/src/routes/member/profile.ts",
188
+ "apps/api/src/routes/member/deletion-request.ts",
167
189
  "apps/api/src/services/member-area.ts",
168
190
  "apps/api/src/lib/member-store.ts",
191
+ // #8226 D12 — the member-request match CLI; both import member-store.ts,
192
+ // which a build without the member slice does not carry.
193
+ "apps/api/src/lib/member-request-match.ts",
194
+ "apps/api/src/bin/member-request-match.ts",
169
195
  ];
170
196
  /**
171
197
  * The workspace asset store (#5854), CO-OWNED by the `workspace` and
@@ -864,6 +890,18 @@ export const MODULE_SLICES = {
864
890
  "apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx",
865
891
  "apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx",
866
892
  "apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx",
893
+ // #7389 — the three api suites that enable `multi-tenant` ALONE and so
894
+ // reach files this slice delivers (`services/tenant-context.ts` and the
895
+ // `routes/workspaces/` index). Each is listed individually, never as a
896
+ // glob (the `scheduling` slice's § D12 property 1: a glob entry is a
897
+ // silent no-op), so it ships to a keyed scaffold with the slice and is
898
+ // stripped from a keyless one with it — instead of a keyless scaffold
899
+ // carrying a test that raises `createNoMappedModuleFoundError`. A suite
900
+ // that enables SEVERAL paid modules cannot be owned by any one of
901
+ // them; those are `EXCLUDE_TEMPLATE_DIRS` entries below.
902
+ "apps/api/src/services/__tests__/lead-promotion.test.ts",
903
+ "apps/api/src/services/__tests__/leads-inbox.test.ts",
904
+ "apps/api/src/__tests__/content-write-production-boot.test.ts",
867
905
  "apps-paid/admin/**",
868
906
  ],
869
907
  // The whole-app glob above lands its files at their REPO-relative path
@@ -1232,6 +1270,13 @@ export const MODULE_SLICES = {
1232
1270
  // records: "each tenancy slice must deliver it").
1233
1271
  "apps/api/src/lib/tenant-db.ts",
1234
1272
  "apps/api/src/services/tenant-context.ts",
1273
+ // #7389 — `routes/workspaces/__tests__/meetings.test.ts` and
1274
+ // `meetings-connection.test.ts` (listed below) register the real
1275
+ // `tenantPreHandlerPlugin` in front of `meetings.ts`, so a scheduling
1276
+ // scaffold without `multi-tenant` could not load them. It imports only
1277
+ // `services/tenant-context.ts` (owned above) and the base
1278
+ // `default-tenant.ts`, so co-owning it keeps the closure tight.
1279
+ "apps/api/src/middleware/tenant.ts",
1235
1280
  // #7533 — a scheduling connection per workspace. `connections.ts` (the
1236
1281
  // cipher + the one SchedulingConnection data-access boundary) and
1237
1282
  // `resolve.ts` (the workspace-aware provider resolution the meetings,
@@ -1248,6 +1293,23 @@ export const MODULE_SLICES = {
1248
1293
  "apps/api/src/routes/schedule/__tests__/public-connection.test.ts",
1249
1294
  "apps/api/src/routes/webhooks/__tests__/calcom-connection.test.ts",
1250
1295
  "apps/api/src/config/__tests__/scheduling-credential-key.test.ts",
1296
+ // #8169 D9 — the two member reads that reach `services/scheduling/`,
1297
+ // registered under `scheduling && (workspace || multi-tenant)`, plus
1298
+ // the three member files they import, which this slice then CO-OWNS
1299
+ // with the two tenancy slices (`MEMBER_AREA_FILES`) — exactly as it
1300
+ // co-owns `lib/tenant-db.ts` for `routes/workspaces/meetings.ts` above.
1301
+ "apps/api/src/routes/member/meeting-types.ts",
1302
+ "apps/api/src/routes/member/availability.ts",
1303
+ // #8170 D11 — the member booking, the same shape.
1304
+ "apps/api/src/routes/member/bookings.ts",
1305
+ "apps/api/src/routes/member/pieces.ts",
1306
+ "apps/api/src/services/member-area.ts",
1307
+ "apps/api/src/lib/member-store.ts",
1308
+ // #7389 — the two api suites that enable `scheduling` alone, so they
1309
+ // strip with `routes/schedule/index.ts`, which they load by a computed
1310
+ // specifier the import-graph closure cannot follow.
1311
+ "apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts",
1312
+ "apps/api/src/__tests__/openapi-keyless-hub.test.ts",
1251
1313
  ],
1252
1314
  // pageContributions (spec 5478; parent § D6 shape 1) — the booker, the
1253
1315
  // per-booking-type page and the confirmation page, whose route files
@@ -1463,6 +1525,10 @@ export const MODULE_SLICES = {
1463
1525
  "apps/api/src/bin/listing-csv-sweep.ts",
1464
1526
  "apps/api/src/bin/followup-sweep.ts",
1465
1527
  "apps/api/src/bin/seed-waitlist-experiments.ts",
1528
+ // #7389 — the listing-directory suite enables `signal-collectors` alone
1529
+ // and loads `routes/waitlist-signups/index.ts` through the server it
1530
+ // builds, so it strips with that route. Individually, never as a glob.
1531
+ "apps/api/src/routes/project-listings/__tests__/directory.test.ts",
1466
1532
  "apps/mcp/src/tools/get_flow.ts",
1467
1533
  "apps/mcp/src/tools/submit_flow_step.ts",
1468
1534
  // #3591 PR-E — the signed-in MCP tools that twin the tenant-scoped
@@ -2013,6 +2079,44 @@ export function coOwnedModuleDestinations() {
2013
2079
  // docs/specs/7315-a-gated-pro-tool-re-spec.md D4) — same reason as the
2014
2080
  // three suites above: it imports `SITE_ARGS` from the monorepo's root
2015
2081
  // `tests/parity/` tree, which no scaffold carries.
2082
+ // - apps/mcp/__tests__/write-tool-guard.test.ts (#8071, spec
2083
+ // docs/specs/8071-mcp-write-guard-spec.md D4) — it reads every anonymous
2084
+ // catalog tool's handler file, and a scaffold carries only the handler
2085
+ // files of the modules it selected while the catalog lists every tool.
2086
+ // `WRITE_TOOLS` is edited in the monorepo, which is where it must run.
2087
+ // - the suites a keyless scaffold's own `pnpm test` cannot run (#7389) —
2088
+ // file entries, matched exactly. Two classes, neither of which a slice can
2089
+ // own (a slice's `ownedFiles` carries a suite that needs ONE paid module;
2090
+ // the six api suites that do are listed under `multi-tenant`, `scheduling`
2091
+ // and `signal-collectors` above, and a keyed scaffold runs them):
2092
+ // * monorepo fixtures — each reads a path only this repo has:
2093
+ // `services/__tests__/aeo-score.test.ts` (packages/aeo's result.json),
2094
+ // `routes/leads/__tests__/index.test.ts` (packages/validation's
2095
+ // newsletter-lead fixture), `routes/workspaces/__tests__/
2096
+ // inbox-wire-schemas.test.ts` (the root `tests/parity/` core),
2097
+ // mcp's `workspace-surface.test.ts` (the same core) and mcp's
2098
+ // `audit.test.ts` (the T1 fixture site's `project.yaml`).
2099
+ // * whole-composition suites — each builds the real server or reads
2100
+ // the whole tree under SEVERAL module sets, or asserts the
2101
+ // monorepo's own identity, so no single slice is its owner and a
2102
+ // scaffold that selected only some of the modules would fail it:
2103
+ // `server.test.ts` (asserts the monorepo's "AI Launch Kit API" name,
2104
+ // and a `signal-collectors` case), `cors.test.ts` (multi-tenant +
2105
+ // scheduling), `root-modules.test.ts`, `module-exclusion.test.ts`,
2106
+ // `openapi/__tests__/surface-drift.test.ts`,
2107
+ // `review-surface-registration.test.ts` and
2108
+ // `workspace-module-registration.test.ts` (each a matrix over module
2109
+ // sets), and mcp's `parity.test.ts` (counts the Server Actions and
2110
+ // tool handlers of the FULL tree, which a stripped scaffold lacks).
2111
+ // The exclusion is per file, not a `testPathIgnorePatterns` entry in the
2112
+ // scaffold's jest config: that would hide these suites from a keyed
2113
+ // scaffold too, and from the suites a slice owns.
2114
+ // - database/__tests__/build.test.ts (#7959) — the monorepo build probe
2115
+ // (#7565). Its `beforeAll` symlinks `REPO_ROOT/packages` and copies
2116
+ // `REPO_ROOT/tsconfig.base.json`, neither of which a scaffold has, so it
2117
+ // red the scaffold's database jest run. Matched exactly, not by basename
2118
+ // in `EXCLUDE_FILES`, which would drop a `build.test.ts` anywhere in the
2119
+ // snapshot; every other database suite still ships.
2016
2120
  export const EXCLUDE_TEMPLATE_DIRS = [
2017
2121
  "apps/web/__tests__",
2018
2122
  "apps/web/app/__tests__",
@@ -2023,6 +2127,21 @@ export const EXCLUDE_TEMPLATE_DIRS = [
2023
2127
  "apps/api/src/__tests__/api-parity-paid.test.ts",
2024
2128
  "apps/mcp/__tests__/api-parity.test.ts",
2025
2129
  "apps/mcp/__tests__/pro-tool-call-gate.test.ts",
2130
+ "apps/mcp/__tests__/write-tool-guard.test.ts",
2131
+ "apps/api/src/services/__tests__/aeo-score.test.ts",
2132
+ "apps/api/src/routes/leads/__tests__/index.test.ts",
2133
+ "apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts",
2134
+ "apps/api/src/__tests__/server.test.ts",
2135
+ "apps/api/src/__tests__/cors.test.ts",
2136
+ "apps/api/src/__tests__/root-modules.test.ts",
2137
+ "apps/api/src/__tests__/module-exclusion.test.ts",
2138
+ "apps/api/src/openapi/__tests__/surface-drift.test.ts",
2139
+ "apps/api/src/__tests__/review-surface-registration.test.ts",
2140
+ "apps/api/src/__tests__/workspace-module-registration.test.ts",
2141
+ "apps/mcp/__tests__/workspace-surface.test.ts",
2142
+ "apps/mcp/__tests__/parity.test.ts",
2143
+ "apps/mcp/__tests__/audit.test.ts",
2144
+ "database/__tests__/build.test.ts",
2026
2145
  ];
2027
2146
  export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
2028
2147
  const roots = new Set(snapshotRoots);
@@ -747,11 +747,14 @@ export async function scaffold(opts) {
747
747
  // two return pages, once every layer has landed (so the spoke filter has
748
748
  // already dropped the hub-checkout files) and before the manifest, whose
749
749
  // code-route rows then record /checkout/thank-you and /checkout/unavailable.
750
+ // #8217 D8 — a spoke that signs in through its hub also gets the billing
751
+ // action and the web seam render that reaches it.
750
752
  const spokeCommerce = spoke !== undefined && result.modules.includes("commerce");
751
753
  if (spokeCommerce) {
752
754
  writeSpokeCheckoutSlice(result.targetDir, {
753
755
  site: spokeCheckoutSite(projectName),
754
756
  versions,
757
+ signIn: spoke.signIn,
755
758
  });
756
759
  }
757
760
  // #6944 — the manifest records what this scaffold IS, once every layer has
@@ -1,6 +1,7 @@
1
1
  import type { EnvCatalogEntry, PlanEnvRowsOptions } from "./lib/declared-env.js";
2
2
  import { type InstallReportRow } from "./lib/extract-module-bundle.js";
3
3
  import { type WorkspaceVersionMap } from "./substitute.js";
4
+ import { type SignInMode } from "./topology.js";
4
5
  /** The checkout package a spoke's two route files import. */
5
6
  export declare const SPOKE_CHECKOUT_PACKAGE = "@working-theory/spoke-checkout";
6
7
  /** The site's checkout data — written once, the site's from then on. */
@@ -48,13 +49,19 @@ export declare const CHECKOUT_E2E_WORKFLOW: string;
48
49
  /**
49
50
  * The slice for `site`, in order: the two route files, the config, the two
50
51
  * pages (D3), then the checkout e2e workflow (#7839 D8), which does not
51
- * depend on `site`. Throws `TopologyError` for an invalid stamp (SEC-7838-10).
52
+ * depend on `site`. With `signIn: "hub"` the billing action is the third
53
+ * pair, so the first three paths are `SPOKE_CHECKOUT_ROUTE_FILES` (#8217 D8).
54
+ * Throws `TopologyError` for an invalid stamp (SEC-7838-10).
52
55
  */
53
- export declare function renderSpokeCheckoutFiles(site: string): [string, string][];
56
+ export declare function renderSpokeCheckoutFiles(site: string, opts?: {
57
+ signIn?: SignInMode | undefined;
58
+ }): [string, string][];
54
59
  /**
55
60
  * Write the slice into `targetDir` (D3). A file that already exists is left
56
61
  * byte-for-byte and reported `skipped` — the config is the site's once
57
- * written. Adds the package to `apps/web/package.json` when absent, and
62
+ * written. With `signIn: "hub"` (#8217 D8) the slice carries the billing
63
+ * action, and the web seam — generated, so always rewritten — becomes the
64
+ * billing render: reported `patched` when it changed, not at all when equal. Adds the package to `apps/web/package.json` when absent, and
58
65
  * raises a plain pin older than the one it writes (#7839): the generated
59
66
  * checkout e2e workflow runs a command a `^0.2.0` pin never reaches. Any
60
67
  * other range — newer, `workspace:`, a tag — is the site's and is kept.
@@ -64,4 +71,5 @@ export declare function renderSpokeCheckoutFiles(site: string): [string, string]
64
71
  export declare function writeSpokeCheckoutSlice(targetDir: string, opts: {
65
72
  site: string;
66
73
  versions: WorkspaceVersionMap;
74
+ signIn?: SignInMode | undefined;
67
75
  }): InstallReportRow[];