# Changelog

All notable changes to this project will be documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.8.0]

### Fixed

- **Unprotected routes table not updating after adding a policy**: routes stayed visible in the "Unprotected Routes" section after a rate limit was set because the filter compared `METHOD:/path` display values against path-only strings stripped of the method prefix; comparison now uses the full composite key correctly.
- **Invalid rate limit strings accepted silently**: submitting a malformed limit (e.g. `2/minutesedrr`) was stored and caused a 500 on every request; the engine now validates the limit string before persisting and the dashboard modal surfaces an inline error with the correct format hint.

### Added

- **Feature flags** (`api-shield[flags]`): a full feature flag system built on the [OpenFeature](https://openfeature.dev/) standard, supporting boolean, string, integer, float, and JSON flag types with multi-condition targeting rules, reusable user segments (explicit included/excluded lists plus attribute-based rules), percentage rollouts, prerequisite flags, individual user targeting, and a live SSE evaluation stream. Flags and segments are manageable from the admin dashboard (`/shield/flags`, `/shield/segments`) and the CLI (`shield flags *`, `shield segments *`) — including a new `shield segments add-rule` command and an "Add Rule" panel in the Edit Segment modal that lets operators add attribute-based targeting rules without touching code or the REST API directly.

- **`SHIELD_SERVICE` env var fallback on all `--service` CLI options**: `shield status`, `shield enable`, `shield disable`, `shield maintenance`, and `shield schedule` all read `SHIELD_SERVICE` automatically — set it once with `export SHIELD_SERVICE=payments-service` and every command scopes itself to that service without repeating `--service`. An explicit `--service` flag always wins.
- **`shield current-service` command**: shows the active service context from the `SHIELD_SERVICE` environment variable, or a hint to set it when the variable is absent.
- **`shield services` command**: lists all distinct service names registered with the Shield Server, so you can discover which services are connected before switching context.
- **Dashboard "Unprotected Routes" section**: the Rate Limits page now surfaces all routes that have no rate limit policy, with an "Add Limit" button per row that opens a modal to configure method, limit, algorithm, and key strategy in-place — no CLI required.
- **Route existence validation in `set_rate_limit_policy()`**: attempting to add a rate limit policy for a route that does not exist now raises `RouteNotFoundException` immediately; the REST API returns `404` and the CLI prints a clear error, preventing phantom policies from accumulating.
- **`ShieldSDK.rate_limit_backend` parameter**: pass a `RedisBackend` instance to share rate limit counters across all replicas of a service connected to the same Shield Server; without it each replica enforces limits independently.
- **Rate limit policy SSE propagation to SDK clients**: policies set or deleted via the CLI or dashboard are now broadcast over the Shield Server's SSE stream as typed `rl_policy` envelopes and applied to every connected SDK client in real time — no restart required.
- **`ShieldSDK` auto-login (`username` / `password` params)**: pass credentials directly to `ShieldSDK` instead of a pre-issued token; on startup the SDK calls `POST /api/auth/login` with `platform="sdk"` and caches the resulting long-lived token for the life of the process — no manual token management required.
- **Separate SDK token lifetime (`sdk_token_expiry`)**: `ShieldServer` and `ShieldAdmin` now accept `sdk_token_expiry` (default 1 year) independently from `token_expiry` (default 24 h for dashboard / CLI users), so service apps can run indefinitely without re-authentication while human sessions remain short-lived.
- **`platform` field on `POST /api/auth/login`**: the login endpoint now accepts `"cli"` (default) or `"sdk"` in the request body; `"sdk"` tokens use `sdk_token_expiry` and are intended for machine-to-machine service authentication.

---

## [0.7.0]

### Added

- **`engine.sync` — synchronous proxy for sync route handlers and background threads**: every async engine method (`enable`, `disable`, `set_maintenance`, `schedule_maintenance`, `set_env_only`, `enable_global_maintenance`, `disable_global_maintenance`, `set_rate_limit_policy`, `delete_rate_limit_policy`, `reset_rate_limit`, `get_state`, `list_states`, `get_audit_log`) is now mirrored on `engine.sync` using `anyio.from_thread.run()`, the same mechanism the shield decorators use internally. Use `engine.sync.*` from plain `def` FastAPI handlers (which FastAPI runs in a worker thread automatically) and background threads — no event-loop wiring required.
- **Env-gate management from dashboard and CLI**: routes can now have their environment gate set or cleared at runtime — without redeployment — via the dashboard "Env Gate" button (opens an inline modal) and the new `shield env set` / `shield env clear` CLI commands.
- **Global rate limit** (`engine.set_global_rate_limit`): a single rate limit policy applied across all routes with higher precedence than per-route limits — checked first, so a request blocked globally never touches a per-route counter. Supports all key strategies, burst allowance, and per-route exemptions (`exempt_routes`). Configurable from the dashboard Rate Limits page and the new `shield grl` CLI command group (`get`, `set`, `delete`, `reset`, `enable`, `disable`).
- **Global rate limit pause / resume** (`engine.disable_global_rate_limit` / `engine.enable_global_rate_limit`): suspend enforcement without removing the policy, then resume it later. Per-route policies are always unaffected.

### Documentation

- Reframed all docs and README as ASGI-first; expanded framework support tables to include Litestar, Starlette, Quart, and Django (ASGI) as planned adapters; added a dedicated Adapters overview page with a clear explanation of why WSGI frameworks (Flask, Django WSGI, Bottle) are out of scope for this project and will be supported in a separate dedicated library.

---

## [0.6.0]

### Fixed

- **OpenAPI schema stale across Gunicorn workers** (`RedisBackend`): route filtering in `/docs`/`/redoc` was inconsistent and the global maintenance banner never appeared after changes made by another worker. The `_run_global_config_listener` task was invalidating the in-process config cache but not bumping `_schema_version`, so the OpenAPI schema cache on receiving workers never expired. A new `shield-route-state-listener` background task now bumps `_schema_version` on every remote route state change too, ensuring all workers rebuild their schema on the next `/openapi.json` request.
- **`RedisBackend` crashes with "attached to a different loop" / "Event loop is closed" after worker restart**: `ConnectionPool` was created once at `__init__` time and shared for the process lifetime; after a gunicorn worker recycle or `uvicorn --reload`, all Redis calls failed because the pool's internal futures were bound to the replaced event loop. Fixed by replacing the single shared pool with a per-event-loop pool dict (`dict[id(loop), (weakref.ref(loop), pool)]`): pools are now created lazily on first use within each event loop and dead entries are pruned automatically when the loop is GC'd.

---

## [0.5.0]

### Fixed

- **Rate limit policies not synced across workers** (`RedisBackend`): updating a policy via the admin API or CLI only updated the in-process dict of the worker that handled the request. Other workers continued enforcing the old (decorator-declared) limit until restart. Fixed by adding a `shield:rl-policy-change` Redis pub/sub channel, every `set` and `delete` now broadcasts the change to all instances, which apply it to their local `_rate_limit_policies` dict immediately via a new background listener task (`shield-rl-policy-listener`), mirroring the existing `shield:global_invalidate` pattern.
- **Rate limit policies reverted to decorator defaults on restart** (`ShieldRouter`): `register_shield_routes()` called `restore_rate_limit_policies()` (via `register_batch`) before re-registering decorator-declared policies, so persisted admin/CLI updates were immediately overwritten on every app startup. Fixed by registering decorator policies first and running `restore_rate_limit_policies()` last, consistent with the order already used by `scan_routes()`.
- **`RedisRateLimitStorage.reset()` blocked the event loop**: the underlying `limits` library exposes a synchronous Redis client; calling `.scan()` and `.delete()` directly inside `async def reset()` stalled the event loop during every admin reset and route `enable()` call. Fixed by offloading the SCAN + DELETE loop to `asyncio.to_thread()`.
- **`enable()` reset rate limit counters with seven sequential calls**: a `for _method in (…)` loop called `_rate_limiter.reset()` once per HTTP method. Replaced with a single `reset_all_for_path()` call (`method=None`), which is the existing one-shot API on `ShieldRateLimiter`.

---

## [0.4.0]

### Added

- **Mobile & tablet responsive dashboard**: all four tables (Routes, Audit, Rate Limits, Blocked) transform into stacked cards on screens narrower than 640 px using a CSS-only card layout with `data-label` attributes. Action buttons collapse to icon-only on small screens.
- **Back-to-top button**: fixed button appears at the bottom-right after scrolling 200 px; hidden at the top.
- **Success toast notifications**: 2.5 s toast appears after any mutating action (enable, disable, maintenance, schedule, rate limit edit/reset/delete).

### Changed

- **CLI table pagination**: `shield status`, `shield log`, `shield rl list`, and `shield rl hits` now support `--page` and `--per-page` (default 20) instead of a flat `--limit`; a footer shows the current slice and the next/prev page flag to run. The `shield log` status column now combines old and new state into a single `old > new` cell and shows a coloured label for rate limit audit actions. The `shield rl hits` table drops the `Key` and `Method` columns in favour of a single `Path` column showing `METHOD /route`.
- **`@env_only` now returns 403 with JSON**: env-gated routes blocked by the wrong environment return `403 ENV_GATED` with `current_env`, `allowed_envs`, and `path` instead of a silent empty 404.
- **Tailwind CSS v3 → v4**: replaced `tailwind.config.js` with a CSS-first config in `input.css` (`@import "tailwindcss"`, `@source`, `@theme`). Dashboard CSS is pre-built and committed; no Node.js required at install time.
- **No CDN dependency**: `shield.min.css` is now served as a local static file instead of the Tailwind CDN script, eliminating the production warning and removing the runtime network dependency.

---

## [0.3.0]

### Added

- **Rate limiting** (`@rate_limit`): per-IP, per-user, per-API-key, and global counters with fixed/sliding/moving window and token bucket algorithms. Supports burst allowance, tiered limits (`{"free": "10/min", "pro": "100/min"}`), exempt IPs/roles, and custom `on_missing_key` behaviour. Works as both a decorator and a `Depends()` dependency. Responses include `X-RateLimit-Limit/Remaining/Reset` and `Retry-After` headers. Requires `api-shield[rate-limit]`.
- **Rate limit custom responses**: `response=` on `@rate_limit` and `responses["rate_limited"]` on `ShieldMiddleware` for replacing the default 429 JSON body with any Starlette `Response`.
- **Rate limit dashboard**: `/shield/rate-limits` tab showing registered policies with reset/edit/delete actions; `/shield/blocked` page for the blocked requests log. Policies can also be managed via the `shield rl` CLI commands (`list`, `set`, `reset`, `delete`, `hits`).
- **Rate limit audit log**: policy changes (`set`, `update`, `reset`, `delete`) are recorded in the audit log alongside route state changes, with coloured action badges in the dashboard.
- **Rate limit storage**: `MemoryRateLimitStorage`, `FileRateLimitStorage` (in-memory counters with periodic disk snapshot), and `RedisRateLimitStorage` (atomic Redis counters, multi-worker safe). Storage is auto-selected based on the main backend.
- **Custom responses** for lifecycle decorators: `response=` on `@maintenance`, `@disabled`, and `@env_only`; `responses=` dict on `ShieldMiddleware` for `"maintenance"`, `"disabled"`, and `"env_gated"` states.
- **`@deprecated` as `Depends()`**: injects `Deprecation`, `Sunset`, and `Link` headers directly without requiring middleware.
- **Webhook deduplication**: `RedisBackend` uses `SET NX EX` to ensure only one instance fires webhooks per event in multi-instance deployments. Fails open on Redis error.
- **Distributed global maintenance**: `RedisBackend` publishes to `shield:global_invalidate` on every `set_global_config()` call so all instances drop their cached config immediately.
- **Distributed Deployments guide** (`docs/guides/distributed.md`): backend capability matrix, global maintenance cache invalidation, scheduler behaviour, webhook dedup, and production checklist.

### Changed

- Default `SHIELD_ENV` changed from `"production"` to `"dev"`. Set `SHIELD_ENV=production` explicitly in production deployments.
- `@deprecated` and all blocking decorators now support `Depends()` in addition to the decorator form.

---

## [0.2.0] — Admin Redesign & Docs

### Added

#### Admin & Dashboard
- `ShieldAdmin`: unified admin interface combining the HTMX dashboard UI and the REST API under a single mount point
- REST API under `/api/` for programmatic route management (enable, disable, maintenance, schedule, audit, global maintenance)
- Token-based authentication with HMAC-SHA256 signing
- Support for single user, multiple users, and custom `ShieldAuthBackend` auth backends
- Automatic token invalidation when credentials change (auth fingerprint mixed into signing key)
- `HttpOnly` session cookies for dashboard browser sessions
- HTMX dashboard with SSE live updates, audit log table, and login page
- Platform field in audit log entries (`"cli"` / `"dashboard"`)

#### CLI
- `shield` CLI redesigned as a thin HTTP client over the `ShieldAdmin` REST API
- `shield login` / `shield logout`, `shield status`, `shield enable`, `shield disable`, `shield maintenance`, `shield schedule`, `shield log`
- `shield global` subcommands: enable, disable, status, exempt-add, exempt-remove
- `shield config set-url` and `shield config show`
- Cross-platform config file at `~/.shield/config.json`
- Server URL auto-discovery via `SHIELD_SERVER_URL` env var, `.shield` file, or config file

#### Documentation
- MkDocs Material documentation site with full tutorial, reference, guides, and adapter sections

### Changed
- CLI no longer accesses the backend directly; all operations go through the REST API
- Custom auth header changed from `Authorization: Bearer` to `X-Shield-Token`
- `ShieldDashboard` retained for backward compatibility; `ShieldAdmin` is now the recommended entry point

---

## [0.1.0] — Initial Release

### Added

#### Core
- `RouteStatus`, `MaintenanceWindow`, `RouteState`, `AuditEntry` Pydantic v2 models
- `ShieldException`, `MaintenanceException`, `EnvGatedException`, `RouteDisabledException`
- `ShieldBackend` ABC with full contract
- `MemoryBackend`: in-process dict with `asyncio.Queue` subscribe
- `FileBackend`: JSON file via `aiofiles` with `asyncio.Lock`
- `RedisBackend`: `redis-py` async with pub/sub for live dashboard updates in multi-instance deployments
- `ShieldEngine`: `check`, `register`, `enable`, `disable`, `set_maintenance`, `set_env_only`, `get_state`, `list_states`, `get_audit_log`
- Fail-open guarantee: backend errors are logged; requests pass through
- `MaintenanceScheduler`: `asyncio.Task`-based scheduler that auto-activates and auto-deactivates maintenance windows
- Webhook support: `add_webhook()` and `SlackWebhookFormatter`

#### FastAPI adapter
- `@maintenance`, `@disabled`, `@env_only`, `@force_active`, `@deprecated` decorators
- `ShieldRouter`: drop-in `APIRouter` replacement with startup registration hook
- `ShieldMiddleware`: ASGI middleware with structured JSON error responses and `Retry-After` header
- `@deprecated`: injects `Deprecation`, `Sunset`, and `Link` response headers
- `apply_shield_to_openapi`: runtime OpenAPI schema filtering
- `setup_shield_docs`: enhanced `/docs` and `/redoc` with maintenance banners
- Global maintenance mode: `enable_global_maintenance`, `disable_global_maintenance`, `get_global_maintenance`

#### Dashboard (original)
- HTMX dashboard with SSE live updates and HTTP basic auth via `ShieldDashboard`
- Route list with status badges, enable/maintenance/disable actions per route
- Audit log table

#### CLI (original)
- `shield` CLI with direct backend access
- `shield status`, `shield enable`, `shield disable`, `shield maintenance`, `shield schedule`, `shield log`

[0.7.0]: https://github.com/Attakay78/api-shield/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/Attakay78/api-shield/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/Attakay78/api-shield/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/Attakay78/api-shield/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/Attakay78/api-shield/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/Attakay78/api-shield/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/Attakay78/api-shield/releases/tag/v0.1.0
