@deployfoundation/foundation-deploy 0.2.13 → 0.2.14

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.
package/dist/bin/app.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  FoundationNetwork,
8
8
  FoundationPipeline,
9
9
  NewsletterStack
10
- } from "../chunk-ww9chzp9.js";
10
+ } from "../chunk-83781ggk.js";
11
11
  import {
12
12
  adminsFor,
13
13
  instanceNames,
@@ -1522,8 +1522,7 @@ function spansIngestionPolicy(account, region) {
1522
1522
  Version: "2012-10-17",
1523
1523
  Statement: [
1524
1524
  statement("SpansFromXray", "aws/spans"),
1525
- statement("ApplicationSignalsFromXray", "/aws/application-signals/data"),
1526
- statement("AgentCoreRuntimeSpansFromXray", "/aws/bedrock-agentcore/runtimes/*")
1525
+ statement("ApplicationSignalsFromXray", "/aws/application-signals/data")
1527
1526
  ]
1528
1527
  });
1529
1528
  }
@@ -1538,7 +1537,7 @@ function hasSpansIngestionPolicy(describeOutput) {
1538
1537
  return false;
1539
1538
  return policies.some((p) => {
1540
1539
  const doc = String(p.policyDocument ?? "");
1541
- return doc.includes("aws/spans") && doc.includes("logs:CreateLogStream") && doc.includes("/aws/bedrock-agentcore/runtimes/");
1540
+ return doc.includes("aws/spans") && doc.includes("logs:CreateLogStream");
1542
1541
  });
1543
1542
  }
