@empire-builder-kit/nx 1.0.0-rc.31 → 1.0.0-rc.32

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 (115) hide show
  1. package/package.json +7 -3
  2. package/src/aws/identity.js +1 -1
  3. package/src/aws/identity.js.map +1 -1
  4. package/src/aws-authority/stage-authority.d.ts +2 -0
  5. package/src/aws-authority/stage-authority.js +152 -20
  6. package/src/aws-authority/stage-authority.js.map +1 -1
  7. package/src/cli/main.d.ts +2 -0
  8. package/src/cli/main.js +11 -0
  9. package/src/cli/main.js.map +1 -1
  10. package/src/developer-agent/workspace-inspection.js +11 -2
  11. package/src/developer-agent/workspace-inspection.js.map +1 -1
  12. package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
  13. package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
  14. package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
  15. package/src/executors/github-bootstrap/schema.json +7 -1
  16. package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +2 -0
  17. package/src/executors/infrastructure-plan/infrastructure-plan.js +18 -1
  18. package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
  19. package/src/executors/local-deploy/local-deploy.d.ts +2 -0
  20. package/src/executors/local-deploy/local-deploy.js +17 -0
  21. package/src/executors/local-deploy/local-deploy.js.map +1 -1
  22. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +2 -0
  23. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +2 -0
  24. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
  25. package/src/executors/secret-initialize/secret-initialize.d.ts +2 -0
  26. package/src/executors/secret-initialize/secret-initialize.js +14 -0
  27. package/src/executors/secret-initialize/secret-initialize.js.map +1 -1
  28. package/src/executors/secret-metadata/secret-metadata.d.ts +2 -0
  29. package/src/executors/secret-metadata/secret-metadata.js +14 -0
  30. package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
  31. package/src/executors/sst-lifecycle/run-sst-command.d.ts +2 -0
  32. package/src/executors/sst-lifecycle/run-sst-command.js +17 -2
  33. package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
  34. package/src/foundation/provision-logical-database.js +2 -0
  35. package/src/foundation/provision-logical-database.js.map +1 -1
  36. package/src/generators/preset/generator.js +6 -1
  37. package/src/generators/preset/generator.js.map +1 -1
  38. package/src/generators/preset/schema.d.ts +3 -0
  39. package/src/generators/preset/schema.json +5 -5
  40. package/src/generators/preset/workspace-files-performance-load.js +21 -12
  41. package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
  42. package/src/generators/preset/workspace-files-stage-authority.d.ts +2 -0
  43. package/src/generators/preset/workspace-files-stage-authority.js +264 -60
  44. package/src/generators/preset/workspace-files-stage-authority.js.map +1 -1
  45. package/src/generators/preset/workspace-files-thin-deploy.d.ts +6 -1
  46. package/src/generators/preset/workspace-files-thin-deploy.js +102 -36
  47. package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -1
  48. package/src/generators/preset/workspace-files-thin-docs.js +26 -10
  49. package/src/generators/preset/workspace-files-thin-docs.js.map +1 -1
  50. package/src/generators/preset/workspace-files.d.ts +1 -0
  51. package/src/generators/preset/workspace-files.js +111 -493
  52. package/src/generators/preset/workspace-files.js.map +1 -1
  53. package/src/generators/repository-policy/repository-policy.js +149 -19
  54. package/src/generators/repository-policy/repository-policy.js.map +1 -1
  55. package/src/generators/repository-policy/schema.d.ts +2 -0
  56. package/src/generators/repository-policy/schema.json +11 -1
  57. package/src/generators/repository-policy/tree-policy.d.ts +3 -0
  58. package/src/generators/repository-policy/tree-policy.js +25 -0
  59. package/src/generators/repository-policy/tree-policy.js.map +1 -0
  60. package/src/generators/slice/built-in-plan-async-work-files.js +41 -0
  61. package/src/generators/slice/built-in-plan-async-work-files.js.map +1 -1
  62. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +2 -0
  63. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
  64. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +19 -0
  65. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +1 -1
  66. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +21 -3
  67. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +1 -1
  68. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +20 -1
  69. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +1 -1
  70. package/src/generators/slice/built-in-plan-operations-files.js +22 -2
  71. package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
  72. package/src/generators/slice/built-in-plan-performance-files.js +60 -0
  73. package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
  74. package/src/generators/slice/built-in-plan-secret-runtime-files.js +13 -0
  75. package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -1
  76. package/src/generators/slice/built-in-plan-testing-files.js +20 -0
  77. package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
  78. package/src/generators/slice/slice.js +3 -3
  79. package/src/generators/slice/slice.js.map +1 -1
  80. package/src/generators/upgrade/upgrade.js +25 -28
  81. package/src/generators/upgrade/upgrade.js.map +1 -1
  82. package/src/generators/workload/performance-registration.js +4 -8
  83. package/src/generators/workload/performance-registration.js.map +1 -1
  84. package/src/git-policy/github-bootstrap.d.ts +10 -2
  85. package/src/git-policy/github-bootstrap.js +109 -13
  86. package/src/git-policy/github-bootstrap.js.map +1 -1
  87. package/src/index.d.ts +1 -0
  88. package/src/index.js +1 -0
  89. package/src/index.js.map +1 -1
  90. package/src/local-dev/tls.js +5 -1
  91. package/src/local-dev/tls.js.map +1 -1
  92. package/src/repository-policy/policy.d.ts +16 -1
  93. package/src/repository-policy/policy.js +226 -12
  94. package/src/repository-policy/policy.js.map +1 -1
  95. package/src/schemas/repository-policy-v1.json +46 -2
  96. package/src/secrets/adapters.d.ts +2 -1
  97. package/src/secrets/adapters.js +12 -2
  98. package/src/secrets/adapters.js.map +1 -1
  99. package/src/supply-chain/generated-container-release-command.js +41 -18
  100. package/src/supply-chain/generated-container-release-command.js.map +1 -1
  101. package/src/workspace-deployment/identity.d.ts +3 -0
  102. package/src/workspace-deployment/identity.js +22 -0
  103. package/src/workspace-deployment/identity.js.map +1 -0
  104. package/src/workspace-deployment/index.d.ts +3 -0
  105. package/src/workspace-deployment/index.js +7 -0
  106. package/src/workspace-deployment/index.js.map +1 -0
  107. package/src/workspace-deployment/migration-evidence.d.ts +29 -0
  108. package/src/workspace-deployment/migration-evidence.js +83 -0
  109. package/src/workspace-deployment/migration-evidence.js.map +1 -0
  110. package/src/workspace-deployment/migration.d.ts +27 -0
  111. package/src/workspace-deployment/migration.js +109 -0
  112. package/src/workspace-deployment/migration.js.map +1 -0
  113. package/src/workspace-deployment/ownership.d.ts +81 -0
  114. package/src/workspace-deployment/ownership.js +381 -0
  115. package/src/workspace-deployment/ownership.js.map +1 -0
