Skip to content

feat(cli): add hyperframes lambda policies role/user/validate#912

Open
jrusso1020 wants to merge 8 commits into
mainfrom
feat-lambda-policies
Open

feat(cli): add hyperframes lambda policies role/user/validate#912
jrusso1020 wants to merge 8 commits into
mainfrom
feat-lambda-policies

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented May 16, 2026

What

Adds hyperframes lambda policies role|user|validate so adopters running hyperframes lambda deploy for the first time don't have to figure out the IAM policy by hand. Without this, the typical first attempt fails with User is not authorized to perform iam:CreateRole on resource ... and a 30-minute detour.

# Print an inline policy doc to attach to the IAM user that runs the CLI.
hyperframes lambda policies user

# Print { TrustRelationship, InlinePolicy } for a CloudFormation service role.
hyperframes lambda policies role --principal=cloudformation

# Diff a checked-in policy against the CLI's required actions (exit non-zero on missing).
hyperframes lambda policies validate ./infra/iam/hyperframes-deploy.json

Why

Per DISTRIBUTED-RENDERING-PLAN.md § 11 Phase 6c P1: "IAM bootstrap — we currently assume the user can already deploy a CFN stack with Lambda+SFN+IAM+CW." This PR removes that assumption.

validate is the CI-friendly variant: wire it into a pre-deploy check that fails loudly if a checked-in IAM policy stops covering what the CLI needs (e.g. after the SAM template adds a new resource).

How

  • New packages/cli/src/commands/lambda/policies.ts with the required-actions registry, the policy-doc builder, and validatePolicy(path).
  • New policies subcommand dispatched from packages/cli/src/commands/lambda.ts.
  • validate expands the common wildcard shapes (s3:*, s3:Get*, *) when checking whether a checked-in policy covers a required action. IAM evaluation order (Deny over Allow) is out of scope — validate only confirms the Allow set is sufficient.
  • Required actions are sorted alphabetically per-service in the source so future diffs stay readable.

Notable design call:

  • Resource: "*" in the generated docs is intentional. CloudFormation creates new ARNs on every adopter's first deploy; scoping the Resource by name would require the adopter to already have deployed, which is exactly what they're trying to do. The CLI's help text + cli.mdx both call out that adopters should narrow Resource to the deployed ARNs after the first successful run.

Test plan

  • 10 unit tests on the policies module (required action set, doc shape, trust policy service principal, validatePolicy against valid / missing / wildcard / single-Statement / Deny-statement inputs)
  • Typecheck clean
  • Lint + format clean
  • Documentation updated in docs/packages/cli.mdx

Stacks on #909 and #910.

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator Author

jrusso1020 commented May 16, 2026

Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

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

Solid addition. CI is green. Here's what I checked:

IAM policy correctness
The action list per service is accurate and appropriate for SAM-based Lambda + Step Functions + S3 + CloudWatch deployment. Resource: "*" is the right call for bootstrap — the PR description and docs both explain the tradeoff honestly and tell adopters to narrow it post-first-deploy. No unnecessary IAM superpowers (no iam:*, no AdministratorAccess). iam:PassRole is included, which is required when CloudFormation creates execution roles. The samArtifactBucket group for the aws-sam-cli-* bucket is a thoughtful edge case.

