diff --git a/docs/handoffs/2026-10-11-py7zip-snapshot-family-handoff.md b/docs/handoffs/2026-10-11-py7zip-snapshot-family-handoff.md index 959fd64..53dc3d2 100644 --- a/docs/handoffs/2026-10-11-py7zip-snapshot-family-handoff.md +++ b/docs/handoffs/2026-10-11-py7zip-snapshot-family-handoff.md @@ -6,15 +6,26 @@ behavior, what the repository actually records about their intent, the defect/legacy classification, compatibility and security implications, the open decision gates, and the phased path to a resolved contract. -**Evidence boundary:** branch `codex/py7zip-mypy-hardening-20261011`, HEAD -`7f4f381` (= `origin/main` `f6e9a1a` + one mypy-hardening commit; local -`main` is stale at `a7b396c`), working tree clean at audit start. The -divergent audit line was read from local tags `v0.8.0` (`3252d39`, -2026-09-29) and `v0.9.0` (`5a60a7c2`, 2026-10-03); its branch tip is -`7822d95`. Topology: `origin/main...audit/py7zip-qualification-20260909` is -**6 behind / 22 ahead** at merge-base `4713a75` (each side has commits the -other lacks), and `v0.9.0` is **not** an ancestor of HEAD. All citations are -to files in this worktree unless prefixed with a ref (e.g. `v0.9.0:`). +**Evidence boundary and required execution base.** The original audit +observations (W1–W13, §11) were taken on branch +`codex/py7zip-mypy-hardening-20261011`, HEAD `7f4f381` (= `origin/main` +`f6e9a1a` + one mypy-hardening commit; local `main` is stale at `a7b396c`), +working tree clean at audit start. The **required execution base for every +phase below is this branch, `docs/py7zip-snapshot-family-handoff-20261012`, +at HEAD `b134d91` or a descendant**: `b134d91` sits directly on +`origin/main` `f6e9a1a` (`f6e9a1a..b134d91` contains only this handoff +document — docs-only), so its code content equals `f6e9a1a`; the `7f4f381` +mypy-hardening commit is **not** part of this base and must not be rebased +onto it. Because that commit shifts `py7zip/py7zip.py` line numbers, all +`py7zip/py7zip.py` anchors in this revision are stated for the execution +base (family methods `224–235`, `snapshot` docstring `234`, `wrapper` +diagnostics `178–202`), not the `7f4f381` numbering. The divergent audit +line was read from local tags `v0.8.0` (`3252d39`, 2026-09-29) and `v0.9.0` +(`5a60a7c2`, 2026-10-03); its branch tip is `7822d95`. Topology: +`origin/main...audit/py7zip-qualification-20260909` is **6 behind / 22 +ahead** at merge-base `4713a75` (each side has commits the other lacks), and +`v0.9.0` is **not** an ancestor of HEAD. All citations are to files in this +worktree at the execution base unless prefixed with a ref (e.g. `v0.9.0:`). **Status:** docs-only handoff. **No source file was changed and no test was weakened, deleted, or edited.** Nothing in this document authorizes a code @@ -66,10 +77,10 @@ defect, not the deliberateness: The failure mode is the harmful part: **silent success**. `wrapper` on a real failure at least prints diagnostics under `verbose`/`debug` -(`py7zip/py7zip.py:222–228`); the snapshot family prints nothing, raises +(`py7zip/py7zip.py:178–202`); the snapshot family prints nothing, raises nothing, spawns nothing, and returns `None`. For a method whose docstring says "Streamlines the process for creating a snapshot" -(`py7zip/py7zip.py:306`) and whose sibling docs promise backup schemas, a +(`py7zip/py7zip.py:234`) and whose sibling docs promise backup schemas, a caller who believes the call ran has no archive and no error. On a backup API, a false success is a data-protection hazard, not a cosmetic gap. @@ -82,11 +93,11 @@ whether … implement them or remove them from the public surface" is recorded as *open work* in `docs/planning/HANDOFF.md:39–40`, and the deciding records still do not exist (§6). -## 2. Current behavior (verified at HEAD `7f4f381`) +## 2. Current behavior (verified at execution base `b134d91`, code identical to `f6e9a1a`) -- `py7zip/py7zip.py:276–307` — all four methods share the signature +- `py7zip/py7zip.py:224–235` — all four methods share the signature `(self, src, dst, options="")` and their bodies are a bare `return`. - Only `snapshot` carries a docstring (`:306`); the other three have none. + Only `snapshot` carries a docstring (`:234`); the other three have none. - The no-op is **unconditional across both runtimes**: the methods never consult `self.legacy`, `self._safe`, or any attribute. Identical behavior on the safe (default) and legacy runtimes. @@ -245,12 +256,22 @@ equips the maintainer to. removing exactly the four stubs plus `cd` — no other public surface (`PLATFORM_ARCHITECTURE_PY7ZIP_2026-10-10.md:37–43`); `AGENTS.md` otherwise requires preserving the public `Py7zip` API (`AGENTS.md:5–8`). -6. **Documentation is the only immediately user-facing surface.** Until the - gates resolve, `docs/FEATURES.md:5–6` keeps advertising non-existent - functionality to every reader of the repo and the PyPI long-description - source. Fixing the advertising does not prejudge implement-vs-remove and - is already scoped as OVH-03 ("Docs truth-pass … `docs/FEATURES` stub - labeling", `2026-10-11-py7zip-overhaul.md:372, 464–466`). +6. **Documentation is the only immediately user-facing surface — and PyPI + visitors are one click away from it.** Until the gates resolve, + `docs/FEATURES.md:5–6` keeps advertising non-existent functionality to + every reader of the repo. The PyPI exposure is by link, not by syndicated + content: `README.md:36` links `docs/FEATURES.md`, and `README.md` is the + PyPI long description (`pyproject.toml:9`, + `readme = {file = "README.md", …}`); the linked file is not rendered as + long-description content, so the false claims do not appear on the + package's PyPI page itself — a prospective `pip install py7zip` visitor + can click the documentation link and land on them. Correcting + `docs/FEATURES.md` therefore fixes the link target; the rendered text of + the already-published 0.6.2 PyPI page changes only with a future release + (no release is authorized by this handoff). Fixing the advertising does + not prejudge implement-vs-remove and is already scoped as OVH-03 + ("Docs truth-pass … `docs/FEATURES` stub labeling", + `2026-10-11-py7zip-overhaul.md:372, 464–466`). ## 5. Security implications @@ -317,22 +338,74 @@ third rule (their interaction) is not written anywhere and is recorded here. Execution order is Phase A → B → (C | C′) → D. Phase A needs no gate and changes no code. Phases C/C′ are already-specified plan tasks; they are listed here bound to their gates, not re-specified. **This handoff executes -none of them** — it is docs-only and uncommitted by instruction. +none of them** — it is docs-only. ("Uncommitted by instruction" described +authoring time only; the handoff has since been committed on this branch — +see §11. That changes the filing, not the force: nothing below is executed +by this document.) ### Phase A — stop the false advertising (docs-only; no gate; maps to OVH-03) - **A1.** Edit `docs/FEATURES.md:5–6`: either delete the two claims or mark - them explicitly as not implemented in any released build, e.g. "planned; - not implemented in 0.6.x (no-op) — see COMPATIBILITY.md". Do not claim a - removal version (DG-1 unrecorded) and do not claim the audit-line - behavior as current (DG-2 unrecorded). + them explicitly as no-ops in PyPI 0.6.2 and on the current main line + (§1 item 2, §2), e.g. "planned; no-op in PyPI 0.6.2 and current + mainline — see COMPATIBILITY.md". Do not describe the implemented + v0.8.0/v0.9.0 GitHub release artifacts (§3.3) as no-ops or as + unimplemented — the no-op claim holds only for PyPI 0.6.2 and this + line. Do not claim a removal version (DG-1 unrecorded) and do not + claim the audit-line behavior as current (DG-2 unrecorded). - **A2.** Same commit: confirm `docs/PLANNING.md:13–14` annotation still matches reality (it does at HEAD) and leave `docs/USAGE.md` untouched — it never documents the family (`docs/USAGE.md:1–16` covers only compress/decompress). -- Acceptance: the edited doc lines are the only changed files - (`git diff --stat` = docs only); `grep -n "full\|incremental\|differential\|snapshot" docs/FEATURES.md` - returns no unqualified feature claim. +- Acceptance (falsifiable): + 1. `git diff --stat` names docs files only (expected: just + `docs/FEATURES.md`; A2 requires no edit at HEAD). + 2. The two claims at `docs/FEATURES.md:5–6` no longer appear: + `grep -nF "point-in-time" docs/FEATURES.md` and + `grep -nF "Simplifies lessor known features" docs/FEATURES.md` both + return empty. + 3. **No assertion that the family works remains** — evaluate each family + word independently, stopping at the next family word or clause + delimiter, whichever comes first. This prevents a qualifier attached to + a later occurrence from masking an earlier unqualified claim. Run: + + ```bash + python3 - <<'PY' + import re + from pathlib import Path + + family = re.compile(r"\b(?:full|incremental|differential|snapshot)s?\b", re.I) + qualifier = re.compile(r"\b(?:not[- ]implemented|planned|no-op)\b", re.I) + delimiter = re.compile("[;.]") + + def unqualified(text: str) -> list[str]: + occurrences = list(family.finditer(text)) + failures = [] + for index, occurrence in enumerate(occurrences): + end = len(text) + if index + 1 < len(occurrences): + end = min(end, occurrences[index + 1].start()) + punctuation = delimiter.search(text, occurrence.end(), end) + if punctuation: + end = min(end, punctuation.start()) + if not qualifier.search(text, occurrence.end(), end): + failures.append(occurrence.group(0)) + return failures + + negative = "Full backups are supported, snapshots are planned" + positive = "Full backups are not implemented; snapshots are planned" + assert unqualified(negative), "mixed-clause counterexample must fail" + assert not unqualified(positive), "qualified occurrences must pass" + + findings = unqualified(Path("docs/FEATURES.md").read_text()) + if findings: + raise SystemExit(f"unqualified family claims: {findings}") + PY + ``` + + The mixed-clause counterexample must fail, the qualified two-occurrence + example must pass, and the final check must return zero findings after + the Phase A documentation correction. - Rollback: revert the single docs commit. Risk: none at runtime. ### Phase B — record the decisions (gates; Maint) @@ -347,10 +420,13 @@ none of them** — it is docs-only and uncommitted by instruction. family (§3.3) and therefore collide with OVH-14 as written. - **B3.** Write the DG-3 interaction rule (§6) into whichever record lands second, and add a one-line cross-reference to the first. -- Acceptance: decision records exist under `docs/planning/`; OVH-14's - blocking condition is discharged only by B1. +- Acceptance (falsifiable): `grep -rln "D-OVH-7" docs/planning/` returns + the DG-1 record, which names a removal version and window; + `grep -rln "D-OVH-1" docs/planning/` returns the DG-2 record, which names + exactly one of merge / cherry-pick / leave-diverged; OVH-14's blocking + condition is discharged only by B1. -### Phase C — removal path (only if DG-2 outcome (c), or DG-2 deferred and DG-1 taken) +### Phase C — removal path (only if DG-2 outcome (c) and DG-1 taken) Execute **OVH-14 exactly as specified** — the two-commit deprecate-then- remove sequence already specified in §3.2 @@ -372,15 +448,30 @@ Not specified by any prior task; requires a new recorded task before code (including `diff_path`/`timestamp` kwargs) from `v0.9.0`. - **C′3.** Update the signature pin (`tests/test_characterization_wrapper.py:311–321`) and the no-op pins - (`:300–308`) to the adopted contract **in the same commit** (rule 3), - and port `TestBackupModes` + the differential re-run pin from the audit - line (`1bb3ea3`, `13fe83e`). + (`:300–308`) to the adopted contract **in the same commit** (rule 3). + **Test design — one choice, fixing an ambiguity in earlier revisions:** + the audit line's `TestBackupModes` is **not** ported as a host-gated + real-binary e2e lane. That suite (v0.9.0 + `tests/test_e2e_bundled.py::TestBackupModes`, introduced by `1bb3ea3`) + runs the real `7za` checked into `bin/` and is gated on the bundled + artifact for the detected platform — a design this line's offline suite + (socket denial via `tests/conftest.py:43–52`; `filterwarnings = + ["error"]`) does not carry. Port its behavioral assertions as **argv pins + against the fake seam** (`tests/fakes.py`, `FakeSubprocess`), per §8 + (adoption cases 2–7), including the differential re-run pin from + `13fe83e` as a two-call fake-seam pin. Do not check a real binary into + `bin/` and do not add a host-gated lane in this phase; that would be a + separate, unrecorded task. - **C′4.** Rewrite `docs/FEATURES.md`, `docs/planning/COMPATIBILITY.md` family rows to the implemented contract; revise OVH-14 to `cd`-only (DG-3). -- Acceptance: full offline suite green with the updated pins; e2e backup - tests green offline via the fake seam; no support claim without a receipt - (`AGENTS.md:24–26`). +- Acceptance: full offline suite green with the updated pins; the ported + backup-mode coverage (§8, adoption cases 2–7) runs entirely through the + fake seam offline — falsifiably: no `tests/test_e2e_bundled.py` is added, + `git status --short -- bin/` shows no new binary (only new additions are + barred; existing tracked `7za` artifacts stay as-is), and every new + backup-mode test goes through `FakeSubprocess`; no support claim without + a receipt (`AGENTS.md:24–26`). ### Phase D — release and verification @@ -413,7 +504,10 @@ Phase-specific cases to add (offline; fake seam per `tests/conftest.py`, `all(not hasattr(Py7zip, m) for m in ("full","incremental","differential","snapshot"))`, mirroring OVH-14's acceptance one-liner (`2026-10-11-py7zip-overhaul.md:653–655`). -- **Adoption (Phase C′):** +- **Adoption (Phase C′)** — design fixed to one choice: **fake-seam argv + pins** (`FakeSubprocess`); the v0.9.0 `TestBackupModes` host-gated + real-binary e2e (checked-in `bin/` artifact) is not ported as such + (§7, C′3): 1. signature pin updated to the adopted parameters (kwarg-only `diff_path`/`timestamp`); 2. `full` argv = `[binary, "a", dst, src, "-y", *options]`; @@ -434,9 +528,12 @@ Phase-specific cases to add (offline; fake seam per `tests/conftest.py`, ## 9. Success criteria -- **Phase A:** `docs/FEATURES.md` no longer advertises the family as - working; diff touches docs only; characterization suite untouched and - green. +- **Phase A:** the falsifiable Phase A checks hold — every family-word + occurrence in `docs/FEATURES.md` carries a not-implemented/planned/no-op + qualifier in its own clause (the §7 Phase A per-occurrence grep guard + returns empty; a qualifier in one clause does not excuse another claim + on the same line), the two `:5–6` claims are gone, the diff touches + docs only; characterization suite untouched and green. - **Phase B:** DG-1 and DG-2 records exist under `docs/planning/`; the DG-3 interaction rule is present in at least one of them; OVH-14's blocking condition references the actual record. @@ -445,8 +542,11 @@ Phase-specific cases to add (offline; fake seam per `tests/conftest.py`, exist and assert the warning (commit 1) and the absence (commit 2); no pin was deleted or weakened outside the same-commit rule. - **Phase C′:** full offline suite green with the adopted contract pinned; - `TestBackupModes`-equivalent e2e coverage exists offline; COMPATIBILITY - and FEATURES describe the implemented behavior; OVH-14 revised to + backup-mode coverage exists as offline fake-seam argv pins per §8 + (adoption cases 2–7) — falsifiably, no new host-gated real-binary e2e + lane is added and no new checked-in `bin/` binary is added; existing + tracked `7za` artifacts remain unchanged. COMPATIBILITY and FEATURES + describe the implemented behavior; OVH-14 revised to `cd`-only. - **Phase D:** the shipped release's notes contain the family callout (migration or deltas), and every support claim about backup modes has a @@ -486,9 +586,24 @@ Phase-specific cases to add (offline; fake seam per `tests/conftest.py`, | W11 | `git log --all --oneline -- "*QUALIFICATION*"` | plan file's commits (`3252d39`, `2e8c3df`, `5a60a7c`, `d6a6c5d`) are on the audit line only | | W12 | Grep sweeps over `docs/` (`FEATURES`, `CHANGELOG`, `PLANNING`, `planning/HANDOFF`, `planning/COMPATIBILITY`, both handoffs) | every claim in §1–§3 cited with file:line | | W13 | PyPI JSON API, `GET https://pypi.org/pypi/py7zip/json` (observed 2026-10-11) | latest version `0.6.2`; release keys end at `0.6.2` (§1, item 2) | - -No tests were executed and no source file was modified (docs-only task; -tree left uncommitted by instruction). +| W14 | `git rev-parse HEAD`; `git diff --stat f6e9a1a..HEAD`; re-read `py7zip/py7zip.py` at HEAD (observed 2026-10-12) | HEAD `b134d91` = `f6e9a1a` + docs-only handoff commits (`7f4f381` not in this history); code identical to `f6e9a1a`; anchors restated for this base: family `224–235` (docstring `234`), wrapper diagnostics `178–202`; README.md:36 links FEATURES.md; pyproject.toml:9 = readme field; v0.9.0 e2e confirmed host-gated real-binary | +| W15 | PR #27 review-verification (2026-10-12): `GET api.github.com/repos/aliasfoxkde/py7zip/releases` → exactly `v0.8.0`/`v0.9.0`, both `draft: false`; `GET pypi.org/pypi/py7zip/json` → latest `0.6.2`; `git rev-parse v0.8.0^{commit} v0.9.0^{commit}` → `3252d397`/`5a60a7c2`; `git show v0.9.0:py7zip/py7zip.py` → family implemented with `diff_path`/`timestamp`; new §7 Phase A grep guard run against the pre-edit `docs/FEATURES.md` claims, the mixed-clause counterexample, and the A1 permitted wording; `git diff --check` | all three review findings confirmed and corrected (§7 A1, §7 Phase A acceptance 3, §9 Phase A, §7 Phase C heading); guard flags the pre-edit claims and the counterexample, passes the permitted wording; diff clean | + +No tests were executed and no source file was modified (docs-only task). +"Tree left uncommitted by instruction" (here and in §7) is a historical +statement about authoring time, not the current state: the handoff has +since been committed on `docs/py7zip-snapshot-family-handoff-20261012` +(`0899976` adds this document; `b134d91` corrects its date). An earlier +revision restated the `py7zip/py7zip.py` anchors for that committed +execution base (W14) and tightened Phase A/C′ acceptance wording. The +present revision (2026-10-12, from PR review) applies three corrections +(W15): the Phase A no-op claim is limited to PyPI 0.6.2 and the current +main line, with the implemented v0.8.0/v0.9.0 GitHub release artifacts +distinguished (§7 A1); the Phase A grep guard is per-occurrence instead +of whole-line, so one qualified clause no longer excuses an unqualified +claim elsewhere on the line (§7 acceptance 3, §9); and Phase C now +requires DG-2 outcome (c) **and** DG-1 taken — the deferred-DG-2 +alternative contradicted §6/DG-3 and is removed (§7). ## 12. Appendix — the implemented contract as it exists at `v0.9.0` (`5a60a7c2`) @@ -504,7 +619,7 @@ Reference for whoever executes Phase C′; verified by W6/W8, quoted from Supporting shape: `Py7zip._normalize_options` maps `""`/`None` → `()` and passes non-empty strings through so the argv runtime raises the same -`TypeError` as the aliases (`v0.9.0:py7zip/py7zip.py:225–233`); +`TypeError` as the aliases (`v0.9.0:py7zip/py7zip.py:224–234`); `Py7zip._backup_executor` returns `self._safe` on the default runtime and, on legacy, resolves `7za` from PATH via `shutil.which` (typed `ArchiveExecutionError` if absent) so both runtimes execute identically