diff --git a/.gitignore b/.gitignore index 6b649cf..db79492 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,4 @@ wheels/ .python-version .venv uv.lock -plan.md site/ diff --git a/CLAUDE.md b/CLAUDE.md index 400a2bc..a8ee165 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,14 +8,16 @@ Guidance for AI agents (Claude Code, etc.) working in this repository. **Where to find what:** -- [`planning/engineering.md`](planning/engineering.md) — the distilled design reference: invariants and *why*, the three protocol seams, exception contract, module layout, testing patterns, optional-extras pattern, remaining roadmap. Read this before adding any new module or extension point. -- [`planning/deferred-work.md`](planning/deferred-work.md) — review-surfaced items that are real but not actionable now. -- [`planning/specs/`](planning/specs/) and [`planning/plans/`](planning/plans/) — per-feature design specs and implementation plans (active work). -- [`planning/archive/specs/`](planning/archive/specs/) and [`planning/archive/plans/`](planning/archive/plans/) — shipped or superseded work, kept for historical context. -- [`planning/retros/`](planning/retros/) — release- and epic-level retrospectives. +- [`architecture/`](architecture/) (repo root) — the per-capability living truth (overview, client, middleware, decoders, errors, resilience, optional extras, testing); the promotion target on every ship. **Read the relevant file before changing that capability.** +- [`planning/README.md`](planning/README.md) — the planning conventions (two axes, change bundles, three lanes, frontmatter) + the change Index. +- [`planning/changes/{active,archive}//`](planning/changes/) — per-change bundles (`design.md` + `plan.md`, or `change.md` for the lightweight lane). +- [`planning/audits/`](planning/audits/) — findings reports + `scripts/` tooling. +- [`planning/retros/`](planning/retros/) — retrospectives. - [`planning/releases/`](planning/releases/) — per-version release notes (also published on GitHub Releases). +- [`planning/deferred.md`](planning/deferred.md) — review-surfaced, not-yet-actionable items. +- [`planning/_templates/`](planning/_templates/) — design/plan/change templates. -**Per-feature workflow:** brainstorming → spec in `planning/specs/` → writing-plans → plan in `planning/plans/` → executing-plans (or subagent-driven-development) → requesting-code-review → finishing-a-development-branch. Topic slugs are kebab-case descriptions (`msgspec-decoder-adapter`), not story IDs. +**Per-feature workflow:** brainstorming → `design.md` in `planning/changes/active//` → writing-plans → `plan.md` in the same bundle → executing-plans (or subagent-driven-development) → requesting-code-review → finishing-a-development-branch. On ship, promote the conclusions into the affected `architecture/.md` by hand and move the bundle to `planning/changes/archive/`. Topic slugs are kebab-case descriptions (`msgspec-decoder-adapter`), not story IDs. ## Commands @@ -61,7 +63,7 @@ These are non-negotiable. CI rejects PRs that violate them. - **Private symbols**: `_leading_underscore`. Cross-module private code lives in `_internal/`. - **Imports**: absolute paths inside `src/httpware/`; relative imports only within the same subpackage. - **Docstrings**: PEP 257. Module/class/public-method required; `D1` (missing docstring) is ignored. -- **Exception construction**: status-keyed `StatusError` subclasses (the 4xx/5xx tree) take a single positional `response: httpx2.Response` and do NOT override `__init__` — all fields via `exc.response.*`. This rule scopes to `StatusError` only; non-status `ClientError` subclasses such as `DecodeError`, `MissingDecoderError`, `BulkheadFullError`, and `RetryBudgetExhaustedError` deliberately define `__init__` with keyword-only fields. See `engineering.md` §4. +- **Exception construction**: status-keyed `StatusError` subclasses (the 4xx/5xx tree) take a single positional `response: httpx2.Response` and do NOT override `__init__` — all fields via `exc.response.*`. This rule scopes to `StatusError` only; non-status `ClientError` subclasses such as `DecodeError`, `MissingDecoderError`, `BulkheadFullError`, `RetryBudgetExhaustedError`, and `CircuitOpenError` deliberately define `__init__` with keyword-only fields. See `architecture/errors.md`. ## Module layout @@ -81,7 +83,7 @@ src/httpware/ Three documented internal boundaries. AI agents must respect them — never cross a seam except through its documented protocol. 1. **Seam A** — `Client`/`AsyncClient` ↔ `Middleware`/`AsyncMiddleware` — middleware chain composed at `Client.__init__` and `AsyncClient.__init__`, frozen for the client's lifetime. Internal terminal calls `httpx2.Client.send` or `httpx2.AsyncClient.send`, maps exceptions, raises `StatusError` on 4xx/5xx. Sync and async surfaces are kept at parity. -2. **Seam B** — `Client`/`AsyncClient` ↔ `ResponseDecoder` list — both clients take `decoders: Sequence[ResponseDecoder] | None` (a *list*, not a single decoder; `None` resolves against installed extras, pydantic-first). When `response_model` is provided, `send`/`send_with_response` (sync and async alike) walk the list and the first decoder whose `can_decode(model: type) -> bool` returns True runs `decode(content: bytes, model: type[T]) -> T`; if no decoder claims the model, `MissingDecoderError` is raised *before* the HTTP call. Decoder exceptions are wrapped as `DecodeError` at the seam. Full contract: [`engineering.md`](planning/engineering.md) §Seam B. +2. **Seam B** — `Client`/`AsyncClient` ↔ `ResponseDecoder` list — both clients take `decoders: Sequence[ResponseDecoder] | None` (a *list*, not a single decoder; `None` resolves against installed extras, pydantic-first). When `response_model` is provided, `send`/`send_with_response` (sync and async alike) walk the list and the first decoder whose `can_decode(model: type) -> bool` returns True runs `decode(content: bytes, model: type[T]) -> T`; if no decoder claims the model, `MissingDecoderError` is raised *before* the HTTP call. Decoder exceptions are wrapped as `DecodeError` at the seam. Full contract: [`architecture/decoders.md`](architecture/decoders.md). 3. **Seam C** — `httpware` ↔ optional extras — each opt-in dependency imported only inside its dedicated module. ## Testing @@ -92,5 +94,5 @@ Three documented internal boundaries. AI agents must respect them — never cros ## When in doubt -- Check [`planning/engineering.md`](planning/engineering.md) before adding a new module or extension point. +- Check the relevant [`architecture/`](architecture/) capability file before adding a new module or extension point. - Surface ambiguity as a documentation gap rather than improvising. diff --git a/architecture/client.md b/architecture/client.md new file mode 100644 index 0000000..be79a1a --- /dev/null +++ b/architecture/client.md @@ -0,0 +1,17 @@ +# Client + +`httpware` ships two clients: a sync `Client` and an async `AsyncClient`, both at the top level. They are thin wrappers over `httpx2.Client` and `httpx2.AsyncClient` respectively. Both carry full feature parity: typed decoding, the middleware chain, the full resilience suite, and `stream()`. + +## The internal terminal + +The bottom of the middleware chain (the "terminal") is internal. It calls `self._httpx2_client.send(request)`, maps `httpx2` errors to `httpware` errors, and raises a `StatusError` subclass on 4xx/5xx. The error-mapping table (what `httpx2` exception maps to which `httpware` exception) lives at the terminal in `src/httpware/client.py`; status-keyed exceptions are looked up via the `STATUS_TO_EXCEPTION` table in `src/httpware/errors.py`. The same terminal lifecycle holds in both worlds — `Client.send` calls `httpx2.Client.send`, `AsyncClient.send` calls `httpx2.AsyncClient.send`. + +## Sync/async parity + +The sync and async surfaces are kept at parity. Shared state is thread-safe where it must be: `RetryBudget` is a single class used by both worlds and is thread-safe. Sync `Bulkhead` uses `threading.Semaphore` and cannot share an instance with `AsyncBulkhead`. + +The async middleware surface uses the `Async*`/`async_*` prefix, aligning with httpx2's convention. + +## Streaming + +`AsyncClient.stream()` provides a context-manager API for chunked response bodies. It bypasses the middleware chain by design. diff --git a/architecture/decoders.md b/architecture/decoders.md new file mode 100644 index 0000000..84b3aa6 --- /dev/null +++ b/architecture/decoders.md @@ -0,0 +1,15 @@ +# Decoders + +A protocol seam is a documented internal boundary. AI agents and contributors must respect it — never cross a seam except through its protocol. + +## Seam B: `Client`/`AsyncClient` ↔ `ResponseDecoder` list + +Both clients take `decoders: Sequence[ResponseDecoder] | None = None` (a *list*, not a single instance) and dispatch via each decoder's `can_decode(model)` predicate. `AsyncClient()` / `Client()` do not raise on missing extras. + +- **Where:** `src/httpware/client.py` ↔ `src/httpware/decoders/`. +- **Contract:** the client holds `_decoders: tuple[ResponseDecoder, ...]` composed at `__init__` and frozen for the client's lifetime. The Protocol exposes two methods: + - `can_decode(model: type) -> bool` — predicate used at send-time to walk `_decoders` and pick the first claiming decoder (`_dispatch_decoder` on both classes). Built-in decoders claim broadly (pydantic via `TypeAdapter(model)` probe, msgspec via `msgspec.inspect.type_info(model)` + `CustomType` filter); list ordering decides ambiguous shared shapes (dataclass, primitive, generic). Native types of another library MUST be rejected. `can_decode` MUST NOT raise — it runs in `_dispatch_decoder`, outside the `DecodeError` try/except, so a raising probe escapes the `ClientError` contract; a decoder that cannot decide must return False, not raise (the built-ins treat any probe failure as False). This is a documented obligation on implementers, not an enforced guard. + - `decode(content: bytes, model: type[T]) -> T` — the decode itself. Any exception is wrapped by `Client.send` / `AsyncClient.send` (when `response_model=` is set) and `Client.send_with_response` / `AsyncClient.send_with_response` into `httpware.DecodeError` (a `ClientError` subclass carrying `response`, `model`, `original`). Decoder implementers do not need to raise `DecodeError` directly. +- **Pre-flight check:** when `response_model=` is set and no decoder claims it, `send` / `send_with_response` raise `MissingDecoderError(model=..., registered_names=...)` BEFORE the HTTP call. `MissingDecoderError` is a sibling of `DecodeError` under `ClientError`, and is distinct from it: `DecodeError` means the decoder ran and the payload was malformed; the two have distinct corrective actions (install an extra or pass `decoders=[...]`). +- **Default list:** `decoders=None` resolves via `client.py:_build_default_decoders()` against installed extras — pydantic-first when both are present, either-only when only one is installed, empty tuple when neither. `AsyncClient()` / `Client()` never raise on missing extras; failure surfaces only at the first `response_model=` use site. +- **Rule:** the decoder must operate on raw bytes in a single parse pass. Two-pass decoding (`json.loads` then `validate_python`) is rejected: a single bytes-in / typed-object-out pass avoids the redundant intermediate `dict` allocation and parses faster. The Pydantic adapter implements this as `TypeAdapter(model).validate_json(content)`, with the `TypeAdapter` cached per-instance on `PydanticDecoder._adapters: dict[type, TypeAdapter]` (populated lazily on first `_get_adapter()` call); the msgspec adapter mirrors the pattern with `MsgspecDecoder._msgspec_decoders: dict[type, msgspec.json.Decoder]`. Cache lifetime matches the decoder/client, not the process — no module-level state, no autouse cache-clear fixtures in tests. diff --git a/architecture/errors.md b/architecture/errors.md new file mode 100644 index 0000000..7bff69b --- /dev/null +++ b/architecture/errors.md @@ -0,0 +1,19 @@ +# Errors + +`StatusError` and all its 4xx/5xx subclasses are constructed with a **single positional `response: httpx2.Response`**. Subclasses do not override `__init__`. All fields are available via `exc.response.*` (status code, headers, content, request, etc.). + +```python +raise NotFoundError(response) # correct +exc.response.status_code # 404 +exc.response.request.url # URL of the failed request +``` + +`__repr__` and the `str()` summary strip `user:pass@` userinfo from `response.request.url` to avoid leaking credentials in tracebacks. Query-string secrets are not stripped here. + +The error-mapping table (what `httpx2` exception maps to which `httpware` exception) lives at the terminal in `src/httpware/client.py`. Status-keyed exceptions are looked up via the `STATUS_TO_EXCEPTION` table in `src/httpware/errors.py`. Unknown 4xx falls back to `ClientStatusError`; unknown 5xx falls back to `ServerStatusError`. + +`TimeoutError` inherits from both `httpware.ClientError` and `builtins.TimeoutError` so `except builtins.TimeoutError` (the form `asyncio.wait_for` uses) also catches httpware-raised timeouts. + +`DecodeError` covers the case where `response_model=` is set, the HTTP call itself succeeded, but the active `ResponseDecoder` raised. The wrap happens at the seam in `Client.send` / `AsyncClient.send` — `except Exception` translates any decoder-side failure into `DecodeError(response=..., model=..., original=...)` with `raise ... from exc` chaining. The `original` attribute exposes the underlying library exception (e.g., `pydantic.ValidationError`, `msgspec.ValidationError`); `__cause__` carries the same reference. + +The "no `__init__` override" rule scopes only to `StatusError` subclasses. Non-status `ClientError` subclasses — `DecodeError`, `MissingDecoderError`, `BulkheadFullError`, `RetryBudgetExhaustedError`, `CircuitOpenError` — deliberately define `__init__` with keyword-only fields. diff --git a/architecture/extras.md b/architecture/extras.md new file mode 100644 index 0000000..2cb34ba --- /dev/null +++ b/architecture/extras.md @@ -0,0 +1,26 @@ +# Optional extras + +A protocol seam is a documented internal boundary. AI agents and contributors must respect it — never cross a seam except through its protocol. + +## Seam C: `httpware` ↔ optional extras + +- **Where:** `pyproject.toml` extras (`[project.optional-dependencies]`) ↔ the adapter modules that import them. +- **Contract:** each optional dependency is imported only inside its own dedicated module (e.g., `pydantic` in `decoders/pydantic.py`; `msgspec` in `decoders/msgspec.py`). New extras are declared in `pyproject.toml` at the same time the code that uses them lands — not earlier. +- **Rule:** never import an extra at package top-level. The package must import cleanly when the extra is not installed. +- **Verification:** `tests/test_optional_extras_isolation.py` runs a fresh-subprocess `import httpware` and asserts that neither pydantic nor msgspec ends up in `sys.modules`. New extras must add the same isolation test. + +## The optional-extras pattern + +`httpware` core has a small dependency set. Capabilities that pull in heavyweight dependencies (`pydantic`, `msgspec`) live behind extras declared in `pyproject.toml`: + +```toml +[project.optional-dependencies] +pydantic = ["pydantic>=2"] +msgspec = ["msgspec>=0.18"] +``` + +Each extra's code lives in a single dedicated module (`decoders/pydantic.py`, `decoders/msgspec.py`). The `import` of the extra happens **inside** that module behind an `is__installed` guard from `_internal/import_checker.py` — never at package top level. This way, `import httpware` works cleanly without the extras installed, and the seam stays observable: `grep -rnE 'from pydantic|import pydantic' src/httpware/ | grep -v import_checker` returns exactly one indented line (the guarded import in `decoders/pydantic.py`), and the same is true for `msgspec`. + +New extras are added at the same time as the code that uses them — never preemptively. The `otel` extra is paired with the code that uses it: `Retry`, `Bulkhead`, `CircuitBreaker`, and `AsyncTimeout` add events to the active OpenTelemetry span via `trace.get_current_span().add_event(...)`. + +Caller-facing pattern: `AsyncClient()` / `Client()` default `decoders=None` resolves via `_build_default_decoders()` against installed extras (pydantic-first when both are present, the installed one when only one is present, empty tuple when neither). Consumers override by passing `decoders=[...]` explicitly; `decoders=[]` is honored as an opt-out. The auto-resolution is a snapshot of `import_checker.is__installed` flags at `__init__` time — there is no runtime re-detection or implicit registry beyond the two built-in decoders. diff --git a/architecture/middleware.md b/architecture/middleware.md new file mode 100644 index 0000000..abfa8f8 --- /dev/null +++ b/architecture/middleware.md @@ -0,0 +1,15 @@ +# Middleware + +A protocol seam is a documented internal boundary. AI agents and contributors must respect it — never cross a seam except through its protocol. + +## Seam A: `Client`/`AsyncClient` ↔ `Middleware`/`AsyncMiddleware` + +- **Where:** `src/httpware/client.py` ↔ `src/httpware/middleware/`. +- **Contract:** the middleware chain is composed once at client construction and frozen for the client's lifetime. Both worlds follow the same contract; the only difference is the per-world type: `AsyncClient` composes `AsyncMiddleware` via `compose_async` (the continuation type is `AsyncNext`), and `Client` composes `Middleware` via `compose` (the continuation type is `Next`). Both `compose` and `compose_async` live in `src/httpware/middleware/chain.py`. The chain bottom (the "terminal") is internal: it calls `self._httpx2_client.send(request)`, maps `httpx2` errors to `httpware` errors, and raises a `StatusError` subclass on 4xx/5xx. Same lifecycle rules in both worlds. +- **Rule:** mutating the chain after construction is not supported. Per-request behavior goes through `httpx2.Request.extensions` or through `extensions=` kwargs at call sites. + +Phase decorators (declared alongside `Middleware`/`AsyncMiddleware`, `Next`/`AsyncNext` in `src/httpware/middleware/__init__.py`) let a middleware target a specific phase of the request lifecycle. + +## Why there is no standalone OpenTelemetry middleware + +`httpware` deliberately does not ship a separate OTel tracing middleware layer. `opentelemetry-instrumentation-httpx` already covers transport-level tracing; a separate httpware middleware would duplicate it. Observability that `httpware` does add lives where it has information httpx2 lacks — the `Retry` and `Bulkhead` span events on the active span (see Resilience). diff --git a/architecture/overview.md b/architecture/overview.md new file mode 100644 index 0000000..8034c36 --- /dev/null +++ b/architecture/overview.md @@ -0,0 +1,42 @@ +# Overview + +`httpware` is a thin opinionated wrapper around `httpx2`. It re-exports `httpx2.Request` and `httpx2.Response` as the public request/response surface and adds three things on top: typed response decoding (via a `ResponseDecoder` protocol; pydantic and msgspec are both opt-in extras), a middleware chain composed at client construction, and a status-keyed exception tree raised automatically on 4xx and 5xx. + +`httpx2` is part of the public surface. Exposing `httpx2.Request`/`httpx2.Response` is the design — `httpware` does not own a full abstraction over the underlying HTTP client. + +## Architectural invariants (CI-enforced) + +These are non-negotiable. CI rejects PRs that violate them. The "why" exists so future contributors can judge edge cases instead of blindly following the rule. + +- **No `httpx2._` private API.** *Why:* private symbols can change between patch releases. We accept the public-API surface as the contract. +- **No `from __future__ import annotations`.** *Why:* Python 3.11+ floor. PEP 604/585 syntax is native; the future-import would only add noise and inconsistency. +- **No `print()`.** *Why:* ruff-enforced. Libraries log; they do not print to stdout. Stray prints leak into consumer applications. +- **No global logging config.** *Why:* `logging.basicConfig()` from a library mutates the consumer's logging tree. We only acquire `logging.getLogger("httpware")` or namespaced child loggers and let consumers configure handlers. +- **Type suppressions use `# ty: ignore[]`.** *Why:* this project uses `ty`, not `mypy`. `# type: ignore` is silently accepted by `ty` but ambiguous; `# ty: ignore[]` is checked and rule-specific. + +## Module layout + +```text +src/httpware/ +├── __init__.py # public exports (both worlds at top level) +├── py.typed +├── client.py # Client (sync) + AsyncClient (async) +├── errors.py # status-keyed exception tree (shared) +├── middleware/ +│ ├── __init__.py # Middleware + AsyncMiddleware, Next + AsyncNext, decorators +│ ├── chain.py # compose + compose_async +│ └── resilience/ +│ ├── __init__.py # re-exports both worlds + RetryBudget +│ ├── bulkhead.py # Bulkhead + AsyncBulkhead +│ ├── budget.py # RetryBudget (thread-safe; shared) +│ ├── retry.py # Retry + AsyncRetry +│ ├── timeout.py # AsyncTimeout +│ ├── circuit_breaker.py # CircuitBreaker + AsyncCircuitBreaker +│ └── _backoff.py # full-jitter helper (shared) +├── decoders/ # shared (ResponseDecoder + adapters) +└── _internal/ + ├── exception_mapping.py # map_httpx2_exception (shared) + ├── import_checker.py # is_*_installed flags + ├── observability.py # _emit_event + └── status.py # _raise_on_status_error, _is_streaming_body_*, STREAMING_BODY_MARKER +``` diff --git a/architecture/resilience.md b/architecture/resilience.md new file mode 100644 index 0000000..cf7f605 --- /dev/null +++ b/architecture/resilience.md @@ -0,0 +1,23 @@ +# Resilience + +`httpware` ships a resilience suite under `httpware.middleware.resilience`, composed via the standard middleware chain (Seam A). It is pure stdlib — no optional extra. + +## Retry + RetryBudget + +`Retry` (and `AsyncRetry`) is a retry middleware backed by a Finagle-style `RetryBudget` — a token bucket that caps the proportion of traffic spent on retries so a degraded backend cannot be amplified into a retry storm. `RetryBudget` is a single thread-safe class shared by both worlds. Backoff between attempts uses full-jitter. + +## Bulkhead + +`Bulkhead` / `AsyncBulkhead` is a concurrency limiter. `AsyncBulkhead` uses `asyncio.Semaphore` with a bounded acquire wait; sync `Bulkhead` uses `threading.Semaphore`. A sync instance cannot share with an async one. Both are sharable across clients (one instance = one shared concurrency pool). + +## CircuitBreaker + AsyncTimeout + +`AsyncCircuitBreaker` and sync `CircuitBreaker` are a classic consecutive-failure circuit breaker: the circuit opens after `failure_threshold` consecutive counted failures, fast-fails while OPEN, admits one probe after `reset_timeout` (HALF_OPEN), and closes again after `success_threshold` consecutive probe successes; a probe failure re-opens it. A *counted failure* is a `NetworkError`, an httpware `TimeoutError`, or a `StatusError` whose `status_code` is in the effective failure set (default: all 5xx, 500–599); 4xx including 429 count as successes, and any other exception type propagates unchanged without affecting circuit state. When the breaker refuses a request — OPEN, or HALF_OPEN with the single probe slot already taken — it raises `CircuitOpenError` and never forwards to `next`; the error's `retry_after` carries the seconds until the next probe will be admitted, or `None` when a concurrent probe is already in flight. A breaker instance is sharable across clients (one shared circuit); a sync instance cannot be shared with an async one. + +`AsyncTimeout` is an async-only middleware that bounds the total wall-clock for the whole inner pipeline (most importantly across an `AsyncRetry` loop, whose attempts and backoff sleeps `httpx2` cannot bound). It is not a per-call timeout — `httpx2`'s connect/read/write/pool timeouts are the right tool for a single outbound call, and `AsyncTimeout` does not duplicate them. It rejects a non-finite or non-positive `timeout` at construction, and on expiry raises httpware `TimeoutError`. There is no sync `Timeout`: a sync total-deadline cannot interrupt a blocking call mid-flight, and `httpx2` already covers sync per-call timeouts. Sync callers configure `httpx2`'s timeouts directly. + +The recommended (documented, not enforced) composition order is `AsyncTimeout → AsyncCircuitBreaker → AsyncBulkhead → AsyncRetry → terminal`. With the breaker outside retry, an open circuit short-circuits the entire retry loop and the breaker counts one outcome per fully-exhausted retry sequence rather than per attempt. + +## Observability + +`Retry`, `Bulkhead`, `CircuitBreaker`, and `AsyncTimeout` emit operational events via stdlib `logging` records on dedicated loggers (`httpware.retry`, `httpware.bulkhead`, `httpware.circuit_breaker`, `httpware.timeout`) and — when `opentelemetry-api` is installed — OpenTelemetry span events on the active span via `trace.get_current_span().add_event(...)`. The circuit-breaker and timeout event names (`circuit.opened`, `circuit.rejected`, `circuit.half_open`, `circuit.closed`, `timeout.exceeded`) join `retry.*` / `bulkhead.*` as the stable observability surface; renames are breaking changes. diff --git a/architecture/testing.md b/architecture/testing.md new file mode 100644 index 0000000..f5daed6 --- /dev/null +++ b/architecture/testing.md @@ -0,0 +1,7 @@ +# Testing + +- **`pytest-asyncio` auto mode.** Async test functions do not require `@pytest.mark.asyncio`. The setting lives in `pyproject.toml` under `[tool.pytest.ini_options]`. +- **`httpx2.MockTransport` for transport mocking, not `respx`.** Tests construct `httpx2.AsyncClient(transport=httpx2.MockTransport(handler))` and pass it as `httpx2_client=` to `AsyncClient` (or the sync equivalent `httpx2.Client(transport=httpx2.MockTransport(handler))` to `Client`). `MockTransport` is the public test seam in `httpx2`; `respx` patches private internals and breaks across `httpx` major versions. +- **Hypothesis property-based tests** for concurrency-sensitive code: `RetryBudget`, `Bulkhead`, retry interleaving. Files are named `test_*_props.py` so they are easy to grep and treat separately in CI. +- **Performance tests are opt-in.** The `perf` pytest marker is registered in `pyproject.toml`; the default `addopts` line includes `-m 'not perf'`. Run benchmarks explicitly with `pytest -m perf`. +- **Coverage is 100% line coverage.** New code is expected to maintain this. diff --git a/docs/errors.md b/docs/errors.md index 0b9a594..1f1ed36 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -183,4 +183,4 @@ Unlike `DecodeError`, this error fires *before* the HTTP request — no traffic - **[Resilience reference](resilience.md)** — `AsyncRetry`, `RetryBudget`, `AsyncBulkhead` parameter tables. - **[Middleware guide](middleware.md)** — the `@async_on_error` decorator can translate exceptions into responses. -- **`planning/engineering.md` §4** — the formal exception contract. +- **`architecture/errors.md`** — the formal exception contract. diff --git a/docs/index.md b/docs/index.md index 34d1a75..e8f60c8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -183,7 +183,7 @@ When installed, `_emit_event` calls `trace.get_current_span().add_event(name, at - **[Errors reference](errors.md)** — the full exception tree, catching strategies, `exc.response.*` access pattern. - **[Testing guide](testing.md)** — mock-transport injection pattern for testing code that uses `httpware`. - **[Recipes](recipes/modern-di.md)** — wiring `AsyncClient` into a `modern-di` container. -- **[Engineering Notes](https://github.com/modern-python/httpware/blob/main/planning/engineering.md)** — design invariants, the three protocol seams, exception contract, module layout, testing patterns, optional-extras pattern. Lives in the repo at `planning/engineering.md`. +- **[Architecture Notes](https://github.com/modern-python/httpware/blob/main/architecture/overview.md)** — per-capability design notes — invariants, the three protocol seams, exception contract, module layout, testing patterns — under `architecture/`. Lives in the repo under `architecture/`. - **[Contributing](dev/contributing.md)** — setup, conventions, workflow. - **[Release notes](https://github.com/modern-python/httpware/releases)** — per-version changelogs. diff --git a/docs/middleware.md b/docs/middleware.md index 35c8d96..c66f42a 100644 --- a/docs/middleware.md +++ b/docs/middleware.md @@ -110,7 +110,7 @@ The example pairs naturally with the 0.6.0 observability events: a `httpware.ret - **Redaction:** Use a `logging.Filter` on the consumer side. `httpware` deliberately does no redaction in-library (per the 0.6.0 observability design). - **URL or header validation:** `httpx2` owns it. Don't reimplement. - **Per-call behavior that doesn't apply to other calls:** Pass through `request.extensions=` (or the `extensions=` kwarg at the call site) instead. Middleware exists for *cross-cutting* concerns. -- **HTTP-level span creation for tracing:** Install `opentelemetry-instrumentation-httpx` instead of writing an OTel middleware in httpware. We retired story `5-4` (standalone OTel middleware) for this reason — `opentelemetry-instrumentation-httpx` already covers transport-level tracing, and a separate httpware layer would duplicate it. See `planning/engineering.md` §8. +- **HTTP-level span creation for tracing:** Install `opentelemetry-instrumentation-httpx` instead of writing an OTel middleware in httpware. We retired story `5-4` (standalone OTel middleware) for this reason — `opentelemetry-instrumentation-httpx` already covers transport-level tracing, and a separate httpware layer would duplicate it. See `architecture/middleware.md`. ## Wiring OpenTelemetry @@ -198,6 +198,6 @@ Sync and async middleware classes do not interop: a `Middleware` cannot be passe ## See also -- **`planning/engineering.md` §3 (Seam A)** — the formal protocol contract and why the chain is frozen at construction. +- **`architecture/middleware.md` (Seam A)** — the formal protocol contract and why the chain is frozen at construction. - **`src/httpware/middleware/resilience/`** — `AsyncRetry`, `AsyncBulkhead`, `RetryBudget` as real-world consumers of this exact protocol. - **[Quick-Start composition example](index.md#with-resilience-middleware)** — composing built-in middleware. diff --git a/docs/resilience.md b/docs/resilience.md index 04e413b..0bbb6b2 100644 --- a/docs/resilience.md +++ b/docs/resilience.md @@ -354,4 +354,4 @@ with Client( - **[Middleware guide](middleware.md)** — write your own resilience middleware against the same protocol `AsyncRetry` and `AsyncBulkhead` use. - **[Errors reference](errors.md)** — `RetryBudgetExhaustedError`, `BulkheadFullError`, `CircuitOpenError`, and the broader exception tree. - **[Observability](index.md#observability)** — the operational events these middleware emit. -- **`planning/engineering.md` §3** — the formal Middleware/Seam-A contract. +- **`architecture/middleware.md`** — the formal Middleware/Seam-A contract. diff --git a/docs/testing.md b/docs/testing.md index c41d2ab..f9a4a2e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -111,4 +111,4 @@ For middleware with state-keeping (counters, circuit-breaker state), assert on i - **[Middleware guide](middleware.md)** — write the middleware you're testing. - **[Resilience reference](resilience.md)** — testing `AsyncRetry`/`AsyncBulkhead` configurations. -- **`planning/engineering.md` §6** — the project's own testing patterns (Hypothesis property-based tests, `pytest-asyncio` auto-mode, the `RecordedTransport`-was-removed history). +- **`architecture/testing.md`** — the project's own testing patterns (Hypothesis property-based tests, `pytest-asyncio` auto-mode, the `RecordedTransport`-was-removed history). diff --git a/planning/README.md b/planning/README.md new file mode 100644 index 0000000..41bacd8 --- /dev/null +++ b/planning/README.md @@ -0,0 +1,129 @@ +# Planning + +Specs, plans, and change history for `httpware`. The living truth about +*what the system does now* lives in [`architecture/`](../architecture/) at +the repo root; this directory records *how it got there*. + +## Conventions + +> This section is the portable convention — identical across the +> modern-python repos. The Index below is repo-specific. To adopt elsewhere, +> copy this section plus [`_templates/`](_templates/) and point that repo's +> `CLAUDE.md` Workflow + truth home at it. + +### Two axes, never mixed + +- **`architecture/` (repo root) — the present.** One file per capability, + living prose, updated whenever a change ships. The truth home. +- **`planning/changes/` — the past-and-pending.** One folder per change, + frozen once shipped. + +Shipping a change **promotes** its conclusions into the affected +`architecture/.md` by hand, then archives the bundle. That +hand-edit is what keeps `architecture/` true; the archived bundle carries the +*why*. + +### Change bundles + +A change is a folder `changes/active/YYYY-MM-DD.NN-/`: + +- `YYYY-MM-DD` — proposal date; `.NN` — zero-padded intra-day counter + (`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably. +- `` — kebab-case description, not a story ID. + +On merge the folder moves to `changes/archive/` with `status: shipped`, `pr:`, +and `outcome:` filled, and its line moves from **Active** to **Archived** in +the Index below. + +### Three lanes + +| Lane | Artifacts | Use when | +|------|-----------|----------| +| **Full** | `design.md` + `plan.md` | design judgment; new file/module; public-API change; cross-cutting/multi-file; non-trivial test design | +| **Lightweight** | `change.md` | small-but-real: ≲30 LOC net, ≤2 files, no new file, no public-API change, single straightforward test | +| **Tiny** | none — conventional commit | typo, dep bump, linter/formatter/CI tweak, mechanical rename, single-line config | + +Heavier lane wins on ambiguity. A `change.md` that outgrows its lane splits +into `design.md` + `plan.md`. + +### Artifacts at a glance + +- **`design.md`** — the spec: the *thinking* (why, design, trade-offs, scope). +- **`plan.md`** — the plan: the *sequencing* (the executor's task checklist). +- **`change.md`** — both, condensed, for the lightweight lane. +- **`releases/.md`** — per-release user-facing notes. +- **`audits/-.md`** — findings from a code/docs/bug-hunt sweep; + spawns fix changes. +- **`retros/-.md`** — what we learned after a body of work. +- **`deferred.md`** — real-but-unscheduled items, each with a revisit trigger. + +Templates live in [`_templates/`](_templates/). + +### Frontmatter + +`design.md` / `change.md`: `status` (draft|approved|shipped|superseded), +`date`, `slug`, `supersedes`, `superseded_by`, `pr`, `outcome`. +`plan.md`: `status`, `date`, `slug`, `spec`, `pr`. Files in `architecture/` +carry **no** frontmatter — living prose, dated by git. + +## Index + +### Active + +- **[portable-planning-convention](changes/active/2026-06-13.03-portable-planning-convention/design.md)** + (2026-06-13) — Adopt the portable two-axis convention: per-capability + `architecture/` truth files + `changes/` bundles, full history backfill, + byte-identical Conventions. *This change.* + +### Archived (shipped) +- **[circuit-breaker-and-timeout](changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md)** (#51, 2026-06-13) — Shipped 0.10.0 — CircuitBreaker + AsyncTimeout +- **[msgspec-nested-customtype-fix](changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md)** (#43, 2026-06-13) — Shipped 0.9.1 — nested-CustomType guard +- **[delta-audit](changes/archive/2026-06-12.01-delta-audit/design.md)** (#43, 2026-06-12) — 0.9.0 delta audit; closed via 0.9.1 +- **[decoder-instance-cache](changes/archive/2026-06-10.02-decoder-instance-cache/design.md)** (#42, 2026-06-10) — Shipped 0.9.0 — per-instance decoder cache +- **[multi-decoder](changes/archive/2026-06-10.01-multi-decoder/design.md)** (#41, 2026-06-10) — Shipped 0.9.0 — multi-decoder routing +- **[readme-link-cleanup](changes/archive/2026-06-08.08-readme-link-cleanup/design.md)** (#39, 2026-06-08) — README link cleanup +- **[mkdocs-gh-pages-migration](changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md)** (#38, 2026-06-08) — Docs host -> GitHub Pages +- **[test-mop-up](changes/archive/2026-06-08.06-test-mop-up/design.md)** (#37, 2026-06-08) — Shipped 0.8.6 — test-only audit findings +- **[small-fixes-mop-up](changes/archive/2026-06-08.05-small-fixes-mop-up/design.md)** (#36, 2026-06-08) — Shipped 0.8.5 — 4 small audit findings +- **[otel-partial-install](changes/archive/2026-06-08.04-otel-partial-install/design.md)** (#35, 2026-06-08) — Shipped 0.8.4 — OTel partial-install guards +- **[post-080-doc-sweep](changes/archive/2026-06-08.03-post-080-doc-sweep/design.md)** (#34, 2026-06-08) — Post-0.8.0 doc sweep +- **[retry-budget-cluster](changes/archive/2026-06-08.02-retry-budget-cluster/design.md)** (#34, 2026-06-08) — Shipped 0.8.3 — 7 RetryBudget findings +- **[send-with-response](changes/archive/2026-06-08.01-send-with-response/design.md)** (#33, 2026-06-08) — Shipped 0.8.2 — send_with_response +- **[deep-audit](changes/archive/2026-06-07.03-deep-audit/design.md)** (#32, 2026-06-07) — Deep audit; findings closed across 0.8.1-0.8.6 +- **[decoder-error](changes/archive/2026-06-07.02-decoder-error/design.md)** (#32, 2026-06-07) — Shipped 0.8.1 — DecodeError at seam B +- **[sync-client](changes/archive/2026-06-07.01-sync-client/design.md)** (#31, 2026-06-07) — Shipped 0.8.0 — sync Client + Async* rename +- **[modern-di-recipe](changes/archive/2026-06-06.01-modern-di-recipe/design.md)** (#29, 2026-06-06) — modern-di DI recipe doc +- **[v0.7-docs-expansion](changes/archive/2026-06-05.07-v0.7-docs-expansion/design.md)** (#28, 2026-06-05) — Shipped 0.7.0 — first-cut user docs +- **[extension-slot-docs](changes/archive/2026-06-05.06-extension-slot-docs/design.md)** (#28, 2026-06-05) — Shipped 0.7.0 — middleware docs +- **[observability](changes/archive/2026-06-05.05-observability/design.md)** (#27, 2026-06-05) — Shipped 0.6.0 — logging + OTel events +- **[streaming](changes/archive/2026-06-05.04-streaming/design.md)** (#26, 2026-06-05) — Shipped 0.5.0 — stream() +- **[docs-sync-0.4](changes/archive/2026-06-05.03-docs-sync-0.4/design.md)** (#25, 2026-06-05) — 0.4 docs sync +- **[bulkhead](changes/archive/2026-06-05.02-bulkhead/design.md)** (#23, 2026-06-05) — Shipped 0.4.0 — Bulkhead +- **[retry-and-retry-budget](changes/archive/2026-06-05.01-retry-and-retry-budget/design.md)** (#22, 2026-06-05) — Shipped 0.4.0 — Retry + RetryBudget +- **[v0.2-retro-and-housekeeping](changes/archive/2026-06-04.02-v0.2-retro-and-housekeeping/design.md)** (#21, 2026-06-04) — Post-0.2 retro + housekeeping +- **[pydantic-optional-extra](changes/archive/2026-06-04.01-pydantic-optional-extra/design.md)** (#21, 2026-06-04) — Shipped 0.3.0 — pydantic moves to an extra +- **[thin-httpx2-wrapper](changes/archive/2026-06-03.02-thin-httpx2-wrapper/design.md)** (#20, 2026-06-03) — Shipped 0.2.0 — the thin-wrapper pivot +- **[input-validation-pass](changes/archive/2026-06-03.01-input-validation-pass/design.md)** (#19, 2026-06-03) — Input-validation hardening +- **[project-hygiene-tidy](changes/archive/2026-06-02.02-project-hygiene-tidy/design.md)** (#18, 2026-06-02) — Repo hygiene pass +- **[docs-reorg-and-mkdocs](changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/design.md)** (#17, 2026-06-02) — Docs reorg + mkdocs scaffolding +- **[auth-coercion](changes/archive/2026-06-01.01-auth-coercion/design.md)** (#16, 2026-06-01) — Shipped (Epic 2); removed by the v0.2 pivot *(superseded by thin-httpx2-wrapper)* +- **[release-0.1.0-prep](changes/archive/2026-05-31.09-release-0.1.0-prep/design.md)** (#14, 2026-05-31) — 0.1.0 released +- **[recordedtransport](changes/archive/2026-05-31.08-recordedtransport/design.md)** (#13, 2026-05-31) — Shipped in 0.1.0; removed by the v0.2 pivot *(superseded by thin-httpx2-wrapper)* +- **[asyncclient](changes/archive/2026-05-31.07-asyncclient/design.md)** (#12, 2026-05-31) — Shipped in 0.1.0; rewritten by the v0.2 pivot *(superseded by thin-httpx2-wrapper)* +- **[msgspec-decoder-via-extras](changes/archive/2026-05-31.06-msgspec-decoder-via-extras/design.md)** (#11, 2026-05-31) — Shipped in 0.1.0; carry-forward decoder +- **[request-immutability-helpers](changes/archive/2026-05-31.05-request-immutability-helpers/design.md)** (#10, 2026-05-31) — Shipped in 0.1.0; removed by the v0.2 pivot *(superseded by thin-httpx2-wrapper)* +- **[phase-shortcut-decorators](changes/archive/2026-05-31.04-phase-shortcut-decorators/design.md)** (#9, 2026-05-31) — Shipped in 0.1.0; survived the v0.2 pivot +- **[middleware-protocol-and-chain](changes/archive/2026-05-31.03-middleware-protocol-and-chain/design.md)** (#8, 2026-05-31) — Shipped in 0.1.0; survived the v0.2 pivot +- **[shipped-work-review](changes/archive/2026-05-31.02-shipped-work-review/design.md)** (#7, 2026-05-31) — 0.1.0-era review of shipped stories +- **[bmad-to-superpowers-transition](changes/archive/2026-05-31.01-bmad-to-superpowers-transition/design.md)** (#6, 2026-05-31) — Bootstrapped the planning workflow + +## Other + +- **[`architecture/`](../architecture/)** at the repo root — the living + per-capability truth (overview, client, middleware, decoders, errors, + resilience, optional extras, testing). This is the promotion target on + every ship. +- **[audits/](audits/)** — findings reports (deep audit + delta audits) and + their `scripts/` tooling. +- **[deferred.md](deferred.md)** — real-but-unscheduled items with revisit + triggers. diff --git a/planning/_templates/change.md b/planning/_templates/change.md new file mode 100644 index 0000000..0fe24c0 --- /dev/null +++ b/planning/_templates/change.md @@ -0,0 +1,38 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Change: One-line capitalized title + +**Lane:** lightweight — ≲30 LOC net, ≤2 files, no new file, no public-API +change, a single straightforward test. If it outgrows this, split into +`design.md` + `plan.md`. + +## Goal + +One or two sentences: what changes and why. + +## Approach + +The shape of the change in brief — enough that a reviewer sees the design +without a full spec. Link the truth home (`architecture/.md`) if a +capability contract moves. + +## Files + +- `path/to/file.py` — what changes +- `tests/test_x.py` — test added / updated + +## Verification + +- [ ] Failing test first — command + expected error. +- [ ] Apply the change. +- [ ] Test passes — command. +- [ ] `just test` — full suite green. +- [ ] `just lint` — clean. diff --git a/planning/_templates/design.md b/planning/_templates/design.md new file mode 100644 index 0000000..fb0fe5b --- /dev/null +++ b/planning/_templates/design.md @@ -0,0 +1,55 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Design: One-line capitalized title + +## Summary + +One paragraph. What changes, at the level a reader needs to decide if this +spec is worth reading in full. + +## Motivation + +Why now. What is broken or missing. Concrete observations / numbers, not +abstract complaints. Link to memory entries or earlier specs when relevant. + +## Non-goals + +What is deliberately out of scope and (when nontrivial) why. Each item is +a sentence; one line each. + +## Design + +### 1. + +What changes, in enough detail that a reader who has not seen the codebase +can follow. Code samples / diagrams welcome. + +### 2. + +... + +## Operations + +Out-of-repo steps (DNS, infra, external account changes). Omit if none. + +## Out of scope + +Already covered above under Non-goals if appropriate. Repeat-list of +explicitly-excluded follow-ups belongs here when the list is long. + +## Testing + +How we know it landed correctly. New pytest? Smoke check on live URL? +Lint pass? Be specific. + +## Risk + +What could go wrong, ranked by likelihood × impact. Mitigations. diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md new file mode 100644 index 0000000..f2b90e8 --- /dev/null +++ b/planning/_templates/plan.md @@ -0,0 +1,56 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +spec: my-change +pr: null +--- + +# — implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** One sentence — what shipping this plan achieves. No design +rationale; link to the spec for that. + +**Spec:** [`design.md`](./design.md) + +**Branch:** `feat/my-change` (or `fix/`, `chore/`, etc.) + +**Commit strategy:** Per-task commits / single commit / squash on merge. +Whichever fits. + +--- + +### Task 1: + +**Files:** +- Modify: `path/to/file.py` +- Create: `path/to/new.py` + +One sentence on what this task accomplishes. No deeper reasoning — that's +in the spec. + +- [ ] **Step 1: ** + + Run / edit / verify command. Expected output. + +- [ ] **Step 2: ** + + ... + +- [ ] **Step 3: Commit** + + ```bash + git add path/to/file.py + git commit -m ": + + Co-Authored-By: Claude Opus 4.7 (1M context) " + ``` + +--- + +### Task 2: ... diff --git a/planning/audit/2026-06-07-deep-audit.md b/planning/audits/2026-06-07-deep-audit.md similarity index 100% rename from planning/audit/2026-06-07-deep-audit.md rename to planning/audits/2026-06-07-deep-audit.md diff --git a/planning/audit/2026-06-12-delta-audit.md b/planning/audits/2026-06-12-delta-audit.md similarity index 100% rename from planning/audit/2026-06-12-delta-audit.md rename to planning/audits/2026-06-12-delta-audit.md diff --git a/planning/audit/2026-06-13-delta-audit.md b/planning/audits/2026-06-13-delta-audit.md similarity index 100% rename from planning/audit/2026-06-13-delta-audit.md rename to planning/audits/2026-06-13-delta-audit.md diff --git a/planning/audit/_discover.json b/planning/audits/scripts/_discover.json similarity index 100% rename from planning/audit/_discover.json rename to planning/audits/scripts/_discover.json diff --git a/planning/audit/workflow-delta.mjs b/planning/audits/scripts/workflow-delta.mjs similarity index 100% rename from planning/audit/workflow-delta.mjs rename to planning/audits/scripts/workflow-delta.mjs diff --git a/planning/audit/workflow.mjs b/planning/audits/scripts/workflow.mjs similarity index 100% rename from planning/audit/workflow.mjs rename to planning/audits/scripts/workflow.mjs diff --git a/planning/changes/active/2026-06-13.03-portable-planning-convention/design.md b/planning/changes/active/2026-06-13.03-portable-planning-convention/design.md new file mode 100644 index 0000000..86dc496 --- /dev/null +++ b/planning/changes/active/2026-06-13.03-portable-planning-convention/design.md @@ -0,0 +1,225 @@ +--- +status: draft +date: 2026-06-13 +slug: portable-planning-convention +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Design: Adopt the portable two-axis planning convention + +## Summary + +Replace `httpware`'s current `planning/specs/` + `planning/plans/` + +`planning/archive/` layout with the portable two-axis convention already +running in `faststream-outbox`: a **truth axis** (per-capability +`architecture/` files at the repo root, present-tense living prose) and a +**history axis** (`planning/changes/{active,archive}//` +change bundles, frozen on ship). The single `planning/engineering.md` truth +file is **split** into eight capability files under `architecture/`; the ~38 +existing spec/plan pairs are regrouped into dated change bundles with full +backfilled frontmatter; the `planning/README.md` carries a byte-identical +`## Conventions` block plus a repo-specific Index. This change is itself the +inaugural `changes/active/` bundle, demonstrating the convention it defines. + +## Motivation + +- **Cross-repo consistency.** `faststream-outbox` and `httpware` are sibling + `modern-python` packages. A single portable convention (same README + `Conventions` block, same `_templates/`, same bundle shape) means an agent + or contributor moving between them sees one workflow, not two. +- **The current layout mixes the two axes.** `planning/specs/` / + `planning/plans/` are flat and split a single change across two + directories; the spec↔plan pair for one feature lives in two places. + Worse, "active" has drifted: per the project's own record every roadmap + item is shipped (0.10.1 shipped 2026-06-13), yet 17 shipped pairs still sit + in the flat `specs/`/`plans/` dirs because nothing moved them to + `archive/`. The archive only reaches 2026-06-05. The layout no longer + tells the truth about what is in flight. +- **`engineering.md` is becoming a bottleneck and is already stale.** It is a + single 20 KB file written as history ("as of 0.9.0…"), and it predates the + 0.10.0 CircuitBreaker/AsyncTimeout work — it documents neither. A truth + home should be present-tense and current; splitting into capability files + makes each one small, owned, and individually promotable on ship. + +## Non-goals + +- **No rewrite of capability content** beyond the present-tense reflow and + the one currency fix (CircuitBreaker/AsyncTimeout, below). The split moves + and reframes existing prose; it does not re-litigate any design. +- **No change to `releases/` or `retros/`.** Both already match the target + shape and stay put. +- **No production-code change.** This touches `architecture/`, `planning/`, + `docs/`, and `CLAUDE.md` only. `src/` and `tests/` are untouched. +- **No new audit/retro content.** Existing audit reports and retros move or + stay; none are authored here. + +## Design + +### 1. Two axes + +Adopt the `faststream-outbox` model verbatim: + +- **`architecture/` (repo root) — the present.** One file per capability, + living present-tense prose, no frontmatter, dated by git. The truth home; + shipping a change **promotes** its conclusions here by hand. +- **`planning/changes/` — the past-and-pending.** One folder per change, + frozen on ship. + +### 2. Split `engineering.md` into `architecture/` (the re-projection) + +This is not a mechanical carve. `engineering.md` is written as history; the +truth axis is the present. So the split **re-projects** onto two axes: +present-tense capability prose goes to `architecture/`; history, roadmap, and +deferred items go to the history axis (change bundles, `releases/`, +`deferred.md`) or are dropped. The "as of 0.x …" narration is flattened to +present tense. + +Eight capability files, aligned to the codebase seams and to the docs that +reference `engineering.md` by section number: + +| `architecture/` file | Source in `engineering.md` | Docs repointed here | +|----------------------|----------------------------|---------------------| +| `overview.md` | §1 intent (present tense), §2 CI-enforced invariants, §5 module-layout map | `docs/index.md` blob link | +| `client.md` | `Client`/`AsyncClient` surface, the internal terminal, error-mapping location, sync/async parity, `stream()` | — | +| `middleware.md` | §3 Seam A (chain, `compose`/`compose_async`, frozen-at-construction, phase decorators) + "why no standalone OTel middleware" | `docs/middleware.md` `§3`, `§8` | +| `decoders.md` | §3 Seam B (`can_decode`/`decode` dispatch, default-list resolution, single-pass rule, per-instance cache, `MissingDecoderError` pre-flight) | — | +| `errors.md` | §4 exception contract (`StatusError` tree, single positional `response`, `STATUS_TO_EXCEPTION`, dual-inherit `TimeoutError`, `DecodeError`, credential stripping) | `docs/errors.md` `§4` | +| `resilience.md` | Retry/`RetryBudget`/Bulkhead/backoff **+ CircuitBreaker/AsyncTimeout** + the logging/OTel events those middlewares emit | `docs/resilience.md` `§3` | +| `extras.md` | §3 Seam C + §7 optional-extras pattern + the extra-isolation test | — | +| `testing.md` | §6 testing patterns | `docs/testing.md` `§6` | + +**What dissolves** (does *not* become an `architecture/` file, because it is +history not present): + +- §8 roadmap — every item is shipped or retired; the record now lives in the + archived change bundles + `releases/`. +- The v0.1→v0.2 "deleted / rewritten by the pivot" archaeology — lives in the + v0.2 pivot retro and its archived bundle. +- §9 deferred-work stub — its only content is a pointer to `deferred.md`. + +`planning/engineering.md` is **deleted** once its content lands in +`architecture/`. `CLAUDE.md` names `architecture/` as the promotion target in +its place. + +### 3. Currency fix: CircuitBreaker / AsyncTimeout + +`engineering.md` predates 0.10.0 and documents neither `CircuitBreaker` nor +`AsyncTimeout`. A `resilience.md` that omitted them would publish a knowingly +stale truth file, so `resilience.md` folds in a present-tense paragraph for +both, sourced from the shipped +`changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md`. This is +the smallest honest content addition; no broader rewrite. + +### 4. `planning/README.md` + +- **Intro paragraph** (repo-specific): truth lives in `architecture/` at the + repo root; this directory records how it got there. +- **`## Conventions`** (portable): copied **byte-identical** from + `faststream-outbox/planning/README.md` — including its + `architecture/.md` / "one file per capability" language, which + is now literally accurate for this repo. +- **`## Index`** (repo-specific): **Active** (this convention change) and + **Archived (shipped)** lists; **Other** points at `architecture/`, + `audits/`, `deferred.md`. + +### 5. `_templates/` + +Copy `design.md`, `plan.md`, `change.md` from +`faststream-outbox/planning/_templates/` **as-is**. + +### 6. Change-bundle migration (full backfill) + +- **`changes/active/`** holds exactly one bundle after migration: this + change, `2026-06-13.NN-portable-planning-convention/`. Every other existing + spec/plan is shipped. +- **`changes/archive/`** — all 17 flat `specs/`+`plans/` pairs and the ~21 + `archive/specs/`+`archive/plans/` pairs regroup into + `/{design,plan}.md`. `.NN` ordering is derived from + **git merge order / PR number** (several dates collide — 2026-05-31 ×8, + 2026-06-05 ×7, 2026-06-08 ×8, 2026-06-13 dense). Frontmatter is fully + backfilled: `status: shipped`, `date`, `slug`, `pr:`, `outcome:`, and + `supersedes`/`superseded_by` where a v0.1 surface was superseded by the + v0.2 pivot. +- **Orphans** (design with no matching plan) become **design-only bundles** + (no `plan.md`): `2026-05-31-shipped-work-review.md` and + `2026-06-04-v0.2-retro-and-housekeeping-design.md`. +- **Audit pairs vs reports.** The "run an audit" design+plan pairs + (`deep-audit`, `delta-audit`) are change bundles like any other. The + resulting **findings reports** (`planning/audit/2026-06-*.md`) are not + bundles — see §7. + +### 7. Other directory moves + +- `planning/audit/2026-06-*.md` (reports) → `planning/audits/`. +- `planning/audit/{workflow,workflow-delta}.mjs`, `_discover.json` (tooling) + → `planning/audits/scripts/`. +- `planning/retros/`, `planning/releases/` — unchanged. +- `planning/deferred-work.md` → `planning/deferred.md` (rename for cross-repo + consistency). + +### 8. Link repointing + +- **`docs/` (published site, 6 refs).** `planning/engineering.md §N` → + `architecture/.md#anchor` per the §2 table. The + `docs/index.md` GitHub blob link → `architecture/` (or `overview.md`). + `mkdocs build --strict` is the backstop that proves none was missed. +- **`CLAUDE.md`.** Rewrite "Where to find what", the Per-feature Workflow + line, the Seam B link, and "When in doubt" to name `architecture/` as the + promotion target and the `planning/changes/` flow as the lane. Keep all + architecture-invariant content intact. +- **`engineering.md` internal links** disappear with the file; the content + they sat in moves into the right `architecture/` file using in-file anchors + rather than `planning/...` paths. +- **`releases/`** — scan for any inbound `planning/specs|plans|archive` + references and repoint to the archived bundle path. + +## Operations + +None. No DNS, infra, or external-account changes. (The docs site rebuilds +from `main` on the existing workflow; no config change.) + +## Out of scope + +- Coarser/finer `architecture/` granularity than the eight files above — the + eight mirror the codebase seams and make every docs `§N` repoint a clean + file+anchor. +- Splitting `faststream-outbox` further or changing its convention — this + repo consumes that convention, it does not modify it. +- Any `src/`/`tests/` change. + +## Testing + +- `just lint` and `just lint-ci` — clean (markdown / eof-fixer / ruff scope + is unaffected, but run to be sure no tracked file regressed). +- `mkdocs build --strict` — proves every repointed docs link resolves. +- Grep gates: + - `planning/engineering.md` no longer exists. + - No tracked file outside `planning/changes/archive/` references + `planning/(specs|plans|archive|audit|deferred-work)` or + `planning/engineering.md`. + - `architecture/` contains exactly the eight files; none carries + frontmatter. + - `changes/active/` contains exactly this bundle. + +## Risk + +- **Broken docs link slips through** (likely / medium). *Mitigation:* + `mkdocs build --strict` fails the build on any unresolved internal link; + the grep gate catches stale `planning/...` paths the strict build would not + (e.g. links pointing outside `docs/`). +- **Wrong `.NN` ordering on collision-heavy dates** (medium / low). + *Mitigation:* derive every `.NN` from `git log` merge order / PR number, + not from guesswork; the ordering only affects timeline sort, not + correctness of content. +- **Re-projection drops or distorts a fact** while flattening "as of 0.x" + prose (medium / medium). *Mitigation:* the split is move-and-reframe, not + rewrite; each `architecture/` file is diffable against the corresponding + `engineering.md` section, and the currency fix is the only intentional + content addition. +- **Scope creep** — the migration is large (~38 bundles, 8 files, full + backfill). *Mitigation:* the plan sequences it into independent, + verifiable tasks (templates+README first, then split, then bundle + migration, then link repointing, then verification). diff --git a/planning/changes/active/2026-06-13.03-portable-planning-convention/plan.md b/planning/changes/active/2026-06-13.03-portable-planning-convention/plan.md new file mode 100644 index 0000000..283a0c0 --- /dev/null +++ b/planning/changes/active/2026-06-13.03-portable-planning-convention/plan.md @@ -0,0 +1,552 @@ +--- +status: draft +date: 2026-06-13 +slug: portable-planning-convention +spec: portable-planning-convention +pr: null +--- + +# portable-planning-convention — implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate `httpware`'s planning layout to the portable two-axis +convention: per-capability `architecture/` truth files + `planning/changes/` +bundles, with all history backfilled and every inbound link repointed. + +**Architecture:** Split `planning/engineering.md` into eight present-tense +`architecture/` capability files; regroup the ~38 existing spec/plan pairs +into `planning/changes/archive//` bundles with full +frontmatter (PR + outcome from the embedded map below); author a +byte-identical `## Conventions` README; repoint the six `docs/` references and +`CLAUDE.md`. Docs-only change — no `src/` or `tests/` edits. + +**Tech stack:** Markdown, `git mv`, `just lint-ci`, `mkdocs build --strict`. + +**Spec:** [`design.md`](./design.md) + +**Branch:** `chore/portable-planning-convention` (already created; the active +bundle's `design.md` + this `plan.md` are already committed there). + +**Commit strategy:** One commit per task. + +--- + +## Reference: the complete bundle map + +Every archived bundle below. `id` = `.NN-` (date = the proposal +date on the existing filename; `.NN` = PR-merge order within that date). Each +bundle gets `design.md` (+ `plan.md` unless marked **design-only**). Source +files are the current `planning/{specs,plans,archive/specs,archive/plans}/` +paths sharing the `-` stem. + +| Bundle id | PR | outcome (frontmatter `outcome:`) | supersedes / superseded_by | +|-----------|----|----------------------------------|----------------------------| +| `2026-05-31.01-bmad-to-superpowers-transition` | #6 | Bootstrapped the planning workflow | — | +| `2026-05-31.02-shipped-work-review` **design-only** | #7 | 0.1.0-era review of shipped stories | — | +| `2026-05-31.03-middleware-protocol-and-chain` | #8 | Shipped in 0.1.0; survived the v0.2 pivot | — | +| `2026-05-31.04-phase-shortcut-decorators` | #9 | Shipped in 0.1.0; survived the v0.2 pivot | — | +| `2026-05-31.05-request-immutability-helpers` | #10 | Shipped in 0.1.0; removed by the v0.2 pivot | superseded_by: `2026-06-03.02-thin-httpx2-wrapper` | +| `2026-05-31.06-msgspec-decoder-via-extras` | #11 | Shipped in 0.1.0; carry-forward decoder | — | +| `2026-05-31.07-asyncclient` | #12 | Shipped in 0.1.0; rewritten by the v0.2 pivot | superseded_by: `2026-06-03.02-thin-httpx2-wrapper` | +| `2026-05-31.08-recordedtransport` | #13 | Shipped in 0.1.0; removed by the v0.2 pivot | superseded_by: `2026-06-03.02-thin-httpx2-wrapper` | +| `2026-05-31.09-release-0.1.0-prep` | #14 | 0.1.0 released | — | +| `2026-06-01.01-auth-coercion` | #16 | Shipped (Epic 2); removed by the v0.2 pivot | superseded_by: `2026-06-03.02-thin-httpx2-wrapper` | +| `2026-06-02.01-docs-reorg-and-mkdocs` | #17 | Docs reorg + mkdocs scaffolding | — | +| `2026-06-02.02-project-hygiene-tidy` | #18 | Repo hygiene pass | — | +| `2026-06-03.01-input-validation-pass` | #19 | Input-validation hardening | — | +| `2026-06-03.02-thin-httpx2-wrapper` | #20 | Shipped 0.2.0 — the thin-wrapper pivot | supersedes: `2026-05-31.05`, `.07`, `.08`, `2026-06-01.01` | +| `2026-06-04.01-pydantic-optional-extra` | #21 | Shipped 0.3.0 — pydantic moves to an extra | — | +| `2026-06-04.02-v0.2-retro-and-housekeeping` **design-only** | #21 | Post-0.2 retro + housekeeping | — | +| `2026-06-05.01-retry-and-retry-budget` | #22 | Shipped 0.4.0 — Retry + RetryBudget | — | +| `2026-06-05.02-bulkhead` | #23 | Shipped 0.4.0 — Bulkhead | — | +| `2026-06-05.03-docs-sync-0.4` | #25 | 0.4 docs sync | — | +| `2026-06-05.04-streaming` | #26 | Shipped 0.5.0 — `stream()` | — | +| `2026-06-05.05-observability` | #27 | Shipped 0.6.0 — logging + OTel events | — | +| `2026-06-05.06-extension-slot-docs` | #28 | Shipped 0.7.0 — middleware docs | — | +| `2026-06-05.07-v0.7-docs-expansion` | #28 | Shipped 0.7.0 — first-cut user docs | — | +| `2026-06-06.01-modern-di-recipe` | #29 | modern-di DI recipe doc | — | +| `2026-06-07.01-sync-client` | #31 | Shipped 0.8.0 — sync `Client` + `Async*` rename | — | +| `2026-06-07.02-decoder-error` | #32 | Shipped 0.8.1 — `DecodeError` at seam B | — | +| `2026-06-07.03-deep-audit` | #32 | Deep audit; findings closed across 0.8.1–0.8.6 | — | +| `2026-06-08.01-send-with-response` | #33 | Shipped 0.8.2 — `send_with_response` | — | +| `2026-06-08.02-retry-budget-cluster` | #34 | Shipped 0.8.3 — 7 RetryBudget findings | — | +| `2026-06-08.03-post-080-doc-sweep` | #34 | Post-0.8.0 doc sweep | — | +| `2026-06-08.04-otel-partial-install` | #35 | Shipped 0.8.4 — OTel partial-install guards | — | +| `2026-06-08.05-small-fixes-mop-up` | #36 | Shipped 0.8.5 — 4 small audit findings | — | +| `2026-06-08.06-test-mop-up` | #37 | Shipped 0.8.6 — test-only audit findings | — | +| `2026-06-08.07-mkdocs-gh-pages-migration` | #38 | Docs host → GitHub Pages | — | +| `2026-06-08.08-readme-link-cleanup` | #39 | README link cleanup | — | +| `2026-06-10.01-multi-decoder` | #41 | Shipped 0.9.0 — multi-decoder routing | — | +| `2026-06-10.02-decoder-instance-cache` | #42 | Shipped 0.9.0 — per-instance decoder cache | — | +| `2026-06-12.01-delta-audit` | #43 | 0.9.0 delta audit; closed via 0.9.1 | — | +| `2026-06-13.01-msgspec-nested-customtype-fix` | #43 | Shipped 0.9.1 — nested-CustomType guard | — | +| `2026-06-13.02-circuit-breaker-and-timeout` | #51 | Shipped 0.10.0 — CircuitBreaker + AsyncTimeout | — | + +`2026-06-13.03-portable-planning-convention` is **this** change — already in +`changes/active/`, frontmatter finalized at merge (Task 8). + +Filename→stem note: the source plan for circuit-breaker is +`planning/plans/2026-06-13-circuit-breaker-and-timeout.md` (no `-plan` +suffix); everything else uses `-design.md` / `-plan.md`. + +--- + +### Task 1: Bootstrap the convention skeleton + +**Files:** +- Modify: `.gitignore` (remove the bare `plan.md` rule) +- Create: `planning/_templates/{design,plan,change}.md` +- Create dirs: `planning/changes/archive/`, `planning/audits/scripts/` +- Rename: `planning/deferred-work.md` → `planning/deferred.md` + +- [ ] **Step 0: Remove the bare `plan.md` gitignore rule** + + Line 26 of `.gitignore` is a bare `plan.md` (intended for a root scratch + file) that would make every bundle's `plan.md` untracked — incompatible + with the convention. Delete that line. Verify: + ```bash + ! git check-ignore planning/changes/active/2026-06-13.03-portable-planning-convention/plan.md && echo "plan.md tracked" + ``` + Expected: `plan.md tracked`. (Already applied during planning if committed + with this bundle; confirm it stuck.) + +- [ ] **Step 1: Copy the templates as-is** + + ```bash + mkdir -p planning/_templates planning/changes/archive planning/audits/scripts + cp /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/design.md planning/_templates/design.md + cp /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/plan.md planning/_templates/plan.md + cp /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/change.md planning/_templates/change.md + ``` + +- [ ] **Step 2: Rename deferred-work.md and keep a `.gitkeep`-free clean tree** + + ```bash + git mv planning/deferred-work.md planning/deferred.md + ``` + +- [ ] **Step 3: Verify the templates are byte-identical to source** + + ```bash + for f in design plan change; do + diff -q /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/$f.md planning/_templates/$f.md + done + ``` + Expected: no output (identical). + +- [ ] **Step 4: Commit** + + The `git mv` in Step 2 already staged the rename; just add the templates. + ```bash + git add planning/_templates planning/deferred.md + git commit -m "chore(planning): bootstrap convention skeleton (templates, deferred.md rename) + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 2: Split `engineering.md` into `architecture/` (and delete it) + +**Files:** +- Create: `architecture/{overview,client,middleware,decoders,errors,resilience,extras,testing}.md` +- Delete: `planning/engineering.md` +- Source: `planning/engineering.md` (read in full first) + +Re-projection rules for every file: +1. Relocate the mapped `engineering.md` content; **flatten "as of 0.x …" + narration to present tense** (the truth axis is the present). +2. **Drop history pointers** (`see planning/archive/specs/…`, "Shipped in + v0.X", roadmap "see …" links) — that record now lives in the bundles. +3. **No frontmatter** in any `architecture/` file (living prose, dated by git). +4. Do **not** carry §8 (roadmap), the v0.1→v0.2 "deleted/rewritten" + archaeology, or the §9 deferred stub into any file — they dissolve. + +Per-file source map: + +| File | From `engineering.md` | +|------|-----------------------| +| `overview.md` | §1 (present-tense intent + the "three things" framing + "httpx2 is part of the public surface"), §2 invariants (keep the *why*), §5 module-layout tree | +| `client.md` | the `Client`/`AsyncClient` surface, the internal terminal + error-mapping location (from §3 intro + §4 paras 4–5), sync/async parity, `stream()` (from §1 0.5.0 line) | +| `middleware.md` | §3 Seam A in full + the "why no standalone OTel middleware" rationale (from §8's 5-4 retirement note) | +| `decoders.md` | §3 Seam B in full (dispatch, default-list, single-pass rule, per-instance cache, `MissingDecoderError`) | +| `errors.md` | §4 in full | +| `resilience.md` | Retry/`RetryBudget`/Bulkhead/backoff + the logging/OTel events they emit + **new** CircuitBreaker/AsyncTimeout paragraph | +| `extras.md` | §3 Seam C + §7 optional-extras pattern + the isolation test | +| `testing.md` | §6 in full | + +- [ ] **Step 1: Read the source** + + Read `planning/engineering.md` end to end before writing any file. + +- [ ] **Step 2: Author the eight files** + + Write each `architecture/*.md` per the map and the re-projection rules. + Each file opens with an `#` H1 title (e.g. `# Errors`) and present-tense + prose. Keep code samples (the `raise NotFoundError(response)` block, the + `pyproject` extras block, the module tree) verbatim. + +- [ ] **Step 3: Add the CircuitBreaker/AsyncTimeout paragraph to `resilience.md`** + + Source the present-tense facts from + `planning/plans/2026-06-13-circuit-breaker-and-timeout.md` and + `planning/specs/2026-06-13-circuit-breaker-and-timeout-design.md` (still at + their pre-migration paths during this task). Cover: `AsyncCircuitBreaker` + (classic consecutive-failure) + sync `CircuitBreaker`; `CircuitOpenError`; + `AsyncTimeout` overall-deadline middleware (rejects non-finite timeouts); + and the documented composition order (`AsyncBulkhead` → … per the shipped + docs). Do not invent behavior not in those sources. + +- [ ] **Step 4: Delete the old truth file** + + ```bash + git rm planning/engineering.md + ``` + +- [ ] **Step 5: Sanity-check no `architecture/` file carries frontmatter or history pointers** + + ```bash + ! grep -rlE '^---$|planning/(specs|plans|archive)|^status:' architecture/ && echo OK + ``` + Expected: `OK`. + +- [ ] **Step 6: Commit** + + Step 4 already staged the deletion; just add the new files. + ```bash + git add architecture/ + git commit -m "docs(architecture): split engineering.md into per-capability truth files + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 3: Migrate the archive cohort (2026-05-31 → 2026-06-05) + +**Files:** rows `2026-05-31.*` through `2026-06-05.*` of the bundle map. +Source dirs: `planning/archive/specs/`, `planning/archive/plans/`. + +For each bundle: +- `mkdir -p planning/changes/archive/` +- `git mv` the design source → `planning/changes/archive//design.md` +- `git mv` the plan source → `planning/changes/archive//plan.md` (skip for + **design-only** rows) +- Prepend frontmatter to `design.md`: `status: shipped`, `date: `, + `slug: `, `supersedes`/`superseded_by` per the map (else `null`), + `pr: `, `outcome: ''`. To `plan.md`: `status: shipped`, + `date`, `slug`, `spec: `, `pr: `. +- If the file already has a leading `# …` H1, leave the body; insert + frontmatter above it. + +- [ ] **Step 1: Move + frontmatter the 2026-05-31 cohort (rows .01–.09)** + + Process each `2026-05-31.NN` row. Example for `.03`: + ```bash + mkdir -p planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain + git mv planning/archive/specs/2026-05-31-middleware-protocol-and-chain-design.md \ + planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/design.md + git mv planning/archive/plans/2026-05-31-middleware-protocol-and-chain-plan.md \ + planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/plan.md + ``` + Then add frontmatter per the rule above. `.02-shipped-work-review` is + **design-only** (source `planning/archive/specs/2026-05-31-shipped-work-review.md`, + no plan). + +- [ ] **Step 2: Move + frontmatter the 2026-06-01 → 2026-06-05 cohort** + + Rows `2026-06-01.01` through `2026-06-05.07`. `.02-v0.2-retro-and-housekeeping` + (2026-06-04) is **design-only**. Apply `supersedes:` to + `2026-06-03.02-thin-httpx2-wrapper` and `superseded_by:` to its three + superseded bundles per the map. + +- [ ] **Step 3: Verify the source dirs are empty and removable** + + ```bash + ls planning/archive/specs planning/archive/plans 2>/dev/null + ``` + Expected: empty. Then `git rm -r --ignore-unmatch` is unnecessary (git mv + already staged the moves); remove the now-empty `planning/archive/` tree: + ```bash + rmdir planning/archive/specs planning/archive/plans planning/archive 2>/dev/null || true + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add planning/changes/archive + git commit -m "docs(planning): migrate 0.1.0–0.7.0 specs/plans into change bundles + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 4: Migrate the flat cohort (2026-06-06 → 2026-06-13) + +**Files:** rows `2026-06-06.*` through `2026-06-13.02` of the bundle map. +Source dirs: `planning/specs/`, `planning/plans/`. + +Same move+frontmatter procedure as Task 3. + +- [ ] **Step 1: Move + frontmatter rows 2026-06-06 → 2026-06-08** + + Process `2026-06-06.01` through `2026-06-08.08`. Both `decoder-error` and + `deep-audit` are `pr: 32`; both `retry-budget-cluster` and + `post-080-doc-sweep` are `pr: 34`. Example for `deep-audit`: + ```bash + mkdir -p planning/changes/archive/2026-06-07.03-deep-audit + git mv planning/specs/2026-06-07-deep-audit-design.md \ + planning/changes/archive/2026-06-07.03-deep-audit/design.md + git mv planning/plans/2026-06-07-deep-audit-plan.md \ + planning/changes/archive/2026-06-07.03-deep-audit/plan.md + ``` + +- [ ] **Step 2: Move + frontmatter rows 2026-06-10 → 2026-06-13** + + Process `2026-06-10.01` through `2026-06-13.02`. For circuit-breaker the + plan source has no `-plan` suffix: + ```bash + mkdir -p planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout + git mv planning/specs/2026-06-13-circuit-breaker-and-timeout-design.md \ + planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md + git mv planning/plans/2026-06-13-circuit-breaker-and-timeout.md \ + planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md + ``` + +- [ ] **Step 3: Verify flat source dirs are empty** + + ```bash + ls planning/specs planning/plans 2>/dev/null + ``` + Expected: empty. Then: + ```bash + rmdir planning/specs planning/plans 2>/dev/null || true + ``` + +- [ ] **Step 4: Commit** + + ```bash + git add planning/changes/archive + git commit -m "docs(planning): migrate 0.8.0–0.10.0 specs/plans into change bundles + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 5: Move audit reports and tooling + +**Files:** +- `planning/audit/2026-06-07-deep-audit.md` → `planning/audits/2026-06-07-deep-audit.md` +- `planning/audit/2026-06-12-delta-audit.md` → `planning/audits/2026-06-12-delta-audit.md` +- `planning/audit/2026-06-13-delta-audit.md` → `planning/audits/2026-06-13-delta-audit.md` +- `planning/audit/{workflow,workflow-delta}.mjs`, `_discover.json` → `planning/audits/scripts/` + +- [ ] **Step 1: Move reports and tooling** + + ```bash + git mv planning/audit/2026-06-07-deep-audit.md planning/audits/2026-06-07-deep-audit.md + git mv planning/audit/2026-06-12-delta-audit.md planning/audits/2026-06-12-delta-audit.md + git mv planning/audit/2026-06-13-delta-audit.md planning/audits/2026-06-13-delta-audit.md + git mv planning/audit/workflow.mjs planning/audits/scripts/workflow.mjs + git mv planning/audit/workflow-delta.mjs planning/audits/scripts/workflow-delta.mjs + git mv planning/audit/_discover.json planning/audits/scripts/_discover.json + rmdir planning/audit 2>/dev/null || true + ``` + +- [ ] **Step 2: Verify old audit dir is gone** + + ```bash + test ! -d planning/audit && echo OK + ``` + Expected: `OK`. + +- [ ] **Step 3: Commit** + + ```bash + git add planning/audits + git commit -m "docs(planning): move audit reports to audits/, tooling to audits/scripts/ + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 6: Author `planning/README.md` + +**Files:** +- Create: `planning/README.md` +- Source for the `## Conventions` block: `faststream-outbox/planning/README.md` + lines 7–67 (the `## Conventions` heading through the end of `### Frontmatter`). + +- [ ] **Step 1: Write the repo-specific intro** + + ```markdown + # Planning + + Specs, plans, and change history for `httpware`. The living truth about + *what the system does now* lives in [`architecture/`](../architecture/) at + the repo root; this directory records *how it got there*. + ``` + +- [ ] **Step 2: Append the byte-identical `## Conventions` block** + + Copy `faststream-outbox/planning/README.md` lines 7–67 verbatim. Verify: + ```bash + sed -n '7,67p' /Users/kevinsmith/src/pypi/faststream-outbox/planning/README.md > /tmp/conv-src.md + # after writing planning/README.md, extract the same block and diff: + awk '/^## Conventions$/{f=1} f; /^### Frontmatter$/{c=1} c&&/^## Index$/{exit}' planning/README.md + ``` + The `## Conventions` … through end-of-`### Frontmatter` text must match + `/tmp/conv-src.md` exactly (modern-python repos share this block byte-for-byte). + +- [ ] **Step 3: Write the repo-specific `## Index`** + + - `### Active` → one entry: **portable-planning-convention** + (`changes/active/2026-06-13.03-portable-planning-convention/design.md`). + - `### Archived (shipped)` → one bullet per archived bundle from the map, + newest first, each linking `changes/archive//design.md` with `(#PR, + date)` and a one-line gloss. + - `## Other` → `architecture/` (the promotion target), `audits/`, + `deferred.md`. + +- [ ] **Step 4: Commit** + + ```bash + git add planning/README.md + git commit -m "docs(planning): add README with portable Conventions + repo Index + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 7: Repoint inbound links + +**Files:** +- Modify: `docs/resilience.md:357`, `docs/middleware.md:113`, `docs/middleware.md:201`, `docs/errors.md:186`, `docs/testing.md:114`, `docs/index.md:186` +- Modify: `CLAUDE.md` (the "Where to find what" list, the Per-feature Workflow line, the Seam B link at ~:84, the "When in doubt" links at ~:90–95) +- Scan: `planning/releases/*.md` + +Repoint map (prose `engineering.md §N` → `architecture/`): + +| Location | Old | New | +|----------|-----|-----| +| `docs/resilience.md:357` | `` `planning/engineering.md` §3 `` | `` `architecture/middleware.md` `` | +| `docs/middleware.md:113` | `` `planning/engineering.md` §8 `` | `` `architecture/middleware.md` `` | +| `docs/middleware.md:201` | `` `planning/engineering.md` §3 (Seam A) `` | `` `architecture/middleware.md` (Seam A) `` | +| `docs/errors.md:186` | `` `planning/engineering.md` §4 `` | `` `architecture/errors.md` `` | +| `docs/testing.md:114` | `` `planning/engineering.md` §6 `` | `` `architecture/testing.md` `` | +| `docs/index.md:186` | `[…](https://github.com/modern-python/httpware/blob/main/planning/engineering.md)` | `[…](https://github.com/modern-python/httpware/blob/main/architecture/overview.md)` and reword the gloss to point at the `architecture/` set | + +- [ ] **Step 1: Edit the six `docs/` references** + + Apply the repoint map. These are inline-code prose refs (not mkdocs links), + except `docs/index.md:186` which is an absolute GitHub URL — update the URL + and adjust its description to "per-capability design notes under + `architecture/`". + +- [ ] **Step 2: Rewrite the `CLAUDE.md` planning section** + + - "Where to find what" list: replace the `planning/engineering.md`, + `planning/specs/`/`planning/plans/`, `planning/archive/...`, + `planning/deferred-work.md` bullets with: `architecture/` (the truth home + / promotion target), `planning/changes/{active,archive}/` (change + bundles), `planning/audits/`, `planning/retros/`, `planning/releases/`, + `planning/deferred.md`, `planning/_templates/`. + - Per-feature Workflow line: `brainstorming → design.md in + changes/active// → writing-plans → plan.md in the same bundle → + executing-plans / subagent-driven-development → requesting-code-review → + finishing-a-development-branch; on ship, promote into + architecture/.md and move the bundle to changes/archive/`. + - Seam B link (`[engineering.md](planning/engineering.md) §Seam B`) → + `[architecture/decoders.md](architecture/decoders.md)`. + - "When in doubt" links → `architecture/` (e.g. + `[architecture/overview.md](architecture/overview.md)`). + +- [ ] **Step 3: Scan and repoint `planning/releases/`** + + ```bash + grep -rn -E 'planning/(specs|plans|archive|audit|deferred-work|engineering)' planning/releases/ || echo "none" + ``` + Repoint any hit to the matching `changes/archive//` bundle (or + `architecture/`/`audits/`/`deferred.md`). If `none`, no edit. + +- [ ] **Step 4: Commit** + + ```bash + git add docs CLAUDE.md planning/releases + git commit -m "docs: repoint inbound links to architecture/ + changes/ + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 8: Verify and finalize + +**Files:** none created; verification + active-bundle frontmatter note. + +- [ ] **Step 1: Grep gates — no stale paths remain** + + ```bash + test ! -f planning/engineering.md && echo "engineering.md gone" + # No tracked file outside changes/archive references the old paths: + grep -rIn -E 'planning/(specs|plans|archive|audit|deferred-work)|planning/engineering\.md' \ + --include='*.md' --include='*.yml' --include='*.yaml' --include='*.toml' . \ + | grep -vE '^\./planning/changes/archive/' || echo "no stale refs" + ``` + Expected: `engineering.md gone` and `no stale refs`. + +- [ ] **Step 2: `architecture/` has exactly eight files, none with frontmatter** + + ```bash + ls architecture/ | sort | tr '\n' ' '; echo + ! grep -rl '^---$' architecture/ && echo "no frontmatter" + ``` + Expected: the eight files; `no frontmatter`. + +- [ ] **Step 3: `changes/active/` holds only this bundle** + + ```bash + ls planning/changes/active/ + ``` + Expected: `2026-06-13.03-portable-planning-convention`. + +- [ ] **Step 4: Lint** + + ```bash + just lint-ci + ``` + Expected: clean (no auto-fix needed; CI-equivalent check). + +- [ ] **Step 5: Docs build strict** + + ```bash + uv run mkdocs build --strict + ``` + Expected: build succeeds, no warnings. (If `mkdocs` is not in the default + env, use the docs group: `uv run --group docs mkdocs build --strict`, or + the project's documented docs command.) + +- [ ] **Step 6: Note — active-bundle frontmatter is finalized at merge** + + When this PR merges, set the active bundle's `design.md`/`plan.md` + frontmatter to `status: shipped`, `pr: `, fill `outcome:`, move + the bundle to `changes/archive/2026-06-13.03-portable-planning-convention/`, + and move its Index line from **Active** to **Archived**. (This is the first + exercise of the promotion step the convention defines.) + +- [ ] **Step 7: Commit any verification fixups** + + ```bash + git add -A + git commit -m "chore(planning): verification fixups for convention migration + + Co-Authored-By: Claude Opus 4.8 (1M context) " || echo "nothing to commit" + ``` diff --git a/planning/archive/specs/2026-05-31-bmad-to-superpowers-transition-design.md b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-transition/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-bmad-to-superpowers-transition-design.md rename to planning/changes/archive/2026-05-31.01-bmad-to-superpowers-transition/design.md index ee3b4d9..8130967 100644 --- a/planning/archive/specs/2026-05-31-bmad-to-superpowers-transition-design.md +++ b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-transition/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: bmad-to-superpowers-transition +supersedes: null +superseded_by: null +pr: 6 +outcome: 'Bootstrapped the planning workflow' +--- + # bmad → superpowers transition (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-bmad-to-superpowers-transition-plan.md b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-transition/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-bmad-to-superpowers-transition-plan.md rename to planning/changes/archive/2026-05-31.01-bmad-to-superpowers-transition/plan.md index 12ff9bb..bdb1718 100644 --- a/planning/archive/plans/2026-05-31-bmad-to-superpowers-transition-plan.md +++ b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-transition/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: bmad-to-superpowers-transition +spec: bmad-to-superpowers-transition +pr: 6 +--- + # bmad → superpowers transition Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-shipped-work-review.md b/planning/changes/archive/2026-05-31.02-shipped-work-review/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-shipped-work-review.md rename to planning/changes/archive/2026-05-31.02-shipped-work-review/design.md index 2190c07..64bf2db 100644 --- a/planning/archive/specs/2026-05-31-shipped-work-review.md +++ b/planning/changes/archive/2026-05-31.02-shipped-work-review/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: shipped-work-review +supersedes: null +superseded_by: null +pr: 7 +outcome: '0.1.0-era review of shipped stories' +--- + # Retrospective code review — stories 1-1 through 1-5 - **Date:** 2026-05-31 diff --git a/planning/archive/specs/2026-05-31-middleware-protocol-and-chain-design.md b/planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-middleware-protocol-and-chain-design.md rename to planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/design.md index 84b7f0b..0a100e6 100644 --- a/planning/archive/specs/2026-05-31-middleware-protocol-and-chain-design.md +++ b/planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: middleware-protocol-and-chain +supersedes: null +superseded_by: null +pr: 8 +outcome: 'Shipped in 0.1.0; survived the v0.2 pivot' +--- + # Middleware protocol and chain composition (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-middleware-protocol-and-chain-plan.md b/planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-middleware-protocol-and-chain-plan.md rename to planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/plan.md index 6330762..1da5b54 100644 --- a/planning/archive/plans/2026-05-31-middleware-protocol-and-chain-plan.md +++ b/planning/changes/archive/2026-05-31.03-middleware-protocol-and-chain/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: middleware-protocol-and-chain +spec: middleware-protocol-and-chain +pr: 8 +--- + # Middleware protocol and chain composition Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-phase-shortcut-decorators-design.md b/planning/changes/archive/2026-05-31.04-phase-shortcut-decorators/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-phase-shortcut-decorators-design.md rename to planning/changes/archive/2026-05-31.04-phase-shortcut-decorators/design.md index fa5371d..4d85348 100644 --- a/planning/archive/specs/2026-05-31-phase-shortcut-decorators-design.md +++ b/planning/changes/archive/2026-05-31.04-phase-shortcut-decorators/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: phase-shortcut-decorators +supersedes: null +superseded_by: null +pr: 9 +outcome: 'Shipped in 0.1.0; survived the v0.2 pivot' +--- + # Phase-shortcut decorators (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-phase-shortcut-decorators-plan.md b/planning/changes/archive/2026-05-31.04-phase-shortcut-decorators/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-phase-shortcut-decorators-plan.md rename to planning/changes/archive/2026-05-31.04-phase-shortcut-decorators/plan.md index 5a0d92e..25161f4 100644 --- a/planning/archive/plans/2026-05-31-phase-shortcut-decorators-plan.md +++ b/planning/changes/archive/2026-05-31.04-phase-shortcut-decorators/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: phase-shortcut-decorators +spec: phase-shortcut-decorators +pr: 9 +--- + # Phase-shortcut decorators Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-request-immutability-helpers-design.md b/planning/changes/archive/2026-05-31.05-request-immutability-helpers/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-request-immutability-helpers-design.md rename to planning/changes/archive/2026-05-31.05-request-immutability-helpers/design.md index 65ad975..05d735c 100644 --- a/planning/archive/specs/2026-05-31-request-immutability-helpers-design.md +++ b/planning/changes/archive/2026-05-31.05-request-immutability-helpers/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: request-immutability-helpers +supersedes: null +superseded_by: 2026-06-03.02-thin-httpx2-wrapper +pr: 10 +outcome: 'Shipped in 0.1.0; removed by the v0.2 pivot' +--- + # Request / Response immutability helper expansion (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-request-immutability-helpers-plan.md b/planning/changes/archive/2026-05-31.05-request-immutability-helpers/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-request-immutability-helpers-plan.md rename to planning/changes/archive/2026-05-31.05-request-immutability-helpers/plan.md index 6eb95a3..737e51f 100644 --- a/planning/archive/plans/2026-05-31-request-immutability-helpers-plan.md +++ b/planning/changes/archive/2026-05-31.05-request-immutability-helpers/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: request-immutability-helpers +spec: request-immutability-helpers +pr: 10 +--- + # Request / Response immutability helper expansion Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-msgspec-decoder-via-extras-design.md b/planning/changes/archive/2026-05-31.06-msgspec-decoder-via-extras/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-msgspec-decoder-via-extras-design.md rename to planning/changes/archive/2026-05-31.06-msgspec-decoder-via-extras/design.md index fc233a4..27a42d7 100644 --- a/planning/archive/specs/2026-05-31-msgspec-decoder-via-extras-design.md +++ b/planning/changes/archive/2026-05-31.06-msgspec-decoder-via-extras/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: msgspec-decoder-via-extras +supersedes: null +superseded_by: null +pr: 11 +outcome: 'Shipped in 0.1.0; carry-forward decoder' +--- + # msgspec decoder via extras (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-msgspec-decoder-via-extras-plan.md b/planning/changes/archive/2026-05-31.06-msgspec-decoder-via-extras/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-msgspec-decoder-via-extras-plan.md rename to planning/changes/archive/2026-05-31.06-msgspec-decoder-via-extras/plan.md index b8542a1..df878ef 100644 --- a/planning/archive/plans/2026-05-31-msgspec-decoder-via-extras-plan.md +++ b/planning/changes/archive/2026-05-31.06-msgspec-decoder-via-extras/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: msgspec-decoder-via-extras +spec: msgspec-decoder-via-extras +pr: 11 +--- + # msgspec decoder via extras Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-asyncclient-design.md b/planning/changes/archive/2026-05-31.07-asyncclient/design.md similarity index 99% rename from planning/archive/specs/2026-05-31-asyncclient-design.md rename to planning/changes/archive/2026-05-31.07-asyncclient/design.md index 8942bfd..f1d599a 100644 --- a/planning/archive/specs/2026-05-31-asyncclient-design.md +++ b/planning/changes/archive/2026-05-31.07-asyncclient/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: asyncclient +supersedes: null +superseded_by: 2026-06-03.02-thin-httpx2-wrapper +pr: 12 +outcome: 'Shipped in 0.1.0; rewritten by the v0.2 pivot' +--- + # AsyncClient (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-asyncclient-plan.md b/planning/changes/archive/2026-05-31.07-asyncclient/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-asyncclient-plan.md rename to planning/changes/archive/2026-05-31.07-asyncclient/plan.md index c10d715..130cbde 100644 --- a/planning/archive/plans/2026-05-31-asyncclient-plan.md +++ b/planning/changes/archive/2026-05-31.07-asyncclient/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: asyncclient +spec: asyncclient +pr: 12 +--- + # AsyncClient Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-recordedtransport-design.md b/planning/changes/archive/2026-05-31.08-recordedtransport/design.md similarity index 98% rename from planning/archive/specs/2026-05-31-recordedtransport-design.md rename to planning/changes/archive/2026-05-31.08-recordedtransport/design.md index 22c2cea..9c3e3f3 100644 --- a/planning/archive/specs/2026-05-31-recordedtransport-design.md +++ b/planning/changes/archive/2026-05-31.08-recordedtransport/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: recordedtransport +supersedes: null +superseded_by: 2026-06-03.02-thin-httpx2-wrapper +pr: 13 +outcome: 'Shipped in 0.1.0; removed by the v0.2 pivot' +--- + # RecordedTransport (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-recordedtransport-plan.md b/planning/changes/archive/2026-05-31.08-recordedtransport/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-recordedtransport-plan.md rename to planning/changes/archive/2026-05-31.08-recordedtransport/plan.md index 3c76339..3c1198d 100644 --- a/planning/archive/plans/2026-05-31-recordedtransport-plan.md +++ b/planning/changes/archive/2026-05-31.08-recordedtransport/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: recordedtransport +spec: recordedtransport +pr: 13 +--- + # RecordedTransport Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-05-31-release-0.1.0-prep-design.md b/planning/changes/archive/2026-05-31.09-release-0.1.0-prep/design.md similarity index 99% rename from planning/archive/specs/2026-05-31-release-0.1.0-prep-design.md rename to planning/changes/archive/2026-05-31.09-release-0.1.0-prep/design.md index 68518b1..6a29cb1 100644 --- a/planning/archive/specs/2026-05-31-release-0.1.0-prep-design.md +++ b/planning/changes/archive/2026-05-31.09-release-0.1.0-prep/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: release-0.1.0-prep +supersedes: null +superseded_by: null +pr: 14 +outcome: '0.1.0 released' +--- + # Release 0.1.0 prep (design) - **Date:** 2026-05-31 diff --git a/planning/archive/plans/2026-05-31-release-0.1.0-prep-plan.md b/planning/changes/archive/2026-05-31.09-release-0.1.0-prep/plan.md similarity index 99% rename from planning/archive/plans/2026-05-31-release-0.1.0-prep-plan.md rename to planning/changes/archive/2026-05-31.09-release-0.1.0-prep/plan.md index c64745e..2d6c6fd 100644 --- a/planning/archive/plans/2026-05-31-release-0.1.0-prep-plan.md +++ b/planning/changes/archive/2026-05-31.09-release-0.1.0-prep/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-05-31 +slug: release-0.1.0-prep +spec: release-0.1.0-prep +pr: 14 +--- + # Release 0.1.0 prep Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-01-auth-coercion-design.md b/planning/changes/archive/2026-06-01.01-auth-coercion/design.md similarity index 99% rename from planning/archive/specs/2026-06-01-auth-coercion-design.md rename to planning/changes/archive/2026-06-01.01-auth-coercion/design.md index c7e2dd0..a05d636 100644 --- a/planning/archive/specs/2026-06-01-auth-coercion-design.md +++ b/planning/changes/archive/2026-06-01.01-auth-coercion/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-01 +slug: auth-coercion +supersedes: null +superseded_by: 2026-06-03.02-thin-httpx2-wrapper +pr: 16 +outcome: 'Shipped (Epic 2); removed by the v0.2 pivot' +--- + # Auth coercion (design) - **Date:** 2026-06-01 diff --git a/planning/archive/plans/2026-06-01-auth-coercion-plan.md b/planning/changes/archive/2026-06-01.01-auth-coercion/plan.md similarity index 99% rename from planning/archive/plans/2026-06-01-auth-coercion-plan.md rename to planning/changes/archive/2026-06-01.01-auth-coercion/plan.md index d41b281..433c76c 100644 --- a/planning/archive/plans/2026-06-01-auth-coercion-plan.md +++ b/planning/changes/archive/2026-06-01.01-auth-coercion/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-01 +slug: auth-coercion +spec: auth-coercion +pr: 16 +--- + # Auth coercion as middleware Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-02-docs-reorg-and-mkdocs-design.md b/planning/changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/design.md similarity index 98% rename from planning/archive/specs/2026-06-02-docs-reorg-and-mkdocs-design.md rename to planning/changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/design.md index 3e1689a..5a9925c 100644 --- a/planning/archive/specs/2026-06-02-docs-reorg-and-mkdocs-design.md +++ b/planning/changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-02 +slug: docs-reorg-and-mkdocs +supersedes: null +superseded_by: null +pr: 17 +outcome: 'Docs reorg + mkdocs scaffolding' +--- + # Docs reorg and minimal mkdocs site (design) - **Date:** 2026-06-02 diff --git a/planning/archive/plans/2026-06-02-docs-reorg-and-mkdocs-plan.md b/planning/changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/plan.md similarity index 99% rename from planning/archive/plans/2026-06-02-docs-reorg-and-mkdocs-plan.md rename to planning/changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/plan.md index f430dff..99b5e11 100644 --- a/planning/archive/plans/2026-06-02-docs-reorg-and-mkdocs-plan.md +++ b/planning/changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-02 +slug: docs-reorg-and-mkdocs +spec: docs-reorg-and-mkdocs +pr: 17 +--- + # Docs reorg + minimal mkdocs site implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-02-project-hygiene-tidy-design.md b/planning/changes/archive/2026-06-02.02-project-hygiene-tidy/design.md similarity index 99% rename from planning/archive/specs/2026-06-02-project-hygiene-tidy-design.md rename to planning/changes/archive/2026-06-02.02-project-hygiene-tidy/design.md index bfe5071..7387bc7 100644 --- a/planning/archive/specs/2026-06-02-project-hygiene-tidy-design.md +++ b/planning/changes/archive/2026-06-02.02-project-hygiene-tidy/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-02 +slug: project-hygiene-tidy +supersedes: null +superseded_by: null +pr: 18 +outcome: 'Repo hygiene pass' +--- + # Project hygiene tidy (design) - **Date:** 2026-06-02 diff --git a/planning/archive/plans/2026-06-02-project-hygiene-tidy-plan.md b/planning/changes/archive/2026-06-02.02-project-hygiene-tidy/plan.md similarity index 99% rename from planning/archive/plans/2026-06-02-project-hygiene-tidy-plan.md rename to planning/changes/archive/2026-06-02.02-project-hygiene-tidy/plan.md index 6f40a77..feea447 100644 --- a/planning/archive/plans/2026-06-02-project-hygiene-tidy-plan.md +++ b/planning/changes/archive/2026-06-02.02-project-hygiene-tidy/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-02 +slug: project-hygiene-tidy +spec: project-hygiene-tidy +pr: 18 +--- + # Project hygiene tidy implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-03-input-validation-pass-design.md b/planning/changes/archive/2026-06-03.01-input-validation-pass/design.md similarity index 99% rename from planning/archive/specs/2026-06-03-input-validation-pass-design.md rename to planning/changes/archive/2026-06-03.01-input-validation-pass/design.md index 43d0251..0d7047c 100644 --- a/planning/archive/specs/2026-06-03-input-validation-pass-design.md +++ b/planning/changes/archive/2026-06-03.01-input-validation-pass/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-03 +slug: input-validation-pass +supersedes: null +superseded_by: null +pr: 19 +outcome: 'Input-validation hardening' +--- + # Input-validation pass (design) - **Date:** 2026-06-03 diff --git a/planning/archive/plans/2026-06-03-input-validation-pass-plan.md b/planning/changes/archive/2026-06-03.01-input-validation-pass/plan.md similarity index 99% rename from planning/archive/plans/2026-06-03-input-validation-pass-plan.md rename to planning/changes/archive/2026-06-03.01-input-validation-pass/plan.md index a9069a1..18cd18c 100644 --- a/planning/archive/plans/2026-06-03-input-validation-pass-plan.md +++ b/planning/changes/archive/2026-06-03.01-input-validation-pass/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-03 +slug: input-validation-pass +spec: input-validation-pass +pr: 19 +--- + # Input-validation pass implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-03-thin-httpx2-wrapper-design.md b/planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/design.md similarity index 98% rename from planning/archive/specs/2026-06-03-thin-httpx2-wrapper-design.md rename to planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/design.md index af00938..1b378a8 100644 --- a/planning/archive/specs/2026-06-03-thin-httpx2-wrapper-design.md +++ b/planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-03 +slug: thin-httpx2-wrapper +supersedes: [2026-05-31.05-request-immutability-helpers, 2026-05-31.07-asyncclient, 2026-05-31.08-recordedtransport, 2026-06-01.01-auth-coercion] +superseded_by: null +pr: 20 +outcome: 'Shipped 0.2.0 — the thin-wrapper pivot' +--- + # Design: thin httpx2 wrapper (v0.2 pivot) **Status:** spec — awaiting review diff --git a/planning/archive/plans/2026-06-03-thin-httpx2-wrapper-plan.md b/planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/plan.md similarity index 99% rename from planning/archive/plans/2026-06-03-thin-httpx2-wrapper-plan.md rename to planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/plan.md index 4c65333..5a41af4 100644 --- a/planning/archive/plans/2026-06-03-thin-httpx2-wrapper-plan.md +++ b/planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-03 +slug: thin-httpx2-wrapper +spec: thin-httpx2-wrapper +pr: 20 +--- + # Thin httpx2 wrapper (v0.2 pivot) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-04-pydantic-optional-extra-design.md b/planning/changes/archive/2026-06-04.01-pydantic-optional-extra/design.md similarity index 99% rename from planning/archive/specs/2026-06-04-pydantic-optional-extra-design.md rename to planning/changes/archive/2026-06-04.01-pydantic-optional-extra/design.md index e117385..13c4754 100644 --- a/planning/archive/specs/2026-06-04-pydantic-optional-extra-design.md +++ b/planning/changes/archive/2026-06-04.01-pydantic-optional-extra/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-04 +slug: pydantic-optional-extra +supersedes: null +superseded_by: null +pr: 21 +outcome: 'Shipped 0.3.0 — pydantic moves to an extra' +--- + # Spec: pydantic as an optional extra (0.3.0) **Date:** 2026-06-04 diff --git a/planning/archive/plans/2026-06-04-pydantic-optional-extra-plan.md b/planning/changes/archive/2026-06-04.01-pydantic-optional-extra/plan.md similarity index 99% rename from planning/archive/plans/2026-06-04-pydantic-optional-extra-plan.md rename to planning/changes/archive/2026-06-04.01-pydantic-optional-extra/plan.md index 0cfd6d8..f5d3558 100644 --- a/planning/archive/plans/2026-06-04-pydantic-optional-extra-plan.md +++ b/planning/changes/archive/2026-06-04.01-pydantic-optional-extra/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-04 +slug: pydantic-optional-extra +spec: pydantic-optional-extra +pr: 21 +--- + # Pydantic-as-optional-extra (0.3.0) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-04-v0.2-retro-and-housekeeping-design.md b/planning/changes/archive/2026-06-04.02-v0.2-retro-and-housekeeping/design.md similarity index 98% rename from planning/archive/specs/2026-06-04-v0.2-retro-and-housekeeping-design.md rename to planning/changes/archive/2026-06-04.02-v0.2-retro-and-housekeeping/design.md index b201e73..2fcd5ae 100644 --- a/planning/archive/specs/2026-06-04-v0.2-retro-and-housekeeping-design.md +++ b/planning/changes/archive/2026-06-04.02-v0.2-retro-and-housekeeping/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-04 +slug: v0.2-retro-and-housekeeping +supersedes: null +superseded_by: null +pr: 21 +outcome: 'Post-0.2 retro + housekeeping' +--- + # Spec: v0.2 retrospective and planning/ housekeeping **Date:** 2026-06-04 diff --git a/planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md b/planning/changes/archive/2026-06-05.01-retry-and-retry-budget/design.md similarity index 99% rename from planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md rename to planning/changes/archive/2026-06-05.01-retry-and-retry-budget/design.md index 05fdf9a..9f1ed8e 100644 --- a/planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md +++ b/planning/changes/archive/2026-06-05.01-retry-and-retry-budget/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: retry-and-retry-budget +supersedes: null +superseded_by: null +pr: 22 +outcome: 'Shipped 0.4.0 — Retry + RetryBudget' +--- + # Spec: Retry middleware + RetryBudget (0.4.0, slice A of Epic 3) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-retry-and-retry-budget-plan.md b/planning/changes/archive/2026-06-05.01-retry-and-retry-budget/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-retry-and-retry-budget-plan.md rename to planning/changes/archive/2026-06-05.01-retry-and-retry-budget/plan.md index 120c3f8..c10df78 100644 --- a/planning/archive/plans/2026-06-05-retry-and-retry-budget-plan.md +++ b/planning/changes/archive/2026-06-05.01-retry-and-retry-budget/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: retry-and-retry-budget +spec: retry-and-retry-budget +pr: 22 +--- + # Retry middleware + RetryBudget (0.4.0 slice 1) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-05-bulkhead-design.md b/planning/changes/archive/2026-06-05.02-bulkhead/design.md similarity index 98% rename from planning/archive/specs/2026-06-05-bulkhead-design.md rename to planning/changes/archive/2026-06-05.02-bulkhead/design.md index 2bddff5..d5f360e 100644 --- a/planning/archive/specs/2026-06-05-bulkhead-design.md +++ b/planning/changes/archive/2026-06-05.02-bulkhead/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: bulkhead +supersedes: null +superseded_by: null +pr: 23 +outcome: 'Shipped 0.4.0 — Bulkhead' +--- + # Spec: Bulkhead middleware (0.4.0, Epic 3 slice 2) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-bulkhead-plan.md b/planning/changes/archive/2026-06-05.02-bulkhead/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-bulkhead-plan.md rename to planning/changes/archive/2026-06-05.02-bulkhead/plan.md index ece683a..d49c122 100644 --- a/planning/archive/plans/2026-06-05-bulkhead-plan.md +++ b/planning/changes/archive/2026-06-05.02-bulkhead/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: bulkhead +spec: bulkhead +pr: 23 +--- + # Bulkhead middleware (0.4.0, Epic 3 slice 2) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-05-docs-sync-0.4-design.md b/planning/changes/archive/2026-06-05.03-docs-sync-0.4/design.md similarity index 98% rename from planning/archive/specs/2026-06-05-docs-sync-0.4-design.md rename to planning/changes/archive/2026-06-05.03-docs-sync-0.4/design.md index 5cfacba..584e3e5 100644 --- a/planning/archive/specs/2026-06-05-docs-sync-0.4-design.md +++ b/planning/changes/archive/2026-06-05.03-docs-sync-0.4/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: docs-sync-0.4 +supersedes: null +superseded_by: null +pr: 25 +outcome: '0.4 docs sync' +--- + # Spec: User-docs freshness pass for 0.4 (Epic 3 story 3-6) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-docs-sync-0.4-plan.md b/planning/changes/archive/2026-06-05.03-docs-sync-0.4/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-docs-sync-0.4-plan.md rename to planning/changes/archive/2026-06-05.03-docs-sync-0.4/plan.md index 1a32c96..cbfdff5 100644 --- a/planning/archive/plans/2026-06-05-docs-sync-0.4-plan.md +++ b/planning/changes/archive/2026-06-05.03-docs-sync-0.4/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: docs-sync-0.4 +spec: docs-sync-0.4 +pr: 25 +--- + # Docs-sync 0.4 (Epic 3 story 3-6) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-05-streaming-design.md b/planning/changes/archive/2026-06-05.04-streaming/design.md similarity index 99% rename from planning/archive/specs/2026-06-05-streaming-design.md rename to planning/changes/archive/2026-06-05.04-streaming/design.md index 2325499..2ee6621 100644 --- a/planning/archive/specs/2026-06-05-streaming-design.md +++ b/planning/changes/archive/2026-06-05.04-streaming/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: streaming +supersedes: null +superseded_by: null +pr: 26 +outcome: 'Shipped 0.5.0 — stream()' +--- + # Spec: AsyncClient.stream context manager (0.5.0, Epic 4 story 4-3) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-streaming-plan.md b/planning/changes/archive/2026-06-05.04-streaming/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-streaming-plan.md rename to planning/changes/archive/2026-06-05.04-streaming/plan.md index eb055b6..20d8e58 100644 --- a/planning/archive/plans/2026-06-05-streaming-plan.md +++ b/planning/changes/archive/2026-06-05.04-streaming/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: streaming +spec: streaming +pr: 26 +--- + # AsyncClient.stream + Retry-refuses-streamed-body (0.5.0, Epic 4 story 4-3) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-05-observability-design.md b/planning/changes/archive/2026-06-05.05-observability/design.md similarity index 99% rename from planning/archive/specs/2026-06-05-observability-design.md rename to planning/changes/archive/2026-06-05.05-observability/design.md index 2d6bdd0..7182a23 100644 --- a/planning/archive/specs/2026-06-05-observability-design.md +++ b/planning/changes/archive/2026-06-05.05-observability/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: observability +supersedes: null +superseded_by: null +pr: 27 +outcome: 'Shipped 0.6.0 — logging + OTel events' +--- + # Spec: Resilience observability — structured logging + opt-in OTel attribute enrichment (0.6.0, Epic 5) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-observability-plan.md b/planning/changes/archive/2026-06-05.05-observability/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-observability-plan.md rename to planning/changes/archive/2026-06-05.05-observability/plan.md index ae70348..a923fbb 100644 --- a/planning/archive/plans/2026-06-05-observability-plan.md +++ b/planning/changes/archive/2026-06-05.05-observability/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: observability +spec: observability +pr: 27 +--- + # Resilience observability (0.6.0, Epic 5 re-scoped) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-05-extension-slot-docs-design.md b/planning/changes/archive/2026-06-05.06-extension-slot-docs/design.md similarity index 98% rename from planning/archive/specs/2026-06-05-extension-slot-docs-design.md rename to planning/changes/archive/2026-06-05.06-extension-slot-docs/design.md index 9f607dc..9257d4b 100644 --- a/planning/archive/specs/2026-06-05-extension-slot-docs-design.md +++ b/planning/changes/archive/2026-06-05.06-extension-slot-docs/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: extension-slot-docs +supersedes: null +superseded_by: null +pr: 28 +outcome: 'Shipped 0.7.0 — middleware docs' +--- + # Spec: Extension-slot docs (Epic 3 story 3-6) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-extension-slot-docs-plan.md b/planning/changes/archive/2026-06-05.06-extension-slot-docs/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-extension-slot-docs-plan.md rename to planning/changes/archive/2026-06-05.06-extension-slot-docs/plan.md index 5854561..0dacc32 100644 --- a/planning/archive/plans/2026-06-05-extension-slot-docs-plan.md +++ b/planning/changes/archive/2026-06-05.06-extension-slot-docs/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: extension-slot-docs +spec: extension-slot-docs +pr: 28 +--- + # Extension-slot docs (0.7.0, Epic 3 story 3-6) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/archive/specs/2026-06-05-v0.7-docs-expansion-design.md b/planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/design.md similarity index 99% rename from planning/archive/specs/2026-06-05-v0.7-docs-expansion-design.md rename to planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/design.md index e1cef33..f97c974 100644 --- a/planning/archive/specs/2026-06-05-v0.7-docs-expansion-design.md +++ b/planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-05 +slug: v0.7-docs-expansion +supersedes: null +superseded_by: null +pr: 28 +outcome: 'Shipped 0.7.0 — first-cut user docs' +--- + # Spec: v0.7 docs expansion (Middleware + Resilience + Errors + Testing) **Date:** 2026-06-05 diff --git a/planning/archive/plans/2026-06-05-v0.7-docs-expansion-plan.md b/planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/plan.md similarity index 99% rename from planning/archive/plans/2026-06-05-v0.7-docs-expansion-plan.md rename to planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/plan.md index f46acf1..5e987e9 100644 --- a/planning/archive/plans/2026-06-05-v0.7-docs-expansion-plan.md +++ b/planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-05 +slug: v0.7-docs-expansion +spec: v0.7-docs-expansion +pr: 28 +--- + # v0.7 docs expansion (Resilience + Errors + Testing + OTel wiring) Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-06-modern-di-recipe-design.md b/planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md similarity index 99% rename from planning/specs/2026-06-06-modern-di-recipe-design.md rename to planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md index 94624a9..1fa2414 100644 --- a/planning/specs/2026-06-06-modern-di-recipe-design.md +++ b/planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-06 +slug: modern-di-recipe +supersedes: null +superseded_by: null +pr: 29 +outcome: 'modern-di DI recipe doc' +--- + # Spec: `modern-di` setup-friction recipe **Date:** 2026-06-06 diff --git a/planning/plans/2026-06-06-modern-di-recipe-plan.md b/planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md similarity index 99% rename from planning/plans/2026-06-06-modern-di-recipe-plan.md rename to planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md index 3c692a9..7032619 100644 --- a/planning/plans/2026-06-06-modern-di-recipe-plan.md +++ b/planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-06 +slug: modern-di-recipe +spec: modern-di-recipe +pr: 29 +--- + # `modern-di` recipe + `AsyncClient.aclose()` Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-07-sync-client-design.md b/planning/changes/archive/2026-06-07.01-sync-client/design.md similarity index 99% rename from planning/specs/2026-06-07-sync-client-design.md rename to planning/changes/archive/2026-06-07.01-sync-client/design.md index 212722f..1375941 100644 --- a/planning/specs/2026-06-07-sync-client-design.md +++ b/planning/changes/archive/2026-06-07.01-sync-client/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-07 +slug: sync-client +supersedes: null +superseded_by: null +pr: 31 +outcome: 'Shipped 0.8.0 — sync Client + Async* rename' +--- + # Spec: Sync `Client` + httpx2-aligned `Async*` rename **Date:** 2026-06-07 diff --git a/planning/plans/2026-06-07-sync-client-plan.md b/planning/changes/archive/2026-06-07.01-sync-client/plan.md similarity index 99% rename from planning/plans/2026-06-07-sync-client-plan.md rename to planning/changes/archive/2026-06-07.01-sync-client/plan.md index b6b2dcb..5be27ed 100644 --- a/planning/plans/2026-06-07-sync-client-plan.md +++ b/planning/changes/archive/2026-06-07.01-sync-client/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-07 +slug: sync-client +spec: sync-client +pr: 31 +--- + # Sync `Client` + httpx2-aligned `Async*` rename — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-07-decoder-error-design.md b/planning/changes/archive/2026-06-07.02-decoder-error/design.md similarity index 99% rename from planning/specs/2026-06-07-decoder-error-design.md rename to planning/changes/archive/2026-06-07.02-decoder-error/design.md index 892a1b6..9366869 100644 --- a/planning/specs/2026-06-07-decoder-error-design.md +++ b/planning/changes/archive/2026-06-07.02-decoder-error/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-07 +slug: decoder-error +supersedes: null +superseded_by: null +pr: 32 +outcome: 'Shipped 0.8.1 — DecodeError at seam B' +--- + # Spec: `DecodeError` — close the decoder-exception gap at Seam 3 **Date:** 2026-06-07 diff --git a/planning/plans/2026-06-07-decoder-error-plan.md b/planning/changes/archive/2026-06-07.02-decoder-error/plan.md similarity index 99% rename from planning/plans/2026-06-07-decoder-error-plan.md rename to planning/changes/archive/2026-06-07.02-decoder-error/plan.md index 839ba37..c8aa13a 100644 --- a/planning/plans/2026-06-07-decoder-error-plan.md +++ b/planning/changes/archive/2026-06-07.02-decoder-error/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-07 +slug: decoder-error +spec: decoder-error +pr: 32 +--- + # DecodeError Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-07-deep-audit-design.md b/planning/changes/archive/2026-06-07.03-deep-audit/design.md similarity index 99% rename from planning/specs/2026-06-07-deep-audit-design.md rename to planning/changes/archive/2026-06-07.03-deep-audit/design.md index 3ff62f5..bc4055e 100644 --- a/planning/specs/2026-06-07-deep-audit-design.md +++ b/planning/changes/archive/2026-06-07.03-deep-audit/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-07 +slug: deep-audit +supersedes: null +superseded_by: null +pr: 32 +outcome: 'Deep audit; findings closed across 0.8.1-0.8.6' +--- + # Spec: Deep audit of httpware — code + docs **Date:** 2026-06-07 diff --git a/planning/plans/2026-06-07-deep-audit-plan.md b/planning/changes/archive/2026-06-07.03-deep-audit/plan.md similarity index 99% rename from planning/plans/2026-06-07-deep-audit-plan.md rename to planning/changes/archive/2026-06-07.03-deep-audit/plan.md index 889bb36..fbf5578 100644 --- a/planning/plans/2026-06-07-deep-audit-plan.md +++ b/planning/changes/archive/2026-06-07.03-deep-audit/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-07 +slug: deep-audit +spec: deep-audit +pr: 32 +--- + # Deep Audit Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-send-with-response-design.md b/planning/changes/archive/2026-06-08.01-send-with-response/design.md similarity index 98% rename from planning/specs/2026-06-08-send-with-response-design.md rename to planning/changes/archive/2026-06-08.01-send-with-response/design.md index c6f8e5c..59dc578 100644 --- a/planning/specs/2026-06-08-send-with-response-design.md +++ b/planning/changes/archive/2026-06-08.01-send-with-response/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: send-with-response +supersedes: null +superseded_by: null +pr: 33 +outcome: 'Shipped 0.8.2 — send_with_response' +--- + # Spec: `send_with_response` — atomic (raw response, decoded body) pair **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-send-with-response-plan.md b/planning/changes/archive/2026-06-08.01-send-with-response/plan.md similarity index 99% rename from planning/plans/2026-06-08-send-with-response-plan.md rename to planning/changes/archive/2026-06-08.01-send-with-response/plan.md index 6d5680e..2c27fb0 100644 --- a/planning/plans/2026-06-08-send-with-response-plan.md +++ b/planning/changes/archive/2026-06-08.01-send-with-response/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: send-with-response +spec: send-with-response +pr: 33 +--- + # `send_with_response` Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-retry-budget-cluster-design.md b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md similarity index 99% rename from planning/specs/2026-06-08-retry-budget-cluster-design.md rename to planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md index 52c40d0..773d501 100644 --- a/planning/specs/2026-06-08-retry-budget-cluster-design.md +++ b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: retry-budget-cluster +supersedes: null +superseded_by: null +pr: 34 +outcome: 'Shipped 0.8.3 — 7 RetryBudget findings' +--- + # Spec: Retry/Budget cluster — close 7 audit findings (0.8.3) **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-retry-budget-cluster-plan.md b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md similarity index 99% rename from planning/plans/2026-06-08-retry-budget-cluster-plan.md rename to planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md index d0b594b..02e139d 100644 --- a/planning/plans/2026-06-08-retry-budget-cluster-plan.md +++ b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: retry-budget-cluster +spec: retry-budget-cluster +pr: 34 +--- + # Retry/Budget Cluster Implementation Plan (0.8.3) > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-post-080-doc-sweep-design.md b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md similarity index 99% rename from planning/specs/2026-06-08-post-080-doc-sweep-design.md rename to planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md index 3570213..7b2f850 100644 --- a/planning/specs/2026-06-08-post-080-doc-sweep-design.md +++ b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: post-080-doc-sweep +supersedes: null +superseded_by: null +pr: 34 +outcome: 'Post-0.8.0 doc sweep' +--- + # Spec: Post-0.8.0 doc-staleness sweep **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-post-080-doc-sweep-plan.md b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md similarity index 99% rename from planning/plans/2026-06-08-post-080-doc-sweep-plan.md rename to planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md index 1786948..f3bdda0 100644 --- a/planning/plans/2026-06-08-post-080-doc-sweep-plan.md +++ b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: post-080-doc-sweep +spec: post-080-doc-sweep +pr: 34 +--- + # Post-0.8.0 Doc-Staleness Sweep Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-otel-partial-install-design.md b/planning/changes/archive/2026-06-08.04-otel-partial-install/design.md similarity index 98% rename from planning/specs/2026-06-08-otel-partial-install-design.md rename to planning/changes/archive/2026-06-08.04-otel-partial-install/design.md index 8321067..9202233 100644 --- a/planning/specs/2026-06-08-otel-partial-install-design.md +++ b/planning/changes/archive/2026-06-08.04-otel-partial-install/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: otel-partial-install +supersedes: null +superseded_by: null +pr: 35 +outcome: 'Shipped 0.8.4 — OTel partial-install guards' +--- + # Spec: OTel partial-install hardening (0.8.4) **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-otel-partial-install-plan.md b/planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md similarity index 99% rename from planning/plans/2026-06-08-otel-partial-install-plan.md rename to planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md index f2a3151..872b21f 100644 --- a/planning/plans/2026-06-08-otel-partial-install-plan.md +++ b/planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: otel-partial-install +spec: otel-partial-install +pr: 35 +--- + # OTel Partial-Install Hardening Implementation Plan (0.8.4) > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-small-fixes-mop-up-design.md b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md similarity index 98% rename from planning/specs/2026-06-08-small-fixes-mop-up-design.md rename to planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md index 92f65aa..c54e8bf 100644 --- a/planning/specs/2026-06-08-small-fixes-mop-up-design.md +++ b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: small-fixes-mop-up +supersedes: null +superseded_by: null +pr: 36 +outcome: 'Shipped 0.8.5 — 4 small audit findings' +--- + # Spec: Small-fixes mop-up (0.8.5) **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-small-fixes-mop-up-plan.md b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md similarity index 99% rename from planning/plans/2026-06-08-small-fixes-mop-up-plan.md rename to planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md index b7ecfdd..1f4e56e 100644 --- a/planning/plans/2026-06-08-small-fixes-mop-up-plan.md +++ b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: small-fixes-mop-up +spec: small-fixes-mop-up +pr: 36 +--- + # Small-Fixes Mop-Up Implementation Plan (0.8.5) > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-test-mop-up-design.md b/planning/changes/archive/2026-06-08.06-test-mop-up/design.md similarity index 99% rename from planning/specs/2026-06-08-test-mop-up-design.md rename to planning/changes/archive/2026-06-08.06-test-mop-up/design.md index 659e1e6..2bdc648 100644 --- a/planning/specs/2026-06-08-test-mop-up-design.md +++ b/planning/changes/archive/2026-06-08.06-test-mop-up/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: test-mop-up +supersedes: null +superseded_by: null +pr: 37 +outcome: 'Shipped 0.8.6 — test-only audit findings' +--- + # Spec: Test mop-up (0.8.6) **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-test-mop-up-plan.md b/planning/changes/archive/2026-06-08.06-test-mop-up/plan.md similarity index 99% rename from planning/plans/2026-06-08-test-mop-up-plan.md rename to planning/changes/archive/2026-06-08.06-test-mop-up/plan.md index 141cc8f..e61fa47 100644 --- a/planning/plans/2026-06-08-test-mop-up-plan.md +++ b/planning/changes/archive/2026-06-08.06-test-mop-up/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: test-mop-up +spec: test-mop-up +pr: 37 +--- + # Test Mop-Up Implementation Plan (0.8.6) > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-mkdocs-gh-pages-migration-design.md b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md similarity index 97% rename from planning/specs/2026-06-08-mkdocs-gh-pages-migration-design.md rename to planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md index d82f44b..3f8b51a 100644 --- a/planning/specs/2026-06-08-mkdocs-gh-pages-migration-design.md +++ b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: mkdocs-gh-pages-migration +supersedes: null +superseded_by: null +pr: 38 +outcome: 'Docs host -> GitHub Pages' +--- + # Spec: Migrate docs hosting from ReadTheDocs to GitHub Pages **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-mkdocs-gh-pages-migration-plan.md b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md similarity index 99% rename from planning/plans/2026-06-08-mkdocs-gh-pages-migration-plan.md rename to planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md index be92571..fc9f7aa 100644 --- a/planning/plans/2026-06-08-mkdocs-gh-pages-migration-plan.md +++ b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: mkdocs-gh-pages-migration +spec: mkdocs-gh-pages-migration +pr: 38 +--- + # mkdocs GitHub Pages Migration Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-08-readme-link-cleanup-design.md b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md similarity index 97% rename from planning/specs/2026-06-08-readme-link-cleanup-design.md rename to planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md index 3c8dfa3..b1a06d3 100644 --- a/planning/specs/2026-06-08-readme-link-cleanup-design.md +++ b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: readme-link-cleanup +supersedes: null +superseded_by: null +pr: 39 +outcome: 'README link cleanup' +--- + # Spec: README + top-level link cleanup, plus one-shot link audit **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-readme-link-cleanup-plan.md b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md similarity index 99% rename from planning/plans/2026-06-08-readme-link-cleanup-plan.md rename to planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md index 8c8e371..c49fa25 100644 --- a/planning/plans/2026-06-08-readme-link-cleanup-plan.md +++ b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-08 +slug: readme-link-cleanup +spec: readme-link-cleanup +pr: 39 +--- + # README + top-level link cleanup Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-09-multi-decoder-design.md b/planning/changes/archive/2026-06-10.01-multi-decoder/design.md similarity index 99% rename from planning/specs/2026-06-09-multi-decoder-design.md rename to planning/changes/archive/2026-06-10.01-multi-decoder/design.md index 66f30d9..8c1967f 100644 --- a/planning/specs/2026-06-09-multi-decoder-design.md +++ b/planning/changes/archive/2026-06-10.01-multi-decoder/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-10 +slug: multi-decoder +supersedes: null +superseded_by: null +pr: 41 +outcome: 'Shipped 0.9.0 — multi-decoder routing' +--- + # Spec: multi-decoder routing — `decoders=[...]` with type-dispatched claim policy **Date:** 2026-06-09 diff --git a/planning/plans/2026-06-09-multi-decoder-plan.md b/planning/changes/archive/2026-06-10.01-multi-decoder/plan.md similarity index 99% rename from planning/plans/2026-06-09-multi-decoder-plan.md rename to planning/changes/archive/2026-06-10.01-multi-decoder/plan.md index aa81c26..bfa206c 100644 --- a/planning/plans/2026-06-09-multi-decoder-plan.md +++ b/planning/changes/archive/2026-06-10.01-multi-decoder/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-10 +slug: multi-decoder +spec: multi-decoder +pr: 41 +--- + # Multi-Decoder Routing Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-10-decoder-instance-cache-design.md b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md similarity index 98% rename from planning/specs/2026-06-10-decoder-instance-cache-design.md rename to planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md index 94b234b..c2ee541 100644 --- a/planning/specs/2026-06-10-decoder-instance-cache-design.md +++ b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-10 +slug: decoder-instance-cache +supersedes: null +superseded_by: null +pr: 42 +outcome: 'Shipped 0.9.0 — per-instance decoder cache' +--- + # Spec: decoder per-instance cache — drop module-level `@lru_cache` **Date:** 2026-06-10 diff --git a/planning/plans/2026-06-10-decoder-instance-cache-plan.md b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md similarity index 99% rename from planning/plans/2026-06-10-decoder-instance-cache-plan.md rename to planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md index 15fc657..08567e4 100644 --- a/planning/plans/2026-06-10-decoder-instance-cache-plan.md +++ b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-10 +slug: decoder-instance-cache +spec: decoder-instance-cache +pr: 42 +--- + # Per-Instance Decoder Cache Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-12-delta-audit-design.md b/planning/changes/archive/2026-06-12.01-delta-audit/design.md similarity index 98% rename from planning/specs/2026-06-12-delta-audit-design.md rename to planning/changes/archive/2026-06-12.01-delta-audit/design.md index d0a85c3..dc62c0c 100644 --- a/planning/specs/2026-06-12-delta-audit-design.md +++ b/planning/changes/archive/2026-06-12.01-delta-audit/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-12 +slug: delta-audit +supersedes: null +superseded_by: null +pr: 43 +outcome: '0.9.0 delta audit; closed via 0.9.1' +--- + # Spec: Delta audit of the 0.9.0 multi-decoder epic — code + docs **Date:** 2026-06-12 diff --git a/planning/plans/2026-06-12-delta-audit-plan.md b/planning/changes/archive/2026-06-12.01-delta-audit/plan.md similarity index 99% rename from planning/plans/2026-06-12-delta-audit-plan.md rename to planning/changes/archive/2026-06-12.01-delta-audit/plan.md index 3b984cd..69659fb 100644 --- a/planning/plans/2026-06-12-delta-audit-plan.md +++ b/planning/changes/archive/2026-06-12.01-delta-audit/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-12 +slug: delta-audit +spec: delta-audit +pr: 43 +--- + # 0.9.0 Delta Audit Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Task 2 must run inline in the main session** — the Workflow tool is a main-session orchestration tool and is not available to dispatched subagents. diff --git a/planning/specs/2026-06-13-msgspec-nested-customtype-fix-design.md b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md similarity index 98% rename from planning/specs/2026-06-13-msgspec-nested-customtype-fix-design.md rename to planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md index a570a3e..5e02c17 100644 --- a/planning/specs/2026-06-13-msgspec-nested-customtype-fix-design.md +++ b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-13 +slug: msgspec-nested-customtype-fix +supersedes: null +superseded_by: null +pr: 43 +outcome: 'Shipped 0.9.1 — nested-CustomType guard' +--- + # Spec: Fix `MsgspecDecoder.can_decode` false-positive on nested CustomType **Date:** 2026-06-13 diff --git a/planning/plans/2026-06-13-msgspec-nested-customtype-fix-plan.md b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md similarity index 99% rename from planning/plans/2026-06-13-msgspec-nested-customtype-fix-plan.md rename to planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md index ee049c4..727ce00 100644 --- a/planning/plans/2026-06-13-msgspec-nested-customtype-fix-plan.md +++ b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-13 +slug: msgspec-nested-customtype-fix +spec: msgspec-nested-customtype-fix +pr: 43 +--- + # MsgspecDecoder Nested-CustomType Fix Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/specs/2026-06-13-circuit-breaker-and-timeout-design.md b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md similarity index 99% rename from planning/specs/2026-06-13-circuit-breaker-and-timeout-design.md rename to planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md index 16aef0f..6b80516 100644 --- a/planning/specs/2026-06-13-circuit-breaker-and-timeout-design.md +++ b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-13 +slug: circuit-breaker-and-timeout +supersedes: null +superseded_by: null +pr: 51 +outcome: 'Shipped 0.10.0 — CircuitBreaker + AsyncTimeout' +--- + # Spec: CircuitBreaker + AsyncTimeout — completing the resilience suite **Date:** 2026-06-13 diff --git a/planning/plans/2026-06-13-circuit-breaker-and-timeout.md b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md similarity index 99% rename from planning/plans/2026-06-13-circuit-breaker-and-timeout.md rename to planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md index 2ce7b06..4e14bbb 100644 --- a/planning/plans/2026-06-13-circuit-breaker-and-timeout.md +++ b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md @@ -1,3 +1,11 @@ +--- +status: shipped +date: 2026-06-13 +slug: circuit-breaker-and-timeout +spec: circuit-breaker-and-timeout +pr: 51 +--- + # CircuitBreaker + AsyncTimeout Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. diff --git a/planning/deferred-work.md b/planning/deferred.md similarity index 93% rename from planning/deferred-work.md rename to planning/deferred.md index 87fbcf8..a54caad 100644 --- a/planning/deferred-work.md +++ b/planning/deferred.md @@ -2,7 +2,7 @@ Items raised in reviews that are real but not actionable now. -As of 0.7.0, all planned epics (3, 4, 5, 6) are closed — see [`engineering.md`](engineering.md) §8. The Open section below is the long-tail register: items that remain technically real but depend on speculative future work, so they're parked here pending a concrete trigger. +As of 0.7.0, all planned epics (3, 4, 5, 6) are closed — see the [change Index](README.md). The Open section below is the long-tail register: items that remain technically real but depend on speculative future work, so they're parked here pending a concrete trigger. ## Open diff --git a/planning/engineering.md b/planning/engineering.md deleted file mode 100644 index 37a6680..0000000 --- a/planning/engineering.md +++ /dev/null @@ -1,159 +0,0 @@ -# `httpware` engineering notes - -This doc is the single distilled reference for `httpware` design rationale, protocol seams, and remaining roadmap. It complements `CLAUDE.md` (at the repo root): `CLAUDE.md` holds AI-enforced invariants and operational commands; this file holds the reasoning and the structural map. - -## 1. Project intent - -`httpware` is a thin opinionated wrapper around `httpx2`. It re-exports `httpx2.Request` and `httpx2.Response` as the public request/response surface and adds three things on top: typed response decoding (via a `ResponseDecoder` protocol; pydantic and msgspec are both opt-in extras as of 0.3.0), a middleware chain composed at client construction, and a status-keyed exception tree raised automatically on 4xx and 5xx. As of 0.9.0, both clients take `decoders: Sequence[ResponseDecoder] | None = None` (a *list*, not a single instance) and dispatch via each decoder's `can_decode(model)` predicate; the default resolves against installed extras (pydantic-first when both present) and `AsyncClient()` / `Client()` no longer raise on missing extras. A new `MissingDecoderError` (sibling of `DecodeError` under `ClientError`) fires before the HTTP call when `response_model=` is set but no registered decoder claims the model. As of 0.4.0, the package ships a small resilience suite under `httpware.middleware.resilience` — a `Retry` middleware with a Finagle-style `RetryBudget`, plus a `Bulkhead` concurrency limiter — composed via the standard middleware chain. As of 0.5.0, `AsyncClient.stream()` provides a context-manager API for chunked response bodies; it bypasses the middleware chain by design (see planning/archive/specs/2026-06-05-streaming-design.md). As of 0.6.0, `Retry` and `Bulkhead` emit operational events via stdlib `logging` records (`httpware.retry` / `httpware.bulkhead` loggers) and — when `opentelemetry-api` is installed — OpenTelemetry span events on the active span. As of 0.7.0, the first-cut user-docs surface is live at (Middleware, Resilience, Errors, Testing guides) and Epic 3 is closed. - -As of 0.8.0 the async middleware surface uses the `Async*`/`async_*` prefix (aligning with httpx2's convention); the `attempt_timeout=` kwarg was removed from `AsyncRetry` in the same release — see `planning/specs/2026-06-07-sync-client-design.md` for the rationale. - -0.8.0 also shipped a sync `Client` with full feature parity (typed decoding, middleware chain, `Retry`/`Bulkhead`, `stream()`). `RetryBudget` is now thread-safe (one class, both worlds). Sync `Bulkhead` uses `threading.Semaphore` and cannot share an instance with `AsyncBulkhead`. See `planning/specs/2026-06-07-sync-client-design.md`. - -The 0.1.0 release attempted to own a full abstraction over the underlying HTTP client. v0.2 walks that back: `httpx2` is part of the public surface. - -## 2. Architectural invariants (CI-enforced) - -These are non-negotiable. CI rejects PRs that violate them. The "why" exists so future contributors can judge edge cases instead of blindly following the rule. - -- **No `httpx2._` private API.** *Why:* private symbols can change between patch releases. We accept the public-API surface as the contract. -- **No `from __future__ import annotations`.** *Why:* Python 3.11+ floor. PEP 604/585 syntax is native; the future-import would only add noise and inconsistency. -- **No `print()`.** *Why:* ruff-enforced. Libraries log; they do not print to stdout. Stray prints leak into consumer applications. -- **No global logging config.** *Why:* `logging.basicConfig()` from a library mutates the consumer's logging tree. We only acquire `logging.getLogger("httpware")` or namespaced child loggers and let consumers configure handlers. -- **Type suppressions use `# ty: ignore[]`.** *Why:* this project uses `ty`, not `mypy`. `# type: ignore` is silently accepted by `ty` but ambiguous; `# ty: ignore[]` is checked and rule-specific. - -The 0.1.0 "no `httpx2` leakage outside `transports/httpx2.py`" invariant is **retired in v0.2**. Exposing `httpx2.Request`/`httpx2.Response` is the design. - -## 3. The three protocol seams - -A protocol seam is a documented internal boundary. AI agents and contributors must respect it — never cross a seam except through its protocol. - -The 0.1.0 seams numbered 1 (Middleware↔Transport) and 4 (Transport↔httpx2) have collapsed into the `AsyncClient` terminal — there is no transport abstraction in v0.2. - -### Seam A: `Client`/`AsyncClient` ↔ `Middleware`/`AsyncMiddleware` - -- **Where:** `src/httpware/client.py` ↔ `src/httpware/middleware/`. -- **Contract:** the middleware chain is composed once at client construction and frozen for the client's lifetime. Both worlds follow the same contract; the only difference is the per-world type: `AsyncClient` composes `AsyncMiddleware` via `compose_async` (the continuation type is `AsyncNext`), and `Client` composes `Middleware` via `compose` (the continuation type is `Next`). Both `compose` and `compose_async` live in `src/httpware/middleware/chain.py`. The chain bottom (the "terminal") is internal: it calls `self._httpx2_client.send(request)`, maps `httpx2` errors to `httpware` errors, and raises a `StatusError` subclass on 4xx/5xx. Same lifecycle rules in both worlds. -- **Rule:** mutating the chain after construction is not supported. Per-request behavior goes through `httpx2.Request.extensions` or through `extensions=` kwargs at call sites. - -### Seam B: `Client`/`AsyncClient` ↔ `ResponseDecoder` list - -- **Where:** `src/httpware/client.py` ↔ `src/httpware/decoders/`. -- **Contract:** the client holds `_decoders: tuple[ResponseDecoder, ...]` composed at `__init__` and frozen for the client's lifetime. The Protocol exposes two methods: - - `can_decode(model: type) -> bool` — predicate used at send-time to walk `_decoders` and pick the first claiming decoder (`_dispatch_decoder` on both classes). Built-in decoders claim broadly (pydantic via `TypeAdapter(model)` probe, msgspec via `msgspec.inspect.type_info(model)` + `CustomType` filter); list ordering decides ambiguous shared shapes (dataclass, primitive, generic). Native types of another library MUST be rejected. `can_decode` MUST NOT raise — it runs in `_dispatch_decoder`, outside the `DecodeError` try/except, so a raising probe escapes the `ClientError` contract; a decoder that cannot decide must return False, not raise (the built-ins treat any probe failure as False). This is a documented obligation on implementers, not an enforced guard. - - `decode(content: bytes, model: type[T]) -> T` — the decode itself. Any exception is wrapped by `Client.send` / `AsyncClient.send` (when `response_model=` is set) and `Client.send_with_response` / `AsyncClient.send_with_response` into `httpware.DecodeError` (a `ClientError` subclass carrying `response`, `model`, `original`). Decoder implementers do not need to raise `DecodeError` directly. -- **Pre-flight check:** when `response_model=` is set and no decoder claims it, `send` / `send_with_response` raise `MissingDecoderError(model=..., registered_names=...)` BEFORE the HTTP call. Distinct from `DecodeError` (which means the decoder ran and the payload was malformed); distinct corrective actions (install an extra or pass `decoders=[...]`). -- **Default list:** `decoders=None` resolves via `client.py:_build_default_decoders()` against installed extras — pydantic-first when both are present, either-only when only one is installed, empty tuple when neither. `AsyncClient()` / `Client()` never raise on missing extras; failure surfaces only at the first `response_model=` use site. -- **Rule:** the decoder must operate on raw bytes in a single parse pass. Two-pass decoding (`json.loads` then `validate_python`) is rejected: a single bytes-in / typed-object-out pass avoids the redundant intermediate `dict` allocation and parses faster. The Pydantic adapter implements this as `TypeAdapter(model).validate_json(content)`, with the `TypeAdapter` cached per-instance on `PydanticDecoder._adapters: dict[type, TypeAdapter]` (populated lazily on first `_get_adapter()` call); the msgspec adapter mirrors the pattern with `MsgspecDecoder._msgspec_decoders: dict[type, msgspec.json.Decoder]`. Cache lifetime matches the decoder/client, not the process — no module-level state, no autouse cache-clear fixtures in tests. - -### Seam C: `httpware ↔ optional extras` - -- **Where:** `pyproject.toml` extras (`[project.optional-dependencies]`) ↔ the adapter modules that import them. -- **Contract:** each optional dependency is imported only inside its own dedicated module (e.g., `pydantic` in `decoders/pydantic.py`; `msgspec` in `decoders/msgspec.py`). Future extras (e.g., OpenTelemetry under Epic 5) follow the same pattern, with the extra declared in `pyproject.toml` at the same time the code that uses it lands — not earlier. -- **Rule:** never import an extra at package top-level. The package must import cleanly when the extra is not installed. -- **Verification:** `tests/test_optional_extras_isolation.py` runs a fresh-subprocess `import httpware` and asserts that neither pydantic nor msgspec ends up in `sys.modules`. New extras must add the same isolation test. - -## 4. Exception contract - -`StatusError` and all its 4xx/5xx subclasses are constructed with a **single positional `response: httpx2.Response`**. Subclasses do not override `__init__`. All fields are available via `exc.response.*` (status code, headers, content, request, etc.). - -```python -raise NotFoundError(response) # correct -exc.response.status_code # 404 -exc.response.request.url # URL of the failed request -``` - -`__repr__` and the `str()` summary strip `user:pass@` userinfo from `response.request.url` to avoid leaking credentials in tracebacks. Query-string secrets are not stripped here. - -The error-mapping table (what `httpx2` exception maps to which `httpware` exception) lives at the `AsyncClient` terminal in `src/httpware/client.py`. Status-keyed exceptions are looked up via the `STATUS_TO_EXCEPTION` table in `src/httpware/errors.py`. Unknown 4xx falls back to `ClientStatusError`; unknown 5xx falls back to `ServerStatusError`. - -`TimeoutError` inherits from both `httpware.ClientError` and `builtins.TimeoutError` so `except builtins.TimeoutError` (the form `asyncio.wait_for` uses) also catches httpware-raised timeouts. - -`DecodeError` covers the case where `response_model=` is set, the HTTP call itself succeeded, but the active `ResponseDecoder` raised. The wrap happens at the seam in `Client.send` / `AsyncClient.send` — `except Exception` translates any decoder-side failure into `DecodeError(response=..., model=..., original=...)` with `raise ... from exc` chaining. The `original` attribute exposes the underlying library exception (e.g., `pydantic.ValidationError`, `msgspec.ValidationError`); `__cause__` carries the same reference. - -## 5. Module layout - -Current tree: - -```text -src/httpware/ -├── __init__.py # public exports (both worlds at top level) -├── py.typed -├── client.py # Client (sync) + AsyncClient (async) -├── errors.py # status-keyed exception tree (shared) -├── middleware/ -│ ├── __init__.py # Middleware + AsyncMiddleware, Next + AsyncNext, decorators -│ ├── chain.py # compose + compose_async -│ └── resilience/ -│ ├── __init__.py # re-exports both worlds + RetryBudget -│ ├── bulkhead.py # Bulkhead + AsyncBulkhead -│ ├── budget.py # RetryBudget (thread-safe; shared) -│ ├── retry.py # Retry + AsyncRetry -│ └── _backoff.py # full-jitter helper (shared) -├── decoders/ # shared (ResponseDecoder + adapters) -└── _internal/ - ├── exception_mapping.py # map_httpx2_exception (shared) - ├── import_checker.py # is_*_installed flags - ├── observability.py # _emit_event - └── status.py # _raise_on_status_error, _is_streaming_body_*, STREAMING_BODY_MARKER -``` - -**Deleted relative to 0.1.0:** `request.py`, `response.py`, `config.py`, `transports/` (Transport protocol + Httpx2Transport), `_internal/auth.py`, `_internal/chain.py`. The `RecordedTransport` testing helper is gone; tests inject `httpx2.MockTransport` via `httpx2_client=` instead. - -## 6. Testing patterns - -- **`pytest-asyncio` auto mode.** Async test functions do not require `@pytest.mark.asyncio`. The setting lives in `pyproject.toml` under `[tool.pytest.ini_options]`. -- **`httpx2.MockTransport` for transport mocking, not `respx`.** Tests construct `httpx2.AsyncClient(transport=httpx2.MockTransport(handler))` and pass it as `httpx2_client=` to `AsyncClient`. `MockTransport` is the public test seam in `httpx`; `respx` patches private internals and breaks across `httpx` major versions. (The 0.1-era `RecordedTransport` testing helper was removed in the v0.2 pivot.) See [`docs/testing.md`](../docs/testing.md) for the user-facing version of this pattern. -- **Hypothesis property-based tests** for concurrency-sensitive code: `RetryBudget`, `Bulkhead`, retry interleaving. Files are named `test_*_props.py` so they are easy to grep and treat separately in CI. -- **Performance tests are opt-in.** The `perf` pytest marker is registered in `pyproject.toml`; the default `addopts` line includes `-m 'not perf'`. Run benchmarks explicitly with `pytest -m perf`. -- **Coverage is 100% line coverage.** The five merged stories ship at 100% line coverage. New code is expected to maintain this. - -## 7. Optional-extras pattern - -`httpware` core has a small dependency set. Capabilities that pull in heavyweight dependencies (`pydantic`, `msgspec`) live behind extras declared in `pyproject.toml`: - -```toml -[project.optional-dependencies] -pydantic = ["pydantic>=2"] -msgspec = ["msgspec>=0.18"] -``` - -Each extra's code lives in a single dedicated module (`decoders/pydantic.py`, `decoders/msgspec.py`). The `import` of the extra happens **inside** that module behind an `is__installed` guard from `_internal/import_checker.py` — never at package top level. This way, `import httpware` works cleanly without the extras installed, and the seam stays observable: `grep -rnE 'from pydantic|import pydantic' src/httpware/ | grep -v import_checker` returns exactly one indented line (the guarded import in `decoders/pydantic.py`), and the same is true for `msgspec`. - -New extras are added at the same time as the code that uses them — never preemptively. (An `otel` extra existed pre-0.4 but was removed once we noticed it was advertising functionality that didn't exist. 0.6.0 reintroduces it paired with the code that uses it — `Retry` and `Bulkhead` add events to the active OpenTelemetry span via `trace.get_current_span().add_event(...)`.) - -Caller-facing pattern: as of 0.9.0, `AsyncClient()` / `Client()` default `decoders=None` resolves via `_build_default_decoders()` against installed extras (pydantic-first when both are present; empty tuple when neither). Consumers override by passing `decoders=[...]` explicitly; `decoders=[]` is honored as an opt-out. The auto-resolution is a snapshot of `import_checker.is__installed` flags at `__init__` time — there is no runtime re-detection or implicit registry beyond the two built-in decoders. - -## 8. Remaining roadmap - -Post-pivot, the roadmap has three categories. Topic slugs in `planning/specs/` and `planning/plans/` use kebab-case descriptions. - -### Deleted by the v0.2 pivot - -`1-8` `RecordedTransport`, `2-3` Request immutability helpers, `2-4` auth coercion middleware, `4-1` `StreamResponse` type, `4-2` transport stream implementation, `5-3` `Redactor` middleware. - -### Rewritten by the v0.2 pivot - -`1-7` `AsyncClient` (the heart of v0.2 — shipped in the pivot PR), `2-5` wire middleware into `AsyncClient` (trivially part of `1-7`), `6-1` migration guide (extended with httpware 0.1→0.2 notes), `6-4` CI invariant gates (drop the "no `httpx2` leakage" rule). - -### Shipped (all epics closed) - -- **Epic 3 — Resilience: SHIPPED.** - - **v0.4 slice 1:** `Retry` middleware + Finagle-style `RetryBudget` token bucket + `attempt_timeout=` parameter (folded-in 3-1; `attempt_timeout=` was removed in 0.8.0 — see v0.8.0 entry below). See [`planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md`](archive/specs/2026-06-05-retry-and-retry-budget-design.md) and [`planning/archive/plans/2026-06-05-retry-and-retry-budget-plan.md`](archive/plans/2026-06-05-retry-and-retry-budget-plan.md). - - **v0.4 slice 2:** `Bulkhead` middleware (concurrency limiter via `asyncio.Semaphore` with bounded acquire wait). See [`planning/archive/specs/2026-06-05-bulkhead-design.md`](archive/specs/2026-06-05-bulkhead-design.md) and [`planning/archive/plans/2026-06-05-bulkhead-plan.md`](archive/plans/2026-06-05-bulkhead-plan.md). - - **v0.7:** `3-6` extension-slot docs — [`docs/middleware.md`](../docs/middleware.md). Covers the Middleware Protocol, phase decorators, a Request-ID worked example, and "when NOT to write a middleware." See [`planning/archive/specs/2026-06-05-extension-slot-docs-design.md`](archive/specs/2026-06-05-extension-slot-docs-design.md) and [`planning/archive/plans/2026-06-05-extension-slot-docs-plan.md`](archive/plans/2026-06-05-extension-slot-docs-plan.md). - - **v0.7 also bundles** the rest of the first-cut user-docs surface — [`docs/resilience.md`](../docs/resilience.md) (Retry/RetryBudget/Bulkhead reference), [`docs/errors.md`](../docs/errors.md) (exception tree + catching strategies), [`docs/testing.md`](../docs/testing.md) (mock-transport injection pattern) — plus an "OpenTelemetry wiring" section appended to `docs/middleware.md`. See [`planning/archive/specs/2026-06-05-v0.7-docs-expansion-design.md`](archive/specs/2026-06-05-v0.7-docs-expansion-design.md) and [`planning/archive/plans/2026-06-05-v0.7-docs-expansion-plan.md`](archive/plans/2026-06-05-v0.7-docs-expansion-plan.md). - - **v0.8.0:** sync `Client` with full feature parity (middleware chain, decoder seam, `Retry`, `Bulkhead`, `stream()`); async surface renamed to `Async*`/`async_*` prefix; `attempt_timeout=` removed from `AsyncRetry`. Breaking release for every public async middleware import. -- **Epic 4 — Streaming: SHIPPED in v0.5.** `AsyncClient.stream()` context manager + Retry refuses streamed-body requests. See [`planning/archive/specs/2026-06-05-streaming-design.md`](archive/specs/2026-06-05-streaming-design.md) and [`planning/archive/plans/2026-06-05-streaming-plan.md`](archive/plans/2026-06-05-streaming-plan.md). -- **Epic 5 — Observability: SHIPPED in v0.6** — re-scoped from the original 4-story plan. `Retry` and `Bulkhead` emit operational events via stdlib `logging` + opt-in OpenTelemetry span events. Stories `5-1` (Layer 1 middleware hooks) and `5-4` (standalone OTel middleware) RETIRED — `opentelemetry-instrumentation-httpx` already covers transport-level tracing; a separate httpware middleware would duplicate it. See [`planning/archive/specs/2026-06-05-observability-design.md`](archive/specs/2026-06-05-observability-design.md) and [`planning/archive/plans/2026-06-05-observability-plan.md`](archive/plans/2026-06-05-observability-plan.md). -- **Epic 6 — Ship v1.0: SHIPPED.** `6-2` docs site live at (mkdocs-material, hand-written content only, auto-publishing from `main`). Stories `6-3` (benchmarks) and `6-5` (Trusted Publishers + Sigstore release flow) RETIRED — neither carries enough value to maintain. Tag-driven release via the existing `publish.yml` workflow stays as-is. -- **Carry-forward decoder:** `1-6` msgspec decoder via extras — second `ResponseDecoder` adapter, already implemented; verified surviving in the pivot. -- **Middleware protocol:** `2-1` and `2-2` already implemented in the pivot (protocol, chain, phase decorators). - -All planned epics are closed as of v0.8.0. The next semver bump is a judgment call (cut v1.0 from the current main; or keep iterating at 0.x and let the API settle further before promising stability). - -When work starts on a roadmap item, it gets a spec at `planning/specs/YYYY-MM-DD--design.md` and a plan at `planning/plans/YYYY-MM-DD--plan.md`. - -## 9. Deferred work - -Review-surfaced items that are real but not actionable now live in `planning/deferred-work.md` at the repository root. Each entry cites the originating story and the file/line, and explains why the fix is deferred (cross-story dependency, scope, performance/security tradeoff, etc.). When a deferred item becomes actionable, it migrates into the spec for the story that resolves it. diff --git a/planning/releases/0.10.1.md b/planning/releases/0.10.1.md index 422f38f..2d380d2 100644 --- a/planning/releases/0.10.1.md +++ b/planning/releases/0.10.1.md @@ -2,7 +2,7 @@ **Patch release. Bug fixes + hardening from the 0.10.0 delta audit. No breaking changes** (one additive observability field; see below). -Audit report: [`planning/audit/2026-06-13-delta-audit.md`](../audit/2026-06-13-delta-audit.md). +Audit report: [`planning/audits/2026-06-13-delta-audit.md`](../audits/2026-06-13-delta-audit.md). ## Fixes diff --git a/planning/releases/0.2.0.md b/planning/releases/0.2.0.md index 231a24d..19bdbc5 100644 --- a/planning/releases/0.2.0.md +++ b/planning/releases/0.2.0.md @@ -42,4 +42,4 @@ async with httpware.AsyncClient( ## What's next -Epic 3 (resilience middleware — retry, timeout, bulkhead) and Epic 5 (observability) ship in subsequent minor releases. See `planning/engineering.md` section 8 for the post-pivot roadmap. +Epic 3 (resilience middleware — retry, timeout, bulkhead) and Epic 5 (observability) ship in subsequent minor releases. See `architecture/overview.md` for the post-pivot roadmap. diff --git a/planning/releases/0.3.0.md b/planning/releases/0.3.0.md index 96b2c97..249f30d 100644 --- a/planning/releases/0.3.0.md +++ b/planning/releases/0.3.0.md @@ -40,4 +40,4 @@ async with AsyncClient(decoder=PydanticDecoder()) as client: ## What's next -Epic 3 (resilience middleware — retry, timeout, bulkhead) and Epic 5 (observability) ship in subsequent releases. See `planning/engineering.md` §8. +Epic 3 (resilience middleware — retry, timeout, bulkhead) and Epic 5 (observability) ship in subsequent releases. See `architecture/overview.md`. diff --git a/planning/releases/0.4.0.md b/planning/releases/0.4.0.md index 4d34908..23d2658 100644 --- a/planning/releases/0.4.0.md +++ b/planning/releases/0.4.0.md @@ -144,8 +144,6 @@ Out of scope for this release (per the specs, may revisit on real-user pain): pe ## References -- Retry spec: [`planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md`](../archive/specs/2026-06-05-retry-and-retry-budget-design.md) -- Retry plan: [`planning/archive/plans/2026-06-05-retry-and-retry-budget-plan.md`](../archive/plans/2026-06-05-retry-and-retry-budget-plan.md) -- Bulkhead spec: [`planning/archive/specs/2026-06-05-bulkhead-design.md`](../archive/specs/2026-06-05-bulkhead-design.md) -- Bulkhead plan: [`planning/archive/plans/2026-06-05-bulkhead-plan.md`](../archive/plans/2026-06-05-bulkhead-plan.md) -- Roadmap: [`planning/engineering.md`](../engineering.md) §8 +- Retry bundle: [`planning/changes/archive/2026-06-05.01-retry-and-retry-budget/`](../changes/archive/2026-06-05.01-retry-and-retry-budget/) — design + implementation plan. +- Bulkhead bundle: [`planning/changes/archive/2026-06-05.02-bulkhead/`](../changes/archive/2026-06-05.02-bulkhead/) — design + implementation plan. +- Architecture notes: [`architecture/resilience.md`](../../architecture/resilience.md) diff --git a/planning/releases/0.5.0.md b/planning/releases/0.5.0.md index c44f294..654a79b 100644 --- a/planning/releases/0.5.0.md +++ b/planning/releases/0.5.0.md @@ -49,6 +49,5 @@ except NotFoundError as exc: ## References -- Spec: [`planning/archive/specs/2026-06-05-streaming-design.md`](../archive/specs/2026-06-05-streaming-design.md) -- Plan: [`planning/archive/plans/2026-06-05-streaming-plan.md`](../archive/plans/2026-06-05-streaming-plan.md) -- Roadmap: [`planning/engineering.md`](../engineering.md) §8 +- Bundle: [`planning/changes/archive/2026-06-05.04-streaming/`](../changes/archive/2026-06-05.04-streaming/) — design + implementation plan. +- Architecture notes: [`architecture/overview.md`](../../architecture/overview.md) diff --git a/planning/releases/0.6.0.md b/planning/releases/0.6.0.md index 070dab5..e99404c 100644 --- a/planning/releases/0.6.0.md +++ b/planning/releases/0.6.0.md @@ -18,7 +18,7 @@ This release adds operational-event emission to `Retry` and `Bulkhead` via two c Purely additive: - All previously-shipping methods behave identically. - Successful retries and successful bulkhead acquisitions emit nothing — the four events fire only on operational concern. -- Per `engineering.md §2`, httpware never configures handlers, levels, or calls `logging.basicConfig()`. Consumers own their logging configuration. +- Per `architecture/overview.md`, httpware never configures handlers, levels, or calls `logging.basicConfig()`. Consumers own their logging configuration. - The `otel` extra is opt-in — `pip install httpware` continues to work without `opentelemetry-api`. ## Usage @@ -58,6 +58,5 @@ This effectively closes Epic 5. Remaining roadmap is Epic 6 (ship v1.0): docs si ## References -- Spec: [`planning/archive/specs/2026-06-05-observability-design.md`](../archive/specs/2026-06-05-observability-design.md) -- Plan: [`planning/archive/plans/2026-06-05-observability-plan.md`](../archive/plans/2026-06-05-observability-plan.md) -- Roadmap: [`planning/engineering.md`](../engineering.md) §8 +- Bundle: [`planning/changes/archive/2026-06-05.05-observability/`](../changes/archive/2026-06-05.05-observability/) — design + implementation plan. +- Architecture notes: [`architecture/overview.md`](../../architecture/overview.md) diff --git a/planning/releases/0.7.0.md b/planning/releases/0.7.0.md index cfa842a..1fed137 100644 --- a/planning/releases/0.7.0.md +++ b/planning/releases/0.7.0.md @@ -34,8 +34,6 @@ Remaining roadmap is Epic 6 (ship v1.0): `6-2` docs site infrastructure (mkdocs ## References -- Middleware spec: [`planning/archive/specs/2026-06-05-extension-slot-docs-design.md`](../archive/specs/2026-06-05-extension-slot-docs-design.md) -- Docs-expansion spec: [`planning/archive/specs/2026-06-05-v0.7-docs-expansion-design.md`](../archive/specs/2026-06-05-v0.7-docs-expansion-design.md) -- Middleware plan: [`planning/archive/plans/2026-06-05-extension-slot-docs-plan.md`](../archive/plans/2026-06-05-extension-slot-docs-plan.md) -- Docs-expansion plan: [`planning/archive/plans/2026-06-05-v0.7-docs-expansion-plan.md`](../archive/plans/2026-06-05-v0.7-docs-expansion-plan.md) -- Roadmap: [`planning/engineering.md`](../engineering.md) §8 +- Middleware docs bundle: [`planning/changes/archive/2026-06-05.06-extension-slot-docs/`](../changes/archive/2026-06-05.06-extension-slot-docs/) — design + implementation plan. +- Docs-expansion bundle: [`planning/changes/archive/2026-06-05.07-v0.7-docs-expansion/`](../changes/archive/2026-06-05.07-v0.7-docs-expansion/) — design + implementation plan. +- Architecture notes: [`architecture/overview.md`](../../architecture/overview.md) diff --git a/planning/releases/0.8.0.md b/planning/releases/0.8.0.md index 216c9d7..556a3dd 100644 --- a/planning/releases/0.8.0.md +++ b/planning/releases/0.8.0.md @@ -55,7 +55,6 @@ Then update the symbol references in the file bodies (your type checker will gui ## References -- Design spec: [`planning/specs/2026-06-07-sync-client-design.md`](../specs/2026-06-07-sync-client-design.md) -- Implementation plan: [`planning/plans/2026-06-07-sync-client-plan.md`](../plans/2026-06-07-sync-client-plan.md) -- Engineering notes: [`planning/engineering.md`](../engineering.md) §3 Seam A, §5 module layout -- Source spec parent (httpx convention): [`planning/archive/specs/2026-06-03-thin-httpx2-wrapper-design.md`](../archive/specs/2026-06-03-thin-httpx2-wrapper-design.md) +- Sync-client bundle: [`planning/changes/archive/2026-06-07.01-sync-client/`](../changes/archive/2026-06-07.01-sync-client/) — design + implementation plan. +- Architecture notes: [`architecture/middleware.md`](../../architecture/middleware.md) (Seam A), [`architecture/overview.md`](../../architecture/overview.md) (module layout). +- Source spec parent (httpx convention): [`planning/changes/archive/2026-06-03.02-thin-httpx2-wrapper/`](../changes/archive/2026-06-03.02-thin-httpx2-wrapper/) diff --git a/planning/releases/0.8.1.md b/planning/releases/0.8.1.md index 0514e29..c768334 100644 --- a/planning/releases/0.8.1.md +++ b/planning/releases/0.8.1.md @@ -55,10 +55,9 @@ If you already catch `httpware.ClientError`, nothing changes — your handler no - `Client.send` / `AsyncClient.send` — both wrap the decoder call (one `try/except` each). - `ResponseDecoder.decode` — protocol signature unchanged; docstring grows one sentence documenting the seam wrap. - `PydanticDecoder` and `MsgspecDecoder` — unchanged. -- Docs: `docs/errors.md` (hierarchy + new section), `planning/engineering.md` (Seam B contract + §4 paragraph), `README.md` (one-line note on the `response_model=` paragraph). +- Docs: `docs/errors.md` (hierarchy + new section), `architecture/decoders.md` (Seam B contract) + `architecture/errors.md` (§4 paragraph), `README.md` (one-line note on the `response_model=` paragraph). ## See also -- [`planning/specs/2026-06-07-decoder-error-design.md`](../specs/2026-06-07-decoder-error-design.md) — design rationale. -- [`planning/plans/2026-06-07-decoder-error-plan.md`](../plans/2026-06-07-decoder-error-plan.md) — implementation plan. +- [`planning/changes/archive/2026-06-07.02-decoder-error/`](../changes/archive/2026-06-07.02-decoder-error/) — design rationale + implementation plan. - PR [#32](https://github.com/modern-python/httpware/pull/32). diff --git a/planning/releases/0.8.3.md b/planning/releases/0.8.3.md index 2361d9b..eebb4e5 100644 --- a/planning/releases/0.8.3.md +++ b/planning/releases/0.8.3.md @@ -1,6 +1,6 @@ # httpware 0.8.3 — RetryBudget cluster + retry/client robustness -**Patch release with three behavioral changes you should know about.** All driven by the [deep audit](../audit/2026-06-07-deep-audit.md); collectively close 7 audit findings (3 RetryBudget, 2 retry-surface nits, 2 chunk-3 test rewrites). +**Patch release with three behavioral changes you should know about.** All driven by the [deep audit](../audits/2026-06-07-deep-audit.md); collectively close 7 audit findings (3 RetryBudget, 2 retry-surface nits, 2 chunk-3 test rewrites). ## TL;DR @@ -57,7 +57,7 @@ The note now fires only at the dedicated streaming-refusal site, where streaming ## Audit findings closed -7 of the 35 audit findings from [`planning/audit/2026-06-07-deep-audit.md`](../audit/2026-06-07-deep-audit.md) — the entire RetryBudget cross-cutting cluster plus 2 adjacent retry-surface nits. +7 of the 35 audit findings from [`planning/audits/2026-06-07-deep-audit.md`](../audits/2026-06-07-deep-audit.md) — the entire RetryBudget cross-cutting cluster plus 2 adjacent retry-surface nits. ## Upgrade diff --git a/planning/releases/0.8.4.md b/planning/releases/0.8.4.md index e46eacd..1c9fc69 100644 --- a/planning/releases/0.8.4.md +++ b/planning/releases/0.8.4.md @@ -11,7 +11,7 @@ Two flaws in that gate let a partial install crash a live request: 1. `opentelemetry` is a PEP 420 native namespace package. Any `opentelemetry-instrumentation-*` package creates the `opentelemetry/` directory, so `find_spec("opentelemetry")` returns a non-None spec even when `opentelemetry-api` is absent. 2. The lazy `from opentelemetry import trace` inside `_emit_event` was not wrapped in `try/except`. With the false-positive flag from (1), the import then raised `ImportError` mid-emit, crashing the middleware calling `_emit_event` — `AsyncRetry`, `Retry`, `AsyncBulkhead`, `Bulkhead` — in the middle of a live HTTP request. -The audit's [chunk-2 finding](../audit/2026-06-07-deep-audit.md) named both halves of the hole; this release closes both. +The audit's [chunk-2 finding](../audits/2026-06-07-deep-audit.md) named both halves of the hole; this release closes both. ## The fix diff --git a/planning/releases/0.9.0.md b/planning/releases/0.9.0.md index 6786631..48108d2 100644 --- a/planning/releases/0.9.0.md +++ b/planning/releases/0.9.0.md @@ -87,9 +87,7 @@ with patch.object(PydanticDecoder, "_get_adapter", side_effect=TypeError): ## References -- Design spec: [`planning/specs/2026-06-09-multi-decoder-design.md`](../specs/2026-06-09-multi-decoder-design.md) -- Implementation plan: [`planning/plans/2026-06-09-multi-decoder-plan.md`](../plans/2026-06-09-multi-decoder-plan.md) -- Cache-refactor spec: [`planning/specs/2026-06-10-decoder-instance-cache-design.md`](../specs/2026-06-10-decoder-instance-cache-design.md) -- Cache-refactor plan: [`planning/plans/2026-06-10-decoder-instance-cache-plan.md`](../plans/2026-06-10-decoder-instance-cache-plan.md) -- Engineering notes: [`planning/engineering.md`](../engineering.md) §3 Seam B +- Multi-decoder bundle: [`planning/changes/archive/2026-06-10.01-multi-decoder/`](../changes/archive/2026-06-10.01-multi-decoder/) — design + implementation plan. +- Cache-refactor bundle: [`planning/changes/archive/2026-06-10.02-decoder-instance-cache/`](../changes/archive/2026-06-10.02-decoder-instance-cache/) — design + implementation plan. +- Architecture notes: [`architecture/decoders.md`](../../architecture/decoders.md) — Seam B contract. - PRs: [#41](https://github.com/modern-python/httpware/pull/41), [#42](https://github.com/modern-python/httpware/pull/42)