diff --git a/Cargo.lock b/Cargo.lock index cde120563..e76ab59fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2564,7 +2564,7 @@ dependencies = [ [[package]] name = "maxplayer" -version = "0.5.9" +version = "0.5.10" dependencies = [ "cashu", "cdk", @@ -2583,7 +2583,7 @@ dependencies = [ [[package]] name = "maxplayer-core" -version = "0.5.9" +version = "0.5.10" dependencies = [ "async-trait", "base64 0.22.1", @@ -2621,7 +2621,7 @@ dependencies = [ [[package]] name = "maxplayer-desktop" -version = "0.5.9" +version = "0.5.10" dependencies = [ "eframe", "maxplayer-core", @@ -2630,7 +2630,7 @@ dependencies = [ [[package]] name = "maxplayer-evals" -version = "0.5.9" +version = "0.5.10" dependencies = [ "maxplayer-core", "serde", @@ -2639,7 +2639,7 @@ dependencies = [ [[package]] name = "maxplayer-relay-write-policy" -version = "0.5.9" +version = "0.5.10" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 95a3de57c..98a11c2cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ default-members = [ resolver = "3" [workspace.package] -version = "0.5.9" +version = "0.5.10" edition = "2024" description = "Maxplayer" repository = "https://github.com/MakePrisms/maxplayerai" diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0ec1a4f8a..ee05bec20 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,60 @@ +## v0.5.10 + +A seller's delivery lock is now bounded by the lifetime of the push *work*, not by the patience of +whatever async arm started it. A caller that times out no longer hands the seat to the next delivery +while bytes from the old one are still on the wire, and a revoked push stops at the next boundary +instead of doing its local work when the slot comes free. Nothing about relay policy, token expiry +or the per-leg authorization from v0.5.9 changes. + +### The delivery turn is bounded by the work, not by its caller (#1006) + +The seat's delivery lock was released when the future that started the push finished. Three things +followed from that. A push revoked while parked on a blocking thread still occupied the turn, and +still performed its local work once the thread woke. Queued and pre-HTTP work honoured neither +cancellation nor a deadline. And an async supervisor cancelled at an await took the lock guard with +it while its own blocking thread was still mid-upload, which let the next delivery open a second +`git-receive-pack` against the same remote. + +The turn is now handed back only when **both** sides are done with it: the work has actually stopped +(or provably never started), **and** the supervising arm has left the excluded section. Either +condition alone has been a bug — supervisor-alone was the original defect, and work-alone is its +mirror. A caller's timeout does not free the seat for live work: it revokes, declares its own side +finished, and returns `TimedOut`. + +New `crates/maxplayer-core/src/delivery_turn.rs` carries the exclusion token itself — the lock's +owned guard, moved in, so a dying supervisor cannot take exclusion with it — plus an absolute +deadline fixed when the turn is created. `begin()` is queue admission on the blocking thread, and +`RunningWork` is dropped on the thread that did the work. `seller_git` composes one gate for the +transport: the delivery's authority first, the turn's lifetime second. + +That gate is asked at queue admission, before the push-config rewrite, before pack generation, at +pack negotiation, before the mint so a dead delivery never joins the signer queue, again after the +mint because the queue wait is exactly where a turn dies unnoticed, on every buffered pack chunk, +and before every wire request. + +The drain bound is stated as a constant rather than inferred: +`DELIVERY_DRAIN_BOUND = DELIVERY_PUSH_TIMEOUT + DEFAULT_HTTP_LEG_TIMEOUT` = 150s + 120s = 270s, with +a build-time assertion on the sum and a test pinning the literal. It is not an HTTP timeout — it is +the work's absolute deadline, checked at every boundary above, plus the one in-flight leg whose +bytes cannot be recalled. + +One span stays outside that guarantee and is documented rather than hidden: libgit2's delta search +discards its cancellation answer, so it is bounded by the delivery's object list rather than by a +clock. It is measured from its true start and an overrun is reported with the number; +`UNINTERRUPTIBLE_DELTA_BUDGET = 5s` is what that span is expected to fit in, held finite and +strictly inside the work deadline by a second compile-time assertion. A hard bound there needs a +killable executor — the local phase in a child process, the deadline enforced by a signal — which is +an architectural change, written down instead of smuggled in. + +The tests run the real signer actor, the real transport and a real HTTPS git fixture, with no sleep +used as scheduling proof: the fixture parks a chosen request and announces it, and ordering is read +off one journal. They cover cancellation before dispatch (`.git/config` byte-identical, no mint, no +dial), cancellation during the signer queue wait with the mint parked inside the round trip, +cancellation mid-flight with the advertisement held at the server, and a real GET and POST across a +caller timeout — the POST held open at the relay while the arm that started it times out, a second +real delivery launched into that window and proved pending on acquisition, landing its ref only +after the abandoned upload stops. Peak concurrency stays 1 throughout. + ## v0.5.9 Every delivery push now mints its authorization at the request it is sent on, and a contained job diff --git a/npm/cli-darwin-arm64/package.json b/npm/cli-darwin-arm64/package.json index fbcc54df3..8eb4c3ac1 100644 --- a/npm/cli-darwin-arm64/package.json +++ b/npm/cli-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@maxplayerai/darwin-arm64", - "version": "0.5.9", + "version": "0.5.10", "description": "maxplayer binary for darwin-arm64 — payload package, install `maxplayer` instead", "license": "MIT OR Apache-2.0", "repository": { diff --git a/npm/cli-linux-arm64/package.json b/npm/cli-linux-arm64/package.json index f19db7407..82f2282af 100644 --- a/npm/cli-linux-arm64/package.json +++ b/npm/cli-linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@maxplayerai/linux-arm64", - "version": "0.5.9", + "version": "0.5.10", "description": "maxplayer binary for linux-arm64 — payload package, install `maxplayer` instead", "license": "MIT OR Apache-2.0", "repository": { diff --git a/npm/cli-linux-x64/package.json b/npm/cli-linux-x64/package.json index 0207f43ca..25a303574 100644 --- a/npm/cli-linux-x64/package.json +++ b/npm/cli-linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@maxplayerai/linux-x64", - "version": "0.5.9", + "version": "0.5.10", "description": "maxplayer binary for linux-x64 — payload package, install `maxplayer` instead", "license": "MIT OR Apache-2.0", "repository": { diff --git a/npm/maxplayer/package.json b/npm/maxplayer/package.json index 84d0e72b1..7fdfcedc2 100644 --- a/npm/maxplayer/package.json +++ b/npm/maxplayer/package.json @@ -1,6 +1,6 @@ { "name": "maxplayer", - "version": "0.5.9", + "version": "0.5.10", "description": "CLI and MCP server for the maxplayer agent marketplace — buy and sell agent work", "license": "MIT OR Apache-2.0", "keywords": ["maxplayer", "mcp", "nostr", "cashu", "agent"], @@ -13,9 +13,9 @@ }, "files": ["bin/maxplayer.js", "README.md", "LICENSE-MIT", "LICENSE-APACHE"], "optionalDependencies": { - "@maxplayerai/linux-x64": "0.5.9", - "@maxplayerai/linux-arm64": "0.5.9", - "@maxplayerai/darwin-arm64": "0.5.9" + "@maxplayerai/linux-x64": "0.5.10", + "@maxplayerai/linux-arm64": "0.5.10", + "@maxplayerai/darwin-arm64": "0.5.10" }, "//": "engines is deliberately >=18 and NOT 22 (issue #696). bin/maxplayer.js is the only JS this package ships, and its real floor is Node 14.18: the highest-versioned things in it are the `node:` prefix in require() (14.18) and `??` (14.0). spawnSync, require.resolve, os.constants.signals and optional catch binding are all older, and there is no ESM, no top-level await, no built-in fetch/glob use. Nothing above 18 was found. Do not raise this to match prose that claims 22 — the docs were wrong and were corrected to 18+ instead.", "engines": { diff --git a/web/app/.well-known/skills/muse-buyer/references/settlement.md b/web/app/.well-known/skills/muse-buyer/references/settlement.md index 91a77ee8a..cdd76f698 100644 --- a/web/app/.well-known/skills/muse-buyer/references/settlement.md +++ b/web/app/.well-known/skills/muse-buyer/references/settlement.md @@ -1,7 +1,7 @@ # Settlement: when money actually moves, and what to do when it half-works Read this before telling a human where their sats went. Every statement here is -checked against maxplayer 0.5.9 source in this repository; the file paths are named +checked against maxplayer 0.5.10 source in this repository; the file paths are named so you can check them yourself. ## Money can move without you @@ -65,7 +65,7 @@ one. A free job (`payment: "none"`, which requires `amount_sats: 0`) runs the **same** acceptance, integrity and execution-sentinel checks, and the same materialisation. -What it does not run is the payment leg: at 0.5.9 a free bind is routed straight +What it does not run is the payment leg: at 0.5.10 a free bind is routed straight through verification and materialisation (`collect.rs`), and the response reports - `state: "none"`, diff --git a/web/app/.well-known/skills/muse-buyer/references/verification.md b/web/app/.well-known/skills/muse-buyer/references/verification.md index b00026f61..c5ee7e8a4 100644 --- a/web/app/.well-known/skills/muse-buyer/references/verification.md +++ b/web/app/.well-known/skills/muse-buyer/references/verification.md @@ -3,14 +3,14 @@ Read this before you rely on a step. Every claim in the skill that could cost money carries one of three tiers, and the tier is stated here. -**Pinned base: maxplayer 0.5.9** — the version in this repository's `Cargo.toml` at the +**Pinned base: maxplayer 0.5.10** — the version in this repository's `Cargo.toml` at the commit this page ships from. Check yours before trusting anything below: ```bash maxplayer --version ``` -## Tier 1 — source-checked in this repository at 0.5.9 +## Tier 1 — source-checked in this repository at 0.5.10 Read from the code, not from anyone's report. diff --git a/web/app/.well-known/skills/muse-buyer/skill.md b/web/app/.well-known/skills/muse-buyer/skill.md index bc85e301b..41a1670c7 100644 --- a/web/app/.well-known/skills/muse-buyer/skill.md +++ b/web/app/.well-known/skills/muse-buyer/skill.md @@ -13,10 +13,10 @@ money the human did not agree to. job is in this file and its two references — no other skill has to be installed, and none of the instructions here defer to one. If another page disagrees with this one about setup, targeting, awarding or retrying, **this bundle governs** for a Muse -account on 0.5.9. (`maxplayer buyer serve --home` in older buyer material is one such -stale instruction: at 0.5.9 the flag is refused.) +account on 0.5.10. (`maxplayer buyer serve --home` in older buyer material is one such +stale instruction: at 0.5.10 the flag is refused.) -**Pinned to maxplayer 0.5.9** — this repository's version. Run `maxplayer --version` +**Pinned to maxplayer 0.5.10** — this repository's version. Run `maxplayer --version` first; on another version, re-read the tool schemas before trusting the examples, which are checked against the source in this tree, not yours. Verification status for every claim, including what is UNPROVEN: