Skip to content

feat: ship a Kiro power that drives this accelerator - #41

Open
omrsamer wants to merge 2 commits into
aws-samples:mainfrom
omrsamer:feat/kiro-power
Open

feat: ship a Kiro power that drives this accelerator#41
omrsamer wants to merge 2 commits into
aws-samples:mainfrom
omrsamer:feat/kiro-power

Conversation

@omrsamer

@omrsamer omrsamer commented Aug 25, 2026

Copy link
Copy Markdown

What this adds

A Kiro power at kiro/agentcore-enterprise-platform/ that lets Kiro drive this
accelerator: pick a profile from a described situation, deploy and verify a module at a
time, diagnose what failed, audit what it costs, and tear it down cleanly.

POWER.md routes. Fourteen steering/*.md files hold the detail and load on demand, so
nothing beyond the router enters context until it is needed.

Six of the steering files are runbooks — deploy the platform, deploy one module,
verify, recover a failed deploy, audit cost, tear down. They are ordered procedures with a
verify gate after each step and explicit halt conditions. Everything that creates,
changes, deletes or bills is proposed one command at a time, with its cost stated, and
waits for approval. Read-only checks are grouped, because teaching someone to click
through five prompts without reading is worse than not prompting.

The content is the operational knowledge this repository does not currently write down:
what each module deploys versus what its title suggests, which command proves a layer works
rather than that its stack reached CREATE_COMPLETE, why module 6 goes silent for seven
minutes, which flags are sharp, and which security controls are enforced rather than merely
declared.

README.md gets a ## Drive It with Kiro section and a pointer from the five-step path.

Why it ships in this repository rather than in one of its own

A hallucinated --flag reads exactly like a real one and only fails in front of a user.
Worse, a renamed flag is indistinguishable from a hallucinated one a month later. Prose
about a CLI rots silently, and the failure lands on whoever is mid-session.

So scripts/check-kiro-power.sh (new, ~558 lines, stdlib Python + bash, no AWS calls and
no network
) makes drift a build failure:

  • Resolves every file:line citation in the power against this tree and bounds-checks the
    line range — 66 of them.
  • Checks every restated profile module sequence against PROFILE_MODULES in
    scripts/deploy.sh — 25 sequences across 5 profiles.
  • Checks every cited --flag against the action it is used with — 133 of them.
  • Verifies every cross-file steering/x.md#anchor link resolves — 57.
  • Parses all 211 bash blocks for syntax.
  • Asserts installability: Kiro's power validator rejects a directory containing
    anything outside POWER.md, mcp.json and steering/*.md — including a hidden file at
    any depth — with an error that does not name the offending file. The gate ports that
    allowlist rather than trusting it, so a stray .DS_Store fails CI here instead of
    failing install for a user.

It runs in the existing shell-checks job, which is already this repo's no-AWS
self-checks job and already sets up Python 3.13. One added step.

The installer allowlist is also why the six runbooks are steering/runbook-*.md rather
than skills/<name>/SKILL.md: skills/ is not on the allowlist, so on an installed power
every runbook read would fail.

The power lives at kiro/, not .kiro/, because .gitignore deliberately excludes
.kiro/ ("Kiro session steering (local-only, must never ship)"). That looked like a
maintainer decision worth respecting rather than overriding with a negation.

The gate's value, and its limit

The power was written against bbc1a0b and then rebased onto 750e283. The gate passed
clean on the new tree — and six claims were still wrong, because they were semantic
rather than textual. Worth stating plainly, since it bounds what CI here can promise:

  • --profile no longer "exports feature flags"; it materializes presets/<p>.yaml as
    platform.yaml. The scope trap survives (still cdk deploy --all) but the durability
    problem it used to imply is fixed.
  • ls --profile <p> was documented as a free read-only way to measure the blast radius.
    The argv pre-scan runs on every action but config and is suppressed only by
    --dry-run, so it now rewrites platform.yaml. The power points at
    expected_stacks() instead.
  • The parser fails closed. --dryrun, --modul 6, --stack=identity,
    --team platfrom and --profile greenfeld all exit 1 now. Four documented traps were
    fixed upstream and are removed from the power. What survives is deploy --dry-run,
    accepted and ignored, so that one is promoted to the headline.
  • confirm_footprint prints cdk ls and blocks before --all, so the scope trap is
    interactive-visible and silent only under --yes / NON_INTERACTIVE=1.
  • Bare deploy --team viability is manifest-dependent now: a2a defaults false in the
    schema, so --team agent fails under greenfield, migration and security-focused.
    The old table claimed it always worked; there is now a per-manifest matrix computed from
    expected_stacks().
  • 3LO providers need a *_client_secret_name; plaintext keys are rejected and a client id
    without a secret name fails at synth.

Also added, because a user will ask and the power was silent: the use-cases/ extension
point and its four guardrails, --yes, and the post-destroy sweep — including that
NON_INTERACTIVE=1 without --yes reports leftovers and leaves them, which can exit 0
with a NAT gateway still billing.

On posture

The power is blunt about this repository on purpose, in the same terms as
docs/SECURITY_CONTROLS.md: security controls are opt-in and default off; a -security
stack at CREATE_COMPLETE means the resources exist, not that anything is enforced;
enable_networking=true is not an air-gapped VPC; Cedar ships in LOG_ONLY with an
unconstrained permit. A facilitator who overstates the posture loses the room in the first
security question, so the power says so first.

Verification

  • bash scripts/check-kiro-power.sh — all checks passed. 16 power files, 19 scanned; 66
    citations resolve with line ranges in bounds; 25 profile sequences match
    PROFILE_MODULES; 133 flags valid for their action; 57 anchors resolve; 211 bash blocks
    parse.
  • The gate is negative-tested at 8/8 planted defects (bad line number, renamed flag,
    wrong profile sequence, dead anchor, broken bash, disallowed file, hidden file, path that
    no longer exists).
  • shellcheck --severity=warning over git ls-files '*.sh' (5 files): clean on 0.11.0.
  • bash scripts/check-deploy-config.sh, check-workshop-flow.sh, check-contract.sh: all
    pass.
  • The behaviour behind the six corrections above was verified by running it, not by
    reading: materialization on ls --profile, the --dry-run suppression, the hand-edited
    manifest refusal and its --yes override, and all five fail-closed parser paths.
  • The footprint table was recomputed from expected_stacks() across all five presets.
    greenfield = 6 and platform-team = 10 match what the power already claimed from an
    earlier measurement; the three missing rows are now filled in.
  • The content behind it comes from real deploys in a test account (greenfield walk, memory,
    A2A, module C networking + check_network.py, module E, a langgraph-agent swap, a full
    destroy), plus deliberate failure injection. Timings in the power are measured, not
    estimated — including the destroy that exited non-zero after 11.6 minutes having deleted
    4 of 10 stacks with the NAT still available, which is why the teardown runbook checks
    the exit code and then checks the NAT anyway.
  • No account IDs, ARNs, or customer-identifying values in the diff; run logs stayed local.

Second commit: .github/labeler.yml

The label job fails on this PR. It is not caused by this PR, and it cannot be fixed by
one: .github/labeler.yml has never existed on main, so actions/labeler@v7 404s
fetching its own config. 15 of the last 15 label runs failed, across 10 branches —
including feat/verify-command (#40, merged red) and a dependabot branch.

actions/labeler resolves its config with ref: github.context.sha
(src/api/get-content.ts),
which under pull_request_target is the base branch commit — and the workflow has no
checkout step, so the log reads "not found locally, fetching via the api". The second
commit here adds that file.

It will not turn this PR's own label job green, and the run on this head confirms it:
the error is byte-identical to the run before the commit existed. The config is read from
the base, so it only takes effect for pull requests opened after this merges. I have left
the red X rather than pretend otherwise.

Because CI 404s before it parses, this repository's CI cannot exercise the config until
it is on main.
Verified locally instead: YAML parses; every key checked against the
accepted set in src/changedFiles.ts at the pinned v7 SHA; every glob confirmed to match
at least one of the 195 tracked files; one pattern that matched nothing
(**/package-lock.json — no lockfile here) dropped rather than left as dead config. Labels
are limited to the four that already exist (documentation, python, github_actions,
dependencies), because actions/labeler creates missing labels implicitly with an
arbitrary colour, which would be an effect outside the file.

This is a second scoped task in one merge request, which CONTRIBUTING.md asks me not to
do. Splitting it out is a one-command change if you would rather review it separately — say
so and I will.

One recommendation, not part of this PR

This content also exists as a standalone power repository, which means two copies of the
same ~40k words. Vendoring here is the right call for the reason above — CI can only verify
the copy that sits next to the source — but the better long-term shape is for this
repository to be the authoring home
and the standalone repo to be generated from
kiro/agentcore-enterprise-platform/. Happy to wire that up if you'd like it; it needs a
decision from you about where the power is published from, so it is deliberately not in
this diff.

Two further things I noticed and left alone. The labeler config above is the only place I
stepped outside the "one merge request addresses one scoped task" rule, for the reason given
there; these two I did not:

  • README.md shows a bare python scripts/check_network.py, which fails outside an
    activated venv where every other documented invocation uses .venv/bin/python.
  • There is no 7th runbook for the multi-team --team split, which a facilitator running
    three parallel teams would want.

Both are separate PRs if wanted.


🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Commit: 3feafb0 | Updated: 2026-08-25 13:40:56 UTC

Security Scan Results

Scanner S C H M L I Time Action Result Thresh
bandit 0 0 0 0 0 0 765ms 0 PASSED MED (g)
cdk-nag 0 0 0 0 0 0 8.2s 0 PASSED MED (g)
cfn-nag 0 0 0 0 0 0 15ms 0 PASSED MED (g)
checkov 0 0 0 0 0 0 5.5s 0 PASSED MED (g)
detect-secrets 0 0 0 0 0 0 645ms 0 PASSED MED (g)
grype 0 0 0 0 0 0 1m 1s 0 PASSED MED (g)
npm-audit 0 0 0 0 0 0 192ms 0 PASSED MED (g)
opengrep 0 2 0 0 0 0 17.4s 2 FAILED MED (g)
semgrep 0 0 0 0 0 0 <1ms 0 MISSING MED (g)
syft 0 0 0 0 0 0 3.1s 0 PASSED MED (g)

