Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
452ae32
delivery push: bound the lifetime of the WORK, not the patience of it…
Sep 14, 2026
f1c9a29
delivery push: the turn is handed back when BOTH sides are finished w…
Sep 14, 2026
0575a69
delivery push: the required cancellation tests, on the real wire
Sep 14, 2026
0995308
delivery push: interrupt the local pack phase, and name the span that…
Sep 14, 2026
8ecc498
delivery push: the killable executor, and the feasibility it rests on
Sep 14, 2026
1731140
delivery push: unknown exit retains the turn, and the reader has a ce…
Sep 14, 2026
2b1dd47
delivery push: the production push runs in the killable child
Sep 14, 2026
443b9ca
delivery push: gate the finite stop on the production path
Sep 14, 2026
c6c4c3f
delivery push: bound the mint and the cleanup, and watch a second del…
Sep 14, 2026
3bd1546
delivery push: print the measured hold and handover
Sep 14, 2026
32be84b
delivery push: bound every parent phase, and release only on an exit …
Sep 14, 2026
a119e94
delivery push: make the shipped binary do a real delivery, verifying …
Sep 14, 2026
1d63e60
delivery push: serialize the shipped-child gates on the one trust sto…
Sep 14, 2026
e41c00c
delivery push: act on a revocation, enforce the protocol, and never s…
Sep 15, 2026
5dffa3e
delivery push: charge the pipe transit to the child that spends it
Sep 15, 2026
a48d1b3
delivery push: observe the second delivery's Poll::Pending, not a mar…
Sep 15, 2026
99596db
delivery push: fill in the shipped-child hold matrix, both legs and b…
Sep 15, 2026
55df3bb
delivery push: mint the shipped child's tokens through the real signe…
Sep 15, 2026
3b58e67
delivery push: pin revoked-vs-deadline on the type, not on a log subs…
Sep 15, 2026
c68c309
delivery push: gate both halves of the arm rule, and unbreak the lib …
Sep 15, 2026
58e59a2
delivery push: wait for the wedged child, do not sleep at it
Sep 15, 2026
71efb46
delivery push: gate the oid comparison on purpose, and stop counting …
Sep 15, 2026
4b7e967
delivery push: measure every parent phase from the deadline, and poll…
Sep 15, 2026
d2ea822
delivery push: gate the revocation bound against a child that is not …
Sep 15, 2026
07063e4
delivery push: gate the production signer under a held reply and a fu…
Sep 15, 2026
ec922ba
delivery push: hold the seat with real libgit2 packing, and watch a s…
Sep 15, 2026
1f9e7ec
delivery push: gate a real task-abort with a second delivery observed…
Sep 15, 2026
71d5599
delivery push: restore the persistent post-mint revocation case along…
Sep 15, 2026
4553cb1
executor: one shared authority-poll deadline, and a reap budget per c…
Sep 15, 2026
25bcf33
executor tests: prove the shared poll deadline as a rule, and measure…
Sep 15, 2026
28ce0e3
executor tests: the cap stops the encoder, and the reap window is one…
Sep 15, 2026
42c52fd
tests: compare abort cleanup against the remaining deadline, not the …
Sep 15, 2026
1c82543
delivery push: hold the delivery inside libgit2's own object walk, no…
Sep 15, 2026
f9512db
delivery push: time the stop against a real signer that cannot answer
Sep 15, 2026
205aee4
delivery push: poll the second delivery THROUGH the reap, and ask the…
Sep 15, 2026
a5a55e4
delivery push: give the deadline its own thread, so the kill stops wa…
Sep 15, 2026
d116d2b
delivery custody: hand the seat on from a thread the stalled supervis…
Sep 15, 2026
2fce05f
delivery custody: prove the custody suite goes red under both mutations
Sep 15, 2026
47c1455
delivery custody: confirm the exit from the thread that killed it
Sep 15, 2026
1c4baba
delivery attribution: a write that failed because we killed the child…
Sep 15, 2026
db0bbb1
T3: make the abort case a test of cancellation, not of patience
Sep 15, 2026
69c0d4f
T2: wire the real PushAuthority, making the call-for-call claim true
Sep 15, 2026
56e542e
mutation rigor: named-assertion reds, enforced counts, hash-bound rec…
Sep 15, 2026
2933dcf
gate-final.log's 101: the cause, established, and what the fix does a…
Sep 15, 2026
fafb4e7
tests: gate my stalled-supervisor test on the feature it needs
Sep 15, 2026
ee2b290
fix(delivery): withhold the seat until cleanup is established, and ch…
Sep 15, 2026
85877db
test(delivery): identify and prove the mechanism that ends an aborted…
Sep 15, 2026
e9dc340
docs(test): name the startup residual and the executor change that wo…
Sep 15, 2026
51fbaed
test(delivery): decide the overlap from evidence a starved observer c…
Sep 15, 2026
7952041
test(delivery): show the overlap oracle going red on a real overlap
Sep 15, 2026
097feab
test(delivery): fail when the observer never looked, instead of passing
Sep 15, 2026
5e61933
fix(delivery): publish the observed EOF before the queued frame, not …
Sep 16, 2026
fa24f4b
test(delivery): wedge the fixture child as ONE process, so the group …
ditto-agent Sep 17, 2026
68971b9
test(delivery): read the abort from the ask that carried the kill, no…
ditto-agent Sep 17, 2026
4850ab4
docs(executor): name the fork-vs-group-kill race as a residual of the…
ditto-agent Sep 17, 2026
53d1322
test(delivery): gate the observed-pending suite on the feature it needs
ditto-agent Sep 17, 2026
0914881
test(delivery): give the observed-pending children a startup allowanc…
ditto-agent Sep 17, 2026
12d29d0
fix(executor): serialize child spawns, so a sibling spawn cannot inhe…
ditto-agent Sep 17, 2026
37d4971
docs(executor): accept the inherited-stdout residual on darwin, and s…
ditto-agent Sep 17, 2026
3ff05c6
test(delivery): gate the custody and unconfirmed-lane suites on the f…
ditto-agent Sep 17, 2026
edac10f
test(delivery): a fixture that answers must read its request first
ditto-agent Sep 17, 2026
f66528e
test(delivery): establish observed-pending by holding the executor's …
ditto-agent Sep 17, 2026
3b4a918
test(delivery): take the hold before the abort, and witness the child…
ditto-agent Sep 17, 2026
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
4 changes: 4 additions & 0 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion crates/maxplayer-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ futures-util = { version = "0.3", default-features = false }
nostr-relay-builder = "0.44"
# Local git-over-HTTPS auth fixture (tests/git_http_fixture): rustls server with an
# rcgen self-signed cert. ring-backed on both — aws-lc-rs is not in the workspace lock.
rcgen = { version = "0.13", default-features = false, features = ["ring"] }
# `pem` so the fixture can hand a test the certificate it actually serves, as a trust anchor file:
# `tests/delivery_push_verified_tls.rs` pushes with verification ON rather than `GIT_SSL_NO_VERIFY`,
# because a delivery-push child is given `SSL_CERT_FILE` and never the bypass. The `pem` crate is
# already in the lock (3.0.6); this adds a feature edge, not a resolution.
rcgen = { version = "0.13", default-features = false, features = ["ring", "pem"] }
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
tokio = { version = "1.52.0", features = ["io-util", "macros", "net", "rt", "rt-multi-thread", "time", "test-util"] }
# The p-gate relay fixture speaks raw NIP-01 over a websocket, because it has to answer a p-gated REQ
Expand Down
3,316 changes: 3,316 additions & 0 deletions crates/maxplayer-core/src/delivery_executor.rs

