Skip to content
Merged
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
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,34 @@ sibling project the same week this note was added.

## [Unreleased]

## [0.0.58] - 2026-09-02

A removal answers three ways when nothing records what this build
wrote. `remove` takes the declared namespaces whole, which is exact for a
target this provider wrote and is guessing at one it never touched: measured on
released 0.0.57, a target holding only a person's own configuration answered
*"Removed everything <provider> owns"* and took it, recoverable from the
capture and under a sentence that did not describe what happened.

Now: a record removes, as before. A target with nothing this provider declares
on it is silent and unchanged, because "already removed" must stay a no-op or
a repeat becomes an error where nothing happened. A target with declared
entries and no record is refused by name -- `state: refused`,
`unsupported_operation`, exit 0 -- with the entries it would have taken in the
detail, so a consumer can show a person what it declined to take. The same
three answers on the surface a person types and on the wire, and the question
is asked again under the lock, because the state file is outside the target's
identity on purpose and a record can be deleted between a plan and its apply.

The shape was agreed with the consumer before it shipped, and their half went
first: their reader now carries a refusal's reason and detail through to a
person rather than collapsing every non-planned answer into one sentence about
shape.

Every declared surface of all seven products is re-measured in the bytes each
current pin names, with an invented control absent in all of them: 72 of 72
present.

## [0.0.57] - 2026-09-02

A kind declared only by a scoped profile validates and plans under
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [
]

[workspace.package]
version = "0.0.57"
version = "0.0.58"
edition = "2024"
rust-version = "1.89"
license = "AGPL-3.0-or-later"
Expand All @@ -23,9 +23,9 @@ sha2 = "0.11"
# `setup-core::archive`); an inflate loop is not, because its bugs are
# memory-safety bugs and it is not improved by being hand-written here.
miniz_oxide = "0.9"
setup-core = { path = "crates/setup-core", version = "0.0.57" }
provider-v3 = { path = "crates/provider-v3", version = "0.0.57" }
harness-runtime = { path = "crates/harness-runtime", version = "0.0.57" }
setup-core = { path = "crates/setup-core", version = "0.0.58" }
provider-v3 = { path = "crates/provider-v3", version = "0.0.58" }
harness-runtime = { path = "crates/harness-runtime", version = "0.0.58" }

[workspace.lints.rust]
unsafe_code = "forbid"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ release is a convenience, not the authorised copy.

```bash
docker run --rm -v "$HOME/.config:/config" \
ghcr.io/nddev-opennetwork/opencode-setup-system:0.0.57 \
ghcr.io/nddev-opennetwork/opencode-setup-system:0.0.58 \
status --target /config/<dir> --json
```

Expand Down
38 changes: 38 additions & 0 deletions crates/harness-runtime/src/human.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,44 @@ fn restore(harness: &Harness, target: &Path, backup: Option<String>) -> Result<(
}

fn remove(harness: &Harness, target: &Path) -> Result<()> {
// **A removal with no record of its own is not this provider's removal.**
// `remove_managed` walks the declared namespaces and takes each whole, which
// is exactly right for a target this build wrote: the setup owns those
// entries and the state file says so. On a target it never wrote, the same
// walk empties somebody else's home while the report says "removed
// everything <provider> owns" -- and it owned nothing here. Measured
// 2026-09-02 on a target holding only a person's own `config.toml`,
// `AGENTS.md` and `prompts/`: all three went, recoverable from the slot the
// capture took, under a sentence that did not describe what happened.
//
// The scoped branch of `remove_managed` already refuses for the same
// reason, in the same words -- *this build does not know what it wrote* --
// and this is that refusal on the surface a person types. The wire is not
// changed: a consumer's removal is authorized by a plan it made against a
// target it installed, and its own flow writes state first.
// Three answers, and the middle one is why this is not a flat refusal: a
// target with nothing of ours on it is *already removed*, and saying so
// quietly keeps a repeat from becoming an error where nothing happened.
// The consumer chose this shape on 2026-09-02 and takes the same three
// over the wire.
let resolved = Target::resolve(target, harness.control_directory)?;
match wire::classify_removal(harness, &resolved, HUMAN_SCOPE)? {
wire::Removal::Recorded => {}
wire::Removal::NothingHere => {
println!(
"Nothing to remove: {} has applied no setup at {}, and none of \
what it declares is here.",
harness.provider_id,
resolved.root().display()
);
return Ok(());
}
wire::Removal::WouldTakeUnrecorded(present) => {
return Err(wire::unrecorded_removal_refusal(
harness, &resolved, &present,
));
}
}
let report = mutate(
harness,
target,
Expand Down
Loading
Loading