Repository navigation
io-cli 0.38.1 - #101
Merged
Merged
io-cli 0.38.1#101
Conversation
Generated by scripts/update-tap.sh from the v0.38.0 Release's own SHA256SUMS, which is why this lands after the Release rather than in it: the artifacts these files name do not exist until the tag builds them. Verified on this machine: brew style reports no offenses, brew audit --strict --online exits 0, and brew install initorigin/io-cli/io produces a working io 0.38.0 whose formula names the same aarch64 digest the published SHA256SUMS does. io upgrade from that binary prints brew upgrade io.
Point the tap and the bucket at 0.38.0
0.79.0 declares no breaking change and deletes no line of `src/`. It still costs three tables their totality: `EventKind::Program` and `RUN_PROGRAM_TOOL` are declared unconditionally rather than behind the `codeact` feature this crate does not enable, so not enabling it is no protection. `triage::TRIAGE` gains a `program` row, dispositioned `Silent` — the event says a program was offered or has finished, and the acts it took are not on it, each one re-entering dispatch as its own `tool_call`. `acp_map::MAPPING` gains the same row for the same reason: announcing the program itself would put a tool call in an editor that has no `call_id` and never completes. `acp_map::TOOL_KINDS` gains `run_program` as `execute`, which is what it is. Three hard `53` literals are replaced rather than raised to `54`. Repairing a count gate by raising its count weakens the gate to accommodate the change that fired it, so each now asserts the property the number stood in for: the parsed kinds contain no duplicate, the table has one row per kind the locked harness declares, and the three dispositions partition the table. Two were named by the dependency audit; the third only appeared when the suite ran.
…written `io config set` turned a bare word into TOML source by consulting `configure::kind_of`, and a key that catalogue does not name fell to an arm that quoted it unconditionally. `[app.io-cli.containment]` and `[run.context]` are two such sections, so `max_total_agents 4` reached the file as `"4"`, io-harness refused to deserialize the section, and the session ran on defaults behind a single warning line — a feature switched off in silence. `run.context.max_tokens 120000` was refused outright the same way. The session door never reached that function at all: `Action::Config` handed `write_where` the operator's raw text as TOML source. So `/config k 4` wrote the integer while `io config set k 4` wrote the string, and `/config policy.defaults.net allow` was refused because a bare `allow` is not a TOML value. Two doors, two spellings, neither right. `configure::source_for` is now the one speller and both doors call it. A key the catalogue does not name takes the shape of the word: a bare integer, float or boolean is written as one, everything else is a quoted string. That is not this crate inventing a schema — io-harness still owns what a value means, and `configure::write`'s round trip still refuses and rolls back what it cannot read back, in its own words. A refusal at write time beats a silently wrong file. `configure::composer_words` is the only thing that differs between the doors: a shell has already split and unquoted an argv value, and nothing has done either to a composer line. F1's second half is a source-text gate over `src/main.rs`, because nothing under `tests/` links the driver and a criterion whose only site is the driver is unfalsifiable without one. F2 drives the built binary for the four shapes, which is the door the field test used.
…at the door A verification gate under `io exec` never delivered exit 6. The 2026-09-05 field test configured a criterion that always fails, watched it fail on every step of a nine-minute run, and killed the process with no stdout and no exit code. The cause is not io-harness and not the retry budget. io-harness evaluates the criterion after every step and hands the failure back, which is a reason for the agent to keep going; it returns only when the gate passes or the step cap is reached. The cap is this crate's `contract::MAX_STEPS`, a thousand — and that constant's own documentation argues a thousand is safe because three other bounds exist: the harness stops an agent that stalls, `[run]`'s budgets stop one that spends, and `Ctrl+C` stops one an operator has seen enough of. A gate removes all three at once, and headless removes the third by itself. So a gated headless run the operator gave no `[run]` budget now takes `contract::GATED_MAX_STEPS` instead. Forty is a chosen number and it is named as one: a gate exists to be retried against, so the cap has to leave room for several write-then-check cycles, which io-harness's own twelve does not. Any `max_steps`, `max_duration_secs` or `max_tokens` the operator wrote is a bound they chose and still wins. `app.io-cli.gates.retries` remains unhonoured headless — `gates::may_retry` has one caller and it is the session driver. That is behaviour work rather than a correction, and `US-IO-CLI-0.38.1-I01` defers it with the evidence. Separately: `[app.io-cli]` is one section with no `deny_unknown_fields`, so a single mistyped value anywhere in it fails the whole `Config::app` call and takes the gate down with it. `io exec` never called `settings::stored`, so that ran with no criterion and reported success without verifying anything. It now refuses before the first step when the broken section names a gate, and prints the notice when it does not.
Twenty-five citations name the dependency's source by file and line. The pin moved to 0.79.0, so each was re-derived against that tree rather than having its version string rewritten — `tests/docs.rs`'s `f19` checks the cited version and never the cited line, so a mechanical rewrite would leave a wrong line looking freshly checked. Seven of the cited harness files are byte-identical between 0.78.0 and 0.79.0, established with `diff -q` rather than assumed. Three moved and their numbers were re-derived: `config.rs` by 86 lines around the `read_instructions` region, `contract.rs` by 11, `run.rs` by 5. Four unversioned sub-citations that would otherwise have rotted silently were updated with them. The re-derivation found a citation that was already wrong before this release. `src/commands.rs:1709` cited `config.rs:2808` for where `DEFAULT_INSTRUCTIONS` is read; at 0.78.0 that line sits inside the `[[policy.layers]]` doc block and has nothing to do with instructions. The real site was `2920` then and is `3006` now, so the number had been stale through at least one prior pin with no gate able to see it. Every claim standing beside a citation was re-checked and holds: `Effect` is still not `#[non_exhaustive]` and `ExecMode` still is, the harness still speaks exactly stdio and streamable HTTP, `substitute` still has no tilde branch, `with_args` is still a silent no-op on an HTTP server, `Act::Exec` still matches a binary name and no arguments, and the step and retry defaults are still 8, 12 and 2. The diff is comment-only; no code line was touched. Also here: the CHANGELOG entry for 0.38.1, with both behaviour changes named under a behaviour-change heading, and the live arm for a gated `io exec` that cannot pass — asserted on the exit code and on the wall clock, because an exit code alone would eventually have appeared on the old build too, at a thousand steps.
…inds by literal `f19` sweeps `tests/` as well as `src/` — it was widened in 0.76.0 after a stale citation sat in `tests/adapt.rs` and this gate walked past it — so seven more citations named the unpinned 0.78.0. Each was verified against the 0.79.0 tree rather than having its version string rewritten: `plugin.rs:1097`, `tools/mod.rs:55-58` and `:33-42` and `config.rs:190` are byte-identical between the two versions, established with `diff`, and `config.rs:2934-2937` moved to `:3020-3023`. One of those seven is in `tests/docs.rs` itself, which is **exempt from its own sweep** so that it may quote the citation form it looks for without matching itself. The exemption is argued in the file and the argument holds, but it means `f19` structurally cannot see a stale citation in the one file whose job is catching them. Left as it is here — widening a gate is not patch work — and recorded in the release record. `f8_this_release_has_seen_every_kind_io_harness_emits` is deleted rather than bumped to 54. Its own comment already said the drift check had moved to `tests/triage.rs`, and what it kept was a bare count: it failed saying a number had moved, where `the_table_names_every_kind_the_locked_harness_declares` fails saying *which* kind is new. A count a reader repairs by editing the number is the gate this release has now removed five of. Three of those five were named by the dependency audit before the pin moved. Two were reachable only by running the suite. That is the argument for running the gates rather than trusting an audit of what a pin ought to touch. Also here: the live arm for O3 now measures the step count rather than the wall clock. The first draft timed the run and asserted it finished inside ten minutes; `tests/timing.rs`'s N1 refused it, and N1 is right for a better reason than the one it states — elapsed time is a proxy that a slow host or a chatty model moves, while the step count is the bound itself. If `GATED_MAX_STEPS` fired the run stopped at or under forty steps, where before this release it walked toward a thousand.
An adversarial read of the release diff found three defects behind a green suite,
a green clippy and a green fmt. All three are the shape this review exists to
hunt: a fix applied once, correct where it was applied, and wrong one step to the
side.
**`io resume` is the second headless door and got neither fix.** `Resuming::call`
built its contract without `contract::gated_bound`, and `resume_main` never called
`settings::ungated_by_a_broken_section` — both new functions had exactly one call
site each, on the `io exec` path. A resume is precisely the command an operator
reaches for after the first door ended badly, and the 2026-09-05 field test's run
was *killed*: a killed run is `Pending::Died`, which is carried on rather than
refused. Bounding one door would have fixed the walk to a thousand steps only
until somebody typed the obvious next thing.
**Inference reached into `[app.io-cli]`, where nothing catches a wrong guess.**
`source_for`'s documentation claimed `configure::write`'s round trip refuses a
value io-harness will not read back. That is true of io-harness's own sections and
false of this crate's: the harness reads `[app.io-cli]` as an opaque value, which
is exactly why `max_total_agents = "4"` reached a file and switched fan-out off in
silence. So inferring there bought the same failure with the types reversed —
`app.io-cli.keys.accept 1` is a legitimate single-character binding whose map is
`BTreeMap<String, String>`, and a bare `1` takes the whole section down with it.
`[app.io-cli.containment]`'s five keys join the catalogue, where they always
belonged, and an `app.io-cli.*` key the catalogue does not name is written as a
string as before.
**The argv-door gate could not fail on the defect it names.** It asserted
`out.contains("4")`, and `config get` prints the raw TOML slice — so under the
sabotage its own doc comment described, the file holds `"4"`, `get` prints `"4"`,
and the assertion passes. The only binary-level test of this release's headline
fix was asserting nothing about it. It now compares the value column by equality.
Four further findings, none blocking:
- `gated_bound` re-derived a criterion from the configuration where it should read
the contract's own `verify`. The two differ: `configured` attaches the criterion
through `criterion_for`, which also declines when a review criterion's reviewer
will not build — so the old test would have capped an *ungated* run at forty, a
new failure mode invented for a misconfiguration that previously only cost the
gate.
- `[run] max_steps = 1000` is indistinguishable from the floor, so writing the
documented default down silently yields forty. io-harness's `run` section is
private and cannot be asked whether the key was present. Documented, and pinned
by two test rows so it is a chosen cost — `1001` is honoured as written.
- Both new table rows sat after `dialed`; `EventKind` declares `Program` before
it, and both tables promise enum order so they can be read down the side of
`observe.rs` when the pin moves.
- `composer_words` stripped an outer quote pair without looking between them, so
`"cargo" "test"` became the single word `cargo" "test` — a one-element command
naming a binary that does not exist, which TOML, `Vec<String>` and the round
trip all accept.
Two smaller ones: the partition assertion in `tests/triage.rs` claimed to check
"every kind" when it can only fail on a fourth `Disposition` variant, and the
refusals `source_for` inherited from the argv door told a session operator to run
`config set`. Both reworded.
io-cli 0.38.1
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 PR for 0.38.1.
develop→main.The feature PR is #100, merged with 9/9 checks green across Linux, macOS and Windows.
git diff --quiet feat/0.38.1 developis clean, so this ships exactly the tree those gates ran on — no Dependabot commits landed in between.What ships
io config setwrites the value you typed. A key outside io-cli's catalogue was quoted unconditionally, soapp.io-cli.containment.max_total_agents 4reached the file as"4", io-harness refused to read the section back, and fan-out was off behind one warning line. The session's/confignever went through that code at all and wrote raw text as TOML source — two doors, two spellings, neither right. There is now one speller and both doors call it, with inference stopping at[app.io-cli]because io-harness reads that section as an opaque value and there is no round trip under it.A gated
io execends. A failing verification gate ran until killed; the cause was this crate's own thousand-step cap, whose documentation argues a thousand is safe because three other bounds exist — every one of which a gate removes. Gated runs with no[run]budget now takeGATED_MAX_STEPS, at both headless doors, and an unreadable gate refuses the run rather than reporting success unverified.io-harness 0.79.0, with five count literals replaced by the properties they stood in for and thirty-two citations re-anchored with each line verified.
Behaviour changes
io config geton a key outside the catalogue reads back an unquoted number or boolean where it read back a quoted string.io execthat previously ran to the step cap now exits6. A job relying on a timeout will fail fast instead; a run needing more steps sets[run] max_steps,max_duration_secsormax_tokens.Record
Sealed on the feature branch before the merge, pinned
sha256:ce43c82fa61e22268c7d8f21139dda86f93a727e32a0edbf47db00e23e7991e8,ultraship validateok over 124 checks.The release adopted its own recorded fallback scope mid-development after four of eleven criteria turned out to name upstream surfaces that do not exist; the rest of the 2026-09-05 field test moves to 0.38.2 as working code with its gates on
feat/0.38.2. An adversarial read of the diff found three blockers behind an entirely green suite, all fixed here.