@deployfoundation/foundation-deploy 0.2.51 → 0.2.52

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,30 +1,34 @@
1
+ import {
2
+ agentImage,
3
+ agentImageTagRequired,
4
+ lambdaCode,
5
+ releaseAccess,
6
+ releaseImageRepositoryArn,
7
+ releaseSource
8
+ } from "./chunk-pxhftba1.js";
1
9
  import {
2
10
  CUSTOMIZATION_ARTIFACT_NAME,
3
11
  FOUNDATION_VERSION_RE,
4
12
  LIVE_ENDPOINT_NAME,
5
13
  WEB_SEARCH_TARGET,
6
- agentImage,
7
- agentImageTagRequired,
8
14
  capabilityEnabled,
15
+ instanceNames,
16
+ newsletterManifestFor,
17
+ provisionsIntegration,
18
+ requiresAuthenticatedQueue,
19
+ slackCommandPrefixes
20
+ } from "./chunk-rxetj2p9.js";
21
+ import {
9
22
  capabilityScheduledJobs,
10
23
  crmOutreachConfigured,
11
24
  crmPolicyFingerprint,
12
25
  encodeProactiveChannels,
13
- instanceNames,
14
26
  isScheduledJob,
15
- lambdaCode,
16
- newsletterManifestFor,
17
27
  parseInstanceConfig,
18
28
  parseSchedule,
19
- provisionsIntegration,
20
- releaseAccess,
21
- releaseImageRepositoryArn,
22
- releaseSource,
23
- requiresAuthenticatedQueue,
24
29
  scheduledJobBindings,
25
- slackCommandPrefixes,
26
30
  webhookJobBindings
27
- } from "./chunk-gnz747z2.js";
31
+ } from "./chunk-96fjaysn.js";
28
32
 
29
33
  // src/stacks/agent-stack.ts
30
34
  import * as cdk from "aws-cdk-lib";
@@ -221,20 +225,27 @@ class FoundationAgent extends cdk.Stack {
221
225
  role.addToPolicy(new iam.PolicyStatement({
222
226
  sid: "DenyDirectCrmTableAccess",
223
227
  effect: iam.Effect.DENY,
224
- actions: [
225
- "dynamodb:GetItem",
226
- "dynamodb:BatchGetItem",
227
- "dynamodb:TransactGetItems",
228
- "dynamodb:Query",
229
- "dynamodb:Scan",
230
- "dynamodb:PartiQLSelect",
231
- "dynamodb:ExecuteStatement",
232
- "dynamodb:BatchExecuteStatement",
233
- "dynamodb:ExportTableToPointInTime"
234
- ],
228
+ actions: ["dynamodb:*"],
235
229
  resources
236
230
  }));
237
231
  }
232
+ denyDirectCrmHttpSecretAccess(role, instance) {
233
+ if (!provisionsIntegration(instance, "crm") || instance.crm.provider.kind !== "http")
234
+ return;
235
+ role.addToPolicy(new iam.PolicyStatement({
236
+ sid: "DenyCrmHttpSecret",
237
+ effect: iam.Effect.DENY,
238
+ actions: ["secretsmanager:*"],
239
+ resources: [
240
+ cdk.Stack.of(this).formatArn({
241
+ service: "secretsmanager",
242
+ resource: "secret",
243
+ resourceName: `${this.names.secretCrmHttp}-??????`,
244
+ arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME
245
+ })
246
+ ]
247
+ }));
248
+ }
238
249
  denyDirectKnockSecretAccess(role, props) {
239
250
  if (!provisionsIntegration(props.instance, "knock"))
240
251
  return;
@@ -385,6 +396,7 @@ class FoundationAgent extends cdk.Stack {
385
396
  managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("ReadOnlyAccess")]
386
397
  });
387
398
  this.denyDirectCrmTableAccess(role, props.instance, props.crmTable);
399
+ this.denyDirectCrmHttpSecretAccess(role, props.instance);
388
400
  this.denyDirectKnockSecretAccess(role, props);