@@ -18,18 +18,36 @@ pnpm nx run <workspace>:stage-authority-render -- \\
18
18
  \`\`\`
19
19
 
20
20
  Locally admitted stages require at least one reviewed
21
- \`--aws-principal-arn\`. Stages that require protected GitHub deployment require
22
- one exact \`--github-repository\` environment trust and reject local AWS
23
- principals. The \`starter\` profile may add that exact GitHub trust to either of
24
- its locally admitted stages because its GitHub paved road is optional; locally
25
- admitted stages in isolated profiles reject GitHub trust.
21
+ \`--aws-principal-arn\`. Stages that require only protected GitHub deployment
22
+ require one exact \`--github-repository\` environment trust and reject local AWS
23
+ principals. Production-isolated staging requires both the local principal and
24
+ the protected GitHub environment trust: local staging remains supported, while
25
+ the protected \`develop\` deployment produces the receipt consumed by production
26
+ promotion. The \`starter\` profile may add that exact GitHub trust to either of
27
+ its locally admitted stages because its GitHub paved road is optional. Fully
28
+ isolated development rejects GitHub trust.
26
29
 
27
- GitHub trust is scoped to the repository's exact stage environment.
28
- Repositories that use GitHub's immutable owner/repository-ID OIDC subject
29
- customization must also supply both \`--github-owner-id=<numeric-id>\` and
30
- \`--github-repository-id=<numeric-id>\`; supplying only one ID or mixing an ID
31
- with a name-only subject is rejected. Apply the rendered template as the named
32
- stack shown in the manifest, then read back the deployed template and policies
30
+ GitHub trust is scoped to the repository's exact stage environment and always
31
+ uses GitHub's immutable owner/repository-ID OIDC subject customization. Every
32
+ \`--github-repository\` must be accompanied by both
33
+ \`--github-owner-id=<numeric-id>\` and
34
+ \`--github-repository-id=<numeric-id>\`; a partial or name-only subject is
35
+ rejected. Obtain the canonical slug and IDs from the GitHub bootstrap report,
36
+ but do not enable immutable subjects yet for an existing repository. First apply the rendered
37
+ \`ownership-template.json\` as the owner-claim stack named in
38
+ \`ownership-manifest.json\`. CloudFormation creates the retained
39
+ \`/ebk/<stage>/workspace-owner\` record atomically. Then apply
40
+ \`template.json\` as the authority stack shown in \`manifest.json\`; its
41
+ CloudFormation Rule reads the exact owner before any IAM change. A second
42
+ workspace therefore cannot address the same account, Region, and stage tuple,
43
+ while a clone retaining the committed deployment ID reuses both stacks.
44
+ Read back and simulate the effective immutable-subject trust, then apply the
45
+ GitHub bootstrap change that configures the repository's default immutable OIDC
46
+ subject. This AWS-trust-first order prevents an existing protected deployment
47
+ from being stranded between the subject-format and trust-policy changes.
48
+ Existing deployed workspaces must first commit the explicit migration evidence
49
+ described in \`docs/workspace-deployment-ownership.md\`; never claim an existing
50
+ tuple by editing policy directly. Read back the deployed template and policies
33
51
  and compare their canonical digests before any application mutation. Simulate
34
52
  representative permitted lifecycle calls and denied account, trust,
35
53
  boundary-removal, cross-stage, cross-account, and cross-Region calls against
@@ -90,14 +108,28 @@ The role may inspect secret metadata. SST 4.17.1's generated Foundation Aurora
90
108
  credential is the one value exception: the temporary deployer may put and read
91
109
  that exact stage/account/Region-bound secret version so the pinned provider can
92
110
  create and reconcile it. The reserved name must also carry the exact
93
- \`ebk:stage\` tag. Operator-secret and all other secret values remain denied.
94
- Because \`CreateSecret\` and
95
- \`UpdateSecret\` can optionally carry a value, IAM cannot distinguish a
96
- metadata-only call; reviewed generated source and CloudTrail are the honest
97
- controls for those two provider APIs.
111
+ \`ebk:stage\` tag. \`GetSecretValue\` and \`PutSecretValue\` remain denied for
112
+ operator-secret and all other Slice containers.
113
+ Generated source creates Slice containers inside the deterministic
114
+ \`ebk/<stage>/*\` namespace. The temporary deployer's \`UpdateSecret\` authority
115
+ is denied outside that namespace and the exact generated Foundation
116
+ provider-secret identity. Imported, externally owned or shared, unmanaged,
117
+ other-stage, cross-account, and cross-Region containers remain outside this
118
+ update authority. The namespace is reserved for EBK-owned, stage-exclusive
119
+ containers, including explicitly approved cross-Slice use; imported or
120
+ externally owned containers must use a name outside it. The namespace contract
121
+ covers generated database, machine-client, generated-value, and EBK-owned
122
+ operator containers without adding a new IAM exception for every provider
123
+ resource type. It does not admit
124
+ \`GetSecretValue\` or \`PutSecretValue\` for Slice containers. Because
125
+ \`CreateSecret\` and \`UpdateSecret\` can optionally carry a value, IAM cannot
126
+ distinguish a metadata-only call; reviewed generated source, temporary
127
+ credentials, the stage namespace, and CloudTrail are the honest controls for
128
+ those provider APIs.
98
129
  SSM value reads are limited to the stage's Foundation configuration, SST 4.17.1
99
- bootstrap record, stage-qualified SST passphrases, and SST's VPC private-key
100
- reference. Foundation removal enumerates Product Slice registration names and
130
+ bootstrap record, stage-qualified SST passphrases, SST's VPC private-key
131
+ reference, and the exact retained workspace-owner record. The deployment role
132
+ can read but cannot change or delete that owner. Foundation removal enumerates Product Slice registration names and
101
133
  metadata without reading their values. Recursive and historical SSM value reads
102
134
  are denied. These controls,
103
135
  the exact trust, temporary credentials, requested Region, known account/stage
@@ -116,8 +148,67 @@ bootstrap state and assets. Tag and name denies are defense in depth, not a hard
116
148
  IAM boundary between same-account deployment roles. Choose
117
149
  \`production-isolated\` when production requires an AWS-enforced deployment
118
150
  blast-radius boundary from development and staging.
151
+ `,
152
+ 'docs/workspace-deployment-ownership.md': `# Workspace deployment ownership
153
+
154
+ Every generated workspace commits one stable, random \`workspaceDeploymentId\`
155
+ and \`workspaceDeploymentProvenance: { kind: "fresh", schemaVersion: 1 }\`.
156
+ That identity belongs to the workspace, not to a checkout: clones with the same
157
+ committed policy are the same deployment owner.
158
+
159
+ Before authority or SST lifecycle work, each active account/Region/canonical
160
+ stage tuple must contain exactly one retained String parameter at
161
+ \`/ebk/<stage>/workspace-owner\`. The value binds the tuple to the generated
162
+ deployment ID; the current workspace name remains diagnostic metadata and may
163
+ be renamed without transferring authority. The ownership claim is deliberately
164
+ exclusive and retained. Two workspaces do not cohabit one tuple, and removing
165
+ an application stack does not release the claim.
166
+
167
+ Render stage authority with \`pnpm nx run ${options.name}:stage-authority-render --
168
+ --stage=<stage> ...\`. Apply the emitted \`ownership-template.json\` as the exact
169
+ claim stack in \`ownership-manifest.json\` before applying the authority template.
170
+ CloudFormation's fixed-name SSM create is the atomic claim. The authority
171
+ template accepts only that exact parameter path and fails its Rule before any
172
+ IAM change unless its value is the exact committed owner. The deployment role
173
+ may read the owner but is explicitly denied every owner mutation.
174
+
175
+ For a read-only report, run
176
+ \`node tools/stage-authority-render.mjs --stage=<stage> --ownership-doctor=true
177
+ --profile=<temporary-profile>\`. It reports \`owned\`, \`unclaimed\`, or \`conflict\`;
178
+ a conflict identifies the committed owner without changing AWS.
179
+
180
+ ## Existing prerelease deployments
181
+
182
+ An existing policy without the required deployment identity provenance is fail
183
+ closed. Inventory
184
+ each configured tuple read-only and commit bounded evidence under
185
+ \`docs/adoption/\`. The evidence must bind the exact prior policy SHA-256,
186
+ workspace name, proposed \`ebkws-<32 lowercase hex>\` ID, and every configured
187
+ tuple. The ID, exact Region, and every active-stage account must be committed in
188
+ the same migration change; an empty interim topology cannot reserve future
189
+ coordinates. Each tuple declares \`empty\` with no resource digests or
190
+ \`existing-workspace\` with sorted SHA-256 digests of its bounded resource
191
+ identity evidence. Then run the repository-policy generator with both
192
+ \`--workspaceDeploymentId\` and
193
+ \`--workspaceDeploymentMigrationEvidence\`. Report-only mode shows the exact
194
+ result with \`workspaceDeploymentProvenance.kind: "migrated"\`; \`--apply\`
195
+ commits it. Every later policy read revalidates the retained evidence bytes,
196
+ workspace identity, the syntactically valid diagnostic name recorded at migration,
197
+ and exact active stage/account/Region tuple set. The current package name may be
198
+ renamed without renewing ownership evidence because authority is bound to the
199
+ immutable deployment ID rather than that diagnostic label.
200
+ A profile, account, or Region change for a migrated workspace requires a new
201
+ versioned evidence path whose previous-policy digest binds the current policy;
202
+ unrelated policy changes retain the existing binding.
203
+
204
+ This migration is an explicit ownership assertion, not automatic discovery.
205
+ Never use it to adopt a tuple whose resources belong to another workspace. An
206
+ occupied but unclaimed tuple requires reviewed evidence that the existing
207
+ resources belong to this workspace. There is no implicit compatibility claim,
208
+ ownership transfer, or force-adoption path.
119
209
  `,
