@empire-builder-kit/nx 1.0.0-beta.10

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 (243) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +34 -0
  4. package/executors.json +74 -0
  5. package/generators.json +40 -0
  6. package/migrations/standard-pre-v1-to-v1.md +11 -0
  7. package/package.json +80 -0
  8. package/src/blueprints/execution.d.ts +31 -0
  9. package/src/blueprints/execution.js +166 -0
  10. package/src/blueprints/execution.js.map +1 -0
  11. package/src/blueprints/index.d.ts +5 -0
  12. package/src/blueprints/index.js +9 -0
  13. package/src/blueprints/index.js.map +1 -0
  14. package/src/blueprints/inputs.d.ts +43 -0
  15. package/src/blueprints/inputs.js +103 -0
  16. package/src/blueprints/inputs.js.map +1 -0
  17. package/src/blueprints/manifest.d.ts +281 -0
  18. package/src/blueprints/manifest.js +441 -0
  19. package/src/blueprints/manifest.js.map +1 -0
  20. package/src/blueprints/resolver.d.ts +28 -0
  21. package/src/blueprints/resolver.js +218 -0
  22. package/src/blueprints/resolver.js.map +1 -0
  23. package/src/blueprints/transitions.d.ts +20 -0
  24. package/src/blueprints/transitions.js +61 -0
  25. package/src/blueprints/transitions.js.map +1 -0
  26. package/src/executors/deploy/deploy.d.ts +4 -0
  27. package/src/executors/deploy/deploy.js +6 -0
  28. package/src/executors/deploy/deploy.js.map +1 -0
  29. package/src/executors/deployment-plan/deployment-plan.d.ts +9 -0
  30. package/src/executors/deployment-plan/deployment-plan.js +50 -0
  31. package/src/executors/deployment-plan/deployment-plan.js.map +1 -0
  32. package/src/executors/deployment-plan/schema.json +24 -0
  33. package/src/executors/dev/dev.d.ts +4 -0
  34. package/src/executors/dev/dev.js +6 -0
  35. package/src/executors/dev/dev.js.map +1 -0
  36. package/src/executors/dev-doctor/dev-doctor.d.ts +31 -0
  37. package/src/executors/dev-doctor/dev-doctor.js +260 -0
  38. package/src/executors/dev-doctor/dev-doctor.js.map +1 -0
  39. package/src/executors/dev-doctor/schema.json +17 -0
  40. package/src/executors/dev-setup/dev-setup.d.ts +11 -0
  41. package/src/executors/dev-setup/dev-setup.js +454 -0
  42. package/src/executors/dev-setup/dev-setup.js.map +1 -0
  43. package/src/executors/dev-setup/schema.json +16 -0
  44. package/src/executors/fleet/fleet.d.ts +62 -0
  45. package/src/executors/fleet/fleet.js +613 -0
  46. package/src/executors/fleet/fleet.js.map +1 -0
  47. package/src/executors/fleet/schema.json +37 -0
  48. package/src/executors/foundation-preflight/foundation-preflight.d.ts +15 -0
  49. package/src/executors/foundation-preflight/foundation-preflight.js +116 -0
  50. package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -0
  51. package/src/executors/foundation-preflight/schema.json +31 -0
  52. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +8 -0
  53. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +41 -0
  54. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -0
  55. package/src/executors/foundation-profile-preflight/schema.json +21 -0
  56. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +13 -0
  57. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +69 -0
  58. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -0
  59. package/src/executors/foundation-remove-preflight/schema.json +17 -0
  60. package/src/executors/github-bootstrap/github-bootstrap.d.ts +8 -0
  61. package/src/executors/github-bootstrap/github-bootstrap.js +30 -0
  62. package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -0
  63. package/src/executors/github-bootstrap/schema.json +25 -0
  64. package/src/executors/refresh/refresh.d.ts +4 -0
  65. package/src/executors/refresh/refresh.js +6 -0
  66. package/src/executors/refresh/refresh.js.map +1 -0
  67. package/src/executors/remove/remove.d.ts +4 -0
  68. package/src/executors/remove/remove.js +6 -0
  69. package/src/executors/remove/remove.js.map +1 -0
  70. package/src/executors/sst-lifecycle/run-sst-command.d.ts +31 -0
  71. package/src/executors/sst-lifecycle/run-sst-command.js +226 -0
  72. package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -0
  73. package/src/executors/sst-lifecycle/schema.json +45 -0
  74. package/src/executors/unlock/unlock.d.ts +4 -0
  75. package/src/executors/unlock/unlock.js +6 -0
  76. package/src/executors/unlock/unlock.js.map +1 -0
  77. package/src/executors/work-markers/schema.json +14 -0
  78. package/src/executors/work-markers/work-markers.d.ts +6 -0
  79. package/src/executors/work-markers/work-markers.js +20 -0
  80. package/src/executors/work-markers/work-markers.js.map +1 -0
  81. package/src/foundation/aurora-logical-database.d.ts +76 -0
  82. package/src/foundation/aurora-logical-database.js +419 -0
  83. package/src/foundation/aurora-logical-database.js.map +1 -0
  84. package/src/foundation/discovery.d.ts +55 -0
  85. package/src/foundation/discovery.js +55 -0
  86. package/src/foundation/discovery.js.map +1 -0
  87. package/src/foundation/index.d.ts +6 -0
  88. package/src/foundation/index.js +10 -0
  89. package/src/foundation/index.js.map +1 -0
  90. package/src/foundation/local-database-contract.d.ts +30 -0
  91. package/src/foundation/local-database-contract.js +75 -0
  92. package/src/foundation/local-database-contract.js.map +1 -0
  93. package/src/foundation/preflight.d.ts +31 -0
  94. package/src/foundation/preflight.js +114 -0
  95. package/src/foundation/preflight.js.map +1 -0
  96. package/src/foundation/provision-database-bindings.d.ts +30 -0
  97. package/src/foundation/provision-database-bindings.js +113 -0
  98. package/src/foundation/provision-database-bindings.js.map +1 -0
  99. package/src/foundation/provision-logical-database.d.ts +26 -0
  100. package/src/foundation/provision-logical-database.js +163 -0
  101. package/src/foundation/provision-logical-database.js.map +1 -0
  102. package/src/foundation/requirements.d.ts +37 -0
  103. package/src/foundation/requirements.js +141 -0
  104. package/src/foundation/requirements.js.map +1 -0
  105. package/src/foundation/stages.d.ts +14 -0
  106. package/src/foundation/stages.js +51 -0
  107. package/src/foundation/stages.js.map +1 -0
  108. package/src/generators/function/function.d.ts +13 -0
  109. package/src/generators/function/function.js +71 -0
  110. package/src/generators/function/function.js.map +1 -0
  111. package/src/generators/function/schema.d.ts +5 -0
  112. package/src/generators/function/schema.json +20 -0
  113. package/src/generators/job/job.d.ts +4 -0
  114. package/src/generators/job/job.js +5 -0
  115. package/src/generators/job/job.js.map +1 -0
  116. package/src/generators/preset/generator.d.ts +5 -0
  117. package/src/generators/preset/generator.js +41 -0
  118. package/src/generators/preset/generator.js.map +1 -0
  119. package/src/generators/preset/schema.d.ts +4 -0
  120. package/src/generators/preset/schema.json +22 -0
  121. package/src/generators/preset/workspace-files.d.ts +18 -0
  122. package/src/generators/preset/workspace-files.js +1605 -0
  123. package/src/generators/preset/workspace-files.js.map +1 -0
  124. package/src/generators/repository-policy/repository-policy.d.ts +4 -0
  125. package/src/generators/repository-policy/repository-policy.js +107 -0
  126. package/src/generators/repository-policy/repository-policy.js.map +1 -0
  127. package/src/generators/repository-policy/schema.d.ts +18 -0
  128. package/src/generators/repository-policy/schema.json +46 -0
  129. package/src/generators/service/service.d.ts +4 -0
  130. package/src/generators/service/service.js +5 -0
  131. package/src/generators/service/service.js.map +1 -0
  132. package/src/generators/slice/schema.d.ts +7 -0
  133. package/src/generators/slice/schema.json +38 -0
  134. package/src/generators/slice/slice.d.ts +26 -0
  135. package/src/generators/slice/slice.js +1698 -0
  136. package/src/generators/slice/slice.js.map +1 -0
  137. package/src/generators/upgrade/schema.d.ts +8 -0
  138. package/src/generators/upgrade/schema.json +31 -0
  139. package/src/generators/upgrade/upgrade.d.ts +27 -0
  140. package/src/generators/upgrade/upgrade.js +682 -0
  141. package/src/generators/upgrade/upgrade.js.map +1 -0
  142. package/src/generators/workload/schema.d.ts +6 -0
  143. package/src/generators/workload/schema.json +21 -0
  144. package/src/generators/workload/workload.d.ts +16 -0
  145. package/src/generators/workload/workload.js +157 -0
  146. package/src/generators/workload/workload.js.map +1 -0
  147. package/src/git-policy/action-pins.d.ts +28 -0
  148. package/src/git-policy/action-pins.js +35 -0
  149. package/src/git-policy/action-pins.js.map +1 -0
  150. package/src/git-policy/github-bootstrap.d.ts +23 -0
  151. package/src/git-policy/github-bootstrap.js +759 -0
  152. package/src/git-policy/github-bootstrap.js.map +1 -0
  153. package/src/git-policy/index.d.ts +3 -0
  154. package/src/git-policy/index.js +7 -0
  155. package/src/git-policy/index.js.map +1 -0
  156. package/src/git-policy/routing.d.ts +15 -0
  157. package/src/git-policy/routing.js +77 -0
  158. package/src/git-policy/routing.js.map +1 -0
  159. package/src/index.d.ts +8 -0
  160. package/src/index.js +12 -0
  161. package/src/index.js.map +1 -0
  162. package/src/local-dev/contracts.d.ts +31 -0
  163. package/src/local-dev/contracts.js +126 -0
  164. package/src/local-dev/contracts.js.map +1 -0
  165. package/src/local-dev/effects.d.ts +16 -0
  166. package/src/local-dev/effects.js +63 -0
  167. package/src/local-dev/effects.js.map +1 -0
  168. package/src/local-dev/gateway-daemon.d.ts +4 -0
  169. package/src/local-dev/gateway-daemon.js +39 -0
  170. package/src/local-dev/gateway-daemon.js.map +1 -0
  171. package/src/local-dev/gateway.d.ts +60 -0
  172. package/src/local-dev/gateway.js +368 -0
  173. package/src/local-dev/gateway.js.map +1 -0
  174. package/src/local-dev/identity.d.ts +16 -0
  175. package/src/local-dev/identity.js +101 -0
  176. package/src/local-dev/identity.js.map +1 -0
  177. package/src/local-dev/index.d.ts +7 -0
  178. package/src/local-dev/index.js +11 -0
  179. package/src/local-dev/index.js.map +1 -0
  180. package/src/local-dev/portless.d.ts +15 -0
  181. package/src/local-dev/portless.js +115 -0
  182. package/src/local-dev/portless.js.map +1 -0
  183. package/src/local-dev/ports.d.ts +10 -0
  184. package/src/local-dev/ports.js +39 -0
  185. package/src/local-dev/ports.js.map +1 -0
  186. package/src/local-dev/proxy.d.ts +37 -0
  187. package/src/local-dev/proxy.js +271 -0
  188. package/src/local-dev/proxy.js.map +1 -0
  189. package/src/local-dev/tls.d.ts +24 -0
  190. package/src/local-dev/tls.js +294 -0
  191. package/src/local-dev/tls.js.map +1 -0
  192. package/src/ownership/composition.d.ts +16 -0
  193. package/src/ownership/composition.js +125 -0
  194. package/src/ownership/composition.js.map +1 -0
  195. package/src/ownership/foundation-record.d.ts +66 -0
  196. package/src/ownership/foundation-record.js +108 -0
  197. package/src/ownership/foundation-record.js.map +1 -0
  198. package/src/ownership/hash.d.ts +2 -0
  199. package/src/ownership/hash.js +10 -0
  200. package/src/ownership/hash.js.map +1 -0
  201. package/src/ownership/index.d.ts +6 -0
  202. package/src/ownership/index.js +10 -0
  203. package/src/ownership/index.js.map +1 -0
  204. package/src/ownership/json-ownership.d.ts +13 -0
  205. package/src/ownership/json-ownership.js +147 -0
  206. package/src/ownership/json-ownership.js.map +1 -0
  207. package/src/ownership/record.d.ts +186 -0
  208. package/src/ownership/record.js +336 -0
  209. package/src/ownership/record.js.map +1 -0
  210. package/src/ownership/workspace-record.d.ts +66 -0
  211. package/src/ownership/workspace-record.js +108 -0
  212. package/src/ownership/workspace-record.js.map +1 -0
  213. package/src/repository-policy/aurora-preflight.d.ts +15 -0
  214. package/src/repository-policy/aurora-preflight.js +45 -0
  215. package/src/repository-policy/aurora-preflight.js.map +1 -0
  216. package/src/repository-policy/deployment-plan.d.ts +24 -0
  217. package/src/repository-policy/deployment-plan.js +124 -0
  218. package/src/repository-policy/deployment-plan.js.map +1 -0
  219. package/src/repository-policy/foundation-profile.d.ts +20 -0
  220. package/src/repository-policy/foundation-profile.js +45 -0
  221. package/src/repository-policy/foundation-profile.js.map +1 -0
  222. package/src/repository-policy/index.d.ts +4 -0
  223. package/src/repository-policy/index.js +8 -0
  224. package/src/repository-policy/index.js.map +1 -0
  225. package/src/repository-policy/policy.d.ts +59 -0
  226. package/src/repository-policy/policy.js +274 -0
  227. package/src/repository-policy/policy.js.map +1 -0
  228. package/src/schemas/blueprint-input-schema-v1.json +23 -0
  229. package/src/schemas/blueprint-v1.json +186 -0
  230. package/src/schemas/foundation-ownership-v2.json +42 -0
  231. package/src/schemas/foundation-requirement-v1.json +32 -0
  232. package/src/schemas/generated-ownership-v2.json +117 -0
  233. package/src/schemas/repository-policy-v1.json +58 -0
  234. package/src/schemas/workspace-ownership-v2.json +42 -0
  235. package/src/validation.d.ts +14 -0
  236. package/src/validation.js +14 -0
  237. package/src/validation.js.map +1 -0
  238. package/src/work-markers/index.d.ts +1 -0
  239. package/src/work-markers/index.js +5 -0
  240. package/src/work-markers/index.js.map +1 -0
  241. package/src/work-markers/work-markers.d.ts +39 -0
  242. package/src/work-markers/work-markers.js +359 -0
  243. package/src/work-markers/work-markers.js.map +1 -0