CLI command structure
Follows the existing lambda subcommand pattern exactly — positional target maps to role|user|validate, positional extra maps to the validate path argument, and parsePrincipal matches the existing parseFormat/parseQuality enum-parsing convention. The policies case is dispatched correctly in the switch block. policies is already added to help.ts in the base PR (#910) so it will surface in hyperframes --help.

Security
No hardcoded credentials. No ARN injection vectors — validatePolicy reads a file path and parses JSON; no shell exec involved. actionMatches wildcard expansion is conservative: only *, service:*, and prefix* patterns, which matches the IAM wildcard spec. Deny statements are correctly ignored in the static Allow-set analysis (with a comment explaining why IAM evaluation order is out of scope).

Code quality
Types are clean — separate PolicyStatement and TrustPolicyStatement shapes prevent mixing action-policy and trust-policy fields. allRequiredActions() is a pure function that dedupes and sorts, making it safe to call from both buildPolicyDocument and validatePolicy. Error paths use process.exitCode = 1 (not process.exit(1)) in the JSON branch so the JSON output still flushes before exit — correct pattern. 10 unit tests cover all wildcard cases, the single-Statement edge case, and Deny-statement handling.

One non-blocking note
The validate command's inputPath is sourced from args.extra (the second positional), meaning hyperframes lambda policies validate ./policy.json works, but the UX is a bit implicit — the file path is the second positional, not the first (args.target is "validate"). This is consistent with how lambda sites create <projectDir> works, so it's fine within the existing convention; just worth documenting in --help output if a describe block gets added later.

Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

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

Phase-6b IAM bootstrap. The required-action registry, doc shapes, and validatePolicy semantics are mostly right; the test surface is the strongest part of the diff (10 cases, including wildcard expansion + the Deny semantics carve-out). Findings below center on (a) action-set gaps that will surface as failed first deploys, (b) a couple of validate-path correctness edges, and (c) UX/discoverability misses.

Calibrated strengths

  • validatePolicy separation: required / granted / missing as a structured result (policies.ts:264-308) is the right shape for both CLI and CI consumers.
  • Deny-statement scope note is explicit (policies.ts:289, test policies.test.ts:158-176) — "Allow set sufficiency only, evaluation order out of scope" is documented and tested. Good intentional limit.
  • Trust-policy split into its own type (policies.ts:44-53) instead of polluting PolicyStatement with an optional Principal field — small but right.

Findings

blockerMissing required actions for first sam deploy --resolve-s3. The samDeploy driver passes --resolve-s3 (sam.ts samDeploy()), which on first run calls s3:ListAllMyBuckets (a.k.a. s3:ListBuckets) to discover/create the aws-sam-cli-managed-default-* artifact bucket. That action is not in any group in REQUIRED_ACTIONS (policies.ts:59-150). A first-deploy adopter with the generated policy will hit AccessDenied: ListAllMyBuckets before SAM ever uploads the ZIP — which is exactly the failure mode this PR is trying to eliminate. Add s3:ListAllMyBuckets to s3Bucket. Likely also cloudformation:ValidateTemplate (CFN template validation during change-set creation) — verify by smoke-running the generated policy against a fresh account.

importantWildcard matcher silently misses NotAction / NotResource statements. validatePolicy (policies.ts:288-296) only inspects stmt.Action. A policy authored as { Effect: "Allow", NotAction: ["iam:DeleteUser"] } grants everything except iam:DeleteUser. The validator will report all required actions as missing, producing a false negative. Either (a) detect NotAction and treat as * (true-but-overscoped grant), or (b) raise an "unsupported policy shape" error so users don't trust a misleading green/red. Same applies to NotResource if resource-scoping is added later. Pin this with a test.

importantsubcommand positional description in citty schema is stale. packages/cli/src/commands/lambda.ts:62 still reads "deploy | sites | render | progress | destroy" — no policies. This shows up directly in hyperframes lambda --help and in citty-generated usage. Fix the string and add an example in examples (top of file) so policies validate shows up in the auto-rendered help.

importantDuplicate samArtifactBucket group is dead code. REQUIRED_ACTIONS.samArtifactBucket = ["s3:CreateBucket", "s3:GetObject", "s3:ListBucket", "s3:PutObject"] (policies.ts:147-150) is fully subsumed by s3Bucket + s3Object and gets deduped by the Set in allRequiredActions. The comment claims "adopters who set --s3-bucket can drop these" but there is no toggle that lets them drop a subset; the registry is flat and unioned. Either remove the group (it's misleading) or convert the structure into a real per-feature toggle so the comment is true.

importantpolicies role --principal=lambda is a footgun. The CLI's deploy/destroy/render flow never asks an adopter to assemble a Lambda execution role by hand — the SAM template creates it. Exposing lambda as a --principal value produces a trust-policy for lambda.amazonaws.com plus a full deploy-superset inline policy, which is a confusingly-overscoped Lambda execution role no human should attach. Either drop the lambda option (CFN-only) or, if you want to support standalone runtime-role inspection, scope the inline policy to a runtime-only subset (S3 + Step Functions read/write + lambda:InvokeFunction), not the full deploy set.

importantWildcard expansion is end-anchored only. actionMatches (policies.ts:311-324) handles *, service:*, and prefix* but not mid-string wildcards (s3:Get*Object) or ? single-char wildcards. IAM supports both. This is acceptable for V1 — but the validator should say so. Right now a policy with s3:Get*Object will be silently reported as not granting s3:GetObject, which a user will read as "missing permission" and overscope to fix. Either implement glob-style matching (cheap with a regex), or print a one-time warning when a non-end-anchored * appears in Action.

importantMissing negative-path tests on the validate path. The test suite has zero coverage for: missing/non-existent file (ENOENT bubble-up unfriendly to users), unparseable JSON (raw SyntaxError), Statement field absent entirely, NotAction / NotResource shapes. These are the inputs CI graders will throw at this — pin them.

nit--json flag semantics on policies user are surprising. user always emits JSON to stdout; the --json flag only suppresses the trailing # Attach … comment on stderr (policies.ts:215-221). Anyone reading hyperframes lambda policies user --json will assume "without --json I get text format" — but there is no non-JSON format. Rename to --quiet, or in --help, document the flag's actual effect (suppresses the explanatory comment).

nitvalidate --json only exits non-zero when missing.length > 0, swallowing other failures. If validatePolicy itself throws (bad JSON, missing file), the unhandled rejection produces a non-zero exit but with a noisy stack trace; CI consumers will scrape stderr. Wrap the throw site in runPolicies with a friendly error envelope ({ ok: false, error: "..." }) when --json is set, so CI integrators have one parse shape.

nitextra positional is doing double duty for sites create <dir> and policies validate <path>. Reusable today, but the description string (lambda.ts:72) still says "e.g. sites create <projectDir>" — update to mention policies validate <policy.json> too, so a user grepping --help finds the right slot.

nitbuildPolicyDocument() returns the same actions for both role and user. Mostly fine, but it means a CloudFormation service role gets iam:PassRole on * — which lets that role hand any other role to any service. CFN typically needs iam:PassRole only for the roles SAM creates inside this stack. Scoping is per the PR body deferred to the adopter; just ensure the docs callout in cli.mdx mentions this specific Resource-tightening priority (CFN role's iam:PassRole is the highest-risk grant in the bundle).

Cross-reference with hf#907 SAM template (HEAD fff432d): the action set matches what template.yaml provisions for the runtime/state-machine roles (X-Ray put, S3 CRUD, SFN logging, alarms), and the Tags-on-Globals + per-service *:TagResource actions are correctly present in the registry. So the static template surface is covered; the gaps are around the invocation surface (s3:ListAllMyBuckets, possibly cloudformation:ValidateTemplate).

Verdict: REQUEST CHANGES
Reasoning: One blocker (s3:ListAllMyBuckets missing — defeats the stated purpose of the PR on first deploy) plus four importants (NotAction blind-spot, stale subcommand help, dead samArtifactBucket group, --principal=lambda footgun). Each is a small fix; once those land + the validate negative tests pin behavior, this is APPROVE.

Review by Vai

miguel-heygen
miguel-heygen previously approved these changes May 17, 2026
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

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

Blocker from the previous review is addressed:

REQUIRED_ACTIONS missing s3:ListAllMyBuckets and cloudformation:ValidateTemplate — Fixed. Both actions are present in the REQUIRED_ACTIONS map in policies.ts:

  • s3:ListAllMyBuckets is in the s3Bucket group (verified in diff)
  • cloudformation:ValidateTemplate is in the cloudformation group (verified in diff)

The allRequiredActions() test also asserts coverage of key touchpoints, and the validatePolicy tests verify the full required set is used for diffing. Solid test coverage with wildcard expansion, NotAction/NotResource warning paths, and edge cases.

vanceingalls
vanceingalls previously approved these changes May 17, 2026
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

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

Re-review at HEAD 5c99590. Flipping CHANGES_REQUESTED → APPROVE — blocker + all 4 importants addressed.

Status of prior findings

  • Blocker (missing s3:ListAllMyBuckets) — ADDRESSED. policies.ts:126 lists it; :75 adds cloudformation:ValidateTemplate too. sam deploy --resolve-s3 won't hit the first-deploy access-denied anymore.
  • Important (validatePolicy ignores NotAction/NotResource) — ADDRESSED. :340-348 now emits explicit warnings on each shape rather than silently ignoring; user knows the validator's coverage gap on those statements.
  • Important (subcommand help description missing policies) — ADDRESSED. lambda.ts:67 now reads "deploy | sites | render | progress | destroy | policies".
  • Important (REQUIRED_ACTIONS.samArtifactBucket dead code) — ADDRESSED. The dead key is removed (grep at HEAD returns no references).
  • Important (policies role --principal=lambda footgun) — ADDRESSED. policies.ts:184-187 adds an explicit comment that lambda-execution trust is intentionally out of scope ("the SAM template creates its own scoped execution role, and emitting a lambda.amazonaws.com trust paired with the full deploy-superset inline policy below would…") — the comment naming the failure mode prevents future-reader confusion.

Notes

The CFN-superset Resource: "*" is documented as deliberate (policies.ts:160-167 — adopters can't predict ARNs before first deploy) with an explicit narrow-after-deploy recommendation. Reasonable trade-off for the bootstrap-IAM use case.

Verdict: APPROVE.

Review by Vai (re-review)

@jrusso1020 jrusso1020 changed the base branch from feat-lambda-cli to main May 17, 2026 07:02
@jrusso1020 jrusso1020 dismissed stale reviews from vanceingalls and miguel-heygen May 17, 2026 07:02

The base branch was changed.

Wraps the @hyperframes/aws-lambda SDK + the Phase 6a SAM template behind
a single CLI surface so an end-to-end render is three commands instead
of the ~8 manual bun+sam+aws steps the smoke script does today:

  hyperframes lambda deploy
  hyperframes lambda render ./my-project --width 1920 --height 1080 --wait
  hyperframes lambda destroy

Subcommands:
  - deploy:        build handler.zip + sam-deploy + persist stack outputs
                   to <cwd>/.hyperframes/lambda-stack-<name>.json
  - sites create:  pre-upload a project to S3 with a stable content hash
                   so re-renders skip the tar+PUT pass
  - render:        start a Step Functions execution; --wait blocks and
                   streams per-chunk progress + accrued cost
  - progress:      one-shot snapshot — status, frames, cost breakdown,
                   errors. Accepts renderId or executionArn
  - destroy:       sam-delete + drop the local state file (S3 bucket
                   is Retain'd by the template; documented in --help
                   and in docs/packages/cli.mdx)

To keep @sparticuz/chromium out of the CLI's transitive deps, this also
adds a dedicated ./sdk subpath export to @hyperframes/aws-lambda; the
CLI imports from @hyperframes/aws-lambda/sdk exclusively. The existing
. barrel still re-exports both handler + SDK for adopters who want one
entry point.

Defaults are deliberately cost-conservative for first-time users:
--concurrency=8 (low enough to never surprise) and --memory=10240 (the
common case; documented for adopters who want to tune down).

Tests: 5 unit tests on the state-file round-trip. CLI integration
against sam local invoke is part of the upcoming PR 6.6 (lambda-local
regression harness).
Two small cleanups on top of the lambda CLI:

  - Replace parseFormat / parseCodec / parseQuality / parseChromeSource
    (four near-identical helpers) with a single generic parseEnum() +
    typed const-tuple lookups. The four callers now read as one-line
    arrow functions that lift the allowed values out of the function
    body so they're easy to extend.

  - DEFAULT_STACK_NAME was const-declared then re-exported at the
    bottom of state.ts; just mark the const export inline.

No behavior changes. All CLI tests still pass.
esbuild can't bundle @hyperframes/aws-lambda's transitive AWS SDK
deps (@aws-sdk/* + @smithy/*) cleanly into a node binary — the
SDK's .browser.js conditional re-exports break the resolver:

  ESM Build failed
    No matching export in "splitStream.browser.js" for import
    "splitStream" (and ~10 similar errors)

Mark aws-lambda as `external` so esbuild doesn't follow it, and
move it from devDependencies to dependencies so the published CLI
can resolve it from node_modules at runtime. The lambda subverb
files dynamic-import only on `hyperframes lambda *` invocation, so
the CLI cold-start cost is unchanged.

The install-size hit (AWS SDK + @sparticuz/chromium ≈ 200 MiB) is
documented as a v1 tradeoff; a future split into a lambda-sdk-only
subpackage can pare this back.
Two blockers + four important items from Vai's review:

  - `--memory` was parsed and recorded in the local state file but
    never forwarded to `sam deploy` as a parameter override. Worse,
    `progress.ts` then read the *recorded* value for cost math, so
    `--memory 5120` produced wrong cost numbers downstream. Thread
    `LambdaMemoryMb` through samDeploy's --parameter-overrides.

  - `--profile` was only consumed by deploy / destroy. render and
    progress fell back to the default credentials chain — a user
    with `--profile prod` would silently render against their
    default account (wrong-account billing footgun). Set
    `process.env.AWS_PROFILE` (and `AWS_REGION`) in the dispatcher
    before any subverb runs; the AWS SDK reads them natively, so
    render / progress / sites all benefit without each subverb
    threading the flag through the SDK call.

  - `--profile` + destroy now also reads `process.env.AWS_PROFILE`
    as a fallback (matching deploy's existing env fallback).

  - `--wait --json` printed both the start handle AND the final
    progress snapshot, producing two concatenated JSON blobs that
    `jq` rejected. Now emits a single document: handle (without
    --wait) OR final progress (with --wait).

  - Negative integers on `--width` / `--height` / `--chunk-size` /
    `--max-parallel-chunks` / `--memory` / `--concurrency` now fail
    loudly via a new `parsePositiveInt` wrapper instead of flowing
    into the SDK and producing opaque AWS validation errors mid-
    render.

  - `DEFAULT_STACK_NAME` is now centralized to the literal
    `"hyperframes-default"` and consumed from one place. Previously
    the value was assembled as `hyperframes-${"default"}` in three
    sites and hardcoded as `"hyperframes-default"` in a fourth.
    `requireStack`'s hint now matches the dispatcher's default.

The faked `SiteHandle` for `--site-id` keeps the documented
placeholder fields but also surfaces `bucketName` (from PR 909's
extended SiteHandle interface), matching the SDK contract.

All CLI unit tests + the full bundler build still pass.
@jrusso1020 jrusso1020 force-pushed the feat-lambda-policies branch from 5c99590 to 34c2fa0 Compare May 17, 2026 07:06
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

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

Re-approve after rebase. Diff verified unchanged — s3:ListAllMyBuckets still present in REQUIRED_ACTIONS.

Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

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

Re-approve after rebase onto main. Force-push dismissed my prior --approve (require_last_push_approval: true) — content unchanged, same commits replayed on the new base. All findings from the prior review's resolution still apply.

Re-review by Vai (post-rebase re-stamp)

The "Smoke: global install" CI step packs the CLI via `npm pack` and
installs it globally via `npm install -g <tgz>`. npm doesn't understand
the workspace: protocol, so a runtime `dependencies` entry of
`@hyperframes/aws-lambda: workspace:*` blows up with:

  npm error code EUNSUPPORTEDPROTOCOL
  npm error Unsupported URL Type "workspace:": workspace:*

(pnpm rewrites workspace:* on publish; npm pack doesn't.)

Three changes to unblock the smoke + keep the published CLI install
small for users who don't deploy to Lambda:

  - Move `@hyperframes/aws-lambda` from CLI's `dependencies` back to
    `devDependencies`. It's already external in tsup.config.ts; the
    bundle references it via runtime resolution only.

  - Convert the static `import { … } from "@hyperframes/aws-lambda/sdk"`
    in sites.ts / render.ts / progress.ts to `await import()` inside
    each function. tsup with `splitting: false` was inlining those
    static imports at the top of the bundle, which made Node eagerly
    resolve them at CLI startup (MODULE_NOT_FOUND before any lambda
    subcommand even runs). Dynamic imports stay dynamic in the bundle.

  - Add a friendly missing-module check in the lambda dispatcher.
    When a user runs `hyperframes lambda deploy / render / sites /
    progress / destroy` without aws-lambda installed, they now see:

      @hyperframes/aws-lambda is not installed.
      The `hyperframes lambda deploy` command needs it at runtime.
      Install it alongside the CLI:
        npm install -g @hyperframes/aws-lambda

Verified locally: pack + global install + `hyperframes init --example
blank` now succeeds end-to-end (was the same scenario the CI smoke job
runs).
IAM bootstrap subcommand for the lambda CLI. Closes the "first run hits
'User is not authorized to perform iam:CreateRole'" gap that adopters
otherwise have to figure out by hand.

  hyperframes lambda policies user
    → prints an inline-policy doc to attach to the IAM user that runs
      the CLI

  hyperframes lambda policies role --principal=cloudformation
    → prints { TrustRelationship, InlinePolicy } for a service role
      cloudformation can assume

  hyperframes lambda policies validate ./infra/policy.json
    → diffs a checked-in policy against the CLI's required action set,
      expanding s3:* / s3:Get* / * wildcards, exits non-zero on missing
      actions (wire it into CI to catch drift before deploys fail)

The required-actions list is derived from what the SAM template at
examples/aws-lambda/template.yaml needs to create plus what
renderToLambda/getRenderProgress call against S3 + Step Functions at
runtime. Sorted alphabetically per-service so diffs stay readable.

Resource is "*" by design — CloudFormation creates new function /
state-machine / bucket ARNs on every adopter's first deploy. The
generated policy is documented as a starting point; adopters with
stricter postures narrow Resource to the deployed ARNs after the
first successful run.

Tests: 10 unit tests covering the action set, doc shape, trust policy
service principal, and validate() against valid / missing / wildcard /
single-Statement / Deny-statement inputs.
Adds a typed TrustPolicyDocument / TrustPolicyStatement pair so
buildRoleTrustPolicy can return a real type instead of unknown. The
trust-policy shape has a Principal field that the generic
PolicyStatement doesn't model, but it was previously punted via a
return unknown rather than a parallel type.

Test cleanup: drop the `as {...}` casts that the previous return-
unknown signature forced.
One blocker + four importants from Vai's review:

  - REQUIRED_ACTIONS was missing `s3:ListAllMyBuckets` (called by
    `sam deploy --resolve-s3` on first run to discover/create the
    `aws-sam-cli-managed-default-*` artifact bucket) and
    `cloudformation:ValidateTemplate` (CFN template validation
    during change-set creation). Without these, a first-deploy
    adopter with the generated policy hits AccessDenied on the
    very call the PR was meant to unblock. Added both.

  - `policies role --principal=lambda` was a footgun — it produced
    a `lambda.amazonaws.com` trust paired with the full deploy
    superset, i.e. a confusingly-overscoped Lambda execution role
    no human should attach (the SAM template creates its own
    scoped execution role automatically). Dropped `lambda` as a
    principal option; `policies role` now always emits a
    CloudFormation service-role doc.

  - `validatePolicy` silently misreported NotAction/NotResource
    statements (treating them as zero grants), producing false
    negatives. Detect both shapes and surface them via a new
    `warnings: string[]` field; NotAction statements are skipped
    (rather than producing a false negative), NotResource is
    treated as full action grant + a warning.

  - Mid-string wildcards (`s3:Get*Object`, `?`) silently failed
    the matcher. End-anchored wildcards still work; mid-string
    patterns now warn so users know the validator can't expand
    them.

  - Dropped the dead `samArtifactBucket` action group (fully
    subsumed by `s3Bucket` + `s3Object`).

  - `validate --json` now wraps errors in a friendly envelope
    (`{ ok: false, error: "..." }`) so CI consumers have one
    parse shape regardless of failure mode.

  - lambda.ts subcommand description and examples updated to
    include `policies`.

Tests: 5 new negative-path tests cover NotAction warning,
NotResource warning, mid-string wildcard warning, missing file
(ENOENT), malformed JSON (SyntaxError), and absent Statement
field. All 21 policies tests pass.
@jrusso1020 jrusso1020 force-pushed the feat-lambda-policies branch from 34c2fa0 to 2f289f9 Compare May 17, 2026 07:30
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.

3 participants