Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ import { AddOnlineEvalScreen } from "../handlers/project/add/online-eval/screen.
import { AddOnlineInsightScreen } from "../handlers/project/add/online-insight/screen.tsx";
import { AddHarnessScreen } from "../handlers/project/add/harness/screen.tsx";
import { AddConfigBundleScreen } from "../handlers/project/add/config-bundle/screen.tsx";
import { AddPolicyEngineScreen } from "../handlers/project/add/policy-engine/screen.tsx";
import { AddPolicyScreen } from "../handlers/project/add/policy/screen.tsx";
import { AddPaymentManagerScreen } from "../handlers/project/add/payment-manager/screen.tsx";
import { AddPaymentConnectorScreen } from "../handlers/project/add/payment-connector/screen.tsx";
import { ProjectStatusScreen } from "../handlers/project/status/screen.tsx";
Expand Down Expand Up @@ -935,6 +937,11 @@ function RouteTable({ ctx, core }: ScreenProps) {
path="agentcore/add/config-bundle"
element={<AddConfigBundleScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/add/policy-engine"
element={<AddPolicyEngineScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/add/policy" element={<AddPolicyScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/add/payment-manager"
element={<AddPaymentManagerScreen ctx={ctx} core={core} />}
Expand Down
2 changes: 2 additions & 0 deletions src/handlers/project/add/add.screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const WITH_SCREENS = [
"online-insight",
"harness",
"config-bundle",
"policy-engine",
"policy",
"payment-manager",
"payment-connector",
];
Expand Down
2 changes: 2 additions & 0 deletions src/handlers/project/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function createAddProjectResourceHandler(
"online-insight",
"harness",
"config-bundle",
"policy-engine",
"policy",
"payment-manager",
"payment-connector",
);
Expand Down
92 changes: 66 additions & 26 deletions src/handlers/project/add/policy-engine/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,66 @@
import z from "zod";
import { InputValidationError } from "../../../../errors";
import type { AwsDeploymentTarget } from "../../../../projectSchemas/aws-targets";
import type { PolicyEngineSchema } from "../../../../projectSchemas/policy";
import { createHandler, flag, ProjectKey } from "../../../../router";
import { parseTags } from "../../../utils";
import type { AddResourceInput, Project } from "../../types";
import type { AddProjectResourceConfig } from "../types";
import { addProjectResource, requireDeployedNameFits } from "../shared";

// The deployed name is <project>_<target>_<name>, and the service caps it here.
export const POLICY_ENGINE_DEPLOYED_NAME_MAX = 48;

export type AttachMode = "enforce" | "log-only";

// PolicyEngineInput is the engine as the flags state it: the engine's own
// fields plus, optionally, the project Gateways to attach it to and how.
export type PolicyEngineInput = {
name: string;
description?: string;
encryptionKeyArn?: string;
tags?: z.input<typeof PolicyEngineSchema>["tags"];
attachToGateways?: string[];
attachMode?: AttachMode;
};

// toAddPolicyEngineInput is the one place a Policy Engine is built from user
// input — the flags, or the wizard's answers — so both paths bound the deployed
// name the same way and attach Gateways under the same rule.
export function toAddPolicyEngineInput(
project: Project,
targets: readonly AwsDeploymentTarget[],
input: PolicyEngineInput,
): AddResourceInput {
if (input.attachMode !== undefined && input.attachToGateways === undefined) {
throw new InputValidationError("--attach-mode requires --attach-to-gateways");
}
requireDeployedNameFits(
"Policy Engine",
project.name,
input.name,
"_",
POLICY_ENGINE_DEPLOYED_NAME_MAX,
targets,
);
const engine: z.input<typeof PolicyEngineSchema> = {
name: input.name,
description: input.description,
encryptionKeyArn: input.encryptionKeyArn,
tags: input.tags,
};
return {
resourceType: "policy-engine",
resourceConfig: engine,
attachGateways: input.attachToGateways
? {
names: input.attachToGateways,
mode: input.attachMode === "log-only" ? "LOG_ONLY" : "ENFORCE",
}
: undefined,
};
}

export const createAddPolicyEngineHandler = (config: AddProjectResourceConfig) =>
createHandler({
name: "policy-engine",
Expand All @@ -27,40 +82,25 @@ export const createAddPolicyEngineHandler = (config: AddProjectResourceConfig) =
),
],
handle: async (ctx, flags) => {
if (flags["attach-mode"] !== undefined && flags["attach-to-gateways"] === undefined) {
throw new InputValidationError("--attach-mode requires --attach-to-gateways");
}
const project = ctx.require(ProjectKey);
requireDeployedNameFits(
"Policy Engine",
project.name,
flags.name,
"_",
48,
const input = toAddPolicyEngineInput(
project,
await config.projectManager.listTargets(project),
{
name: flags.name,
description: flags.description,
encryptionKeyArn: flags["encryption-key-arn"],
tags: parseTags(flags.tags),
attachToGateways: flags["attach-to-gateways"],
attachMode: flags["attach-mode"],
},
);

const engine: z.input<typeof PolicyEngineSchema> = {
name: flags.name,
description: flags.description,
encryptionKeyArn: flags["encryption-key-arn"],
tags: parseTags(flags.tags),
};

await addProjectResource(
ctx,
config,
project,
{
resourceType: "policy-engine",
resourceConfig: engine,
attachGateways: flags["attach-to-gateways"]
? {
names: flags["attach-to-gateways"],
mode: flags["attach-mode"] === "log-only" ? "LOG_ONLY" : "ENFORCE",
}
: undefined,
},
input,
`added Policy Engine '${flags.name}' to '${project.name}'`,
{
notes: flags["attach-to-gateways"]
Expand Down
Loading
Loading