@deployfoundation/foundation-deploy 0.2.38 → 0.2.40

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.
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import {
3
3
  DEFAULT_RELEASE_BUCKET
4
- } from "../chunk-8e2z4rhf.js";
5
- import"../chunk-2ra18dqx.js";
4
+ } from "../chunk-dz91avrb.js";
6
5
 
7
6
  // bin/release-account.ts
8
7
  import * as cdk2 from "aws-cdk-lib";
@@ -0,0 +1,16 @@
1
+ // src/release/trust.ts
2
+ var DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
3
+ var RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
4
+ var RELEASE_KMS_KEY_ARN = "arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
5
+ function configuredKeyArn(env = process.env) {
6
+ const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
7
+ return configured === undefined || configured === "" ? undefined : configured;
8
+ }
9
+ function expectedKeyArn(env = process.env) {
10
+ const configured = configuredKeyArn(env);
11
+ if (configured === undefined)
12
+ throw new Error(`no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`);
13
+ return configured;
14
+ }
15
+
16
+ export { DEFAULT_RELEASE_BUCKET, configuredKeyArn, expectedKeyArn };
@@ -17,13 +17,14 @@ import {
17
17
  parseInstanceConfig,
18
18
  parseSchedule,
19
19
  provisionsIntegration,
20
+ releaseAccess,
20
21
  releaseImageRepositoryArn,
21
22
  releaseSource,
22
23
  requiresAuthenticatedQueue,
23
24
  scheduledJobBindings,
24
25
  slackCommandPrefixes,
25
26
  webhookJobBindings
26
- } from "./chunk-2ra18dqx.js";
27
+ } from "./chunk-g56mqyfx.js";
27
28
 
28
29
  // src/stacks/agent-stack.ts
29
30
  import * as cdk from "aws-cdk-lib";
@@ -1846,6 +1847,18 @@ function deployStatements(stack, names, targets, instance) {
1846
1847
  const runtimeSecret = secretArn(names.secretRuntime);
1847
1848
  const knockOauthClient = secretArn(names.secretKnockOauthClient);
1848
1849
  return [
1850
+ new iam3.PolicyStatement({
1851
+ sid: "ReadFoundationRelease",
1852
+ actions: ["s3:GetObject"],
1853
+ resources: [`arn:${stack.partition}:s3:::${targets.release.bucket}/releases/*`]
1854
+ }),
1855
+ ...targets.release.signingKeyArn === undefined ? [] : [
1856
+ new iam3.PolicyStatement({
1857
+ sid: "VerifyReleaseSignature",
1858
+ actions: ["kms:Verify"],
1859
+ resources: [targets.release.signingKeyArn]
1860
+ })
1861
+ ],
1849
1862
  new iam3.PolicyStatement({
1850
1863
  sid: "AssumeCdkBootstrapRoles",
1851
1864
  actions: ["sts:AssumeRole"],
@@ -1927,7 +1940,8 @@ function deployStatements(stack, names, targets, instance) {
1927
1940
  "bedrock-agentcore:GetAgentRuntime",
1928
1941
  "bedrock-agentcore:ListAgentRuntimeVersions",
1929
1942
  "bedrock-agentcore:GetAgentRuntimeEndpoint",
1930
- "bedrock-agentcore:UpdateAgentRuntimeEndpoint"
1943
+ "bedrock-agentcore:UpdateAgentRuntimeEndpoint",
1944
+ "bedrock-agentcore:CreateAgentRuntimeEndpoint"
1931
1945
  ],
1932
1946
  resources: [
1933
1947
  `arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:runtime/${names.runtimeName}*`,
@@ -1970,7 +1984,7 @@ class FoundationCi extends cdk3.Stack {
1970
1984
  }),
1971
1985
  inlinePolicies: {
1972
1986
  FoundationDeploy: new iam4.PolicyDocument({
1973
- statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey }, props.instance)
1987
+ statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey, release: releaseAccess(this) }, props.instance)
1974
1988
  })
1975
1989
  }
1976
1990
  });
@@ -3453,7 +3467,7 @@ class FoundationPipeline extends cdk8.Stack {
3453
3467
  assumedBy: new iam9.ServicePrincipal("codebuild.amazonaws.com"),
3454
3468
  inlinePolicies: {
3455
3469
  FoundationDeploy: new iam9.PolicyDocument({
3456
- statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey }, props.instance)
3470
+ statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey, release: releaseAccess(this) }, props.instance)
3457
3471
  })
3458
3472
  }
3459
3473
  });
@@ -1,3 +1,8 @@
1
+ import {
2
+ DEFAULT_RELEASE_BUCKET,
3
+ configuredKeyArn
4
+ } from "./chunk-dz91avrb.js";
5
+
1
6
  // ../core/src/instance.ts
2
7
  import { parse as parseYaml } from "yaml";
3
8
  import { z as z2 } from "zod";
@@ -1256,31 +1261,45 @@ var contextStorage = new AsyncLocalStorage;
1256
1261
  // ../core/src/config.ts
