Skip to content

feat(project): add policy-engine and policy TUI wizards - #2462

Merged
notgitika merged 3 commits into
aws:refactorfrom
notgitika:feat/project-add-policy-tui
Sep 30, 2026
Merged

notgitika merged 3 commits into
aws:refactorfrom
notgitika:feat/project-add-policy-tui

Conversation

@notgitika

@notgitika notgitika commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds interactive agentcore add policy-engine and agentcore add policy wizards, the next two screens in the project add wizard series (design doc: https://artifactory.amazon.dev/view/agentcore-project-add-wizard-designs). Built directly on refactor; no stacking.

policy-engine (name → gateways → review)

  • name — live-validated against PolicyEngineNameSchema and the deployed <project>_<target>_<name> 48-character cap, checked against the longest declared target, so the step reports the real budget rather than the schema's 48.
  • gateways — a checklist of the project's Gateways, skipped entirely when the project has none. Checking one reveals the enforcement mode (enforce default / log-only) beneath the list, which is exactly the --attach-mode requires --attach-to-gateways rule; enter moves from the list into the mode rows, enter again continues. Enter with nothing checked continues without attaching.
  • review says (none) · attach from a Gateway later when nothing was picked. Success suggests bare agentcore add policy as the next step, which opens the policy wizard (naming the engine with --engine would select the headless path).

policy (engine → name → source → statement → enforcement → review)

  • engine — picked from the project's Policy Engines; the empty state points at agentcore add policy-engine.
  • source — "type or paste the Cedar statement" continues to a multi-line editor; "load it from a file" opens a path input in place under that row, validated as an existing readable file before the step advances. A file keeps its provenance: the path is recorded as sourceFile, as --statement file://… does.
  • enforcement — active (default) / log-only.
  • review shows the inferred authorization phase (INITIATE or RETURN_OUTPUT) with a note that it was inferred, since inferAuthorizationPhase is a substring heuristic and the user should see the guess before it is written. A file-sourced statement is loaded when the review mounts, asynchronously and through SourceResolver (the same strict UTF-8 path as --statement file://…), so a Latin-1 file is refused rather than decoded lossily and nothing reads while the path is typed. SourceResolverConfig.stdin became optional for this file-only use.

Both handlers gain a shared builder (toAddPolicyEngineInput, toAddPolicyInput) that the flags and the wizard call, so both paths bound names, attach Gateways, map modes and infer the phase identically. Flag-only, as designed: --description, --encryption-key-arn, --tags on the engine; --description, --validation-mode, --authorization-phase on the policy.

Related Issue

Closes #

Documentation PR

Not applicable: no flags or help text change.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (please describe):

Testing

  • I ran bun test
  • I ran the relevant end-to-end tests with bun run test:e2e, or explained why they are not applicable — TUI-only change, no deployed resources
  • I ran bun run typecheck
  • I ran bun run lint:check
  • I ran bun run format:check
  • I ran bun run build
  • If I modified src/assets/, I updated affected snapshots with bun test <test-file> --update-snapshots and committed them

New screen tests for both wizards: the bare engine matching the flag path byte for byte; attaching to checked Gateways in the revealed mode and the resulting policyEngineConfiguration; enter with nothing checked; up from the mode rows back to the list; name pattern and deployed-name budget; the inline policy with the inferred INITIATE on review; a file-sourced policy recording sourceFile and inferring RETURN_OUTPUT; a missing file kept on the step; a non-UTF-8 file refused on review and on submit; an empty statement refused; a multi-line statement kept as typed; both empty states; rejected adds handing the form back; esc to the menu; and TTY/non-TTY/flag/--json dispatch for each command. Also walked the built bundle in a scratch project.

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

A bare `agentcore add policy-engine` on a TTY asks for a name, bounded
live by the deployed 48-character cap, and — when the project has
Gateways — which to attach the engine to, with the enforcement mode
revealed beneath the checklist once one is checked, which is the
--attach-mode-requires-gateways rule. A bare `agentcore add policy` picks
the engine from the project, names the policy, takes the Cedar statement
either pasted into a multi-line editor or from a file whose path opens in
place under that row and is recorded as sourceFile, asks active or
log-only, and shows the inferred authorization phase on the review so the
heuristic's guess is seen before it is written.

Both handlers gain a shared builder the flags and the wizard call, so the
two paths bound names, attach Gateways, map modes and infer the phase
identically. --description, --encryption-key-arn and --tags on the engine
and --description, --validation-mode and --authorization-phase on the
policy stay flag-only.
@github-actions github-actions Bot added the size/xl PR size: XL label Sep 29, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added agentcore-harness-reviewing AgentCore Harness review in progress claude-security-reviewing Claude Code /security-review in progress labels Sep 29, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 29, 2026

@agentcore-devx-automation agentcore-devx-automation Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AgentCore Harness Review

Verdict: Looks good

Consistent with the earlier project add … TUI wizards in this series. The two new screens are cleanly factored, and refactoring both handlers to funnel through shared toAddPolicyEngineInput / toAddPolicyInput builders is a nice change — it guarantees the wizard and the flag path share deployed-name budgeting, gateway attachment shape, mode mapping, and phase inference.

Notes from the review (all minor / non-blocking):

  • statementOf in policy/screen.tsx calls readFileSync on every render of the review step (via summaryOf). The file was already validated by readableFileSchema on the source step and is read again asynchronously on submit, so the sync read on render is a small unnecessary cost. A useMemo (or reading once when leaving the source step) would tidy this up, but the file is small/local and the schema keeps unreadable files from getting here, so it's fine as is.
  • The wizard shows the inferred authorization phase computed from the readFileSync snapshot on the review, but the submit path re-reads the file async and re-infers before writing. If the file changes between review and submit the value written can differ from the value shown; the "inferred from the statement" label makes this reasonable, and --authorization-phase in the flag path lets users override it. Worth being aware of, not worth changing.
  • PolicyEngineInput.description/encryptionKeyArn/tags are declared on the shared type but the wizard never populates them (they are flag-only by design). Consider a shorter wizard-facing type if you want to make that intent explicit, but the current shape is fine and keeps the two callsites symmetric.

Tests exercise real project state via temp directories rather than mocks, cover the interesting compound-field navigation (list ↔ mode focus, unchecking everything, up-from-first-mode-row), the deployed-name budget against the longest target, both file/inline source paths, the empty-engine empty state, and TTY/flag/--json dispatch. LGTM to merge.

@agentcore-devx-automation agentcore-devx-automation Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label Sep 29, 2026
@codecov-commenter

codecov-commenter commented Sep 29, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.24242% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.26%. Comparing base (773cb64) to head (e161a4a).

Files with missing lines Patch % Lines
src/handlers/project/add/policy-engine/screen.tsx 98.29% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           refactor    #2462      +/-   ##
============================================
+ Coverage     97.24%   97.26%   +0.02%     
============================================
  Files           617      619       +2     
  Lines         43848    44334     +486     
============================================
+ Hits          42639    43121     +482     
- Misses         1209     1213       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@notgitika
notgitika marked this pull request as draft September 29, 2026 22:35
Comment on lines +186 to +188
if (values.source === "file") {
try {
statement = await readFile(values.statementFile, "utf8");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Could we use SourceResolver.resolveText for strict UTF-8 decoding in both submission and preview? A Latin-1 Cedar literal "café" is silently saved as "caf�" here, changing what the policy matches, while the review only shows the filename.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Both the review and the submit now go through SourceResolver.resolveText("statement", "file://…"), so a Latin-1 file is refused with the resolver's own must contain valid UTF-8 message instead of being decoded lossily. That needed SourceResolverConfig.stdin to become optional (a screen has no injected IO and never resolves -); with it omitted, - is refused. Covered by a new test that writes Latin-1 bytes and checks both the review row and the submit.

Comment on lines +92 to +95
function statementOf(values: PolicyFormValues): string | undefined {
if (values.source === "inline") return values.statement;
try {
return readFileSync(values.statementFile, "utf8");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Could we validate the path first, then load the statement asynchronously and keep it for the summary? summaryOf(values) runs on every render, so typing a named-pipe path freezes the TUI before Enter or readableFileSchema can reject it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. The sync read in render is gone. The review step is its own component now, PolicyReview, which loads the statement asynchronously when it mounts — so nothing reads while the path is being typed, and only after readableFileSchema has already accepted it (a FIFO fails isFile() there). The row reads reading <path>… until it resolves, then the inferred phase, or the resolver's error if it fails.

? `attached to ${values.attached.length} ${values.attached.length === 1 ? "Gateway" : "Gateways"} in ${values.mode} mode`
: undefined
}
successNextSteps={[`agentcore add policy --engine ${values.name}`, "agentcore deploy"]}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Could we suggest bare agentcore add policy here so it opens the wizard and asks for the engine? Passing --engine selects headless mode, so copying this command fails with required option '--name' not specified, even in a TTY.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: the next step is bare agentcore add policy now, with a comment on why --engine would defeat it. The test asserts the flag is absent.

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 30, 2026
… path

A file-sourced statement is read through SourceResolver, as `--statement
file://…` reads it, so a file that is not valid UTF-8 is refused instead
of decoded with replacement characters that would change what the policy
matches. The review loads it asynchronously when the step mounts rather
than synchronously on every render, so typing a path never blocks the TUI
and only a path the source step has already accepted is read. The engine
wizard's next step is bare `agentcore add policy`, which opens the policy
wizard; naming the engine with --engine would select the headless path.
SourceResolverConfig.stdin is optional now, for callers that only ever
resolve inline values and files.
…d-policy-tui

# Conflicts:
#	src/components/Root.tsx
#	src/handlers/project/add/add.screen.test.tsx
#	src/handlers/project/add/index.ts
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels Sep 30, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Sep 30, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Sep 30, 2026
@notgitika
notgitika marked this pull request as ready for review September 30, 2026 02:24
@notgitika
notgitika merged commit 3b8330f into aws:refactor Sep 30, 2026
16 of 22 checks passed
@notgitika
notgitika deleted the feat/project-add-policy-tui branch September 30, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants