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

## [Unreleased]

## [0.0.49] - 2026-08-31

A software operation applied through `ai-stp` now answers with the
plan digest it was handed. It did not before, in any of the seven, so `harness
install`, `harness update` and `harness remove` through the consumer refused
after the program had already been installed: the effect landed and the
operation stayed unverified over a prefix holding a working build. The
configuration operations always carried the echo; the program lifecycle owes the
same one, and the contract says so.

The consumer released tolerance ahead of this, as 0.0.12: a missing echo is
accepted for the three program operations, a mismatched one is still refused,
and a configuration operation must still carry both. That tolerance exists in
0.0.12 and in no earlier published version, so 0.0.11 and before refuse this
release's predecessor and 0.0.12 accepts both.

Nothing here could have found it. Every test asked whether the provider does
what its own answer says, and it did -- the answer was consistent with itself and
identical across all seven. The consumer found it by running the released
0.0.48 through its own install path. The new test asserts the echo against the
digest the wire was handed, for all three program operations.

Beside it, the rendered lock file is now projected from this workspace's own
committed resolution rather than re-resolved inside each tree, so a published
tree no longer depends on which machine rendered it; twenty-one recorded product
measurements were re-asked against the artifacts their baselines pin, three of
which returned a false negative because the search term was a label this project
invented rather than a string the product carries; and the schema survey was
completed for the four harnesses that had never been asked, including one that
publishes a manifest schema this provider must not declare.

## [0.0.48] - 2026-08-31

Antigravity's official updater publishes native Windows x86_64 and
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.48"
version = "0.0.49"
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.48" }
provider-v3 = { path = "crates/provider-v3", version = "0.0.48" }
harness-runtime = { path = "crates/harness-runtime", version = "0.0.48" }
setup-core = { path = "crates/setup-core", version = "0.0.49" }
provider-v3 = { path = "crates/provider-v3", version = "0.0.49" }
harness-runtime = { path = "crates/harness-runtime", version = "0.0.49" }

[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.48 \
ghcr.io/nddev-opennetwork/opencode-setup-system:0.0.49 \
status --target /config/<dir> --json
```

Expand Down
32 changes: 16 additions & 16 deletions SUPPORT.md

Large diffs are not rendered by default.

87 changes: 86 additions & 1 deletion crates/harness-runtime/src/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,7 @@ fn apply(
// touches the namespaces the effect machinery below exists to mutate, so it
// parts company here rather than pretending to be one of those effects.
if Operation::SOFTWARE.contains(&operation) {
return software::apply(harness, prefix, operation, downloaded);
return apply_software(harness, prefix, operation, plan_digest, downloaded);
}
if let Some(named) = prefix {
return Err(Error::refuse(
Expand Down Expand Up @@ -1166,6 +1166,46 @@ pub(crate) fn perform(
}))
}

/// The program lifecycle's answer, carrying the plan echo the wire owes.
///
/// The echo is added here rather than inside `software::apply`, because it is a
/// fact about the wire call and not about the prefix: this layer is where the
/// plan artifact and its digest exist, and `software::apply` deliberately knows
/// about neither.
///
/// **It was missing from both of `software::apply`'s answer shapes, and the cost
/// was carried by every one of the seven.** The consumer requires the echo for
/// every operation, so `harness install`, `harness update` and `harness remove`
/// through `ai-stp` refused *after* the program was installed -- the effect
/// landed and the operation stayed `applied_unverified` over a prefix holding a
/// working build. Reported by the consumer's own session on 2026-08-31 after
/// running the released `0.0.48` through `harness install`, and confirmed here
/// by reading both sites rather than by taking the report: the configuration
/// answer carries `plan_digest`; neither software answer did.
///
/// Nothing on this side could have raised it. The producer tests asked whether
/// the provider does what its own answer says, which it did, and the contract
/// sentence saying the program lifecycle carries *"the same journal, backup and
/// plan-digest"* was read as being about `plan-operation` alone. That is why the
/// test beside it asserts the **wire** shape against the contract's list rather
/// than against this function's output.
fn apply_software(
harness: &Harness,
prefix: Option<&Path>,
operation: Operation,
plan_digest: &str,
downloaded: &[std::path::PathBuf],
) -> Result<serde_json::Value> {
let mut answer = software::apply(harness, prefix, operation, downloaded)?;
if let Some(fields) = answer.as_object_mut() {
fields.insert(
"plan_digest".to_owned(),
serde_json::Value::String(plan_digest.to_owned()),
);
}
Ok(answer)
}

/// What the target's own state says before this operation touches it.
///
/// The setup it names, the definition digest that setup had, and the files this
Expand Down Expand Up @@ -5469,6 +5509,51 @@ mod tests {
);
}

#[test]
fn every_software_apply_echoes_the_plan_digest_it_was_handed() {
// **Red before 2026-08-31, and no test here could have been.**
//
// The consumer requires the plan echo on *every* apply, and neither of
// `software::apply`'s two answer shapes carried it. So `harness
// install`, `harness update` and `harness remove` through `ai-stp`
// refused after the program had already been installed: the effect
// landed, and the operation stayed `applied_unverified` over a prefix
// holding a working build. All seven released providers, and it reached
// this repository as a measurement from the consumer's own session
// rather than from anything here.
//
// Why nothing caught it: every test around this one asks whether the
// provider does what its own answer *says*, and it did. The contract
// owns the list of what an answer must carry -- "the same journal,
// backup and plan-digest" -- and that sentence was read as being about
// `plan-operation`. So this asserts the echo against the digest the wire
// was handed, which is the only value that can disagree, and it does it
// for all three operations rather than the one that was reported.
for operation in ["software_install", "software_update", "software_remove"] {
let target = seeded(&format!("software-echo-{operation}"));
let file = downloaded(&target, TEST_PAYLOAD);
if operation != "software_install" {
plan_then_install(&target, "software_install", Some(&file));
}
let prefix = ready_prefix(&target);
let planned = software_plan(&target, operation);
assert_eq!(planned["state"], "planned", "plan refused: {planned}");
let digest = planned["plan_digest"].as_str().unwrap().to_owned();
let artifact = if operation == "software_remove" {
None
} else {
Some(file.as_path())
};
let applied = apply_planned(&target, &prefix, operation, &planned, artifact);
assert_eq!(applied["state"], "verified", "apply refused: {applied}");
assert_eq!(
applied["plan_digest"],
serde_json::Value::String(digest),
"{operation} answered without the plan echo the wire owes: {applied}"
);
}
}

