feat: enable CloudWatch Transaction Search on deploy and guard A/B runs - #2356
Conversation
Ports the old CLI's CloudWatch Transaction Search enablement so evaluations have agent spans in `aws/spans` to read. `enableTransactionSearch` runs four idempotent steps — Application Signals discovery, the X-Ray CloudWatch Logs resource policy, the trace-segment destination flip, and the indexing rule — and hard-fails with a new TransactionSearchSetupError if any step is denied. Standalone helper with injectable clients (transactionSearchClients builds the real ones); no deploy wiring yet. Adds @aws-sdk/client-xray and @aws-sdk/client-application-signals.
Call enableTransactionSearch during `project deploy`, after local prerequisite checks and before provisioning/synth, so agent spans reach `aws/spans` for evaluations. Runs on every deploy and hard-fails (TransactionSearchSetupError) if the principal lacks the setup permissions. The enabler is injected into CdkBackend (like the bootstrap probe) so deploy tests exercise the flow without real AWS calls.
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Changes requested
The feature is well-scoped and the test coverage in both transactionSearch.test.ts and the new harness path in cdk.test.ts is solid. One design issue worth resolving before merge:
Transaction Search runs even when the deploy is really a teardown
In src/core/project/backends/cdk.ts around lines 249–253, enableTransactionSearch is invoked before the empty-assembly check at line 284 that routes to teardown(). That means a user running deploy on a project whose spec is empty (e.g. right after project remove all) will still make Application Signals / CloudWatch Logs / X-Ray mutations, and — because the enabler hard-fails — a principal that has deploy-and-destroy permissions but lacks the TS setup permissions will be blocked from tearing down their own stack with a TransactionSearchSetupError. That's a regression in the destroy path.
Options for the author:
- Move the
enableTransactionSearchcall below thecountDeployableResources(...) === 0branch so it only runs on real deploys, not teardowns. Since teardown doesn't need spans, this preserves the "spans always exist after a successful deploy" invariant. - Keep the current position but skip the enable when the assembly resource count is zero (functionally the same as option 1, just later in the code).
- If you deliberately want TS setup on every deploy invocation, document why teardowns should hard-fail on missing TS permissions and mention it in the error message so the user understands what's blocking their destroy.
Minor (not blocking)
- In
cdk.ts, the newimport { enableTransactionSearch, transactionSearchClients }/import type { AwsCredentials }/type TransactionSearchEnabler = …block (lines 32–40) is inserted in the middle of the import block, splitting it before the laterimport type { CreateCloudFormationClient }on line 41. Consider moving theTransactionSearchEnablertype below all imports for consistency with the rest of the file.
Transaction Search delivers agent OTel spans to the `aws/spans` log group that evaluations read. Fold the enable and guard concerns onto one client: - ObservabilityClient gains isTransactionSearchEnabled() (check) and enableTransactionSearch() (the 4 idempotent setup steps). It now takes AwsClients and builds its own CloudWatchClient. - AwsClients gains xray() and applicationSignals() factories, alongside the existing control/data/iam/logs. - Deploy enables Transaction Search through the project backend, hard-failing when the principal lacks the setup permissions. - A/B test runs fail fast with a clear error when Transaction Search is off. The guard lives in the config-based and target-based run handlers so both paths are covered.
|
Claude Security Review: no high-confidence findings. (run) |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor #2356 +/- ##
============================================
- Coverage 97.26% 97.26% -0.01%
============================================
Files 610 610
Lines 40590 40736 +146
============================================
+ Hits 39480 39621 +141
- Misses 1110 1115 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Deploy turns Transaction Search on for the target account/region so evaluations have spans to read. Projects that manage it elsewhere can now opt out with `transactionSearch: false` in the project spec (defaults on), and deploy skips the enable step.
|
Claude Security Review: no high-confidence findings. (run) |
…share destination check - Deploy never fails on Transaction Search: any enable error is caught and skipped with a message (drops the hard-fail and the unavailable-region case). - Share a private transactionSearchActive(options) destination check between isTransactionSearchEnabled and enableTransactionSearch.
|
Claude Security Review: no high-confidence findings. (run) |
…lready active Check transactionSearchActive() first and return early — skip Application Signals discovery, the resource policy, and the destination/indexing calls when Transaction Search is already delivering spans. Only a fresh setup runs them.
|
Claude Security Review: no high-confidence findings. (run) |
Move the deploy opt-out from the project spec to the global CLI config (`agentcore config set transactionSearch false`, default on). The deploy handler reads it from GlobalConfigAccessor and threads it through to the backend gate.
|
Claude Security Review: no high-confidence findings. (run) |
… TUI The deploy screen reads GlobalConfigAccessorKey from its context and threads transactionSearch into projectManager.deploy, matching the CLI handler. Uses the optional ctx.value() so screen tests without the accessor default to enabled.
|
Claude Security Review: no high-confidence findings. (run) |
What
Enable CloudWatch Transaction Search automatically and guard evaluation flows that depend on it. Transaction Search delivers agent OTel spans to the
aws/spanslog group, which evaluations read to score sessions — so this makes sure those spans exist.This folds the former A/B guard PR (#2349) into this one; both the enable and the check now live on a single client.
Changes
ObservabilityClientowns both concerns. It now takesAwsClientsand builds its ownCloudWatchClient, and exposes:isTransactionSearchEnabled(options)— read-only check (DescribeLogGroupsonaws/spans).enableTransactionSearch(params)— the four idempotent setup steps (Application Signals discovery → CloudWatch Logs resource policy → X-Ray trace-segment destination → X-Ray indexing rule).AwsClientsgainsxray()andapplicationSignals()factories, alongside the existingcontrol/data/iam/logs(required, same convention).transactionSearch: falsein its spec to skip the enable step (defaults on) — for projects that manage Transaction Search elsewhere.runhandlers fail fast withTransactionSearchNotEnabledError(pointing to the docs) when Transaction Search is off, closing the gap where the target-based path was unguarded.Idempotency
Every setup step is idempotent, so it is safe to run on each deploy: Application Signals discovery no-ops if already enabled, the resource policy is skipped when already present, the trace destination is only flipped when not already CloudWatch Logs, and the indexing rule is an overwrite.
Testing
tsc --noEmitclean; oxlint + prettier clean.renderer.test.tsare a pre-existing, unrelated untracked file).isTransactionSearchEnabledreturns the correct boolean for both an enabled region and a fresh region;enableTransactionSearchruns all four steps successfully on a fresh region and is idempotent on a second run.Follow-up
Transaction Search enablement currently lives in
CdkBackend.deploy()because that is the only layer where the deploy target's credentials and account are resolved. It is a backend-agnostic concern, though — any backend deploying to an account wants it. As a follow-up, move target resolution (target name → credentials → account) up intoProjectManagerso the enablement can run there once, independent of the chosen backend, instead of being embedded in the CDK backend.