389
401
  this.denyDirectEmailSecretAccess(role, props.secrets.googleEmail, instanceNames(props.instance).secretGoogleEmail);
390
402
  this.denyDirectDocsSecretAccess(role, instanceNames(props.instance).secretGoogleDocs);
@@ -454,6 +466,7 @@ class FoundationAgent extends cdk.Stack {
454
466
  description: `${props.instance.displayName} AgentCore execution role (least privilege)`
455
467
  });
456
468
  this.denyDirectCrmTableAccess(role, props.instance, props.crmTable);
469
+ this.denyDirectCrmHttpSecretAccess(role, props.instance);
457
470
  this.denyDirectKnockSecretAccess(role, props);
458
471
  this.denyDirectUpworkApprovalAccess(role, props.upworkApprovalTable);
459
472
  this.denyDirectEmailSecretAccess(role, props.secrets.googleEmail, instanceNames(props.instance).secretGoogleEmail);
@@ -1550,12 +1563,14 @@ class FoundationApi extends cdk2.Stack {
1550
1563
  }
1551
1564
  if (provisionsIntegration(props.instance, "crm")) {
1552
1565
  const crm = crmConfigForProvisioning(props.configPath);
1566
+ const provider = props.instance.crm.provider;
1553
1567
  const crmTable = props.crmTable;
1554
1568
  if (crmTable === undefined)
1555
1569
  throw new Error(`${id}: integrations.crm is enabled but no CRM table was supplied by FoundationData`);
1570
+ const usesTable = provider.kind === "dynamodb" || crm.outreach !== undefined;
1556
1571
  const crmProxyRole = new iam2.Role(this, "CrmProxyRole", {
1557
1572
  assumedBy: new iam2.ServicePrincipal("lambda.amazonaws.com"),
1558
- description: `${display} CRM proxy Lambda role (self-hosted CRM table access only)`,
1573
+ description: provider.kind === "dynamodb" ? `${display} CRM proxy Lambda role (self-hosted CRM table access only)` : `${display} CRM proxy Lambda role (crm/1 key and outreach ledger only)`,
1559
1574
  managedPolicies: [
1560
1575
  iam2.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
1561
1576
  ]
@@ -1565,30 +1580,83 @@ class FoundationApi extends cdk2.Stack {
1565
1580
  actions: ["secretsmanager:GetSecretValue"],
1566
1581
  resources: [secretArnPattern2(props.secrets.signing)]
1567
1582
  }));
1568
- crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1569
- sid: "CrmTableData",
1570
- actions: [
1571
- "dynamodb:GetItem",
1572
- "dynamodb:PutItem",
1573
- "dynamodb:UpdateItem",
1574
- "dynamodb:DeleteItem",
1575
- "dynamodb:BatchGetItem",
1576
- "dynamodb:BatchWriteItem",
1577
- "dynamodb:TransactWriteItems",
1578
- "dynamodb:ConditionCheckItem"
1579
- ],
1580
- resources: [crmTable.tableArn]
1581
- }));
1582
- crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1583
- sid: "CrmRecordsIndex",
1584
- actions: ["dynamodb:Query"],
1585
- resources: [crmTable.tableArn, `${crmTable.tableArn}/index/RecordsIndex`]
1586
- }));
1587
- crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1588
- sid: "CrmTableKey",
1589
- actions: ["kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
1590
- resources: [(crmTable.encryptionKey ?? props.dataKey).keyArn]
1591
- }));
1583
+ if (provider.kind === "dynamodb") {
1584
+ crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1585
+ sid: "CrmTableData",
1586
+ actions: [
1587
+ "dynamodb:GetItem",
1588
+ "dynamodb:PutItem",
1589
+ "dynamodb:UpdateItem",
1590
+ "dynamodb:DeleteItem",
1591
+ "dynamodb:BatchGetItem",
1592
+ "dynamodb:BatchWriteItem",
1593
+ "dynamodb:TransactWriteItems",
1594
+ "dynamodb:ConditionCheckItem"
1595
+ ],
1596
+ resources: [crmTable.tableArn]
1597
+ }));
1598
+ crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1599
+ sid: "CrmRecordsIndex",
1600
+ actions: ["dynamodb:Query"],
1601
+ resources: [crmTable.tableArn, `${crmTable.tableArn}/index/RecordsIndex`]
1602
+ }));
1603
+ } else {
1604
+ crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1605
+ sid: "CrmHttpSecret",
1606
+ actions: ["secretsmanager:GetSecretValue"],
1607
+ resources: [
1608
+ secretArnPattern2(secretsmanager.Secret.fromSecretNameV2(this, "CrmHttpSecret", names.secretCrmHttp))
1609
+ ]
1610
+ }));
1611
+ if (usesTable)
1612
+ crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1613
+ sid: "CrmOutreachLedger",
1614
+ actions: [
1615
+ "dynamodb:GetItem",
1616
+ "dynamodb:PutItem",
1617
+ "dynamodb:UpdateItem",
1618
+ "dynamodb:DeleteItem",
1619
+ "dynamodb:TransactWriteItems"
1620
+ ],
1621
+ resources: [crmTable.tableArn],
1622
+ conditions: {
1623
+ "ForAllValues:StringEquals": {
1624
+ "dynamodb:LeadingKeys": [crmOutreachLedgerPartition(props.instance.name)]
1625
+ }
1626
+ }
1627
+ }));
1628
+ }
1629
+ if (usesTable)
1630
+ crmProxyRole.addToPolicy(new iam2.PolicyStatement({
1631
+ sid: "CrmTableKey",
1632
+ actions: ["kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
1633
+ resources: [(crmTable.encryptionKey ?? props.dataKey).keyArn]
1634
+ }));
1635
+ const release = releaseVersionContext(this);
1636
+ const crmProxyEnvironment = {
1637
+ FOUNDATION_CRM_TABLE_NAME: crmTable.tableName,
1638
+ FOUNDATION_SLACK_SIGNING_SECRET_ID: props.secrets.signing.secretName,
1639
+ FOUNDATION_SLACK_TEAM_ID: props.instance.slack.teamId,
1640
+ FOUNDATION_CRM_TENANT_ID: props.instance.name,
1641
+ FOUNDATION_CRM_ALLOWED_CHANNELS: JSON.stringify(crm.channels),
1642
+ FOUNDATION_CRM_ALLOWED_STATUSES: JSON.stringify(crm.statuses),
1643
+ FOUNDATION_CRM_ACTIVITY_TYPES: JSON.stringify(crm.activityTypes),
1644
+ FOUNDATION_CRM_MAX_BATCH_SIZE: String(crm.maxBatchSize),
1645
+ FOUNDATION_CRM_STATUS_POLICY: JSON.stringify(crm.statusPolicy),
1646
+ FOUNDATION_CRM_DELETES: String(crm.deletes),
1647
+ ...crm.outreach === undefined ? {} : {
1648
+ FOUNDATION_CRM_OUTREACH: JSON.stringify(crm.outreach),
1649
+ FOUNDATION_CRM_TIMEZONE: crm.timezone
1650
+ },
1651
+ ...provider.kind === "http" ? {
1652
+ FOUNDATION_CRM_PROVIDER: "http",
1653
+ FOUNDATION_CRM_HTTP_BASE_URL: provider.baseUrl,
1654
+ FOUNDATION_CRM_HTTP_SECRET_ID: names.secretCrmHttp,
1655
+ FOUNDATION_CRM_HTTP_EXPECTED_TENANT: provider.expectedTenant,
1656
+ ...release === undefined ? {} : { FOUNDATION_RELEASE: release }
1657
+ } : {}
1658
+ };
1659
+ assertLambdaEnvironmentBudget(crmProxyEnvironment);
1592
1660
  this.crmProxyFunction = new lambda.Function(this, "CrmProxyFunction", {
1593
1661
  functionName: names.crmProxyFunctionName,
1594
1662
  runtime: lambda.Runtime.NODEJS_22_X,
@@ -1597,20 +1665,7 @@ class FoundationApi extends cdk2.Stack {
1597
1665
  role: crmProxyRole,
1598
1666
  timeout: cdk2.Duration.seconds(30),
1599
1667
  memorySize: 256,
1600
- environment: {
1601
- FOUNDATION_CRM_TABLE_NAME: crmTable.tableName,
1602
- FOUNDATION_SLACK_SIGNING_SECRET_ID: props.secrets.signing.secretName,
1603
- FOUNDATION_SLACK_TEAM_ID: props.instance.slack.teamId,
1604
- FOUNDATION_CRM_TENANT_ID: props.instance.name,
1605
- FOUNDATION_CRM_ALLOWED_CHANNELS: JSON.stringify(crm.channels),
1606
- FOUNDATION_CRM_ALLOWED_STATUSES: JSON.stringify(crm.statuses),
1607
- FOUNDATION_CRM_ACTIVITY_TYPES: JSON.stringify(crm.activityTypes),
1608
- FOUNDATION_CRM_MAX_BATCH_SIZE: String(crm.maxBatchSize),
1609
- ...crm.outreach === undefined ? {} : {
1610
- FOUNDATION_CRM_OUTREACH: JSON.stringify(crm.outreach),
1611
- FOUNDATION_CRM_TIMEZONE: crm.timezone
1612
- }
1613
- }
1668
+ environment: crmProxyEnvironment
1614
1669
  });
1615
1670
  new cdk2.CfnOutput(this, "CrmProxyFunctionArn", {
1616
1671
  value: this.crmProxyFunction.functionArn
@@ -1660,6 +1715,12 @@ class FoundationApi extends cdk2.Stack {
1660
1715
  agentRuntimeArn: props.agentRuntimeArn,
1661
1716
  alarmAction
1662
1717
  });
1718
+ if (this.crmProxyFunction !== undefined && props.instance.crm.provider.kind === "http")
1719
+ addCrmProviderAlarms(this, {
1720
+ instance: props.instance,
1721
+ functionName: names.crmProxyFunctionName,
1722
+ alarmAction
1723
+ });
1663
1724
  let dispatcherFunctionName;
1664
1725
  if (configuredScheduledJobs.length > 0) {
1665
1726
  const dispatchRole = new iam2.Role(this, "ScheduledJobDispatcherRole", {
@@ -1782,34 +1843,13 @@ function addConfigLoadAlarm(stack, args) {
1782
1843
  return;
1783
1844
  const runtimeId = cdk2.Fn.select(1, cdk2.Fn.split("/", args.agentRuntimeArn));
1784
1845
  const logGroupName = `/aws/bedrock-agentcore/runtimes/${runtimeId}-${LIVE_ENDPOINT_NAME}`;
1785
- const role = new iam2.Role(stack, "AgentLiveLogGroupRole", {
1786
- assumedBy: new iam2.ServicePrincipal("lambda.amazonaws.com"),
1787
- description: `${args.instance.displayName} creates the runtime's live log group when missing`,
1788
- managedPolicies: [
1789
- iam2.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
1790
- ]
1791
- });
1792
- role.addToPolicy(new iam2.PolicyStatement({
1846
+ const ensureLogGroup = ensureLogGroupExists(stack, {
1847
+ id: "AgentLiveLogGroup",
1793
1848
  sid: "CreateRuntimeLiveLogGroup",
1794
- actions: ["logs:CreateLogGroup"],
1795
- resources: [
1796
- `arn:${stack.partition}:logs:${stack.region}:${stack.account}:log-group:${logGroupName}`
1797
- ]
1798
- }));
1799
- const ensureFunction = new lambda.Function(stack, "AgentLiveLogGroupFunction", {
1800
- runtime: lambda.Runtime.NODEJS_22_X,
1801
- handler: "index.runtimeLogGroupHandler",
1802
- code: lambdaCode(stack, "gateway"),
1803
- role,
1804
- timeout: cdk2.Duration.seconds(30),
1805
- memorySize: 128
1806
- });
1807
- const ensureLogGroup = new cdk2.CustomResource(stack, "AgentLiveLogGroup", {
1808
- serviceToken: ensureFunction.functionArn,
1809
1849
  resourceType: "Custom::FoundationRuntimeLogGroup",
1810
- properties: { LogGroupName: logGroupName }
1850
+ description: `${args.instance.displayName} creates the runtime's live log group when missing`,
1851
+ logGroupName
1811
1852
  });
1812
- ensureLogGroup.node.addDependency(role);
1813
1853
  const filter = new logs2.MetricFilter(stack, "ConfigLoadFailureMetricFilter", {
1814
1854
  logGroup: logs2.LogGroup.fromLogGroupName(stack, "AgentLiveLogs", logGroupName),
1815
1855
  filterPattern: logs2.FilterPattern.any(...CONFIG_LOAD_FAILURE_EVENTS.map((event) => logs2.FilterPattern.stringValue("$.event", "=", event))),
@@ -1835,22 +1875,136 @@ function addConfigLoadAlarm(stack, args) {
1835
1875
  alarm.addAlarmAction(args.alarmAction);
1836
1876
  return alarm;
1837
1877
  }
1878
+ function ensureLogGroupExists(stack, args) {
1879
+ const role = new iam2.Role(stack, `${args.id}Role`, {
1880
+ assumedBy: new iam2.ServicePrincipal("lambda.amazonaws.com"),
1881
+ description: args.description,
1882
+ managedPolicies: [
1883
+ iam2.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole")
1884
+ ]
1885
+ });
1886
+ role.addToPolicy(new iam2.PolicyStatement({
1887
+ sid: args.sid,
1888
+ actions: ["logs:CreateLogGroup"],
1889
+ resources: [
1890
+ `arn:${stack.partition}:logs:${stack.region}:${stack.account}:log-group:${args.logGroupName}`
1891
+ ]
1892
+ }));
1893
+ const ensureFunction = new lambda.Function(stack, `${args.id}Function`, {
1894
+ runtime: lambda.Runtime.NODEJS_22_X,
1895
+ handler: "index.runtimeLogGroupHandler",
1896
+ code: lambdaCode(stack, "gateway"),
1897
+ role,
1898
+ timeout: cdk2.Duration.seconds(30),
1899
+ memorySize: 128
1900
+ });
1901
+ const ensureLogGroup = new cdk2.CustomResource(stack, args.id, {
1902
+ serviceToken: ensureFunction.functionArn,
1903
+ resourceType: args.resourceType,
1904
+ properties: { LogGroupName: args.logGroupName }
1905
+ });
1906
+ ensureLogGroup.node.addDependency(role);
1907
+ return ensureLogGroup;
1908
+ }
1909
+ var CRM_PROVIDER_ALARMS = [
1910
+ {
1911
+ event: "crm_proxy",
1912
+ code: "tenant_mismatch",
1913
+ name: "TenantMismatch",
1914
+ description: "The CRM answered as a different tenant from crm.provider.expectedTenant, so the CRM proxy refused the reply and the CRM tools fail closed. Check that the key written with crm-key was issued for that tenant, and that expectedTenant is right."
1915
+ },
1916
+ {
1917
+ event: "crm_proxy",
1918
+ code: "upstream_unauthorized",
1919
+ name: "UpstreamUnauthorized",
1920
+ description: "The CRM refused the CRM proxy's key (401), even after the proxy re-read it: revoked, expired or wrong. Issue a new key in the CRM and run foundation-deploy crm-key; the proxy picks it up on its next call."
1921
+ },
1922
+ {
1923
+ event: "crm_proxy",
1924
+ code: "upstream_forbidden",
1925
+ name: "UpstreamForbidden",
1926
+ description: "The CRM answered 403: a scope the key lacks, the CRM's kill switch, or a WAF in front of it blocking the proxy. Check the key's scopes, the switch and the WAF's logs."
1927
+ },
1928
+ {
1929
+ event: "crm_proxy",
1930
+ code: "unconfigured",
1931
+ name: "Unconfigured",
1932
+ description: "The CRM proxy is unconfigured, so every CRM tool fails closed: no key written yet (run foundation-deploy crm-key), or the CRM's /meta does not match the deployment. The log line's check field names which: contract, statuses, activity_types, batch_size or operations."
1933
+ },
1934
+ {
1935
+ event: "crm_proxy_warning",
1936
+ code: "ledger_kept",
1937
+ name: "LedgerKept",
1938
+ description: "A draft's outreach reservation was kept because the CRM's answer was unknown (a timeout, a 5xx, a 429 or a reply without the contract header). The weekly cap counts it, failing closed. Check in the CRM whether that draft was logged."
1939
+ },
1940
+ {
1941
+ event: "crm_proxy_warning",
1942
+ code: "policy_mismatch",
1943
+ name: "PolicyMismatch",
1944
+ description: "The CRM's own policy in /meta differs from the deployment's capabilities.crm (the log line's fields name which). Each side enforces its own, so the stricter wins; align the two."
1945
+ }
1946
+ ];
1947
+ function addCrmProviderAlarms(stack, args) {
1948
+ const logGroupName = `/aws/lambda/${args.functionName}`;
1949
+ const ensureLogGroup = ensureLogGroupExists(stack, {
1950
+ id: "CrmProxyLogGroup",
1951
+ sid: "CreateCrmProxyLogGroup",
1952
+ resourceType: "Custom::FoundationLambdaLogGroup",
1953
+ description: `${args.instance.displayName} creates the CRM proxy's log group when missing`,
1954
+ logGroupName
1955
+ });
1956
+ const logGroup = logs2.LogGroup.fromLogGroupName(stack, "CrmProxyLogs", logGroupName);
1957
+ return CRM_PROVIDER_ALARMS.map((entry) => {
1958
+ const filter = new logs2.MetricFilter(stack, `CrmProxy${entry.name}MetricFilter`, {
1959
+ logGroup,
1960
+ filterPattern: logs2.FilterPattern.all(logs2.FilterPattern.stringValue("$.event", "=", entry.event), logs2.FilterPattern.stringValue("$.code", "=", entry.code)),
1961
+ metricNamespace: `Foundation/${args.instance.naming.prefix}`,
1962
+ metricName: `CrmProxy${entry.name}`,
1963
+ metricValue: "1"
1964
+ });
1965
+ filter.node.addDependency(ensureLogGroup);
1966
+ const alarm = new cloudwatch.Alarm(stack, `CrmProxy${entry.name}Alarm`, {
1967
+ alarmDescription: `${args.instance.displayName} CRM proxy logged ${entry.code}. ${entry.description}`,
1968
+ metric: filter.metric({
1969
+ statistic: "Sum",
1970
+ period: cdk2.Duration.minutes(entry.code === "policy_mismatch" ? 60 : 5)
1971
+ }),
1972
+ threshold: 1,
1973
+ evaluationPeriods: 1,
1974
+ comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
1975
+ treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING
1976
+ });
1977
+ alarm.addAlarmAction(args.alarmAction);
1978
+ return alarm;
1979
+ });
1980
+ }
1981
+ function crmOutreachLedgerPartition(tenantId) {
1982
+ return `TENANT#${tenantId}#OUTREACH`;
1983
+ }
1984
+ function releaseVersionContext(scope) {
1985
+ const explicit = String(scope.node.tryGetContext("foundationVersion") ?? "");
1986
+ const version = explicit !== "" ? explicit : releaseSource(scope)?.version;
1987
+ return version !== undefined && FOUNDATION_VERSION_RE.test(version) ? version : undefined;
1988
+ }
1838
1989
  function crmConfigForProvisioning(configPath) {
1839
1990
  const config = parseInstanceConfig(readFileSync(configPath, "utf8"));
1840
1991
  const crm = config.capabilities.crm;
1841
1992
  const missing = [
1842
1993
  ...crm.enabled ? [] : ["capabilities.crm.enabled"],
1843
1994
  ...crm.channels.length > 0 ? [] : ["capabilities.crm.channels"],
1844
- ...crm.statuses.length > 0 ? [] : ["capabilities.crm.statuses"],
1995
+ ...crm.statuses.company.length > 0 ? [] : ["capabilities.crm.statuses.company"],
1996
+ ...crm.statuses.contact.length > 0 ? [] : ["capabilities.crm.statuses.contact"],
1845
1997
  ...crm.activityTypes.length > 0 ? [] : ["capabilities.crm.activityTypes"]
1846
1998
  ];
1847
1999
  if (missing.length > 0)
1848
2000
  throw new Error(`${configPath}: integrations.crm requires a configured CRM capability (${missing.join(", ")})`);
1849
2001
  return {
1850
2002
  channels: [...crm.channels],
1851
- statuses: [...crm.statuses],
2003
+ statuses: { company: [...crm.statuses.company], contact: [...crm.statuses.contact] },
1852
2004
  activityTypes: [...crm.activityTypes],
1853
2005
  maxBatchSize: crm.maxBatchSize,
2006
+ statusPolicy: crm.statusPolicy,
2007
+ deletes: crm.deletes,
1854
2008
  policyFingerprint: crmPolicyFingerprint(crm),
1855
2009
  ...crmOutreachConfigured(crm.outreach) ? { outreach: crm.outreach } : {},
1856
2010
  timezone: config.timezone
@@ -1929,7 +2083,7 @@ function assertLambdaEnvironmentBudget(values) {
1929
2083
  bytes += cdk2.Token.isUnresolved(value) ? LAMBDA_ENVIRONMENT_TOKEN_BYTES : Buffer.byteLength(value);
1930
2084
  }
1931
2085
  if (bytes > LAMBDA_ENVIRONMENT_MAX_BYTES) {
1932
- throw new Error(`Gateway Lambda environment needs an estimated ${bytes} bytes; the AWS limit is ${LAMBDA_ENVIRONMENT_MAX_BYTES}. Shorten job prompts or reduce configured values.`);
2086
+ throw new Error(`A Lambda environment needs an estimated ${bytes} bytes; the AWS limit is ${LAMBDA_ENVIRONMENT_MAX_BYTES}. Shorten job prompts or reduce configured values.`);
1933
2087
  }
1934
2088
  }
1935
2089
  function scheduledJobsFor(configPath) {
@@ -2120,6 +2274,7 @@ class FoundationData extends cdk4.Stack {
2120
2274
  crmTable;
2121
2275
  upworkApprovalTable;
2122
2276
  etradePendingTable;
2277
+ crmHttpSecret;
2123
2278
  secrets;
2124
2279
  constructor(scope, id, props) {
2125
2280
  super(scope, id, props);
@@ -2238,6 +2393,15 @@ class FoundationData extends cdk4.Stack {
2238
2393
  generateStringKey: "placeholder"
2239
2394
  }
2240
2395
  }) : undefined;
2396
+ this.crmHttpSecret = provisionsIntegration(props.instance, "crm") && props.instance.crm.provider.kind === "http" ? new secretsmanager2.Secret(this, "CrmHttpSecret", {
2397
+ secretName: names.secretCrmHttp,
2398
+ description: `crm/1 key for ${display}'s CRM ({ api_key }); written by foundation-deploy crm-key, read only by the CRM proxy`,
2399
+ removalPolicy: cdk4.RemovalPolicy.DESTROY,
2400
+ generateSecretString: {
2401
+ secretStringTemplate: "{}",
2402
+ generateStringKey: "placeholder"
2403
+ }
2404
+ }) : undefined;
2241
2405
  const upwork = provisionsIntegration(props.instance, "upwork") ? secretShell("UpworkSecret", names.secretUpwork, `Upwork OAuth client and token store for ${display} ({ client_id, client_secret, access_token?, refresh_token?, expires_at?, tenant_id? })`) : undefined;
2242
2406
  const etradeClient = provisionsIntegration(props.instance, "etrade") ? new secretsmanager2.Secret(this, "EtradeClientSecret", {
2243
2407
  secretName: names.secretEtradeClient,
@@ -0,0 +1,4 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ export { __require };