Large diffs are not rendered by default.

805 changes: 805 additions & 0 deletions crates/maxplayer-core/src/delivery_turn.rs

Large diffs are not rendered by default.

278 changes: 267 additions & 11 deletions crates/maxplayer-core/src/git_transport.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/maxplayer-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ pub mod crossmint;
pub mod crossmint_hop;
pub mod delivery;
pub mod delivery_sentinel;
pub mod delivery_turn;
#[cfg(feature = "git-delivery")]
pub mod delivery_git;
#[cfg(feature = "git-delivery")]
pub mod delivery_executor;
#[cfg(feature = "git-delivery")]
pub mod git_transport;
#[cfg(feature = "git-delivery")]
pub mod delivery_orchestrator;
Expand Down
762 changes: 714 additions & 48 deletions crates/maxplayer-core/src/seller_git.rs

Large diffs are not rendered by default.

493 changes: 417 additions & 76 deletions crates/maxplayer-core/src/seller_node/run.rs

Large diffs are not rendered by default.

154 changes: 154 additions & 0 deletions crates/maxplayer-core/tests/delivery_executor_platform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! Feasibility gate F3 (platform) for the killable delivery-push executor.
//!
//! The design rests on one kernel property: `SIGKILL` cannot be caught, blocked or ignored, and a
//! process that has been reaped is a process that has stopped. Every claim in
//! `delivery_executor`'s documentation — and the whole reason the local pack phase moves into a
//! child at all — reduces to that. So it is exercised here against a child that *deliberately
//! refuses* the polite signal, rather than assumed from the man page.
//!
//! All three platforms this product ships (`.github/release-platforms.json`: two linux-musl targets
//! and aarch64-apple-darwin) are POSIX, so what this file proves on one of them is the same
//! mechanism the others run. There is no Windows artifact and therefore no `TerminateProcess` path
//! to test: a platform that is not on that list is not a platform this executor claims.

#![cfg(feature = "git-delivery")]

use std::io::{BufRead, BufReader};
use std::path::Path;
use std::time::{Duration, Instant};

use maxplayer_core::delivery_executor::{KillableChild, REAP_BOUND};

/// True while a pid still exists. Signal 0 performs the permission and existence checks and delivers
/// nothing, which is exactly the question "is it gone".
fn alive(pid: i32) -> bool {
unsafe { libc::kill(pid, 0) == 0 }
}

/// F3, the core claim: a child that ignores `SIGTERM` and never returns to its own control flow is
/// still ended by the deadline, and the parent learns it has ACTUALLY exited.
///
/// This is the shape of the failure the executor exists for. libgit2's delta search does not ignore
/// a signal by choice, but it refuses to look at the one cancellation answer it is offered
/// (`pack-objects.c:979`), which leaves the parent in the same position: no cooperation available.
#[test]
fn a_child_that_refuses_to_stop_is_stopped_anyway_and_its_exit_is_confirmed() {
// `trap '' TERM` installs the ignore; the loop never checks anything. Nothing short of SIGKILL
// ends this process.
let mut child = KillableChild::spawn(
Path::new("/bin/sh"),
&["-c", "trap '' TERM; while :; do sleep 0.05; done"],
)
.expect("spawn a shell");
let pid = child.pid();

// Give it long enough to have installed the trap and entered the loop, so the kill lands on a
// process that is genuinely refusing, not on one still starting up.
std::thread::sleep(Duration::from_millis(200));
assert!(alive(pid), "the fixture child died before the test began");

let started = Instant::now();
let reap = child
.kill_and_reap()
.expect("a runnable child is reapable well inside the bound");
let measured = started.elapsed();

assert!(
child.is_reaped(),
"kill_and_reap returned without the kernel confirming the exit; the turn must never be \
released on an unconfirmed kill"
);
assert!(
reap <= REAP_BOUND && measured <= REAP_BOUND,
"a runnable child took {measured:?} to die, past the {REAP_BOUND:?} the executor budgets"
);
assert!(
!alive(pid),
"pid {pid} still exists after kill_and_reap said it was gone"
);
}

/// F3, second half: the kill reaches the process GROUP.
///
/// libgit2 spawns nothing today, so this is defence rather than a live bug — but a bound that holds
/// only while no descendant exists is not a bound, and the kill is written to the group precisely so
/// the guarantee does not depend on that remaining true.
#[test]
fn the_kill_reaches_a_descendant_that_would_otherwise_outlive_the_delivery() {
// The shell reports its background grandchild's pid on stdout, then both ignore TERM and block.
let mut child = KillableChild::spawn(
Path::new("/bin/sh"),
&[
"-c",
"trap '' TERM; (trap '' TERM; while :; do sleep 0.05; done) & echo $!; \
while :; do sleep 0.05; done",
],
)
.expect("spawn a shell");
let parent_pid = child.pid();
let stdout = child.stdout().expect("stdout");
let mut reader = BufReader::new(stdout);
let mut reported = String::new();
reader.read_line(&mut reported).expect("the grandchild pid");
let grandchild: i32 = reported.trim().parse().expect("a pid");

std::thread::sleep(Duration::from_millis(200));
assert!(alive(grandchild), "the fixture grandchild never started");

child.kill_and_reap().expect("the group is reapable");

// The grandchild is not ours to reap — it is reparented to init — so allow the kernel a moment
// to tear it down, then require it gone.
let deadline = Instant::now() + Duration::from_secs(2);
while alive(grandchild) && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(10));
}
assert!(
!alive(grandchild),
"grandchild {grandchild} outlived the delivery; the kill did not reach the process group, \
so work could continue after the seat was handed on"
);
assert!(!alive(parent_pid));
}

/// Dropping the handle kills and reaps too: success, error, panic and early return all leave the
/// same state behind, which is what lets the executor promise that no path out of it leaves a
/// delivery packing.
#[test]
fn dropping_the_handle_leaves_no_process_behind() {
let pid = {
let child = KillableChild::spawn(
Path::new("/bin/sh"),
&["-c", "trap '' TERM; while :; do sleep 0.05; done"],
)
.expect("spawn a shell");
let pid = child.pid();
std::thread::sleep(Duration::from_millis(150));
assert!(alive(pid));
pid
};
let deadline = Instant::now() + REAP_BOUND;
while alive(pid) && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(5));
}
assert!(
!alive(pid),
"pid {pid} survived the handle that owned it; a forgotten child is a delivery still running"
);
}

/// A second reap is a no-op rather than a second kill: once the exit is confirmed the pid may have
/// been reused, and signalling it again would be signalling a stranger.
#[test]
fn reaping_twice_does_not_signal_a_reused_pid() {
let mut child =
KillableChild::spawn(Path::new("/bin/sh"), &["-c", "sleep 30"]).expect("spawn a shell");
child.kill_and_reap().expect("first reap");
assert!(child.is_reaped());
let again = child.kill_and_reap().expect("second reap is a no-op");
assert_eq!(
again,
Duration::ZERO,
"the second reap did work; a confirmed-dead child must never be signalled again"
);
}
Loading
Loading