From 387a4a6417d63d8072958007a31e67ea2648aeb8 Mon Sep 17 00:00:00 2001 From: schickling-assistant <261620128+schickling-assistant@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:06:16 +0200 Subject: [PATCH] test(resync): prove a resync record reaches DING as stream work `tests/resync.rs` deferred the DING wake to "the existing delivery suite", but that suite only covers a publicly emitted stream event, which reaches the inbox through a different admission path than `emit_builtin_resync` and is read with `message::list_inbox` rather than the `new_arrivals` scan the live loop actually uses. Nothing proved a resync record survives that scan. Add `tests/resync_ding.rs` covering the join against the real supervisor ingress, and register the target in the derivation that already gates live resync integration. Verified the test is load-bearing: filtering resync out of `new_arrivals` makes it fail on the arrival assertion. agent-identity: dev3.direct.claude.gvacdkt7 agent-persona: generalist agent-supervisor: unavailable agent-tool: Claude Code agent-tool-version: 2.1.237 agent-runtime: Claude Code 2.1.237 tooling-profile: dotfiles@cab57ad --- flake.nix | 2 + tests/resync.rs | 5 +- tests/resync_ding.rs | 122 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 tests/resync_ding.rs diff --git a/flake.nix b/flake.nix index 3dd7bd6e..211da0da 100644 --- a/flake.nix +++ b/flake.nix @@ -171,6 +171,8 @@ "--test" "resync" "--test" + "resync_ding" + "--test" "profile_wasm" ]; }); diff --git a/tests/resync.rs b/tests/resync.rs index b224c288..fe3d0fd5 100644 --- a/tests/resync.rs +++ b/tests/resync.rs @@ -1,6 +1,7 @@ //! Resync end-to-end: carrier change → classified digest-keyed superseded emit → inbox record -//! ([`06-resync`](../docs/vrs/06-resync/spec.md)). The DING wake past the inbox record is owned by -//! the existing delivery suite; this proves the resync-specific half against the real ingress. +//! ([`06-resync`](../docs/vrs/06-resync/spec.md)). This proves the resync-specific ingress half +//! against the real supervisor; the DING wake past the inbox record is proven by +//! [`tests/resync_ding.rs`], and the generic delivery transport by the existing delivery suite. use std::fs; use std::path::{Path, PathBuf}; diff --git a/tests/resync_ding.rs b/tests/resync_ding.rs new file mode 100644 index 00000000..e32dcc99 --- /dev/null +++ b/tests/resync_ding.rs @@ -0,0 +1,122 @@ +//! The join between resync ([`06-resync`](../docs/vrs/06-resync/spec.md)) and DING delivery. +//! +//! [`tests/resync.rs`] proves the ingress half and defers the wake to "the existing delivery +//! suite". That suite proves the wake for a *publicly* emitted stream event +//! ([`tests/event_e2e.rs::event_emit_cli_returns_a_stable_json_receipt_and_ding_marks_the_record`]), +//! which reaches the inbox through a different admission path than a built-in resync record, and +//! reads the inbox with `message::list_inbox` rather than the arrival scan the live loop uses. +//! +//! So nothing proved that a resync record survives DING's own `new_arrivals` scan — the place a +//! stream predicate would silently swallow it — or that it renders as stream work. This file +//! closes that seam against the real supervisor ingress. + +use std::collections::HashSet; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; + +fn write_agent(root: &Path) -> PathBuf { + let dir = root.join("agents/hetz/worker"); + fs::create_dir_all(&dir).unwrap(); + fs::write( + dir.join("agent.kdl"), + r#"agent "worker" { + host "hetz" + command "agent" + resource "goal" uri="resources/goal.md" reason="Mission." +}"#, + ) + .unwrap(); + st2::event::publish_owner_binding_for_test(root, "hetz").unwrap(); + dir +} + +fn resync_records(inbox: &Path) -> usize { + fs::read_dir(inbox) + .map(|entries| { + entries + .flatten() + .filter(|entry| { + fs::read_to_string(entry.path()) + .is_ok_and(|contents| contents.contains("stream: resync")) + }) + .count() + }) + .unwrap_or(0) +} + +fn wait_for(condition: impl Fn() -> usize, expected: usize) -> bool { + let deadline = Instant::now() + Duration::from_secs(15); + while Instant::now() < deadline { + if condition() >= expected { + return true; + } + std::thread::sleep(Duration::from_millis(50)); + } + condition() >= expected +} + +/// A carrier change becomes a `[DING]` notice: the record is a new arrival to DING's own scan, and +/// it renders as stream work rather than as a message from an unknown peer. +#[test] +fn a_resync_record_is_a_ding_arrival_and_renders_as_stream_work() { + let catalog = tempfile::tempdir().unwrap(); + let agent_dir = write_agent(catalog.path()); + let inbox = agent_dir.join("resources/inbox"); + + let supervisor = + st2::resync::ResyncSupervisor::spawn(catalog.path().to_path_buf(), "hetz".to_owned()); + assert!( + supervisor + .refresh( + &st2::discover_strict(catalog.path()).specs, + "hetz", + &[], + &[], + ) + .is_empty() + ); + std::thread::sleep(Duration::from_millis(300)); + + // Start DING's seen-set from the current unread set, exactly as `run_ding` does at startup, so + // the arrival below is the only thing this scan can report. + let mut seen: HashSet = HashSet::new(); + let backlog = st2::ding::new_arrivals(&inbox, &mut seen); + assert!( + backlog.is_empty(), + "seeding is silent, so there is no backlog" + ); + + let goal = agent_dir.join("resources/goal.md"); + fs::create_dir_all(goal.parent().unwrap()).unwrap(); + fs::write(&goal, "ship the thing\n").unwrap(); + assert!( + wait_for(|| resync_records(&inbox), 1), + "the goal carrier change must reach the inbox" + ); + + let arrivals = st2::ding::new_arrivals(&inbox, &mut seen); + assert_eq!( + arrivals.len(), + 1, + "the resync record must reach DING's arrival scan, not be filtered out of it" + ); + let record = &arrivals[0]; + assert_eq!(record.stream.as_deref(), Some("resync")); + assert!( + record.event_id.is_some(), + "a resync record carries an event id" + ); + + let notice = st2::ding::poke_text(catalog.path(), "hetz", "hetz.worker", record); + assert!( + notice.starts_with("[DING] » hetz.worker/resync: resource goal changed"), + "a resync record renders as stream work: {notice}" + ); + + // The arrival is consumed exactly once: a second scan re-poking it would duplicate the wake. + assert!( + st2::ding::new_arrivals(&inbox, &mut seen).is_empty(), + "a delivered resync record must not be reported as a new arrival again" + ); +}