Repository navigation
io-cli 0.27.0 - #68
Merged
Merged
Conversation
…hange it `/store` commits a page carrying the file's own page arithmetic — bytes on disk, bytes already free inside it, the session and run counts and the per-table breakdown — with each session's own size beside it. `/store rm <id>`, `/store sweep <date>` and `/store compact` change it, each behind a confirmation whose row 0 declines. Every figure is io-harness's. `Store::store_size`, `session_size`, `delete_session`, `sweep_sessions` and `compact` have all been public since before this product had a home to keep a store in, and none of them was called anywhere in this crate. The distinction the module exists to make is that a deletion does not shrink the file: SQLite frees pages into the database rather than out of it, so a removal moves bytes from `file_bytes` into `free_bytes` and the file on disk stays the size it was. A `VACUUM` is the only reclamation available, because every store this crate has created was created without `auto_vacuum`. The report says so, and the gate asserts `file_bytes` is unchanged by a removal rather than merely no larger. The sweep asks for the rule rather than for a count. There is no public reader for `sessions.created_at`, the column it filters on, so the set a date selects cannot be known in advance — and the one available substitute, a session's earliest turn, is strictly later than the session itself and would under-state a deletion. Filed as io-harness#216; the figures are reported from `Pruned` once the sweep has run, refused ids included. `store::acts` puts the confirmation's one decision in the library, because `src/main.rs` is a binary nothing under `tests/` can link. Also corrects a stale comment: `src/main.rs` claimed `/skills` confirms with "leave it" at row 0. It does the opposite, and it is the only confirmation in the product that does.
… verbatim
`/export` writes this session's conversation as markdown for a person to read,
and `/export trace` writes one run's canonical trace exactly as io-harness
produced it. Both go through `Workspace::write_file`, so the path policy the
session runs under is the one that applies, and an existing file is refused
rather than overwritten: an export is a snapshot, and the next one is a
different snapshot.
The trace is passed through untouched. Its whole value is that io-harness
excludes wall-clock stamps, measured durations, an ephemeral tempdir path and
autoincrement ids from it so two runs of one case can be compared; a trace this
crate reformatted would compare against nothing. The gate is a byte comparison
for that reason, not a parse-and-compare, which would pass on exactly the
defect that matters.
It is not JSON. `canonical_trace` builds pipe-delimited lines by hand, there is
no JSON alternative in io-harness, and `StepRecord` does not derive `Serialize`.
The export takes a `.txt` path: a file named `.json` that is not JSON breaks for
anyone who pipes it to a JSON tool.
An existence check through `read_bytes` was wrong and would have refused every
first export — a missing file is `Error::Config("no such file")`, not
`Error::Io`. It asks `resolve(path)?.exists()` now, which separates whether a
path may be addressed from whether it may be written.
`Inspect` reaches ten commands, which is the bound, so its gate is an equality
now rather than "there is room left".
`/undo <path>` restores one file from the snapshot taken before this run first wrote it. `/undo step <n>` reverse-applies one step's recorded diff. A bare `/undo` undoes the whole turn, which is what the rewind chord has always done — and both doors now reach one `undo_whole_turn`, so the word and the keystroke cannot drift apart. Two events fire for the first time in this product's history. `Rewound` is emitted only inside `rewind_run_observed` and `Reverted` only inside `rewind_step_observed`; `src/rewind.rs` called the plain `rewind_run` from 0.4.0 until now, so the arm rendering one of them in `src/events.rs` was unreachable from the day it was written. The gates assert on what an observer was handed, because the rendered sentence is identical either way. Reverting a step is order-sensitive: a later step still standing on the same lines makes the revert stale and io-harness leaves the file alone rather than fuzzy-matching it. That is said on screen, and only when something actually came back stale. The four answers to a file restore stay four answers. `NotKept` and `NotRecorded` mean different things, and collapsing them tells an operator "nothing to undo" about a file whose previous contents were merely over the snapshot cap. `/contain` moves from `Turn` to `Session` to make room, on that group's own rule: `Turn` means a command acting on the work the turn just finished, and `/contain on|off` is a posture that changes how every later turn is driven. The bound stays at ten, as it did in 0.19.0, 0.22.0 and 0.26.0.
The nine events this release set out to draw were already in src/triage.rs with a disposition of Silent and a written route — the surface by which the fact reaches the operator instead. Eight of those routes are better arguments than drawing would be: `reverted` and `rewound` route to io-cli's own rewind summary, so drawing them would double-report the undo this release just wired up, and `plan_proposed` routes to the overlay showing that same plan. One route was wrong. `speculated` named `io exec --json` and the durable trace, and both are places a person goes deliberately, afterwards, already suspecting something. A route is meant to name where a fact reaches somebody who was not looking for it. It draws a line now, carrying what reading ahead bought and what it cost — and only when something was discarded, because io-harness emits the event whenever anything was started at all, and a line in every transcript saying nothing went wrong is a line nobody reads. The set gate this release planned to build already existed: tests/triage.rs reads `pub enum EventKind` out of the pinned io-harness source and fails by name when the two sets differ. What it lacked was any check on the route column, so a silence could name a surface that does not exist. It cannot now. tests/glyphs.rs caught the new arm by itself and by name, which is what it was written for in 0.14.0.
…odel can reach them `delete_session`, `sweep_sessions` and `compact()` are each named in exactly one file, and it is the one whose whole subject is the store. The roadmap's words for /store are that no model can call any of it; "nothing was added" is not assertable, so this asserts its reachable form — a second module naming one of these is the first step towards a caller that is not a keystroke. The other half is that no source file calls `with_tools`, which is how a model is handed reach at all. Both sweeps strip comments, and that was verified by writing the forbidden name into one and watching nothing fail.
Every confirmation this release adds is drawn through the real render buffer at six widths in both glyph sets, with a workspace path chosen not to fit. The property names no number: no row overflows, and row 0 is still the declining row — a confirmation that dropped its "leave it" on a narrow terminal would leave an operator with only the destructive choice on screen. That is 0.26.0's correction applied. A gate that picks a width is asserting arithmetic it will get wrong, and that release's own narrow fixture passed at eighty because the field could not fit either way. N3 is asserted as the absence of a configuration key, because that is the only way this release could have changed what a turn is built from: every surface it adds is a command, none is configured, and none reaches contract::session. The contract's existing Debug-equality gates pass unamended, which is the rest of the same claim made by instruments this release did not touch.
Three README sections, each stating the fact that surprises: a deletion does not shrink the file, a removal takes the session's restore points but not its memory, a step revert is order-sensitive, a restore overwrites an edit made afterwards, an existing export is refused rather than overwritten, and the canonical trace is not JSON. docs/config.example.toml is untouched, which is the point: this release adds no configuration key, and a change to that file would contradict the gate that says so. The nightly rustdoc gate caught a redundant explicit link target in src/undo.rs — the eighth time in ten releases it has found something stable cargo doc, clippy and cargo test --doc all miss.
Two reviewers, split by area, over a fully green suite. The tenth consecutive release in which that gate has paid for itself, and the two most severe were found by both independently. **A sweep date was never validated, and the comparison is lexical.** `sweep_sessions` filters a text column, so `/store sweep 2026-8-1` — an unpadded month — sorts above every real timestamp, as does `/store sweep yesterday`. Either would have removed every session in the store, across every workspace, while the confirmation echoed back exactly what was typed and read as correct. The boundary is now parsed to a shape and normalised, or refused by name. **The session io is running could be deleted from inside itself.** The /store page lists every session with its root, which is what makes the operator's own the recognisable one; io-harness refuses nothing and enforces no foreign keys, so the process would have carried on writing turns into a session that no longer existed and every later /undo and /export would have reported it empty. **The export was the whole turn tree, not the conversation.** session_turns is documented as the tree; a rewind moves the head without deleting the turn, so an undone turn was being exported, unmarked, into the one artifact whose purpose is being read by somebody who was not there. It walks Session::history now. **The whole-turn undo reported through the one-slot footer**, so every "left as the turn left it" warning was overwritten and an operator was told a restore had happened that had not. It records into the scrollback, like its two siblings. Also: /export wrote under the file's policy rather than the session's effective posture, so it wrote while the footer said read only; the step revert did the same, against a contract io-harness documents; a dangling symlink read as an empty path and a write would have followed it out of the workspace; export::write now refuses an existing file itself rather than trusting a check taken before the confirmation; Reverted::NoHunk printed a Rust debug string; a sweep never said how many restore points it took; and the /store page's session total counted sessions its own list could not show. A gate that could not fail is fixed: the sweep confirmation's "carries no count" assertion had a loop-invariant `&&` that made it hold for any title at all. The three new commands' argued forms had no test whatsoever — including the two refusal families whose entire purpose is that a mistyped verb never reaches something destructive. undo::whole_run and Grain::label are gone. Both were public, tested, and called by nothing — the shape this codebase shipped in 0.20.0, 0.21.0 and 0.26.0. And several comments claimed src/events.rs had an unreachable arm for Rewound and Reverted. It has no arm for either: both are Silent by decision and route to the rewind summary. What the observed forms buy is that the events exist at all.
io-cli 0.27.0
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.
The release pull request for 0.27.0. Everything in it merged into
developthrough #67;git diff --quiet feat/0.27.0 developis clean, so this ships exactly the tree the record describes.The store stops being something you cannot see, undo stops being all-or-nothing, and the work stops ending when the terminal closes.
/store— the file's own page arithmetic, per session and in total;rm,sweepandcompact, each behind a confirmation whose first row declines. A deletion does not shrink the file, and io says so./undo— one file, one step, or the whole turn./export— the conversation as markdown, a run's canonical trace verbatim./containmoves toSession, freeing theTurnslot/undotakes without widening the bound.Gates
Six CodeQL alerts were raised and dismissed as false positives with evidence: all six are the
rust/cleartext-loggingheuristic firing on the namesession_id, which here is a SQLitelast_insert_rowid()integer with no entropy that authenticates nothing, in assertion messages in test binaries that never ship.The record is sealed and pinned at
sha256:b9b8e449…,ultraship validateexits 0 over 72 checks.