Add kiro/agentcore-enterprise-platform/, a Kiro power carrying the
operational knowledge this repository does not: which profile fits a
described situation, what each module deploys versus what its title
suggests, which command proves a layer works rather than that its stack
reached CREATE_COMPLETE, why module 6 goes silent for seven minutes,
which flags are sharp, and which security controls are enforced rather
than merely declared. POWER.md routes; 14 steering files hold the detail
and load on demand.

Six of those steering files are runbooks - deploy the platform, deploy
one module, verify, recover a failed deploy, audit cost, tear down. They
are ordered procedures with a verify gate after each step and explicit
halt conditions, for driving a deployment rather than explaining one.
Everything that creates, changes, deletes or bills is proposed one
command at a time with its cost stated, and waits for approval.

Why it ships here instead of in a repository of its own: a hallucinated
--flag reads exactly like a real one and only fails in front of a user,
and a renamed flag is indistinguishable from a hallucinated one a month
later. scripts/check-kiro-power.sh resolves every file:line citation
against this tree, checks every restated profile sequence against
PROFILE_MODULES in scripts/deploy.sh, and checks every cited --flag
against the script it is used with, so a rename breaks the build instead
of quietly breaking someone's session. It also asserts installability:
Kiro's power validator rejects a directory containing anything outside
POWER.md, mcp.json and steering/*.md - including a hidden file at any
depth - with an error that does not name the offending file, so the gate
ports that allowlist rather than trusting it. That is also why the six
runbooks are steering/runbook-*.md rather than skills/<name>/SKILL.md:
skills/ is not on the installer's allowlist, so on an installed power
every runbook read would fail.

The gate is stdlib-only with no AWS and no network calls, and runs in the
existing shell-checks job, which is already this repo's no-AWS
self-checks job and already sets up Python 3.13.

The power was written against bbc1a0b and re-verified against this base.
That surfaced seven stale claims. Six are the argument for the CI gate but
also its limit - the gate caught none of them, because they are semantic
rather than textual. The seventh it caught outright:

- --profile no longer "exports feature flags"; it materializes
  presets/X.yaml as platform.yaml. The scope trap survives (still
  cdk deploy --all) but the durability problem it used to imply is fixed.
- ls --profile <p> was documented as a free read-only way to measure the
  blast radius. The argv pre-scan runs on every action but config and is
  suppressed only by --dry-run, so it now rewrites platform.yaml.
  Verified directly. The power now points at expected_stacks() instead.
- The parser fails closed. --dryrun, --modul 6, --stack=identity,
  --team platfrom and --profile greenfeld all exit 1 now; four of the
  power's documented traps were fixed upstream and are removed. What
  survives is deploy --dry-run, accepted and ignored, so that one is
  promoted to the headline.
- confirm_footprint prints cdk ls and blocks before --all, so the trap is
  interactive-visible and silent only under --yes / NON_INTERACTIVE=1.
- Bare deploy --team viability is manifest-dependent now: a2a defaults
  false in the schema, so --team agent fails under greenfield, migration
  and security-focused. The old table claimed it always worked.
- 3LO providers need a *_client_secret_name; plaintext keys are rejected
  and a client id without a secret name fails at synth.
- scripts/test.py is gone (74db5ae), replaced by scripts/verify.py and a
  deploy.sh verify action. The gate failed on the citation, which is exactly
  what it is for. The power documented test.py's inability to fail in
  detail; that analysis is now history, so verify.md keeps it as a
  pre-upgrade symptom - a red AccessDeniedException on step 2 identifies an
  older checkout - and documents verify.py's own two limits instead: it
  verifies what the configuration promises rather than what the account
  contains, and it has no check for -auth, -identity, -security or uc-*.
  deploy.sh verify is added to the actions table, along with the fact that
  the script's one-line Usage: string omits it.

Naming a retired path is a category the gate now handles explicitly rather
than by rewording around it: RETIRED maps the path to why it went, and the
check asserts the path is still *absent*, so a resurrected scripts/test.py
fails instead of sitting silently in an allowlist. Negative-tested.

Also added, because a user will ask and the power was silent: the
use-cases/ extension point and its four guardrails, --yes, and the
post-destroy sweep - including that NON_INTERACTIVE=1 without --yes
reports leftovers and leaves them, which can exit 0 with a NAT still
billing.

README.md gets a "Drive It with Kiro" section plus a pointer from the
five-step path, and states plainly that the power is blunt about this
repository's posture: controls are opt-in and default off, a -security
stack at CREATE_COMPLETE means the resources exist and not that anything
is enforced, enable_networking=true is not an air-gapped VPC, and Cedar
ships LOG_ONLY.

Verification:
- bash scripts/check-kiro-power.sh: all checks passed. 16 power files;
  71 source citations resolve with line ranges in bounds; 25 profile
  sequences across 5 profiles match PROFILE_MODULES; 134 cited flag
  usages exist in the script they are used with; 57 anchors resolve;
  214 bash blocks parse; 19 files scanned for account-identifying values.
- Negative-tested the gate against 8 planted defects - a .DS_Store and a
  notes.txt under steering/, a .py at the power root, a skills/ tree, a
  citation to a file that does not exist, a line number past EOF, a wrong
  greenfield sequence, and a --fast-path flag that does not exist. All 8
  caught; baseline green.
- Behaviour above verified by running it, not by reading: materialization
  on ls --profile, the --dry-run suppression, the hand-edit refusal and
  its --yes override, and all five fail-closed parser paths.
- Footprint table recomputed from expected_stacks() across all five
  presets; greenfield 6 and platform-team 10 match what the power already
  claimed, and the three missing rows are now filled in.
- shellcheck --severity=warning over git ls-files '*.sh' (6 files): clean
  on 0.11.0.
- bash scripts/check-deploy-config.sh, check-workshop-flow.sh and
  check-contract.sh: all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@omrsamer

Copy link
Copy Markdown
Author

CI caught real drift, which is the outcome the gate exists for — so worth writing down rather than just fixing.

shell-checks failed on:

FAIL  scripts/test.py does not exist in this repo
      (cited at kiro/agentcore-enterprise-platform/steering/patterns.md:49)

#40 (feat/verify-command) merged 28 minutes after the base I branched from, retiring scripts/test.py in favour of scripts/verify.py and a deploy.sh verify action. GitHub tested the merge, so the gate compared the power against a tree it had never seen. Rebased onto b5e72f3 and fixed:

  • verify.md's test.py section is now a verify.py section — the footprint→check mapping table, that any failed check exits 1, and two limits worth stating: it verifies what the configuration promises rather than what the account contains (a manifest with networking off will not run check_network.py against a deployed VPC), and it has no check for -auth, -identity, -security or uc-* stacks, so it is not the answer to "are the controls working."
  • The old analysis is kept as history, not deleted. The power documented test.py's inability to fail in some detail, including the red ✗ AccessDeniedException … Authorization method mismatch on step 2 of a healthy platform. That is now a diagnostic: someone reporting it is on a pre-verify.py checkout, and the answer is git pull.
  • Twelve other test.py references across six files updated. runbook-verify-platform.md now runs deploy.sh verify as Step 1 and keeps the matrix for the rows verify does not cover and for isolating a failure.
  • deploy.md gains a verify row, plus the note that the script's one-line Usage: string omits verify while the Actions: block below it documents it (scripts/deploy.sh:1177) — so --help should not be read as evidence the action does not exist.

On the gate itself: naming a retired path is a legitimate thing for this content to do, and it will recur. Rewording around the check would have been the wrong fix — that is a test driving the content instead of checking it. So it is now a declared category: RETIRED maps the path to why it went, and the check asserts the path is still absent, so a resurrected scripts/test.py fails instead of sitting silently in an allowlist. Negative-tested both directions.

Re-verified on b5e72f3: check-kiro-power.sh green (71 citations, 25 profile sequences, 134 flags, 57 anchors, 214 bash blocks), shellcheck --severity=warning clean over 6 files, and check-deploy-config.sh / check-workshop-flow.sh / check-contract.sh all pass.

Unrelated, and pre-existing on main: the label job fails on every PR in this repo, including #40 and #39, because .github/labeler.yml does not exist — actions/labeler@v7 needs it. Not touched here, since it is outside this PR's scope, but it is a one-file fix if you want it as a separate change.

.github/workflows/label.yml runs actions/labeler@v7 on pull_request_target but
.github/labeler.yml was never added, so the action 404s fetching its own config
and the job has failed on every pull request since the workflow landed.

The file has to be on the default branch. actions/labeler resolves its config
with `ref: github.context.sha` (src/api/get-content.ts), and under
pull_request_target that is the base branch commit, not the pull request head —
so no contributor branch can supply it, and the workflow itself runs from the
base copy. This is why the failure looks unfixable from a fork.

Labels are limited to four that already exist in the repository
(documentation, python, github_actions, dependencies). actions/labeler creates
missing labels implicitly with an arbitrary colour, so introducing a new name
would have an effect outside this file.

Verification: config parsed as YAML; every key checked against the accepted set
in actions/labeler src/changedFiles.ts at the pinned v7 SHA
(bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13); every glob confirmed to match at
least one tracked file, against all 195 files on main at b5e72f3 — one
non-matching pattern (**/package-lock.json, no lockfile in this repo) was
dropped rather than left as dead config; all four label names confirmed present
via `gh label list`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant