Conversation
Extraction used to negotiate. A character Windows refuses became a question for the user, a trailing dot was dropped without asking, and a reserved device name gained a `_`; the entries were then written under whatever name that arrived at. The tree on disk was this crate's invention rather than the archive's, and nothing downstream could tell which files those were. The policy is now the narrow one: all of the archive under its own names, or none of it. `refuse_unwritable_names` judges the whole listing against `NameRules::can_write` before the first byte, and the first component the host cannot hold exactly as spelled fails the extraction with `NameError::Unwritable`, naming the entry, the component and why. All four faults answer the same way, because from the caller's side they are one failure: the name they asked for is not the name they would get. Consequences worth stating rather than discovering: - `extract` and `extract_with` now refuse archives they used to extract. On Windows that includes ordinary Linux tarballs holding `aux.log` or `notes.txt.`, which is the whole point and also the cost. - `ExtractOptions::with_replacements` is inert. Kept, with `Substitutions`, so the two front-end dialogs still compile while they are taken out. - `check_replacements` no longer runs from `extract_with`: validating an input that is ignored is worse than not taking one. - `refuse_overwriting_the_archive` drops its plan argument. It followed the rewritten name because a substitution could land an entry on the archive that the archive's own name did not match; nothing rewrites a name any more. - `NamePlan` is now always the identity and `extract_with` calls the plain backends. The type, the `extract_*_planned` variants and tar's second write path are dead and deliberately left in place: that path carries the containment guard and should not move in a commit about naming. The tests are untouched on purpose, so the ones that pinned the old behaviour fail. They are the record of what was decided before and rewriting them in the same commit would hide the size of the change.
`run_extract` refused an archive whose listing needed a character replaced and waved through a trailing dot or a reserved device, because those two had one correct answer that needed nobody. Core applied them silently and extraction continued. Core refuses all three now, so the split has nothing behind it: an archive with `notes.txt.` in it will not extract on Windows whatever this check says. Widened to the whole report. Refusing here as well is still not redundant. Core stops at the first offending component; this has the same listing in hand and names every entry at fault at once, which is the message someone can act on. `adjustments` is left in place and is now provably empty by the time it is reached, since the report above it was empty. It goes when `Outcome::Extracted` loses the field. Tests untouched, so the cases that pinned a trailing dot extracting fail.
`NameInspection`'s doc claimed the extraction validates the answers again before opening the archive. It did, and no longer does: core neither validates nor applies a substitution, so an answer cannot reach a file name at all. The dialog still runs and still collects answers that go nowhere. Removing it, along with the `unwritable_names` command it is built from, is the follow-up.
The five that failed asserted the answers being applied. Rewritten to assert what happens instead, keeping each one pointed at the guarantee it was written for rather than deleting it and losing the record. - `every_fault_stops_the_whole_archive_the_same_way` replaces `the_answers_are_written_and_the_listing_names_what_is_on_disk`, and is now a table over all four faults, since the point of the policy is that they have one ending. `summary.txt` rides along in each archive: it is writable everywhere and must still not reach disk. - `a_colon_entry_is_refused_and_its_neighbour_is_never_written` keeps issue #63 and moves its sharpest assertion. `notes.txt` used to have to survive intact; now it must not exist at all. - `two_entries_that_used_to_collide_are_refused_for_their_own_names` records that a collision cannot be manufactured any more, and asserts no planned name appears in the message, which is what would show something still working one out. - `a_trailing_dot_is_refused_rather_than_folded_onto_the_name_beside_it` is the renamed collision-with-an-untouched-entry case. It was passing under a name that had stopped being true. - `an_answer_no_longer_rescues_an_entry_the_host_cannot_write` is the old answer-validation test turned around. Its subject is gone, so it now guards the policy most likely to be quietly undone: the most reasonable answer to the most ordinary question must still extract nothing. `an_entry_with_no_answer_stops_before_anything_is_written` is dropped, absorbed whole by the first of these. In security.rs, two tests depended on the renamed write path that no longer exists. Both keep their real assertion (the archive intact, the file outside the output directory absent) and say in the body that the reason changed: refusal now comes before the write rather than the guard catching it during. `a_replacement_cannot_carry_an_entry_out_of_the_output_directory` still offers the hostile answers and still must see nothing move.
`a_name_that_needs_no_answer_is_adjusted_rather_than_refused` was passing while asserting the opposite of what the CLI does, which is the worst state a test can be in: it exercised `adjustments`, a pure function that still works, and its name and doc comment described a product behaviour that is gone. Both adjustment tests now pin the criterion `run_extract` actually uses. The report's `characters` half is empty for a trailing dot or a device name — that is why they used to extract — and `is_empty()` is not, which is the whole of the one-line change. Judged through `NameReport` rather than by running the command, since these faults are Windows-only and this suite runs on Linux. `adjustments` and `Adjustment` lose their only tests and their import here. They are dead code, reached only where the report is provably empty, and go when `Outcome::Extracted` loses the field.
The host-rules half of this suite drove the NUL byte through the whole exchange: ask, answer, extract, check the name on disk. The answer reaches nothing now, so those assertions had no subject. - `no_answer_rescues_a_name_this_computer_cannot_write` replaces both the substantive answer and the empty one with a table over the two, since they were the same test with a different string. It is the one to look at if the dialog is ever wired back up by accident. - `a_hostile_answer_changes_nothing_because_no_answer_is_applied` keeps the `../escaped` fixture from the separator test. That answer used to be refused by the ruleset; now it is simply inert, and the archive extracts while nothing called `escaped` appears anywhere. - `the_refusal_says_which_character_is_the_problem` keeps the reason travelling to the dialog, which matters more here than anywhere: a NUL prints as nothing, so an entry named without its character looks fine. - `an_entry_beside_the_name_it_would_have_taken_is_still_just_refused` records that the collision it was built for cannot happen, and holds on to the part that still matters — the writable neighbour is not written either.
Left behind by rewriting the last test in two files. `fmt/check` is CI's first gate and everything waits on it, so this cost a whole run.
rustfmt breaks a call whose arguments exceed 60 columns, not 100, which is the width the eye checks against. Both of these fit a line and still get split.
The three documents describing the naming layer all still described the scheme that was replaced, which is worse than describing nothing: threat_model.md is the stated source of truth for why each guard exists, and it was explaining a guard that had stopped existing. **threat_model.md**, measure 4b, is rewritten around the narrowing and keeps the reason for it, since the scheme it replaced looked like the kinder answer and somebody will propose it again. What it produced was a tree of names this program had invented, indistinguishable afterwards from the archive's own. The `unwritable_names` report is now described as what it became: a prediction of a refusal rather than a questionnaire. Measure 4a loses the paragraph about following the *planned* name. That guard had to, while a substitution could land an entry on the archive under a name the archive did not spell; with nothing renamed the two names are one, and the guard got simpler rather than weaker. Two test names it cited no longer existed, both renamed in this branch, and a sweep of every identifier the docs cite says the rest resolve. The new pair that pins the policy itself is cited too. **architecture.md**, the names.rs section, is rewritten the same way, and now says which surface is inert (`Substitutions`, `with_replacements`, `NamePlan`, the `extract_*_planned` variants) so the next reader does not take dead machinery for live design. The CLI section gains the pre-flight refusal and why it duplicates core's: core stops at the first offending component, the CLI has the whole listing and names every entry at once. Also corrected along the way, drift that predates this branch: both architecture.md and desktop.md said the desktop exposes four Tauri commands and listed four. There are five — `unwritable_names` arrived later — and desktop.md's layout block never gained `src/names.rs` either.
None of these come from this branch; they are drift the review found while checking the naming rewrite, and they are worth the same commit because they are all "the documentation describes a smaller product than the one that ships". **`--verify` was undocumented for the CLI.** The flag exists, is shipped, and `docs/server.md` documents its server-side twin in full, while README.md and architecture.md's command surface never mentioned it. Worse, neither said the thing a user would actually want to know: *every* local compression is already read back, and `--verify` only chooses how deeply. A compressor here finalises on drop, so a run that dies partway through leaves an archive that opens cleanly and is silently short — the default check is what catches that, and it was invisible in the docs. Also recorded: the flag is refused with `--server`, because the archive is built on the far side. **`test (desktop, rust)` was missing from the CI section**, which exists to describe the job graph and listed every other job. It is the only Linux job that compiles the Tauri crate, and it needs Node for the bundle `generate_context!()` embeds. **The README's test counts were stale**, and were stale before this branch: 620/505 written against 623/507 on `main`. Now 621/506, which this branch's own net of two removed tests produced. A number nothing verifies will drift again; worth either dropping or adding a check, and it is not obvious which. A sweep of every internal link, anchor, cited file path and cited test name across all eight documents came back clean otherwise.
One logical change across CI and the documentation, kept in one commit because splitting it would leave a state where the workflows enforce one branch name and the documents describe another. **Workflows.** `gitflow.yml` is the one that matters: it only triggers on PRs whose base is in `branches:`, and its two jobs are the required status checks on the rulesets. Left pointing at `dev`, a PR into `develop` would never run it, so the required check would never report and the PR would be unmergeable — blocked waiting on something that cannot arrive. Its trigger, both `base_ref` guards, the release head-branch check and every message now say `develop`. `test-and-build.yml` gets the push trigger and the `head_ref` gate that lets the macOS and Windows suites run on the release path. `deploy-landing.yml` gets its push trigger. `release.yml` only mentioned the old name in a comment. **The staging branch keeps its name.** `pages/landing-dev` is what serves the staging site, so renaming it would change that URL and buy nothing. The name describes the environment, not the branch the build came from, and both `deployment.md` and the workflow now say so rather than leaving it looking like something that was missed. **Documentation.** 41 references across `git_flow.md`, `deployment.md`, `architecture.md` and `README.md`. The rename was applied only to the backticked branch reference, so `pages/landing-dev`, the `collapse-server-aio:dev` image tag, `npm run tauri dev` and the word "development" were left alone; the ASCII flow diagram and a table's column alignment were fixed by hand afterwards. **Not in this commit, and required before `dev` is deleted:** the rulesets. `protect-dev` still targets `refs/heads/dev`. The documentation now calls it `protect-develop`, which is a promise this commit cannot keep on its own.
chore: move the development branch from dev to develop
Two lines, and they were the only mentions left in tracked files. `.gitignore` listed `CLAUDE.md`, a file that was never committed — the entry existed so it could not be. With the file gone from the working copy too, the entry has nothing to ignore, and leaving it would keep a name in the repository for a file the repository does not have. `.dockerignore` excluded `.claude/` under "never ship history or local tooling state into the context", beside `.git/`, `.github/` and `node_modules/`. **That second one has a small cost, stated rather than buried:** if a `.claude/` directory ever appears at the root it now enters the Docker build context. It does not exist today, and `*.md` further down already keeps stray markdown out, so nothing changes for the images as they stand. The exclusion was hygiene, not a dependency — no build reads that path — but it was doing something, and this gives it up on purpose. Re-adding the line is the fix if a context ever grows for that reason. Nothing here touches the build: `cargo`, the Dockerfiles and the workflows never read either path.
…repo chore: take the assistant tooling out of the repository
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release of the work sitting on
develop. No version bump is needed, andthat is worth explaining rather than assuming.
v0.10.0 was bumped but never shipped
The newest tag in the repository is
v0.9.0. All twelve version locations — thefive
Cargo.tomls,tauri.conf.json, bothpackage.jsons and all fourlockfiles — already read
0.10.0, set byc8d2b66and merged tomainin#111, but no
v0.10.0tag was ever pushed and no release was ever published.So
0.10.0is a version number nobody has. Shipping this state asv0.10.0iscorrect: it needs no bump, it burns no number, and it leaves no gap in the tag
sequence. Bumping to
0.11.0would create a phantom0.10.0that existed onlyin the manifests.
The audit is in the checklist below; every location was verified before opening
this.
What ships
Extraction refuses a name this system cannot write, instead of renaming it.
The behaviour change users will notice. A refused character used to become a
question and be replaced with the user's answer; a trailing dot or space was
truncated in silence; a reserved device name gained a
_. What that producedwas a tree of names the program had invented, indistinguishable afterwards from
the archive's own. An extraction now reproduces what the archive says or it does
not happen, and the refusal names the entry, the component and the reason.
On Linux almost nothing changes:
NameRules::unix()refuses only the NUL byte.This is a Windows-facing change, and the Windows CI leg exercises it.
Closes #95 (the names the user approved are not provably the names extracted —
there is nothing to approve any more) and #91 (the collision guard, which cannot
be reached now that nothing is renamed). Supersedes #90, whose reported symptom
is now the intended policy for every unwritable name.
Repository furniture, no user-facing effect: the development branch moved
from
devtodevelopacross the workflows and documentation, and theassistant tooling entries came out of
.gitignoreand.dockerignore.Pre-release checklist
0.10.0developis green, includingtest (rust, windows)andtest (rust, macos)docs/threat_model.mdmeasure 4b describes the shipped behaviourv0.10.0on the merge commit —release.ymlrefuses a tag whosecommit is not on
main, and checks the tag againstapps/cli/Cargo.tomland
tauri.conf.json