From f431a958e91debacd4976710257ef89a946fca61 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 19:37:09 +0300 Subject: [PATCH 01/13] docs(spec): adopt portable two-axis planning convention Co-Authored-By: Claude Opus 4.8 (1M context) --- .../design.md | 225 ++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 planning/changes/active/2026-06-13.03-portable-planning-convention/design.md 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). From 4ee73fb970d0a96d3ec996758158aad2606d25de Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 19:47:21 +0300 Subject: [PATCH 02/13] docs(plan): portable planning convention migration plan Also remove the bare plan.md gitignore rule, incompatible with the per-bundle plan.md the convention introduces. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 1 - .../plan.md | 552 ++++++++++++++++++ 2 files changed, 552 insertions(+), 1 deletion(-) create mode 100644 planning/changes/active/2026-06-13.03-portable-planning-convention/plan.md 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/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" + ``` From 5cdfc840e1de7fb91b97d7eab07e2ee924644842 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 19:54:39 +0300 Subject: [PATCH 03/13] chore(planning): bootstrap convention skeleton (templates, deferred.md rename) Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/_templates/change.md | 38 +++++++++++++++ planning/_templates/design.md | 55 +++++++++++++++++++++ planning/_templates/plan.md | 56 ++++++++++++++++++++++ planning/{deferred-work.md => deferred.md} | 0 4 files changed, 149 insertions(+) create mode 100644 planning/_templates/change.md create mode 100644 planning/_templates/design.md create mode 100644 planning/_templates/plan.md rename planning/{deferred-work.md => deferred.md} (100%) 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/deferred-work.md b/planning/deferred.md similarity index 100% rename from planning/deferred-work.md rename to planning/deferred.md From 29c82a02b02131b54ceab6fd96cc32ce39915f32 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 19:59:03 +0300 Subject: [PATCH 04/13] docs(architecture): split engineering.md into per-capability truth files Co-Authored-By: Claude Opus 4.8 (1M context) --- architecture/client.md | 17 ++++ architecture/decoders.md | 15 ++++ architecture/errors.md | 19 +++++ architecture/extras.md | 26 ++++++ architecture/middleware.md | 15 ++++ architecture/overview.md | 42 ++++++++++ architecture/resilience.md | 23 ++++++ architecture/testing.md | 7 ++ planning/engineering.md | 159 ------------------------------------- 9 files changed, 164 insertions(+), 159 deletions(-) create mode 100644 architecture/client.md create mode 100644 architecture/decoders.md create mode 100644 architecture/errors.md create mode 100644 architecture/extras.md create mode 100644 architecture/middleware.md create mode 100644 architecture/overview.md create mode 100644 architecture/resilience.md create mode 100644 architecture/testing.md delete mode 100644 planning/engineering.md diff --git a/architecture/client.md b/architecture/client.md new file mode 100644 index 0000000..64a6531 --- /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 `Retry`/`Bulkhead` resilience primitives, 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..eddb5b0 --- /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 `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. + +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..5d99d8d --- /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` and `Bulkhead` 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; 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..343674d --- /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 `httpx`; `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/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. From ff0c75022ebbdacaed55b0e21e885c7f03deb585 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:06:05 +0300 Subject: [PATCH 05/13] docs(architecture): fix stale resilience-suite enumerations and terminal/decoder wording Co-Authored-By: Claude Opus 4.8 (1M context) --- architecture/client.md | 2 +- architecture/errors.md | 2 +- architecture/extras.md | 4 ++-- architecture/testing.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/architecture/client.md b/architecture/client.md index 64a6531..be79a1a 100644 --- a/architecture/client.md +++ b/architecture/client.md @@ -1,6 +1,6 @@ # 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 `Retry`/`Bulkhead` resilience primitives, and `stream()`. +`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 diff --git a/architecture/errors.md b/architecture/errors.md index eddb5b0..7bff69b 100644 --- a/architecture/errors.md +++ b/architecture/errors.md @@ -10,7 +10,7 @@ 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`. +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. diff --git a/architecture/extras.md b/architecture/extras.md index 5d99d8d..2cb34ba 100644 --- a/architecture/extras.md +++ b/architecture/extras.md @@ -21,6 +21,6 @@ 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` and `Bulkhead` add events to the active OpenTelemetry span via `trace.get_current_span().add_event(...)`. +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; 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. +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/testing.md b/architecture/testing.md index 343674d..f5daed6 100644 --- a/architecture/testing.md +++ b/architecture/testing.md @@ -1,7 +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 `httpx`; `respx` patches private internals and breaks across `httpx` major versions. +- **`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. From 2df7e16b83707673803f4e7c8ea57045b7bd04bd Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:09:42 +0300 Subject: [PATCH 06/13] docs(planning): migrate 0.1.0-0.7.0 specs/plans into change bundles Co-Authored-By: Claude Opus 4.8 (1M context) --- .../design.md} | 10 ++++++++++ .../plan.md} | 8 ++++++++ .../2026-05-31.02-shipped-work-review/design.md} | 10 ++++++++++ .../design.md} | 10 ++++++++++ .../plan.md} | 8 ++++++++ .../2026-05-31.04-phase-shortcut-decorators/design.md} | 10 ++++++++++ .../2026-05-31.04-phase-shortcut-decorators/plan.md} | 8 ++++++++ .../design.md} | 10 ++++++++++ .../plan.md} | 8 ++++++++ .../design.md} | 10 ++++++++++ .../2026-05-31.06-msgspec-decoder-via-extras/plan.md} | 8 ++++++++ .../archive/2026-05-31.07-asyncclient/design.md} | 10 ++++++++++ .../archive/2026-05-31.07-asyncclient/plan.md} | 8 ++++++++ .../archive/2026-05-31.08-recordedtransport/design.md} | 10 ++++++++++ .../archive/2026-05-31.08-recordedtransport/plan.md} | 8 ++++++++ .../2026-05-31.09-release-0.1.0-prep/design.md} | 10 ++++++++++ .../archive/2026-05-31.09-release-0.1.0-prep/plan.md} | 8 ++++++++ .../archive/2026-06-01.01-auth-coercion/design.md} | 10 ++++++++++ .../archive/2026-06-01.01-auth-coercion/plan.md} | 8 ++++++++ .../2026-06-02.01-docs-reorg-and-mkdocs/design.md} | 10 ++++++++++ .../2026-06-02.01-docs-reorg-and-mkdocs/plan.md} | 8 ++++++++ .../2026-06-02.02-project-hygiene-tidy/design.md} | 10 ++++++++++ .../2026-06-02.02-project-hygiene-tidy/plan.md} | 8 ++++++++ .../2026-06-03.01-input-validation-pass/design.md} | 10 ++++++++++ .../2026-06-03.01-input-validation-pass/plan.md} | 8 ++++++++ .../2026-06-03.02-thin-httpx2-wrapper/design.md} | 10 ++++++++++ .../archive/2026-06-03.02-thin-httpx2-wrapper/plan.md} | 8 ++++++++ .../2026-06-04.01-pydantic-optional-extra/design.md} | 10 ++++++++++ .../2026-06-04.01-pydantic-optional-extra/plan.md} | 8 ++++++++ .../design.md} | 10 ++++++++++ .../2026-06-05.01-retry-and-retry-budget/design.md} | 10 ++++++++++ .../2026-06-05.01-retry-and-retry-budget/plan.md} | 8 ++++++++ .../archive/2026-06-05.02-bulkhead/design.md} | 10 ++++++++++ .../archive/2026-06-05.02-bulkhead/plan.md} | 8 ++++++++ .../archive/2026-06-05.03-docs-sync-0.4/design.md} | 10 ++++++++++ .../archive/2026-06-05.03-docs-sync-0.4/plan.md} | 8 ++++++++ .../archive/2026-06-05.04-streaming/design.md} | 10 ++++++++++ .../archive/2026-06-05.04-streaming/plan.md} | 8 ++++++++ .../archive/2026-06-05.05-observability/design.md} | 10 ++++++++++ .../archive/2026-06-05.05-observability/plan.md} | 8 ++++++++ .../2026-06-05.06-extension-slot-docs/design.md} | 10 ++++++++++ .../archive/2026-06-05.06-extension-slot-docs/plan.md} | 8 ++++++++ .../2026-06-05.07-v0.7-docs-expansion/design.md} | 10 ++++++++++ .../archive/2026-06-05.07-v0.7-docs-expansion/plan.md} | 8 ++++++++ 44 files changed, 398 insertions(+) rename planning/{archive/specs/2026-05-31-bmad-to-superpowers-transition-design.md => changes/archive/2026-05-31.01-bmad-to-superpowers-transition/design.md} (98%) rename planning/{archive/plans/2026-05-31-bmad-to-superpowers-transition-plan.md => changes/archive/2026-05-31.01-bmad-to-superpowers-transition/plan.md} (99%) rename planning/{archive/specs/2026-05-31-shipped-work-review.md => changes/archive/2026-05-31.02-shipped-work-review/design.md} (98%) rename planning/{archive/specs/2026-05-31-middleware-protocol-and-chain-design.md => changes/archive/2026-05-31.03-middleware-protocol-and-chain/design.md} (98%) rename planning/{archive/plans/2026-05-31-middleware-protocol-and-chain-plan.md => changes/archive/2026-05-31.03-middleware-protocol-and-chain/plan.md} (99%) rename planning/{archive/specs/2026-05-31-phase-shortcut-decorators-design.md => changes/archive/2026-05-31.04-phase-shortcut-decorators/design.md} (98%) rename planning/{archive/plans/2026-05-31-phase-shortcut-decorators-plan.md => changes/archive/2026-05-31.04-phase-shortcut-decorators/plan.md} (99%) rename planning/{archive/specs/2026-05-31-request-immutability-helpers-design.md => changes/archive/2026-05-31.05-request-immutability-helpers/design.md} (98%) rename planning/{archive/plans/2026-05-31-request-immutability-helpers-plan.md => changes/archive/2026-05-31.05-request-immutability-helpers/plan.md} (99%) rename planning/{archive/specs/2026-05-31-msgspec-decoder-via-extras-design.md => changes/archive/2026-05-31.06-msgspec-decoder-via-extras/design.md} (98%) rename planning/{archive/plans/2026-05-31-msgspec-decoder-via-extras-plan.md => changes/archive/2026-05-31.06-msgspec-decoder-via-extras/plan.md} (99%) rename planning/{archive/specs/2026-05-31-asyncclient-design.md => changes/archive/2026-05-31.07-asyncclient/design.md} (99%) rename planning/{archive/plans/2026-05-31-asyncclient-plan.md => changes/archive/2026-05-31.07-asyncclient/plan.md} (99%) rename planning/{archive/specs/2026-05-31-recordedtransport-design.md => changes/archive/2026-05-31.08-recordedtransport/design.md} (98%) rename planning/{archive/plans/2026-05-31-recordedtransport-plan.md => changes/archive/2026-05-31.08-recordedtransport/plan.md} (99%) rename planning/{archive/specs/2026-05-31-release-0.1.0-prep-design.md => changes/archive/2026-05-31.09-release-0.1.0-prep/design.md} (99%) rename planning/{archive/plans/2026-05-31-release-0.1.0-prep-plan.md => changes/archive/2026-05-31.09-release-0.1.0-prep/plan.md} (99%) rename planning/{archive/specs/2026-06-01-auth-coercion-design.md => changes/archive/2026-06-01.01-auth-coercion/design.md} (99%) rename planning/{archive/plans/2026-06-01-auth-coercion-plan.md => changes/archive/2026-06-01.01-auth-coercion/plan.md} (99%) rename planning/{archive/specs/2026-06-02-docs-reorg-and-mkdocs-design.md => changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/design.md} (98%) rename planning/{archive/plans/2026-06-02-docs-reorg-and-mkdocs-plan.md => changes/archive/2026-06-02.01-docs-reorg-and-mkdocs/plan.md} (99%) rename planning/{archive/specs/2026-06-02-project-hygiene-tidy-design.md => changes/archive/2026-06-02.02-project-hygiene-tidy/design.md} (99%) rename planning/{archive/plans/2026-06-02-project-hygiene-tidy-plan.md => changes/archive/2026-06-02.02-project-hygiene-tidy/plan.md} (99%) rename planning/{archive/specs/2026-06-03-input-validation-pass-design.md => changes/archive/2026-06-03.01-input-validation-pass/design.md} (99%) rename planning/{archive/plans/2026-06-03-input-validation-pass-plan.md => changes/archive/2026-06-03.01-input-validation-pass/plan.md} (99%) rename planning/{archive/specs/2026-06-03-thin-httpx2-wrapper-design.md => changes/archive/2026-06-03.02-thin-httpx2-wrapper/design.md} (98%) rename planning/{archive/plans/2026-06-03-thin-httpx2-wrapper-plan.md => changes/archive/2026-06-03.02-thin-httpx2-wrapper/plan.md} (99%) rename planning/{archive/specs/2026-06-04-pydantic-optional-extra-design.md => changes/archive/2026-06-04.01-pydantic-optional-extra/design.md} (99%) rename planning/{archive/plans/2026-06-04-pydantic-optional-extra-plan.md => changes/archive/2026-06-04.01-pydantic-optional-extra/plan.md} (99%) rename planning/{archive/specs/2026-06-04-v0.2-retro-and-housekeeping-design.md => changes/archive/2026-06-04.02-v0.2-retro-and-housekeeping/design.md} (98%) rename planning/{archive/specs/2026-06-05-retry-and-retry-budget-design.md => changes/archive/2026-06-05.01-retry-and-retry-budget/design.md} (99%) rename planning/{archive/plans/2026-06-05-retry-and-retry-budget-plan.md => changes/archive/2026-06-05.01-retry-and-retry-budget/plan.md} (99%) rename planning/{archive/specs/2026-06-05-bulkhead-design.md => changes/archive/2026-06-05.02-bulkhead/design.md} (98%) rename planning/{archive/plans/2026-06-05-bulkhead-plan.md => changes/archive/2026-06-05.02-bulkhead/plan.md} (99%) rename planning/{archive/specs/2026-06-05-docs-sync-0.4-design.md => changes/archive/2026-06-05.03-docs-sync-0.4/design.md} (98%) rename planning/{archive/plans/2026-06-05-docs-sync-0.4-plan.md => changes/archive/2026-06-05.03-docs-sync-0.4/plan.md} (99%) rename planning/{archive/specs/2026-06-05-streaming-design.md => changes/archive/2026-06-05.04-streaming/design.md} (99%) rename planning/{archive/plans/2026-06-05-streaming-plan.md => changes/archive/2026-06-05.04-streaming/plan.md} (99%) rename planning/{archive/specs/2026-06-05-observability-design.md => changes/archive/2026-06-05.05-observability/design.md} (99%) rename planning/{archive/plans/2026-06-05-observability-plan.md => changes/archive/2026-06-05.05-observability/plan.md} (99%) rename planning/{archive/specs/2026-06-05-extension-slot-docs-design.md => changes/archive/2026-06-05.06-extension-slot-docs/design.md} (98%) rename planning/{archive/plans/2026-06-05-extension-slot-docs-plan.md => changes/archive/2026-06-05.06-extension-slot-docs/plan.md} (99%) rename planning/{archive/specs/2026-06-05-v0.7-docs-expansion-design.md => changes/archive/2026-06-05.07-v0.7-docs-expansion/design.md} (99%) rename planning/{archive/plans/2026-06-05-v0.7-docs-expansion-plan.md => changes/archive/2026-06-05.07-v0.7-docs-expansion/plan.md} (99%) 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. From 4fe4b328a11e563944676eefdd9be3de043f4239 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:17:18 +0300 Subject: [PATCH 07/13] docs(planning): migrate 0.8.0-0.10.0 specs/plans into change bundles Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-06.01-modern-di-recipe/design.md} | 12 +++++++++++- .../archive/2026-06-06.01-modern-di-recipe/plan.md} | 10 +++++++++- .../archive/2026-06-07.01-sync-client/design.md} | 12 +++++++++++- .../archive/2026-06-07.01-sync-client/plan.md} | 10 +++++++++- .../archive/2026-06-07.02-decoder-error/design.md} | 12 +++++++++++- .../archive/2026-06-07.02-decoder-error/plan.md} | 10 +++++++++- .../archive/2026-06-07.03-deep-audit/design.md} | 12 +++++++++++- .../archive/2026-06-07.03-deep-audit/plan.md} | 10 +++++++++- .../2026-06-08.01-send-with-response/design.md} | 12 +++++++++++- .../2026-06-08.01-send-with-response/plan.md} | 10 +++++++++- .../2026-06-08.02-retry-budget-cluster/design.md} | 12 +++++++++++- .../2026-06-08.02-retry-budget-cluster/plan.md} | 10 +++++++++- .../2026-06-08.03-post-080-doc-sweep/design.md} | 12 +++++++++++- .../2026-06-08.03-post-080-doc-sweep/plan.md} | 10 +++++++++- .../2026-06-08.04-otel-partial-install/design.md} | 12 +++++++++++- .../2026-06-08.04-otel-partial-install/plan.md} | 10 +++++++++- .../2026-06-08.05-small-fixes-mop-up/design.md} | 12 +++++++++++- .../2026-06-08.05-small-fixes-mop-up/plan.md} | 10 +++++++++- .../archive/2026-06-08.06-test-mop-up/design.md} | 12 +++++++++++- .../archive/2026-06-08.06-test-mop-up/plan.md} | 10 +++++++++- .../design.md} | 12 +++++++++++- .../2026-06-08.07-mkdocs-gh-pages-migration/plan.md} | 10 +++++++++- .../2026-06-08.08-readme-link-cleanup/design.md} | 12 +++++++++++- .../2026-06-08.08-readme-link-cleanup/plan.md} | 10 +++++++++- .../archive/2026-06-10.01-multi-decoder/design.md} | 12 +++++++++++- .../archive/2026-06-10.01-multi-decoder/plan.md} | 10 +++++++++- .../2026-06-10.02-decoder-instance-cache/design.md} | 12 +++++++++++- .../2026-06-10.02-decoder-instance-cache/plan.md} | 10 +++++++++- .../archive/2026-06-12.01-delta-audit/design.md} | 12 +++++++++++- .../archive/2026-06-12.01-delta-audit/plan.md} | 10 +++++++++- .../design.md} | 12 +++++++++++- .../plan.md} | 10 +++++++++- .../design.md} | 12 +++++++++++- .../plan.md} | 10 +++++++++- 34 files changed, 340 insertions(+), 34 deletions(-) rename planning/{specs/2026-06-06-modern-di-recipe-design.md => changes/archive/2026-06-06.01-modern-di-recipe/design.md} (99%) rename planning/{plans/2026-06-06-modern-di-recipe-plan.md => changes/archive/2026-06-06.01-modern-di-recipe/plan.md} (99%) rename planning/{specs/2026-06-07-sync-client-design.md => changes/archive/2026-06-07.01-sync-client/design.md} (99%) rename planning/{plans/2026-06-07-sync-client-plan.md => changes/archive/2026-06-07.01-sync-client/plan.md} (99%) rename planning/{specs/2026-06-07-decoder-error-design.md => changes/archive/2026-06-07.02-decoder-error/design.md} (98%) rename planning/{plans/2026-06-07-decoder-error-plan.md => changes/archive/2026-06-07.02-decoder-error/plan.md} (99%) rename planning/{specs/2026-06-07-deep-audit-design.md => changes/archive/2026-06-07.03-deep-audit/design.md} (98%) rename planning/{plans/2026-06-07-deep-audit-plan.md => changes/archive/2026-06-07.03-deep-audit/plan.md} (99%) rename planning/{specs/2026-06-08-send-with-response-design.md => changes/archive/2026-06-08.01-send-with-response/design.md} (98%) rename planning/{plans/2026-06-08-send-with-response-plan.md => changes/archive/2026-06-08.01-send-with-response/plan.md} (99%) rename planning/{specs/2026-06-08-retry-budget-cluster-design.md => changes/archive/2026-06-08.02-retry-budget-cluster/design.md} (98%) rename planning/{plans/2026-06-08-retry-budget-cluster-plan.md => changes/archive/2026-06-08.02-retry-budget-cluster/plan.md} (99%) rename planning/{specs/2026-06-08-post-080-doc-sweep-design.md => changes/archive/2026-06-08.03-post-080-doc-sweep/design.md} (98%) rename planning/{plans/2026-06-08-post-080-doc-sweep-plan.md => changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md} (99%) rename planning/{specs/2026-06-08-otel-partial-install-design.md => changes/archive/2026-06-08.04-otel-partial-install/design.md} (97%) rename planning/{plans/2026-06-08-otel-partial-install-plan.md => changes/archive/2026-06-08.04-otel-partial-install/plan.md} (99%) rename planning/{specs/2026-06-08-small-fixes-mop-up-design.md => changes/archive/2026-06-08.05-small-fixes-mop-up/design.md} (98%) rename planning/{plans/2026-06-08-small-fixes-mop-up-plan.md => changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md} (99%) rename planning/{specs/2026-06-08-test-mop-up-design.md => changes/archive/2026-06-08.06-test-mop-up/design.md} (98%) rename planning/{plans/2026-06-08-test-mop-up-plan.md => changes/archive/2026-06-08.06-test-mop-up/plan.md} (99%) rename planning/{specs/2026-06-08-mkdocs-gh-pages-migration-design.md => changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md} (97%) rename planning/{plans/2026-06-08-mkdocs-gh-pages-migration-plan.md => changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md} (99%) rename planning/{specs/2026-06-08-readme-link-cleanup-design.md => changes/archive/2026-06-08.08-readme-link-cleanup/design.md} (97%) rename planning/{plans/2026-06-08-readme-link-cleanup-plan.md => changes/archive/2026-06-08.08-readme-link-cleanup/plan.md} (99%) rename planning/{specs/2026-06-09-multi-decoder-design.md => changes/archive/2026-06-10.01-multi-decoder/design.md} (99%) rename planning/{plans/2026-06-09-multi-decoder-plan.md => changes/archive/2026-06-10.01-multi-decoder/plan.md} (99%) rename planning/{specs/2026-06-10-decoder-instance-cache-design.md => changes/archive/2026-06-10.02-decoder-instance-cache/design.md} (98%) rename planning/{plans/2026-06-10-decoder-instance-cache-plan.md => changes/archive/2026-06-10.02-decoder-instance-cache/plan.md} (99%) rename planning/{specs/2026-06-12-delta-audit-design.md => changes/archive/2026-06-12.01-delta-audit/design.md} (98%) rename planning/{plans/2026-06-12-delta-audit-plan.md => changes/archive/2026-06-12.01-delta-audit/plan.md} (99%) rename planning/{specs/2026-06-13-msgspec-nested-customtype-fix-design.md => changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md} (97%) rename planning/{plans/2026-06-13-msgspec-nested-customtype-fix-plan.md => changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md} (99%) rename planning/{specs/2026-06-13-circuit-breaker-and-timeout-design.md => changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md} (99%) rename planning/{plans/2026-06-13-circuit-breaker-and-timeout.md => changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md} (99%) 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..d5a8d37 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 @@ -272,4 +282,4 @@ Trade-off: deferring it to a separate PR (option C in the brainstorming) keeps t Trade-off: this error name and behavior are stable in `modern-di` as of the version of the source we inspected. If they change, the recipe gets a one-line edit. Worth the precision — vague language like "you'll get an error" is worse than naming the actual exception. **Risk: telling readers `lambda c: c.aclose()` does not work is a strong claim.** -Trade-off: the claim is correct (verified against `modern_di/providers/factory.py:27` — `is_async_finalizer = inspect.iscoroutinefunction(self.finalizer)`, and a lambda is not a coroutine function regardless of what it returns). Calling it out explicitly saves users from a confusing "coroutine was never awaited" warning and a connection pool that quietly leaks. Worth the precision. +Trade-off: the claim is correct (verified against `modern_di/providers/factory.py:27` — `is_async_finalizer = inspect.iscoroutinefunction(self.finalizer)`, and a lambda is not a coroutine function regardless of what it returns). Calling it out explicitly saves users from a confusing "coroutine was never awaited" warning and a connection pool that quietly leaks. Worth the precision. \ No newline at end of file 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..81d38fd 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. @@ -609,4 +617,4 @@ This task produced no source changes — it was verification only. Move on to PR - **Type consistency:** `AsyncClient.aclose` is referenced identically in the source (Task 1 Step 5), the recipe sample finalizer (Task 2 Step 1), and the verification script (Task 3 Step 1). `UserApi`, `BillingApi` names match across recipe and verification script. -- **Naming:** test names follow the existing `test_aexit_*` pattern in `test_client_lifecycle.py` — `test_aclose_closes_owned_httpx2_client` and `test_aclose_is_idempotent_for_owned_client`. Spec used slightly looser names; plan tightened them to the existing convention. +- **Naming:** test names follow the existing `test_aexit_*` pattern in `test_client_lifecycle.py` — `test_aclose_closes_owned_httpx2_client` and `test_aclose_is_idempotent_for_owned_client`. Spec used slightly looser names; plan tightened them to the existing convention. \ No newline at end of file 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..d966ed7 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 @@ -582,4 +592,4 @@ A new file `planning/releases/0.8.0.md` (or `1.0.0.md`) drafts release notes cov - `planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md` — async `Retry` + `RetryBudget` design; the sync versions mirror this (minus `attempt_timeout`) - `planning/archive/specs/2026-06-05-bulkhead-design.md` — async `Bulkhead` design; sync `Bulkhead` mirrors this with `threading.Semaphore` - `httpx` documentation — naming-convention reference (`Client` + `AsyncClient` at top level) -- `websockets` documentation — ecosystem precedent for async-primary + sync-secondary (the inverse of what we chose, considered and rejected) +- `websockets` documentation — ecosystem precedent for async-primary + sync-secondary (the inverse of what we chose, considered and rejected) \ No newline at end of file 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..e13674f 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. @@ -3522,4 +3530,4 @@ After completing all tasks, verify: - [ ] `100% line coverage` maintained - [ ] `just lint` clean - [ ] `import httpware` does not transitively import pydantic/msgspec/opentelemetry (Task B21 step 3) -- [ ] Both PRs merged before cutting the single release +- [ ] Both PRs merged before cutting the single release \ No newline at end of file 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 98% 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..da4fd62 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 @@ -257,4 +267,4 @@ Release notes: - **Fix:** decoder exceptions from `response_model=` are now wrapped in a new `httpware.DecodeError` (a `ClientError` subclass), closing the gap where `pydantic.ValidationError` / `msgspec.ValidationError` / `msgspec.DecodeError` would escape `except httpware.ClientError`. - **New:** `httpware.DecodeError` — direct child of `ClientError`. Fields: `response`, `model`, `original`. -- **Behavior change:** consumers catching pydantic/msgspec exceptions directly need to switch to `except httpware.DecodeError` (or the broader `except httpware.ClientError`). No shim layer; the previously-leaking exceptions weren't a documented contract. +- **Behavior change:** consumers catching pydantic/msgspec exceptions directly need to switch to `except httpware.DecodeError` (or the broader `except httpware.ClientError`). No shim layer; the previously-leaking exceptions weren't a documented contract. \ No newline at end of file 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..fcfa1c1 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. @@ -920,4 +928,4 @@ Expected: the new `DecodeError` class and the two `try/except` blocks in `client - [ ] **Step 6: Confirm release readiness** -The work is now ready to ship as `0.8.1`. No version bump happens in this plan — release-cutting is a separate manual step per the project's existing convention (bare-semver git tag, no CHANGELOG file, release notes on GitHub Releases). +The work is now ready to ship as `0.8.1`. No version bump happens in this plan — release-cutting is a separate manual step per the project's existing convention (bare-semver git tag, no CHANGELOG file, release notes on GitHub Releases). \ No newline at end of file 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 98% 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..6454685 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 @@ -281,4 +291,4 @@ The audit is complete when: 1. `planning/audit/2026-06-07-deep-audit.md` exists, contains findings from every dimension the user approved running, and is committed. 2. Each finding includes: title, file:line, claim, evidence quote, verifier consensus, suggested direction. 3. The summary header reflects final counts after cross-chunk dedup. -4. The user has reviewed the report and either approved it as the audit deliverable or requested specific re-runs (which trigger a second pass on those dimensions only). +4. The user has reviewed the report and either approved it as the audit deliverable or requested specific re-runs (which trigger a second pass on those dimensions only). \ No newline at end of file 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..197b043 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. @@ -746,4 +754,4 @@ Done after writing the plan: - **Spec coverage:** Every section in the spec maps to a task. Discover → Task 4 step 3 / dry-run step 1. 8 dimensions → Task 2's `DIMENSION_PROMPTS`. Schemas → Task 2. Verifier panel → Task 2 `VERIFIER_PROMPTS`. 2-of-3 consensus → Task 2 script body. Synthesis → Task 2 `SYNTHESIS_PROMPT`. Severity buckets → enforced in synthesis prompt. Gate behavior → Tasks 4-7 each have a "Report and gate" step. Final merge → Task 8. Open questions resolved at top of plan. - **No placeholders:** All prompts written out verbatim. No "TBD" or "fill in." Schemas are concrete JSON Schema objects. -- **Type/name consistency:** `audit_file`, `discover_file`, `chunk_id`, `dimensions`, `run_discover` are used consistently across all tasks. Severity strings (`blocker|high|medium|low|nit`) match between spec and plan. +- **Type/name consistency:** `audit_file`, `discover_file`, `chunk_id`, `dimensions`, `run_discover` are used consistently across all tasks. Severity strings (`blocker|high|medium|low|nit`) match between spec and plan. \ No newline at end of file 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..c4e6b79 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 @@ -212,4 +222,4 @@ Semver justification: a `0.x` minor would be defensible too (new public API), bu ## Out of scope, recorded for later - **Per-verb siblings** (`get_with_response`, etc.) — if a concrete httpware consumer beyond semvertag's `list_tags` surfaces a need, revisit. Until then, file under `planning/deferred-work.md` post-merge. -- **Pagination helper** (`paginate_links(client, request, response_model=...) -> Iterator[T]`) — tempting because Link-header pagination is the dominant `send_with_response` use case, but a generic pagination helper has to make choices (RFC 5988 only? cursor-style? page-number style?) that belong in the consuming library, not in `httpware`. Out of scope for the foreseeable future. +- **Pagination helper** (`paginate_links(client, request, response_model=...) -> Iterator[T]`) — tempting because Link-header pagination is the dominant `send_with_response` use case, but a generic pagination helper has to make choices (RFC 5988 only? cursor-style? page-number style?) that belong in the consuming library, not in `httpware`. Out of scope for the foreseeable future. \ No newline at end of file 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..d85ae0c 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. @@ -658,4 +666,4 @@ Use `gh pr create` per the project's existing convention. Title: `feat: add send **Type consistency:** `send_with_response`, `response_model`, `DecodeError(response=, model=, original=)`, `_dispatch`, `_decoder.decode(response.content, response_model)`, `tuple[httpx2.Response, T]` — used identically across Tasks 1–4 and the spec. ✓ -**Placeholder scan:** no TBD/TODO. Every step has either concrete code or an exact command with expected output. +**Placeholder scan:** no TBD/TODO. Every step has either concrete code or an exact command with expected output. \ No newline at end of file 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 98% 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..c39b785 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 @@ -285,4 +295,4 @@ None deferred. All behavioral choices are made (Retry-After: give up with PEP 67 3. New tests cover every finding's behavioral change. 4. `planning/releases/0.8.3.md` exists and structurally mirrors `planning/releases/0.8.1.md`. 5. PR opened against `main` with title `fix(retry-budget): close 7 audit findings (0.8.3)` and a body that summarizes each finding closed and the behavioral changes. -6. After user approval + merge + tag, [memory: release_0_8_3_shipped](../../.claude/projects/-Users-kevinsmith-src-pypi-httpware/memory/) is added. +6. After user approval + merge + tag, [memory: release_0_8_3_shipped](../../.claude/projects/-Users-kevinsmith-src-pypi-httpware/memory/) is added. \ No newline at end of file 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..f394f64 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. @@ -1324,4 +1332,4 @@ Expected: 7 fix commits + spec commit on the branch (excluding the spec commit w - **Placeholder scan:** All before/after code blocks are verbatim with surrounding context. Test code is complete (no "similar to" or "add appropriate"). Commit-message HEREDOCs are filled in. - **Type/name consistency:** `_RETRY_AFTER_EXCEEDS_MAX_DELAY_NOTE` introduced in T3 is used consistently. `_CountingBudget` introduced in T2 is used in both async + sync test additions with the same shape. `is_closed` is the same attribute name in both T6 async + sync fixes. - **TDD ordering:** Every task that touches production code has a failing-test step before the fix. T1 tightens the assertion (`<=` → `==`) so the rewritten test fails against the unfixed production. T2-T6 each follow red-green-commit. -- **Test parity:** Every async test addition has a corresponding sync mirror step. Where sync helper names may differ (`_SleepRecorder` vs `_SleepRecorderSync`, etc.), the step explicitly says "read the file first to confirm". +- **Test parity:** Every async test addition has a corresponding sync mirror step. Where sync helper names may differ (`_SleepRecorder` vs `_SleepRecorderSync`, etc.), the step explicitly says "read the file first to confirm". \ No newline at end of file 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 98% 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..eeef064 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 @@ -241,4 +251,4 @@ None deferred. Every change is specified concretely above. The plan will transla 1. All six per-file commits land on `main` with messages naming the audit findings they close. 2. `just lint-ci` is green after every commit and after the final one. 3. The post-sweep grep returns zero matches for every stale phrase listed in Verification. -4. The audit file itself is unchanged by this work — findings stay where they are; future readers can match each commit's message back to the audit by file:line. +4. The audit file itself is unchanged by this work — findings stay where they are; future readers can match each commit's message back to the audit by file:line. \ No newline at end of file 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..0926ef9 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. @@ -654,4 +662,4 @@ Report to the user: 6 commits ready, every audit doc-staleness finding from the - **Spec coverage:** Every change in the spec's "Per-file change list" maps to a task. CLAUDE.md → Task 1; README.md → Task 2; docs/index.md → Task 3; docs/resilience.md → Task 4; planning/engineering.md → Task 5; decoders/__init__.py → Task 6. Verification → Task 7. - **Placeholder scan:** All before/after strings are verbatim. No "TBD" / "TODO" / "similar to Task N" patterns. Each task's grep commands and expected outputs are concrete. - **Type/name consistency:** "Seam A/B/C" used consistently from Task 1 (CLAUDE.md introduces them) through Task 6 (decoders picks them up). The `Retry`/`AsyncRetry` and `Bulkhead`/`AsyncBulkhead` pairings are formatted identically in Tasks 2, 3, and 4. -- **Order dependency:** Task 6 depends on Task 1 (decoders docstring uses the Seam-B label CLAUDE.md establishes). Task 7 depends on Tasks 1-6. Tasks 2, 3, 4, 5 are mutually independent and could in principle run in parallel, but the plan executes sequentially to keep the diff stream readable. +- **Order dependency:** Task 6 depends on Task 1 (decoders docstring uses the Seam-B label CLAUDE.md establishes). Task 7 depends on Tasks 1-6. Tasks 2, 3, 4, 5 are mutually independent and could in principle run in parallel, but the plan executes sequentially to keep the diff stream readable. \ No newline at end of file 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 97% 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..e8f808b 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 @@ -208,4 +218,4 @@ uv run pytest -x --no-cov -q ## Open questions -None. Both fixes are precisely specified by the audit; the tests are straightforward; the release-notes shape is established. +None. Both fixes are precisely specified by the audit; the tests are straightforward; the release-notes shape is established. \ No newline at end of file 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..f02cd94 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. @@ -497,4 +505,4 @@ Report the PR URL. - **Spec coverage:** Spec finding #1 = T1 (import_checker probe target). Spec finding #2 = T2 (try/except wrap + docstring extension). Spec tests section = T1 + T2 test steps. Spec release notes section = T3. PR opening = T3 Step 5. - **Placeholder scan:** All code blocks are complete with verbatim old_string / new_string. Test bodies are complete. Commit messages are filled in. No "TBD" / "similar to". - **Type/name consistency:** `_TEST_LOGGER` is reused from the existing file in Task 2 (the test file already defines it). The `_BrokenOpenTelemetry` class is local to one test. `find_spec("opentelemetry.trace")` is the exact same string in source and test source-check. -- **TDD ordering:** T1 and T2 each follow red-green-commit. T1's red is a source-level assertion (the live `find_spec` calls pass either way under `--all-extras`, so source-pinning is the regression guard). T2's red is a real runtime ImportError out of `_emit_event`. +- **TDD ordering:** T1 and T2 each follow red-green-commit. T1's red is a source-level assertion (the live `find_spec` calls pass either way under `--all-extras`, so source-pinning is the regression guard). T2's red is a real runtime ImportError out of `_emit_event`. \ No newline at end of file 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..757a2a5 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 @@ -284,4 +294,4 @@ Full suite + lint green after every commit. ## Open questions -None. All four fixes are precisely specified and the audit's recommended directions hold. +None. All four fixes are precisely specified and the audit's recommended directions hold. \ No newline at end of file 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..c24d5a8 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. @@ -704,4 +712,4 @@ Return DONE with the PR URL. - **Placeholder scan:** All before/after strings are verbatim. Test bodies complete. Commit messages filled in. No "TBD" / "similar to". - **Type/name consistency:** `_LOGGER` lowercase is used in the LoggingMiddleware example; the existing `RequestIdMiddleware` further down the file uses the same lowercase convention (verified during spec design). The `actual` variable in Task 4's symmetric assertion is reused in the error message. - **TDD ordering:** T1 has a clean red→green sequence with two tests. T2 is a contract-narrowing change verified by existing tests (no new test needed — see spec §Tests). T3 is docs-only. T4 is a test-quality change; manual verification step confirms the new symmetric failure mode is real. -- **Order dependency:** None. T1-T4 are independent; T5 depends on T1-T4 all landing. The branch sequences them in order for diff readability. +- **Order dependency:** None. T1-T4 are independent; T5 depends on T1-T4 all landing. The branch sequences them in order for diff readability. \ No newline at end of file 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 98% 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..757abbd 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 @@ -393,4 +403,4 @@ Full suite + lint green after every commit. ## Open questions -None. All five fixes are precisely specified. The finding excluded as INVALID is documented in scope-note above. +None. All five fixes are precisely specified. The finding excluded as INVALID is documented in scope-note above. \ No newline at end of file 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..ce6cb40 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. @@ -807,4 +815,4 @@ Return DONE with the PR URL. - **Placeholder scan:** All code blocks complete with verbatim test bodies. Commit messages filled. No "TBD" / "similar to". - **Type/name consistency:** `_FakeDecoder` defined at module level in T3 is used in both async and sync tests (consistent). `_InFlightHandler` in T4's new file mirrors the async file's class name (intentional). The shared counter pattern uses `_lock` consistently. - **TDD ordering:** T1-T3 each have a verify-state step before the fix; T4 is a new-file addition with the existing async file as the reference template; T5 appends to an existing file. All tasks have "run the test" steps that establish PASS as the success criterion. -- **Risks:** T4 (threading + ThreadPoolExecutor + MockTransport) is the highest-flake risk. The plan includes escalation guidance for the implementer: drop `max_examples` to 10, then DONE_WITH_CONCERNS if still flaky. +- **Risks:** T4 (threading + ThreadPoolExecutor + MockTransport) is the highest-flake risk. The plan includes escalation guidance for the implementer: drop `max_examples` to 10, then DONE_WITH_CONCERNS if still flaky. \ No newline at end of file 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..6138a0f 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 @@ -113,4 +123,4 @@ If any step fails, the rollback is to revert the PR — RTD remains live and una ## Scope check -This is a single-PR structural change. No decomposition needed. Aligns with the stated preference: clean cutover landing as one structural PR before any substantive follow-up work. +This is a single-PR structural change. No decomposition needed. Aligns with the stated preference: clean cutover landing as one structural PR before any substantive follow-up work. \ No newline at end of file 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..ddb3bc4 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. @@ -617,4 +625,4 @@ These steps happen on `main` after the PR merges, in coordination with the repo 4. After admin sets Pages source + DNS resolves, load `https://httpware.modern-python.org/` and verify content matches the previous RTD site. 5. Push a trivial docs edit in a follow-up commit; confirm the workflow re-triggers and the live site updates within ~2 minutes. -If any post-merge step fails, the rollback is `git revert` of the merge commit. ReadTheDocs remains live and unaltered until manually archived, so reverting cleanly restores the prior state. +If any post-merge step fails, the rollback is `git revert` of the merge commit. ReadTheDocs remains live and unaltered until manually archived, so reverting cleanly restores the prior state. \ No newline at end of file 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..a27d6bf 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 @@ -85,4 +95,4 @@ Near-zero. All changes are text-only in markdown files. No code changes. No CI c ## Scope check -Single-PR, low-risk change. Three line-edits + a one-shot script run. No decomposition needed. +Single-PR, low-risk change. Three line-edits + a one-shot script run. No decomposition needed. \ No newline at end of file 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..032c94c 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. @@ -420,4 +428,4 @@ If files outside this list show up, STOP and investigate before proceeding. ## Post-PR - Wait for review and merge. -- After merge, `gh-pages` is unaffected (no `docs/` content changed in the README-only path), so no docs redeploy is triggered. If Task 7 modified `docs/`, the `Deploy Docs` workflow re-runs on `main` push. +- After merge, `gh-pages` is unaffected (no `docs/` content changed in the README-only path), so no docs redeploy is triggered. If Task 7 modified `docs/`, the `Deploy Docs` workflow re-runs on `main` push. \ No newline at end of file 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..13e1fb9 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 @@ -392,4 +402,4 @@ Tag and GitHub Release notes follow the existing bare-semver tag convention ([re **Engineering doc update** — `planning/engineering.md` Seam B description is updated: - Old: "Called when `response_model` is provided. Signature: `decode(content: bytes, model: type[T]) -> T`." -- New: "Implementations expose `can_decode(model) -> bool` (dispatch predicate) and `decode(content, model) -> T` (the decode). The client holds a tuple `_decoders` and walks it in order on every `response_model=` use; first matching decoder wins. `MissingDecoderError` fires before the HTTP call when no decoder matches." +- New: "Implementations expose `can_decode(model) -> bool` (dispatch predicate) and `decode(content, model) -> T` (the decode). The client holds a tuple `_decoders` and walks it in order on every `response_model=` use; first matching decoder wins. `MissingDecoderError` fires before the HTTP call when no decoder matches." \ No newline at end of file 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..145208a 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. @@ -1932,4 +1940,4 @@ After the final commit, verify the implementation against the spec. - [ ] **Final suite:** `just lint && just test` is green with 100% coverage. -- [ ] **Release notes:** Plan does NOT cover writing release notes — that's a separate ship step. Confirm `planning/releases/0.9.0.md` is created during the release flow, not here. +- [ ] **Release notes:** Plan does NOT cover writing release notes — that's a separate ship step. Confirm `planning/releases/0.9.0.md` is created during the release flow, not here. \ No newline at end of file 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..ec68cb6 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 @@ -291,4 +301,4 @@ If 0.9.0 has already been tagged by the time this lands, retag the patch as `0.9 The [[msgspec_basemodel_customtype_quirk]] memory's code reference (`src/httpware/decoders/msgspec.py:can_decode`) stays valid — the `type_info` + `CustomType` filter survives this refactor verbatim. No update needed there. -The [[multi_decoder_routing_shipped]] memory mentions "cached `msgspec.json.Decoder(model)`" in passing under headline changes — should be amended to "per-instance cached" if a future reader cares; minor. +The [[multi_decoder_routing_shipped]] memory mentions "cached `msgspec.json.Decoder(model)`" in passing under headline changes — should be amended to "per-instance cached" if a future reader cares; minor. \ No newline at end of file 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..909ee2b 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. @@ -511,4 +519,4 @@ After Task 2 lands, verify against the spec: - [ ] **Final suite:** `just lint && just test` green at 100% coverage after both tasks. -- [ ] **No regression in test count:** `tests/test_decoders_pydantic.py` and `tests/test_decoders_msgspec.py` should have the same number of test functions as before (one new test in msgspec to cover the now-instance-method TypeError fallback — see Task 2 Step 1; one removed autouse fixture from each file). +- [ ] **No regression in test count:** `tests/test_decoders_pydantic.py` and `tests/test_decoders_msgspec.py` should have the same number of test functions as before (one new test in msgspec to cover the now-instance-method TypeError fallback — see Task 2 Step 1; one removed autouse fixture from each file). \ No newline at end of file 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..a9af2e0 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 @@ -132,4 +142,4 @@ One chunk: ~600k–900k Sonnet (3 finders + ~3 verifiers × ~20–35 surviving c 1. `planning/audit/2026-06-12-delta-audit.md` exists, committed, with findings from all four dimensions (or an explicit "no findings survived" note per dimension). 2. Each finding carries title, `file:line`, claim, evidence quote, verifier consensus, suggested direction. 3. No file other than the audit doc and `workflow-delta.mjs` is created or modified by the run. -4. The user has reviewed the report as the audit deliverable. +4. The user has reviewed the report as the audit deliverable. \ No newline at end of file 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..c748012 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. @@ -530,4 +538,4 @@ If format gaps exist, fix the doc directly (formatting only — never alter find - [ ] **Step 3: Report to the user** -Post a summary: bucket counts, the headline finding, any dimension gaps or constraint violations, and a pointer to the audit doc. Findings are the deliverable — do not start fixing them. +Post a summary: bucket counts, the headline finding, any dimension gaps or constraint violations, and a pointer to the audit doc. Findings are the deliverable — do not start fixing them. \ No newline at end of file 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 97% 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..5e597db 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 @@ -126,4 +136,4 @@ All four behaviors get sync + async coverage where a client is involved (test #3 2. `MsgspecDecoder().can_decode(list[Struct])`, `can_decode(Struct)`, `can_decode(dict[str, int])` still return `True`. 3. A msgspec-only client with `response_model=list[PUser]` raises `MissingDecoderError` before any request is sent (transport handler not invoked). 4. `just lint` and `just test` pass; no `pydantic.py` changes; no public-API or protocol change. -5. The fix ships as a patch release (0.9.1) per the project's patch-for-bugfix convention. +5. The fix ships as a patch release (0.9.1) per the project's patch-for-bugfix convention. \ No newline at end of file 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..77ed942 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. @@ -312,4 +320,4 @@ Summarize: the fix, the two test layers (unit rejection/acceptance + sync/async **Placeholder scan:** none — every code/command step shows literal content. -**Type/name consistency:** `_contains_custom_type` referenced identically in Steps 3 and 4; test fixtures (`_Item`, `_PydanticUser`, `_DC`) and helpers (`_async_client_with_body`/`_sync_client_with_body` not needed — Task 2 builds clients inline to attach a `pytest.fail` handler, matching the existing `test_async_missing_decoder_when_none_claim` pattern) all match the real files read during planning. +**Type/name consistency:** `_contains_custom_type` referenced identically in Steps 3 and 4; test fixtures (`_Item`, `_PydanticUser`, `_DC`) and helpers (`_async_client_with_body`/`_sync_client_with_body` not needed — Task 2 builds clients inline to attach a `pytest.fail` handler, matching the existing `test_async_missing_decoder_when_none_claim` pattern) all match the real files read during planning. \ No newline at end of file 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..7e3bfba 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 @@ -306,4 +316,4 @@ TDD, 100% branch coverage enforced (`--cov-fail-under=100`). `httpx2.MockTranspo ## Open questions -None. All design decisions resolved in brainstorming (see "Resolved design decisions" above). +None. All design decisions resolved in brainstorming (see "Resolved design decisions" above). \ No newline at end of file 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..a7873bb 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. @@ -1547,4 +1555,4 @@ The branch is ready for `requesting-code-review` → `finishing-a-development-br - **Spec coverage:** CircuitOpenError (T1), AsyncTimeout incl. `cm.expired()` inner-vs-deadline (T2), breaker state machine + classification + concurrency + loop guard (T3/T4), events (asserted in T2–T4 via caplog), property invariant (T5), docs/ordering/release (T6). All spec sections map to a task. - **`test_observability.py`:** spec mentioned a centralized assertion "if asserted centrally" — there is none, so events are asserted in the feature files. Noted at the top of File Structure. - **Type/name consistency:** `_CircuitBreakerState` methods (`admit`, `on_success`, `on_failure`, `release_probe`, `is_failure_status`, `_open`, `_emit`) are referenced identically in both wrappers. Role constants `_ROLE_CLOSED`/`_ROLE_PROBE`. Logger `httpware.circuit_breaker`. Event names match the spec table exactly. -- **Shared-core deviation** from bulkhead/retry duplication is called out in Task 3 with rationale; preserves the three spec-required properties. +- **Shared-core deviation** from bulkhead/retry duplication is called out in Task 3 with rationale; preserves the three spec-required properties. \ No newline at end of file From cd443f2e20001d46c92f6e851ec81081ca219f20 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:20:20 +0300 Subject: [PATCH 08/13] chore(planning): normalize trailing newlines on migrated flat-cohort bundles The pre-migration planning/specs and planning/plans files lacked trailing newlines; eof-fixer . --check (run by just lint-ci) flags them. Normalized to match the rest of the tree. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../changes/archive/2026-06-06.01-modern-di-recipe/design.md | 2 +- planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md | 2 +- planning/changes/archive/2026-06-07.01-sync-client/design.md | 2 +- planning/changes/archive/2026-06-07.01-sync-client/plan.md | 2 +- planning/changes/archive/2026-06-07.02-decoder-error/design.md | 2 +- planning/changes/archive/2026-06-07.02-decoder-error/plan.md | 2 +- planning/changes/archive/2026-06-07.03-deep-audit/design.md | 2 +- planning/changes/archive/2026-06-07.03-deep-audit/plan.md | 2 +- .../changes/archive/2026-06-08.01-send-with-response/design.md | 2 +- .../changes/archive/2026-06-08.01-send-with-response/plan.md | 2 +- .../archive/2026-06-08.02-retry-budget-cluster/design.md | 2 +- .../changes/archive/2026-06-08.02-retry-budget-cluster/plan.md | 2 +- .../changes/archive/2026-06-08.03-post-080-doc-sweep/design.md | 2 +- .../changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md | 2 +- .../archive/2026-06-08.04-otel-partial-install/design.md | 2 +- .../changes/archive/2026-06-08.04-otel-partial-install/plan.md | 2 +- .../changes/archive/2026-06-08.05-small-fixes-mop-up/design.md | 2 +- .../changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md | 2 +- planning/changes/archive/2026-06-08.06-test-mop-up/design.md | 2 +- planning/changes/archive/2026-06-08.06-test-mop-up/plan.md | 2 +- .../archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md | 2 +- .../archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md | 2 +- .../changes/archive/2026-06-08.08-readme-link-cleanup/design.md | 2 +- .../changes/archive/2026-06-08.08-readme-link-cleanup/plan.md | 2 +- planning/changes/archive/2026-06-10.01-multi-decoder/design.md | 2 +- planning/changes/archive/2026-06-10.01-multi-decoder/plan.md | 2 +- .../archive/2026-06-10.02-decoder-instance-cache/design.md | 2 +- .../archive/2026-06-10.02-decoder-instance-cache/plan.md | 2 +- planning/changes/archive/2026-06-12.01-delta-audit/design.md | 2 +- planning/changes/archive/2026-06-12.01-delta-audit/plan.md | 2 +- .../2026-06-13.01-msgspec-nested-customtype-fix/design.md | 2 +- .../archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md | 2 +- .../archive/2026-06-13.02-circuit-breaker-and-timeout/design.md | 2 +- .../archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md | 2 +- 34 files changed, 34 insertions(+), 34 deletions(-) diff --git a/planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md b/planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md index d5a8d37..1fa2414 100644 --- a/planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md +++ b/planning/changes/archive/2026-06-06.01-modern-di-recipe/design.md @@ -282,4 +282,4 @@ Trade-off: deferring it to a separate PR (option C in the brainstorming) keeps t Trade-off: this error name and behavior are stable in `modern-di` as of the version of the source we inspected. If they change, the recipe gets a one-line edit. Worth the precision — vague language like "you'll get an error" is worse than naming the actual exception. **Risk: telling readers `lambda c: c.aclose()` does not work is a strong claim.** -Trade-off: the claim is correct (verified against `modern_di/providers/factory.py:27` — `is_async_finalizer = inspect.iscoroutinefunction(self.finalizer)`, and a lambda is not a coroutine function regardless of what it returns). Calling it out explicitly saves users from a confusing "coroutine was never awaited" warning and a connection pool that quietly leaks. Worth the precision. \ No newline at end of file +Trade-off: the claim is correct (verified against `modern_di/providers/factory.py:27` — `is_async_finalizer = inspect.iscoroutinefunction(self.finalizer)`, and a lambda is not a coroutine function regardless of what it returns). Calling it out explicitly saves users from a confusing "coroutine was never awaited" warning and a connection pool that quietly leaks. Worth the precision. diff --git a/planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md b/planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md index 81d38fd..7032619 100644 --- a/planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md +++ b/planning/changes/archive/2026-06-06.01-modern-di-recipe/plan.md @@ -617,4 +617,4 @@ This task produced no source changes — it was verification only. Move on to PR - **Type consistency:** `AsyncClient.aclose` is referenced identically in the source (Task 1 Step 5), the recipe sample finalizer (Task 2 Step 1), and the verification script (Task 3 Step 1). `UserApi`, `BillingApi` names match across recipe and verification script. -- **Naming:** test names follow the existing `test_aexit_*` pattern in `test_client_lifecycle.py` — `test_aclose_closes_owned_httpx2_client` and `test_aclose_is_idempotent_for_owned_client`. Spec used slightly looser names; plan tightened them to the existing convention. \ No newline at end of file +- **Naming:** test names follow the existing `test_aexit_*` pattern in `test_client_lifecycle.py` — `test_aclose_closes_owned_httpx2_client` and `test_aclose_is_idempotent_for_owned_client`. Spec used slightly looser names; plan tightened them to the existing convention. diff --git a/planning/changes/archive/2026-06-07.01-sync-client/design.md b/planning/changes/archive/2026-06-07.01-sync-client/design.md index d966ed7..1375941 100644 --- a/planning/changes/archive/2026-06-07.01-sync-client/design.md +++ b/planning/changes/archive/2026-06-07.01-sync-client/design.md @@ -592,4 +592,4 @@ A new file `planning/releases/0.8.0.md` (or `1.0.0.md`) drafts release notes cov - `planning/archive/specs/2026-06-05-retry-and-retry-budget-design.md` — async `Retry` + `RetryBudget` design; the sync versions mirror this (minus `attempt_timeout`) - `planning/archive/specs/2026-06-05-bulkhead-design.md` — async `Bulkhead` design; sync `Bulkhead` mirrors this with `threading.Semaphore` - `httpx` documentation — naming-convention reference (`Client` + `AsyncClient` at top level) -- `websockets` documentation — ecosystem precedent for async-primary + sync-secondary (the inverse of what we chose, considered and rejected) \ No newline at end of file +- `websockets` documentation — ecosystem precedent for async-primary + sync-secondary (the inverse of what we chose, considered and rejected) diff --git a/planning/changes/archive/2026-06-07.01-sync-client/plan.md b/planning/changes/archive/2026-06-07.01-sync-client/plan.md index e13674f..5be27ed 100644 --- a/planning/changes/archive/2026-06-07.01-sync-client/plan.md +++ b/planning/changes/archive/2026-06-07.01-sync-client/plan.md @@ -3530,4 +3530,4 @@ After completing all tasks, verify: - [ ] `100% line coverage` maintained - [ ] `just lint` clean - [ ] `import httpware` does not transitively import pydantic/msgspec/opentelemetry (Task B21 step 3) -- [ ] Both PRs merged before cutting the single release \ No newline at end of file +- [ ] Both PRs merged before cutting the single release diff --git a/planning/changes/archive/2026-06-07.02-decoder-error/design.md b/planning/changes/archive/2026-06-07.02-decoder-error/design.md index da4fd62..9366869 100644 --- a/planning/changes/archive/2026-06-07.02-decoder-error/design.md +++ b/planning/changes/archive/2026-06-07.02-decoder-error/design.md @@ -267,4 +267,4 @@ Release notes: - **Fix:** decoder exceptions from `response_model=` are now wrapped in a new `httpware.DecodeError` (a `ClientError` subclass), closing the gap where `pydantic.ValidationError` / `msgspec.ValidationError` / `msgspec.DecodeError` would escape `except httpware.ClientError`. - **New:** `httpware.DecodeError` — direct child of `ClientError`. Fields: `response`, `model`, `original`. -- **Behavior change:** consumers catching pydantic/msgspec exceptions directly need to switch to `except httpware.DecodeError` (or the broader `except httpware.ClientError`). No shim layer; the previously-leaking exceptions weren't a documented contract. \ No newline at end of file +- **Behavior change:** consumers catching pydantic/msgspec exceptions directly need to switch to `except httpware.DecodeError` (or the broader `except httpware.ClientError`). No shim layer; the previously-leaking exceptions weren't a documented contract. diff --git a/planning/changes/archive/2026-06-07.02-decoder-error/plan.md b/planning/changes/archive/2026-06-07.02-decoder-error/plan.md index fcfa1c1..c8aa13a 100644 --- a/planning/changes/archive/2026-06-07.02-decoder-error/plan.md +++ b/planning/changes/archive/2026-06-07.02-decoder-error/plan.md @@ -928,4 +928,4 @@ Expected: the new `DecodeError` class and the two `try/except` blocks in `client - [ ] **Step 6: Confirm release readiness** -The work is now ready to ship as `0.8.1`. No version bump happens in this plan — release-cutting is a separate manual step per the project's existing convention (bare-semver git tag, no CHANGELOG file, release notes on GitHub Releases). \ No newline at end of file +The work is now ready to ship as `0.8.1`. No version bump happens in this plan — release-cutting is a separate manual step per the project's existing convention (bare-semver git tag, no CHANGELOG file, release notes on GitHub Releases). diff --git a/planning/changes/archive/2026-06-07.03-deep-audit/design.md b/planning/changes/archive/2026-06-07.03-deep-audit/design.md index 6454685..bc4055e 100644 --- a/planning/changes/archive/2026-06-07.03-deep-audit/design.md +++ b/planning/changes/archive/2026-06-07.03-deep-audit/design.md @@ -291,4 +291,4 @@ The audit is complete when: 1. `planning/audit/2026-06-07-deep-audit.md` exists, contains findings from every dimension the user approved running, and is committed. 2. Each finding includes: title, file:line, claim, evidence quote, verifier consensus, suggested direction. 3. The summary header reflects final counts after cross-chunk dedup. -4. The user has reviewed the report and either approved it as the audit deliverable or requested specific re-runs (which trigger a second pass on those dimensions only). \ No newline at end of file +4. The user has reviewed the report and either approved it as the audit deliverable or requested specific re-runs (which trigger a second pass on those dimensions only). diff --git a/planning/changes/archive/2026-06-07.03-deep-audit/plan.md b/planning/changes/archive/2026-06-07.03-deep-audit/plan.md index 197b043..fbf5578 100644 --- a/planning/changes/archive/2026-06-07.03-deep-audit/plan.md +++ b/planning/changes/archive/2026-06-07.03-deep-audit/plan.md @@ -754,4 +754,4 @@ Done after writing the plan: - **Spec coverage:** Every section in the spec maps to a task. Discover → Task 4 step 3 / dry-run step 1. 8 dimensions → Task 2's `DIMENSION_PROMPTS`. Schemas → Task 2. Verifier panel → Task 2 `VERIFIER_PROMPTS`. 2-of-3 consensus → Task 2 script body. Synthesis → Task 2 `SYNTHESIS_PROMPT`. Severity buckets → enforced in synthesis prompt. Gate behavior → Tasks 4-7 each have a "Report and gate" step. Final merge → Task 8. Open questions resolved at top of plan. - **No placeholders:** All prompts written out verbatim. No "TBD" or "fill in." Schemas are concrete JSON Schema objects. -- **Type/name consistency:** `audit_file`, `discover_file`, `chunk_id`, `dimensions`, `run_discover` are used consistently across all tasks. Severity strings (`blocker|high|medium|low|nit`) match between spec and plan. \ No newline at end of file +- **Type/name consistency:** `audit_file`, `discover_file`, `chunk_id`, `dimensions`, `run_discover` are used consistently across all tasks. Severity strings (`blocker|high|medium|low|nit`) match between spec and plan. diff --git a/planning/changes/archive/2026-06-08.01-send-with-response/design.md b/planning/changes/archive/2026-06-08.01-send-with-response/design.md index c4e6b79..59dc578 100644 --- a/planning/changes/archive/2026-06-08.01-send-with-response/design.md +++ b/planning/changes/archive/2026-06-08.01-send-with-response/design.md @@ -222,4 +222,4 @@ Semver justification: a `0.x` minor would be defensible too (new public API), bu ## Out of scope, recorded for later - **Per-verb siblings** (`get_with_response`, etc.) — if a concrete httpware consumer beyond semvertag's `list_tags` surfaces a need, revisit. Until then, file under `planning/deferred-work.md` post-merge. -- **Pagination helper** (`paginate_links(client, request, response_model=...) -> Iterator[T]`) — tempting because Link-header pagination is the dominant `send_with_response` use case, but a generic pagination helper has to make choices (RFC 5988 only? cursor-style? page-number style?) that belong in the consuming library, not in `httpware`. Out of scope for the foreseeable future. \ No newline at end of file +- **Pagination helper** (`paginate_links(client, request, response_model=...) -> Iterator[T]`) — tempting because Link-header pagination is the dominant `send_with_response` use case, but a generic pagination helper has to make choices (RFC 5988 only? cursor-style? page-number style?) that belong in the consuming library, not in `httpware`. Out of scope for the foreseeable future. diff --git a/planning/changes/archive/2026-06-08.01-send-with-response/plan.md b/planning/changes/archive/2026-06-08.01-send-with-response/plan.md index d85ae0c..2c27fb0 100644 --- a/planning/changes/archive/2026-06-08.01-send-with-response/plan.md +++ b/planning/changes/archive/2026-06-08.01-send-with-response/plan.md @@ -666,4 +666,4 @@ Use `gh pr create` per the project's existing convention. Title: `feat: add send **Type consistency:** `send_with_response`, `response_model`, `DecodeError(response=, model=, original=)`, `_dispatch`, `_decoder.decode(response.content, response_model)`, `tuple[httpx2.Response, T]` — used identically across Tasks 1–4 and the spec. ✓ -**Placeholder scan:** no TBD/TODO. Every step has either concrete code or an exact command with expected output. \ No newline at end of file +**Placeholder scan:** no TBD/TODO. Every step has either concrete code or an exact command with expected output. diff --git a/planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md index c39b785..773d501 100644 --- a/planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md +++ b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/design.md @@ -295,4 +295,4 @@ None deferred. All behavioral choices are made (Retry-After: give up with PEP 67 3. New tests cover every finding's behavioral change. 4. `planning/releases/0.8.3.md` exists and structurally mirrors `planning/releases/0.8.1.md`. 5. PR opened against `main` with title `fix(retry-budget): close 7 audit findings (0.8.3)` and a body that summarizes each finding closed and the behavioral changes. -6. After user approval + merge + tag, [memory: release_0_8_3_shipped](../../.claude/projects/-Users-kevinsmith-src-pypi-httpware/memory/) is added. \ No newline at end of file +6. After user approval + merge + tag, [memory: release_0_8_3_shipped](../../.claude/projects/-Users-kevinsmith-src-pypi-httpware/memory/) is added. diff --git a/planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md index f394f64..02e139d 100644 --- a/planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md +++ b/planning/changes/archive/2026-06-08.02-retry-budget-cluster/plan.md @@ -1332,4 +1332,4 @@ Expected: 7 fix commits + spec commit on the branch (excluding the spec commit w - **Placeholder scan:** All before/after code blocks are verbatim with surrounding context. Test code is complete (no "similar to" or "add appropriate"). Commit-message HEREDOCs are filled in. - **Type/name consistency:** `_RETRY_AFTER_EXCEEDS_MAX_DELAY_NOTE` introduced in T3 is used consistently. `_CountingBudget` introduced in T2 is used in both async + sync test additions with the same shape. `is_closed` is the same attribute name in both T6 async + sync fixes. - **TDD ordering:** Every task that touches production code has a failing-test step before the fix. T1 tightens the assertion (`<=` → `==`) so the rewritten test fails against the unfixed production. T2-T6 each follow red-green-commit. -- **Test parity:** Every async test addition has a corresponding sync mirror step. Where sync helper names may differ (`_SleepRecorder` vs `_SleepRecorderSync`, etc.), the step explicitly says "read the file first to confirm". \ No newline at end of file +- **Test parity:** Every async test addition has a corresponding sync mirror step. Where sync helper names may differ (`_SleepRecorder` vs `_SleepRecorderSync`, etc.), the step explicitly says "read the file first to confirm". diff --git a/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md index eeef064..7b2f850 100644 --- a/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md +++ b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/design.md @@ -251,4 +251,4 @@ None deferred. Every change is specified concretely above. The plan will transla 1. All six per-file commits land on `main` with messages naming the audit findings they close. 2. `just lint-ci` is green after every commit and after the final one. 3. The post-sweep grep returns zero matches for every stale phrase listed in Verification. -4. The audit file itself is unchanged by this work — findings stay where they are; future readers can match each commit's message back to the audit by file:line. \ No newline at end of file +4. The audit file itself is unchanged by this work — findings stay where they are; future readers can match each commit's message back to the audit by file:line. diff --git a/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md index 0926ef9..f3bdda0 100644 --- a/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md +++ b/planning/changes/archive/2026-06-08.03-post-080-doc-sweep/plan.md @@ -662,4 +662,4 @@ Report to the user: 6 commits ready, every audit doc-staleness finding from the - **Spec coverage:** Every change in the spec's "Per-file change list" maps to a task. CLAUDE.md → Task 1; README.md → Task 2; docs/index.md → Task 3; docs/resilience.md → Task 4; planning/engineering.md → Task 5; decoders/__init__.py → Task 6. Verification → Task 7. - **Placeholder scan:** All before/after strings are verbatim. No "TBD" / "TODO" / "similar to Task N" patterns. Each task's grep commands and expected outputs are concrete. - **Type/name consistency:** "Seam A/B/C" used consistently from Task 1 (CLAUDE.md introduces them) through Task 6 (decoders picks them up). The `Retry`/`AsyncRetry` and `Bulkhead`/`AsyncBulkhead` pairings are formatted identically in Tasks 2, 3, and 4. -- **Order dependency:** Task 6 depends on Task 1 (decoders docstring uses the Seam-B label CLAUDE.md establishes). Task 7 depends on Tasks 1-6. Tasks 2, 3, 4, 5 are mutually independent and could in principle run in parallel, but the plan executes sequentially to keep the diff stream readable. \ No newline at end of file +- **Order dependency:** Task 6 depends on Task 1 (decoders docstring uses the Seam-B label CLAUDE.md establishes). Task 7 depends on Tasks 1-6. Tasks 2, 3, 4, 5 are mutually independent and could in principle run in parallel, but the plan executes sequentially to keep the diff stream readable. diff --git a/planning/changes/archive/2026-06-08.04-otel-partial-install/design.md b/planning/changes/archive/2026-06-08.04-otel-partial-install/design.md index e8f808b..9202233 100644 --- a/planning/changes/archive/2026-06-08.04-otel-partial-install/design.md +++ b/planning/changes/archive/2026-06-08.04-otel-partial-install/design.md @@ -218,4 +218,4 @@ uv run pytest -x --no-cov -q ## Open questions -None. Both fixes are precisely specified by the audit; the tests are straightforward; the release-notes shape is established. \ No newline at end of file +None. Both fixes are precisely specified by the audit; the tests are straightforward; the release-notes shape is established. diff --git a/planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md b/planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md index f02cd94..872b21f 100644 --- a/planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md +++ b/planning/changes/archive/2026-06-08.04-otel-partial-install/plan.md @@ -505,4 +505,4 @@ Report the PR URL. - **Spec coverage:** Spec finding #1 = T1 (import_checker probe target). Spec finding #2 = T2 (try/except wrap + docstring extension). Spec tests section = T1 + T2 test steps. Spec release notes section = T3. PR opening = T3 Step 5. - **Placeholder scan:** All code blocks are complete with verbatim old_string / new_string. Test bodies are complete. Commit messages are filled in. No "TBD" / "similar to". - **Type/name consistency:** `_TEST_LOGGER` is reused from the existing file in Task 2 (the test file already defines it). The `_BrokenOpenTelemetry` class is local to one test. `find_spec("opentelemetry.trace")` is the exact same string in source and test source-check. -- **TDD ordering:** T1 and T2 each follow red-green-commit. T1's red is a source-level assertion (the live `find_spec` calls pass either way under `--all-extras`, so source-pinning is the regression guard). T2's red is a real runtime ImportError out of `_emit_event`. \ No newline at end of file +- **TDD ordering:** T1 and T2 each follow red-green-commit. T1's red is a source-level assertion (the live `find_spec` calls pass either way under `--all-extras`, so source-pinning is the regression guard). T2's red is a real runtime ImportError out of `_emit_event`. diff --git a/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md index 757a2a5..c54e8bf 100644 --- a/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md +++ b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/design.md @@ -294,4 +294,4 @@ Full suite + lint green after every commit. ## Open questions -None. All four fixes are precisely specified and the audit's recommended directions hold. \ No newline at end of file +None. All four fixes are precisely specified and the audit's recommended directions hold. diff --git a/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md index c24d5a8..1f4e56e 100644 --- a/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md +++ b/planning/changes/archive/2026-06-08.05-small-fixes-mop-up/plan.md @@ -712,4 +712,4 @@ Return DONE with the PR URL. - **Placeholder scan:** All before/after strings are verbatim. Test bodies complete. Commit messages filled in. No "TBD" / "similar to". - **Type/name consistency:** `_LOGGER` lowercase is used in the LoggingMiddleware example; the existing `RequestIdMiddleware` further down the file uses the same lowercase convention (verified during spec design). The `actual` variable in Task 4's symmetric assertion is reused in the error message. - **TDD ordering:** T1 has a clean red→green sequence with two tests. T2 is a contract-narrowing change verified by existing tests (no new test needed — see spec §Tests). T3 is docs-only. T4 is a test-quality change; manual verification step confirms the new symmetric failure mode is real. -- **Order dependency:** None. T1-T4 are independent; T5 depends on T1-T4 all landing. The branch sequences them in order for diff readability. \ No newline at end of file +- **Order dependency:** None. T1-T4 are independent; T5 depends on T1-T4 all landing. The branch sequences them in order for diff readability. diff --git a/planning/changes/archive/2026-06-08.06-test-mop-up/design.md b/planning/changes/archive/2026-06-08.06-test-mop-up/design.md index 757abbd..2bdc648 100644 --- a/planning/changes/archive/2026-06-08.06-test-mop-up/design.md +++ b/planning/changes/archive/2026-06-08.06-test-mop-up/design.md @@ -403,4 +403,4 @@ Full suite + lint green after every commit. ## Open questions -None. All five fixes are precisely specified. The finding excluded as INVALID is documented in scope-note above. \ No newline at end of file +None. All five fixes are precisely specified. The finding excluded as INVALID is documented in scope-note above. diff --git a/planning/changes/archive/2026-06-08.06-test-mop-up/plan.md b/planning/changes/archive/2026-06-08.06-test-mop-up/plan.md index ce6cb40..e61fa47 100644 --- a/planning/changes/archive/2026-06-08.06-test-mop-up/plan.md +++ b/planning/changes/archive/2026-06-08.06-test-mop-up/plan.md @@ -815,4 +815,4 @@ Return DONE with the PR URL. - **Placeholder scan:** All code blocks complete with verbatim test bodies. Commit messages filled. No "TBD" / "similar to". - **Type/name consistency:** `_FakeDecoder` defined at module level in T3 is used in both async and sync tests (consistent). `_InFlightHandler` in T4's new file mirrors the async file's class name (intentional). The shared counter pattern uses `_lock` consistently. - **TDD ordering:** T1-T3 each have a verify-state step before the fix; T4 is a new-file addition with the existing async file as the reference template; T5 appends to an existing file. All tasks have "run the test" steps that establish PASS as the success criterion. -- **Risks:** T4 (threading + ThreadPoolExecutor + MockTransport) is the highest-flake risk. The plan includes escalation guidance for the implementer: drop `max_examples` to 10, then DONE_WITH_CONCERNS if still flaky. \ No newline at end of file +- **Risks:** T4 (threading + ThreadPoolExecutor + MockTransport) is the highest-flake risk. The plan includes escalation guidance for the implementer: drop `max_examples` to 10, then DONE_WITH_CONCERNS if still flaky. diff --git a/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md index 6138a0f..3f8b51a 100644 --- a/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md +++ b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/design.md @@ -123,4 +123,4 @@ If any step fails, the rollback is to revert the PR — RTD remains live and una ## Scope check -This is a single-PR structural change. No decomposition needed. Aligns with the stated preference: clean cutover landing as one structural PR before any substantive follow-up work. \ No newline at end of file +This is a single-PR structural change. No decomposition needed. Aligns with the stated preference: clean cutover landing as one structural PR before any substantive follow-up work. diff --git a/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md index ddb3bc4..fc9f7aa 100644 --- a/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md +++ b/planning/changes/archive/2026-06-08.07-mkdocs-gh-pages-migration/plan.md @@ -625,4 +625,4 @@ These steps happen on `main` after the PR merges, in coordination with the repo 4. After admin sets Pages source + DNS resolves, load `https://httpware.modern-python.org/` and verify content matches the previous RTD site. 5. Push a trivial docs edit in a follow-up commit; confirm the workflow re-triggers and the live site updates within ~2 minutes. -If any post-merge step fails, the rollback is `git revert` of the merge commit. ReadTheDocs remains live and unaltered until manually archived, so reverting cleanly restores the prior state. \ No newline at end of file +If any post-merge step fails, the rollback is `git revert` of the merge commit. ReadTheDocs remains live and unaltered until manually archived, so reverting cleanly restores the prior state. diff --git a/planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md index a27d6bf..b1a06d3 100644 --- a/planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md +++ b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/design.md @@ -95,4 +95,4 @@ Near-zero. All changes are text-only in markdown files. No code changes. No CI c ## Scope check -Single-PR, low-risk change. Three line-edits + a one-shot script run. No decomposition needed. \ No newline at end of file +Single-PR, low-risk change. Three line-edits + a one-shot script run. No decomposition needed. diff --git a/planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md index 032c94c..c49fa25 100644 --- a/planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md +++ b/planning/changes/archive/2026-06-08.08-readme-link-cleanup/plan.md @@ -428,4 +428,4 @@ If files outside this list show up, STOP and investigate before proceeding. ## Post-PR - Wait for review and merge. -- After merge, `gh-pages` is unaffected (no `docs/` content changed in the README-only path), so no docs redeploy is triggered. If Task 7 modified `docs/`, the `Deploy Docs` workflow re-runs on `main` push. \ No newline at end of file +- After merge, `gh-pages` is unaffected (no `docs/` content changed in the README-only path), so no docs redeploy is triggered. If Task 7 modified `docs/`, the `Deploy Docs` workflow re-runs on `main` push. diff --git a/planning/changes/archive/2026-06-10.01-multi-decoder/design.md b/planning/changes/archive/2026-06-10.01-multi-decoder/design.md index 13e1fb9..8c1967f 100644 --- a/planning/changes/archive/2026-06-10.01-multi-decoder/design.md +++ b/planning/changes/archive/2026-06-10.01-multi-decoder/design.md @@ -402,4 +402,4 @@ Tag and GitHub Release notes follow the existing bare-semver tag convention ([re **Engineering doc update** — `planning/engineering.md` Seam B description is updated: - Old: "Called when `response_model` is provided. Signature: `decode(content: bytes, model: type[T]) -> T`." -- New: "Implementations expose `can_decode(model) -> bool` (dispatch predicate) and `decode(content, model) -> T` (the decode). The client holds a tuple `_decoders` and walks it in order on every `response_model=` use; first matching decoder wins. `MissingDecoderError` fires before the HTTP call when no decoder matches." \ No newline at end of file +- New: "Implementations expose `can_decode(model) -> bool` (dispatch predicate) and `decode(content, model) -> T` (the decode). The client holds a tuple `_decoders` and walks it in order on every `response_model=` use; first matching decoder wins. `MissingDecoderError` fires before the HTTP call when no decoder matches." diff --git a/planning/changes/archive/2026-06-10.01-multi-decoder/plan.md b/planning/changes/archive/2026-06-10.01-multi-decoder/plan.md index 145208a..bfa206c 100644 --- a/planning/changes/archive/2026-06-10.01-multi-decoder/plan.md +++ b/planning/changes/archive/2026-06-10.01-multi-decoder/plan.md @@ -1940,4 +1940,4 @@ After the final commit, verify the implementation against the spec. - [ ] **Final suite:** `just lint && just test` is green with 100% coverage. -- [ ] **Release notes:** Plan does NOT cover writing release notes — that's a separate ship step. Confirm `planning/releases/0.9.0.md` is created during the release flow, not here. \ No newline at end of file +- [ ] **Release notes:** Plan does NOT cover writing release notes — that's a separate ship step. Confirm `planning/releases/0.9.0.md` is created during the release flow, not here. diff --git a/planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md index ec68cb6..c2ee541 100644 --- a/planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md +++ b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/design.md @@ -301,4 +301,4 @@ If 0.9.0 has already been tagged by the time this lands, retag the patch as `0.9 The [[msgspec_basemodel_customtype_quirk]] memory's code reference (`src/httpware/decoders/msgspec.py:can_decode`) stays valid — the `type_info` + `CustomType` filter survives this refactor verbatim. No update needed there. -The [[multi_decoder_routing_shipped]] memory mentions "cached `msgspec.json.Decoder(model)`" in passing under headline changes — should be amended to "per-instance cached" if a future reader cares; minor. \ No newline at end of file +The [[multi_decoder_routing_shipped]] memory mentions "cached `msgspec.json.Decoder(model)`" in passing under headline changes — should be amended to "per-instance cached" if a future reader cares; minor. diff --git a/planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md index 909ee2b..08567e4 100644 --- a/planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md +++ b/planning/changes/archive/2026-06-10.02-decoder-instance-cache/plan.md @@ -519,4 +519,4 @@ After Task 2 lands, verify against the spec: - [ ] **Final suite:** `just lint && just test` green at 100% coverage after both tasks. -- [ ] **No regression in test count:** `tests/test_decoders_pydantic.py` and `tests/test_decoders_msgspec.py` should have the same number of test functions as before (one new test in msgspec to cover the now-instance-method TypeError fallback — see Task 2 Step 1; one removed autouse fixture from each file). \ No newline at end of file +- [ ] **No regression in test count:** `tests/test_decoders_pydantic.py` and `tests/test_decoders_msgspec.py` should have the same number of test functions as before (one new test in msgspec to cover the now-instance-method TypeError fallback — see Task 2 Step 1; one removed autouse fixture from each file). diff --git a/planning/changes/archive/2026-06-12.01-delta-audit/design.md b/planning/changes/archive/2026-06-12.01-delta-audit/design.md index a9af2e0..dc62c0c 100644 --- a/planning/changes/archive/2026-06-12.01-delta-audit/design.md +++ b/planning/changes/archive/2026-06-12.01-delta-audit/design.md @@ -142,4 +142,4 @@ One chunk: ~600k–900k Sonnet (3 finders + ~3 verifiers × ~20–35 surviving c 1. `planning/audit/2026-06-12-delta-audit.md` exists, committed, with findings from all four dimensions (or an explicit "no findings survived" note per dimension). 2. Each finding carries title, `file:line`, claim, evidence quote, verifier consensus, suggested direction. 3. No file other than the audit doc and `workflow-delta.mjs` is created or modified by the run. -4. The user has reviewed the report as the audit deliverable. \ No newline at end of file +4. The user has reviewed the report as the audit deliverable. diff --git a/planning/changes/archive/2026-06-12.01-delta-audit/plan.md b/planning/changes/archive/2026-06-12.01-delta-audit/plan.md index c748012..69659fb 100644 --- a/planning/changes/archive/2026-06-12.01-delta-audit/plan.md +++ b/planning/changes/archive/2026-06-12.01-delta-audit/plan.md @@ -538,4 +538,4 @@ If format gaps exist, fix the doc directly (formatting only — never alter find - [ ] **Step 3: Report to the user** -Post a summary: bucket counts, the headline finding, any dimension gaps or constraint violations, and a pointer to the audit doc. Findings are the deliverable — do not start fixing them. \ No newline at end of file +Post a summary: bucket counts, the headline finding, any dimension gaps or constraint violations, and a pointer to the audit doc. Findings are the deliverable — do not start fixing them. diff --git a/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md index 5e597db..5e02c17 100644 --- a/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md +++ b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/design.md @@ -136,4 +136,4 @@ All four behaviors get sync + async coverage where a client is involved (test #3 2. `MsgspecDecoder().can_decode(list[Struct])`, `can_decode(Struct)`, `can_decode(dict[str, int])` still return `True`. 3. A msgspec-only client with `response_model=list[PUser]` raises `MissingDecoderError` before any request is sent (transport handler not invoked). 4. `just lint` and `just test` pass; no `pydantic.py` changes; no public-API or protocol change. -5. The fix ships as a patch release (0.9.1) per the project's patch-for-bugfix convention. \ No newline at end of file +5. The fix ships as a patch release (0.9.1) per the project's patch-for-bugfix convention. diff --git a/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md index 77ed942..727ce00 100644 --- a/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md +++ b/planning/changes/archive/2026-06-13.01-msgspec-nested-customtype-fix/plan.md @@ -320,4 +320,4 @@ Summarize: the fix, the two test layers (unit rejection/acceptance + sync/async **Placeholder scan:** none — every code/command step shows literal content. -**Type/name consistency:** `_contains_custom_type` referenced identically in Steps 3 and 4; test fixtures (`_Item`, `_PydanticUser`, `_DC`) and helpers (`_async_client_with_body`/`_sync_client_with_body` not needed — Task 2 builds clients inline to attach a `pytest.fail` handler, matching the existing `test_async_missing_decoder_when_none_claim` pattern) all match the real files read during planning. \ No newline at end of file +**Type/name consistency:** `_contains_custom_type` referenced identically in Steps 3 and 4; test fixtures (`_Item`, `_PydanticUser`, `_DC`) and helpers (`_async_client_with_body`/`_sync_client_with_body` not needed — Task 2 builds clients inline to attach a `pytest.fail` handler, matching the existing `test_async_missing_decoder_when_none_claim` pattern) all match the real files read during planning. diff --git a/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md index 7e3bfba..6b80516 100644 --- a/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md +++ b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/design.md @@ -316,4 +316,4 @@ TDD, 100% branch coverage enforced (`--cov-fail-under=100`). `httpx2.MockTranspo ## Open questions -None. All design decisions resolved in brainstorming (see "Resolved design decisions" above). \ No newline at end of file +None. All design decisions resolved in brainstorming (see "Resolved design decisions" above). diff --git a/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md index a7873bb..4e14bbb 100644 --- a/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md +++ b/planning/changes/archive/2026-06-13.02-circuit-breaker-and-timeout/plan.md @@ -1555,4 +1555,4 @@ The branch is ready for `requesting-code-review` → `finishing-a-development-br - **Spec coverage:** CircuitOpenError (T1), AsyncTimeout incl. `cm.expired()` inner-vs-deadline (T2), breaker state machine + classification + concurrency + loop guard (T3/T4), events (asserted in T2–T4 via caplog), property invariant (T5), docs/ordering/release (T6). All spec sections map to a task. - **`test_observability.py`:** spec mentioned a centralized assertion "if asserted centrally" — there is none, so events are asserted in the feature files. Noted at the top of File Structure. - **Type/name consistency:** `_CircuitBreakerState` methods (`admit`, `on_success`, `on_failure`, `release_probe`, `is_failure_status`, `_open`, `_emit`) are referenced identically in both wrappers. Role constants `_ROLE_CLOSED`/`_ROLE_PROBE`. Logger `httpware.circuit_breaker`. Event names match the spec table exactly. -- **Shared-core deviation** from bulkhead/retry duplication is called out in Task 3 with rationale; preserves the three spec-required properties. \ No newline at end of file +- **Shared-core deviation** from bulkhead/retry duplication is called out in Task 3 with rationale; preserves the three spec-required properties. From cabec474c2fa8df8fbd02b5a9625d9cb7984217d Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:21:24 +0300 Subject: [PATCH 09/13] docs(planning): move audit reports to audits/, tooling to audits/scripts/ Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/{audit => audits}/2026-06-07-deep-audit.md | 0 planning/{audit => audits}/2026-06-12-delta-audit.md | 0 planning/{audit => audits}/2026-06-13-delta-audit.md | 0 planning/{audit => audits/scripts}/_discover.json | 0 planning/{audit => audits/scripts}/workflow-delta.mjs | 0 planning/{audit => audits/scripts}/workflow.mjs | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename planning/{audit => audits}/2026-06-07-deep-audit.md (100%) rename planning/{audit => audits}/2026-06-12-delta-audit.md (100%) rename planning/{audit => audits}/2026-06-13-delta-audit.md (100%) rename planning/{audit => audits/scripts}/_discover.json (100%) rename planning/{audit => audits/scripts}/workflow-delta.mjs (100%) rename planning/{audit => audits/scripts}/workflow.mjs (100%) 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 From f045ccde645d6734508984d6452055f24effd3d4 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:26:30 +0300 Subject: [PATCH 10/13] docs(planning): add README with portable Conventions + repo Index Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/README.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 planning/README.md diff --git a/planning/README.md b/planning/README.md new file mode 100644 index 0000000..f713e61 --- /dev/null +++ b/planning/README.md @@ -0,0 +1,128 @@ +# 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. From 5c0297d7c7ba5ce7b9a2802a6c2bf7c91886d514 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:28:24 +0300 Subject: [PATCH 11/13] docs(planning): restore blank line before README Index heading Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/README.md b/planning/README.md index f713e61..41bacd8 100644 --- a/planning/README.md +++ b/planning/README.md @@ -65,6 +65,7 @@ Templates live in [`_templates/`](_templates/). `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 From 9aecad0e3a21a2580ee32405dce56a3b1bde84e6 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:36:27 +0300 Subject: [PATCH 12/13] docs: repoint inbound links to architecture/ and changes/ bundles Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 20 +++++++++++--------- docs/errors.md | 2 +- docs/index.md | 2 +- docs/middleware.md | 4 ++-- docs/resilience.md | 2 +- docs/testing.md | 2 +- planning/releases/0.10.1.md | 2 +- planning/releases/0.2.0.md | 2 +- planning/releases/0.3.0.md | 2 +- planning/releases/0.4.0.md | 8 +++----- planning/releases/0.5.0.md | 5 ++--- planning/releases/0.6.0.md | 5 ++--- planning/releases/0.7.0.md | 8 +++----- planning/releases/0.8.0.md | 7 +++---- planning/releases/0.8.1.md | 5 ++--- planning/releases/0.8.3.md | 4 ++-- planning/releases/0.8.4.md | 2 +- planning/releases/0.9.0.md | 8 +++----- 18 files changed, 41 insertions(+), 49 deletions(-) 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/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/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..6f1838e 100644 --- a/planning/releases/0.6.0.md +++ b/planning/releases/0.6.0.md @@ -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) From d30c71d866182d995ad5386d08ac197b3be9caca Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 13 Jun 2026 20:49:53 +0300 Subject: [PATCH 13/13] docs(planning): repoint two stray bare engineering.md refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deferred.md had a now-broken markdown link to engineering.md §8 (roadmap, dissolved); 0.6.0 release note had a bare engineering.md §2 prose ref. Both missed in the link-repoint pass because they lacked the planning/ prefix. Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/deferred.md | 2 +- planning/releases/0.6.0.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/planning/deferred.md b/planning/deferred.md index 87fbcf8..a54caad 100644 --- a/planning/deferred.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/releases/0.6.0.md b/planning/releases/0.6.0.md index 6f1838e..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