#[test]
fn installing_software_spends_no_backup_slot() {
// Ten slots exist and they hold configuration. If a software install
Expand Down
2 changes: 1 addition & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# powershell -ExecutionPolicy Bypass -File install.ps1 -Version 0.1.0
[CmdletBinding()]
param(
[string]$Version = "0.0.48",
[string]$Version = "0.0.49",
[string]$InstallDir = "$env:LOCALAPPDATA\Programs\opencode-setup-system"
)
$ErrorActionPreference = "Stop"
Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ set -eu

REPO="NDDev-OpenNetwork/opencode-setup-system"
BINARY="opencode-setup-system"
VERSION="${1:-0.0.48}"
VERSION="${1:-0.0.49}"
PREFIX="${OPENCODE_INSTALL_DIR:-$HOME/.local/bin}"

case "$(uname -s)" in
Expand Down
21 changes: 20 additions & 1 deletion provider-kit/v3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- `manifest.json` фиксирует команды, operations, native vocabularies, provenance и
network phases.
- `provider-info.schema.json` является закрытой JSON Schema ответа `provider-info`.
- `status-response.schema.json` является закрытой JSON Schema ответа `status`.
- `conformance-cases.json` перечисляет обязательные fail-closed классы.
- `SHA256SUMS` привязывает точные bytes остальных артефактов.
- `KIT-IDENTITY.json` называет ровно одну ревизию комплекта: агрегатный digest
Expand All @@ -31,7 +32,25 @@

Читателю комплекта принадлежит другая проверка, и она не требует ничего чужого:
`SHA256SUMS` привязывает точные байты остальных артефактов, а `KIT-IDENTITY.json`
называет агрегатный digest от канонических байт `SHA256SUMS`. Для этого комплект
называет SHA-256 самого файла `SHA256SUMS` без нормализации. Для этого комплект
их и несёт.

## Status response

Всегда обязательны protocol/provider/harness identity, canonical target,
`state`, оба target digests, `cleanup_state`, `journal`, `backups`,
`provider_state` и `shadowed_by`.

Полный flat provenance обязателен условно: только когда вложенный
`provider_state` сообщает `present=true`, `readable=true` и
`drift_state=clean`. Тогда ответ называет state schema, provider build/release,
setup definition/version/passport, components, bundle/artifact, projection/plan,
operation/precondition, native ownership, written paths, backup и previous
verified identity. Missing, foreign-schema и local-drift состояния не заполняют
эти поля выдуманными значениями.

Эта ревизия сначала публикует schema и conformance cases. Consumer enforcement
включается только после того, как provider systems вендорят и выпускают kit;
одного присутствия файла недостаточно, чтобы считать старый status отвергнутым.

Редактировать generated JSON вручную нельзя.
5 changes: 3 additions & 2 deletions references/opencode-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
],
"verified_at": "2026-08-29T10:56:16+00:00",
"native_surfaces": {
"verified_at": "2026-08-27",
"verified_at": "2026-08-31",
"config_home": "~/.config/opencode",
"surfaces": [
{
Expand Down Expand Up @@ -212,7 +212,8 @@
{
"path": "$HOME/.claude/skills",
"reason": "OpenCode also reads Claude Code's skills directory for compatibility -- `.claude/skills/<name>/SKILL.md` is a path literal in the pinned binary, and the vendor lists it as *Global Claude-compatible*. Another product's home, never this provider's to own, and recorded because claude-setup-system owns `skills` there.",
"source": "https://opencode.ai/docs/skills/ and measured from the pinned artifact, digest verified before reading (opencode 1.18.24)"
"source": "https://opencode.ai/docs/skills/ and measured from the pinned artifact, digest verified before reading (opencode 1.18.24)",
"note": "**Re-asked at 1.18.25 on 2026-08-31.** The pinned `opencode-linux-x64-1.18.25.tgz` was fetched and its sha256 checked against this baseline's own artifact table before a byte was read; `.claude/skills` is still there. Searched with three controls -- `nddev_invented_reference_line`, `~/.nddev-not-a-home/skills/` and `NDDEV_INVENTED_SURFACE` -- each zero in the same bytes, so the search discriminates."
},
{
"path": "opencode-runtime-state",
Expand Down
Loading