Skip to content

✨ feat(infra): add declarative limits management via YAML and Lambda provisioner (#405)#409

Merged
sodre merged 22 commits into
mainfrom
feat/405-declarative-limits
Feb 20, 2026
Merged

✨ feat(infra): add declarative limits management via YAML and Lambda provisioner (#405)#409
sodre merged 22 commits into
mainfrom
feat/405-declarative-limits

Conversation

@sodre

@sodre sodre commented Feb 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Add declarative limits management allowing operators to define rate limits in YAML files and apply them idempotently via a Lambda provisioner, similar to Terraform's plan/apply workflow
  • Introduce zae-limiter limits CLI command group with plan, apply, diff, and cfn-template subcommands for managing limits declaratively
  • Add zae_limiter_provisioner Lambda package with manifest parsing, diff engine, applier, and handler supporting both CLI invocations and CloudFormation Custom Resource events
  • Add #PROVISIONER state record in DynamoDB to track managed vs unmanaged limits, enabling safe removal of limits deleted from YAML without clobbering manual overrides
  • Extend CloudFormation template with the limits provisioner Lambda function and export its ARN for cross-stack reference
  • Add get_provisioner_state() / put_provisioner_state() to Repository and sk_provisioner() key builder to schema

Test plan

  • Unit tests pass: manifest parsing, diff engine, applier, handler, CLI, provisioner builder, stack manager (uv run pytest tests/unit/ -v)
  • Integration tests pass: idempotent apply, removal tracking, namespace auto-registration (uv run pytest tests/integration/test_provisioner.py -v)
  • limits plan prints human-readable diff without modifying DynamoDB
  • limits apply applies limits idempotently (second run produces zero changes)
  • limits diff detects out-of-band drift between YAML and live state
  • limits cfn-template generates valid CloudFormation template with Custom::ZaeLimiterLimits resource
  • Removing a limit from YAML and running apply deletes it only if tracked in #PROVISIONER record
  • Manually set limits (not in YAML) are preserved across applies

Closes #405

🤖 Generated with Claude Code

@sodre sodre added this to the v1.3.0 milestone Feb 20, 2026
@sodre sodre added area/cli Command line interface area/infra CloudFormation, IAM, infrastructure labels Feb 20, 2026
@codecov

codecov Bot commented Feb 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.12%. Comparing base (b8800bb) to head (4a46c5d).
⚠️ Report is 23 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #409       +/-   ##
===========================================
+ Coverage   53.58%   92.12%   +38.54%     
===========================================
  Files          33       35        +2     
  Lines        7383     7672      +289     
===========================================
+ Hits         3956     7068     +3112     
+ Misses       3427      604     -2823     
Flag Coverage Δ
doctest 28.58% <5.53%> (-0.91%) ⬇️
e2e 42.17% <20.06%> (-1.53%) ⬇️
integration 51.10% <20.41%> (-1.18%) ⬇️
unit 91.87% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

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

sodre and others added 18 commits February 20, 2026 06:06
Design for YAML-based limit declarations applied through a Lambda
provisioner, serving both CLI and CloudFormation paths with
Terraform-style state tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12-task TDD implementation plan covering schema, repository, manifest
parsing, diff engine, applier, Lambda handler, builder, CFN template,
CLI commands, stack manager, integration tests, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ord (#405)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…imits (#405)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…405)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…405)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Also fix mypy errors in applier.py (assert target not None for
resource/entity levels) and add types-PyYAML to dev deps and
pre-commit mypy hook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…te (#405)

Adds EnableProvisioner parameter, ProvisionerRole, ProvisionerLogGroup,
ProvisionerFunction, and corresponding outputs (ARN, Name).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
)

Add deploy_provisioner_code() to StackManager following the same
pattern as deploy_lambda_code(). Builds and deploys the provisioner
package to the {stack}-limits-provisioner Lambda function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New `limits` command group for declarative limits management:
- `limits plan`: Preview changes (terraform plan style)
- `limits apply`: Apply changes from YAML file
- `limits diff`: Show drift between YAML and live state
- `limits cfn-template`: Generate CFN template from YAML

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…er (#405)

Tests verify against LocalStack:
- Apply creates limits readable via Repository API
- Idempotent apply produces update (not create) actions
- Removal deletes managed items, leaves unmanaged alone

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the new `limits` CLI command group (plan, apply, diff,
cfn-template), provisioner package structure, DynamoDB schema additions,
and declarative limits workflow in CLAUDE.md, cli.md, and deployment.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nd CLI (#405)

Add tests for uncovered edge cases: provisioner builder metadata
fallback and placeholder removal, stack manager endpoint_url passthrough
and error handling (waiter failures, ClientError), CLI diff/apply
commands, cfn-template entity/burst fields, _load_yaml validation,
and _invoke_provisioner Lambda integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#405)

Add tests for dest dir collision cleanup in provisioner_builder and
successful namespace resolution path in _invoke_provisioner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When `_invoke_provisioner()` resolves the namespace, if the requested
namespace doesn't exist yet (NamespaceNotFoundError), it now connects
to the default namespace, auto-registers the new namespace, and returns
the scoped namespace ID. This enables `limits apply` to work on first
run without requiring manual namespace setup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…405)

After rebasing on main, Repository.connect() was renamed to
Repository.open() with a new signature (namespace as first positional
arg, stack= kwarg). Update limits_cli.py and all affected tests.

Also fix pre-existing test failures in test_cli.py where 3 TestLimitParsing
tests still used mock_repo_class.connect instead of .open.

Fix integration test_provisioner.py to unpack get_system_defaults() tuple
return type after the burst field refactor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sodre sodre force-pushed the feat/405-declarative-limits branch from 7981ac3 to 49c7d7b Compare February 20, 2026 11:23
sodre and others added 4 commits February 20, 2026 06:52
Align with the Limit model refactor (PR #408) that removed the
redundant `burst` field — `capacity` now serves as the bucket ceiling.

- Remove `burst` from LimitDecl (accept it in YAML for backwards
  compat, treating it as capacity override)
- Stop writing `bx` attribute to DynamoDB in applier
- Remove Burst from CFN template generation and handler parsing
- Update docs to remove burst references from YAML examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ler (#405)

Integration tests (LocalStack):
- Handler plan returns changes without modifying state
- Handler apply persists provisioner state (#PROVISIONER record)
- Handler apply creates entity-level limits
- Handler removal flow deletes items removed from manifest
- Handler CFN create and delete lifecycle
- Handler on_unavailable setting persistence

AWS e2e tests:
- Full provisioner lifecycle (apply → verify → modify → verify → delete)
- CFN event lifecycle (Create → Update → Delete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The provisioner role name was hardcoded as ${StackName}-prov, which
fails in environments requiring role names matching a prefix pattern
(e.g., PowerUserPB-*). Now uses the RoleNameFormat parameter like
all other IAM roles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add end-to-end test that exercises the full CloudFormation custom resource
lifecycle: deploy main stack with provisioner Lambda, upload real code,
then create/update/delete a second CFN stack with Custom::ZaeLimiterLimits.

Handler changes to support CFN integration:
- Add CFN response protocol (_send_cfn_response) to POST SUCCESS/FAILED
  to CloudFormation's pre-signed ResponseURL
- Add namespace name-to-ID resolution (_resolve_namespace_id) so CFN
  templates only need Namespace (name), not NamespaceId
- Wrap _handle_cfn with _handle_cfn_with_response for error handling
- Return physical_resource_id for stable custom resource identity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sodre sodre marked this pull request as ready for review February 20, 2026 17:19
@sodre sodre merged commit d4bf8e0 into main Feb 20, 2026
25 checks passed
@sodre sodre deleted the feat/405-declarative-limits branch February 20, 2026 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cli Command line interface area/infra CloudFormation, IAM, infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Declarative limits management via YAML files and Lambda provisioner

1 participant