Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/mutation-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,14 @@ Focused tests (also listed in `[tool.mutmut].pytest_add_cli_args_test_selection`
| Timeout | 3 |
| **Score** | **1061 / 2091 = 50.7%** |

Per-module, derived from the mutant sources and survivor list (these reconcile
to the 50.7% total, so they are measured rather than estimated):
Per-module, derived from the mutant sources and survivor list. **These rows do
not reconcile to the headline totals and should not be read as if they do:** the
seven rows below are the whole `only_mutate` scope, yet they sum to **2010**
mutants (990 survived + 1020 killed) against a headline of **2091** (1027 + 1061
+ 3 timeouts). **81 mutants — 37 survived, 41 killed, 3 timeout — are
unattributed.** Per-module scores are therefore measured but incomplete; the
headline 50.7% is the number to quote. Re-derive the split before using any row
as a target.

| Module | Total | Survived | Killed | Score | vs 2026-07-09 |
|--------|------:|---------:|-------:|------:|--------------:|
Expand Down Expand Up @@ -125,7 +131,7 @@ Enforced by `./scripts/run_mutation_baseline.sh` after `mutmut export-cicd-stats
Categories (not an exhaustive dump of the 1027 survivors):

1. **Equivalent / cosmetic** — error-message string literals, log format, `getattr` default when tests always set the attribute, `ZipFile(..., "r")` vs default mode.
2. **`_finalize_slice` (output.py)** — subprocess exit interpretation, JSON emit, path display. **Addressed (C.4):** `tests/fakes/orca_stub` + `tests/test_slice_stub_integration.py` now run these branches against a real fake-slicer subprocess. Residual survivors here should be cosmetic (log strings / path display); re-measure before treating any as "accepted".
2. **`_finalize_slice` (output.py)** — subprocess exit interpretation, JSON emit, path display. **Addressed (C.4), but the prediction failed:** `tests/fakes/orca_stub` + `tests/test_slice_stub_integration.py` run these branches against a real fake-slicer subprocess, and the re-run still scored **21.8%** — **269 survivors, the single largest pocket (26% of all survivors)**. They are *not* cosmetic: the bulk are unconstrained `_finalize_slice` exit-code interpretation, JSON emit, and path display (see the C.4 correction above). Extract those decisions into assertable units or stop counting the module — do not treat these as accepted.
3. **DNS cache / hop bookkeeping (netsafety)** — TTL, cache size clear, attribute names on redirect requests. Core `is_global` refuse path is well killed.
4. **URL normalize / Content-Disposition edges (validation/naming)** — ambiguous scheme-less inputs and RFC2231 header tuples; behavior partially covered; full combinatorial matrix deferred.
5. **Dry-run prediction (predict.py)** — Printables/archive/extension branches that return `None` early; many mutants are observationally equivalent under the focused suite.
Expand Down Expand Up @@ -154,4 +160,4 @@ Artifacts (`mutants/`, `.mutmut-cache`, `.hypothesis/`) are gitignored.

- mutmut 3.x needs Python ≥ 3.10 (CI mutation job uses 3.12).
- Hypothesis property tests live in `tests/test_properties_safety.py` and are part of the focused mutmut suite.
- Raising the score further: the hermetic `_finalize_slice` tests now exist (C.4, `tests/test_slice_stub_integration.py`); re-run `mutmut` on `slicer/output.py` and update the per-module row. Optionally still move pure 3mf validation to a tiny module so mutmut does not spend budget on I/O.
- Raising the score further: the `slicer/output.py` re-run is **done** (2026-08-04, 21.8% — the row above is current); adding hermetic tests moved line coverage but not the mutation score, so the next lever is extracting `_finalize_slice`'s decisions into assertable units, or dropping the module from `only_mutate` rather than carrying a 21.8% row. Separately, attribute the 81 unaccounted mutants (see §Measured 2026-08-04) before trusting per-module targets. Optionally still move pure 3mf validation to a tiny module so mutmut does not spend budget on I/O.
14 changes: 8 additions & 6 deletions docs/plans/a-plus-gameplan.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Gameplan: A+ across the board

**Date:** 2026-08-13
**Baseline (this checkout, dirty tree, Linux py3.12):** 1459 passed / 1 live deselected, **89.4%** branch over 8413 statements. Current scoreboard: overall **A**, none below A−. Product **A−**. A+ is not earned.
**Current measured (2026-08-14, post-#119/#120):** **1499** passed / 1 live deselected, **90.99%** branch on Linux (**90.68%** Windows — the binding leg, macOS passing) over **8368** statements, CI floor **86**. Scoreboard: overall **A**, none below A−. Product **A−**. **A+ is not earned.**

**Progress 2026-08-13:** W1 moved coverage **89.4 → 91.0%** (1499 passed, 8368 stmts) via transport/session/camera/ftps tests + dead `abort` tails after `emit_json_error`. W2/W3 landed (residual acceptance, facade freeze, scoreboard refresh). **Did not raise the CI floor** (91.0 < 92.5 margin). **Not A+ across the board.** Remaining: 91.0→92, floor 92, `mypy --strict`, `v1.0.0` tag.
*Historical baseline (2026-08-13, dirty tree, Linux py3.12): 1459 passed, 89.4% over 8413 statements — superseded by the line above; kept because the W1 sizing below was computed from it.*

**Progress 2026-08-13:** W1 moved coverage **89.4 → 90.99%** (1499 passed, 8368 stmts) via transport/session/camera/ftps tests + dead `abort` tails after `emit_json_error`. W2/W3 landed (residual acceptance, facade freeze, scoreboard refresh). **Did not raise the CI floor** (90.99 < the 92.5 margin W4 requires). **Not A+ across the board.** Remaining: 90.99→92.5, floor 86→92, `mypy --strict`, `v1.0.0` tag.

This is an execution plan, not another audit. Truth sources stay [quality-roadmap.md](../quality-roadmap.md) §2 / §3.1 / §5 and [test-backlog.md](../test-backlog.md).

Expand All @@ -29,7 +31,7 @@ A+ across the board means **all three** of:
| Error model | A | **A+** | Already entry-only `sys.exit`. |
| Tests | A | **A+** | **92%** total on Linux. Per-module A+ floors only for §5's four. |
| CI / release | A | **A+** | Raise `--cov-fail-under` to **92** only if Linux ≥ **92.5%** (Windows last trailed ~0.3 pt). |
| Docs | A | **A+** | Refresh 1448/88.9%/8419; fix stale camera-TCP sentence; accept residuals. |
| Docs | A | **A+** | Number refresh **done** (#120: 1499/90.99%/8368) and camera/`--confirm`/doctor accuracy **done** (#121/#122). Remaining: field-level `api.md` sync. |
| Product | A− | **A−** until tag | Classifier + changelog can be prepared. **Do not tag `v1.0.0` without an explicit user “tag it”.** |

**Consequence:** this session can make every *unblocked* row A+ and leave Typing A + Product A−. That is **not** “A+ across the board.” Do not write that phrase into the scoreboard until the tag exists and Typing is either strict or the A+ definition is deliberately changed in a separate, reviewed docs PR.
Expand All @@ -38,7 +40,7 @@ A+ across the board means **all three** of:

### W1 — Coverage to ≥92.5% Linux (Tests A+)

Need ~280 extra statement/branch hits vs 89.4%. Hit the fattest *decision* holes first; do not pragma I/O loops just to move the number.
Sized when coverage was 89.4%; **as of 2026-08-14 Linux is 90.99%**, so the remaining gap to 92.5% is roughly **~125** statement/branch hits, not the ~280 originally scoped. The per-module misses below are pre-W1 and were not re-measured — re-run `--cov-report=term-missing` before picking targets. Hit the fattest *decision* holes first; do not pragma I/O loops just to move the number.

| Priority | Module | Last miss | Approach |
|----------|--------|-----------|----------|
Expand All @@ -54,8 +56,8 @@ After W1: remeasure with `pytest -m "not live" --cov=bambu_cli`. Do not raise th

### W2 — Docs truth + residual acceptance (Docs A+, Security A+)

- Scoreboard / backlog: **1459** passing, **89.4%→measured**, **8413** stmts, **27** schemas, floor still 86 until W4.
- Delete the stale “TCP failure on 6000 still falls back to the streamer” sentence.
- Scoreboard / backlog: **done** in #120 — **1499** passing, **90.99%** Linux / **90.68%** Windows, **8368** stmts, floor still 86 until W4. (Schema count **27** was never re-verified; confirm before quoting.)
- Delete the stale “TCP failure on 6000 still falls back to the streamer” sentence. **Done** in #121/#122.
- SECURITY.md: mark camera streamer / `insecure_tls` / leftover container / HTTP integrity / TOFU / Windows ACLs as **accepted 1.0 residuals** (not open P0s). That is what “close or explicitly accept” means.

### W3 — Architecture cheap A+
Expand Down
61 changes: 52 additions & 9 deletions docs/plans/post-audit-gameplan.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Gameplan: post-audit hardening + `feat/tui` merge prep

**Audience:** coding agents (Claude / Grok / etc.) executing work in this repo.
**Source:** 2026-07-31 deep read-only audit (parent session + 3× explore agents on Grok 4.5).
**Branch at audit:** `feat/tui` @ `0d63378` (12 commits ahead of `main`).
**Measured suite (Linux, that session):** `1314 passed`, `1 deselected` (live), **88.6%** branch coverage.
**Version:** `0.5.0.dev0` — pre-1.0 Beta.
**Audience:** coding agents executing work in this repo.
**Source:** 2026-07-31 deep read-only audit (one parent pass + 3× parallel exploration passes).
**Branch at audit:** `feat/tui` @ `0d63378` (12 commits ahead of `main`) — **since merged** (#97, #104).
**Measured suite at audit (Linux, that session):** `1314 passed`, `1 deselected` (live), **88.6%** branch coverage.
**Version at audit:** `0.5.0.dev0`.

> **Every number and file path above is a 2026-07-31 snapshot, not current state.**
> Current: **1499** non-live tests, **90.99%** Linux / **90.68%** Windows over **8368**
> statements, CI floor **86**, version **0.5.0** (Beta), Python **3.10/3.12/3.14**
> (3.9 dropped in #115). `protocols/mqtt.py` is no longer an ~885 LOC hotspot — it
> was split into `mqtt_tls` / `mqtt_cmd` / `mqtt_print` / `mqtt_monitor` /
> `mqtt_session` in #119. Camera code lives under `protocols/`, not
> `bambu_cli/camera.py`. **See the re-verified status block below the residual
> table before acting on any row.**

> **Revision 2026-07-31 (cross-check pass).** A second independent audit re-verified this plan's premises and found the plan **missing a confirmed merge blocker** plus eight other findings; the new items are folded into the residual table and into **WS-B** below. S1/S2/S3 were re-verified in code and hold — **S2 is a genuine catch this plan surfaced that the other audit missed**. **Q2's diagnosis was wrong and is corrected below.** Post-fix suite is `1321 passed`, **88.58%**.

Expand All @@ -22,7 +31,7 @@ Copy these into every work session:
4. **`sys.exit` only in `bambu_cli/cli.py`.** Domain raises `BambuError` / `abort`. CI greps this.
5. **No `@mockable`**, no `isinstance(..., Mock)` / test-awareness branches in production.
6. **Do not** hand-maintain package / py_compile / help-command lists (setuptools + smokes auto-discover).
7. **Do not** add Claude-Session or similar trailers to commits/PRs.
7. **Do not** add AI/session attribution trailers to commits/PRs.
8. **LOCAL-ONLY:** `CLAUDE.md` is gitignored via `.git/info/exclude` — never commit it.
9. **TUI is human-only:** no machine contract; never try to drive `tui`/`go` via `--json`.
10. **Confirm choke point:** under `bambu_cli/tui/`, `confirm=True` must appear only in `screens/confirm.py` (the Start print path). Preserve this invariant; add a CI grep if you touch confirm.
Expand Down Expand Up @@ -86,6 +95,40 @@ A green pytest alone is **not** evidence ruff/mypy/bandit passed.
| R2 | `privacy_smoke` is a crying-wolf gate: exit 1 locally on correctly-gitignored files (it walks the filesystem without consulting git), and on CI runners the account-name patterns resolve to the filtered generic `runner`, so its two best checks never build. Red locally, disarmed remotely | Medium |
| R3 | `CLAUDE.md:16` prints `--cov-fail-under=81`; CI is **83** (`ci.yml:77`, `CONTRIBUTING.md:25`, `AGENTS.md:104`). An agent following CLAUDE.md runs a weaker gate than CI | Medium |

### Status re-verified 2026-08-14 — read this before working any row above

The table is the **2026-07-31 audit snapshot**, kept as the record. Each row below
was re-checked against current `main` (`85c831e`); cited paths and line numbers are
from that check, not from the audit.

**Closed — do not re-open:**

| ID | Evidence |
|----|----------|
| S1 | Camera is fail-closed/opt-in: a failed direct grab does not start the streamer without `camera_allow_streamer` / `--allow-camera-streamer`; pin mismatch or `ssl.SSLError` with a pin hard-aborts; `camera_direct_only` forbids it entirely |
| S5 | `utils.py:63-67` is now a 4-line shim delegating to `jsonio.redact_url_credentials`. There is no longer a weaker second implementation — both spellings return `https://192.168.1.5/a.stl` for the audit's own `bob:secret123@…` repro |
| A3 | `protocols/mqtt.py` split into five siblings (#119) |
| A4 | `mqtt_port` is live, not dead: `context.py:89,137` → `printer.py:339` → `mqtt_tls.py:143` `_mqtt_port()` → `mqtt_tls.py:162` `client.connect(resolved_ip, _mqtt_port(printer), …)`. Doctor no longer lies |
| Q1 | CI floor is **86** (#119) against ~91% measured |
| T1 | Textual pin is `>=8.0,<9.0` (#115) — the `<2.0` cap is gone and the 8.x break is fixed |
| B1 | Rich markup injection fixed before the TUI merged (#97) |
| R1 / R3 | Media hygiene handled; `CLAUDE.md` cites floor 86 |

**Still open — re-verified as real:**

| ID | Evidence |
|----|----------|
| P1 | **Confirmed printer-safety bug.** `_numeric_values("400abc")` returns `[]` while `_numeric_values("400")` returns `[400.0]` (`slicer/options.py:162-175`), so a non-numeric nozzle/bed override contributes no values and temp validation passes vacuously where plain `400` correctly errors |
| Q4 | **Partly closed, and less closed than #116's message implies.** The 81 `sys.modules.setdefault("paho…")` calls are gone (0 remain), but `tests/bambu_test_base.py:26-29` still *unconditionally* assigns `sys.modules["paho"|"paho.mqtt"|"paho.mqtt.client"] = MagicMock()` at import time and never restores them. Real paho is installed and importable. This is also an order-dependence hazard of exactly the kind `AGENTS.md` forbids |
| Q5 | Both halves hold. `test_cmd_print_dry_run_success` (`tests/test_doctor_and_safety.py:198`) asserts `nlst` and `get_status` but never that the print publish did **not** happen; `test_cmd_stop_with_confirm` (`tests/test_cmd_device.py:107`) ends at `mock_send_command.assert_called_once()` with no payload check |
| Q6 | `tests/contracts/test_schema_validation.py:~299` still hand-builds `{"status": "print_started", …}` and validates it against the schema — no `bambu_cli` code runs, so emitter and schema can still drift together |
| A1 | Open, but the path moved: `interactive/core.py:558` and `interactive/presets.py:81` now import `build_parser` from `bambu_cli.cliparse` (not `bambu_cli.cli`). Domain→parser coupling remains |
| A2 | `utils.py:58` `_JSON_EMITTED` (with `_LAST_ERROR_PAYLOAD` / `_LAST_DOWNLOAD_PAYLOAD`) are still process globals mutated via `global` at `utils.py:133-134` |
| S3 | `commands/snapshot.py:399` is still an unbounded `resp.read()`. Severity is lower than the audit's Medium: the streamer path now requires explicit opt-in, so it is reachable only after the user enables it |
| R2 | Still crying wolf — `uv run python tests/privacy_smoke.py` exits **1** on a clean checkout |
| T2 / WS4 | Confirm-modal ergonomics and quit-during-prepare — unchanged |
| W3.4 | Never implemented: `ci.yml` contains no grep asserting `confirm=True` under `bambu_cli/tui` is confined to `screens/confirm.py` |

---

## 1. Outcome goals
Expand Down Expand Up @@ -281,7 +324,7 @@ Before merging to `main`:
- [ ] `ci_workflow_smoke` if parser/CI touched
- [ ] Multi-OS GitHub Actions green on tip (not only an older SHA like `cc6f78c`)
- [ ] Human smoke: `plate tui --sim` on a real TTY (not agent-driven)
- [ ] No Claude-Session trailers; no force-push of shared history without ask
- [ ] No AI/session attribution trailers; no force-push of shared history without ask
- [ ] Version remains `.dev0` until release tag process ([docs/releasing.md](../releasing.md))

**Do not auto-merge.** Human review required (plan precedent).
Expand Down Expand Up @@ -348,7 +391,7 @@ Before merging to `main`:

---

## 8. Pasteable brief for a new Claude session
## 8. Pasteable brief for a new agent session

```text
You are working in your local platecli checkout on branch feat/tui (or main if TUI already merged).
Expand All @@ -359,7 +402,7 @@ Priority order: WS-B (TUI markup blocker — already fixed in the working tree,

Also open and unassigned: S5 (utils redactor weaker + 0% covered), P1 (temperature override fails open on non-numeric values), Q4 (paho never exercised against the real library), Q5 (dry-run and cmd_stop tests cannot fail as intended). See the residual table.

Constraints: AGENTS.md + SECURITY.md; no --confirm / BAMBU_LIVE without asking; sys.exit only in cli.py; no @mockable; no Claude-Session trailers; do not commit CLAUDE.md.
Constraints: AGENTS.md + SECURITY.md; no --confirm / BAMBU_LIVE without asking; sys.exit only in cli.py; no @mockable; no AI/session attribution trailers; do not commit the local-only agent notes file.

Verify with ruff + ruff format + mypy + bandit + pytest -m "not live" (ResourceWarning error) at the stated cov floor. After ci.yml changes, run tests/ci_workflow_smoke.py.

Expand Down
13 changes: 7 additions & 6 deletions docs/plans/tui-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ built, documented in [the manual](../manual.md#full-screen-mode-plate-tui), and
covered by pilot tests. What the plan describes as future work has either shipped
or is recorded as an accepted quirk in the PR — read the manual and
[quality-roadmap](../quality-roadmap.md) for current behaviour, not this file.
**Branch:** all implementation work goes on **`feat/tui`** (branched from `main`).
Each phase below is sized for one ~1–2 h autonomous session and MUST leave the
repo green (every gate in §8 passing) so the session can commit safely.
**Prerequisite reading:** `AGENTS.md`, `docs/plans/interactive-mode-plan.md`
(house conventions, printer-safety rules), `docs/quality-roadmap.md`.
*(Historical, as written:)* **Branch:** all implementation work goes on
**`feat/tui`** (branched from `main`) — that branch merged via #97/#104 and no
longer exists. Each phase below was sized for one ~1–2 h autonomous session and
had to leave the repo green (every gate in §8 passing). **Prerequisite reading
at the time:** `AGENTS.md`, `docs/plans/interactive-mode-plan.md` (house
conventions, printer-safety rules), `docs/quality-roadmap.md`.

## 1. Goal

Expand Down Expand Up @@ -361,7 +362,7 @@ Never run against a live printer or set `BAMBU_LIVE=1` without asking Dylan.
**Acceptance criteria:** all §8 gates green from a clean checkout of
`feat/tui`; `uv build` + package smoke green (styles.tcss present in wheel);
docs updated. Final commit to `feat/tui`; branch ready for PR review — do not
merge to `main`, do not add Claude-Session trailers.
merge to `main`, do not add AI/session attribution trailers.

## 9. Quality gates (run at the end of EVERY phase)

Expand Down