@@ -0,0 +1,1605 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WORKSPACE_PACKAGE_MANAGER_VERSION = exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = void 0;
4
+ exports.renderWorkspaceFiles = renderWorkspaceFiles;
5
+ exports.renderWorkspaceRootFilePlan = renderWorkspaceRootFilePlan;
6
+ exports.renderFoundationFilePlan = renderFoundationFilePlan;
7
+ const index_js_1 = require("../../ownership/index.js");
8
+ const index_js_2 = require("../../git-policy/index.js");
9
+ const index_js_3 = require("../../repository-policy/index.js");
10
+ const TOOL_VERSIONS = {
11
+ eslint: '^9.8.0',
12
+ eslintJs: '^9.8.0',
13
+ globals: '^16.0.0',
14
+ nodeTypes: '25.5.0',
15
+ nx: '23.1.0',
16
+ pnpm: '10.30.1',
17
+ prettier: '^3.8.2',
18
+ sst: '4.11.0',
19
+ typescript: '~5.9.2',
20
+ typescriptEslint: '^8.40.0',
21
+ };
22
+ function json(value) {
23
+ return `${JSON.stringify(value, null, 2)}\n`;
24
+ }
25
+ const FOUNDATION_ROOT = 'packages/foundation/';
26
+ const FOUNDATION_OWNERSHIP_PATH = `${FOUNDATION_ROOT}.ebk/ownership.json`;
27
+ const FOUNDATION_TOOL_OWNED_PATHS = new Set(['sst-env.d.ts']);
28
+ const WORKSPACE_OWNERSHIP_PATH = '.ebk/ownership.json';
29
+ const WORKSPACE_SEEDED_PATHS = new Set([
30
+ '.github/CODEOWNERS',
31
+ 'AGENTS.md',
32
+ 'CLAUDE.md',
33
+ 'README.md',
34
+ 'docs/architecture.md',
35
+ 'docs/git-workflow.md',
36
+ 'docs/repository-operating-policy.md',
37
+ 'docs/work-markers.md',
38
+ 'ebk.repository-policy.json',
39
+ 'package.json',
40
+ ]);
41
+ exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS = [
42
+ '/name',
43
+ '/$schema',
44
+ '/projectType',
45
+ '/tags',
46
+ '/targets/deployment-plan',
47
+ '/targets/dev',
48
+ '/targets/dev-doctor',
49
+ '/targets/dev-setup',
50
+ '/targets/github-bootstrap',
51
+ '/targets/install-git-hooks',
52
+ '/targets/verify',
53
+ '/targets/work-markers',
54
+ '/targets/workspace-health',
55
+ ];
56
+ const FOUNDATION_SEEDED_PATHS = new Set([
57
+ 'README.md',
58
+ 'docs/costs.md',
59
+ 'docs/runbooks.md',
60
+ 'docs/security.md',
61
+ ]);
62
+ function createFoundationOwnership(files, frameworkVersion) {
63
+ const paths = Object.keys(files)
64
+ .filter((path) => path.startsWith(FOUNDATION_ROOT) &&
65
+ path !== FOUNDATION_OWNERSHIP_PATH &&
66
+ !FOUNDATION_TOOL_OWNED_PATHS.has(path.slice(FOUNDATION_ROOT.length)))
67
+ .map((path) => {
68
+ const relativePath = path.slice(FOUNDATION_ROOT.length);
69
+ return {
70
+ mode: FOUNDATION_SEEDED_PATHS.has(relativePath)
71
+ ? 'seeded'
72
+ : 'managed',
73
+ origin: 'framework',
74
+ path: relativePath,
75
+ sha256: (0, index_js_1.hashGeneratedContent)(files[path] ?? ''),
76
+ };
77
+ });
78
+ paths.sort((left, right) => left.path.localeCompare(right.path));
79
+ return {
80
+ frameworkVersion,
81
+ kind: 'foundation',
82
+ paths,
83
+ schemaVersion: index_js_1.FOUNDATION_OWNERSHIP_SCHEMA_VERSION,
84
+ };
85
+ }
86
+ function createWorkspaceOwnership(files, frameworkVersion) {
87
+ const paths = Object.keys(files)
88
+ .filter((path) => !path.startsWith(FOUNDATION_ROOT) && path !== WORKSPACE_OWNERSHIP_PATH)
89
+ .map((path) => {
90
+ const ownership = {
91
+ ...(path === 'project.json'
92
+ ? {
93
+ managedJsonPointers: [...exports.WORKSPACE_PROJECT_MANAGED_JSON_POINTERS],
94
+ }
95
+ : {}),
96
+ mode: WORKSPACE_SEEDED_PATHS.has(path)
97
+ ? 'seeded'
98
+ : 'managed',
99
+ origin: 'framework',
100
+ path,
101
+ };
102
+ return {
103
+ ...ownership,
104
+ sha256: (0, index_js_1.hashOwnedContent)(files[path] ?? '', ownership),
105
+ };
106
+ });
107
+ paths.sort((left, right) => left.path.localeCompare(right.path));
108
+ return {
109
+ frameworkVersion,
110
+ kind: 'workspace',
111
+ paths,
112
+ schemaVersion: index_js_1.WORKSPACE_OWNERSHIP_SCHEMA_VERSION,
113
+ };
114
+ }
115
+ function renderWorkspaceFiles(options) {
116
+ const { frameworkVersion, name } = options;
117
+ const checkoutAction = (0, index_js_2.pinnedAction)(index_js_2.GITHUB_ACTION_PINS.checkout);
118
+ const configureAwsAction = (0, index_js_2.pinnedAction)(index_js_2.GITHUB_ACTION_PINS.configureAwsCredentials);
119
+ const pnpmAction = (0, index_js_2.pinnedAction)(index_js_2.GITHUB_ACTION_PINS.pnpmSetup);
120
+ const setupNodeAction = (0, index_js_2.pinnedAction)(index_js_2.GITHUB_ACTION_PINS.setupNode);
121
+ const files = {
122
+ '.github/CODEOWNERS': `# Replace this placeholder with the GitHub user or team that owns protected source.
123
+ # GitHub bootstrap remains blocked until a non-placeholder catch-all owner is committed.
124
+ * @REPLACE_WITH_GITHUB_CODE_OWNER
125
+ `,
126
+ '.github/actions-pins.json': json(index_js_2.GITHUB_ACTION_PINS),
127
+ '.github/workflows/ci.yml': `name: Verify
128
+
129
+ on:
130
+ pull_request:
131
+ branches: [develop, main]
132
+ push:
133
+ branches: [develop, main]
134
+ workflow_dispatch:
135
+
136
+ concurrency:
137
+ group: verify-\${{ github.ref }}
138
+ cancel-in-progress: true
139
+
140
+ permissions:
141
+ contents: read
142
+
143
+ jobs:
144
+ policy:
145
+ name: policy
146
+ runs-on: ubuntu-latest
147
+ steps:
148
+ - uses: ${checkoutAction}
149
+ with:
150
+ fetch-depth: 0
151
+ ref: \${{ github.event.pull_request.head.sha || github.sha }}
152
+ - uses: ${pnpmAction}
153
+ - uses: ${setupNodeAction}
154
+ with:
155
+ node-version: 22
156
+ cache: pnpm
157
+ - run: pnpm install --frozen-lockfile
158
+ - run: node tools/ebk-git-policy.mjs
159
+
160
+ verify:
161
+ name: verify
162
+ runs-on: ubuntu-latest
163
+ steps:
164
+ - uses: ${checkoutAction}
165
+ - uses: ${pnpmAction}
166
+ - uses: ${setupNodeAction}
167
+ with:
168
+ node-version: 22
169
+ cache: pnpm
170
+ - run: pnpm install --frozen-lockfile
171
+ - run: pnpm nx format:check
172
+ - run: pnpm nx run-many -t verify --parallel=3
173
+ `,
174
+ '.github/workflows/deploy.yml': `name: Deploy
175
+
176
+ on:
177
+ push:
178
+ branches: [develop, main]
179
+ workflow_dispatch:
180
+ inputs:
181
+ action:
182
+ description: Lifecycle action
183
+ required: true
184
+ default: deploy
185
+ type: choice
186
+ options: [deploy, refresh, unlock, remove]
187
+ project:
188
+ description: Required registered project for explicit unlock or remove
189
+ required: false
190
+ type: string
191
+
192
+ concurrency:
193
+ group: deploy-\${{ github.ref }}
194
+ cancel-in-progress: false
195
+
196
+ permissions:
197
+ contents: read
198
+
199
+ jobs:
200
+ route:
201
+ runs-on: ubuntu-latest
202
+ steps:
203
+ - name: Validate protected ref
204
+ env:
205
+ EBK_PROTECTED_REF: \${{ github.ref }}
206
+ run: |
207
+ case "$EBK_PROTECTED_REF" in
208
+ refs/heads/develop|refs/heads/main) ;;
209
+ *) echo 'Protected lifecycle must be dispatched from develop or main'; exit 1 ;;
210
+ esac
211
+
212
+ plan-staging:
213
+ needs: route
214
+ if: github.ref == 'refs/heads/develop'
215
+ runs-on: ubuntu-latest
216
+ outputs:
217
+ foundation_project: \${{ steps.plan.outputs.foundation_project }}
218
+ has_slices: \${{ steps.plan.outputs.has_slices }}
219
+ ordered_projects: \${{ steps.plan.outputs.ordered_projects }}
220
+ slice_projects: \${{ steps.plan.outputs.slice_projects }}
221
+ steps:
222
+ - uses: ${checkoutAction}
223
+ - uses: ${pnpmAction}
224
+ - uses: ${setupNodeAction}
225
+ with:
226
+ node-version: 22
227
+ cache: pnpm
228
+ - run: pnpm install --frozen-lockfile
229
+ - id: plan
230
+ env:
231
+ EBK_ACTION: \${{ github.event_name == 'push' && 'deploy' || inputs.action }}
232
+ EBK_PROJECT: \${{ inputs.project || '' }}
233
+ run: |
234
+ args=("--action=$EBK_ACTION" "--stage=staging")
235
+ if [ -n "$EBK_PROJECT" ]; then args+=("--project=$EBK_PROJECT"); fi
236
+ pnpm nx run ${name}:deployment-plan -- "\${args[@]}"
237
+
238
+ plan-production:
239
+ needs: route
240
+ if: github.ref == 'refs/heads/main'
241
+ runs-on: ubuntu-latest
242
+ outputs:
243
+ foundation_project: \${{ steps.plan.outputs.foundation_project }}
244
+ foundation_profile: \${{ steps.plan.outputs.foundation_profile }}
245
+ has_slices: \${{ steps.plan.outputs.has_slices }}
246
+ ordered_projects: \${{ steps.plan.outputs.ordered_projects }}
247
+ slice_projects: \${{ steps.plan.outputs.slice_projects }}
248
+ steps:
249
+ - uses: ${checkoutAction}
250
+ - uses: ${pnpmAction}
251
+ - uses: ${setupNodeAction}
252
+ with:
253
+ node-version: 22
254
+ cache: pnpm
255
+ - run: pnpm install --frozen-lockfile
256
+ - id: plan
257
+ env:
258
+ EBK_ACTION: \${{ github.event_name == 'push' && 'deploy' || inputs.action }}
259
+ EBK_PROJECT: \${{ inputs.project || '' }}
260
+ run: |
261
+ args=("--action=$EBK_ACTION" "--stage=production")
262
+ if [ -n "$EBK_PROJECT" ]; then args+=("--project=$EBK_PROJECT"); fi
263
+ pnpm nx run ${name}:deployment-plan -- "\${args[@]}"
264
+
265
+ deploy-staging:
266
+ name: deploy staging
267
+ needs: plan-staging
268
+ environment: staging
269
+ permissions:
270
+ contents: read
271
+ id-token: write
272
+ env:
273
+ AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
274
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
275
+ AWS_REGION: \${{ vars.AWS_REGION }}
276
+ EBK_ACTION: \${{ github.event_name == 'push' && 'deploy' || inputs.action }}
277
+ EBK_STAGE: staging
278
+ FOUNDATION_PROJECT: \${{ needs.plan-staging.outputs.foundation_project }}
279
+ ORDERED_PROJECTS: \${{ needs.plan-staging.outputs.ordered_projects }}
280
+ SLICE_PROJECTS: \${{ needs.plan-staging.outputs.slice_projects }}
281
+ runs-on: ubuntu-latest
282
+ steps:
283
+ - uses: ${checkoutAction}
284
+ - name: Validate environment configuration
285
+ run: |
286
+ test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'staging AWS_DEPLOY_ROLE_ARN is required'; exit 1; }
287
+ test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'staging AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
288
+ test -n "$AWS_REGION" || { echo 'staging AWS_REGION is required'; exit 1; }
289
+ case "$AWS_DEPLOY_ROLE_ARN" in
290
+ arn:aws:iam::$AWS_EXPECTED_ACCOUNT_ID:role/*) ;;
291
+ *) echo 'Deployment role does not belong to the expected staging account'; exit 1 ;;
292
+ esac
293
+ - uses: ${configureAwsAction}
294
+ with:
295
+ role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
296
+ role-session-name: ebk-\${{ github.run_id }}-\${{ github.run_attempt }}
297
+ aws-region: \${{ vars.AWS_REGION }}
298
+ - name: Verify assumed account
299
+ run: test "$(aws sts get-caller-identity --query Account --output text)" = "$AWS_EXPECTED_ACCOUNT_ID"
300
+ - uses: ${pnpmAction}
301
+ - uses: ${setupNodeAction}
302
+ with:
303
+ node-version: 22
304
+ cache: pnpm
305
+ - run: pnpm install --frozen-lockfile
306
+ - name: Run ordered lifecycle
307
+ run: |
308
+ if [ -n "$FOUNDATION_PROJECT" ]; then
309
+ pnpm nx run "$FOUNDATION_PROJECT:$EBK_ACTION" --stage="$EBK_STAGE"
310
+ fi
311
+ if [ -n "$SLICE_PROJECTS" ]; then
312
+ pnpm nx run-many -t "$EBK_ACTION" --projects="$SLICE_PROJECTS" --stage="$EBK_STAGE" --parallel=1
313
+ fi
314
+
315
+ deploy-production:
316
+ name: deploy production
317
+ needs: plan-production
318
+ environment: production
319
+ permissions:
320
+ contents: read
321
+ id-token: write
322
+ env:
323
+ AWS_DEPLOY_ROLE_ARN: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
324
+ AWS_EXPECTED_ACCOUNT_ID: \${{ vars.AWS_EXPECTED_ACCOUNT_ID }}
325
+ AWS_REGION: \${{ vars.AWS_REGION }}
326
+ EBK_ACTION: \${{ github.event_name == 'push' && 'deploy' || inputs.action }}
327
+ EBK_ALLOW_PRODUCTION_REMOVE: \${{ vars.EBK_ALLOW_PRODUCTION_REMOVE || 'false' }}
328
+ EBK_STAGE: production
329
+ FOUNDATION_PROJECT: \${{ needs.plan-production.outputs.foundation_project }}
330
+ ORDERED_PROJECTS: \${{ needs.plan-production.outputs.ordered_projects }}
331
+ SLICE_PROJECTS: \${{ needs.plan-production.outputs.slice_projects }}
332
+ runs-on: ubuntu-latest
333
+ steps:
334
+ - uses: ${checkoutAction}
335
+ - name: Validate environment configuration
336
+ run: |
337
+ test -n "$AWS_DEPLOY_ROLE_ARN" || { echo 'production AWS_DEPLOY_ROLE_ARN is required'; exit 1; }
338
+ test -n "$AWS_EXPECTED_ACCOUNT_ID" || { echo 'production AWS_EXPECTED_ACCOUNT_ID is required'; exit 1; }
339
+ test -n "$AWS_REGION" || { echo 'production AWS_REGION is required'; exit 1; }
340
+ case "$AWS_DEPLOY_ROLE_ARN" in
341
+ arn:aws:iam::$AWS_EXPECTED_ACCOUNT_ID:role/*) ;;
342
+ *) echo 'Deployment role does not belong to the expected production account'; exit 1 ;;
343
+ esac
344
+ - uses: ${configureAwsAction}
345
+ with:
346
+ role-to-assume: \${{ secrets.AWS_DEPLOY_ROLE_ARN }}
347
+ role-session-name: ebk-\${{ github.run_id }}-\${{ github.run_attempt }}
348
+ aws-region: \${{ vars.AWS_REGION }}
349
+ - name: Verify assumed account
350
+ run: test "$(aws sts get-caller-identity --query Account --output text)" = "$AWS_EXPECTED_ACCOUNT_ID"
351
+ - uses: ${pnpmAction}
352
+ - uses: ${setupNodeAction}
353
+ with:
354
+ node-version: 22
355
+ cache: pnpm
356
+ - run: pnpm install --frozen-lockfile
357
+ - name: Run ordered lifecycle
358
+ run: |
359
+ if [ -n "$FOUNDATION_PROJECT" ]; then
360
+ pnpm nx run "$FOUNDATION_PROJECT:$EBK_ACTION" --stage="$EBK_STAGE"
361
+ fi
362
+ if [ -n "$SLICE_PROJECTS" ]; then
363
+ pnpm nx run-many -t "$EBK_ACTION" --projects="$SLICE_PROJECTS" --stage="$EBK_STAGE" --parallel=1
364
+ fi
365
+ `,
366
+ '.github/workflows/sync-back.yml': `name: Sync production history
367
+
368
+ on:
369
+ workflow_run:
370
+ workflows: [Deploy]
371
+ types: [completed]
372
+
373
+ permissions:
374
+ contents: read
375
+ pull-requests: write
376
+
377
+ jobs:
378
+ sync-back:
379
+ if: >-
380
+ github.event.workflow_run.conclusion == 'success' &&
381
+ github.event.workflow_run.head_branch == 'main'
382
+ runs-on: ubuntu-latest
383
+ steps:
384
+ - name: Open or locate main-to-develop pull request
385
+ env:
386
+ GH_TOKEN: \${{ github.token }}
387
+ GH_REPO: \${{ github.repository }}
388
+ run: |
389
+ number=$(gh pr list --state open --head main --base develop --json number --jq '.[0].number // empty')
390
+ if [ -z "$number" ]; then
391
+ gh pr create \
392
+ --head main \
393
+ --base develop \
394
+ --title 'chore: sync production history to develop' \
395
+ --body 'Managed EBK sync-back. Review and merge; this workflow never pushes or auto-merges.'
396
+ number=$(gh pr list --state open --head main --base develop --json number --jq '.[0].number')
397
+ fi
398
+ echo "Sync-back pull request #$number is ready for review."
399
+
400
+ head_sha=$(gh pr view "$number" --json headRefOid --jq '.headRefOid')
401
+ for attempt in 1 2 3 4 5 6; do
402
+ runs=$(gh api \
403
+ -H 'Accept: application/vnd.github+json' \
404
+ "/repos/$GH_REPO/actions/workflows/ci.yml/runs?event=pull_request&head_sha=$head_sha" \
405
+ --jq '.total_count')
406
+ if [ "$runs" -gt 0 ]; then
407
+ echo 'Pull-request verification run materialized; approve it if GitHub marks it approval-required.'
408
+ exit 0
409
+ fi
410
+ sleep 5
411
+ done
412
+ echo 'No pull-request verification run materialized. Close and reopen the sync pull request as a human, then approve its workflows.'
413
+ exit 1
414
+ `,
415
+ 'tools/ebk-pre-push.mjs': `import { createInterface } from 'node:readline';
416
+
417
+ const protectedRefs = new Set(['refs/heads/develop', 'refs/heads/main']);
418
+ const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
419
+ let rejected = false;
420
+
421
+ for await (const line of lines) {
422
+ const [, , remoteRef] = line.trim().split(/\\s+/);
423
+ if (!protectedRefs.has(remoteRef)) continue;
424
+ rejected = true;
425
+ const branch = remoteRef.slice('refs/heads/'.length);
426
+ const route = branch === 'main'
427
+ ? 'develop -> main promotion'
428
+ : 'feature -> develop or main -> develop sync-back';
429
+ console.error('Direct push to ' + branch + ' rejected. Use a reviewed ' + route + ' pull request.');
430
+ }
431
+
432
+ if (rejected) process.exitCode = 1;
433
+ `,
434
+ 'tools/install-ebk-hooks.mjs': `import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
435
+ import { spawnSync } from 'node:child_process';
436
+
437
+ const marker = '# Empire Builder Kit managed pre-push hook';
438
+ const resolved = spawnSync('git', ['rev-parse', '--git-path', 'hooks/pre-push'], {
439
+ encoding: 'utf8',
440
+ shell: false,
441
+ });
442
+ if (resolved.status !== 0 || !resolved.stdout.trim()) {
443
+ console.error(resolved.stderr.trim() || 'Unable to resolve the repository-local Git hook path.');
444
+ process.exit(1);
445
+ }
446
+
447
+ const hookPath = resolved.stdout.trim();
448
+ if (existsSync(hookPath) && !readFileSync(hookPath, 'utf8').includes(marker)) {
449
+ console.error('A non-EBK pre-push hook already exists at ' + hookPath + '; preserve or compose it manually.');
450
+ process.exit(1);
451
+ }
452
+
453
+ writeFileSync(
454
+ hookPath,
455
+ '#!/bin/sh\\n' + marker + '\\nnode tools/ebk-pre-push.mjs\\n',
456
+ 'utf8',
457
+ );
458
+ chmodSync(hookPath, 0o755);
459
+ console.log('Installed the repository-local EBK pre-push hook at ' + hookPath + '.');
460
+ `,
461
+ '.gitignore': `.nx/cache
462
+ .nx/workspace-data
463
+ .ebk/cache
464
+ .next
465
+ .open-next
466
+ dist
467
+ *.tsbuildinfo
468
+ node_modules
469
+ .sst
470
+ .env
471
+ .env.*
472
+ !.env.example
473
+ `,
474
+ '.prettierignore': `node_modules
475
+ dist
476
+ .nx
477
+ .next
478
+ .open-next
479
+ .sst
480
+ **/next-env.d.ts
481
+ pnpm-lock.yaml
482
+ `,
483
+ '.prettierrc.json': json({ singleQuote: true }),
484
+ 'AGENTS.md': `# Coding agent instructions
485
+
486
+ Use \`pnpm nx\` for workspace tasks and read the repository runbooks before changing
487
+ infrastructure or protected deployment policy. Preserve the ownership boundary recorded in
488
+ \`.ebk/ownership.json\`: managed files belong to EBK, seeded files are generated starting
489
+ points that adopters may change, and unrecorded files are adopter-owned.
490
+
491
+ ## Work markers
492
+
493
+ Run \`pnpm nx run ${name}:work-markers\` when beginning or handing off implementation work.
494
+ Search results are durable pointers to custom or nondeterministic work that the deterministic
495
+ generator cannot complete safely. Read the marker's self-contained description and its
496
+ \`GH:<number>\` or \`PLAN:<phase>/<slug>\` reference before acting.
497
+
498
+ Blueprint and repository upgrades may also add a marker to an editable surface when preserved
499
+ custom code must be reconciled with the new contract. Treat that marker as incomplete upgrade
500
+ work: adapt the custom code, run the relevant verification, retain evidence, and then remove it.
501
+
502
+ Generators and agents may add markers only to seeded or adopter-owned source files. Never add
503
+ one to a managed file or edit managed output to resolve one. Complete the referenced work,
504
+ preserve relevant tests and evidence, then remove the marker. \`EBK-SECURITY\` and
505
+ \`EBK-BLOCKER\` prevent staging and production deploy or refresh; remove and unlock recovery
506
+ remain available. See \`docs/work-markers.md\` for the fixed vocabulary and syntax.
507
+ `,
508
+ 'CLAUDE.md': `# Claude workspace guidance
509
+
510
+ Read and follow \`AGENTS.md\`, including its generated-file ownership rules and work-marker
511
+ workflow. Before implementing or handing off work, run
512
+ \`pnpm nx run ${name}:work-markers\` and inspect every reported reference.
513
+ `,
514
+ 'eslint.config.mjs': `import eslint from '@eslint/js';
515
+ import globals from 'globals';
516
+ import tseslint from 'typescript-eslint';
517
+
518
+ export default tseslint.config(
519
+ { ignores: ['**/.next/**', '**/.open-next/**', '**/.sst/**', '**/dist/**', '**/node_modules/**'] },
520
+ eslint.configs.recommended,
521
+ ...tseslint.configs.recommended,
522
+ {
523
+ languageOptions: { globals: globals.node },
524
+ rules: { '@typescript-eslint/no-explicit-any': 'off' },
525
+ },
526
+ );
527
+ `,
528
+ 'README.md': `# ${name}
529
+
530
+ This workspace was created with Empire Builder Kit v1.
531
+
532
+ ## Start here
533
+
534
+ 1. Review \`docs/architecture.md\`.
535
+ 2. Review the generated Foundation before deploying it.
536
+ 3. Add product slices through the Empire Builder Kit Nx plugin.
537
+ 4. Review \`docs/repository-operating-policy.md\` and \`docs/git-workflow.md\` before enabling production.
538
+ 5. Read \`AGENTS.md\` and \`docs/work-markers.md\` before assigning generated customization work to agents.
539
+
540
+ For one-origin fleet development, first run \`pnpm nx run ${name}:dev-setup\`, inspect the
541
+ generated root certificate, then explicitly approve \`--trust\`. The supported v1 origin uses
542
+ the complete explicit-port HTTPS URL reported at startup; optional \`--portless\` setup is
543
+ experimental. Diagnose without changing trust or forwarding rules through
544
+ \`pnpm nx run ${name}:dev-doctor --stage=<personal-stage>\`.
545
+ \`dev-doctor\` trust and connectivity status requires a real exact-host TLS
546
+ handshake; optional portless access reports ready only after a live port-443 probe. Then complete
547
+ \`aws sso login --profile <bounded-development-profile>\`, then start selected Slices with
548
+ \`pnpm dev --slices=billing,accounts --profile=<bounded-development-profile>\`. Fleet reads
549
+ the region from that profile, propagates the AWS context to child Slices, and uses SST
550
+ \`mono\` mode so agents and non-cursor-addressable terminals receive ordinary logs.
551
+
552
+ Workspace creation only writes source files. It does not create or change cloud resources.
553
+ `,
554
+ 'docs/architecture.md': `# Workspace architecture
555
+
556
+ This workspace uses three public Empire Builder Kit packages:
557
+
558
+ - \`@empire-builder-kit/create\` creates the workspace source tree.
559
+ - \`@empire-builder-kit/nx\` owns generators and infrastructure lifecycle executors.
560
+ - \`@empire-builder-kit/runtime\` owns application-facing runtime contracts.
561
+
562
+ The Standard Foundation lives at \`packages/foundation\`. Product slices live beside it under
563
+ \`packages/<slice-name>\` and consume versioned Foundation contracts instead of importing
564
+ Foundation implementation files.
565
+
566
+ Canonical consumer stages use the matching \`development\`, \`staging\`, or \`production\`
567
+ Foundation. Personal and preview consumer stages may use only the \`development\` Foundation.
568
+ `,
569
+ 'docs/git-workflow.md': `# Protected Git workflow
570
+
571
+ Ordinary work starts from \`develop\` and returns through a reviewed pull request to
572
+ \`develop\`. A production promotion is exactly \`develop\` to \`main\`. After production,
573
+ review and merge the managed \`main\` to \`develop\` sync-back pull request before ordinary
574
+ work resumes.
575
+
576
+ Both protected branches require the stable \`policy\` and \`verify\` checks, one approving
577
+ review including an approval from the committed catch-all CODEOWNER and someone other than the
578
+ most recent pusher, dismissed stale reviews, resolved conversations, and merge commits. Replace
579
+ the placeholder in \`.github/CODEOWNERS\` with a GitHub user or team that has repository access
580
+ before bootstrap. An approval from \`github-actions[bot]\` cannot satisfy the independent
581
+ code-owner gate; the Actions repository setting is needed to create the managed sync-back pull
582
+ request, not to approve it.
583
+ Direct updates, force pushes, deletion, squash merging, and rebase merging are prohibited.
584
+ GitHub rulesets, environments, and AWS IAM are the authorization boundaries; local hooks are
585
+ only defense in depth.
586
+
587
+ The \`staging\` environment accepts only \`develop\`; \`production\` accepts only \`main\`.
588
+ The committed \`productionApproval.mode\` selects one of two production authorization
589
+ contracts:
590
+
591
+ - \`promotion-pr\` is compatible with private repositories on GitHub Team. The required
592
+ non-author CODEOWNER approval on the exact \`develop\` to \`main\` promotion pull request
593
+ is the production authorization. Merging that pull request starts production deployment;
594
+ there is no second EBK-managed environment approval or wait timer. The approver may also be
595
+ the human whose merge triggers deployment. Roll back through a reviewed revert on the
596
+ protected branches and let the resulting \`main\` push redeploy. A repository write actor who
597
+ can run Actions may also manually dispatch \`deploy\`, \`refresh\`, or \`unlock\` against the
598
+ already approved \`main\` tip without another human gate. That residual cannot introduce
599
+ unreviewed source; production \`remove\` remains separately variable-gated. Restrict write and
600
+ Actions access, audit dispatches, or select \`environment-reviewer\` when every production
601
+ action needs separate approval.
602
+ - \`environment-reviewer\` adds a separate production environment reviewer with self-review
603
+ prevention. A private repository needs GitHub Enterprise capability for this mode. Bootstrap
604
+ inspects that capability and fails closed on an unsupported plan; it never silently changes
605
+ the committed mode.
606
+
607
+ Both modes retain required checks, a non-author CODEOWNER approval, no ruleset bypass, the
608
+ literal production environment, and its branch-scoped credentials. Each environment owns its own
609
+ \`AWS_DEPLOY_ROLE_ARN\`, \`AWS_EXPECTED_ACCOUNT_ID\`, and \`AWS_REGION\`; production also
610
+ defines \`EBK_ALLOW_PRODUCTION_REMOVE\`, normally as \`false\`. Never add repository- or
611
+ organization-visible fallbacks for these protected values. Keep repository default workflow
612
+ permissions read-only; the generated jobs request only the additional permissions they need.
613
+
614
+ Run the GitHub bootstrap in report mode first. Source upgrades never alter remote GitHub
615
+ settings. Apply a reviewed report only after \`develop\` exists and the candidate's exact
616
+ \`policy\` and \`verify\` check contexts have run.
617
+
618
+ Set \`GH_REPO=<owner>/<repository>\` and a suitable \`GH_TOKEN\`, then run
619
+ \`pnpm nx run ${name}:github-bootstrap\`. Review the deterministic report before rerunning
620
+ with \`--apply\`. The command never creates branches, credentials, secrets, or AWS resources.
621
+ Supply \`--productionReviewerIds=<id,...>\` only when committed policy selects
622
+ \`environment-reviewer\`; it is rejected in \`promotion-pr\` mode.
623
+
624
+ Install the optional defense-in-depth pre-push hook explicitly with
625
+ \`pnpm nx run ${name}:install-git-hooks\`. It refuses to overwrite an unrelated existing hook.
626
+
627
+ The sync-back workflow uses only \`GITHUB_TOKEN\` and never pushes or auto-merges. GitHub may
628
+ place its pull-request workflows in an approval-required state. Approve them; if no run
629
+ materializes, close and reopen the pull request as a human and then approve it.
630
+ `,
631
+ 'docs/repository-operating-policy.md': `# Repository Operating Policy
632
+
633
+ \`ebk.repository-policy.json\` is the reviewed source of truth for production access,
634
+ production approval, per-Slice readiness, and the one shared production Foundation profile.
635
+ New workspaces start with production disabled and explicit Team-compatible
636
+ \`productionApproval.mode: "promotion-pr"\`. A missing approval mode or Slice entry fails
637
+ closed; no GitHub capability is allowed to choose a mode implicitly.
638
+
639
+ The named repository modes are source-writing presets, not runtime state. Report a change
640
+ first and inspect the complete current/next policy:
641
+
642
+ \`\`\`bash
643
+ pnpm nx g @empire-builder-kit/nx:repository-policy \\
644
+ --preset=controlled-production --slices=billing
645
+ \`\`\`
646
+
647
+ Apply the reviewed source change separately with \`--apply\`. This command never contacts AWS
648
+ or GitHub. Use \`--status=blocked|pilot|ready\` for explicit Slice transitions and
649
+ \`--profile=cost-optimized|standard\` for an explicit shared Foundation decision.
650
+ Use \`--productionApprovalMode=promotion-pr|environment-reviewer\` for an explicit approval
651
+ transition. This changes committed policy only; a separate bootstrap report/apply reconciles
652
+ remote GitHub settings.
653
+
654
+ Policies created before beta.7 do not contain \`productionApproval\`. After applying the
655
+ workspace source upgrade, report the explicit amendment with
656
+ \`pnpm nx g @empire-builder-kit/nx:repository-policy --productionApprovalMode=promotion-pr\`,
657
+ review it, and rerun with \`--apply\` before health or bootstrap. This narrowly accepts a policy
658
+ whose sole defect is the missing member; every other defect still fails closed. Validation
659
+ provides no legacy default. Use \`promotion-pr\` for a private GitHub Team repository. Use
660
+ \`environment-reviewer\` only when the repository plan can enforce a separate environment
661
+ reviewer.
662
+
663
+ - \`blocked\` prohibits production create, update, and migration.
664
+ - \`pilot\` permits protected evidence gathering without claiming normal latency or maturity.
665
+ - \`ready\` records an adopter decision after its evidence and review; it is not inferred.
666
+ - removal and unlock remain explicit protected recovery paths so disabled policy cannot strand
667
+ resources.
668
+
669
+ Protected production deploy and refresh validate the complete Foundation/Slice set before AWS
670
+ access. The Foundation is always ordered first. The set is derived from committed policy and
671
+ cannot be narrowed with an ad hoc project argument.
672
+
673
+ \`standard\` keeps Aurora warm at 2–16 ACU. \`cost-optimized\` is a prerelease candidate at
674
+ 0–4 ACU with a five-minute auto-pause and potential cold-resume latency. Both keep managed NAT,
675
+ 30-day backups, deletion protection, retained data, and the protected GitHub/OIDC/AWS path.
676
+ Changing policy affects only later lifecycle requests; it never deploys or removes resources.
677
+ Changing profile requires a separate reviewed Foundation deployment and retained operational
678
+ evidence.
679
+
680
+ \`EBK_PRODUCTION_FOUNDATION_PROFILE\` is reserved managed executor input. The lifecycle
681
+ executor derives it from validated committed policy immediately before SST and removes any
682
+ ambient shell value. Do not set it as a local, repository, organization, or environment
683
+ variable. Incubation keeps the last configured profile, or \`standard\` for legacy/default
684
+ policy, solely so protected remove and unlock can still render existing production resources;
685
+ production deploy remains disabled.
686
+ `,
687
+ 'docs/work-markers.md': `# Agent work markers
688
+
689
+ EBK work markers let deterministic generators identify places where an agent or human must
690
+ make a contextual product decision. They are searchable pointers to durable work, not a
691
+ replacement for the referenced issue or plan.
692
+
693
+ ## Syntax and vocabulary
694
+
695
+ Use one full-line source comment with a self-contained description. Examples include
696
+ \`// EBK-TODO(GH:123): Replace the sample authorization decision with the approved policy.\`
697
+ and
698
+ \`# EBK-ASSUMPTION(PLAN:1/operator-proof): Confirm this timeout during the operator pilot.\`
699
+
700
+ The fixed vocabulary is:
701
+
702
+ - \`EBK-TODO\` — known implementation work;
703
+ - \`EBK-ASSUMPTION\` — a decision that needs validation;
704
+ - \`EBK-QUESTION\` — an unresolved design or product question;
705
+ - \`EBK-SECURITY\` — security work that blocks protected promotion; and
706
+ - \`EBK-BLOCKER\` — any other work that blocks protected promotion.
707
+
708
+ Vocabulary names used in comment prose are inert. The scanner treats only a kind immediately
709
+ followed by \`(\` or \`:\` as a marker attempt, so malformed marker-shaped comments still fail
710
+ validation.
711
+
712
+ Until Work Orchestration imports these markers, references must be an existing GitHub issue
713
+ (\`GH:<positive-number>\`) or an append-only roadmap key
714
+ (\`PLAN:<phase>/<slug>\`). The description must remain useful without opening the reference.
715
+ The scanner validates syntax and ownership now; it intentionally does not claim that a
716
+ reference exists. Phase 2 owns reference import, retained old-to-new ID mapping, and later
717
+ orphan checks. Do not invent temporary Work Orchestration numbers.
718
+
719
+ ## Generator and agent workflow
720
+
721
+ Generators may emit a marker only into a \`seeded\` customization surface. They must never put
722
+ one in a \`managed\` file: managed output stays deterministic, and a needed change there is a
723
+ framework defect to report outside the generated file. Adopter-owned files may also contain
724
+ markers. Framework upgrades preserve seeded source and its unresolved markers.
725
+
726
+ Blueprint and repository update tooling may create a deterministic reconciliation marker in a
727
+ seeded or adopter-owned file when a preserved customization needs human or agent judgment. The
728
+ update must preserve the custom code and identify the changed contract in the description; a
729
+ successful update command alone does not resolve that marker. Reconcile the code, run the
730
+ relevant checks, retain evidence at the referenced work item, and only then remove the marker.
731
+ Automatic insertion requires an explicit publisher migration or other deterministic anchor;
732
+ the generic upgrade command does not guess where to edit preserved custom code.
733
+
734
+ At the start and handoff of agent work, run:
735
+
736
+ \`pnpm nx run ${name}:work-markers\`
737
+
738
+ The same ordinary validation runs through workspace health and CI. Complete the referenced
739
+ custom work and remove the marker; do not merely delete it while leaving the requirement
740
+ unresolved. Open \`TODO\`, \`ASSUMPTION\`, and \`QUESTION\` markers are reported but do not block
741
+ deployment by themselves. Open \`SECURITY\` and \`BLOCKER\` markers fail staging and production
742
+ deploy or refresh before AWS access. Protected remove and unlock remain usable for recovery.
743
+ There is no local or silent bypass in this prerelease contract; any future waiver mechanism
744
+ must be committed, reviewable, and auditable.
745
+ `,
746
+ 'ebk.repository-policy.json': json((0, index_js_3.createIncubationRepositoryPolicy)()),
747
+ 'nx.json': json({
748
+ $schema: './node_modules/nx/schemas/nx-schema.json',
749
+ defaultBase: 'develop',
750
+ namedInputs: {
751
+ default: ['{projectRoot}/**/*', 'sharedGlobals'],
752
+ production: [
753
+ 'default',
754
+ '!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
755
+ '!{projectRoot}/tsconfig.spec.json',
756
+ ],
757
+ sharedGlobals: [],
758
+ },
759
+ targetDefaults: {
760
+ build: {
761
+ cache: true,
762
+ dependsOn: ['^build'],
763
+ inputs: ['production', '^production'],
764
+ },
765
+ lint: {
766
+ cache: true,
767
+ inputs: ['default', '^production'],
768
+ },
769
+ test: {
770
+ cache: true,
771
+ dependsOn: ['^build'],
772
+ inputs: ['default', '^production'],
773
+ },
774
+ typecheck: {
775
+ cache: true,
776
+ dependsOn: ['^build'],
777
+ inputs: ['default', '^production'],
778
+ },
779
+ },
780
+ neverConnectToCloud: true,
781
+ }),
782
+ 'package.json': json({
783
+ name,
784
+ version: '0.0.0',
785
+ private: true,
786
+ packageManager: `pnpm@${TOOL_VERSIONS.pnpm}`,
787
+ engines: {
788
+ node: '>=22.12.0',
789
+ pnpm: '>=10.0.0',
790
+ },
791
+ scripts: {
792
+ build: `nx run-many -t build --exclude=${name}`,
793
+ format: 'nx format:write',
794
+ 'format:check': 'nx format:check',
795
+ graph: 'nx graph',
796
+ health: `nx run ${name}:workspace-health`,
797
+ lint: `nx run-many -t lint --exclude=${name}`,
798
+ test: `nx run-many -t test --exclude=${name}`,
799
+ typecheck: `nx run-many -t typecheck --exclude=${name}`,
800
+ verify: `nx run ${name}:workspace-health && nx run-many -t verify --exclude=${name}`,
801
+ dev: `nx run ${name}:dev`,
802
+ },
803
+ dependencies: {
804
+ '@empire-builder-kit/runtime': frameworkVersion,
805
+ },
806
+ devDependencies: {
807
+ '@eslint/js': TOOL_VERSIONS.eslintJs,
808
+ '@empire-builder-kit/nx': frameworkVersion,
809
+ '@types/node': TOOL_VERSIONS.nodeTypes,
810
+ eslint: TOOL_VERSIONS.eslint,
811
+ globals: TOOL_VERSIONS.globals,
812
+ nx: TOOL_VERSIONS.nx,
813
+ prettier: TOOL_VERSIONS.prettier,
814
+ sst: TOOL_VERSIONS.sst,
815
+ typescript: TOOL_VERSIONS.typescript,
816
+ 'typescript-eslint': TOOL_VERSIONS.typescriptEslint,
817
+ },
818
+ }),
819
+ 'project.json': json({
820
+ name,
821
+ $schema: './node_modules/nx/schemas/project-schema.json',
822
+ projectType: 'application',
823
+ tags: ['scope:workspace', 'layer:governance'],
824
+ targets: {
825
+ 'deployment-plan': {
826
+ executor: '@empire-builder-kit/nx:deployment-plan',
827
+ options: {
828
+ action: 'deploy',
829
+ stage: 'staging',
830
+ },
831
+ },
832
+ dev: {
833
+ continuous: true,
834
+ executor: '@empire-builder-kit/nx:fleet',
835
+ options: {
836
+ stage: 'personal',
837
+ },
838
+ },
839
+ 'dev-doctor': {
840
+ executor: '@empire-builder-kit/nx:dev-doctor',
841
+ options: {},
842
+ },
843
+ 'dev-setup': {
844
+ executor: '@empire-builder-kit/nx:dev-setup',
845
+ options: {},
846
+ },
847
+ 'github-bootstrap': {
848
+ executor: '@empire-builder-kit/nx:github-bootstrap',
849
+ options: {},
850
+ },
851
+ 'install-git-hooks': {
852
+ executor: 'nx:run-commands',
853
+ options: { command: 'node tools/install-ebk-hooks.mjs' },
854
+ },
855
+ 'work-markers': {
856
+ executor: '@empire-builder-kit/nx:work-markers',
857
+ options: {},
858
+ },
859
+ verify: {
860
+ executor: 'nx:run-commands',
861
+ options: { command: 'node tools/workspace-health.mjs' },
862
+ },
863
+ 'workspace-health': {
864
+ executor: 'nx:run-commands',
865
+ options: { command: 'node tools/workspace-health.mjs' },
866
+ },
867
+ },
868
+ }),
869
+ 'packages/foundation/README.md': `# Standard Foundation
870
+
871
+ This project owns the workspace-wide AWS platform for each canonical \`development\`,
872
+ \`staging\`, or \`production\` stage. It is a separate SST state boundary from product slices.
873
+ Never deploy it at a personal or preview stage; those consumers map to \`development\`.
874
+
875
+ It provides the v1 network, routing, container compute, Postgres, identity, events,
876
+ observability, configuration, and deployment capabilities. Product slices consume its
877
+ versioned contract; they do not import files from \`infra/\`.
878
+
879
+ Workspace creation does not deploy this project. Review \`docs/costs.md\`,
880
+ \`docs/security.md\`, and \`docs/runbooks.md\` before the first deployment.
881
+
882
+ Set \`EBK_ROOT_DOMAIN\` before deploying when Route 53 in the target account owns a public
883
+ domain. Without it, the shared Router uses its functional provider URL.
884
+ `,
885
+ 'packages/foundation/contracts/foundation.json': json({
886
+ contractVersion: '1.0.0',
887
+ capabilities: [
888
+ 'network',
889
+ 'routing',
890
+ 'container-compute',
891
+ 'postgres',
892
+ 'identity',
893
+ 'events',
894
+ 'observability',
895
+ 'configuration',
896
+ 'deployment',
897
+ ],
898
+ }),
899
+ 'packages/foundation/contracts/costs.json': json({
900
+ schemaVersion: 2,
901
+ aurora: {
902
+ engine: index_js_3.FOUNDATION_AURORA_ENGINE_FAMILY,
903
+ engineVersion: index_js_3.FOUNDATION_AURORA_ENGINE_VERSION,
904
+ },
905
+ productionProfiles: index_js_3.PRODUCTION_FOUNDATION_PROFILE_CONTRACTS,
906
+ productionProfileSource: 'ebk.repository-policy.json',
907
+ stages: {
908
+ development: {
909
+ aurora: 'scale-to-zero',
910
+ dataRetention: 'retained',
911
+ nat: 'ec2',
912
+ },
913
+ staging: {
914
+ aurora: 'scale-to-zero',
915
+ dataRetention: 'retained',
916
+ nat: 'ec2',
917
+ },
918
+ production: {
919
+ aurora: 'repository-policy',
920
+ dataRetention: 'retained-and-protected',
921
+ nat: 'managed',
922
+ },
923
+ },
924
+ usagePriced: [
925
+ 'cloudwatch',
926
+ 'cognito',
927
+ 'ecs-workloads',
928
+ 'eventbridge',
929
+ 'sns',
930
+ 'ssm',
931
+ ],
932
+ }),
933
+ 'packages/foundation/docs/costs.md': `# Foundation cost contract
934
+
935
+ The VPC NAT path and Aurora capacity are the main fixed-cost decisions.
936
+ The machine-readable stage inventory is \`../contracts/costs.json\`; keep this
937
+ seeded decision record aligned with organization-specific budgets and alerts.
938
+
939
+ - Development uses EC2 NAT, a development bastion, and Aurora scale-to-zero.
940
+ - Staging uses EC2 NAT and Aurora scale-to-zero by default.
941
+ - Production always uses managed NAT and retains protected data. Its explicit Repository
942
+ Operating Policy profile selects either the standard always-warm Aurora floor or the
943
+ prerelease cost-optimized auto-pause candidate.
944
+ - ECS, EventBridge, Cognito, SNS, SSM, and CloudWatch are primarily usage-priced; logs,
945
+ metrics, alarms, and retained data still require active cost review.
946
+
947
+ Run \`foundation:remove\` only for disposable stages. Production is retained and protected by
948
+ the SST configuration.
949
+ `,
950
+ 'packages/foundation/docs/runbooks.md': `# Foundation runbooks
951
+
952
+ ## Deployment
953
+
954
+ Use Nx for every supported lifecycle action:
955
+
956
+ - \`pnpm nx run foundation:deploy --stage=development\`
957
+ - \`pnpm nx run foundation:refresh --stage=development\`
958
+ - \`pnpm nx run foundation:unlock --stage=development\`
959
+ - \`pnpm nx run foundation:remove --stage=development\`
960
+
961
+ Do not invoke SST lifecycle commands directly. Production deployment belongs in the trusted
962
+ CI path.
963
+
964
+ Foundation stages are canonical. Run a Product Slice with \`--stage=<personal-name>\` to map
965
+ that consumer to the development Foundation. The slice preflight reports both stages; it
966
+ does not deploy a personal Foundation. Full mapped personal-stage deploys fail closed.
967
+
968
+ ## Database restore
969
+
970
+ Production Aurora retains 30 days of automated backups; non-production retains 7 days.
971
+ Restore into a new cluster, validate application migrations and data integrity, then cut over
972
+ consumers deliberately. Never expose the Foundation master credential to a product slice.
973
+ `,
974
+ 'packages/foundation/docs/security.md': `# Foundation security contract
975
+
976
+ - Production resources are retained and protected.
977
+ - The shared Aurora master credential is Foundation-internal.
978
+ - Product slices receive scoped logical-database credentials through the provisioning contract.
979
+ - Cognito owns authentication; product slices own authorization policy and app clients.
980
+ - Lambda and server-side web workloads use resource linking. Containers use the documented
981
+ stage-scoped SSM/secret path and least-privilege IAM.
982
+ - Public domains are configured explicitly through \`EBK_ROOT_DOMAIN\`; generated source never
983
+ contains credentials or customer domain assumptions.
984
+ `,
985
+ 'packages/foundation/ebk-globals.d.ts': `// SST and Pulumi inject these globals while evaluating sst.config.ts.
986
+ declare const sst: any;
987
+ declare const aws: any;
988
+ declare const $config: any;
989
+ declare const $app: {
990
+ name: string;
991
+ stage: string;
992
+ removal: 'remove' | 'retain' | 'retain-all';
993
+ protect: boolean;
994
+ };
995
+ `,
996
+ 'packages/foundation/infra/foundation.ts': `import {
997
+ FOUNDATION_SSM_SCHEMA_VERSION,
998
+ STANDARD_FOUNDATION_CAPABILITIES,
999
+ foundationSsmPath,
1000
+ type FoundationSsmKey,
1001
+ } from '@empire-builder-kit/nx/foundation';
1002
+ import {
1003
+ FOUNDATION_AURORA_ENGINE,
1004
+ FOUNDATION_AURORA_ENGINE_VERSION,
1005
+ productionFoundationProfileContract,
1006
+ } from '@empire-builder-kit/nx/repository-policy';
1007
+
1008
+ function publishFoundation(values: Record<FoundationSsmKey, any>) {
1009
+ return Object.fromEntries(
1010
+ Object.entries(values).map(([key, value]) => [
1011
+ key,
1012
+ new aws.ssm.Parameter('FoundationSsm_' + key, {
1013
+ name: foundationSsmPath($app.stage, key as FoundationSsmKey),
1014
+ type: 'String',
1015
+ value,
1016
+ }),
1017
+ ]),
1018
+ );
1019
+ }
1020
+
1021
+ export function foundation() {
1022
+ const isProduction = $app.stage === 'production';
1023
+ const isStaging = $app.stage === 'staging';
1024
+ const rootDomain = process.env.EBK_ROOT_DOMAIN?.trim();
1025
+ const productionConfiguration = isProduction
1026
+ ? productionFoundationProfileContract(
1027
+ process.env.EBK_PRODUCTION_FOUNDATION_PROFILE,
1028
+ )
1029
+ : undefined;
1030
+
1031
+ const vpc = new sst.aws.Vpc('FoundationVpc', {
1032
+ az: 2,
1033
+ bastion: $app.stage === 'development',
1034
+ nat: productionConfiguration?.contract.nat ?? 'ec2',
1035
+ });
1036
+ const cluster = new sst.aws.Cluster('FoundationCluster', { vpc });
1037
+ const router = new sst.aws.Router('FoundationRouter', {
1038
+ ...(rootDomain
1039
+ ? {
1040
+ domain: {
1041
+ name: rootDomain,
1042
+ aliases: ['*.' + rootDomain],
1043
+ dns: sst.aws.dns(),
1044
+ },
1045
+ }
1046
+ : {}),
1047
+ });
1048
+ const database = new sst.aws.Aurora('FoundationDatabase', {
1049
+ dataApi: true,
1050
+ engine: FOUNDATION_AURORA_ENGINE,
1051
+ version: FOUNDATION_AURORA_ENGINE_VERSION,
1052
+ scaling:
1053
+ productionConfiguration?.contract.aurora ??
1054
+ (isStaging
1055
+ ? { min: '0 ACU', max: '4 ACU', pauseAfter: '5 minutes' }
1056
+ : { min: '0 ACU', max: '2 ACU', pauseAfter: '5 minutes' }),
1057
+ transform: {
1058
+ cluster: {
1059
+ backupRetentionPeriod:
1060
+ productionConfiguration?.contract.backupRetentionDays ?? 7,
1061
+ deletionProtection:
1062
+ productionConfiguration?.contract.deletionProtection ?? false,
1063
+ },
1064
+ },
1065
+ vpc,
1066
+ });
1067
+ const userPool = new sst.aws.CognitoUserPool('FoundationIdentity', {
1068
+ usernames: ['email'],
1069
+ });
1070
+ const eventBus = new sst.aws.Bus('FoundationEventBus');
1071
+ const alertTopic = new sst.aws.SnsTopic('FoundationAlerts');
1072
+ const alertTopicPolicy = new aws.sns.TopicPolicy(
1073
+ 'FoundationAlertTopicPolicy',
1074
+ {
1075
+ arn: alertTopic.arn,
1076
+ policy: aws.iam.getPolicyDocumentOutput({
1077
+ statements: [
1078
+ {
1079
+ actions: ['sns:Publish'],
1080
+ principals: [
1081
+ { identifiers: ['events.rds.amazonaws.com'], type: 'Service' },
1082
+ ],
1083
+ resources: [alertTopic.arn],
1084
+ },
1085
+ ],
1086
+ }).json,
1087
+ },
1088
+ );
1089
+ const databaseInstanceId = database.nodes.instance.identifier;
1090
+ const databaseEvents = isProduction
1091
+ ? new aws.rds.EventSubscription(
1092
+ 'FoundationDatabaseEvents',
1093
+ {
1094
+ eventCategories: ['failure', 'notification', 'serverless'],
1095
+ snsTopic: alertTopic.arn,
1096
+ sourceIds: [databaseInstanceId],
1097
+ sourceType: 'db-instance',
1098
+ },
1099
+ { dependsOn: [alertTopicPolicy] },
1100
+ )
1101
+ : undefined;
1102
+ const dashboard = new aws.cloudwatch.Dashboard('FoundationDashboard', {
1103
+ dashboardName: 'ebk-' + $app.stage + '-foundation',
1104
+ dashboardBody: databaseInstanceId.apply((identifier: string) => JSON.stringify({
1105
+ widgets: [
1106
+ {
1107
+ type: 'text',
1108
+ x: 0,
1109
+ y: 0,
1110
+ width: 24,
1111
+ height: 3,
1112
+ properties: {
1113
+ markdown:
1114
+ '# Empire Builder Kit Foundation\\nStage: ' +
1115
+ $app.stage +
1116
+ '\\nProduction profile: ' +
1117
+ (productionConfiguration?.profile ?? 'not-applicable'),
1118
+ },
1119
+ },
1120
+ {
1121
+ type: 'metric',
1122
+ x: 0,
1123
+ y: 3,
1124
+ width: 12,
1125
+ height: 6,
1126
+ properties: {
1127
+ title: 'Aurora capacity and pause state',
1128
+ region: process.env.AWS_REGION ?? 'us-east-1',
1129
+ period: 60,
1130
+ stat: 'Minimum',
1131
+ metrics: [
1132
+ ['AWS/RDS', 'ServerlessDatabaseCapacity', 'DBInstanceIdentifier', identifier],
1133
+ ['.', 'ACUUtilization', '.', '.'],
1134
+ ],
1135
+ },
1136
+ },
1137
+ {
1138
+ type: 'metric',
1139
+ x: 12,
1140
+ y: 3,
1141
+ width: 12,
1142
+ height: 6,
1143
+ properties: {
1144
+ title: 'Aurora connections',
1145
+ region: process.env.AWS_REGION ?? 'us-east-1',
1146
+ period: 60,
1147
+ stat: 'Maximum',
1148
+ metrics: [
1149
+ ['AWS/RDS', 'DatabaseConnections', 'DBInstanceIdentifier', identifier],
1150
+ ],
1151
+ },
1152
+ },
1153
+ ],
1154
+ })),
1155
+ });
1156
+ const databaseCapacityAlarm = new aws.cloudwatch.MetricAlarm(
1157
+ 'FoundationDatabaseCapacityAlarm',
1158
+ {
1159
+ alarmActions: [alertTopic.arn],
1160
+ alarmDescription:
1161
+ 'Aurora Serverless capacity utilization stayed above 90 percent.',
1162
+ comparisonOperator: 'GreaterThanThreshold',
1163
+ dimensions: { DBInstanceIdentifier: databaseInstanceId },
1164
+ evaluationPeriods: 5,
1165
+ metricName: 'ACUUtilization',
1166
+ namespace: 'AWS/RDS',
1167
+ period: 60,
1168
+ statistic: 'Average',
1169
+ threshold: 90,
1170
+ treatMissingData: 'notBreaching',
1171
+ },
1172
+ );
1173
+
1174
+ if (new Set(['development', 'staging', 'production']).has($app.stage)) {
1175
+ new aws.cloudwatch.EventArchive('FoundationEventArchive', {
1176
+ eventSourceArn: eventBus.arn,
1177
+ retentionDays: 30,
1178
+ });
1179
+ }
1180
+
1181
+ publishFoundation({
1182
+ alertTopicArn: alertTopic.arn,
1183
+ capabilities: STANDARD_FOUNDATION_CAPABILITIES.join(','),
1184
+ clusterId: cluster.id,
1185
+ contractVersion: '1.0.0',
1186
+ databaseClusterArn: database.clusterArn,
1187
+ databaseClusterId: database.id,
1188
+ databaseMasterSecretArn: database.secretArn,
1189
+ eventBusArn: eventBus.arn,
1190
+ eventBusName: eventBus.name,
1191
+ hasPublicDomain: rootDomain ? 'true' : 'false',
1192
+ identityUserPoolArn: userPool.arn,
1193
+ identityUserPoolId: userPool.id,
1194
+ rootDomain: rootDomain ?? 'none',
1195
+ routerDistributionId: router.distributionID,
1196
+ routerUrl: router.url,
1197
+ schemaVersion: String(FOUNDATION_SSM_SCHEMA_VERSION),
1198
+ vpcId: vpc.id,
1199
+ });
1200
+
1201
+ return {
1202
+ alertTopicArn: alertTopic.arn,
1203
+ capabilities: STANDARD_FOUNDATION_CAPABILITIES,
1204
+ clusterId: cluster.id,
1205
+ contractVersion: '1.0.0',
1206
+ databaseCapacityAlarmName: databaseCapacityAlarm.name,
1207
+ dashboardName: dashboard.dashboardName,
1208
+ databaseClusterId: database.id,
1209
+ databaseEventSubscriptionId: databaseEvents?.id,
1210
+ eventBusName: eventBus.name,
1211
+ identityUserPoolId: userPool.id,
1212
+ name: 'foundation',
1213
+ productionFoundationProfile: productionConfiguration?.profile,
1214
+ rootDomain,
1215
+ routerUrl: router.url,
1216
+ vpcId: vpc.id,
1217
+ };
1218
+ }
1219
+ `,
1220
+ 'packages/foundation/infra/index.ts': `export { foundation as app } from './foundation.js';
1221
+ `,
1222
+ 'packages/foundation/package.json': json({
1223
+ name: `@${name}/foundation`,
1224
+ version: '0.0.0',
1225
+ private: true,
1226
+ }),
1227
+ 'packages/foundation/project.json': json({
1228
+ name: 'foundation',
1229
+ $schema: '../../node_modules/nx/schemas/project-schema.json',
1230
+ projectType: 'application',
1231
+ sourceRoot: 'packages/foundation/infra',
1232
+ tags: [
1233
+ 'scope:foundation',
1234
+ 'type:sst',
1235
+ 'layer:platform',
1236
+ 'visibility:internal',
1237
+ ],
1238
+ targets: {
1239
+ deploy: {
1240
+ executor: '@empire-builder-kit/nx:deploy',
1241
+ dependsOn: [
1242
+ 'verify',
1243
+ { target: 'profile-preflight', params: 'forward' },
1244
+ ],
1245
+ options: {
1246
+ cwd: 'packages/foundation',
1247
+ stage: 'development',
1248
+ stageMode: 'foundation',
1249
+ },
1250
+ },
1251
+ dev: {
1252
+ executor: '@empire-builder-kit/nx:dev',
1253
+ dependsOn: ['verify'],
1254
+ options: {
1255
+ cwd: 'packages/foundation',
1256
+ stage: 'development',
1257
+ stageMode: 'foundation',
1258
+ },
1259
+ },
1260
+ lint: {
1261
+ executor: 'nx:run-commands',
1262
+ options: {
1263
+ command: 'eslint .',
1264
+ cwd: 'packages/foundation',
1265
+ },
1266
+ },
1267
+ refresh: {
1268
+ executor: '@empire-builder-kit/nx:refresh',
1269
+ dependsOn: [{ target: 'profile-preflight', params: 'forward' }],
1270
+ options: {
1271
+ cwd: 'packages/foundation',
1272
+ stage: 'development',
1273
+ stageMode: 'foundation',
1274
+ },
1275
+ },
1276
+ remove: {
1277
+ executor: '@empire-builder-kit/nx:remove',
1278
+ dependsOn: [{ target: 'remove-preflight', params: 'forward' }],
1279
+ options: {
1280
+ cwd: 'packages/foundation',
1281
+ stage: 'development',
1282
+ stageMode: 'foundation',
1283
+ },
1284
+ },
1285
+ 'remove-preflight': {
1286
+ executor: '@empire-builder-kit/nx:foundation-remove-preflight',
1287
+ options: { stage: 'development' },
1288
+ },
1289
+ 'profile-preflight': {
1290
+ executor: '@empire-builder-kit/nx:foundation-profile-preflight',
1291
+ options: { stage: 'development' },
1292
+ },
1293
+ typecheck: {
1294
+ executor: 'nx:run-commands',
1295
+ options: {
1296
+ command: 'tsc --noEmit -p tsconfig.json',
1297
+ cwd: 'packages/foundation',
1298
+ },
1299
+ },
1300
+ unlock: {
1301
+ executor: '@empire-builder-kit/nx:unlock',
1302
+ options: {
1303
+ cwd: 'packages/foundation',
1304
+ stage: 'development',
1305
+ stageMode: 'foundation',
1306
+ },
1307
+ },
1308
+ verify: {
1309
+ executor: 'nx:run-commands',
1310
+ dependsOn: ['lint', 'typecheck'],
1311
+ options: {
1312
+ command: 'node scripts/verify-foundation.mjs',
1313
+ cwd: 'packages/foundation',
1314
+ },
1315
+ },
1316
+ },
1317
+ }),
1318
+ 'packages/foundation/scripts/verify-foundation.mjs': `import { readFileSync } from 'node:fs';
1319
+
1320
+ const descriptor = JSON.parse(
1321
+ readFileSync(new URL('../contracts/foundation.json', import.meta.url), 'utf8'),
1322
+ );
1323
+ const costs = JSON.parse(
1324
+ readFileSync(new URL('../contracts/costs.json', import.meta.url), 'utf8'),
1325
+ );
1326
+ const expectedCapabilities = [
1327
+ 'network',
1328
+ 'routing',
1329
+ 'container-compute',
1330
+ 'postgres',
1331
+ 'identity',
1332
+ 'events',
1333
+ 'observability',
1334
+ 'configuration',
1335
+ 'deployment',
1336
+ ];
1337
+
1338
+ if (descriptor.contractVersion !== '1.0.0') {
1339
+ throw new Error('Foundation contractVersion must be 1.0.0.');
1340
+ }
1341
+ if (JSON.stringify(descriptor.capabilities) !== JSON.stringify(expectedCapabilities)) {
1342
+ throw new Error('Foundation capability descriptor does not match the v1 standard.');
1343
+ }
1344
+ if (
1345
+ costs.schemaVersion !== 2 ||
1346
+ costs.aurora?.engineVersion !== '${index_js_3.FOUNDATION_AURORA_ENGINE_VERSION}' ||
1347
+ costs.productionProfileSource !== 'ebk.repository-policy.json' ||
1348
+ costs.productionProfiles?.standard?.aurora?.min !== '2 ACU' ||
1349
+ costs.productionProfiles?.['cost-optimized']?.aurora?.min !== '0 ACU' ||
1350
+ !costs.stages?.development ||
1351
+ !costs.stages?.staging ||
1352
+ costs.stages?.production?.dataRetention !== 'retained-and-protected'
1353
+ ) {
1354
+ throw new Error('Foundation cost manifest does not match the v1 stage contract.');
1355
+ }
1356
+
1357
+ process.stdout.write('Foundation source contract verified.\\n');
1358
+ `,
1359
+ 'packages/foundation/sst-env.d.ts': `// Bootstrap placeholder. SST overwrites this file after install, dev, or deploy.
1360
+ export {};
1361
+ `,
1362
+ 'packages/foundation/sst.config.ts': `// SST/Pulumi ambient globals are declared in ebk-globals.d.ts.
1363
+ export default $config({
1364
+ app(input: { stage?: string } | undefined) {
1365
+ const stage = input?.stage ?? 'development';
1366
+ if (!['development', 'staging', 'production'].includes(stage)) {
1367
+ throw new Error(
1368
+ 'Foundation stage ' + stage + ' is not canonical. Use development, staging, or production.',
1369
+ );
1370
+ }
1371
+ const production = stage === 'production';
1372
+ return {
1373
+ name: 'foundation',
1374
+ removal: production ? 'retain' : 'remove',
1375
+ protect: production,
1376
+ home: 'aws',
1377
+ providers: {
1378
+ aws: {
1379
+ region: process.env.AWS_REGION ?? 'us-east-1',
1380
+ },
1381
+ },
1382
+ };
1383
+ },
1384
+ async run() {
1385
+ const { app } = await import('./infra/index.js');
1386
+ return app();
1387
+ },
1388
+ });
1389
+ `,
1390
+ 'packages/foundation/tsconfig.json': json({
1391
+ extends: '../../tsconfig.base.json',
1392
+ compilerOptions: { types: ['node'] },
1393
+ include: [
1394
+ 'infra/**/*.ts',
1395
+ 'sst.config.ts',
1396
+ 'sst-env.d.ts',
1397
+ 'ebk-globals.d.ts',
1398
+ ],
1399
+ exclude: ['.sst', 'dist', 'node_modules'],
1400
+ }),
1401
+ 'pnpm-workspace.yaml': `packages:
1402
+ - 'packages/*'
1403
+ - 'packages/*/apps/*'
1404
+ - 'packages/*/jobs/*'
1405
+ - 'packages/*/libs/*'
1406
+ - 'packages/*/services/*'
1407
+ `,
1408
+ 'tsconfig.base.json': json({
1409
+ compilerOptions: {
1410
+ composite: true,
1411
+ declarationMap: true,
1412
+ forceConsistentCasingInFileNames: true,
1413
+ isolatedModules: true,
1414
+ lib: ['es2022'],
1415
+ module: 'nodenext',
1416
+ moduleResolution: 'nodenext',
1417
+ noEmitOnError: true,
1418
+ noFallthroughCasesInSwitch: true,
1419
+ noImplicitOverride: true,
1420
+ noImplicitReturns: true,
1421
+ noUnusedLocals: true,
1422
+ skipLibCheck: true,
1423
+ strict: true,
1424
+ target: 'es2022',
1425
+ },
1426
+ }),
1427
+ 'tsconfig.json': json({
1428
+ extends: './tsconfig.base.json',
1429
+ files: [],
1430
+ references: [],
1431
+ }),
1432
+ 'tools/ebk-git-policy.mjs': `import { evaluateGitPolicy } from '@empire-builder-kit/nx/git-policy';
1433
+ import { spawnSync } from 'node:child_process';
1434
+
1435
+ const git = (...args) => {
1436
+ const result = spawnSync('git', args, { encoding: 'utf8', shell: false });
1437
+ if (result.status !== 0) {
1438
+ throw new Error('git ' + args.join(' ') + ' failed: ' + (result.stderr || result.stdout).trim());
1439
+ }
1440
+ return result.stdout.trim();
1441
+ };
1442
+ const isAncestor = (ancestor, descendant) =>
1443
+ spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
1444
+ encoding: 'utf8',
1445
+ shell: false,
1446
+ }).status === 0;
1447
+
1448
+ const eventName = process.env.GITHUB_EVENT_NAME;
1449
+ if (!['pull_request', 'push', 'workflow_dispatch'].includes(eventName)) {
1450
+ throw new Error('Unsupported or missing GITHUB_EVENT_NAME; Git policy fails closed.');
1451
+ }
1452
+
1453
+ const request = { eventName, ref: process.env.GITHUB_REF };
1454
+ if (eventName === 'pull_request' || eventName === 'push') {
1455
+ git(
1456
+ 'fetch',
1457
+ '--no-tags',
1458
+ 'origin',
1459
+ 'main:refs/remotes/origin/main',
1460
+ 'develop:refs/remotes/origin/develop',
1461
+ );
1462
+ }
1463
+ if (eventName === 'pull_request') {
1464
+ const baseRef = process.env.GITHUB_BASE_REF;
1465
+ const headRef = process.env.GITHUB_HEAD_REF;
1466
+ if (!baseRef || !headRef) {
1467
+ throw new Error('Pull-request branch metadata is missing.');
1468
+ }
1469
+ Object.assign(request, {
1470
+ baseIsAncestorOfHead: isAncestor('refs/remotes/origin/' + baseRef, 'HEAD'),
1471
+ baseRef,
1472
+ headRef,
1473
+ mainIsAncestorOfDevelop: isAncestor(
1474
+ 'refs/remotes/origin/main',
1475
+ 'refs/remotes/origin/develop',
1476
+ ),
1477
+ });
1478
+ } else if (eventName === 'push') {
1479
+ Object.assign(request, {
1480
+ baseIsAncestorOfHead: isAncestor('refs/remotes/origin/develop', 'HEAD'),
1481
+ mainIsAncestorOfDevelop: isAncestor(
1482
+ 'refs/remotes/origin/main',
1483
+ 'refs/remotes/origin/develop',
1484
+ ),
1485
+ });
1486
+ }
1487
+
1488
+ const decision = evaluateGitPolicy(request);
1489
+ process.stdout.write(JSON.stringify(decision, null, 2) + '\\n');
1490
+ if (!decision.allowed) process.exitCode = 1;
1491
+ `,
1492
+ 'tools/workspace-health.mjs': `import { validateRepositoryOperatingPolicy } from '@empire-builder-kit/nx/repository-policy';
1493
+ import { assertWorkMarkerReport, formatWorkMarkerReport, scanWorkMarkers } from '@empire-builder-kit/nx/work-markers';
1494
+ import { createHash } from 'node:crypto';
1495
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
1496
+ import { join } from 'node:path';
1497
+
1498
+ const parseJson = (contents, label) => {
1499
+ try {
1500
+ return JSON.parse(contents);
1501
+ } catch (error) {
1502
+ throw new Error(label + ' is not valid JSON: ' + (error instanceof Error ? error.message : String(error)));
1503
+ }
1504
+ };
1505
+ const readJson = (path) => parseJson(readFileSync(path, 'utf8'), path);
1506
+ const sha256 = (contents) => createHash('sha256').update(contents, 'utf8').digest('hex');
1507
+ const stableValue = (value) => {
1508
+ if (Array.isArray(value)) return value.map(stableValue);
1509
+ if (!value || typeof value !== 'object') return value;
1510
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
1511
+ };
1512
+ const pointerValue = (document, pointer) => {
1513
+ let current = document;
1514
+ for (const segment of pointer.slice(1).split('/').map((part) => part.replaceAll('~1', '/').replaceAll('~0', '~'))) {
1515
+ if (!current || typeof current !== 'object' || !Object.hasOwn(current, segment)) {
1516
+ return { exists: false };
1517
+ }
1518
+ current = current[segment];
1519
+ }
1520
+ return { exists: true, value: current };
1521
+ };
1522
+ const ownedSha256 = (contents, entry) => {
1523
+ if (!entry.managedJsonPointers) return sha256(contents);
1524
+ const document = parseJson(contents, 'Managed JSON file ' + entry.path);
1525
+ const projection = [...entry.managedJsonPointers].sort().map((pointer) => {
1526
+ const resolved = pointerValue(document, pointer);
1527
+ return { exists: resolved.exists, pointer, ...(resolved.exists ? { value: resolved.value } : {}) };
1528
+ });
1529
+ return sha256(JSON.stringify(stableValue(projection)));
1530
+ };
1531
+ const validateOwnership = (root, record, label) => {
1532
+ for (const entry of record.paths ?? []) {
1533
+ if (entry.mode !== 'managed') continue;
1534
+ const path = join(root, entry.path);
1535
+ if (!existsSync(path)) throw new Error(label + ' managed path is missing: ' + entry.path);
1536
+ if (ownedSha256(readFileSync(path, 'utf8'), entry) !== entry.sha256) {
1537
+ throw new Error(label + ' managed path has drifted: ' + entry.path);
1538
+ }
1539
+ }
1540
+ };
1541
+
1542
+ const workspace = readJson('.ebk/ownership.json');
1543
+ const foundation = readJson('packages/foundation/.ebk/ownership.json');
1544
+ if (
1545
+ workspace.schemaVersion !== ${index_js_1.WORKSPACE_OWNERSHIP_SCHEMA_VERSION} ||
1546
+ workspace.kind !== 'workspace' ||
1547
+ foundation.schemaVersion !== ${index_js_1.FOUNDATION_OWNERSHIP_SCHEMA_VERSION} ||
1548
+ foundation.kind !== 'foundation'
1549
+ ) {
1550
+ throw new Error('EBK workspace or Foundation ownership metadata is incompatible.');
1551
+ }
1552
+ validateOwnership('.', workspace, 'Workspace');
1553
+ validateOwnership('packages/foundation', foundation, 'Foundation');
1554
+
1555
+ const policy = validateRepositoryOperatingPolicy(readJson('ebk.repository-policy.json'));
1556
+ if (!policy.valid) {
1557
+ throw new Error(
1558
+ 'Repository Operating Policy is invalid:\\n' +
1559
+ policy.issues.map(({ message, path }) => '- ' + path + ': ' + message).join('\\n'),
1560
+ );
1561
+ }
1562
+
1563
+ for (const name of readdirSync('packages')) {
1564
+ if (name === 'foundation') continue;
1565
+ const ownershipPath = join('packages', name, '.ebk/ownership.json');
1566
+ if (!existsSync(ownershipPath)) continue;
1567
+ const ownership = readJson(ownershipPath);
1568
+ if (ownership.schemaVersion !== ${index_js_1.GENERATED_OWNERSHIP_SCHEMA_VERSION}) {
1569
+ throw new Error('Product Slice ' + name + ' ownership metadata is incompatible.');
1570
+ }
1571
+ validateOwnership(join('packages', name), ownership, 'Product Slice ' + name);
1572
+ }
1573
+ const markerReport = scanWorkMarkers(process.cwd());
1574
+ process.stdout.write(formatWorkMarkerReport(markerReport) + '\\n');
1575
+ assertWorkMarkerReport(markerReport);
1576
+ process.stdout.write('Empire Builder Kit workspace source health is green.\\n');
1577
+ `,
1578
+ };
1579
+ files[FOUNDATION_OWNERSHIP_PATH] = json(createFoundationOwnership(files, frameworkVersion));
1580
+ files[WORKSPACE_OWNERSHIP_PATH] = json(createWorkspaceOwnership(files, frameworkVersion));
1581
+ return files;
1582
+ }
1583
+ function renderWorkspaceRootFilePlan(options) {
1584
+ const workspaceFiles = renderWorkspaceFiles(options);
1585
+ const files = Object.fromEntries(Object.entries(workspaceFiles).filter(([path]) => !path.startsWith(FOUNDATION_ROOT)));
1586
+ return {
1587
+ files,
1588
+ ownership: JSON.parse(workspaceFiles[WORKSPACE_OWNERSHIP_PATH]),
1589
+ };
1590
+ }
1591
+ function renderFoundationFilePlan(options) {
1592
+ const workspaceFiles = renderWorkspaceFiles(options);
1593
+ const files = Object.fromEntries(Object.entries(workspaceFiles)
1594
+ .filter(([path]) => path.startsWith(FOUNDATION_ROOT))
1595
+ .map(([path, contents]) => [
1596
+ path.slice(FOUNDATION_ROOT.length),
1597
+ contents,
1598
+ ]));
1599
+ return {
1600
+ files,
1601
+ ownership: JSON.parse(workspaceFiles[FOUNDATION_OWNERSHIP_PATH]),
1602
+ };
1603
+ }
1604
+ exports.WORKSPACE_PACKAGE_MANAGER_VERSION = TOOL_VERSIONS.pnpm;
1605
+ //# sourceMappingURL=workspace-files.js.map