1257
1262
  import { createHash as createHash2 } from "node:crypto";
1258
1263
  import { parse as parseYaml2 } from "yaml";
1259
- import { z as z4 } from "zod";
1264
+ import { z as z5 } from "zod";
1260
1265
 
1261
1266
  // ../core/src/jobs.ts
1262
1267
  import { createHash, createHmac, timingSafeEqual } from "node:crypto";
1268
+ import { z as z4 } from "zod";
1269
+
1270
+ // ../core/src/thinking-level.ts
1263
1271
  import { z as z3 } from "zod";
1272
+ var ThinkingLevel = z3.enum(["none", "low", "medium", "high", "xhigh", "max"]);
1273
+
1274
+ // ../core/src/jobs.ts
1264
1275
  var JOB_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,40}$/;
1265
- var RawScheduleTrigger = z3.object({
1266
- schedule: z3.string().min(1)
1276
+ var RawScheduleTrigger = z4.object({
1277
+ schedule: z4.string().min(1)
1267
1278
  }).strict();
1268
- var RawWebhookTrigger = z3.object({
1269
- webhook: z3.object({}).strict()
1279
+ var RawWebhookTrigger = z4.object({
1280
+ webhook: z4.object({}).strict()
1270
1281
  }).strict();
1271
- var RawJob = z3.object({
1272
- id: z3.string().regex(JOB_ID_PATTERN),
1273
- name: z3.string().min(1).max(80),
1274
- channel: z3.string().regex(/^[CG][A-Z0-9]{8,}$/, "a job runs in a Slack channel id"),
1275
- on: z3.union([RawScheduleTrigger, RawWebhookTrigger]),
1276
- prompt: z3.string().min(1).max(4000)
1277
- }).strict();
1278
- var RawJobs = z3.array(RawJob).default([]).superRefine((jobs, ctx) => {
1282
+ var RawJob = z4.object({
1283
+ id: z4.string().regex(JOB_ID_PATTERN),
1284
+ name: z4.string().min(1).max(80),
1285
+ channel: z4.string().regex(/^[CG][A-Z0-9]{8,}$/, "a job runs in a Slack channel id"),
1286
+ on: z4.union([RawScheduleTrigger, RawWebhookTrigger]),
1287
+ prompt: z4.string().min(1).max(4000),
1288
+ thinkingLevel: ThinkingLevel.optional()
1289
+ }).strict().superRefine((job, ctx) => {
1290
+ if (job.thinkingLevel !== undefined && !("schedule" in job.on))
1291
+ ctx.addIssue({
1292
+ code: z4.ZodIssueCode.custom,
1293
+ path: ["thinkingLevel"],
1294
+ message: "thinkingLevel applies to scheduled jobs only"
1295
+ });
1296
+ });
1297
+ var RawJobs = z4.array(RawJob).default([]).superRefine((jobs, ctx) => {
1279
1298
  const seen = new Set;
1280
1299
  for (const [index, job] of jobs.entries()) {
1281
1300
  if (seen.has(job.id)) {
1282
1301
  ctx.addIssue({
1283
- code: z3.ZodIssueCode.custom,
1302
+ code: z4.ZodIssueCode.custom,
1284
1303
  path: [index, "id"],
1285
1304
  message: `duplicate job id: ${job.id}`
1286
1305
  });
@@ -1301,9 +1320,13 @@ function scheduledJobFingerprint(input) {
1301
1320
  return createHash("sha256").update(JSON.stringify([input.routineId, input.name, input.channel, input.prompt])).digest("hex");
1302
1321
  }
1303
1322
  function scheduledJobBindings(jobs) {
1304
- return jobs.filter(isScheduledJob).map(({ id, name, channel, prompt }) => {
1323
+ return jobs.filter(isScheduledJob).map(({ id, name, channel, prompt, thinkingLevel }) => {
1305
1324
  const input = { routineId: `job-${id}`, name, channel, prompt };
1306
- return { routineId: input.routineId, fingerprint: scheduledJobFingerprint(input) };
1325
+ return {
1326
+ routineId: input.routineId,
1327
+ fingerprint: scheduledJobFingerprint(input),
1328
+ ...thinkingLevel === undefined ? {} : { thinkingLevel }
1329
+ };
1307
1330
  });
1308
1331
  }
1309
1332
  function jobWebhookKey(instanceSecret, jobId) {
@@ -1369,11 +1392,10 @@ function capabilityScheduledJobs(config) {
1369
1392
 
1370
1393
  // ../core/src/config.ts
1371
1394
  var MODEL_RE = /^([a-z0-9-]+)\/(.+)$/;
1372
- var ThinkingLevel = z4.enum(["none", "low", "medium", "high", "xhigh", "max"]);
1373
- var RawImages = z4.object({
1374
- backend: z4.enum(["gemini"]).default("gemini"),
1375
- model: z4.string().min(1).default("gemini-2.5-flash-image"),
1376
- dailyCap: z4.number().int().min(0).default(40)
1395
+ var RawImages = z5.object({
1396
+ backend: z5.enum(["gemini"]).default("gemini"),
1397
+ model: z5.string().min(1).default("gemini-2.5-flash-image"),
1398
+ dailyCap: z5.number().int().min(0).default(40)
1377
1399
  }).strict().default({});
1378
1400
  var OMP_MODEL_ROLES = [
1379
1401
  "default",
@@ -1386,40 +1408,40 @@ var OMP_MODEL_ROLES = [
1386
1408
  "task",
1387
1409
  "advisor"
1388
1410
  ];
1389
- var RawModelRoles = z4.record(z4.enum(OMP_MODEL_ROLES), z4.string().regex(MODEL_RE)).default({});
1390
- var RawChannelModels = z4.record(z4.string().regex(/^[CDG][A-Z0-9]+$/, "channelModels keys must be Slack conversation ids"), z4.string().regex(MODEL_RE, "channel model must be provider/modelId")).default({});
1391
- var RawSubagents = z4.object({
1392
- enabled: z4.boolean().default(true),
1393
- maxConcurrency: z4.number().int().min(1).max(8).default(3),
1394
- maxRecursionDepth: z4.number().int().min(1).max(3).default(2),
1395
- effort: z4.boolean().default(true)
1411
+ var RawModelRoles = z5.record(z5.enum(OMP_MODEL_ROLES), z5.string().regex(MODEL_RE)).default({});
1412
+ var RawChannelModels = z5.record(z5.string().regex(/^[CDG][A-Z0-9]+$/, "channelModels keys must be Slack conversation ids"), z5.string().regex(MODEL_RE, "channel model must be provider/modelId")).default({});
1413
+ var RawSubagents = z5.object({
1414
+ enabled: z5.boolean().default(true),
1415
+ maxConcurrency: z5.number().int().min(1).max(8).default(3),
1416
+ maxRecursionDepth: z5.number().int().min(1).max(3).default(2),
1417
+ effort: z5.boolean().default(true)
1396
1418
  }).strict().default({});
1397
- var RawMemory = z4.object({ ompBackend: z4.enum(["off", "local"]).default("off") }).strict().default({});
1419
+ var RawMemory = z5.object({ ompBackend: z5.enum(["off", "local"]).default("off") }).strict().default({});
1398
1420
  var SKILL_SOURCE_SCOPES = ["shared", "instance"];
1399
1421
  var RawSkillSourceMetadata = {
1400
- owner: z4.string().min(1).optional(),
1401
- scope: z4.enum(SKILL_SOURCE_SCOPES).optional(),
1402
- requires: z4.array(z4.enum(CAPABILITY_IDS)).optional()
1422
+ owner: z5.string().min(1).optional(),
1423
+ scope: z5.enum(SKILL_SOURCE_SCOPES).optional(),
1424
+ requires: z5.array(z5.enum(CAPABILITY_IDS)).optional()
1403
1425
  };
1404
- var RawSkillSource = z4.union([
1405
- z4.object({
1406
- github: z4.string().min(1),
1407
- ref: z4.string().min(1).optional(),
1408
- path: z4.string().optional(),
1426
+ var RawSkillSource = z5.union([
1427
+ z5.object({
1428
+ github: z5.string().min(1),
1429
+ ref: z5.string().min(1).optional(),
1430
+ path: z5.string().optional(),
1409
1431
  ...RawSkillSourceMetadata
1410
1432
  }).strict(),
1411
- z4.object({ s3: z4.string().min(1), ...RawSkillSourceMetadata }).strict()
1433
+ z5.object({ s3: z5.string().min(1), ...RawSkillSourceMetadata }).strict()
1412
1434
  ]);
1413
- var RawSkills = z4.object({
1414
- sources: z4.array(RawSkillSource).optional(),
1415
- promoteTo: z4.string().min(1).optional()
1435
+ var RawSkills = z5.object({
1436
+ sources: z5.array(RawSkillSource).optional(),
1437
+ promoteTo: z5.string().min(1).optional()
1416
1438
  }).strict().default({});
1417
- var RawInstanceConfig = z4.object({
1418
- name: z4.string().min(1),
1419
- model: z4.string().regex(MODEL_RE, "model must be provider/modelId"),
1420
- admins: z4.array(z4.string().min(1)).min(1, "admins must list at least one Slack user id"),
1421
- instructions: z4.string().default(""),
1422
- timezone: z4.string().default("UTC").refine(isValidTimezone, (tz) => ({ message: `unknown timezone: ${tz}` })),
1439
+ var RawInstanceConfig = z5.object({
1440
+ name: z5.string().min(1),
1441
+ model: z5.string().regex(MODEL_RE, "model must be provider/modelId"),
1442
+ admins: z5.array(z5.string().min(1)).min(1, "admins must list at least one Slack user id"),
1443
+ instructions: z5.string().default(""),
1444
+ timezone: z5.string().default("UTC").refine(isValidTimezone, (tz) => ({ message: `unknown timezone: ${tz}` })),
1423
1445
  thinkingLevel: ThinkingLevel.optional(),
1424
1446
  skills: RawSkills,
1425
1447
  images: RawImages,
@@ -1496,24 +1518,24 @@ function parseInstanceConfig(yamlText) {
1496
1518
  }
1497
1519
  // ../core/src/company-capabilities.ts
1498
1520
  import { parse as parseYaml3 } from "yaml";
1499
- import { z as z5 } from "zod";
1521
+ import { z as z6 } from "zod";
1500
1522
  var COMPANY_CAPABILITY_DECLARATION_VERSION = 1;
1501
1523
  var COMPANY_CAPABILITY_DECLARATION_MAX_COUNT = CAPABILITY_IDS.length;
1502
1524
  var COMPANY_CAPABILITY_DESCRIPTION_MAX_CHARS = 280;
1503
1525
  var COMPANY_CAPABILITY_DECLARATION_MAX_BYTES = 16 * 1024;
1504
- var RawCompanyCapabilityDeclaration = z5.object({
1505
- id: z5.enum(CAPABILITY_IDS),
1506
- description: z5.string().min(1).max(COMPANY_CAPABILITY_DESCRIPTION_MAX_CHARS).refine((value) => value.trim() === value && !Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127), "description must not have surrounding whitespace or control characters")
1526
+ var RawCompanyCapabilityDeclaration = z6.object({
1527
+ id: z6.enum(CAPABILITY_IDS),
1528
+ description: z6.string().min(1).max(COMPANY_CAPABILITY_DESCRIPTION_MAX_CHARS).refine((value) => value.trim() === value && !Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127), "description must not have surrounding whitespace or control characters")
1507
1529
  }).strict();
1508
- var RawCompanyCapabilityDeclarations = z5.object({
1509
- version: z5.literal(COMPANY_CAPABILITY_DECLARATION_VERSION),
1510
- capabilities: z5.array(RawCompanyCapabilityDeclaration).max(COMPANY_CAPABILITY_DECLARATION_MAX_COUNT)
1530
+ var RawCompanyCapabilityDeclarations = z6.object({
1531
+ version: z6.literal(COMPANY_CAPABILITY_DECLARATION_VERSION),
1532
+ capabilities: z6.array(RawCompanyCapabilityDeclaration).max(COMPANY_CAPABILITY_DECLARATION_MAX_COUNT)
1511
1533
  }).strict().superRefine((document, ctx) => {
1512
1534
  const seen = new Set;
1513
1535
  for (const [index, declaration] of document.capabilities.entries()) {
1514
1536
  if (seen.has(declaration.id)) {
1515
1537
  ctx.addIssue({
1516
- code: z5.ZodIssueCode.custom,
1538
+ code: z6.ZodIssueCode.custom,
1517
1539
  path: ["capabilities", index, "id"],
1518
1540
  message: `duplicate capability declaration: ${declaration.id}`
1519
1541
  });
@@ -1580,13 +1602,13 @@ var LIVE_ENDPOINT_NAME = "live";
1580
1602
  // src/release/manifest.ts
1581
1603
  import { createHash as createHash3 } from "node:crypto";
1582
1604
  import { readFileSync as readFileSync2, statSync } from "node:fs";
1583
- import { z as z6 } from "zod";
1605
+ import { z as z7 } from "zod";
1584
1606
  var RELEASE_VERSION_RE = /^v\d+\.\d+\.\d+$/;
1585
- var Sha256 = z6.string().regex(/^[0-9a-f]{64}$/, "must be a lowercase hex sha256");
1586
- var ReleaseArtifactSchema = z6.object({
1587
- key: z6.string().min(1),
1607
+ var Sha256 = z7.string().regex(/^[0-9a-f]{64}$/, "must be a lowercase hex sha256");
1608
+ var ReleaseArtifactSchema = z7.object({
1609
+ key: z7.string().min(1),
1588
1610
  sha256: Sha256,
1589
- bytes: z6.number().int().nonnegative()
1611
+ bytes: z7.number().int().nonnegative()
1590
1612
  }).strict();
1591
1613
  var SIGNING_ALGORITHMS = [
1592
1614
  "RSASSA_PSS_SHA_256",
@@ -1599,21 +1621,21 @@ var SIGNING_ALGORITHMS = [
1599
1621
  "ECDSA_SHA_384",
1600
1622
  "ECDSA_SHA_512"
1601
1623
  ];
1602
- var ReleaseManifestSchema = z6.object({
1603
- version: z6.string().regex(RELEASE_VERSION_RE, "must be vMAJOR.MINOR.PATCH"),
1604
- gitCommit: z6.string().regex(/^[0-9a-f]{40}$/, "must be a full commit sha"),
1605
- createdAt: z6.string().datetime(),
1606
- agentImage: z6.object({
1607
- repository: z6.string().min(1),
1608
- tag: z6.string().min(1),
1609
- digest: z6.string().regex(/^sha256:[0-9a-f]{64}$/)
1624
+ var ReleaseManifestSchema = z7.object({
1625
+ version: z7.string().regex(RELEASE_VERSION_RE, "must be vMAJOR.MINOR.PATCH"),
1626
+ gitCommit: z7.string().regex(/^[0-9a-f]{40}$/, "must be a full commit sha"),
1627
+ createdAt: z7.string().datetime(),
1628
+ agentImage: z7.object({
1629
+ repository: z7.string().min(1),
1630
+ tag: z7.string().min(1),
1631
+ digest: z7.string().regex(/^sha256:[0-9a-f]{64}$/)
1610
1632
  }).strict(),
1611
- lambda: z6.record(ReleaseArtifactSchema),
1612
- skills: z6.object({ key: z6.string().min(1), sha256: Sha256 }).strict(),
1613
- signature: z6.object({
1614
- kmsKeyArn: z6.string().min(1),
1615
- algorithm: z6.enum(SIGNING_ALGORITHMS),
1616
- value: z6.string().min(1)
1633
+ lambda: z7.record(ReleaseArtifactSchema),
1634
+ skills: z7.object({ key: z7.string().min(1), sha256: Sha256 }).strict(),
1635
+ signature: z7.object({
1636
+ kmsKeyArn: z7.string().min(1),
1637
+ algorithm: z7.enum(SIGNING_ALGORITHMS),
1638
+ value: z7.string().min(1)
1617
1639
  }).strict().optional()
1618
1640
  }).strict();
1619
1641
  function parseManifest(json) {
@@ -1816,6 +1838,14 @@ function releaseSource(scope) {
1816
1838
  throw new Error(`-c releaseBucket=<name> is required when deploying a release (or set ${RELEASE_BUCKET_ENV}); it is the bucket holding ${manifest.lambda.gateway?.key ?? "the release artifacts"}`);
1817
1839
  return { version: manifest.version, bucket, manifest };
1818
1840
  }
1841
+ function releaseAccess(scope, env = process.env) {
1842
+ const context = scope.node.tryGetContext("releaseBucket");
1843
+ const fromContext = typeof context === "string" && context !== "" ? context : undefined;
1844
+ const fromEnv = env[RELEASE_BUCKET_ENV];
1845
+ const bucket = fromContext ?? (fromEnv !== undefined && fromEnv !== "" ? fromEnv : DEFAULT_RELEASE_BUCKET);
1846
+ const signingKeyArn = configuredKeyArn(env);
1847
+ return { bucket, ...signingKeyArn === undefined ? {} : { signingKeyArn } };
1848
+ }
1819
1849
  function releaseBucket(scope, source) {
1820
1850
  const stack = cdk.Stack.of(scope);
1821
1851
  const existing = stack.node.tryFindChild("FoundationReleaseBucket");
@@ -1899,4 +1929,4 @@ function ecrRepositoryArn(repositoryUri) {
1899
1929
  return `arn:aws:ecr:${region}:${account}:repository/${name}`;
1900
1930
  }
1901
1931
 
1902
- export { parseSchedule, BASE_SLACK_EVENTS, CAPABILITY_IDS, requiredGithubPermissions, requiredSlackScopes, requiredSlackCommands, encodeProactiveChannels, CUSTOMIZATION_ARTIFACT_NAME, newsletterManifestFor, FOUNDATION_VERSION_RE, versionedConfigKey, instanceNames, slackCommandPrefix, slackCommandPrefixes, isScheduledJob, isWebhookJob, webhookJobBindings, scheduledJobBindings, jobWebhookKey, capabilityScheduledJobs, crmOutreachConfigured, crmPolicyFingerprint, parseInstanceConfig, loadInstanceFile, configPathFor, capabilityEnabled, requiresAuthenticatedQueue, enabledIntegrations, provisionsIntegration, adminsFor, WEB_SEARCH_TARGET, WEB_SEARCH_CONNECTOR_VERSION, LIVE_ENDPOINT_NAME, RELEASE_VERSION_RE, parseManifest, manifestKey, lambdaKey, skillsKey, buildManifest, manifestSigningPayload, verifyManifest, lambdaBundleContext, BUN_VERSION, LAMBDA_ENTRY_POINTS, skipBundle, RELEASE_BUCKET_ENV, RELEASE_DIR_ENV, releaseCacheDir, releaseSource, lambdaCode, agentImage, agentImageTagRequired, releaseImageRepositoryArn, ecrRepositoryArn };
1932
+ export { parseSchedule, BASE_SLACK_EVENTS, CAPABILITY_IDS, requiredGithubPermissions, requiredSlackScopes, requiredSlackCommands, encodeProactiveChannels, CUSTOMIZATION_ARTIFACT_NAME, newsletterManifestFor, FOUNDATION_VERSION_RE, versionedConfigKey, instanceNames, slackCommandPrefix, slackCommandPrefixes, isScheduledJob, isWebhookJob, webhookJobBindings, scheduledJobBindings, jobWebhookKey, capabilityScheduledJobs, crmOutreachConfigured, crmPolicyFingerprint, parseInstanceConfig, loadInstanceFile, configPathFor, capabilityEnabled, requiresAuthenticatedQueue, enabledIntegrations, provisionsIntegration, adminsFor, WEB_SEARCH_TARGET, WEB_SEARCH_CONNECTOR_VERSION, LIVE_ENDPOINT_NAME, RELEASE_VERSION_RE, parseManifest, manifestKey, lambdaKey, skillsKey, buildManifest, manifestSigningPayload, verifyManifest, lambdaBundleContext, BUN_VERSION, LAMBDA_ENTRY_POINTS, skipBundle, RELEASE_BUCKET_ENV, RELEASE_DIR_ENV, releaseCacheDir, releaseSource, releaseAccess, lambdaCode, agentImage, agentImageTagRequired, releaseImageRepositoryArn, ecrRepositoryArn };
package/dist/src/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  FoundationPipeline,
11
11
  NewsletterStack,
12
12
  deployStatements
13
- } from "../chunk-j52g7bsc.js";
13
+ } from "../chunk-fwqmac5n.js";
14
14
  import {
15
15
  BUN_VERSION,
16
16
  LAMBDA_ENTRY_POINTS,
@@ -36,6 +36,7 @@ import {
36
36
  manifestSigningPayload,
37
37
  parseManifest,
38
38
  provisionsIntegration,
39
+ releaseAccess,
39
40
  releaseCacheDir,
40
41
  releaseImageRepositoryArn,
41
42
  releaseSource,
@@ -43,7 +44,8 @@ import {
43
44
  skillsKey,
44
45
  skipBundle,
45
46
  verifyManifest
46
- } from "../chunk-2ra18dqx.js";
47
+ } from "../chunk-g56mqyfx.js";
48
+ import"../chunk-dz91avrb.js";
47
49
  export {
48
50
  BUILD_TIMEOUT,
49
51
  BUN_VERSION,
@@ -80,6 +82,7 @@ export {
80
82
  instanceNames as namesFor,
81
83
  parseManifest,
82
84
  provisionsIntegration,
85
+ releaseAccess,
83
86
  releaseCacheDir,
84
87
  releaseImageRepositoryArn,
85
88
  releaseSource,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deployfoundation/foundation-deploy",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "description": "Deploys one Foundation instance from a published, signed release: the CDK app and the deploy tool.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
package/src/artifacts.ts CHANGED
@@ -41,6 +41,7 @@ import * as s3 from "aws-cdk-lib/aws-s3";
41
41
  import type { Construct } from "constructs";
42
42
  import { lambdaBundleContext } from "./lambda-bundle-context.ts";
43
43
  import { type ReleaseManifest, parseManifest } from "./release/manifest.ts";
44
+ import { DEFAULT_RELEASE_BUCKET, configuredKeyArn } from "./release/trust.ts";
44
45
 
45
46
  /** Pinned Bun used to bundle every Lambda (and the docker fallback image tag). */
46
47
  export const BUN_VERSION = "1.4.0";
@@ -174,6 +175,38 @@ export function releaseSource(scope: Construct): ReleaseSource | undefined {
174
175
  return { version: manifest.version, bucket, manifest };
175
176
  }
176
177
 
178
+ /**
179
+ * What a deployer of this app reads from Foundation's release account: the
180
+ * bucket the tool fetches `releases/*` from and the key whose signature it
181
+ * checks. The deployer roles (`deploy-permissions.ts`) are granted exactly
182
+ * these, cross-account, so a CodePipeline or GitHub Actions deploy can verify
183
+ * a release the way an admin's laptop can.
184
+ *
185
+ * Resolved the way `foundation-deploy` resolves them — `-c releaseBucket`
186
+ * (which the tool passes on a release deploy) or `FOUNDATION_RELEASE_BUCKET`,
187
+ * else the default bucket; `FOUNDATION_RELEASE_KMS_KEY_ARN`, else the pinned
188
+ * key — and resolved on EVERY synth, release or local build, so the grant does
189
+ * not come and go with how the last deploy was run.
190
+ */
191
+ export interface ReleaseAccess {
192
+ bucket: string;
193
+ /** Absent only when no signing key is configured at all; nothing is verified then. */
194
+ signingKeyArn?: string;
195
+ }
196
+
197
+ export function releaseAccess(
198
+ scope: Construct,
199
+ env: Record<string, string | undefined> = process.env,
200
+ ): ReleaseAccess {
201
+ const context: unknown = scope.node.tryGetContext("releaseBucket");
202
+ const fromContext = typeof context === "string" && context !== "" ? context : undefined;
203
+ const fromEnv = env[RELEASE_BUCKET_ENV];
204
+ const bucket =
205
+ fromContext ?? (fromEnv !== undefined && fromEnv !== "" ? fromEnv : DEFAULT_RELEASE_BUCKET);
206
+ const signingKeyArn = configuredKeyArn(env);
207
+ return { bucket, ...(signingKeyArn === undefined ? {} : { signingKeyArn }) };
208
+ }
209
+
177
210
  /**
178
211
  * The release bucket as one imported construct per stack. Every Lambda in a
179
212
  * stack references the same bucket, and importing it once keeps the template's
@@ -29,34 +29,10 @@ import {
29
29
  manifestKey,
30
30
  verifyManifest,
31
31
  } from "../release/manifest.ts";
32
+ import { DEFAULT_RELEASE_BUCKET, expectedKeyArn } from "../release/trust.ts";
32
33
  import { type AwsCallContext, aws } from "./aws.ts";
33
34
  import { FOUNDATION_ROOT, INFRA_ROOT } from "./paths.ts";
34
35
 
35
- /**
36
- * The bucket a release is fetched from when nothing says otherwise.
37
- *
38
- * One name, owned by Foundry 41, created by `release-account-stack.ts`. A
39
- * mirror is `--release-bucket` or `FOUNDATION_RELEASE_BUCKET`, and the
40
- * signature is what makes a mirror safe to use.
41
- */
42
- export const DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
43
-
44
- /** The env var that names the key a deployer will accept a manifest from. */
45
- export const RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
46
-
47
- /**
48
- * The Foundation release signing key, in the Foundry 41 release account
49
- * (793593623536), created by `ReleaseAccountStack` on 2026-09-20.
50
- *
51
- * This constant is what makes signature verification mean anything: a
52
- * deployer refuses a manifest signed by any other key. Changing it rotates
53
- * the root of trust for every customer, so it changes only with a key
54
- * rotation and a release that says so. {@link RELEASE_KMS_KEY_ARN_ENV}
55
- * overrides it, for a private mirror or a test key.
56
- */
57
- export const RELEASE_KMS_KEY_ARN: string | undefined =
58
- "arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
59
-
60
36
  /** What the caller asked for, before anything has been fetched. */
61
37
  export interface ReleaseRequest {
62
38
  /** `vX.Y.Z`, when the caller named a version or one was implied. */
@@ -149,16 +125,6 @@ export function releaseRequest(
149
125
  return { version, bucket };
150
126
  }
151
127
 
152
- /** The key ARN a deployer will accept a manifest from. */
153
- export function expectedKeyArn(env: Record<string, string | undefined> = process.env): string {
154
- const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
155
- if (configured === undefined || configured === "")
156
- throw new Error(
157
- `no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`,
158
- );
159
- return configured;
160
- }
161
-
162
128
  export interface ResolveReleaseDeps {
163
129
  /** Defaults to the SDK-backed verifier for the expected key. */
164
130
  kms?: KmsVerifier;
@@ -17,6 +17,7 @@ import type * as cdk from "aws-cdk-lib";
17
17
  import * as iam from "aws-cdk-lib/aws-iam";
18
18
  import type * as kms from "aws-cdk-lib/aws-kms";
19
19
  import type * as s3 from "aws-cdk-lib/aws-s3";
20
+ import type { ReleaseAccess } from "./artifacts.ts";
20
21
  import { type Instance, type InstanceNames, provisionsIntegration } from "./names.ts";
21
22
 
22
23
  export interface DeployTargets {
@@ -24,6 +25,13 @@ export interface DeployTargets {
24
25
  bucket: s3.IBucket;
25
26
  /** The CMK that bucket is encrypted with. */
26
27
  dataKey: kms.IKey;
28
+ /**
29
+ * Foundation's release bucket and signing key, in the release account
30
+ * (`releaseAccess()` in `artifacts.ts`). Cross-account access needs BOTH
31
+ * sides to allow it: the release account's bucket and key policies name the
32
+ * customer account, and the statements below are this account's half.
33
+ */
34
+ release: ReleaseAccess;
27
35
  }
28
36
 
29
37
  /**
@@ -49,6 +57,30 @@ export function deployStatements(
49
57
  const knockOauthClient = secretArn(names.secretKnockOauthClient);
50
58
 
51
59
  return [
60
+ // A release deploy, before anything else: `aws s3 cp` of
61
+ // `releases/<v>/manifest.json`, then of every Lambda zip and
62
+ // `skills.tar.gz` the manifest names (the digest check in `verifyRelease`),
63
+ // and of `skills.tar.gz` again for the config sync (`unpackReleaseSkills`).
64
+ // HeadObject is authorized by s3:GetObject. The bucket is SSE-S3, so there
65
+ // is no key to grant for it.
66
+ new iam.PolicyStatement({
67
+ sid: "ReadFoundationRelease",
68
+ actions: ["s3:GetObject"],
69
+ resources: [`arn:${stack.partition}:s3:::${targets.release.bucket}/releases/*`],
70
+ }),
71
+ // ...and the manifest's signature, checked with one KMS `Verify` against
72
+ // the pinned key (`src/release/kms.ts`); nothing calls GetPublicKey or
73
+ // DescribeKey. By key ARN, not alias: an identity policy cannot name
74
+ // another account's key by its alias.
75
+ ...(targets.release.signingKeyArn === undefined
76
+ ? []
77
+ : [
78
+ new iam.PolicyStatement({
79
+ sid: "VerifyReleaseSignature",
80
+ actions: ["kms:Verify"],
81
+ resources: [targets.release.signingKeyArn],
82
+ }),
83
+ ]),
52
84
  // How `cdk deploy` does everything else: it assumes the bootstrap roles for
53
85
  // lookup, asset publishing and the CloudFormation call.
54
86
  new iam.PolicyStatement({
@@ -140,8 +172,10 @@ export function deployStatements(
140
172
  actions: ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey*"],
141
173
  resources: [targets.dataKey.keyArn],
142
174
  }),
143
- // `foundation-deploy post-deploy`: the smoke test (`_smoke_test`,
144
- // `_fs_probe`) against DEFAULT, then promotion of the new version onto `live`.
175
+ // `foundation-deploy post-deploy` (and `deploy`'s inline promotion): the
176
+ // smoke test (`_smoke_test`, `_fs_probe`) against DEFAULT, then promotion
177
+ // of the new version onto `live`, which CREATES `live` when an instance
178
+ // brought up by `deploy` alone has none yet (`promoteEndpoint`).
145
179
  new iam.PolicyStatement({
146
180
  sid: "SmokeTestRuntime",
147
181
  actions: [
@@ -150,6 +184,7 @@ export function deployStatements(
150
184
  "bedrock-agentcore:ListAgentRuntimeVersions",
151
185
  "bedrock-agentcore:GetAgentRuntimeEndpoint",
152
186
  "bedrock-agentcore:UpdateAgentRuntimeEndpoint",
187
+ "bedrock-agentcore:CreateAgentRuntimeEndpoint",
153
188
  ],
154
189
  resources: [
155
190
  `arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:runtime/${names.runtimeName}*`,
package/src/index.ts CHANGED
@@ -4,11 +4,13 @@ export {
4
4
  RELEASE_BUCKET_ENV,
5
5
  RELEASE_DIR_ENV,
6
6
  type LambdaArtifactId,
7
+ type ReleaseAccess,
7
8
  type ReleaseSource,
8
9
  agentImage,
9
10
  agentImageTagRequired,
10
11
  ecrRepositoryArn,
11
12
  lambdaCode,
13
+ releaseAccess,
12
14
  releaseCacheDir,
13
15
  releaseImageRepositoryArn,
14
16
  releaseSource,
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Where a deployer gets a release from, and whose signature it accepts.
3
+ *
4
+ * Pure, like `manifest.ts`: the deploy tool reads these to fetch and verify a
5
+ * release, and the CDK app reads them to give the deployer roles
6
+ * (`deploy-permissions.ts`) the release-account permissions that fetch needs.
7
+ * Both must resolve to the same bucket and the same key, so the resolution
8
+ * lives here once rather than in each.
9
+ */
10
+
11
+ /**
12
+ * The bucket a release is fetched from when nothing says otherwise.
13
+ *
14
+ * One name, owned by Foundry 41, created by `release-account-stack.ts`. A
15
+ * mirror is `--release-bucket` or `FOUNDATION_RELEASE_BUCKET`, and the
16
+ * signature is what makes a mirror safe to use.
17
+ */
18
+ export const DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
19
+
20
+ /** The env var that names the key a deployer will accept a manifest from. */
21
+ export const RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
22
+
23
+ /**
24
+ * The Foundation release signing key, in the Foundry 41 release account
25
+ * (793593623536), created by `ReleaseAccountStack` on 2026-09-20.
26
+ *
27
+ * This constant is what makes signature verification mean anything: a
28
+ * deployer refuses a manifest signed by any other key. Changing it rotates
29
+ * the root of trust for every customer, so it changes only with a key
30
+ * rotation and a release that says so. {@link RELEASE_KMS_KEY_ARN_ENV}
31
+ * overrides it, for a private mirror or a test key.
32
+ */
33
+ export const RELEASE_KMS_KEY_ARN: string | undefined =
34
+ "arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
35
+
36
+ /** The key ARN a deployer will accept a manifest from, or `undefined` when none is configured. */
37
+ export function configuredKeyArn(
38
+ env: Record<string, string | undefined> = process.env,
39
+ ): string | undefined {
40
+ const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
41
+ return configured === undefined || configured === "" ? undefined : configured;
42
+ }
43
+
44
+ /** The key ARN a deployer will accept a manifest from. Throws when none is configured. */
45
+ export function expectedKeyArn(env: Record<string, string | undefined> = process.env): string {
46
+ const configured = configuredKeyArn(env);
47
+ if (configured === undefined)
48
+ throw new Error(
49
+ `no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`,
50
+ );
51
+ return configured;
52
+ }