From 71144c3b6e5f481ec28c537e94df8cc5567da07d Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Tue, 23 Jun 2026 13:04:43 -0400 Subject: [PATCH] fix: gate `agentcore run ingest` behind ENABLE_GATED_FEATURES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Knowledge bases (FMKB) are gated behind ENABLE_GATED_FEATURES, but `agentcore run ingest` — which operates exclusively on knowledge bases — was reachable regardless of the flag, both as a CLI command and as a TUI Run-menu option. Match the existing FMKB gating used by `add knowledge-base` / `remove knowledge-base`: - CLI: hide the `ingest` subcommand from help when the gate is off, and reject invocation early with "Knowledge bases are not yet available." - TUI: omit the "Ingest knowledge base" item from the Run menu when the gate is off. Adds an e2e gating test driving the built CLI. --- .../run/__tests__/run-ingest-gating.test.ts | 66 +++++++++++++++++++ src/cli/commands/run/command.tsx | 10 ++- src/cli/tui/screens/run-eval/RunScreen.tsx | 18 +++-- 3 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 src/cli/commands/run/__tests__/run-ingest-gating.test.ts diff --git a/src/cli/commands/run/__tests__/run-ingest-gating.test.ts b/src/cli/commands/run/__tests__/run-ingest-gating.test.ts new file mode 100644 index 000000000..67da19791 --- /dev/null +++ b/src/cli/commands/run/__tests__/run-ingest-gating.test.ts @@ -0,0 +1,66 @@ +import { runCLI } from '../../../../test-utils/index.js'; +import { randomUUID } from 'node:crypto'; +import { mkdir, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +/** + * Knowledge bases (FMKB) are gated behind ENABLE_GATED_FEATURES. `agentcore run + * ingest` operates exclusively on knowledge bases, so it must be gated to match + * `add knowledge-base` / `remove knowledge-base`. These tests drive the built + * CLI so we exercise the actual commander registration (hidden command + the + * in-action guard). + */ +describe('run ingest command — FMKB gating', () => { + let testDir: string; + let projectDir: string; + + beforeAll(async () => { + testDir = join(tmpdir(), `agentcore-run-ingest-${randomUUID()}`); + await mkdir(testDir, { recursive: true }); + const projectName = 'TestProj'; + // Create the project with gated features enabled so the KB add path is + // available; the ingest gating is exercised separately below. + const result = await runCLI(['create', '--name', projectName, '--no-agent'], testDir); + if (result.exitCode !== 0) { + throw new Error(`Failed to create project: ${result.stdout} ${result.stderr}`); + } + projectDir = join(testDir, projectName); + }); + + afterAll(async () => { + await rm(testDir, { recursive: true, force: true }); + }); + + it('rejects `run ingest` when ENABLE_GATED_FEATURES is not set', async () => { + const result = await runCLI(['run', 'ingest', '--name', 'kb-default', '--json'], projectDir); + expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(1); + expect(result.stderr).toContain('Knowledge bases are not yet available.'); + }); + + it('hides `ingest` from `run --help` when ENABLE_GATED_FEATURES is not set', async () => { + const result = await runCLI(['run', '--help'], projectDir); + expect(result.exitCode).toBe(0); + expect(result.stdout).not.toContain('ingest'); + }); + + it('exposes `ingest` in `run --help` when ENABLE_GATED_FEATURES=1', async () => { + const result = await runCLI(['run', '--help'], projectDir, { env: { ENABLE_GATED_FEATURES: '1' } }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('ingest'); + }); + + it('passes the gate (fails later on validation) when ENABLE_GATED_FEATURES=1', async () => { + // With the gate open, the command proceeds past the FMKB guard. We pass an + // unknown KB name so it fails on the validation step rather than the gate — + // proving the gate no longer blocks the command. + const result = await runCLI(['run', 'ingest', '--name', 'does-not-exist', '--json'], projectDir, { + env: { ENABLE_GATED_FEATURES: '1' }, + }); + expect(result.exitCode).toBe(1); + const combined = `${result.stdout} ${result.stderr}`; + expect(combined).not.toContain('Knowledge bases are not yet available.'); + expect(combined).toContain('does-not-exist'); + }); +}); diff --git a/src/cli/commands/run/command.tsx b/src/cli/commands/run/command.tsx index db036ce8d..a3ce25c72 100644 --- a/src/cli/commands/run/command.tsx +++ b/src/cli/commands/run/command.tsx @@ -2,6 +2,7 @@ import { ConfigIO, ValidationError, findConfigRoot, serializeResult } from '../. import type { RecommendationType } from '../../aws/agentcore-recommendation'; import { COMMAND_DESCRIPTIONS } from '../../constants'; import { getErrorMessage } from '../../errors'; +import { isGatedFeaturesEnabled } from '../../feature-flags'; import { handleRunEval } from '../../operations/eval'; import type { RunEvalOptions } from '../../operations/eval'; import { runKbIngestionByName } from '../../operations/ingest'; @@ -654,13 +655,20 @@ export const registerRun = (program: Command) => { // now; future ingestible types could add a --type flag. // ────────────────────────────────────────────────────────────────────── runCmd - .command('ingest') + .command('ingest', { hidden: !isGatedFeaturesEnabled() }) .description('Start a fresh ingestion job for every data source on a deployed knowledge base.') .option('--name ', 'Knowledge base name (must exist in agentcore.json)') .option('--target ', 'Deployment target name (defaults to "default")', 'default') .option('--data-source ', 'Ingest only the data source with this URI (default: all sources)') .option('--json', 'Output as JSON [non-interactive]') .action(async (cliOptions: { name?: string; target?: string; dataSource?: string; json?: boolean }) => { + // FMKB (knowledge bases) is gated behind ENABLE_GATED_FEATURES; ingestion + // operates exclusively on knowledge bases, so it must be gated to match + // `add knowledge-base` / `remove knowledge-base`. + if (!isGatedFeaturesEnabled()) { + console.error('Knowledge bases are not yet available.'); + process.exit(1); + } if (!findConfigRoot()) { console.error('No agentcore project found. Run `agentcore create` first.'); process.exit(1); diff --git a/src/cli/tui/screens/run-eval/RunScreen.tsx b/src/cli/tui/screens/run-eval/RunScreen.tsx index fc3b69f80..76b805f9a 100644 --- a/src/cli/tui/screens/run-eval/RunScreen.tsx +++ b/src/cli/tui/screens/run-eval/RunScreen.tsx @@ -1,3 +1,4 @@ +import { isGatedFeaturesEnabled } from '../../../feature-flags'; import { Screen, WizardSelect } from '../../components'; import type { SelectableItem } from '../../components'; import { HELP_TEXT } from '../../constants'; @@ -45,11 +46,18 @@ export function RunScreen({ title: 'Recommendation', description: 'Optimize system prompts or tool descriptions using agent traces.', }, - { - id: 'run-ingest', - title: 'Ingest knowledge base', - description: 'Start an ingestion job for a deployed knowledge base.', - }, + // Knowledge base ingestion is part of FMKB, which is gated behind + // ENABLE_GATED_FEATURES. Hide the option entirely when the gate is off, + // matching the hidden `agentcore run ingest` CLI command. + ...(isGatedFeaturesEnabled() + ? [ + { + id: 'run-ingest', + title: 'Ingest knowledge base', + description: 'Start an ingestion job for a deployed knowledge base.', + }, + ] + : []), { id: 'run-ab-test', title: 'A/B Test',