120
210
  'tools/stage-authority-render.mjs': `import {
211
+ assertBoundStageAuthorityTemplateFitsBodyLimit,
121
212
  canonicalStageAuthorityTemplate,
122
213
  createStageAuthorityPlan,
123
214
  } from '@empire-builder-kit/nx/aws-authority';
@@ -125,9 +216,15 @@ import {
125
216
  configuredDeploymentAccountForStage,
126
217
  configuredDeploymentRegion,
127
218
  isActiveDeploymentStage,
219
+ readRepositoryOperatingPolicy,
128
220
  requiresGithubDeployment,
129
- validateRepositoryOperatingPolicy,
130
221
  } from '@empire-builder-kit/nx/repository-policy';
222
+ import {
223
+ bindStageAuthorityTemplateToWorkspaceOwner,
224
+ canonicalWorkspaceStageOwner,
225
+ createWorkspaceStageOwnershipClaimPlan,
226
+ inspectWorkspaceStageOwnership,
227
+ } from '@empire-builder-kit/nx/workspace-deployment';
131
228
  import { createHash } from 'node:crypto';
132
229
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
133
230
  import { join } from 'node:path';
@@ -150,19 +247,22 @@ const stage = values.get('stage');
150
247
  if (!['development', 'staging', 'production'].includes(stage)) {
151
248
  throw new Error('--stage must be development, staging, or production.');
152
249
  }
153
- const policyResult = validateRepositoryOperatingPolicy(
154
- JSON.parse(readFileSync('ebk.repository-policy.json', 'utf8')),
155
- );
156
- if (!policyResult.valid) {
157
- throw new Error(
158
- 'Repository Operating Policy is invalid:\\n' +
159
- policyResult.issues.map(({ message, path }) => '- ' + path + ': ' + message).join('\\n'),
160
- );
250
+ const policy = readRepositoryOperatingPolicy(process.cwd());
251
+ const rootPackage = JSON.parse(readFileSync('package.json', 'utf8'));
252
+ const workspaceName = rootPackage?.name;
253
+ if (
254
+ typeof workspaceName !== 'string' ||
255
+ workspaceName.length > 214 ||
256
+ !/^(?:@[a-z0-9][a-z0-9._~-]*\\/)?[a-z0-9][a-z0-9._~-]*$/.test(workspaceName)
257
+ ) {
258
+ throw new Error('Workspace ownership requires the current generated root package name.');
161
259
  }
162
- const policy = policyResult.value;
163
260
  if (policy.deploymentProfile !== '${options.deploymentProfile}') {
164
261
  throw new Error('Generated and committed deployment profiles differ.');
165
262
  }
263
+ if (policy.workspaceDeploymentId !== '${options.workspaceDeploymentId}') {
264
+ throw new Error('Generated and committed workspace deployment identities differ.');
265
+ }
166
266
  if (!isActiveDeploymentStage(policy.deploymentProfile, stage)) {
167
267
  throw new Error('Requested stage is inactive for the committed deployment profile.');
168
268
  }
@@ -172,11 +272,66 @@ const accountId = configuredDeploymentAccountForStage(
172
272
  stage,
173
273
  );
174
274
  const region = configuredDeploymentRegion(policy.deploymentRegion);
275
+ if (/^(?:us-iso-|us-isob-|eu-isoe-|us-isof-)/.test(region)) {
276
+ throw new Error(
277
+ 'The committed Region belongs to an unsupported AWS partition. Generated authority supports aws, aws-cn, and aws-us-gov only.',
278
+ );
279
+ }
175
280
  const partition = region.startsWith('cn-')
176
281
  ? 'aws-cn'
177
282
  : region.startsWith('us-gov-')
178
283
  ? 'aws-us-gov'
179
284
  : 'aws';
285
+ const ownershipTarget = {
286
+ accountId,
287
+ region,
288
+ stage,
289
+ workspaceDeploymentId: policy.workspaceDeploymentId,
290
+ workspaceName,
291
+ };
292
+ const ownershipDoctor = values.get('ownership-doctor');
293
+ if (ownershipDoctor !== undefined && ownershipDoctor !== 'true') {
294
+ throw new Error('--ownership-doctor must be exactly true when supplied.');
295
+ }
296
+ if (ownershipDoctor === 'true') {
297
+ if (principals.length > 0 || values.has('github-repository')) {
298
+ throw new Error('Workspace ownership doctor does not accept authority trust inputs.');
299
+ }
300
+ const profile = values.get('profile');
301
+ const [{ GetCallerIdentityCommand, STSClient }, { fromIni }] = await Promise.all([
302
+ import('@aws-sdk/client-sts'),
303
+ import('@aws-sdk/credential-providers'),
304
+ ]);
305
+ const credentials = profile ? fromIni({ profile }) : undefined;
306
+ const identity = await new STSClient({
307
+ ...(credentials ? { credentials } : {}),
308
+ region,
309
+ }).send(new GetCallerIdentityCommand({}));
310
+ if (identity.Account !== accountId) {
311
+ throw new Error('Workspace ownership doctor caller differs from the committed stage account.');
312
+ }
313
+ const inspection = await inspectWorkspaceStageOwnership(
314
+ ownershipTarget,
315
+ profile ? { profile } : {},
316
+ );
317
+ process.stdout.write(JSON.stringify({
318
+ accountId,
319
+ expectedOwner: inspection.expectedOwner,
320
+ kind: 'ebk-workspace-stage-owner-doctor',
321
+ parameterArn: inspection.parameterArn,
322
+ region,
323
+ ...(inspection.status === 'owned'
324
+ ? { owner: inspection.expectedOwner }
325
+ : inspection.status === 'conflict' && inspection.actualOwner
326
+ ? { owner: inspection.actualOwner }
327
+ : {}),
328
+ ...(inspection.status === 'conflict' ? { reason: inspection.reason } : {}),
329
+ schemaVersion: 1,
330
+ stage,
331
+ status: inspection.status,
332
+ }, null, 2) + '\\n');
333
+ if (inspection.status !== 'owned') process.exitCode = 1;
334
+ } else {
180
335
  const repository = values.get('github-repository');
181
336
  if (repository && !/^[A-Za-z0-9_.-]+[/][A-Za-z0-9_.-]+$/.test(repository)) {
182
337
  throw new Error('--github-repository must be one exact owner/repository slug.');
@@ -184,8 +339,10 @@ if (repository && !/^[A-Za-z0-9_.-]+[/][A-Za-z0-9_.-]+$/.test(repository)) {
184
339
  const githubOwnerId = values.get('github-owner-id');
185
340
  const githubRepositoryId = values.get('github-repository-id');
186
341
  const githubId = /^[1-9][0-9]{0,19}$/;
187
- if (Boolean(githubOwnerId) !== Boolean(githubRepositoryId)) {
188
- throw new Error('--github-owner-id and --github-repository-id must be supplied together.');
342
+ if (repository && (!githubOwnerId || !githubRepositoryId)) {
343
+ throw new Error(
344
+ '--github-repository requires both --github-owner-id and --github-repository-id.',
345
+ );
189
346
  }
190
347
  if ((githubOwnerId || githubRepositoryId) && !repository) {
191
348
  throw new Error('Immutable GitHub IDs require --github-repository.');
@@ -197,36 +354,35 @@ if (
197
354
  throw new Error('Immutable GitHub owner and repository IDs must be positive bounded integers.');
198
355
  }
199
356
  const githubOnly = requiresGithubDeployment(policy.deploymentProfile, stage);
200
- const githubOptional = policy.deploymentProfile === 'starter';
201
- if (githubOnly) {
202
- if (!repository) {
203
- throw new Error(
204
- 'This stage requires protected GitHub deployment and one exact --github-repository trust.',
205
- );
206
- }
207
- if (principals.length > 0) {
208
- throw new Error(
209
- 'Protected GitHub deployment stages reject local --aws-principal-arn trust.',
210
- );
211
- }
212
- } else {
213
- if (principals.length === 0) {
214
- throw new Error(
215
- 'Locally admitted stages require at least one reviewed --aws-principal-arn.',
216
- );
217
- }
218
- if (repository && !githubOptional) {
219
- throw new Error(
220
- 'This locally admitted isolated-profile stage does not permit GitHub trust.',
221
- );
222
- }
357
+ const productionIsolatedStaging =
358
+ policy.deploymentProfile === 'production-isolated' && stage === 'staging';
359
+ const localRequired = !githubOnly;
360
+ const githubRequired = githubOnly || productionIsolatedStaging;
361
+ const githubAllowed = githubRequired || policy.deploymentProfile === 'starter';
362
+ if (localRequired && principals.length === 0) {
363
+ throw new Error(
364
+ 'Locally admitted stages require at least one reviewed --aws-principal-arn.',
365
+ );
366
+ }
367
+ if (!localRequired && principals.length > 0) {
368
+ throw new Error(
369
+ 'Protected GitHub deployment stages reject local --aws-principal-arn trust.',
370
+ );
371
+ }
372
+ if (githubRequired && !repository) {
373
+ throw new Error(
374
+ 'This stage requires protected GitHub deployment and one exact --github-repository trust.',
375
+ );
376
+ }
377
+ if (repository && !githubAllowed) {
378
+ throw new Error(
379
+ 'This locally admitted isolated-profile stage does not permit GitHub trust.',
380
+ );
223
381
  }
224
382
  const [githubOwner, githubRepository] = repository?.split('/') ?? [];
225
383
  const githubSubject = repository
226
384
  ? 'repo:' +
227
- (githubOwnerId
228
- ? githubOwner + '@' + githubOwnerId + '/' + githubRepository + '@' + githubRepositoryId
229
- : repository) +
385
+ githubOwner + '@' + githubOwnerId + '/' + githubRepository + '@' + githubRepositoryId +
230
386
  ':environment:' + stage
231
387
  : undefined;
232
388
  const plan = createStageAuthorityPlan({
@@ -245,16 +401,52 @@ const plan = createStageAuthorityPlan({
245
401
  partition,
246
402
  stage,
247
403
  });
404
+ const ownership = createWorkspaceStageOwnershipClaimPlan(ownershipTarget);
405
+ const authorityTemplate = bindStageAuthorityTemplateToWorkspaceOwner(
406
+ plan.template,
407
+ ownershipTarget,
408
+ );
409
+ assertBoundStageAuthorityTemplateFitsBodyLimit(authorityTemplate);
410
+ const template = canonicalStageAuthorityTemplate(authorityTemplate);
248
411
  const directory = join('.ebk', 'authority', stage);
249
412
  mkdirSync(directory, { mode: 0o700, recursive: true });
250
- const template = canonicalStageAuthorityTemplate(plan.template);
251
413
  const templatePath = join(directory, 'template.json');
252
414
  const manifestPath = join(directory, 'manifest.json');
415
+ const ownershipTemplate = canonicalStageAuthorityTemplate(ownership.template);
416
+ const ownershipTemplatePath = join(directory, 'ownership-template.json');
417
+ const ownershipManifestPath = join(directory, 'ownership-manifest.json');
253
418
  writeFileSync(templatePath, template, { encoding: 'utf8', mode: 0o600 });
419
+ writeFileSync(ownershipTemplatePath, ownershipTemplate, {
420
+ encoding: 'utf8',
421
+ mode: 0o600,
422
+ });
423
+ const ownershipTemplateSha256 = createHash('sha256')
424
+ .update(ownershipTemplate)
425
+ .digest('hex');
426
+ const ownershipManifest = {
427
+ accountId,
428
+ digest: ownershipTemplateSha256,
429
+ kind: 'ebk-workspace-stage-owner-claim',
430
+ parameterArn: ownership.parameterArn,
431
+ parameterName: ownership.parameterName,
432
+ region,
433
+ schemaVersion: 1,
434
+ stackName: ownership.stackName,
435
+ stage,
436
+ templateSha256: ownershipTemplateSha256,
437
+ workspaceDeploymentId: policy.workspaceDeploymentId,
438
+ workspaceName,
439
+ };
440
+ writeFileSync(
441
+ ownershipManifestPath,
442
+ JSON.stringify(ownershipManifest, null, 2) + '\\n',
443
+ { encoding: 'utf8', mode: 0o600 },
444
+ );
445
+ const templateSha256 = createHash('sha256').update(template).digest('hex');
254
446
  const manifest = {
255
447
  accountId,
256
448
  deploymentRoleArn: plan.identity.deploymentRoleArn,
257
- digest: plan.digest,
449
+ digest: templateSha256,
258
450
  kind: 'ebk-stage-authority-render',
259
451
  limitations: plan.limitations,
260
452
  profile: policy.deploymentProfile,
@@ -262,13 +454,25 @@ const manifest = {
262
454
  schemaVersion: 1,
263
455
  stackName: plan.identity.stackName,
264
456
  stage,
265
- templateSha256: createHash('sha256').update(template).digest('hex'),
457
+ templateSha256,
458
+ workspaceDeploymentId: policy.workspaceDeploymentId,
459
+ workspaceName,
460
+ workspaceOwner: canonicalWorkspaceStageOwner(ownershipTarget),
461
+ workspaceOwnerClaimStackName: ownership.stackName,
462
+ workspaceOwnerManifestPath: ownershipManifestPath,
266
463
  };
267
464
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\\n', {
268
465
  encoding: 'utf8',
269
466
  mode: 0o600,
270
467
  });
271
- process.stdout.write(JSON.stringify({ manifestPath, ...manifest, templatePath }) + '\\n');
468
+ process.stdout.write(JSON.stringify({
469
+ manifestPath,
470
+ ...manifest,
471
+ ownershipManifestPath,
472
+ ownershipTemplatePath,
473
+ templatePath,
474
+ }) + '\\n');
475
+ }
272
476
  `,
273
477
  };
274
478
  }
@@ -1 +1 @@
1
- {"version":3,"file":"workspace-files-stage-authority.js","sourceRoot":"","sources":["../../../../../../packages/nx/src/generators/preset/workspace-files-stage-authority.ts"],"names":[],"mappings":";;AAEA,gFAgRC;AAhRD,SAAgB,kCAAkC,CAAC,OAElD;IACC,OAAO;QACL,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiHzC;QACG,kCAAkC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCA2CJ,OAAO,CAAC,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6G5D;KACE,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"workspace-files-stage-authority.js","sourceRoot":"","sources":["../../../../../../packages/nx/src/generators/preset/workspace-files-stage-authority.ts"],"names":[],"mappings":";;AAEA,gFA8dC;AA9dD,SAAgB,kCAAkC,CAAC,OAIlD;IACC,OAAO;QACL,oCAAoC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiJzC;QACG,wCAAwC,EAAE;;;;;;;;;;;;;;;4CAeF,OAAO,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CvD;QACG,kCAAkC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAkDJ,OAAO,CAAC,iBAAiB;;;wCAGrB,OAAO,CAAC,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqNpE;KACE,CAAC;AACJ,CAAC"}
@@ -10,7 +10,6 @@ export interface ThinDeployActionPins {
10
10
  }
11
11
  export interface ThinDeployToolVersions {
12
12
  node: string;
13
- pnpm: string;
14
13
  sst: string;
15
14
  }
16
15
  export interface ThinDeployWorkflowOptions {
@@ -24,6 +23,12 @@ export interface ThinDeployWorkflowFilePlan {
24
23
  managedPaths: readonly [typeof THIN_DEPLOY_WORKFLOW_PATH];
25
24
  }
26
25
  export declare function renderThinDeployVerifyCaller(profile: DeploymentProfile): string;
26
+ /** @internal Exported for executable source-contract tests. */
27
+ export declare function exactStageDeployerGuardScript(operation: string, accountVariable: string): string;
28
+ /** @internal Shared verbatim by every generated protected workflow. */
29
+ export declare function canonicalStageAuthorityDerivationScript(): string;
30
+ /** @internal Exported for executable source-contract tests. */
31
+ export declare function exactStagingPromotionPullGuardScript(): string;
27
32
  /** @internal Shared verbatim with the generated workflow and executable tests. */
28
33
  export declare function stagingPromotionReceiptValidationScript(): string;
29
34
  /** @internal Shared verbatim with the generated workflow and executable tests. */