1544
1543
  function parseDestination(text) {
@@ -25,6 +25,7 @@ import {
25
25
  import * as cdk from "aws-cdk-lib";
26
26
  import * as ecr from "aws-cdk-lib/aws-ecr";
27
27
  import * as iam from "aws-cdk-lib/aws-iam";
28
+ import * as logs from "aws-cdk-lib/aws-logs";
28
29
  class FoundationAgent extends cdk.Stack {
29
30
  repository;
30
31
  executionRole;
@@ -67,6 +68,7 @@ class FoundationAgent extends cdk.Stack {
67
68
  this.accessPoint = accessPoint;
68
69
  this.executionRole = this.buildExecutionRole(props);
69
70
  this.readOnlyRole = this.buildReadOnlyRole(props);
71
+ this.buildSpansIngestionPolicy();
70
72
  const gateway = this.buildToolGateway(display);
71
73
  this.webSearchUrl = gateway.getAtt("GatewayUrl").toString();
72
74
  this.executionRole.addToPolicy(new iam.PolicyStatement({
@@ -318,6 +320,27 @@ class FoundationAgent extends cdk.Stack {
318
320
  }));
319
321
  return role;
320
322
  }
323
+ buildSpansIngestionPolicy() {
324
+ return new logs.ResourcePolicy(this, "AgentSpansIngestion", {
325
+ resourcePolicyName: `${this.stackName}-AgentSpansFromXray`,
326
+ policyStatements: [
327
+ new iam.PolicyStatement({
328
+ sid: "AgentSpansFromXray",
329
+ principals: [new iam.ServicePrincipal("xray.amazonaws.com")],
330
+ actions: ["logs:PutLogEvents", "logs:CreateLogStream"],
331
+ resources: [
332
+ `arn:${this.partition}:logs:${this.region}:${this.account}:log-group:/aws/bedrock-agentcore/runtimes/${this.names.runtimeName}-*:*`
333
+ ],
334
+ conditions: {
335
+ StringEquals: { "aws:SourceAccount": this.account },
336
+ ArnLike: {
337
+ "aws:SourceArn": `arn:${this.partition}:xray:${this.region}:${this.account}:*`
338
+ }
339
+ }
340
+ })
341
+ ]
342
+ });
343
+ }
321
344
  buildExecutionRole(props) {
322
345
  const role = new iam.Role(this, "AgentExecutionRole", {
323
346
  assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"),
package/dist/src/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  FoundationPipeline,
10
10
  NewsletterStack,
11
11
  deployStatements
12
- } from "../chunk-ww9chzp9.js";
12
+ } from "../chunk-83781ggk.js";
13
13
  import {
14
14
  BUN_VERSION,
15
15
  LAMBDA_ENTRY_POINTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deployfoundation/foundation-deploy",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
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,
@@ -82,11 +82,12 @@ export function spansIngestionPolicy(account: string, region: string): string {
82
82
  Statement: [
83
83
  statement("SpansFromXray", "aws/spans"),
84
84
  statement("ApplicationSignalsFromXray", "/aws/application-signals/data"),
85
- // The agent names its OWN runtime log group in `x-aws-log-group`, and
86
- // X-Ray delivers there too: without this, exports fail with "not
87
- // authorized to perform logs:PutLogEvents on /aws/bedrock-agentcore/
88
- // runtimes/<runtime>-live" (2026-09-07).
89
- statement("AgentCoreRuntimeSpansFromXray", "/aws/bedrock-agentcore/runtimes/*"),
85
+ // NOT the runtime's own log group, which is where the agent's spans
86
+ // actually go (`x-aws-log-group`): that grant is per instance and ships
87
+ // with every deploy as the agent stack's `AgentSpansIngestion` policy.
88
+ // Owning it here made it a one-time `setup` side effect, and an instance
89
+ // that reached its account by `deploy` alone never got it — every export
90
+ // a 400 (Olivia, 2026-09-26).
90
91
  ],
91
92
  });
92
93
  }
@@ -102,11 +103,7 @@ export function hasSpansIngestionPolicy(describeOutput: string): boolean {
102
103
  if (!Array.isArray(policies)) return false;
103
104
  return policies.some((p) => {
104
105
  const doc = String((p as { policyDocument?: unknown }).policyDocument ?? "");
105
- return (
106
- doc.includes("aws/spans") &&
107
- doc.includes("logs:CreateLogStream") &&
108
- doc.includes("/aws/bedrock-agentcore/runtimes/")
109
- );
106
+ return doc.includes("aws/spans") && doc.includes("logs:CreateLogStream");
110
107
  });
111
108
  }
112
109
 
@@ -4,6 +4,7 @@ import type * as ec2 from "aws-cdk-lib/aws-ec2";
4
4
  import * as ecr from "aws-cdk-lib/aws-ecr";
5
5
  import * as iam from "aws-cdk-lib/aws-iam";
6
6
  import type * as kms from "aws-cdk-lib/aws-kms";
7
+ import * as logs from "aws-cdk-lib/aws-logs";
7
8
  import type * as s3 from "aws-cdk-lib/aws-s3";
8
9
  import type * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
9
10
  import type { Construct } from "constructs";
@@ -147,6 +148,7 @@ export class FoundationAgent extends cdk.Stack {
147
148
  this.accessPoint = accessPoint;
148
149
  this.executionRole = this.buildExecutionRole(props);
149
150
  this.readOnlyRole = this.buildReadOnlyRole(props);
151
+ this.buildSpansIngestionPolicy();
150
152
 
151
153
  // The gateway is independent of the runtime: it can (and does) exist
152
154
  // during the phase-one bootstrap deploy.
@@ -523,6 +525,48 @@ export class FoundationAgent extends cdk.Stack {
523
525
  return role;
524
526
  }
525
527
 
528
+ /**
529
+ * Let X-Ray write this runtime's spans into the runtime's own log group.
530
+ *
531
+ * The agent posts OTLP to the X-Ray endpoint with `x-aws-log-group` naming
532
+ * `/aws/bedrock-agentcore/runtimes/<runtime>-<endpoint>` (see
533
+ * `packages/agent/src/observability/otel.ts`). X-Ray, not the execution
534
+ * role, then calls `PutLogEvents` — as a role in an AWS service account —
535
+ * so the execution role's `AgentLogs` grant does not apply, and without a
536
+ * CloudWatch Logs RESOURCE policy every export is a 400 "not authorized to
537
+ * perform: logs:PutLogEvents" (Olivia, 2026-09-26). AWS: "If you set
538
+ * OTEL_EXPORTER_OTLP_TRACES_HEADERS to deliver spans to your own log group,
539
+ * you must also add an Amazon CloudWatch Logs resource policy … allow X-Ray
540
+ * (xray.amazonaws.com) to call logs:PutLogEvents on that log group"
541
+ * (bedrock-agentcore devguide, observability-configure).
542
+ *
543
+ * Logs resource policies are account-level (ten per Region), so the name is
544
+ * the stack's own and the grant covers only this runtime's endpoint groups,
545
+ * and only when X-Ray acts for this account. `CreateLogStream` rides along
546
+ * because X-Ray creates the `spans` stream on first delivery.
547
+ */
548
+ private buildSpansIngestionPolicy(): logs.ResourcePolicy {
549
+ return new logs.ResourcePolicy(this, "AgentSpansIngestion", {
550
+ resourcePolicyName: `${this.stackName}-AgentSpansFromXray`,
551
+ policyStatements: [
552
+ new iam.PolicyStatement({
553
+ sid: "AgentSpansFromXray",
554
+ principals: [new iam.ServicePrincipal("xray.amazonaws.com")],
555
+ actions: ["logs:PutLogEvents", "logs:CreateLogStream"],
556
+ resources: [
557
+ `arn:${this.partition}:logs:${this.region}:${this.account}:log-group:/aws/bedrock-agentcore/runtimes/${this.names.runtimeName}-*:*`,
558
+ ],
559
+ conditions: {
560
+ StringEquals: { "aws:SourceAccount": this.account },
561
+ ArnLike: {
562
+ "aws:SourceArn": `arn:${this.partition}:xray:${this.region}:${this.account}:*`,
563
+ },
564
+ },
565
+ }),
566
+ ],
567
+ });
568
+ }
569
+
526
570
  private buildExecutionRole(props: FoundationAgentProps): iam.Role {
527
571
  const role = new iam.Role(this, "AgentExecutionRole", {
528
572
  assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com"),
@@ -574,9 +618,10 @@ export class FoundationAgent extends cdk.Stack {
574
618
  ],
575
619
  }),
576
620
  );
577
- // Spans: the runtime SigV4-posts OTLP to the X-Ray endpoint, which routes
578
- // them into the log group `AgentLogs` above already covers. None of these
579
- // actions take a resource ARN — X-Ray scopes ingestion by account.
621
+ // Spans: the runtime SigV4-posts OTLP to the X-Ray endpoint. These actions
622
+ // only let it call X-Ray (none takes a resource ARN — X-Ray scopes
623
+ // ingestion by account); X-Ray's own write into the runtime log group is
624
+ // authorized by `AgentSpansIngestion`, not by `AgentLogs` above.
580
625
  role.addToPolicy(
581
626
  new iam.PolicyStatement({
582
627
  sid: "TraceIngest",