From 30d72566c8d0e769443305d6c52d2f1b89f30c7b Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 17:29:40 +0000 Subject: [PATCH 1/6] feat(project): resolve all deployed resource types, not just runtimes+harnesses Broaden ProjectManager.resolveDeployedResources so it returns every deployed resource (memory, knowledge-base, credential, evaluator, online-eval, gateway, gateway-target, policy-engine, policy, config-bundle, dataset, payment) with its physical id, alongside the existing runtimes and harnesses. - Widen DeployedProjectResource.resourceType to a new DeployableResource union; add optional parent (policy->engine, gateway-target->gateway). - Generalize findDeployedResourceId with an EXPORT_PARTS table typed 'satisfies Record' so a missing type is a COMPILE error, never a silent miss. ExportName format replicated from @aws/agentcore-cdk logical-ids (not importable: CDK lib, not a CLI dep). - credential ids come from the deployed-state credentials map (never a stack output); payment matches by OutputKey (its CfnOutputs set no ExportName) with a TODO to add exportName in the cdk and fold it in. - Add allowMissing so an undeployed target yields [] instead of throwing; deploy and remove keep the hard failure. Stack resolution unchanged (stackArn-from-state). - Guard the invoke picker to only list runtime/harness now that the resolver returns more types. This function will back the new 'agentcore project status' handler, which returns a JSON status of the project's deployed resources to the customer. --- src/core/project/backends/cdk.test.ts | 123 +++++++++++++++++++++++++ src/core/project/backends/cdk.ts | 118 +++++++++++++++++++++--- src/core/project/backends/types.ts | 2 + src/core/project/manager.tsx | 5 +- src/handlers/project/invoke/screen.tsx | 37 +++++--- src/handlers/project/types.ts | 36 +++++++- 6 files changed, 290 insertions(+), 31 deletions(-) diff --git a/src/core/project/backends/cdk.test.ts b/src/core/project/backends/cdk.test.ts index f152b528b..f9da0c73b 100644 --- a/src/core/project/backends/cdk.test.ts +++ b/src/core/project/backends/cdk.test.ts @@ -705,4 +705,127 @@ describe("CdkBackend.resolveDeployedResources", () => { ).rejects.toThrow(/expects AWS account 111122223333.*999900001111/s); expect(subject.stackReads).toEqual([]); }); + + test("resolves every deployed resource type: exports, payment OutputKey, credential from state, nested parents, underscores", async () => { + const input = await project(); + // The resolver only reads names (and nested target/policy names), so a + // hand-shaped spec is enough here — schema validity is tested elsewhere. + input.spec = { + ...input.spec, + runtimes: [{ name: "web" }], + harnesses: [{ name: "chat" }], + memories: [{ name: "user_mem" }], // underscore must map to -user-mem- + knowledgeBases: [{ name: "kb" }], + credentials: [{ name: "cred" }], // id comes from deployed-state, not outputs + evaluators: [{ name: "ev" }], + onlineEvalConfigs: [{ name: "oe" }], + agentCoreGateways: [{ name: "gw", targets: [{ name: "tgt" }] }], + policyEngines: [{ name: "pe", policies: [{ name: "pol" }] }], + configBundles: [{ name: "cb" }], + datasets: [{ name: "ds" }], + payments: [{ name: "pay" }], + } as unknown as typeof input.spec; + await updateTargetState(json, input.rootPath, TARGET.name, { + stackArn: STACK_ARN, + resources: { credentials: { cred: { credentialProviderArn: "arn:aws:cred/cred" } } }, + }); + const S = "AgentCore-example-default"; + const out = (ExportName: string, OutputValue: string) => ({ ExportName, OutputValue }); + const subject = harness({ + describedStack: { + StackName: S, + CreationTime: new Date(0), + StackStatus: "CREATE_COMPLETE", + Outputs: [ + out(`${S}-web-RuntimeId`, "web-1"), + out(`${S}-Harness-chat-Id`, "chat-1"), + out(`${S}-Memory-user-mem-Id`, "mem-1"), + out(`${S}-KnowledgeBase-kb-Id`, "kb-1"), + out(`${S}-Evaluator-ev-Id`, "ev-1"), + out(`${S}-OnlineEval-oe-Id`, "oe-1"), + out(`${S}-Gateway-gw-Id`, "gw-1"), + out(`${S}-GatewayTarget-tgt-Id`, "tgt-1"), + out(`${S}-PolicyEngine-pe-Id`, "pe-1"), + out(`${S}-Policy-pe-pol-Id`, "pol-1"), + out(`${S}-ConfigBundle-cb-Id`, "cb-1"), + out(`${S}-Dataset-ds-Id`, "ds-1"), + // payment: no ExportName — only a predictable OutputKey + { OutputKey: "PaymentpayManagerId", OutputValue: "pay-1" }, + ], + }, + }); + + const resources = await subject.backend.resolveDeployedResources(input, { target: TARGET }); + + expect(resources).toEqual([ + { resourceType: "runtime", name: "web", id: "web-1", target: TARGET }, + { resourceType: "harness", name: "chat", id: "chat-1", target: TARGET }, + { resourceType: "memory", name: "user_mem", id: "mem-1", target: TARGET }, + { resourceType: "knowledge-base", name: "kb", id: "kb-1", target: TARGET }, + { resourceType: "credential", name: "cred", id: "arn:aws:cred/cred", target: TARGET }, + { resourceType: "evaluator", name: "ev", id: "ev-1", target: TARGET }, + { resourceType: "online-eval", name: "oe", id: "oe-1", target: TARGET }, + { resourceType: "gateway", name: "gw", id: "gw-1", target: TARGET }, + { resourceType: "gateway-target", name: "tgt", parent: "gw", id: "tgt-1", target: TARGET }, + { resourceType: "policy-engine", name: "pe", id: "pe-1", target: TARGET }, + { resourceType: "policy", name: "pol", parent: "pe", id: "pol-1", target: TARGET }, + { resourceType: "config-bundle", name: "cb", id: "cb-1", target: TARGET }, + { resourceType: "dataset", name: "ds", id: "ds-1", target: TARGET }, + { resourceType: "payment", name: "pay", id: "pay-1", target: TARGET }, + ]); + expect(subject.stackReads).toHaveLength(1); + }); + + test("omits a declared non-runtime resource that has no deployed output", async () => { + const input = await project(); + input.spec = { + ...input.spec, + runtimes: [], + harnesses: [], + memories: [{ name: "mem" }], + } as unknown as typeof input.spec; + await updateTargetState(json, input.rootPath, TARGET.name, { stackArn: STACK_ARN }); + const subject = harness({ + describedStack: { + StackName: "AgentCore-example-default", + CreationTime: new Date(0), + StackStatus: "CREATE_COMPLETE", + Outputs: [], + }, + }); + + await expect( + subject.backend.resolveDeployedResources(input, { target: TARGET }), + ).resolves.toEqual([]); + }); + + test("allowMissing returns [] instead of throwing when the target has no stack ARN", async () => { + const input = await project(); + const subject = harness({ describedStack: null }); + + await expect( + subject.backend.resolveDeployedResources(input, { target: TARGET, allowMissing: true }), + ).resolves.toEqual([]); + expect(subject.stackReads).toEqual([]); + }); + + test("allowMissing returns [] instead of throwing when the recorded stack is gone", async () => { + const input = await project(); + await updateTargetState(json, input.rootPath, TARGET.name, { stackArn: STACK_ARN }); + const subject = harness({ describedStack: null }); + + await expect( + subject.backend.resolveDeployedResources(input, { target: TARGET, allowMissing: true }), + ).resolves.toEqual([]); + }); + + test("allowMissing does not swallow a wrong-account error", async () => { + const input = await project(); + await updateTargetState(json, input.rootPath, TARGET.name, { stackArn: STACK_ARN }); + const subject = harness({ account: "999900001111" }); + + await expect( + subject.backend.resolveDeployedResources(input, { target: TARGET, allowMissing: true }), + ).rejects.toThrow(/expects AWS account 111122223333.*999900001111/s); + }); }); diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index 516ce0aa9..e3ad7ab6c 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import type { Stack } from "@aws-sdk/client-cloudformation"; import { MalformedServiceResponseError, ProjectStateError } from "../../../errors/errors"; import type { + DeployableResource, DeployResult, Project, ProjectEvent, @@ -51,17 +52,61 @@ import { describeStack } from "./cdk/stackReader"; type StackDescriber = typeof describeStack; +// Mirrors @aws/agentcore-cdk's exportName() (its src/cdk/logical-ids.ts): join the +// parts with "-" after turning "_" into "-" and dropping anything outside +// [A-Za-z0-9:-]. Replicated rather than imported because that package is a CDK +// construct library, not a CLI dependency — this is the source-of-truth format. +function cfnExportName(...parts: string[]): string { + return parts.map((part) => part.replace(/_/g, "-").replace(/[^a-zA-Z0-9:-]/g, "")).join("-"); +} + +// toCdkId mirrors the payment CfnOutput logical-id construction in the CLI's own +// cdk-stack.ts (assets/cdk/lib/cdk-stack.ts): underscores stripped, rest kept. +function toCdkId(name: string): string { + return name.replace(/_/g, ""); +} + +// The exportName parts (after the stack name) for every type whose deployed id is +// a CloudFormation export. credential + payment are excluded on purpose — credential +// comes from deployed-state, payment matches by OutputKey below. `satisfies Record` +// makes this exhaustive: adding a DeployableResource without a row here is a compile +// error, so a new type can never silently resolve to "not found". +type CfnOutputResource = Exclude; + +const EXPORT_PARTS = { + runtime: (name) => [name, "RuntimeId"], + harness: (name) => ["Harness", name, "Id"], + memory: (name) => ["Memory", name, "Id"], + "knowledge-base": (name) => ["KnowledgeBase", name, "Id"], + evaluator: (name) => ["Evaluator", name, "Id"], + "online-eval": (name) => ["OnlineEval", name, "Id"], + gateway: (name) => ["Gateway", name, "Id"], + "gateway-target": (name) => ["GatewayTarget", name, "Id"], + "policy-engine": (name) => ["PolicyEngine", name, "Id"], + policy: (name, parent) => ["Policy", parent ?? "", name, "Id"], + "config-bundle": (name) => ["ConfigBundle", name, "Id"], + dataset: (name) => ["Dataset", name, "Id"], + // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. + "capacity-provider": (name) => ["CapacityProvider", name, "Id"], +} satisfies Record string[]>; + function findDeployedResourceId( stack: Stack, - input: Pick, + input: { resourceType: Exclude; name: string; parent?: string }, ): string | undefined { if (!stack.StackName) return undefined; - const exportResourceName = input.name.replaceAll("_", "-"); - const exportName = - input.resourceType === "runtime" - ? `${stack.StackName}-${exportResourceName}-RuntimeId` - : `${stack.StackName}-Harness-${exportResourceName}-Id`; - return stack.Outputs?.find((output) => output.ExportName === exportName)?.OutputValue; + // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no + // ExportName, so match by their predictable OutputKey. Once they export a name, + // fold payment into EXPORT_PARTS and delete this branch. + if (input.resourceType === "payment") { + const key = `Payment${toCdkId(input.name)}ManagerId`; + return stack.Outputs?.find((output) => output.OutputKey === key)?.OutputValue; + } + const want = cfnExportName( + stack.StackName, + ...EXPORT_PARTS[input.resourceType](input.name, input.parent), + ); + return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; } export type CdkBackendConfig = { @@ -266,10 +311,11 @@ export class CdkBackend implements ProjectBackend { project: Project, input: ResolveDeployedResourcesBackendInput, ): Promise { - const { target } = input; + const { target, allowMissing } = input; const deployedState = await readDeployedState(this.json, project.rootPath); const stackArn = deployedState.targets[target.name]?.stackArn; if (!stackArn) { + if (allowMissing) return []; throw new ProjectStateError( `Project '${project.name}' is not deployed to target '${target.name}'. ` + `Run 'agentcore project deploy --target ${target.name}' first.`, @@ -279,19 +325,63 @@ export class CdkBackend implements ProjectBackend { const credentials = await this.credentialsForTarget(target); const stack = await this.describeStack(target.region, credentials, stackArn); if (!stack) { + if (allowMissing) return []; throw new ProjectStateError( `Project '${project.name}' is not deployed to target '${target.name}'. ` + `Run 'agentcore project deploy --target ${target.name}' first.`, ); } - const resources = [ - ...project.spec.runtimes.map(({ name }) => ({ resourceType: "runtime" as const, name })), - ...project.spec.harnesses.map(({ name }) => ({ resourceType: "harness" as const, name })), + const { spec } = project; + // Credential ids are never stack outputs — they're created imperatively and + // recorded in deployed-state. Read them from the state we already loaded. + const credentialArns = deployedState.targets[target.name]?.resources?.credentials ?? {}; + + type Declared = { resourceType: DeployableResource; name: string; parent?: string }; + const declared: Declared[] = [ + ...spec.runtimes.map(({ name }) => ({ resourceType: "runtime" as const, name })), + ...spec.harnesses.map(({ name }) => ({ resourceType: "harness" as const, name })), + ...spec.memories.map(({ name }) => ({ resourceType: "memory" as const, name })), + ...spec.knowledgeBases.map(({ name }) => ({ resourceType: "knowledge-base" as const, name })), + ...spec.credentials.map(({ name }) => ({ resourceType: "credential" as const, name })), + ...spec.evaluators.map(({ name }) => ({ resourceType: "evaluator" as const, name })), + ...spec.onlineEvalConfigs.map(({ name }) => ({ resourceType: "online-eval" as const, name })), + ...spec.agentCoreGateways.flatMap((gw) => [ + { resourceType: "gateway" as const, name: gw.name }, + ...(gw.targets ?? []).map(({ name }) => ({ + resourceType: "gateway-target" as const, + name, + parent: gw.name, + })), + ]), + ...(spec.unassignedTargets ?? []).map(({ name }) => ({ + resourceType: "gateway-target" as const, + name, + })), + ...spec.policyEngines.flatMap((engine) => [ + { resourceType: "policy-engine" as const, name: engine.name }, + ...(engine.policies ?? []).map(({ name }) => ({ + resourceType: "policy" as const, + name, + parent: engine.name, + })), + ]), + ...spec.configBundles.map(({ name }) => ({ resourceType: "config-bundle" as const, name })), + ...(spec.datasets ?? []).map(({ name }) => ({ resourceType: "dataset" as const, name })), + ...(spec.payments ?? []).map(({ name }) => ({ resourceType: "payment" as const, name })), + // capacity-provider has no spec array yet — arrives with l3-cdk-constructs#336. ]; - return resources.flatMap((resource) => { - const id = findDeployedResourceId(stack, resource); - return id ? [{ ...resource, id, target }] : []; + + return declared.flatMap((r) => { + const id = + r.resourceType === "credential" + ? credentialArns[r.name]?.credentialProviderArn + : findDeployedResourceId(stack, { + resourceType: r.resourceType, + name: r.name, + parent: r.parent, + }); + return id ? [{ ...r, id, target }] : []; }); } diff --git a/src/core/project/backends/types.ts b/src/core/project/backends/types.ts index dccb11da8..0ac642596 100644 --- a/src/core/project/backends/types.ts +++ b/src/core/project/backends/types.ts @@ -16,6 +16,8 @@ export type DeployBackendInput = { export type ResolveDeployedResourcesBackendInput = { target: AwsDeploymentTarget; + /** When true, an undeployed target yields [] instead of throwing. */ + allowMissing?: boolean; }; /** Builds the deployable artifacts owned by a project's selected backend. */ diff --git a/src/core/project/manager.tsx b/src/core/project/manager.tsx index 4f1b016fb..f4e2168bd 100644 --- a/src/core/project/manager.tsx +++ b/src/core/project/manager.tsx @@ -906,7 +906,10 @@ export class FsProjectManager implements ProjectManager { input: ResolveDeployedResourcesInput, ): Promise { const target = await this.resolveExistingTarget(project, input.target); - const resources = await this.backendFor(project).resolveDeployedResources(project, { target }); + const resources = await this.backendFor(project).resolveDeployedResources(project, { + target, + allowMissing: input.allowMissing, + }); return { resources, target }; } diff --git a/src/handlers/project/invoke/screen.tsx b/src/handlers/project/invoke/screen.tsx index 00ba1386a..1d7501250 100644 --- a/src/handlers/project/invoke/screen.tsx +++ b/src/handlers/project/invoke/screen.tsx @@ -83,24 +83,31 @@ export function ProjectInvokePickerScreen({ ctx, core }: ScreenProps) { const rows = useMemo( () => - (deployed?.resources ?? []).map((resource) => { - if (resource.resourceType === "runtime") { - const configured = project?.spec.runtimes.find(({ name }) => name === resource.name); + // resolveDeployedResources now returns every deployed resource type, but only + // runtimes and harnesses are invokable — drop the rest so they aren't listed. + (deployed?.resources ?? []) + .filter( + (r): r is typeof r & { resourceType: "runtime" | "harness" } => + r.resourceType === "runtime" || r.resourceType === "harness", + ) + .map((resource) => { + if (resource.resourceType === "runtime") { + const configured = project?.spec.runtimes.find(({ name }) => name === resource.name); + return { + ...resource, + type: "Runtime" as const, + protocol: configured?.protocol ?? "HTTP", + source: configured?.codeLocation ?? "-", + }; + } + const configured = project?.spec.harnesses.find(({ name }) => name === resource.name); return { ...resource, - type: "Runtime" as const, - protocol: configured?.protocol ?? "HTTP", - source: configured?.codeLocation ?? "-", + type: "Harness" as const, + protocol: "-", + source: configured?.path ?? "-", }; - } - const configured = project?.spec.harnesses.find(({ name }) => name === resource.name); - return { - ...resource, - type: "Harness" as const, - protocol: "-", - source: configured?.path ?? "-", - }; - }), + }), [deployed, project], ); diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index 5b0b2e7b2..b455ca594 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -143,12 +143,46 @@ export type ResolveDeployedResourceInput = { export type ResolveDeployedResourcesInput = { target: string; + /** + * When true, an undeployed target resolves to an empty resource list instead + * of throwing. `project status` wants to render every declared resource as + * local-only rather than error out before the stack exists; deploy/remove + * still want the hard failure, so it stays opt-in. + */ + allowMissing?: boolean; }; +/** + * Every project resource type that can be surfaced as deployed. Broader than + * {@link ProjectInvokableResource} (runtime/harness) because `project status` + * reports the whole stack, not just what you can invoke. Not derived from + * {@link ProjectResource}: the deployed vocabulary differs (e.g. `payment`, not + * `payment-manager`/`payment-connector`; adds `knowledge-base`, `dataset`, + * `capacity-provider`). + */ +export type DeployableResource = + | "runtime" + | "harness" + | "memory" + | "knowledge-base" + | "credential" + | "evaluator" + | "online-eval" + | "gateway" + | "gateway-target" + | "policy-engine" + | "policy" + | "config-bundle" + | "dataset" + | "payment" + | "capacity-provider"; + export type ResolvedDeployedResource = { - resourceType: ProjectInvokableResource; + resourceType: DeployableResource; name: string; id: string; + /** Owner name for nested types: policy → engine, gateway-target → gateway. */ + parent?: string; target: AwsDeploymentTarget; }; From 44dceb190daaf577846a9d43e8cc21e6f620327a Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 18:27:49 +0000 Subject: [PATCH 2/6] refactor(project): drop dataset from deployed resource scope Datasets are out of scope for 'agentcore project status', so remove them from the resolver: the DeployableResource union, the EXPORT_PARTS table, the spec iteration, and the resolver test. The 'satisfies Record' guard proves the union and the table stayed in sync after the removal. --- src/core/project/backends/cdk.test.ts | 3 --- src/core/project/backends/cdk.ts | 3 +-- src/handlers/project/types.ts | 5 ++--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/core/project/backends/cdk.test.ts b/src/core/project/backends/cdk.test.ts index f9da0c73b..c34aab397 100644 --- a/src/core/project/backends/cdk.test.ts +++ b/src/core/project/backends/cdk.test.ts @@ -722,7 +722,6 @@ describe("CdkBackend.resolveDeployedResources", () => { agentCoreGateways: [{ name: "gw", targets: [{ name: "tgt" }] }], policyEngines: [{ name: "pe", policies: [{ name: "pol" }] }], configBundles: [{ name: "cb" }], - datasets: [{ name: "ds" }], payments: [{ name: "pay" }], } as unknown as typeof input.spec; await updateTargetState(json, input.rootPath, TARGET.name, { @@ -748,7 +747,6 @@ describe("CdkBackend.resolveDeployedResources", () => { out(`${S}-PolicyEngine-pe-Id`, "pe-1"), out(`${S}-Policy-pe-pol-Id`, "pol-1"), out(`${S}-ConfigBundle-cb-Id`, "cb-1"), - out(`${S}-Dataset-ds-Id`, "ds-1"), // payment: no ExportName — only a predictable OutputKey { OutputKey: "PaymentpayManagerId", OutputValue: "pay-1" }, ], @@ -770,7 +768,6 @@ describe("CdkBackend.resolveDeployedResources", () => { { resourceType: "policy-engine", name: "pe", id: "pe-1", target: TARGET }, { resourceType: "policy", name: "pol", parent: "pe", id: "pol-1", target: TARGET }, { resourceType: "config-bundle", name: "cb", id: "cb-1", target: TARGET }, - { resourceType: "dataset", name: "ds", id: "ds-1", target: TARGET }, { resourceType: "payment", name: "pay", id: "pay-1", target: TARGET }, ]); expect(subject.stackReads).toHaveLength(1); diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index e3ad7ab6c..94a895f52 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -85,7 +85,6 @@ const EXPORT_PARTS = { "policy-engine": (name) => ["PolicyEngine", name, "Id"], policy: (name, parent) => ["Policy", parent ?? "", name, "Id"], "config-bundle": (name) => ["ConfigBundle", name, "Id"], - dataset: (name) => ["Dataset", name, "Id"], // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. "capacity-provider": (name) => ["CapacityProvider", name, "Id"], } satisfies Record string[]>; @@ -367,7 +366,7 @@ export class CdkBackend implements ProjectBackend { })), ]), ...spec.configBundles.map(({ name }) => ({ resourceType: "config-bundle" as const, name })), - ...(spec.datasets ?? []).map(({ name }) => ({ resourceType: "dataset" as const, name })), + // datasets are intentionally excluded — out of scope for project status. ...(spec.payments ?? []).map(({ name }) => ({ resourceType: "payment" as const, name })), // capacity-provider has no spec array yet — arrives with l3-cdk-constructs#336. ]; diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index b455ca594..e5bd4125e 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -157,8 +157,8 @@ export type ResolveDeployedResourcesInput = { * {@link ProjectInvokableResource} (runtime/harness) because `project status` * reports the whole stack, not just what you can invoke. Not derived from * {@link ProjectResource}: the deployed vocabulary differs (e.g. `payment`, not - * `payment-manager`/`payment-connector`; adds `knowledge-base`, `dataset`, - * `capacity-provider`). + * `payment-manager`/`payment-connector`; adds `knowledge-base` and + * `capacity-provider`). Datasets are deliberately out of scope for status. */ export type DeployableResource = | "runtime" @@ -173,7 +173,6 @@ export type DeployableResource = | "policy-engine" | "policy" | "config-bundle" - | "dataset" | "payment" | "capacity-provider"; From 4d6475150becfd59301faf3ae426871b9d99aed1 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 21:14:11 +0000 Subject: [PATCH 3/6] refactor(project): resolve every deployed id in one exhaustive switch findDeployedResourceId only knew CloudFormation exports, so its two exceptions leaked outward: payment matched by OutputKey in an early return, and credential -- which is never a stack output at all -- was branched on by the caller. "Where does this id come from" lived in three places. Fold all fourteen types into one resolveResourceId switch that takes both sources (the stack and deployed-state's credential ARNs). The `never` default makes a new DeployableResource a compile error instead of a resource that silently vanishes from `project status`. Renamed off find* because it no longer only searches the stack. Export name literals are unchanged -- each was verified against a real stack, so they are deliberately not derived from the resourceType. --- src/core/project/backends/cdk.ts | 107 ++++++++++++++++++------------- 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index 94a895f52..a1f935023 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -66,46 +66,68 @@ function toCdkId(name: string): string { return name.replace(/_/g, ""); } -// The exportName parts (after the stack name) for every type whose deployed id is -// a CloudFormation export. credential + payment are excluded on purpose — credential -// comes from deployed-state, payment matches by OutputKey below. `satisfies Record` -// makes this exhaustive: adding a DeployableResource without a row here is a compile -// error, so a new type can never silently resolve to "not found". -type CfnOutputResource = Exclude; - -const EXPORT_PARTS = { - runtime: (name) => [name, "RuntimeId"], - harness: (name) => ["Harness", name, "Id"], - memory: (name) => ["Memory", name, "Id"], - "knowledge-base": (name) => ["KnowledgeBase", name, "Id"], - evaluator: (name) => ["Evaluator", name, "Id"], - "online-eval": (name) => ["OnlineEval", name, "Id"], - gateway: (name) => ["Gateway", name, "Id"], - "gateway-target": (name) => ["GatewayTarget", name, "Id"], - "policy-engine": (name) => ["PolicyEngine", name, "Id"], - policy: (name, parent) => ["Policy", parent ?? "", name, "Id"], - "config-bundle": (name) => ["ConfigBundle", name, "Id"], - // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. - "capacity-provider": (name) => ["CapacityProvider", name, "Id"], -} satisfies Record string[]>; - -function findDeployedResourceId( - stack: Stack, - input: { resourceType: Exclude; name: string; parent?: string }, +// resolveResourceId maps a declared resource to its deployed physical id. Most ids +// are CloudFormation exports under a deterministic ExportName; payment and credential +// come from elsewhere, so every source lives in this one switch. The `never` default +// makes a new DeployableResource a compile error rather than a resource that silently +// vanishes from `project status`. +function resolveResourceId( + sources: { + stack: Stack; + /** deployed-state credential ARNs by name — credentials are created imperatively, never by CFN. */ + credentialArns: Record; + }, + input: { resourceType: DeployableResource; name: string; parent?: string }, ): string | undefined { - if (!stack.StackName) return undefined; - // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no - // ExportName, so match by their predictable OutputKey. Once they export a name, - // fold payment into EXPORT_PARTS and delete this branch. - if (input.resourceType === "payment") { - const key = `Payment${toCdkId(input.name)}ManagerId`; - return stack.Outputs?.find((output) => output.OutputKey === key)?.OutputValue; + const { stack, credentialArns } = sources; + const { resourceType, name, parent } = input; + + const byExportName = (...parts: string[]) => { + if (!stack.StackName) return undefined; + const want = cfnExportName(stack.StackName, ...parts); + return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; + }; + + switch (resourceType) { + case "runtime": + return byExportName(name, "RuntimeId"); + case "harness": + return byExportName("Harness", name, "Id"); + case "memory": + return byExportName("Memory", name, "Id"); + case "knowledge-base": + return byExportName("KnowledgeBase", name, "Id"); + case "evaluator": + return byExportName("Evaluator", name, "Id"); + case "online-eval": + return byExportName("OnlineEval", name, "Id"); + case "gateway": + return byExportName("Gateway", name, "Id"); + case "gateway-target": + return byExportName("GatewayTarget", name, "Id"); + case "policy-engine": + return byExportName("PolicyEngine", name, "Id"); + case "policy": + return byExportName("Policy", parent ?? "", name, "Id"); + case "config-bundle": + return byExportName("ConfigBundle", name, "Id"); + // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. + case "capacity-provider": + return byExportName("CapacityProvider", name, "Id"); + // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no + // ExportName, so match by their predictable OutputKey. Once they export a name, + // fold payment in above and delete this case. + case "payment": + return stack.Outputs?.find( + (output) => output.OutputKey === `Payment${toCdkId(name)}ManagerId`, + )?.OutputValue; + case "credential": + return credentialArns[name]?.credentialProviderArn; + default: { + const unhandled: never = resourceType; + return unhandled; + } } - const want = cfnExportName( - stack.StackName, - ...EXPORT_PARTS[input.resourceType](input.name, input.parent), - ); - return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; } export type CdkBackendConfig = { @@ -372,14 +394,7 @@ export class CdkBackend implements ProjectBackend { ]; return declared.flatMap((r) => { - const id = - r.resourceType === "credential" - ? credentialArns[r.name]?.credentialProviderArn - : findDeployedResourceId(stack, { - resourceType: r.resourceType, - name: r.name, - parent: r.parent, - }); + const id = resolveResourceId({ stack, credentialArns }, r); return id ? [{ ...r, id, target }] : []; }); } From d68ac22f106946a804cfa263112011f5351e68e2 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 22:20:59 +0000 Subject: [PATCH 4/6] refactor(project): inline deployed-id resolution into its only caller resolveResourceId had a single call site and took a two-source parameter object (stack + credentialArns) purely to reach values that were already locals there. Closing over them instead removes the parameter object. --- src/core/project/backends/cdk.ts | 120 ++++++++++++++----------------- 1 file changed, 54 insertions(+), 66 deletions(-) diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index a1f935023..3f53291d9 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -1,6 +1,5 @@ import { existsSync } from "node:fs"; import { join } from "node:path"; -import type { Stack } from "@aws-sdk/client-cloudformation"; import { MalformedServiceResponseError, ProjectStateError } from "../../../errors/errors"; import type { DeployableResource, @@ -66,70 +65,6 @@ function toCdkId(name: string): string { return name.replace(/_/g, ""); } -// resolveResourceId maps a declared resource to its deployed physical id. Most ids -// are CloudFormation exports under a deterministic ExportName; payment and credential -// come from elsewhere, so every source lives in this one switch. The `never` default -// makes a new DeployableResource a compile error rather than a resource that silently -// vanishes from `project status`. -function resolveResourceId( - sources: { - stack: Stack; - /** deployed-state credential ARNs by name — credentials are created imperatively, never by CFN. */ - credentialArns: Record; - }, - input: { resourceType: DeployableResource; name: string; parent?: string }, -): string | undefined { - const { stack, credentialArns } = sources; - const { resourceType, name, parent } = input; - - const byExportName = (...parts: string[]) => { - if (!stack.StackName) return undefined; - const want = cfnExportName(stack.StackName, ...parts); - return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; - }; - - switch (resourceType) { - case "runtime": - return byExportName(name, "RuntimeId"); - case "harness": - return byExportName("Harness", name, "Id"); - case "memory": - return byExportName("Memory", name, "Id"); - case "knowledge-base": - return byExportName("KnowledgeBase", name, "Id"); - case "evaluator": - return byExportName("Evaluator", name, "Id"); - case "online-eval": - return byExportName("OnlineEval", name, "Id"); - case "gateway": - return byExportName("Gateway", name, "Id"); - case "gateway-target": - return byExportName("GatewayTarget", name, "Id"); - case "policy-engine": - return byExportName("PolicyEngine", name, "Id"); - case "policy": - return byExportName("Policy", parent ?? "", name, "Id"); - case "config-bundle": - return byExportName("ConfigBundle", name, "Id"); - // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. - case "capacity-provider": - return byExportName("CapacityProvider", name, "Id"); - // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no - // ExportName, so match by their predictable OutputKey. Once they export a name, - // fold payment in above and delete this case. - case "payment": - return stack.Outputs?.find( - (output) => output.OutputKey === `Payment${toCdkId(name)}ManagerId`, - )?.OutputValue; - case "credential": - return credentialArns[name]?.credentialProviderArn; - default: { - const unhandled: never = resourceType; - return unhandled; - } - } -} - export type CdkBackendConfig = { logger: Logger; runner?: ProcessRunner; @@ -359,6 +294,59 @@ export class CdkBackend implements ProjectBackend { const credentialArns = deployedState.targets[target.name]?.resources?.credentials ?? {}; type Declared = { resourceType: DeployableResource; name: string; parent?: string }; + + const byExportName = (...parts: string[]) => { + if (!stack.StackName) return undefined; + const want = cfnExportName(stack.StackName, ...parts); + return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; + }; + + // Where each resource type's deployed id comes from. Keeping every source in one + // switch means the `never` default turns a new DeployableResource into a compile + // error, rather than a resource that silently vanishes from `project status`. + const idOf = ({ resourceType, name, parent }: Declared): string | undefined => { + switch (resourceType) { + case "runtime": + return byExportName(name, "RuntimeId"); + case "harness": + return byExportName("Harness", name, "Id"); + case "memory": + return byExportName("Memory", name, "Id"); + case "knowledge-base": + return byExportName("KnowledgeBase", name, "Id"); + case "evaluator": + return byExportName("Evaluator", name, "Id"); + case "online-eval": + return byExportName("OnlineEval", name, "Id"); + case "gateway": + return byExportName("Gateway", name, "Id"); + case "gateway-target": + return byExportName("GatewayTarget", name, "Id"); + case "policy-engine": + return byExportName("PolicyEngine", name, "Id"); + case "policy": + return byExportName("Policy", parent ?? "", name, "Id"); + case "config-bundle": + return byExportName("ConfigBundle", name, "Id"); + // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. + case "capacity-provider": + return byExportName("CapacityProvider", name, "Id"); + // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no + // ExportName, so match by their predictable OutputKey. Once they export a name, + // fold payment in above and delete this case. + case "payment": + return stack.Outputs?.find( + (output) => output.OutputKey === `Payment${toCdkId(name)}ManagerId`, + )?.OutputValue; + case "credential": + return credentialArns[name]?.credentialProviderArn; + default: { + const unhandled: never = resourceType; + return unhandled; + } + } + }; + const declared: Declared[] = [ ...spec.runtimes.map(({ name }) => ({ resourceType: "runtime" as const, name })), ...spec.harnesses.map(({ name }) => ({ resourceType: "harness" as const, name })), @@ -394,7 +382,7 @@ export class CdkBackend implements ProjectBackend { ]; return declared.flatMap((r) => { - const id = resolveResourceId({ stack, credentialArns }, r); + const id = idOf(r); return id ? [{ ...r, id, target }] : []; }); } From 0c1bd0d824c6473cf70648d7cdc5c002eb26c140 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Tue, 1 Sep 2026 23:56:07 +0000 Subject: [PATCH 5/6] feat(project): report deployed resources by ARN instead of bare id project status surfaces these to customers, where an ARN is the useful identifier. gateway-target stays on its id: AgentCoreMcp exports no -Arn for it yet. --- src/core/project/backends/cdk.test.ts | 80 +++++++++++++++++---------- src/core/project/backends/cdk.ts | 29 +++++----- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/src/core/project/backends/cdk.test.ts b/src/core/project/backends/cdk.test.ts index c34aab397..ac2c632fa 100644 --- a/src/core/project/backends/cdk.test.ts +++ b/src/core/project/backends/cdk.test.ts @@ -21,6 +21,8 @@ const TARGET = { } as const; const STACK_ARN = "arn:aws:cloudformation:us-east-1:111122223333:stack/AgentCore-example-default/abc"; +/** ARN prefix the CDK's `-Arn` exports carry, so fixtures assert ARNs and not bare ids. */ +const ARN = `arn:aws:bedrock-agentcore:${TARGET.region}:${TARGET.account}`; const json = new FsReadWriteJson({ logger: createSilentLogger() }); /** A template holding only what CDK adds itself, as an empty project synthesizes. */ @@ -614,12 +616,12 @@ describe("CdkBackend.resolveDeployedResources", () => { StackStatus: "CREATE_COMPLETE", Outputs: [ { - ExportName: "AgentCore-example-default-checkout-agent-RuntimeId", - OutputValue: "checkout_agent-AbCdEf1234", + ExportName: "AgentCore-example-default-checkout-agent-RuntimeArn", + OutputValue: `${ARN}:runtime/checkout_agent-AbCdEf1234`, }, { - ExportName: "AgentCore-example-default-Harness-support-agent-Id", - OutputValue: "support_agent-AbCdEf1234", + ExportName: "AgentCore-example-default-Harness-support-agent-Arn", + OutputValue: `${ARN}:harness/support_agent-AbCdEf1234`, }, ], }, @@ -631,13 +633,13 @@ describe("CdkBackend.resolveDeployedResources", () => { { resourceType: "runtime", name: "checkout_agent", - id: "checkout_agent-AbCdEf1234", + id: `${ARN}:runtime/checkout_agent-AbCdEf1234`, target: TARGET, }, { resourceType: "harness", name: "support_agent", - id: "support_agent-AbCdEf1234", + id: `${ARN}:harness/support_agent-AbCdEf1234`, target: TARGET, }, ]); @@ -736,19 +738,20 @@ describe("CdkBackend.resolveDeployedResources", () => { CreationTime: new Date(0), StackStatus: "CREATE_COMPLETE", Outputs: [ - out(`${S}-web-RuntimeId`, "web-1"), - out(`${S}-Harness-chat-Id`, "chat-1"), - out(`${S}-Memory-user-mem-Id`, "mem-1"), - out(`${S}-KnowledgeBase-kb-Id`, "kb-1"), - out(`${S}-Evaluator-ev-Id`, "ev-1"), - out(`${S}-OnlineEval-oe-Id`, "oe-1"), - out(`${S}-Gateway-gw-Id`, "gw-1"), + out(`${S}-web-RuntimeArn`, `${ARN}:runtime/web-1`), + out(`${S}-Harness-chat-Arn`, `${ARN}:harness/chat-1`), + out(`${S}-Memory-user-mem-Arn`, `${ARN}:memory/mem-1`), + out(`${S}-KnowledgeBase-kb-Arn`, `${ARN}:knowledge-base/kb-1`), + out(`${S}-Evaluator-ev-Arn`, `${ARN}:evaluator/ev-1`), + out(`${S}-OnlineEval-oe-Arn`, `${ARN}:online-eval/oe-1`), + out(`${S}-Gateway-gw-Arn`, `${ARN}:gateway/gw-1`), + // gateway-target is the one type the CDK exports by id only (no -Arn). out(`${S}-GatewayTarget-tgt-Id`, "tgt-1"), - out(`${S}-PolicyEngine-pe-Id`, "pe-1"), - out(`${S}-Policy-pe-pol-Id`, "pol-1"), - out(`${S}-ConfigBundle-cb-Id`, "cb-1"), + out(`${S}-PolicyEngine-pe-Arn`, `${ARN}:policy-engine/pe-1`), + out(`${S}-Policy-pe-pol-Arn`, `${ARN}:policy/pol-1`), + out(`${S}-ConfigBundle-cb-Arn`, `${ARN}:config-bundle/cb-1`), // payment: no ExportName — only a predictable OutputKey - { OutputKey: "PaymentpayManagerId", OutputValue: "pay-1" }, + { OutputKey: "PaymentpayManagerArn", OutputValue: `${ARN}:payment-manager/pay-1` }, ], }, }); @@ -756,19 +759,40 @@ describe("CdkBackend.resolveDeployedResources", () => { const resources = await subject.backend.resolveDeployedResources(input, { target: TARGET }); expect(resources).toEqual([ - { resourceType: "runtime", name: "web", id: "web-1", target: TARGET }, - { resourceType: "harness", name: "chat", id: "chat-1", target: TARGET }, - { resourceType: "memory", name: "user_mem", id: "mem-1", target: TARGET }, - { resourceType: "knowledge-base", name: "kb", id: "kb-1", target: TARGET }, + { resourceType: "runtime", name: "web", id: `${ARN}:runtime/web-1`, target: TARGET }, + { resourceType: "harness", name: "chat", id: `${ARN}:harness/chat-1`, target: TARGET }, + { resourceType: "memory", name: "user_mem", id: `${ARN}:memory/mem-1`, target: TARGET }, + { + resourceType: "knowledge-base", + name: "kb", + id: `${ARN}:knowledge-base/kb-1`, + target: TARGET, + }, { resourceType: "credential", name: "cred", id: "arn:aws:cred/cred", target: TARGET }, - { resourceType: "evaluator", name: "ev", id: "ev-1", target: TARGET }, - { resourceType: "online-eval", name: "oe", id: "oe-1", target: TARGET }, - { resourceType: "gateway", name: "gw", id: "gw-1", target: TARGET }, + { resourceType: "evaluator", name: "ev", id: `${ARN}:evaluator/ev-1`, target: TARGET }, + { resourceType: "online-eval", name: "oe", id: `${ARN}:online-eval/oe-1`, target: TARGET }, + { resourceType: "gateway", name: "gw", id: `${ARN}:gateway/gw-1`, target: TARGET }, { resourceType: "gateway-target", name: "tgt", parent: "gw", id: "tgt-1", target: TARGET }, - { resourceType: "policy-engine", name: "pe", id: "pe-1", target: TARGET }, - { resourceType: "policy", name: "pol", parent: "pe", id: "pol-1", target: TARGET }, - { resourceType: "config-bundle", name: "cb", id: "cb-1", target: TARGET }, - { resourceType: "payment", name: "pay", id: "pay-1", target: TARGET }, + { + resourceType: "policy-engine", + name: "pe", + id: `${ARN}:policy-engine/pe-1`, + target: TARGET, + }, + { + resourceType: "policy", + name: "pol", + parent: "pe", + id: `${ARN}:policy/pol-1`, + target: TARGET, + }, + { + resourceType: "config-bundle", + name: "cb", + id: `${ARN}:config-bundle/cb-1`, + target: TARGET, + }, + { resourceType: "payment", name: "pay", id: `${ARN}:payment-manager/pay-1`, target: TARGET }, ]); expect(subject.stackReads).toHaveLength(1); }); diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index 3f53291d9..6ef69017f 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -301,42 +301,45 @@ export class CdkBackend implements ProjectBackend { return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; }; - // Where each resource type's deployed id comes from. Keeping every source in one + // Where each resource type's deployed ARN comes from. Keeping every source in one // switch means the `never` default turns a new DeployableResource into a compile // error, rather than a resource that silently vanishes from `project status`. const idOf = ({ resourceType, name, parent }: Declared): string | undefined => { switch (resourceType) { case "runtime": - return byExportName(name, "RuntimeId"); + return byExportName(name, "RuntimeArn"); case "harness": - return byExportName("Harness", name, "Id"); + return byExportName("Harness", name, "Arn"); case "memory": - return byExportName("Memory", name, "Id"); + return byExportName("Memory", name, "Arn"); case "knowledge-base": - return byExportName("KnowledgeBase", name, "Id"); + return byExportName("KnowledgeBase", name, "Arn"); case "evaluator": - return byExportName("Evaluator", name, "Id"); + return byExportName("Evaluator", name, "Arn"); case "online-eval": - return byExportName("OnlineEval", name, "Id"); + return byExportName("OnlineEval", name, "Arn"); case "gateway": - return byExportName("Gateway", name, "Id"); + return byExportName("Gateway", name, "Arn"); + // TODO(cdk): AgentCoreMcp exports GatewayTarget--Id but no -Arn, so this + // is the one resource reported by id. Once the construct exports an Arn, switch + // to byExportName("GatewayTarget", name, "Arn") and this case joins the rest. case "gateway-target": return byExportName("GatewayTarget", name, "Id"); case "policy-engine": - return byExportName("PolicyEngine", name, "Id"); + return byExportName("PolicyEngine", name, "Arn"); case "policy": - return byExportName("Policy", parent ?? "", name, "Id"); + return byExportName("Policy", parent ?? "", name, "Arn"); case "config-bundle": - return byExportName("ConfigBundle", name, "Id"); + return byExportName("ConfigBundle", name, "Arn"); // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. case "capacity-provider": - return byExportName("CapacityProvider", name, "Id"); + return byExportName("CapacityProvider", name, "Arn"); // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no // ExportName, so match by their predictable OutputKey. Once they export a name, // fold payment in above and delete this case. case "payment": return stack.Outputs?.find( - (output) => output.OutputKey === `Payment${toCdkId(name)}ManagerId`, + (output) => output.OutputKey === `Payment${toCdkId(name)}ManagerArn`, )?.OutputValue; case "credential": return credentialArns[name]?.credentialProviderArn; From f52730c454e0820f004f1694013a42bbbe20a4b7 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Wed, 2 Sep 2026 13:10:03 +0000 Subject: [PATCH 6/6] chore(project): drop code comments from deployed-resource resolution --- src/core/project/backends/cdk.test.ts | 5 ----- src/core/project/backends/cdk.ts | 20 -------------------- src/core/project/backends/types.ts | 1 - src/core/project/manager.tsx | 3 --- src/handlers/project/invoke/screen.tsx | 2 -- src/handlers/project/types.ts | 15 --------------- 6 files changed, 46 deletions(-) diff --git a/src/core/project/backends/cdk.test.ts b/src/core/project/backends/cdk.test.ts index ac2c632fa..7cfefd11c 100644 --- a/src/core/project/backends/cdk.test.ts +++ b/src/core/project/backends/cdk.test.ts @@ -21,7 +21,6 @@ const TARGET = { } as const; const STACK_ARN = "arn:aws:cloudformation:us-east-1:111122223333:stack/AgentCore-example-default/abc"; -/** ARN prefix the CDK's `-Arn` exports carry, so fixtures assert ARNs and not bare ids. */ const ARN = `arn:aws:bedrock-agentcore:${TARGET.region}:${TARGET.account}`; const json = new FsReadWriteJson({ logger: createSilentLogger() }); @@ -710,8 +709,6 @@ describe("CdkBackend.resolveDeployedResources", () => { test("resolves every deployed resource type: exports, payment OutputKey, credential from state, nested parents, underscores", async () => { const input = await project(); - // The resolver only reads names (and nested target/policy names), so a - // hand-shaped spec is enough here — schema validity is tested elsewhere. input.spec = { ...input.spec, runtimes: [{ name: "web" }], @@ -745,12 +742,10 @@ describe("CdkBackend.resolveDeployedResources", () => { out(`${S}-Evaluator-ev-Arn`, `${ARN}:evaluator/ev-1`), out(`${S}-OnlineEval-oe-Arn`, `${ARN}:online-eval/oe-1`), out(`${S}-Gateway-gw-Arn`, `${ARN}:gateway/gw-1`), - // gateway-target is the one type the CDK exports by id only (no -Arn). out(`${S}-GatewayTarget-tgt-Id`, "tgt-1"), out(`${S}-PolicyEngine-pe-Arn`, `${ARN}:policy-engine/pe-1`), out(`${S}-Policy-pe-pol-Arn`, `${ARN}:policy/pol-1`), out(`${S}-ConfigBundle-cb-Arn`, `${ARN}:config-bundle/cb-1`), - // payment: no ExportName — only a predictable OutputKey { OutputKey: "PaymentpayManagerArn", OutputValue: `${ARN}:payment-manager/pay-1` }, ], }, diff --git a/src/core/project/backends/cdk.ts b/src/core/project/backends/cdk.ts index 6ef69017f..b853d3fbc 100644 --- a/src/core/project/backends/cdk.ts +++ b/src/core/project/backends/cdk.ts @@ -51,16 +51,10 @@ import { describeStack } from "./cdk/stackReader"; type StackDescriber = typeof describeStack; -// Mirrors @aws/agentcore-cdk's exportName() (its src/cdk/logical-ids.ts): join the -// parts with "-" after turning "_" into "-" and dropping anything outside -// [A-Za-z0-9:-]. Replicated rather than imported because that package is a CDK -// construct library, not a CLI dependency — this is the source-of-truth format. function cfnExportName(...parts: string[]): string { return parts.map((part) => part.replace(/_/g, "-").replace(/[^a-zA-Z0-9:-]/g, "")).join("-"); } -// toCdkId mirrors the payment CfnOutput logical-id construction in the CLI's own -// cdk-stack.ts (assets/cdk/lib/cdk-stack.ts): underscores stripped, rest kept. function toCdkId(name: string): string { return name.replace(/_/g, ""); } @@ -289,8 +283,6 @@ export class CdkBackend implements ProjectBackend { } const { spec } = project; - // Credential ids are never stack outputs — they're created imperatively and - // recorded in deployed-state. Read them from the state we already loaded. const credentialArns = deployedState.targets[target.name]?.resources?.credentials ?? {}; type Declared = { resourceType: DeployableResource; name: string; parent?: string }; @@ -301,9 +293,6 @@ export class CdkBackend implements ProjectBackend { return stack.Outputs?.find((output) => output.ExportName === want)?.OutputValue; }; - // Where each resource type's deployed ARN comes from. Keeping every source in one - // switch means the `never` default turns a new DeployableResource into a compile - // error, rather than a resource that silently vanishes from `project status`. const idOf = ({ resourceType, name, parent }: Declared): string | undefined => { switch (resourceType) { case "runtime": @@ -320,9 +309,6 @@ export class CdkBackend implements ProjectBackend { return byExportName("OnlineEval", name, "Arn"); case "gateway": return byExportName("Gateway", name, "Arn"); - // TODO(cdk): AgentCoreMcp exports GatewayTarget--Id but no -Arn, so this - // is the one resource reported by id. Once the construct exports an Arn, switch - // to byExportName("GatewayTarget", name, "Arn") and this case joins the rest. case "gateway-target": return byExportName("GatewayTarget", name, "Id"); case "policy-engine": @@ -331,12 +317,8 @@ export class CdkBackend implements ProjectBackend { return byExportName("Policy", parent ?? "", name, "Arn"); case "config-bundle": return byExportName("ConfigBundle", name, "Arn"); - // Output arrives with aws/agentcore-l3-cdk-constructs#336; resolves once it ships. case "capacity-provider": return byExportName("CapacityProvider", name, "Arn"); - // TODO(cdk): the CLI's payment CfnOutputs (assets/cdk/lib/cdk-stack.ts) set no - // ExportName, so match by their predictable OutputKey. Once they export a name, - // fold payment in above and delete this case. case "payment": return stack.Outputs?.find( (output) => output.OutputKey === `Payment${toCdkId(name)}ManagerArn`, @@ -379,9 +361,7 @@ export class CdkBackend implements ProjectBackend { })), ]), ...spec.configBundles.map(({ name }) => ({ resourceType: "config-bundle" as const, name })), - // datasets are intentionally excluded — out of scope for project status. ...(spec.payments ?? []).map(({ name }) => ({ resourceType: "payment" as const, name })), - // capacity-provider has no spec array yet — arrives with l3-cdk-constructs#336. ]; return declared.flatMap((r) => { diff --git a/src/core/project/backends/types.ts b/src/core/project/backends/types.ts index 0ac642596..67c0af850 100644 --- a/src/core/project/backends/types.ts +++ b/src/core/project/backends/types.ts @@ -16,7 +16,6 @@ export type DeployBackendInput = { export type ResolveDeployedResourcesBackendInput = { target: AwsDeploymentTarget; - /** When true, an undeployed target yields [] instead of throwing. */ allowMissing?: boolean; }; diff --git a/src/core/project/manager.tsx b/src/core/project/manager.tsx index f4e2168bd..144705073 100644 --- a/src/core/project/manager.tsx +++ b/src/core/project/manager.tsx @@ -889,9 +889,6 @@ export class FsProjectManager implements ProjectManager { const resource = resolved.resources.find( ({ resourceType, name }) => resourceType === input.resourceType && name === input.name, ); - // The declared target wins over the copy on the item: the manager resolved it - // from aws-targets.json, and both invoke handlers pin the AWS region off this - // value, so trusting a backend's echo would let it redirect the call. if (resource) return { ...resource, target: resolved.target }; const label = input.resourceType === "runtime" ? "Runtime" : "Harness"; diff --git a/src/handlers/project/invoke/screen.tsx b/src/handlers/project/invoke/screen.tsx index 1d7501250..411a84ab8 100644 --- a/src/handlers/project/invoke/screen.tsx +++ b/src/handlers/project/invoke/screen.tsx @@ -83,8 +83,6 @@ export function ProjectInvokePickerScreen({ ctx, core }: ScreenProps) { const rows = useMemo( () => - // resolveDeployedResources now returns every deployed resource type, but only - // runtimes and harnesses are invokable — drop the rest so they aren't listed. (deployed?.resources ?? []) .filter( (r): r is typeof r & { resourceType: "runtime" | "harness" } => diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index e5bd4125e..8c9b12e9f 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -143,23 +143,9 @@ export type ResolveDeployedResourceInput = { export type ResolveDeployedResourcesInput = { target: string; - /** - * When true, an undeployed target resolves to an empty resource list instead - * of throwing. `project status` wants to render every declared resource as - * local-only rather than error out before the stack exists; deploy/remove - * still want the hard failure, so it stays opt-in. - */ allowMissing?: boolean; }; -/** - * Every project resource type that can be surfaced as deployed. Broader than - * {@link ProjectInvokableResource} (runtime/harness) because `project status` - * reports the whole stack, not just what you can invoke. Not derived from - * {@link ProjectResource}: the deployed vocabulary differs (e.g. `payment`, not - * `payment-manager`/`payment-connector`; adds `knowledge-base` and - * `capacity-provider`). Datasets are deliberately out of scope for status. - */ export type DeployableResource = | "runtime" | "harness" @@ -180,7 +166,6 @@ export type ResolvedDeployedResource = { resourceType: DeployableResource; name: string; id: string; - /** Owner name for nested types: policy → engine, gateway-target → gateway. */ parent?: string; target: AwsDeploymentTarget; };