test: isolate local rules and reduce repeated fixture setup

This commit is contained in:
2026-10-16 15:04:52 +09:00
parent 6667985883
commit ffaaf2ecb0
22 changed files with 1692 additions and 825 deletions
+10
View File
@@ -91,6 +91,16 @@ debug = "line-tables-only"
[profile.dev.package."*"]
debug = false
# Keep the real SQLite engine in schema/transaction tests without paying for
# an unoptimized C interpreter on every statement. Rust test assertions stay enabled.
[profile.test.package.libsqlite3-sys]
opt-level = 2
# Schema/authority integration tests execute substantial parsing and validation.
# Optimize that code without disabling test/debug assertions or overflow checks.
[profile.test.package.yoi-workspace-server]
opt-level = 1
[workspace.dependencies]
# Internal crates
client = { path = "crates/client" }
+42 -9
View File
@@ -1,13 +1,38 @@
//! Compile the real public macro through a renamed dependency in an isolated consumer.
//! Only Cargo artifacts are shared; Cargo checks the fresh consumer on every invocation.
use std::{fs, path::PathBuf, process::Command};
struct Fixture(PathBuf);
impl Fixture {
fn new() -> Self {
for attempt in 0..1000 {
let path = std::env::temp_dir().join(format!(
"api-macros-renamed-dependency-{}-{attempt}",
std::process::id()
));
match fs::create_dir(&path) {
Ok(()) => return Self(path),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => panic!("create renamed dependency fixture: {error}"),
}
}
panic!("could not allocate a renamed dependency fixture directory");
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[test]
fn macro_expansion_resolves_a_renamed_support_dependency() {
let fixture = std::env::temp_dir().join(format!(
"api-macros-renamed-dependency-{}",
std::process::id()
));
let _ = fs::remove_dir_all(&fixture);
fs::create_dir_all(fixture.join("src")).expect("create fixture source directory");
let fixture = Fixture::new();
let fixture = &fixture.0;
fs::create_dir(fixture.join("src")).expect("create fixture source directory");
let package_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let package_path = package_path
@@ -49,13 +74,21 @@ pub fn operation_count() -> usize {
)
.expect("write fixture source");
// Keep nested Cargo checks off the outer build's target lock. Cargo owns cache
// invalidation and locking between concurrent consumers, not a saved
// test-success marker.
let executable = std::env::current_exe().expect("current test executable");
let target_dir = executable
.parent()
.and_then(|deps| deps.parent())
.expect("test executable has a profile directory")
.join("compile-tests/api-macros-renamed");
let output = Command::new(env!("CARGO"))
.args(["check", "--quiet", "--offline"])
.current_dir(&fixture)
.env("CARGO_TARGET_DIR", fixture.join("target"))
.current_dir(fixture)
.env("CARGO_TARGET_DIR", target_dir)
.output()
.expect("run cargo check for renamed dependency fixture");
let _ = fs::remove_dir_all(&fixture);
assert!(
output.status.success(),
+64 -11
View File
@@ -1,3 +1,6 @@
//! Check the shipped templates against the real PDK and WIT for the guest target.
//! Each check owns its source and lockfile; Cargo alone shares and validates artifacts.
use std::fs;
use std::path::Path;
use std::process::Command;
@@ -135,36 +138,86 @@ fn embedded_service_template_cargo_checks_for_wasm_target() {
fn cargo_check_template(template: &str) {
let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let template_dir = crate_dir.join(format!("../../resources/plugin/templates/{template}"));
let manifest_path = template_dir.join("Cargo.toml");
let lock_path = template_dir.join("Cargo.lock");
let _ = fs::remove_file(&lock_path);
let templates_dir = crate_dir.join("../../resources/plugin/templates");
let template_dir = templates_dir.join(template);
let target_dir = tempfile::tempdir().expect("temporary cargo target dir");
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let output = Command::new(cargo)
// A sibling fixture preserves the shipped relative PDK and WIT paths verbatim.
// Never create or remove a lockfile in the shipped template: another invocation
// may be checking it at the same time, or a developer may own that lockfile.
let fixture = tempfile::Builder::new()
.prefix(".cargo-check-")
.tempdir_in(&templates_dir)
.expect("temporary template fixture");
copy_template_tree(&template_dir, fixture.path(), true);
// Both templates share the PDK dependencies. A separate persistent target
// reuses those artifacts without sharing source or the outer Cargo build lock.
// Cargo fingerprints and its target lock handle changes and concurrent checks.
let executable = std::env::current_exe().expect("current test executable");
let target_dir = executable
.parent()
.and_then(|deps| deps.parent())
.expect("test executable has a profile directory")
.join("compile-tests/plugin-pdk-templates");
let output = Command::new(env!("CARGO"))
.arg("check")
.arg("--manifest-path")
.arg(&manifest_path)
.arg(fixture.path().join("Cargo.toml"))
.arg("--target")
.arg("wasm32-unknown-unknown")
.arg("--offline")
.arg("--target-dir")
.arg(target_dir.path())
.arg(target_dir)
.current_dir(fixture.path())
.env("CARGO_TERM_COLOR", "never")
.output()
.expect("spawn cargo check for embedded template");
let _ = fs::remove_file(&lock_path);
assert!(
output.status.success(),
"template cargo check failed\nstatus: {}\nstdout:\n{}\nstderr:\n{}",
"template {template} cargo check failed\nstatus: {}\nstdout:\n{}\nstderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn copy_template_tree(source: &Path, destination: &Path, is_root: bool) {
let metadata = fs::symlink_metadata(source).expect("read template directory metadata");
assert!(
metadata.is_dir() && !metadata.file_type().is_symlink(),
"template input must be a real directory: {}",
source.display()
);
for entry in fs::read_dir(source).expect("read template directory") {
let entry = entry.expect("read template entry");
let path = entry.path();
let kind = entry.file_type().expect("read template entry type");
assert!(
!kind.is_symlink() && (kind.is_dir() || kind.is_file()),
"unsupported template input (including symlinks): {}",
path.display()
);
let name = entry.file_name();
// Preserve all compiler inputs, including build scripts and nested assets.
// Only omit the root build output and lockfile: each isolated consumer must
// resolve its own lockfile, as the original unlocked template check did.
if is_root
&& ((kind.is_dir() && name == "target") || (kind.is_file() && name == "Cargo.lock"))
{
continue;
}
let copied = destination.join(name);
if kind.is_dir() {
fs::create_dir(&copied).expect("create template fixture directory");
copy_template_tree(&path, &copied, false);
} else {
fs::copy(&path, &copied).expect("copy unmodified template input");
}
}
}
#[test]
fn pdk_runtime_dependencies_are_guest_side_only() {
let cargo: Value = toml::from_str(PDK_CARGO).expect("PDK Cargo.toml parses");
+44 -2
View File
@@ -360,10 +360,52 @@ mod tests {
}
#[tokio::test]
async fn bounded_binary_read_accepts_arbitrary_bytes_at_caller_limit_and_rejects_larger_files()
{
async fn bounded_binary_read_accepts_caller_limits_above_the_image_tool_limit() {
// The caller's limit is a reader policy, not a LocalWorkdir storage contract.
// Supply immutable chunks here; the separate component case exercises the
// real provider across a chunk boundary without repeatedly hashing 16 MiB.
let dir = TempDir::new().unwrap();
let bytes = vec![0xff; 16 * 1024 * 1024];
let hash = Sha256::digest(&bytes).into();
let responses = bytes
.chunks(IMAGE_READ_CHUNK_BYTES)
.enumerate()
.map(|(index, bytes)| {
chunk(
bytes,
(index * IMAGE_READ_CHUNK_BYTES) as u64,
16 * 1024 * 1024,
hash,
)
})
.collect();
let session = binary_session(&dir, responses);
let path = WorkdirPath::new("artifact.bin").unwrap();
assert_eq!(
read_workdir_bytes(&session, path.clone(), bytes.len())
.await
.unwrap(),
bytes
);
let session = binary_session(
&dir,
vec![chunk(
&bytes[..IMAGE_READ_CHUNK_BYTES],
0,
bytes.len() as u64,
hash,
)],
);
assert!(matches!(
read_workdir_bytes(&session, path, bytes.len() - 1).await,
Err(WorkdirError::InvalidArgument(_))
));
}
#[tokio::test]
async fn bounded_binary_read_reassembles_provider_chunks_and_enforces_the_caller_limit() {
let dir = TempDir::new().unwrap();
let bytes = vec![0xff; IMAGE_READ_CHUNK_BYTES + 1];
std::fs::write(dir.path().join("artifact.bin"), &bytes).unwrap();
let session =
LocalWorkdirSession::new(Scope::writable(dir.path()).unwrap(), dir.path().into());
+1 -1
View File
@@ -59,5 +59,5 @@ futures.workspace = true
agen.workspace = true
serial_test = "3.4.0"
tempfile.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "test-util"] }
tokio-tungstenite.workspace = true
+118 -63
View File
@@ -2341,6 +2341,7 @@ pub struct WorkerRuntimeExecutionBackend<F = ProfileRuntimeWorkerFactory> {
pending_restores: Mutex<HashMap<WorkerRef, Arc<PendingRuntimeRestore>>>,
worker_locks: Mutex<HashMap<WorkerRef, Arc<Mutex<()>>>>,
spawn_restore_timeout: Duration,
shutdown_admission_timeout: Duration,
}
impl<F> WorkerRuntimeExecutionBackend<F>
@@ -2362,6 +2363,7 @@ where
pending_restores: Mutex::new(HashMap::new()),
worker_locks: Mutex::new(HashMap::new()),
spawn_restore_timeout: SPAWN_RESTORE_TASK_TIMEOUT,
shutdown_admission_timeout: Duration::from_secs(5),
})
}
@@ -2881,9 +2883,10 @@ where
let shutdown_requested = Arc::clone(shutdown_requested);
let shutdown = shutdown.clone();
let tasks_for_join = tasks.clone();
let admission_timeout = self.shutdown_admission_timeout;
let cleanup = self.run_joined_on_adapter_runtime(async move {
tasks_for_join
.request_shutdown(&handle, &shutdown_requested, Duration::from_secs(5))
.request_shutdown(&handle, &shutdown_requested, admission_timeout)
.await?;
tasks_for_join.confirm_shutdown_and_join(&shutdown).await
});
@@ -3170,9 +3173,10 @@ where
let shutdown_requested = execution.shutdown_requested.clone();
let shutdown = execution.shutdown.clone();
let tasks = execution.tasks.clone();
let admission_timeout = self.shutdown_admission_timeout;
let cleanup = self.run_joined_on_adapter_runtime(async move {
tasks
.request_shutdown(&handle, &shutdown_requested, Duration::from_secs(5))
.request_shutdown(&handle, &shutdown_requested, admission_timeout)
.await?;
tasks.confirm_shutdown_and_join(&shutdown).await?;
handle.delete_uncommitted_uploaded_files().map_err(|error| {
@@ -4871,15 +4875,11 @@ mod tests {
const SAVED_SUBJEKTIV_SYSTEM_PROMPT: &str = "Committed saved Subject Worker system prompt";
async fn saved_subjektiv_restore_fixture(
fn saved_subjektiv_policy(
root: &Path,
materialize_head: bool,
) -> WorkerExecutionRestoreRequest {
let worker_ref = WorkerRef::new(WorkerId::now_v7());
let worker_name = ProfileRuntimeWorkerFactory::runtime_worker_name_for_ref(&worker_ref);
let aggregate_dir = root.join("workers").join(worker_ref.worker_id.to_string());
let session_store = WorkerSessionStore::new(aggregate_dir.join("session")).unwrap();
let store = WorkerAggregateStore::new(&aggregate_dir, &worker_name).unwrap();
worker_ref: &WorkerRef,
) -> (WorkerManifest, CreateWorkerRequest) {
let worker_name = ProfileRuntimeWorkerFactory::runtime_worker_name_for_ref(worker_ref);
let mut saved = WorkerManifest::from_toml(&format!(
r#"
[worker]
@@ -4920,6 +4920,19 @@ mod tests {
language: "English".to_string(),
});
bind_workspace_memory_settings(&mut saved, &request).unwrap();
(saved, request)
}
async fn saved_subjektiv_restore_fixture(
root: &Path,
materialize_head: bool,
) -> WorkerExecutionRestoreRequest {
let worker_ref = WorkerRef::new(WorkerId::now_v7());
let worker_name = ProfileRuntimeWorkerFactory::runtime_worker_name_for_ref(&worker_ref);
let aggregate_dir = root.join("workers").join(worker_ref.worker_id.to_string());
let session_store = WorkerSessionStore::new(aggregate_dir.join("session")).unwrap();
let store = WorkerAggregateStore::new(&aggregate_dir, &worker_name).unwrap();
let (saved, request) = saved_subjektiv_policy(root, &worker_ref);
let client = MockClient::new(Vec::new());
let mut engine =
Engine::<_, agen::state::Mutable, worker::SessionHistoryMetadata>::new_annotated(
@@ -5785,7 +5798,12 @@ mod tests {
fn duplicate_connect_timeout_retains_candidate_waiter_and_no_false_stop() {
let root = tempfile::tempdir().unwrap();
let candidate_root = tempfile::tempdir().unwrap();
let (backend, request) = profile_stop_fixture(root.path());
let (mut backend, request) = profile_stop_fixture(root.path());
// Only admission's deadline is shortened. The real Controller, receipt
// correlation, retained candidate and completion/join barriers stay live.
// The candidate send is gated below, so timeout is not a scheduler race.
let normal_admission_timeout = backend.shutdown_admission_timeout;
backend.shutdown_admission_timeout = Duration::from_millis(100);
let primary = backend
.workers
.lock()
@@ -5864,6 +5882,7 @@ mod tests {
.unwrap()
.contains_key(&request.worker_ref)
);
backend.shutdown_admission_timeout = normal_admission_timeout;
release.send(()).unwrap();
let rejected = backend.stop_worker(&request.worker_ref);
assert!(!rejected.is_accepted());
@@ -5878,21 +5897,16 @@ mod tests {
}
#[test]
#[serial_test::serial(worker_allocation)]
fn pending_restore_join_failure_never_becomes_absence_on_stop_retry() {
let root = tempfile::tempdir().unwrap();
let runtime_store = root.path().join("runtime");
let backend = WorkerRuntimeExecutionBackend::new(
ProfileRuntimeWorkerFactory::new(root.path())
.with_runtime_store_dir(&runtime_store)
.with_runtime_id("runtime-subjektiv-test"),
.with_runtime_store_dir(root.path().join("runtime")),
)
.unwrap();
let request = backend
.run_on_adapter_runtime(async move {
Ok(saved_subjektiv_restore_fixture(&runtime_store, true).await)
})
.unwrap();
// No Controller was ever published: this is the pending-task ownership
// rule, not saved-Session restore. Do not boot a Worker to manufacture it.
let request = direct_restore_request(WorkerRef::new(WorkerId::now_v7()));
let failed = backend
.spawn_on_adapter_runtime(async {
panic!("factory scope failed before publishing result")
@@ -6295,6 +6309,41 @@ mod tests {
);
}
#[test]
fn saved_manifest_policy_rejects_mismatched_or_missing_trusted_settings() {
let root = tempfile::tempdir().unwrap();
let worker_ref = WorkerRef::new(WorkerId::now_v7());
let (saved, request) = saved_subjektiv_policy(root.path(), &worker_ref);
let mut metadata = WorkerMetadata::new(&saved.worker.name, None);
metadata.resolved_manifest_snapshot =
Some(manifest::write_persisted_worker_manifest_snapshot(&saved).unwrap());
let (restored, _) =
ProfileRuntimeWorkerFactory::manifest_for_restore(&metadata, &request).unwrap();
assert_eq!(
manifest::write_persisted_worker_manifest_snapshot(&restored).unwrap(),
metadata.resolved_manifest_snapshot.clone().unwrap()
);
let mut wrong_settings = request.clone();
wrong_settings.memory_settings.as_mut().unwrap().language = "different language".into();
let mut detached = request.clone();
detached.subjektiv_attached = false;
let mut missing_settings = request.clone();
missing_settings.memory_settings = None;
for (invalid, expected) in [
(wrong_settings, "subjektiv settings snapshot mismatch"),
(detached, "unauthorized subjektiv attachment"),
(missing_settings, "missing its trusted settings snapshot"),
] {
let error = ProfileRuntimeWorkerFactory::manifest_for_restore(&metadata, &invalid)
.err()
.expect("invalid saved policy must be rejected");
assert!(error.contains(expected), "{error}");
}
}
// Integration: real Worker startup writes the saved Manifest/Session, then
// both factory entry points reject it before allocating any live resources.
// The local input matrix belongs to saved_manifest_policy_* above.
#[tokio::test]
#[serial_test::serial(worker_allocation)]
async fn profile_factory_saved_manifest_mismatch_rejected_before_live_work() {
@@ -6312,28 +6361,27 @@ mod tests {
.as_mut()
.unwrap()
.language = "different language".into();
let mut detached = request.clone();
detached.request.subjektiv_attached = false;
let mut missing_settings = request.clone();
missing_settings.request.memory_settings = None;
for (invalid, expected) in [
(wrong_settings, "subjektiv settings snapshot mismatch"),
(detached, "unauthorized subjektiv attachment"),
(missing_settings, "missing its trusted settings snapshot"),
] {
let error = factory.preflight_restore(&invalid).await.unwrap_err();
assert!(error.contains(expected), "{error}");
let error = match factory.restore_controller(invalid).await {
Ok(controller) => {
shutdown_profile_controller(controller).await;
panic!("invalid restore started a Controller")
}
Err(error) => error,
};
assert!(error.contains(expected), "{error}");
assert_eq!(persisted_files(&runtime_store), before);
assert!(factory.observation_hub.workers.lock().unwrap().is_empty());
}
let error = factory
.preflight_restore(&wrong_settings)
.await
.unwrap_err();
assert!(
error.contains("subjektiv settings snapshot mismatch"),
"{error}"
);
let error = match factory.restore_controller(wrong_settings).await {
Ok(controller) => {
shutdown_profile_controller(controller).await;
panic!("invalid restore started a Controller")
}
Err(error) => error,
};
assert!(
error.contains("subjektiv settings snapshot mismatch"),
"{error}"
);
assert_eq!(persisted_files(&runtime_store), before);
assert!(factory.observation_hub.workers.lock().unwrap().is_empty());
let run_dir = factory
.worker_aggregate_dir(&request.worker_ref)
.unwrap()
@@ -6770,7 +6818,7 @@ mod tests {
assert!(scope.tasks.lock().unwrap().is_empty());
}
#[tokio::test]
#[tokio::test(start_paused = true)]
async fn execution_scope_timeout_retains_exact_task_until_completion_is_proven() {
let release = Arc::new(tokio::sync::Notify::new());
let task_release = Arc::clone(&release);
@@ -7075,29 +7123,35 @@ mod tests {
);
}
struct DelayedFactory {
completed: Arc<AtomicBool>,
delay: Duration,
struct PendingCreateFactory {
started: mpsc::Sender<()>,
dropped: mpsc::Sender<()>,
}
struct FactoryFutureDrop(mpsc::Sender<()>);
impl Drop for FactoryFutureDrop {
fn drop(&mut self) {
let _ = self.0.send(());
}
}
#[async_trait]
impl RuntimeWorkerFactory for DelayedFactory {
impl RuntimeWorkerFactory for PendingCreateFactory {
async fn spawn_controller(
&self,
_request: WorkerExecutionSpawnRequest,
) -> Result<RuntimeWorkerController, String> {
tokio::time::sleep(self.delay).await;
self.completed.store(true, Ordering::SeqCst);
Err("delayed factory completed".to_string())
let _drop = FactoryFutureDrop(self.dropped.clone());
self.started.send(()).unwrap();
std::future::pending().await
}
async fn restore_controller(
&self,
_request: WorkerExecutionRestoreRequest,
) -> Result<RuntimeWorkerController, String> {
tokio::time::sleep(self.delay).await;
self.completed.store(true, Ordering::SeqCst);
Err("delayed factory completed".to_string())
panic!("create timeout must not restore a Worker")
}
}
@@ -7105,11 +7159,12 @@ mod tests {
fn create_timeout_cancels_factory_and_removes_persisted_worker() {
let root = tempfile::tempdir().unwrap();
let runtime_store_dir = root.path().join("runtime");
let completed = Arc::new(AtomicBool::new(false));
let (started_tx, started) = mpsc::channel();
let (dropped_tx, dropped) = mpsc::channel();
let backend = Arc::new(
WorkerRuntimeExecutionBackend::new(DelayedFactory {
completed: completed.clone(),
delay: Duration::from_millis(200),
WorkerRuntimeExecutionBackend::new(PendingCreateFactory {
started: started_tx,
dropped: dropped_tx,
})
.unwrap()
.with_spawn_restore_timeout(Duration::from_millis(20)),
@@ -7128,7 +7183,9 @@ mod tests {
let worker_id = request.worker_id;
let create_runtime = runtime.clone();
let create = std::thread::spawn(move || create_runtime.create_worker(request));
std::thread::sleep(Duration::from_millis(5));
started
.recv_timeout(Duration::from_secs(5))
.expect("factory must start before observing concurrent deletion");
let delete_error = runtime
.delete_worker(&crate::identity::WorkerRef::new(worker_id))
@@ -7140,11 +7197,9 @@ mod tests {
crate::error::RuntimeError::WorkerNotFound { worker_id: missing } if missing == worker_id
));
assert!(error.to_string().contains("was cancelled"));
std::thread::sleep(Duration::from_millis(250));
assert!(
!completed.load(Ordering::SeqCst),
"timed out factory future must not resume after create returns"
);
dropped
.try_recv()
.expect("cancelled factory future must be dropped before create returns");
assert!(runtime.list_workers().unwrap().is_empty());
assert!(backend.workers.lock().unwrap().is_empty());
assert!(
+1 -1
View File
@@ -212,7 +212,7 @@ mod tests {
})
.unwrap();
let store = Arc::new(
SqliteWorkspaceStore::in_memory_for_test()
SqliteWorkspaceStore::in_memory_fixture()
.unwrap()
.with_db_executor(executor),
);
+19
View File
@@ -237,6 +237,25 @@ impl SqliteWorkspaceAuthority {
let database_path = database_path.into();
let workspace_id = workspace_id.into();
let store = SqliteWorkspaceStore::open(&database_path)?;
Self::from_store(database_path, workspace_id, store)
}
// Normal API fixtures retain real Ticket/MR stores, but the Workspace schema
// was verified when their pristine DB was prepared. Startup still uses new().
#[cfg(test)]
pub(crate) fn from_fixture_store(
database_path: PathBuf,
workspace_id: String,
store: SqliteWorkspaceStore,
) -> Result<Self> {
Self::from_store(database_path, workspace_id, store)
}
fn from_store(
database_path: PathBuf,
workspace_id: String,
store: SqliteWorkspaceStore,
) -> Result<Self> {
let merge_request_source = Arc::new(AuthorityMergeRequestSource {
store: store.clone(),
});
+73 -39
View File
@@ -795,7 +795,7 @@ mod tests {
}
async fn open_store() -> SqliteWorkspaceStore {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store.upsert_workspace(&workspace()).await.unwrap();
store
}
@@ -1321,26 +1321,28 @@ mod tests {
assert!(main_config_has_schema_assertion(DEFAULT_MAIN_CONFIG_SOURCE));
}
#[tokio::test]
async fn candidate_normalizes_main_and_formats_changed_decodal_sources() {
let store = open_store().await;
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let candidate = store
.evaluate_workspace_config_candidate(
"w-config",
&commit_request(
&current,
vec![
update_main(&current, "{}"),
ConfigTreeChange::Create {
path: path("module.dcdl"),
content_type: ConfigContentType::Decodal,
content: "{}".into(),
},
],
),
)
.unwrap();
#[test]
fn candidate_normalizes_main_and_formats_changed_decodal_sources() {
let current = initial_state().unwrap();
let changes = [
update_main(&current, "{}"),
ConfigTreeChange::Create {
path: path("module.dcdl"),
content_type: ConfigContentType::Decodal,
content: "{}".into(),
},
];
let identity = (
current.contract.fingerprint.clone(),
current.projection_digest.clone(),
);
let candidate = evaluate_candidate(
current,
identity,
&changes,
WorkspaceConfigSchemaBundle::empty(),
)
.unwrap();
assert_eq!(
candidate
@@ -1414,34 +1416,44 @@ mod tests {
);
}
#[tokio::test]
async fn required_main_entrypoint_cannot_be_deleted_or_renamed() {
let store = open_store().await;
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let main = current.snapshot.get(&path(MAIN_CONFIG_ENTRYPOINT)).unwrap();
#[test]
fn required_main_entrypoint_cannot_be_deleted_or_renamed() {
for change in [
ConfigTreeChange::Delete {
path: path(MAIN_CONFIG_ENTRYPOINT),
expected_digest: main.content_digest.clone(),
expected_digest: "sha256:current".into(),
},
ConfigTreeChange::Rename {
from: path(MAIN_CONFIG_ENTRYPOINT),
to: path("other.dcdl"),
expected_digest: main.content_digest.clone(),
expected_digest: "sha256:current".into(),
},
ConfigTreeChange::Rename {
from: path("other.dcdl"),
to: path(MAIN_CONFIG_ENTRYPOINT),
expected_digest: "sha256:current".into(),
},
] {
let error = store
.evaluate_workspace_config_candidate(
"w-config",
&commit_request(&current, vec![change]),
)
.unwrap_err();
let error = reject_main_entrypoint_mutation(&[change]).unwrap_err();
assert!(error.to_string().contains("cannot be"));
}
}
#[test]
fn entrypoint_request_requires_exactly_the_server_owned_main() {
for entrypoints in [
vec![],
vec![path("other.dcdl")],
vec![path(MAIN_CONFIG_ENTRYPOINT), path("other.dcdl")],
] {
let error = validate_entrypoint_request(&entrypoints).unwrap_err();
assert!(error.to_string().contains("must be exactly [main.dcdl]"));
}
validate_entrypoint_request(&[path(MAIN_CONFIG_ENTRYPOINT)]).unwrap();
}
#[tokio::test]
async fn browser_cannot_replace_server_owned_entrypoint_contract() {
async fn candidate_store_rejects_entrypoint_override_and_main_deletion_without_mutation() {
let store = open_store().await;
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let mut request = commit_request(&current, Vec::new());
@@ -1450,11 +1462,33 @@ mod tests {
.evaluate_workspace_config_candidate("w-config", &request)
.unwrap_err();
assert!(error.to_string().contains("must be exactly [main.dcdl]"));
let main = current.snapshot.get(&path(MAIN_CONFIG_ENTRYPOINT)).unwrap();
let error = store
.evaluate_workspace_config_candidate(
"w-config",
&commit_request(
&current,
vec![ConfigTreeChange::Delete {
path: path(MAIN_CONFIG_ENTRYPOINT),
expected_digest: main.content_digest.clone(),
}],
),
)
.unwrap_err();
assert!(error.to_string().contains("cannot be deleted"));
assert_eq!(
store
.load_workspace_config("w-config")
.unwrap()
.unwrap()
.snapshot,
current.snapshot
);
}
#[tokio::test]
async fn invalid_candidate_is_never_persisted() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store.upsert_workspace(&workspace()).await.unwrap();
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let main = current.snapshot.get(&path(MAIN_CONFIG_ENTRYPOINT)).unwrap();
@@ -1478,7 +1512,7 @@ mod tests {
#[tokio::test]
async fn valid_candidate_commits_snapshot_content_and_provenance_atomically() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store.upsert_workspace(&workspace()).await.unwrap();
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let committed = store
@@ -1496,7 +1530,7 @@ mod tests {
#[tokio::test]
async fn stale_cas_cannot_overwrite_newer_tree() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store.upsert_workspace(&workspace()).await.unwrap();
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let request = commit_request(&current, vec![update_main(&current, "{ answer = 42; }")]);
@@ -1514,7 +1548,7 @@ mod tests {
#[tokio::test]
async fn committed_content_remains_retrievable_after_later_commit() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store.upsert_workspace(&workspace()).await.unwrap();
let current = store.load_workspace_config("w-config").unwrap().unwrap();
let first = store
+1 -1
View File
@@ -6681,7 +6681,7 @@ mod tests {
})
.unwrap();
let store = Arc::new(
SqliteWorkspaceStore::in_memory_for_test()
SqliteWorkspaceStore::in_memory_fixture()
.unwrap()
.with_db_executor(executor.clone()),
);
+1 -1
View File
@@ -1404,7 +1404,7 @@ mod tests {
WorkerId::from_legacy_u64(1)
}
fn setup() -> SqliteWorkspaceStore {
let s = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let s = SqliteWorkspaceStore::in_memory_fixture().unwrap();
s.with_conn(|c| {
c.execute(
"INSERT INTO accounts(account_id,kind,handle,display_name,created_at,updated_at) \
+218 -174
View File
@@ -3635,6 +3635,38 @@ fn embedded_runtime_request_audience(config: &ServerConfig) -> crate::Result<Str
})
}
#[derive(Clone, Copy)]
enum WorkspaceDatabasePreparation {
Startup,
#[cfg(test)]
PreparedFixture,
}
impl WorkspaceDatabasePreparation {
fn open_store(self, path: &Path) -> Result<crate::SqliteWorkspaceStore> {
match self {
Self::Startup => crate::SqliteWorkspaceStore::open(path),
#[cfg(test)]
Self::PreparedFixture => crate::SqliteWorkspaceStore::connect_prepared_fixture(path),
}
}
fn open_authority(
self,
path: PathBuf,
workspace_id: String,
) -> Result<SqliteWorkspaceAuthority> {
match self {
Self::Startup => SqliteWorkspaceAuthority::new(path, workspace_id),
#[cfg(test)]
Self::PreparedFixture => {
let store = self.open_store(&path)?;
SqliteWorkspaceAuthority::from_fixture_store(path, workspace_id, store)
}
}
}
}
impl WorkspaceApi {
/// Trusted Workspace-scoped Drive access for backend adapters. This is not
/// model authorization; callers must authorize requests before using it.
@@ -3856,7 +3888,57 @@ impl WorkspaceApi {
.await
}
#[cfg(test)]
async fn new_api_fixture(
config: ServerConfig,
execution_backend: Arc<dyn worker_runtime::execution::WorkerExecutionBackend>,
) -> Result<Self> {
// Only a newly copied pristine fixture skips repeated Workspace schema
// checks. Restart/recovery fixtures retain the complete startup path.
let preparation = if config.database_path.exists() {
WorkspaceDatabasePreparation::Startup
} else {
WorkspaceDatabasePreparation::PreparedFixture
};
let store = Arc::new(crate::SqliteWorkspaceStore::open_api_fixture(
&config.database_path,
)?);
seed_test_registered_workspace(store.as_ref(), &config).await?;
Self::new_with_database_preparation(
config,
store,
execution_backend,
BackendResourceBroker::default(),
HostExecutors::default(),
None,
preparation,
)
.await
}
async fn new_with_execution_backend_and_broker(
config: ServerConfig,
store: Arc<dyn ControlPlaneStore>,
execution_backend: Arc<dyn worker_runtime::execution::WorkerExecutionBackend>,
resource_broker: BackendResourceBroker,
host_executors: HostExecutors,
worker_remove_dispatcher: Option<
Arc<crate::worker_source::EmbeddedServerWorkerMutationDispatcher>,
>,
) -> Result<Self> {
Self::new_with_database_preparation(
config,
store,
execution_backend,
resource_broker,
host_executors,
worker_remove_dispatcher,
WorkspaceDatabasePreparation::Startup,
)
.await
}
async fn new_with_database_preparation(
mut config: ServerConfig,
store: Arc<dyn ControlPlaneStore>,
execution_backend: Arc<dyn worker_runtime::execution::WorkerExecutionBackend>,
@@ -3865,6 +3947,7 @@ impl WorkspaceApi {
worker_remove_dispatcher: Option<
Arc<crate::worker_source::EmbeddedServerWorkerMutationDispatcher>,
>,
database_preparation: WorkspaceDatabasePreparation,
) -> Result<Self> {
if store.get_workspace(&config.workspace_id).await?.is_none() {
return Err(crate::Error::Config(format!(
@@ -3904,7 +3987,8 @@ impl WorkspaceApi {
let (config_store, repository_secrets) = executor
.run(move || {
let config_store = Arc::new(
crate::SqliteWorkspaceStore::open(database_path.clone())?
database_preparation
.open_store(&database_path)?
.with_db_executor(config_executor),
);
let repository_secrets = Arc::new(RepositorySecretService::open(
@@ -4005,7 +4089,7 @@ impl WorkspaceApi {
.map_err(|error| {
Error::Store(format!("failed to register subjektiv storage: {error}"))
})?;
let authority = SqliteWorkspaceAuthority::new(database_path, workspace_id)?;
let authority = database_preparation.open_authority(database_path, workspace_id)?;
Ok::<_, Error>((feature_storage, registration, authority))
})
.await?;
@@ -28226,6 +28310,29 @@ async fn set_worker_retention(
))
}
fn cleanup_workdir_decision(
file_status: CleanupWorkdirFileStatus,
cleanliness: CleanupWorkdirCleanliness,
running_linked: bool,
pinned_linked: bool,
) -> (CleanupTargetKind, Option<String>) {
let action = if cleanup_workdir_file_status_is_record_only(file_status) {
CleanupTargetKind::WorkdirRecordDelete
} else if !matches!(cleanliness, CleanupWorkdirCleanliness::Dirty) {
CleanupTargetKind::WorkdirCleanCleanup
} else {
CleanupTargetKind::WorkdirDirtyDiscard
};
let blocking_reason = if running_linked {
Some("workdir is linked to a running Worker".to_string())
} else if pinned_linked {
Some("workdir is linked to a pinned Worker/history".to_string())
} else {
None
};
(action, blocking_reason)
}
fn build_runtime_cleanup_plan(
api: &WorkspaceApi,
runtime_id: &str,
@@ -28371,20 +28478,8 @@ fn build_runtime_cleanup_plan(
.get(record.workdir_id.as_str())
.map(|summary| cleanup_workdir_cleanliness_from_runtime(summary.cleanliness.as_deref()))
.unwrap_or_else(|| cleanup_workdir_cleanliness_from_registry(&record.cleanliness));
let action = if cleanup_workdir_file_status_is_record_only(file_status) {
CleanupTargetKind::WorkdirRecordDelete
} else if !matches!(cleanliness, CleanupWorkdirCleanliness::Dirty) {
CleanupTargetKind::WorkdirCleanCleanup
} else {
CleanupTargetKind::WorkdirDirtyDiscard
};
let blocking_reason = if running_linked {
Some("workdir is linked to a running Worker".to_string())
} else if pinned_linked {
Some("workdir is linked to a pinned Worker/history".to_string())
} else {
None
};
let (action, blocking_reason) =
cleanup_workdir_decision(file_status, cleanliness, running_linked, pinned_linked);
let repository_key = api
.store
.get_repository(&api.config.workspace_id, repository_id)?
@@ -47647,132 +47742,56 @@ mod tests {
}
#[tokio::test]
async fn ticket_checker_fake_model_regressions_preserve_classification_boundaries() {
struct FakeTicketCheckerModel;
impl FakeTicketCheckerModel {
fn respond(request: &BackendJobRequest) -> Value {
let input = ticket_item_checker::parse_input(&request.input).unwrap();
assert!(request.instruction.contains("writer_scope"));
assert!(request.instruction.contains("internal_inconsistency"));
assert!(request.instruction.contains("ambiguous_boundary"));
assert!(
request
.instruction
.contains("Production deployment requires approval")
);
assert!(request.instruction.contains("background, quotations"));
if input
.body
.contains("This time I only create the Ticket; I will not implement it.")
&& !input.body.starts_with("Background bug example:")
{
serde_json::json!({"findings": [{
"category": "writer_scope",
"quote": "I will not implement it",
"reason": "The active requirement records the writer's own implementation boundary.",
"suggestion": "Remove the writer-specific action boundary."
}]})
} else if input.body.contains("The rollout region is undecided.")
&& input.body.contains("The rollout region is us-east-1.")
{
serde_json::json!({"findings": [{
"category": "internal_inconsistency",
"quote": "The rollout region is undecided.",
"reason": "The same active text also settles the rollout region as us-east-1.",
"suggestion": "Choose either the unresolved or settled rollout-region statement."
}]})
} else if input.body == "Implementation stops after Ticket creation." {
serde_json::json!({"findings": [{
"category": "ambiguous_boundary",
"quote": "Implementation stops after Ticket creation.",
"reason": "The text alone does not establish whether this is a delivery constraint or the writer's current action boundary.",
"suggestion": "Confirm whether this limits the product workflow or only the current writer."
}]})
} else {
serde_json::json!({"findings": []})
}
}
}
struct Case {
name: &'static str,
body: &'static str,
advisory_quote: Option<&'static str>,
}
let cases = vec![
Case {
name: "writer-scope-positive",
body: "This time I only create the Ticket; I will not implement it.",
advisory_quote: Some("I will not implement it"),
},
Case {
name: "production-approval-negative",
body: "Production deployment requires approval.",
advisory_quote: None,
},
Case {
name: "quoted-background-negative",
body: "Background bug example: a generated Ticket once said “I will not implement it.” Do not reproduce that sentence in generated requirements.",
advisory_quote: None,
},
Case {
name: "unresolved-versus-settled-positive",
body: "The rollout region is undecided. The rollout region is us-east-1.",
advisory_quote: Some("The rollout region is undecided."),
},
Case {
name: "ambiguous-boundary-confirmation",
body: "Implementation stops after Ticket creation.",
advisory_quote: Some("Implementation stops after Ticket creation."),
},
];
async fn ticket_checker_routes_model_advisory_after_authenticated_save() {
let case = ticket_item_checker::test_support::cases()
.into_iter()
.find(|case| case.name == "writer-scope-positive")
.unwrap();
let temp = tempfile::tempdir().unwrap();
let (api, execution) = test_api_with_recording_backend(temp.path()).await;
let source = spawn_ticket_check_source(&api, "ticket-check-boundary-author");
for case in cases {
let mut input = ticket::NewTicket::new(format!("Boundary case: {}", case.name));
input.body = MarkdownText::new(case.body);
let Json(TicketBackendOperationResult::TicketRef(reference)) =
execute_worker_ticket_test_operation(
State(api.clone()),
AxumPath(ScopedWorkspacePath {
workspace_id: TEST_WORKSPACE_ID.to_string(),
}),
ticket_check_headers(&source),
Json(TicketBackendOperation::Create { input }),
)
.await
.unwrap()
else {
panic!("{}: unexpected Ticket create result", case.name);
};
let job = ticket_check_job(&api, &reference.id);
let attempt = wait_for_ticket_check_attempt(&api, &job).await;
execution.take_inputs();
api.accept_backend_job_result(
attempt.worker.as_ref().unwrap(),
&BackendJobResultSubmission {
job_id: job.request.job_id.clone(),
attempt_id: attempt.attempt_id,
input_digest: job.request.input_digest().unwrap(),
result: FakeTicketCheckerModel::respond(&job.request),
},
let mut input = ticket::NewTicket::new(format!("Boundary case: {}", case.name));
input.body = MarkdownText::new(case.body);
let Json(TicketBackendOperationResult::TicketRef(reference)) =
execute_worker_ticket_test_operation(
State(api.clone()),
AxumPath(ScopedWorkspacePath {
workspace_id: TEST_WORKSPACE_ID.to_string(),
}),
ticket_check_headers(&source),
Json(TicketBackendOperation::Create { input }),
)
.unwrap_or_else(|error| panic!("{}: {error}", case.name));
let notifications = execution.take_inputs();
let advisories = notifications
.iter()
.filter(|(_, content)| content.contains("[Ticket item checker advisory]"))
.collect::<Vec<_>>();
match case.advisory_quote {
Some(quote) => {
assert_eq!(advisories.len(), 1, "{}", case.name);
assert!(advisories[0].1.contains(quote), "{}", case.name);
}
None => assert!(advisories.is_empty(), "{}", case.name),
.await
.unwrap()
else {
panic!("{}: unexpected Ticket create result", case.name);
};
let job = ticket_check_job(&api, &reference.id);
let attempt = wait_for_ticket_check_attempt(&api, &job).await;
execution.take_inputs();
api.accept_backend_job_result(
attempt.worker.as_ref().unwrap(),
&BackendJobResultSubmission {
job_id: job.request.job_id.clone(),
attempt_id: attempt.attempt_id,
input_digest: job.request.input_digest().unwrap(),
result: ticket_item_checker::test_support::FakeTicketCheckerModel::respond(
&job.request,
),
},
)
.unwrap_or_else(|error| panic!("{}: {error}", case.name));
let notifications = execution.take_inputs();
let advisories = notifications
.iter()
.filter(|(_, content)| content.contains("[Ticket item checker advisory]"))
.collect::<Vec<_>>();
match case.advisory_quote {
Some(quote) => {
assert_eq!(advisories.len(), 1, "{}", case.name);
assert!(advisories[0].1.contains(quote), "{}", case.name);
}
None => assert!(advisories.is_empty(), "{}", case.name),
}
}
@@ -56591,16 +56610,56 @@ mod tests {
.unwrap();
}
#[tokio::test]
async fn api_startup_revalidates_workspace_schema_after_store_was_opened() {
let temp = tempfile::tempdir().unwrap();
let config = test_server_config(temp.path());
let store =
Arc::new(SqliteWorkspaceStore::open_api_fixture(&config.database_path).unwrap());
seed_test_registered_workspace(store.as_ref(), &config)
.await
.unwrap();
// Do not hold a live API/Runtime while testing startup of the same store:
// its exclusive Runtime store lease would reject before schema validation.
store
.with_conn(|conn| {
conn.execute_batch("DROP TRIGGER ticket_assignment_ticket_parent_tombstone;")?;
Ok(())
})
.unwrap();
let error = WorkspaceApi::new_for_test(config.clone(), store)
.await
.err()
.expect("API startup must revalidate an already opened store");
assert!(
error
.to_string()
.contains("ticket_assignment_ticket_parent_tombstone"),
"expected missing-trigger refusal from API startup, got {error:?}"
);
let error = WorkspaceApi::new_api_fixture(
config,
Arc::new(DeterministicExecutionBackend::default()),
)
.await
.err()
.expect("existing fixture DBs must use production startup");
assert!(
error
.to_string()
.contains("ticket_assignment_ticket_parent_tombstone"),
"expected missing-trigger refusal reopening fixture, got {error:?}"
);
}
async fn test_api_with_recording_backend(
workspace_root: impl Into<PathBuf>,
) -> (WorkspaceApi, Arc<DeterministicExecutionBackend>) {
let config = test_server_config(workspace_root);
let store = SqliteWorkspaceStore::open_for_test(config.database_path.clone()).unwrap();
let execution = Arc::new(DeterministicExecutionBackend::default());
let api =
WorkspaceApi::new_with_execution_backend(config, Arc::new(store), execution.clone())
.await
.unwrap();
let api = WorkspaceApi::new_api_fixture(config, execution.clone())
.await
.unwrap();
(api, execution)
}
@@ -56619,14 +56678,9 @@ mod tests {
config.repositories[0].source = source;
config.repositories[0].path = None;
config.repositories[0].default_selector = Some("develop".to_string());
let store = SqliteWorkspaceStore::open_for_test(config.database_path.clone()).unwrap();
WorkspaceApi::new_with_execution_backend(
config,
Arc::new(store),
Arc::new(DeterministicExecutionBackend::default()),
)
.await
.unwrap()
WorkspaceApi::new_api_fixture(config, Arc::new(DeterministicExecutionBackend::default()))
.await
.unwrap()
}
async fn test_api_with_docs_repository(workspace_root: impl Into<PathBuf>) -> WorkspaceApi {
@@ -56648,14 +56702,9 @@ mod tests {
docs.path = Some(docs_root);
docs.default_selector = Some("develop".to_string());
config.repositories.push(docs);
let store = SqliteWorkspaceStore::open_for_test(config.database_path.clone()).unwrap();
WorkspaceApi::new_with_execution_backend(
config,
Arc::new(store),
Arc::new(DeterministicExecutionBackend::default()),
)
.await
.unwrap()
WorkspaceApi::new_api_fixture(config, Arc::new(DeterministicExecutionBackend::default()))
.await
.unwrap()
}
fn test_owner_actor() -> RequestActor {
@@ -59899,22 +59948,17 @@ mod tests {
}
}
#[tokio::test]
async fn cleanup_plan_offers_normal_revalidation_for_prior_failure_and_unknown() {
let workspace = tempfile::tempdir().unwrap();
init_clean_git_workspace(workspace.path());
let api = test_api(workspace.path()).await;
for (id, state) in [
("pending-retry", "pending"),
("unknown-retry", "unknown"),
("corrupt-retry", "corrupted"),
] {
seed_cleanup_workdir(&api, id, state, "unknown");
}
let plan = build_runtime_cleanup_plan(&api, "runtime-test").unwrap();
for candidate in plan.workdirs {
assert_eq!(candidate.action, CleanupTargetKind::WorkdirCleanCleanup);
assert!(candidate.blocking_reason.is_none());
#[test]
fn cleanup_plan_offers_normal_revalidation_for_prior_failure_and_unknown() {
for state in ["pending", "unknown", "corrupted"] {
let (action, blocking_reason) = cleanup_workdir_decision(
cleanup_workdir_file_status_from_registry(state),
cleanup_workdir_cleanliness_from_registry("unknown"),
false,
false,
);
assert_eq!(action, CleanupTargetKind::WorkdirCleanCleanup, "{state}");
assert!(blocking_reason.is_none(), "{state}");
}
}
@@ -2218,167 +2218,188 @@ impl SubjectRemovalFixture {
}
}
#[tokio::test]
async fn erased_archive_plan_resend_and_reopen_replan_without_republishing_sources() {
for stage in ["planned", "executing", "failed"] {
for restart in [false, true] {
let mut f = SubjectRemovalFixture::new().await;
f.disable();
let policy = f
.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap();
let conn = rusqlite::Connection::open(&f.api.config.database_path).unwrap();
let archive_parent = f
.dir
.path()
.join(".test-embedded-runtime-store/archives/workers");
if stage == "planned" {
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE UPDATE OF state ON worker_removal_operations WHEN NEW.state='executing' BEGIN SELECT RAISE(ABORT,'injected execution interruption'); END;").unwrap();
} else {
// Fail Archive publication before any receipt. Suppress recording
// failure too to model the executing-at-Host-stop durable boundary.
std::fs::create_dir_all(archive_parent.parent().unwrap()).unwrap();
std::fs::write(&archive_parent, "publication blocked").unwrap();
if stage == "executing" {
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE UPDATE OF state ON worker_removal_operations WHEN NEW.state='failed' BEGIN SELECT RAISE(ABORT,'injected failure-save interruption'); END;").unwrap();
}
// Each case owns a complete isolated fixture; do not serialize independent
// recovery paths inside one test. Transitions within each helper remain sequential.
macro_rules! journal_recovery_cases {
($helper:ident; $($name:ident => ($($argument:expr),*)),+ $(,)?) => {
$(
#[tokio::test]
async fn $name() {
$helper($($argument),*).await;
}
assert_ne!(f.remove().status, StatusCode::OK.as_u16());
conn.execute_batch("DROP TRIGGER IF EXISTS interrupt_removal")
.unwrap();
if stage != "planned" {
std::fs::remove_file(&archive_parent).unwrap();
}
let old = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(serde_json::to_value(old.plan.state).unwrap(), stage);
assert_eq!(
old.plan.session_disposition,
worker_runtime::retention::SessionDisposition::Archive
);
f.api
.erase_subjektiv_journal_session(
&f.attr.session_id,
"erase-interrupted-archive-plan",
)
.unwrap();
if restart {
f = f.reopen().await;
}
let response = f.remove();
assert_eq!(
response.status,
StatusCode::OK.as_u16(),
"{stage}/{restart}: {}",
response.body
);
let saved = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(
saved.plan.session_disposition,
worker_runtime::retention::SessionDisposition::Purge
);
assert_eq!(saved.plan.reason, old.plan.reason);
assert_ne!(saved.plan.operation_id, old.plan.operation_id);
let (state, fingerprint, detail): (String, String, String) = conn.query_row(
"SELECT o.state,o.input_fingerprint,a.detail FROM worker_removal_operations o JOIN worker_retention_audit_events a USING(operation_id) WHERE o.operation_id=?1 AND a.event_kind='erased_archive_superseded'",
[&old.plan.operation_id], |r| Ok((r.get(0)?,r.get(1)?,r.get(2)?))).unwrap();
assert_eq!(state, "stale");
assert_eq!(fingerprint, old.plan.input_fingerprint);
let detail: Value = serde_json::from_str(&detail).unwrap();
assert_eq!(detail["old_acceptance"], "unknown_archive_fenced");
assert!(detail["source_removed"].is_null());
f.assert_erased_removal();
assert_eq!(
f.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap(),
policy
);
}
}
)+
};
}
#[tokio::test]
async fn erased_completed_archive_receipt_is_audited_without_republishing_on_resend_or_reopen() {
for restart in [false, true] {
let mut f = SubjectRemovalFixture::new().await;
f.disable();
let conn = rusqlite::Connection::open(&f.api.config.database_path).unwrap();
// Runtime has committed source_removed, but Backend commit/response is lost.
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE DELETE ON worker_registry BEGIN SELECT RAISE(ABORT,'injected metadata commit interruption'); END;").unwrap();
assert_ne!(f.remove().status, StatusCode::OK.as_u16());
conn.execute_batch("DROP TRIGGER interrupt_removal")
.unwrap();
let old = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(
old.plan.state,
crate::retention::WorkerRemovalPlanState::Failed
);
let receipt = f
.api
.runtime
.execute_worker_retention(&f.target, old.runtime_request.clone())
.unwrap();
assert!(receipt.source_removed);
let archive_id = old.plan.archive_id.as_ref().unwrap();
let archive_source = f
.dir
.path()
.join(".test-embedded-runtime-store/archives/workers")
.join(archive_id)
.join("session");
assert!(archive_source.exists());
// The domain tombstone commits even though retained-source resolution fails.
// Recovery must locate only this plan's exact reserved archive, not publish it.
assert!(
f.api
.erase_subjektiv_journal_session(&f.attr.session_id, "erase-transferred-original")
.is_err()
);
assert!(f.store.journal_session_erased(&f.host).unwrap());
if restart {
f = f.reopen().await;
async fn assert_erased_archive_plan_replans_without_republishing_sources(
stage: &str,
restart: bool,
) {
let mut f = SubjectRemovalFixture::new().await;
f.disable();
let policy = f
.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap();
let conn = rusqlite::Connection::open(&f.api.config.database_path).unwrap();
let archive_parent = f
.dir
.path()
.join(".test-embedded-runtime-store/archives/workers");
if stage == "planned" {
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE UPDATE OF state ON worker_removal_operations WHEN NEW.state='executing' BEGIN SELECT RAISE(ABORT,'injected execution interruption'); END;").unwrap();
} else {
// Fail Archive publication before any receipt. Suppress recording
// failure too to model the executing-at-Host-stop durable boundary.
std::fs::create_dir_all(archive_parent.parent().unwrap()).unwrap();
std::fs::write(&archive_parent, "publication blocked").unwrap();
if stage == "executing" {
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE UPDATE OF state ON worker_removal_operations WHEN NEW.state='failed' BEGIN SELECT RAISE(ABORT,'injected failure-save interruption'); END;").unwrap();
}
let response = f.remove();
assert_eq!(
response.status,
StatusCode::OK.as_u16(),
"{restart}: {}",
response.body
);
let saved = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(saved.plan.operation_id, old.plan.operation_id);
assert_eq!(saved.plan.input_fingerprint, old.plan.input_fingerprint);
assert_eq!(saved.plan.session_disposition, old.plan.session_disposition);
let detail: String = conn.query_row("SELECT detail FROM worker_retention_audit_events WHERE operation_id=?1 AND event_kind='erased_removal_receipt'", [&old.plan.operation_id], |r| r.get(0)).unwrap();
let detail: Value = serde_json::from_str(&detail).unwrap();
assert_eq!(detail["source_removed"], true);
assert_eq!(detail["old_acceptance"], "receipt_confirmed");
assert!(!archive_source.exists());
f.assert_erased_removal();
}
assert_ne!(f.remove().status, StatusCode::OK.as_u16());
conn.execute_batch("DROP TRIGGER IF EXISTS interrupt_removal")
.unwrap();
if stage != "planned" {
std::fs::remove_file(&archive_parent).unwrap();
}
let old = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(serde_json::to_value(old.plan.state).unwrap(), stage);
assert_eq!(
old.plan.session_disposition,
worker_runtime::retention::SessionDisposition::Archive
);
f.api
.erase_subjektiv_journal_session(&f.attr.session_id, "erase-interrupted-archive-plan")
.unwrap();
if restart {
f = f.reopen().await;
}
let response = f.remove();
assert_eq!(
response.status,
StatusCode::OK.as_u16(),
"{stage}/{restart}: {}",
response.body
);
let saved = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(
saved.plan.session_disposition,
worker_runtime::retention::SessionDisposition::Purge
);
assert_eq!(saved.plan.reason, old.plan.reason);
assert_ne!(saved.plan.operation_id, old.plan.operation_id);
let (state, fingerprint, detail): (String, String, String) = conn.query_row(
"SELECT o.state,o.input_fingerprint,a.detail FROM worker_removal_operations o JOIN worker_retention_audit_events a USING(operation_id) WHERE o.operation_id=?1 AND a.event_kind='erased_archive_superseded'",
[&old.plan.operation_id], |r| Ok((r.get(0)?,r.get(1)?,r.get(2)?))).unwrap();
assert_eq!(state, "stale");
assert_eq!(fingerprint, old.plan.input_fingerprint);
let detail: Value = serde_json::from_str(&detail).unwrap();
assert_eq!(detail["old_acceptance"], "unknown_archive_fenced");
assert!(detail["source_removed"].is_null());
f.assert_erased_removal();
assert_eq!(
f.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap(),
policy
);
}
journal_recovery_cases! {
assert_erased_archive_plan_replans_without_republishing_sources;
erased_archive_planned_resend_replans_without_republishing_sources => ("planned", false),
erased_archive_planned_reopen_replans_without_republishing_sources => ("planned", true),
erased_archive_executing_resend_replans_without_republishing_sources => ("executing", false),
erased_archive_executing_reopen_replans_without_republishing_sources => ("executing", true),
erased_archive_failed_resend_replans_without_republishing_sources => ("failed", false),
erased_archive_failed_reopen_replans_without_republishing_sources => ("failed", true),
}
async fn assert_erased_completed_archive_receipt_is_audited_without_republishing(restart: bool) {
let mut f = SubjectRemovalFixture::new().await;
f.disable();
let conn = rusqlite::Connection::open(&f.api.config.database_path).unwrap();
// Runtime has committed source_removed, but Backend commit/response is lost.
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE DELETE ON worker_registry BEGIN SELECT RAISE(ABORT,'injected metadata commit interruption'); END;").unwrap();
assert_ne!(f.remove().status, StatusCode::OK.as_u16());
conn.execute_batch("DROP TRIGGER interrupt_removal")
.unwrap();
let old = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(
old.plan.state,
crate::retention::WorkerRemovalPlanState::Failed
);
let receipt = f
.api
.runtime
.execute_worker_retention(&f.target, old.runtime_request.clone())
.unwrap();
assert!(receipt.source_removed);
let archive_id = old.plan.archive_id.as_ref().unwrap();
let archive_source = f
.dir
.path()
.join(".test-embedded-runtime-store/archives/workers")
.join(archive_id)
.join("session");
assert!(archive_source.exists());
// The domain tombstone commits even though retained-source resolution fails.
// Recovery must locate only this plan's exact reserved archive, not publish it.
assert!(
f.api
.erase_subjektiv_journal_session(&f.attr.session_id, "erase-transferred-original")
.is_err()
);
assert!(f.store.journal_session_erased(&f.host).unwrap());
if restart {
f = f.reopen().await;
}
let response = f.remove();
assert_eq!(
response.status,
StatusCode::OK.as_u16(),
"{restart}: {}",
response.body
);
let saved = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(saved.plan.operation_id, old.plan.operation_id);
assert_eq!(saved.plan.input_fingerprint, old.plan.input_fingerprint);
assert_eq!(saved.plan.session_disposition, old.plan.session_disposition);
let detail: String = conn.query_row("SELECT detail FROM worker_retention_audit_events WHERE operation_id=?1 AND event_kind='erased_removal_receipt'", [&old.plan.operation_id], |r| r.get(0)).unwrap();
let detail: Value = serde_json::from_str(&detail).unwrap();
assert_eq!(detail["source_removed"], true);
assert_eq!(detail["old_acceptance"], "receipt_confirmed");
assert!(!archive_source.exists());
f.assert_erased_removal();
}
journal_recovery_cases! {
assert_erased_completed_archive_receipt_is_audited_without_republishing;
erased_completed_archive_receipt_resend_is_audited_without_republishing => (false),
erased_completed_archive_receipt_reopen_is_audited_without_republishing => (true),
}
#[tokio::test]
@@ -2438,206 +2459,206 @@ async fn erased_archive_without_receipt_or_inventory_remains_unknown_without_rep
assert_eq!(count, 1);
}
#[tokio::test]
async fn erased_archive_retained_diagnostics_receipt_finalizes_only_after_physical_ack_on_resend_or_reopen()
{
for restart in [false, true] {
for interrupt_erase in [false, true] {
let mut f = SubjectRemovalFixture::new().await;
f.disable();
let policy = f
.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
async fn assert_erased_archive_retained_diagnostics_receipt_finalizes_only_after_physical_ack(
restart: bool,
interrupt_erase: bool,
) {
let mut f = SubjectRemovalFixture::new().await;
f.disable();
let policy = f
.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap()
.unwrap();
let policy = f
.api
.config_store
.update_worker_retention_policy(
TEST_WORKSPACE_ID,
&policy.policy_digest,
&crate::retention::WorkerRetentionPolicyUpdate {
policy_id: "fixture-retained-diagnostics".into(),
session_disposition: policy.session_disposition,
metadata_disposition: policy.metadata_disposition,
archive_retention: policy.archive_retention,
diagnostics_disposition: worker_runtime::retention::DiagnosticsDisposition::Retain,
diagnostics_retention_seconds: Some(3600),
},
)
.unwrap();
let worker_root = session_root(f.dir.path(), &f.target)
.parent()
.unwrap()
.to_path_buf();
std::fs::create_dir_all(worker_root.join("runs/fixture-run")).unwrap();
std::fs::write(
worker_root.join("runs/fixture-run/worker.out.log"),
"diagnostic original",
)
.unwrap();
let conn = rusqlite::Connection::open(&f.api.config.database_path).unwrap();
// Real Runtime receipt exists; Backend commit/response is interrupted.
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE DELETE ON worker_registry BEGIN SELECT RAISE(ABORT,'injected metadata commit interruption'); END;").unwrap();
assert_ne!(f.remove().status, StatusCode::OK.as_u16());
conn.execute_batch("DROP TRIGGER interrupt_removal")
.unwrap();
let old = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
let runtime_root = f.dir.path().join(".test-embedded-runtime-store");
let receipt_path = runtime_root
.join("retention/operations")
.join(format!("{}.json", old.plan.operation_id));
let receipt_bytes = std::fs::read(&receipt_path).unwrap();
let receipt = f
.api
.runtime
.execute_worker_retention(&f.target, old.runtime_request.clone())
.unwrap();
assert!(receipt.source_removed);
assert!(receipt.diagnostics_retained);
let diagnostics_root = runtime_root
.join("archives/diagnostics")
.join(&old.plan.operation_id);
let diagnostics_manifest = diagnostics_root.join("manifest.json");
let diagnostics_bytes = std::fs::read(&diagnostics_manifest).unwrap();
let archive_source = runtime_root
.join("archives/workers")
.join(old.plan.archive_id.as_ref().unwrap())
.join("session");
assert!(diagnostics_root.exists());
assert!(archive_source.exists());
// Domain erasure succeeds, but no physical acknowledgement can be
// claimed from the old retained locator after aggregate removal.
assert!(
f.api
.erase_subjektiv_journal_session(&f.attr.session_id, "erase-retained-diagnostics")
.is_err()
);
if interrupt_erase {
// Runtime must reject mismatched diagnostics identity rather than
// acknowledge removal of an unrelated operation. No fake Host ack.
let mut damaged: Value = serde_json::from_slice(&diagnostics_bytes).unwrap();
damaged["input_fingerprint"] = json!("unrelated-operation");
std::fs::write(&diagnostics_manifest, serde_json::to_vec(&damaged).unwrap()).unwrap();
let response = f.remove();
assert_eq!(
response.status,
StatusCode::CONFLICT.as_u16(),
"{}",
response.body
);
let body: Value = serde_json::from_str(&response.body).unwrap();
assert_eq!(body["code"], "erased_removal_reconciliation_unconfirmed");
assert!(
f.api
.store
.get_worker_registry(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
let policy = f
.api
.config_store
.update_worker_retention_policy(
TEST_WORKSPACE_ID,
&policy.policy_digest,
&crate::retention::WorkerRetentionPolicyUpdate {
policy_id: "fixture-retained-diagnostics".into(),
session_disposition: policy.session_disposition,
metadata_disposition: policy.metadata_disposition,
archive_retention: policy.archive_retention,
diagnostics_disposition:
worker_runtime::retention::DiagnosticsDisposition::Retain,
diagnostics_retention_seconds: Some(3600),
},
)
.unwrap();
let worker_root = session_root(f.dir.path(), &f.target)
.parent()
.unwrap()
.to_path_buf();
std::fs::create_dir_all(worker_root.join("runs/fixture-run")).unwrap();
std::fs::write(
worker_root.join("runs/fixture-run/worker.out.log"),
"diagnostic original",
.is_some()
);
let count: i64 = conn.query_row("SELECT COUNT(*) FROM worker_retention_audit_events WHERE operation_id=?1 AND event_kind='erased_removal_receipt'", [&old.plan.operation_id], |r| r.get(0)).unwrap();
assert_eq!(
count, 0,
"failed acknowledgement cannot authorize finalization/audit"
);
assert_eq!(std::fs::read(&receipt_path).unwrap(), receipt_bytes);
let waiting = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(waiting.plan.operation_id, old.plan.operation_id);
assert_eq!(waiting.plan.input_fingerprint, old.plan.input_fingerprint);
assert_eq!(
waiting.plan.state,
crate::retention::WorkerRemovalPlanState::Executing
);
let plans: i64 = conn
.query_row(
"SELECT COUNT(*) FROM worker_removal_operations WHERE worker_id=?1",
[&f.target.worker_id],
|r| r.get(0),
)
.unwrap();
let conn = rusqlite::Connection::open(&f.api.config.database_path).unwrap();
// Real Runtime receipt exists; Backend commit/response is interrupted.
conn.execute_batch("CREATE TRIGGER interrupt_removal BEFORE DELETE ON worker_registry BEGIN SELECT RAISE(ABORT,'injected metadata commit interruption'); END;").unwrap();
assert_ne!(f.remove().status, StatusCode::OK.as_u16());
conn.execute_batch("DROP TRIGGER interrupt_removal")
.unwrap();
let old = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
let runtime_root = f.dir.path().join(".test-embedded-runtime-store");
let receipt_path = runtime_root
.join("retention/operations")
.join(format!("{}.json", old.plan.operation_id));
let receipt_bytes = std::fs::read(&receipt_path).unwrap();
let receipt = f
.api
.runtime
.execute_worker_retention(&f.target, old.runtime_request.clone())
.unwrap();
assert!(receipt.source_removed);
assert!(receipt.diagnostics_retained);
let diagnostics_root = runtime_root
.join("archives/diagnostics")
.join(&old.plan.operation_id);
let diagnostics_manifest = diagnostics_root.join("manifest.json");
let diagnostics_bytes = std::fs::read(&diagnostics_manifest).unwrap();
let archive_source = runtime_root
.join("archives/workers")
.join(old.plan.archive_id.as_ref().unwrap())
.join("session");
assert!(diagnostics_root.exists());
assert!(archive_source.exists());
// Domain erasure succeeds, but no physical acknowledgement can be
// claimed from the old retained locator after aggregate removal.
assert!(
f.api
.erase_subjektiv_journal_session(
&f.attr.session_id,
"erase-retained-diagnostics"
)
.is_err()
);
if interrupt_erase {
// Runtime must reject mismatched diagnostics identity rather than
// acknowledge removal of an unrelated operation. No fake Host ack.
let mut damaged: Value = serde_json::from_slice(&diagnostics_bytes).unwrap();
damaged["input_fingerprint"] = json!("unrelated-operation");
std::fs::write(&diagnostics_manifest, serde_json::to_vec(&damaged).unwrap())
.unwrap();
let response = f.remove();
assert_eq!(
response.status,
StatusCode::CONFLICT.as_u16(),
"{}",
response.body
);
let body: Value = serde_json::from_str(&response.body).unwrap();
assert_eq!(body["code"], "erased_removal_reconciliation_unconfirmed");
assert!(
f.api
.store
.get_worker_registry(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.is_some()
);
let count: i64 = conn.query_row("SELECT COUNT(*) FROM worker_retention_audit_events WHERE operation_id=?1 AND event_kind='erased_removal_receipt'", [&old.plan.operation_id], |r| r.get(0)).unwrap();
assert_eq!(
count, 0,
"failed acknowledgement cannot authorize finalization/audit"
);
assert_eq!(std::fs::read(&receipt_path).unwrap(), receipt_bytes);
let waiting = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(waiting.plan.operation_id, old.plan.operation_id);
assert_eq!(waiting.plan.input_fingerprint, old.plan.input_fingerprint);
assert_eq!(
waiting.plan.state,
crate::retention::WorkerRemovalPlanState::Executing
);
let plans: i64 = conn
.query_row(
"SELECT COUNT(*) FROM worker_removal_operations WHERE worker_id=?1",
[&f.target.worker_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(
plans, 1,
"missing physical ack cannot authorize a replacement plan"
);
std::fs::write(&diagnostics_manifest, &diagnostics_bytes).unwrap();
}
if restart {
f = f.reopen().await;
}
let response = f.remove();
assert_eq!(
response.status,
StatusCode::OK.as_u16(),
"restart={restart}, interrupted={interrupt_erase}: {}",
response.body
);
assert!(!diagnostics_root.exists());
assert!(!archive_source.exists());
assert_eq!(
std::fs::read(&receipt_path).unwrap(),
receipt_bytes,
"historical Runtime receipt must not be rewritten"
);
let saved = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(saved.plan.operation_id, old.plan.operation_id);
assert_eq!(saved.plan.input_fingerprint, old.plan.input_fingerprint);
assert_eq!(
saved.plan.diagnostics_disposition,
old.plan.diagnostics_disposition
);
let detail: String = conn.query_row("SELECT detail FROM worker_retention_audit_events WHERE operation_id=?1 AND event_kind='erased_removal_receipt'", [&old.plan.operation_id], |r| r.get(0)).unwrap();
let detail: Value = serde_json::from_str(&detail).unwrap();
assert_eq!(detail["source_removed"], true);
assert_eq!(detail["old_diagnostics_retained"], true);
assert_eq!(
detail["runtime_erasure_ack"]["session_id"],
f.attr.session_id
);
let diagnostics_catalog: i64 = conn
.query_row(
"SELECT COUNT(*) FROM worker_diagnostics_archives WHERE operation_id=?1",
[&old.plan.operation_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(diagnostics_catalog, 0);
let tombstone_archive: Option<String> = conn
.query_row(
"SELECT archive_id FROM worker_tombstones WHERE operation_id=?1",
[&old.plan.operation_id],
|r| r.get(0),
)
.unwrap();
assert!(tombstone_archive.is_none());
f.assert_erased_removal();
assert_eq!(
f.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap(),
Some(policy)
);
}
assert_eq!(
plans, 1,
"missing physical ack cannot authorize a replacement plan"
);
std::fs::write(&diagnostics_manifest, &diagnostics_bytes).unwrap();
}
if restart {
f = f.reopen().await;
}
let response = f.remove();
assert_eq!(
response.status,
StatusCode::OK.as_u16(),
"restart={restart}, interrupted={interrupt_erase}: {}",
response.body
);
assert!(!diagnostics_root.exists());
assert!(!archive_source.exists());
assert_eq!(
std::fs::read(&receipt_path).unwrap(),
receipt_bytes,
"historical Runtime receipt must not be rewritten"
);
let saved = f
.api
.config_store
.recover_worker_removal_execution(TEST_WORKSPACE_ID, &f.target)
.unwrap()
.unwrap();
assert_eq!(saved.plan.operation_id, old.plan.operation_id);
assert_eq!(saved.plan.input_fingerprint, old.plan.input_fingerprint);
assert_eq!(
saved.plan.diagnostics_disposition,
old.plan.diagnostics_disposition
);
let detail: String = conn.query_row("SELECT detail FROM worker_retention_audit_events WHERE operation_id=?1 AND event_kind='erased_removal_receipt'", [&old.plan.operation_id], |r| r.get(0)).unwrap();
let detail: Value = serde_json::from_str(&detail).unwrap();
assert_eq!(detail["source_removed"], true);
assert_eq!(detail["old_diagnostics_retained"], true);
assert_eq!(
detail["runtime_erasure_ack"]["session_id"],
f.attr.session_id
);
let diagnostics_catalog: i64 = conn
.query_row(
"SELECT COUNT(*) FROM worker_diagnostics_archives WHERE operation_id=?1",
[&old.plan.operation_id],
|r| r.get(0),
)
.unwrap();
assert_eq!(diagnostics_catalog, 0);
let tombstone_archive: Option<String> = conn
.query_row(
"SELECT archive_id FROM worker_tombstones WHERE operation_id=?1",
[&old.plan.operation_id],
|r| r.get(0),
)
.unwrap();
assert!(tombstone_archive.is_none());
f.assert_erased_removal();
assert_eq!(
f.api
.config_store
.worker_retention_policy(TEST_WORKSPACE_ID)
.unwrap(),
Some(policy)
);
}
journal_recovery_cases! {
assert_erased_archive_retained_diagnostics_receipt_finalizes_only_after_physical_ack;
erased_archive_retained_diagnostics_resend_finalizes_only_after_physical_ack => (false, false),
erased_archive_retained_diagnostics_interrupted_resend_finalizes_only_after_physical_ack => (false, true),
erased_archive_retained_diagnostics_reopen_finalizes_only_after_physical_ack => (true, false),
erased_archive_retained_diagnostics_interrupted_reopen_finalizes_only_after_physical_ack => (true, true),
}
@@ -730,13 +730,31 @@ async fn unsafe_names_and_oversized_text_cannot_publish_and_reads_truncate_at_ut
#[tokio::test]
async fn omitted_discovery_limits_use_backend_maximum_pages_in_tools_and_native_wip() {
let f = Fixture::new(["read_write", "read_only"]).await;
let mut expected = std::collections::BTreeSet::new();
for index in 0..201 {
let created = f
.create(&format!("default-page-{index:03}.md"), "bounded")
.await;
expected.insert(entry(&created)["node_id"].as_str().unwrap().to_owned());
}
// This case owns discovery defaults, not repeated mutation routing. Seed through
// the real Drive owner (including blobs/receipts); reads below retain signed tools/WIP.
let drive = f.api.drive().await.unwrap();
let expected = tokio::task::spawn_blocking(move || {
let root = drive.root().unwrap().id;
let mut expected = std::collections::BTreeSet::new();
for index in 0..201 {
let created = drive
.mutate(
&format!("default-page-seed-{index:03}"),
"pagination-fixture",
workspace_drive::Mutation::CreateFile {
parent: root,
name: format!("default-page-{index:03}.md"),
content_type: "text/markdown".into(),
bytes: b"bounded".to_vec(),
},
)
.unwrap();
expected.insert(String::from(created.node.id));
}
expected
})
.await
.unwrap();
let native = native_tools(&f.features[1]);
let root = value(
native_tool(&native, "Inspect", json!({"path":"/drive","refresh":true}))
@@ -401,6 +401,11 @@ async fn deletion_fences_new_requests_then_drains_without_callback_starvation()
let callback = gate.admit(true).expect("parent may still need callback");
reply.send(Ok(read_result())).unwrap();
assert_json(long.await.unwrap(), StatusCode::OK).await;
// HTTP completion may precede cleanup of retained DB/Host request leases.
// Keep the callback alive while observing the real parent-drain condition.
tokio::time::timeout(BOUND, gate.wait_for_parent_requests_to_drain())
.await
.expect("completed HTTP request retained its admission through the drain deadline");
// No new callbacks may prolong the drain once all parents have left.
assert!(gate.admit(true).is_none());
assert!(futures::poll!(&mut deletion).is_pending());
@@ -52,6 +52,28 @@ impl WorkspaceAdmission {
})
}
// Observe parent cleanup without releasing a callback held by the test.
// A reply can arrive before the DB/Host job drops its retained request lease.
#[cfg(test)]
pub(super) async fn wait_for_parent_requests_to_drain(&self) {
loop {
let changed = self.changed.notified();
tokio::pin!(changed);
changed.as_mut().enable();
{
let state = self.state.lock().expect("Workspace admission poisoned");
assert!(
state.deleting,
"parent drain observation requires a deletion fence"
);
if state.requests == 0 {
return;
}
}
changed.await;
}
}
pub(super) async fn lock_deletion(self: &Arc<Self>) -> DeletionLease {
let serial = self.deletion.clone().lock_owned().await;
self.state
@@ -135,3 +157,43 @@ impl Drop for DeletionLease {
.deleting = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db_executor::DbOperationLifetime;
#[tokio::test]
async fn parent_drain_waits_for_retained_job_cleanup_without_waiting_for_callbacks() {
let gate = Arc::new(WorkspaceAdmission::default());
let parent = gate.admit(false).unwrap();
let retained_job = parent.retain();
let mut deletion = Box::pin(gate.lock_deletion());
assert!(futures::poll!(&mut deletion).is_pending());
let callback = gate.admit(true).unwrap();
drop(parent);
let mut parents_drained = Box::pin(gate.wait_for_parent_requests_to_drain());
assert!(futures::poll!(&mut parents_drained).is_pending());
assert!(
gate.admit(true).is_some(),
"retained parent may still need callbacks"
);
drop(retained_job);
tokio::time::timeout(std::time::Duration::from_secs(5), parents_drained)
.await
.expect("parent cleanup did not notify the drain observer");
assert!(gate.admit(true).is_none());
assert!(gate.admit(false).is_none());
assert!(
futures::poll!(&mut deletion).is_pending(),
"callback still owns its lease"
);
drop(callback);
let deletion = tokio::time::timeout(std::time::Duration::from_secs(5), deletion)
.await
.expect("callback cleanup did not finish deletion drain");
assert!(gate.admit(true).is_none());
drop(deletion);
assert!(gate.admit(false).is_some());
}
}
+209 -144
View File
@@ -15616,6 +15616,10 @@ fn table_columns(conn: &Connection, table_name: &str) -> Result<Vec<String>> {
.map_err(Error::from)
}
#[cfg(test)]
#[path = "store_test_support.rs"]
mod test_support;
#[cfg(test)]
mod tests {
include!("assignment_tracking_tests.rs");
@@ -15653,7 +15657,7 @@ mod tests {
fn drive_grants_migrate_and_preserve_audit_through_revoke_worker_delete_and_restart() {
let t = tempfile::tempdir().unwrap();
let path = t.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
let legacy = Connection::open_in_memory().unwrap();
configure_sqlite(&legacy).unwrap();
legacy.execute_batch("CREATE TABLE workspaces(workspace_id TEXT PRIMARY KEY);
@@ -15778,7 +15782,7 @@ mod tests {
}
#[test]
fn drive_grants_baseline_constraints_and_worker_lifetime_fail_closed() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
let proposed = drive_grant_fixture(&store);
let first = store.create_workspace_drive_grant(&proposed).unwrap();
store.with_conn(|c| {
@@ -16139,7 +16143,7 @@ mod tests {
#[tokio::test]
async fn external_workdir_grant_persists_typed_source_and_fences_generations() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store
.upsert_account(&AccountRecord {
account_id: "owner".to_string(),
@@ -16890,39 +16894,38 @@ mod tests {
}
#[test]
fn schema_v59_upgrade_adds_runtime_removal_authority_atomically() {
fn schema_v59_upgrade_adds_runtime_removal_authority_atomically() -> Result<()> {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("server.db");
prepare_retained_schema(&path, 59);
let migrated = SqliteWorkspaceStore::open_for_test(&path).unwrap();
migrated
.with_conn(|conn| {
let table_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'table' AND name = 'runtime_removal_operations'",
[],
|row| row.get(0),
)?;
let trigger_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'trigger' AND (\
name LIKE '%_blocked_by_runtime_removal' OR \
name LIKE 'runtime_binding_%_blocked_by_removal'\
)",
[],
|row| row.get(0),
)?;
let foreign_key_failures: i64 =
conn.query_row("SELECT COUNT(*) FROM pragma_foreign_key_check", [], |row| {
row.get(0)
})?;
assert_eq!(table_count, 1);
assert_eq!(trigger_count, 18);
assert_eq!(foreign_key_failures, 0);
Ok(())
})
.unwrap();
let conn = Connection::open(&path)?;
configure_sqlite(&conn)?;
migrate_runtime_removal_operations_v59_to_v60(&conn)?;
assert_eq!(current_schema_version(&conn)?, 60);
let table_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'table' AND name = 'runtime_removal_operations'",
[],
|row| row.get(0),
)?;
let trigger_count: i64 = conn.query_row(
"SELECT COUNT(*) FROM sqlite_master \
WHERE type = 'trigger' AND (\
name LIKE '%_blocked_by_runtime_removal' OR \
name LIKE 'runtime_binding_%_blocked_by_removal'\
)",
[],
|row| row.get(0),
)?;
let foreign_key_failures: i64 =
conn.query_row("SELECT COUNT(*) FROM pragma_foreign_key_check", [], |row| {
row.get(0)
})?;
assert_eq!(table_count, 1);
assert_eq!(trigger_count, 18);
assert_eq!(foreign_key_failures, 0);
Ok(())
}
#[test]
@@ -16931,12 +16934,10 @@ mod tests {
let path = temp.path().join("server.db");
prepare_retained_schema(&path, 60);
drop(SqliteWorkspaceStore::open_for_test(&path).unwrap());
let conn = Connection::open(&path).unwrap();
assert_eq!(
current_schema_version(&conn).unwrap(),
LATEST_SCHEMA_VERSION
);
configure_sqlite(&conn).unwrap();
migrate_worker_run_generation_v60_to_v61(&conn).unwrap();
assert_eq!(current_schema_version(&conn).unwrap(), 61);
let columns = conn
.prepare("PRAGMA table_info(worker_removal_operations)")
.unwrap()
@@ -17506,34 +17507,100 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
include!("migration_entrypoint_tests.rs");
include!("legacy_evidence_column_tests.rs");
fn prepare_retained_schema(path: &Path, version: i64) {
prepare_schema_v52(path);
let conn = Connection::open(path).unwrap();
#[test]
fn retained_fixture_copies_isolate_seed_rows_and_keep_connection_constraints() {
let temp = tempfile::tempdir().unwrap();
let first = temp.path().join("first.db");
prepare_retained_schema(&first, 59);
let conn = Connection::open(first).unwrap();
conn.execute("INSERT INTO accounts(account_id,kind,handle,display_name,created_at,updated_at) VALUES('owner','user','owner','Owner','1','1')", []).unwrap();
let second = temp.path().join("second.db");
prepare_retained_schema(&second, 59);
let conn = Connection::open(second).unwrap();
configure_sqlite(&conn).unwrap();
for migration in MIGRATIONS
.iter()
.filter(|migration| migration.version > 52 && migration.version <= version)
{
(migration.apply)(&conn).unwrap();
assert_eq!(current_schema_version(&conn).unwrap(), 59);
verify_schema_history(&conn, 59).unwrap();
assert_eq!(
conn.query_row("SELECT COUNT(*) FROM accounts", [], |row| row
.get::<_, i64>(0))
.unwrap(),
0
);
assert_eq!(
conn.query_row("PRAGMA journal_mode", [], |row| row.get::<_, String>(0))
.unwrap(),
"wal"
);
assert!(conn.execute("INSERT INTO workspaces(workspace_id,owner_account_id,display_name,state,created_at,updated_at) VALUES('invalid','missing','Invalid','active','1','1')", []).is_err());
}
fn prepare_retained_schema(path: &Path, version: i64) {
// Cache only empty preconditions, built from frozen DDL and real retained
// migrations. The test's seeded rows and migration under test are never shared.
static TEMPLATES: std::sync::OnceLock<Mutex<std::collections::BTreeMap<i64, Connection>>> =
std::sync::OnceLock::new();
assert!((52..=LATEST_SCHEMA_VERSION).contains(&version));
let mut templates = TEMPLATES
.get_or_init(|| Mutex::new(Default::default()))
.lock()
.unwrap();
if !templates.contains_key(&version) {
let mut source = Connection::open_in_memory().unwrap();
let baseline = if let Some((&baseline, prior)) = templates.range(..=version).next_back()
{
Backup::new(prior, &mut source)
.unwrap()
.run_to_completion(128, Duration::from_millis(1), None)
.unwrap();
configure_sqlite(&source).unwrap();
baseline
} else {
build_schema_v52(&source);
52
};
for migration in MIGRATIONS
.iter()
.filter(|migration| migration.version > baseline && migration.version <= version)
{
(migration.apply)(&source).unwrap();
}
assert_eq!(current_schema_version(&source).unwrap(), version);
templates.insert(version, source);
}
let mut conn = Connection::open(path).unwrap();
Backup::new(&templates[&version], &mut conn)
.unwrap()
.run_to_completion(128, Duration::from_millis(1), None)
.unwrap();
configure_sqlite(&conn).unwrap();
}
fn prepare_schema_v52(path: &Path) {
let conn = Connection::open(path).unwrap();
configure_sqlite(&conn).unwrap();
prepare_retained_schema(path, 52);
}
fn build_schema_v52(conn: &Connection) {
configure_sqlite(conn).unwrap();
// Batch fixture DDL only, never the production migration under test.
// Commit before migration calls and foreign_keys changes below.
let fixture = conn.unchecked_transaction().unwrap();
// Retained fixtures start from genuine pre-tracking Ticket DDL; current
// component 11 would introduce assignment objects before frozen host 85.
conn.execute_batch(include_str!("frozen_ticket_schema_v10.sql"))
.unwrap();
conn.execute_batch("CREATE TABLE ticket_schema_migrations(version INTEGER PRIMARY KEY,name TEXT NOT NULL,applied_at TEXT NOT NULL); INSERT INTO ticket_schema_migrations VALUES(10,'ticket schema baseline','fixture');").unwrap();
fixture.commit().unwrap();
merge_request::migrate(&conn).unwrap();
conn.execute_batch("PRAGMA foreign_keys = OFF; PRAGMA legacy_alter_table = ON;")
.unwrap();
let fixture = conn.unchecked_transaction().unwrap();
create_frozen_schema_v85(&conn).unwrap();
downgrade_assignment_work_schema(&conn);
downgrade_schema_66_ticket_and_workdir_authority(&conn).unwrap();
fixture.commit().unwrap();
conn.execute_batch("PRAGMA legacy_alter_table = OFF; PRAGMA foreign_keys = ON;")
.unwrap();
let fixture = conn.unchecked_transaction().unwrap();
conn.execute_batch(
r#"
DROP TABLE workdir_create_credential_revision_retentions;
@@ -17598,12 +17665,14 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
"#,
)
.unwrap();
fixture.commit().unwrap();
}
fn prepare_schema_v50(path: &Path, workspace_id: Option<&str>) {
prepare_schema_v52(path);
let conn = Connection::open(path).unwrap();
configure_sqlite(&conn).unwrap();
let fixture = conn.unchecked_transaction().unwrap();
conn.execute_batch(
r#"
DROP TABLE workspace_runtime_binding_audit;
@@ -17654,6 +17723,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
[],
)
.unwrap();
fixture.commit().unwrap();
}
#[test]
@@ -18487,10 +18557,8 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
[],
)
.unwrap();
drop(conn);
SqliteWorkspaceStore::migrate_database(&path).unwrap();
let conn = Connection::open(&path).unwrap();
migrate_workspace_runtime_bindings_v50_to_v51(&conn).unwrap();
assert_eq!(current_schema_version(&conn).unwrap(), 51);
let binding_workspaces = {
let mut stmt = conn
.prepare(
@@ -18525,7 +18593,9 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
let path = temp.path().join("server.db");
prepare_schema_v50(&path, None);
let plan = SqliteWorkspaceStore::migration_plan(&path).unwrap();
let conn = Connection::open(&path).unwrap();
configure_sqlite(&conn).unwrap();
let plan = workspace_schema_migration_plan(&conn).unwrap();
assert_eq!(plan.current_schema_version, 50);
assert_eq!(plan.target_schema_version, LATEST_SCHEMA_VERSION);
@@ -18535,12 +18605,10 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
assert!(!table_exists(&unchanged, "workspace_runtime_bindings").unwrap());
drop(unchanged);
SqliteWorkspaceStore::migrate_database(&path).unwrap();
migrate_workspace_runtime_bindings_v50_to_v51(&conn).unwrap();
drop(conn);
let migrated = Connection::open(&path).unwrap();
assert_eq!(
current_schema_version(&migrated).unwrap(),
LATEST_SCHEMA_VERSION
);
assert_eq!(current_schema_version(&migrated).unwrap(), 51);
assert_eq!(
migrated
.query_row(
@@ -18564,7 +18632,8 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
}
#[test]
fn schema_v52_migrates_deletion_operations_and_normalizes_incomplete_reservations() {
fn schema_v52_migrates_deletion_operations_and_normalizes_incomplete_reservations() -> Result<()>
{
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("server.db");
prepare_schema_v52(&path);
@@ -18590,59 +18659,51 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
);",
)
.unwrap();
drop(conn);
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
store
.with_conn(|conn| {
assert_eq!(current_schema_version(conn)?, LATEST_SCHEMA_VERSION);
verify_workspace_deletion_schema(conn)?;
let reservation_state: String = conn.query_row(
"SELECT state FROM worker_create_reservations WHERE workspace_id='workspace-a' AND allocation_key='allocation'",
[],
|row| row.get(0),
)?;
assert_eq!(reservation_state, "created");
let reservation_memory = conn.query_row(
"SELECT request_fingerprint, memory_language
FROM worker_create_reservations
WHERE workspace_id='workspace-a' AND allocation_key='allocation'",
[],
|row| {
Ok((
row.get::<_, Option<String>>(0)?,
row.get::<_, Option<String>>(1)?,
))
},
)?;
assert_eq!(reservation_memory, (None, None));
let complete_memory = conn.query_row(
"SELECT request_fingerprint, memory_language
FROM worker_create_reservations
WHERE workspace_id='workspace-a' AND allocation_key='allocation-complete'",
[],
|row| {
Ok((
row.get::<_, Option<String>>(0)?,
row.get::<_, Option<String>>(1)?,
))
},
)?;
assert_eq!(
complete_memory,
(
Some("request-complete".to_string()),
Some("ja".to_string())
)
);
let violations: i64 =
conn.query_row("SELECT COUNT(*) FROM pragma_foreign_key_check", [], |row| {
row.get(0)
})?;
assert_eq!(violations, 0);
Ok(())
})
.unwrap();
configure_sqlite(&conn)?;
migrate_workspace_deletion_v52_to_v53(&conn)?;
assert_eq!(current_schema_version(&conn)?, 53);
verify_workspace_deletion_schema(&conn)?;
let reservation_state: String = conn.query_row(
"SELECT state FROM worker_create_reservations WHERE workspace_id='workspace-a' AND allocation_key='allocation'",
[],
|row| row.get(0),
)?;
assert_eq!(reservation_state, "created");
let reservation_memory = conn.query_row(
"SELECT request_fingerprint, memory_language
FROM worker_create_reservations
WHERE workspace_id='workspace-a' AND allocation_key='allocation'",
[],
|row| {
Ok((
row.get::<_, Option<String>>(0)?,
row.get::<_, Option<String>>(1)?,
))
},
)?;
assert_eq!(reservation_memory, (None, None));
let complete_memory = conn.query_row(
"SELECT request_fingerprint, memory_language
FROM worker_create_reservations
WHERE workspace_id='workspace-a' AND allocation_key='allocation-complete'",
[],
|row| {
Ok((
row.get::<_, Option<String>>(0)?,
row.get::<_, Option<String>>(1)?,
))
},
)?;
assert_eq!(
complete_memory,
(Some("request-complete".to_string()), Some("ja".to_string()))
);
let violations: i64 =
conn.query_row("SELECT COUNT(*) FROM pragma_foreign_key_check", [], |row| {
row.get(0)
})?;
assert_eq!(violations, 0);
Ok(())
}
#[test]
@@ -18718,7 +18779,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
fn runtime_binding_identity_and_trust_uniqueness_are_workspace_scoped() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
store
.with_conn(|conn| {
conn.execute_batch(
@@ -18870,7 +18931,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
fn workspace_runtime_connection_persists_without_authentication_evidence() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
store
.with_conn(|conn| {
conn.execute_batch(
@@ -19169,7 +19230,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
#[test]
fn runtime_binding_key_mutations_are_identity_guarded_idempotent_and_audited() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store
.with_conn(|conn| {
conn.execute_batch(
@@ -19307,7 +19368,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
#[test]
fn embedded_runtime_binding_can_explicitly_rotate_restart_identity() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store
.with_conn(|conn| {
conn.execute_batch(
@@ -19419,7 +19480,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
async fn workspace_schema_rejects_cross_workspace_ticket_repository_reference_at_write_time() {
let dir = tempfile::tempdir().unwrap();
let database_path = dir.path().join("workspace.sqlite");
let store = SqliteWorkspaceStore::open_for_test(&database_path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&database_path).unwrap();
for workspace_id in ["workspace-a", "workspace-b"] {
store
.upsert_workspace(&WorkspaceRecord {
@@ -19513,7 +19574,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
async fn workspace_display_name_update_is_timestamp_guarded_and_preserves_identity() {
let dir = tempfile::tempdir().unwrap();
let database_path = dir.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&database_path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&database_path).unwrap();
let record = WorkspaceRecord {
workspace_id: "workspace-a".to_string(),
owner_account_id: "owner-account".to_string(),
@@ -19566,7 +19627,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
#[tokio::test]
async fn objective_creation_allocates_and_resolves_workspace_resource_key() {
let dir = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(dir.path().join("server.db")).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(dir.path().join("server.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".into(),
@@ -19612,7 +19673,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
#[tokio::test]
async fn worker_create_reservation_allocates_uuid_before_runtime_and_replays_exact_input() {
let dir = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(dir.path().join("server.db")).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(dir.path().join("server.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".to_string(),
@@ -19916,7 +19977,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
async fn worker_singleton_restart_retry_replays_exact_workdir_intent_and_rejects_changes() {
let dir = tempfile::tempdir().unwrap();
let database = dir.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&database).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&database).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".to_string(),
@@ -20020,7 +20081,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
async fn worker_singleton_ownership_is_workspace_scoped_durable_and_fences_old_generations() {
let dir = tempfile::tempdir().unwrap();
let database = dir.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&database).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&database).unwrap();
for workspace_id in ["workspace-a", "workspace-b"] {
store
.upsert_workspace(&WorkspaceRecord {
@@ -20146,7 +20207,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
async fn concurrent_worker_singleton_reservations_have_one_winner() {
let dir = tempfile::tempdir().unwrap();
let database = dir.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&database).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&database).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".to_string(),
@@ -20202,7 +20263,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
#[tokio::test]
async fn workspace_flow_sources_pin_content_and_builtins_stay_resources() {
let dir = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(dir.path().join("server.db")).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(dir.path().join("server.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".to_string(),
@@ -21144,7 +21205,8 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
// boundary under test. Targets need not be materialized by a Runtime.
async fn manual_worker_start_store() -> (tempfile::TempDir, SqliteWorkspaceStore) {
let dir = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(dir.path().join("workspace.db")).unwrap();
let store =
SqliteWorkspaceStore::open_api_fixture(dir.path().join("workspace.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "w".into(),
@@ -21879,7 +21941,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
use ticket::TicketBackend;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("workspace.db");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "w".into(),
@@ -22007,7 +22069,7 @@ CREATE UNIQUE INDEX ticket_current_worker_role_idx
async fn ticket_worker_assignment_replaces_current_and_preserves_audit_history() {
let dir = tempfile::tempdir().unwrap();
let db = dir.path().join("server.db");
let store = SqliteWorkspaceStore::open_for_test(&db).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&db).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-a".to_string(),
@@ -22322,7 +22384,7 @@ INSERT INTO worker_registry (
async fn role_assignment_routes_and_manual_start_are_state_fenced_atomically() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("workspace.db");
let store = SqliteWorkspaceStore::open_for_test(&db_path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&db_path).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-role".to_string(),
@@ -22732,7 +22794,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn workspace_bootstrap_validates_key_and_rejects_duplicate_workspace() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store
.upsert_account(&AccountRecord {
account_id: "owner-account".to_string(),
@@ -22864,7 +22926,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn repository_records_round_trip() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
assert_eq!(store.schema_version().await.unwrap(), LATEST_SCHEMA_VERSION);
let workspace = WorkspaceRecord {
workspace_id: "local-dev".to_string(),
@@ -22945,7 +23007,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn memory_authority_records_round_trip_and_close_staging() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
assert_eq!(store.schema_version().await.unwrap(), LATEST_SCHEMA_VERSION);
let workspace = WorkspaceRecord {
workspace_id: "local-dev".to_string(),
@@ -23020,7 +23082,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn legacy_memory_reset_is_atomic_scoped_idempotent_and_exact() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
for (workspace_id, display_name) in [
("local-dev", "Local Dev"),
("other-workspace", "Other Workspace"),
@@ -23187,7 +23249,7 @@ INSERT INTO worker_registry (
async fn worker_workdir_registry_round_trips_and_preserves_pinned_retention() {
let temp = tempfile::tempdir().unwrap();
let db = temp.path().join("workspace.db");
let store = SqliteWorkspaceStore::open_for_test(&db).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&db).unwrap();
let workspace = WorkspaceRecord {
workspace_id: "local-dev".to_string(),
owner_account_id: "owner-account".to_string(),
@@ -23604,7 +23666,8 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn worker_projection_preserves_equal_local_ids_from_distinct_runtimes() {
let temp = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(temp.path().join("workspace.db")).unwrap();
let store =
SqliteWorkspaceStore::open_api_fixture(temp.path().join("workspace.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "local-dev".to_string(),
@@ -23730,7 +23793,8 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn worker_projection_reconciles_snapshots_and_preserves_unavailable_lifecycle() {
let temp = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(temp.path().join("workspace.db")).unwrap();
let store =
SqliteWorkspaceStore::open_api_fixture(temp.path().join("workspace.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "local-dev".to_string(),
@@ -23905,7 +23969,8 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn late_runtime_event_does_not_recreate_retention_deleted_catalog_worker() {
let temp = tempfile::tempdir().unwrap();
let store = SqliteWorkspaceStore::open_for_test(temp.path().join("workspace.db")).unwrap();
let store =
SqliteWorkspaceStore::open_api_fixture(temp.path().join("workspace.db")).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "local-dev".to_string(),
@@ -24000,7 +24065,7 @@ INSERT INTO worker_registry (
async fn worker_control_grants_are_idempotent_scoped_and_support_internal_invalidation() {
let dir = tempfile::tempdir().unwrap();
let database = dir.path().join("control-grants.db");
let store = SqliteWorkspaceStore::open_for_test(&database).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&database).unwrap();
store
.upsert_workspace(&WorkspaceRecord {
workspace_id: "workspace-control".to_string(),
@@ -24128,7 +24193,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn account_and_login_records_round_trip() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
assert_eq!(store.schema_version().await.unwrap(), LATEST_SCHEMA_VERSION);
let now = "2026-07-22T00:00:00Z".to_string();
let account = AccountRecord {
@@ -24488,7 +24553,7 @@ INSERT INTO worker_registry (
async fn backend_job_resource_reserve_converges_across_connections_without_changing_input() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("resource-jobs.sqlite");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
seed_resource_job_workspace(&store).await;
let other = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let requests = [
@@ -24606,7 +24671,7 @@ INSERT INTO worker_registry (
async fn backend_job_resource_retry_and_unknown_are_fenced_by_durable_cleanup() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("cleanup-jobs.sqlite");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
seed_resource_job_workspace(&store).await;
let request = resource_job_request("failed", "subject-a", "candidate-1");
let first = store
@@ -24772,7 +24837,7 @@ INSERT INTO worker_registry (
async fn backend_job_resource_retry_races_new_intent_atomically_and_remains_bounded() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("retry-race.sqlite");
let store = SqliteWorkspaceStore::open_for_test(&path).unwrap();
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
seed_resource_job_workspace(&store).await;
// Generic resources use the same common reservation API without domain grants.
let mut request = resource_job_request("generic", "subject-a", "candidate-1");
@@ -24881,7 +24946,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn backend_job_all_older_attempt_cleanups_fence_retry_and_resource_release() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
seed_resource_job_workspace(&store).await;
let mut request = resource_job_request("legacy-overlap", "subject-a", "candidate-1");
request.limits.max_attempts = 3;
@@ -25005,7 +25070,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn backend_job_empty_batch_surface_refresh_locks_subject_and_defers_candidates() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
seed_resource_job_workspace(&store).await;
for surface_state in ["ungenerated", "stale", "failed"] {
let subject = format!("subject-{surface_state}");
@@ -25125,7 +25190,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn backend_job_completed_resource_releases_only_after_worker_cleanup() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
seed_resource_job_workspace(&store).await;
let request = resource_job_request("completed", "subject-a", "candidate-1");
let first = store
@@ -25179,7 +25244,7 @@ INSERT INTO worker_registry (
#[tokio::test]
async fn backend_jobs_fence_results_project_ownership_and_bound_retries() {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
let workspace_id = "workspace-job";
store
.upsert_account(&AccountRecord {
@@ -0,0 +1,190 @@
//! Ordinary store/API fixtures copy a verified empty schema into independent real DBs.
//! Each case owns its connections and executors, with production SQLite settings.
//! Additional connections within fresh API setup reuse its verified schema. Migration,
//! fresh-creation, drift detection and persisted-case reopen tests use uncached constructors.
use super::*;
impl SqliteWorkspaceStore {
pub(crate) fn open_api_fixture(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
if path.exists() {
// Restart tests must reopen their persisted database, not reset it.
return Self::open_for_test(path);
}
Self::from_fixture_connection(Connection::open(path)?)
}
pub(crate) fn in_memory_fixture() -> Result<Self> {
Self::from_fixture_connection(Connection::open_in_memory()?)
}
// Only the fresh API fixture may use this after copying its verified template.
// Open a distinct connection without replacing the DB or sharing a transaction.
pub(crate) fn connect_prepared_fixture(path: &Path) -> Result<Self> {
let conn = Connection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE)?;
configure_sqlite(&conn)?;
Ok(Self {
conn: Arc::new(Mutex::new(conn)),
db_executor: crate::db_executor::DbExecutor::new(
crate::db_executor::DbExecutorLimits::default(),
)?,
})
}
fn from_fixture_connection(mut conn: Connection) -> Result<Self> {
static TEMPLATE: std::sync::OnceLock<Mutex<Option<Connection>>> =
std::sync::OnceLock::new();
{
// Only initialization/copy is serialized, never a test's execution.
let mut template = TEMPLATE
.get_or_init(|| Mutex::new(None))
.lock()
.map_err(|_| Error::Store("API fixture template lock poisoned".into()))?;
if template.is_none() {
let source = Connection::open_in_memory()?;
prepare_connection(&source)?;
*template = Some(source);
}
Backup::new(template.as_ref().unwrap(), &mut conn)?.run_to_completion(
128,
Duration::from_millis(1),
None,
)?;
}
// Backup copies the already verified schema, not connection-local PRAGMAs.
// Existing DBs above still pass through full production startup checks.
configure_sqlite(&conn)?;
Ok(Self {
conn: Arc::new(Mutex::new(conn)),
db_executor: crate::db_executor::DbExecutor::new(
crate::db_executor::DbExecutorLimits::default(),
)?,
})
}
}
#[test]
fn prepared_fixture_connections_observe_only_committed_writes_and_keep_foreign_keys() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("connections.db");
assert!(SqliteWorkspaceStore::connect_prepared_fixture(&path).is_err());
assert!(!path.exists());
let writer = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
let reader = SqliteWorkspaceStore::connect_prepared_fixture(&path).unwrap();
writer.with_conn(|conn| {
let tx = conn.unchecked_transaction()?;
tx.execute("INSERT INTO accounts(account_id,kind,handle,display_name,created_at,updated_at) VALUES('owner','user','owner','Owner','1','1')", [])?;
reader.with_conn(|other| {
assert!(other.is_autocommit());
assert_eq!(other.query_row("SELECT COUNT(*) FROM accounts", [], |row| row.get::<_, i64>(0))?, 0);
Ok(())
})?;
tx.commit()?;
Ok(())
}).unwrap();
reader.with_conn(|conn| {
assert_eq!(conn.query_row("SELECT COUNT(*) FROM accounts", [], |row| row.get::<_, i64>(0))?, 1);
assert_eq!(conn.query_row("PRAGMA journal_mode", [], |row| row.get::<_, String>(0))?, "wal");
assert!(conn.execute("INSERT INTO workspaces(workspace_id,owner_account_id,display_name,state,created_at,updated_at) VALUES('invalid','missing','Invalid','active','1','1')", []).is_err());
Ok(())
}).unwrap();
}
#[test]
fn memory_fixture_copies_fresh_schema_and_isolates_data_and_ddl() {
fn schema(store: &SqliteWorkspaceStore) -> Vec<(String, String, Option<String>)> {
store
.with_conn(|conn| {
let mut statement =
conn.prepare("SELECT type, name, sql FROM sqlite_schema ORDER BY type, name")?;
let rows = statement
.query_map([], |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)))?
.collect::<rusqlite::Result<Vec<_>>>()?;
Ok(rows)
})
.unwrap()
}
let fresh = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let first = SqliteWorkspaceStore::in_memory_fixture().unwrap();
assert_eq!(schema(&first), schema(&fresh));
first.with_conn(|conn| {
assert_eq!(conn.query_row("PRAGMA foreign_keys", [], |row| row.get::<_, i64>(0))?, 1);
conn.execute_batch("INSERT INTO accounts(account_id,kind,handle,display_name,created_at,updated_at) VALUES('owner','user','owner','Owner','1','1'); DROP TRIGGER ticket_assignment_ticket_parent_tombstone;")?;
Ok(())
}).unwrap();
let second = SqliteWorkspaceStore::in_memory_fixture().unwrap();
assert_eq!(schema(&second), schema(&fresh));
second.with_conn(|conn| {
assert_eq!(conn.query_row("SELECT COUNT(*) FROM accounts", [], |row| row.get::<_, i64>(0))?, 0);
assert!(conn.execute("INSERT INTO workspaces(workspace_id,owner_account_id,display_name,state,created_at,updated_at) VALUES('invalid','missing','Invalid','active','1','1')", []).is_err());
Ok(())
}).unwrap();
}
#[test]
fn api_fixture_reopen_rejects_schema_drift_instead_of_replacing_the_database() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("drift.db");
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
store
.with_conn(|conn| {
conn.execute_batch("DROP TRIGGER ticket_assignment_ticket_parent_tombstone;")?;
Ok(())
})
.unwrap();
drop(store);
let error = SqliteWorkspaceStore::open_api_fixture(&path)
.err()
.expect("reopen must verify existing schema");
assert!(
error
.to_string()
.contains("ticket_assignment_ticket_parent_tombstone")
);
let conn = Connection::open(path).unwrap();
assert_eq!(conn.query_row("SELECT COUNT(*) FROM sqlite_schema WHERE name='ticket_assignment_ticket_parent_tombstone'", [], |row| row.get::<_, i64>(0)).unwrap(), 0);
}
#[test]
fn api_fixture_databases_isolate_writes_and_reopen_persisted_rows() {
let temp = tempfile::tempdir().unwrap();
std::thread::scope(|scope| {
let handles: Vec<_> = ["first", "second"]
.into_iter()
.map(|name| {
let path = temp.path().join(format!("{name}.db"));
scope.spawn(move || {
let store = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
store.with_conn(|conn| {
assert_eq!(conn.query_row("PRAGMA foreign_keys", [], |row| row.get::<_, i64>(0))?, 1);
assert_eq!(conn.query_row("PRAGMA journal_mode", [], |row| row.get::<_, String>(0))?, "wal");
conn.execute("INSERT INTO accounts(account_id,kind,handle,display_name,created_at,updated_at) VALUES('owner','user','owner',?1,'1','1')", [name])?;
assert!(conn.execute("INSERT INTO workspaces(workspace_id,owner_account_id,display_name,state,created_at,updated_at) VALUES('invalid','missing','Invalid','active','1','1')", []).is_err());
Ok(())
}).unwrap();
drop(store);
let reopened = SqliteWorkspaceStore::open_api_fixture(&path).unwrap();
reopened.with_conn(|conn| {
assert_eq!(conn.query_row("SELECT display_name FROM accounts WHERE account_id='owner'", [], |row| row.get::<_, String>(0))?, name);
Ok(())
}).unwrap();
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
});
let pristine = SqliteWorkspaceStore::open_api_fixture(temp.path().join("pristine.db")).unwrap();
pristine
.with_conn(|conn| {
assert_eq!(
conn.query_row("SELECT COUNT(*) FROM accounts", [], |row| row
.get::<_, i64>(0))?,
0
);
Ok(())
})
.unwrap();
}
@@ -286,6 +286,94 @@ pub(crate) fn render_result(acceptance: &BackendJobResultAcceptance) -> Result<S
Ok(content)
}
// Supplied model outputs exercise host validation/rendering, not LLM judgment.
#[cfg(test)]
pub(crate) mod test_support {
use super::*;
pub(crate) struct FakeTicketCheckerModel;
impl FakeTicketCheckerModel {
pub(crate) fn respond(request: &BackendJobRequest) -> Value {
let input = parse_input(&request.input).unwrap();
assert!(request.instruction.contains("writer_scope"));
assert!(request.instruction.contains("internal_inconsistency"));
assert!(request.instruction.contains("ambiguous_boundary"));
assert!(
request
.instruction
.contains("Production deployment requires approval")
);
assert!(request.instruction.contains("background, quotations"));
if input
.body
.contains("This time I only create the Ticket; I will not implement it.")
&& !input.body.starts_with("Background bug example:")
{
serde_json::json!({"findings": [{
"category": "writer_scope",
"quote": "I will not implement it",
"reason": "The active requirement records the writer's own implementation boundary.",
"suggestion": "Remove the writer-specific action boundary."
}]})
} else if input.body.contains("The rollout region is undecided.")
&& input.body.contains("The rollout region is us-east-1.")
{
serde_json::json!({"findings": [{
"category": "internal_inconsistency",
"quote": "The rollout region is undecided.",
"reason": "The same active text also settles the rollout region as us-east-1.",
"suggestion": "Choose either the unresolved or settled rollout-region statement."
}]})
} else if input.body == "Implementation stops after Ticket creation." {
serde_json::json!({"findings": [{
"category": "ambiguous_boundary",
"quote": "Implementation stops after Ticket creation.",
"reason": "The text alone does not establish whether this is a delivery constraint or the writer's current action boundary.",
"suggestion": "Confirm whether this limits the product workflow or only the current writer."
}]})
} else {
serde_json::json!({"findings": []})
}
}
}
pub(crate) struct Case {
pub(crate) name: &'static str,
pub(crate) body: &'static str,
pub(crate) advisory_quote: Option<&'static str>,
}
pub(crate) fn cases() -> Vec<Case> {
vec![
Case {
name: "writer-scope-positive",
body: "This time I only create the Ticket; I will not implement it.",
advisory_quote: Some("I will not implement it"),
},
Case {
name: "production-approval-negative",
body: "Production deployment requires approval.",
advisory_quote: None,
},
Case {
name: "quoted-background-negative",
body: "Background bug example: a generated Ticket once said “I will not implement it.” Do not reproduce that sentence in generated requirements.",
advisory_quote: None,
},
Case {
name: "unresolved-versus-settled-positive",
body: "The rollout region is undecided. The rollout region is us-east-1.",
advisory_quote: Some("The rollout region is undecided."),
},
Case {
name: "ambiguous-boundary-confirmation",
body: "Implementation stops after Ticket creation.",
advisory_quote: Some("Implementation stops after Ticket creation."),
},
]
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -301,16 +389,144 @@ mod tests {
})
}
fn ticket_fixture() -> ticket::Ticket {
ticket::Ticket {
meta: ticket::TicketMeta {
id: "ticket-internal".into(),
resource_key: Some("T-42".into()),
slug: "check-this-content".into(),
title: "Check this content".into(),
status: ticket::ExtensibleTicketStatus::Open,
kind: "task".into(),
priority: "P2".into(),
labels: vec![],
created_at: None,
created_by: None,
updated_at: None,
assignee: None,
readiness: None,
risk_flags: vec![],
workflow_state: ticket::TicketState::Open,
workflow_state_explicit: true,
queued_by: None,
queued_at: None,
targets: vec![],
raw: Default::default(),
},
document: ticket::TicketDocument {
body: ticket::MarkdownText::new("Check the saved content."),
raw_frontmatter: Default::default(),
},
tags: vec![],
events: vec![],
artifacts: vec![],
relations: Default::default(),
resolution: None,
closure_operation_key: None,
current_closure: None,
case_history: vec![],
legacy_workflow_snapshot: None,
}
}
fn completed_acceptance(
request: BackendJobRequest,
result: Value,
) -> BackendJobResultAcceptance {
use crate::backend_job::{
BackendJobAttemptRecord, BackendJobAttemptState, BackendJobRecord, BackendJobState,
};
let digest = crate::backend_job::result_digest(&result, request.limits.max_result_bytes)
.unwrap()
.0;
let attempt = BackendJobAttemptRecord {
workspace_id: "checker-unit".into(),
job_id: request.job_id.clone(),
attempt_id: "checker-attempt".into(),
attempt: 1,
input_digest: request.input_digest().unwrap(),
state: BackendJobAttemptState::Completed,
worker: Some(RuntimeWorkerRef::new("runtime", "checker")),
runtime_run_id: Some("run".into()),
dispatched_at: Some("1".into()),
deadline_at: "3".into(),
result: Some(result.clone()),
result_digest: Some(digest.clone()),
failure_category: None,
failure_detail: None,
worker_cleanup_state: None,
worker_cleanup_failure_category: None,
worker_cleanup_failure_detail: None,
worker_cleanup_updated_at: None,
worker_cleanup_completed_at: None,
created_at: "1".into(),
updated_at: "2".into(),
completed_at: Some("2".into()),
};
BackendJobResultAcceptance {
job: BackendJobRecord {
workspace_id: "checker-unit".into(),
intent_fingerprint: request.fingerprint().unwrap(),
request,
state: BackendJobState::Completed,
current_attempt: 1,
result: Some(result),
result_digest: Some(digest),
failure_category: None,
failure_detail: None,
created_at: "1".into(),
updated_at: "2".into(),
completed_at: Some("2".into()),
},
attempt,
replayed: false,
}
}
#[test]
fn supplied_model_classifications_validate_and_render_advisories_or_no_findings() {
let instruction = worker::PromptCatalog::builtins_only()
.unwrap()
.ticket_item_checker()
.unwrap();
for case in test_support::cases() {
let mut ticket = ticket_fixture();
ticket.meta.title = format!("Boundary case: {}", case.name);
ticket.document.body = ticket::MarkdownText::new(case.body);
let request = request(
&ticket,
TicketItemCheckEdit::Create,
RuntimeWorkerRef::new("runtime", "source"),
instruction.clone(),
)
.unwrap();
let result = test_support::FakeTicketCheckerModel::respond(&request);
let checked = validate_result(&request.input, &result).unwrap();
let acceptance = completed_acceptance(request, result);
match (
case.advisory_quote,
result_facts(&acceptance, &ticket).unwrap(),
) {
(Some(quote), TicketItemCheckFacts::Findings(advisory)) => {
assert_eq!(checked.findings.len(), 1, "{}", case.name);
assert!(
advisory.contains("[Ticket item checker advisory]"),
"{}",
case.name
);
assert!(advisory.contains(quote), "{}", case.name);
}
(None, TicketItemCheckFacts::NoFindings) => {
assert!(checked.findings.is_empty(), "{}", case.name);
}
(_, actual) => panic!("{}: unexpected classification {actual:?}", case.name),
}
}
}
#[test]
fn checker_job_identity_reuses_exact_intent_but_separates_edits_and_sources() {
use ticket::TicketBackend;
let dir = tempfile::tempdir().unwrap();
let backend =
ticket::SqliteTicketBackend::open(dir.path().join("tickets.db"), "space").unwrap();
let (_, ticket) = backend
.create_with_snapshot(ticket::NewTicket::new("Check this content"))
.unwrap();
let ticket = ticket_fixture();
let source = RuntimeWorkerRef {
runtime_id: "runtime".into(),
worker_id: "source".into(),
@@ -939,7 +939,7 @@ mod tests {
}
async fn seeded_store() -> (SqliteWorkspaceStore, WorkdirRegistryRecord) {
let store = SqliteWorkspaceStore::in_memory_for_test().unwrap();
let store = SqliteWorkspaceStore::in_memory_fixture().unwrap();
store
.upsert_account(&AccountRecord {
account_id: "account-a".to_string(),
@@ -302,7 +302,7 @@ mod tests {
#[tokio::test]
async fn create_is_atomic_and_exact_retries_converge() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let service = WorkspaceCatalogService::new(
store.clone(),
Arc::new(
@@ -356,7 +356,7 @@ mod tests {
#[test]
fn create_rejects_noncanonical_repository_keys_without_persisting_a_workspace() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let service = WorkspaceCatalogService::new(
store.clone(),
Arc::new(
@@ -517,7 +517,7 @@ mod tests {
#[tokio::test]
async fn idempotency_key_reuse_with_different_payload_is_rejected() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let owner_account_id = owner_account(store.as_ref());
let service = WorkspaceCatalogService::new(
store,
@@ -549,7 +549,7 @@ mod tests {
#[test]
fn repository_free_create_initializes_authority_and_concurrent_retries_converge() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let owner = owner_account(store.as_ref());
let service = WorkspaceCatalogService::new(
store.clone(),
@@ -628,7 +628,7 @@ mod tests {
#[test]
fn replay_reads_current_repository_at_initial_key_and_never_recreates_it() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let owner = owner_account(store.as_ref());
let service = WorkspaceCatalogService::new(
store.clone(),
@@ -756,7 +756,7 @@ mod tests {
#[test]
fn create_rejects_non_user_account_owners() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
store
.upsert_account(&AccountRecord {
account_id: "organization-owner".to_string(),
@@ -794,7 +794,7 @@ mod tests {
#[test]
fn catalog_list_is_scoped_to_the_required_owner_account() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let owner_a = owner_account(store.as_ref());
let owner_b = "account-owner-b".to_string();
store
@@ -862,7 +862,7 @@ mod tests {
#[test]
fn remote_repository_creation_persists_typed_source_without_auth_metadata() {
let store = Arc::new(SqliteWorkspaceStore::in_memory_for_test().unwrap());
let store = Arc::new(SqliteWorkspaceStore::in_memory_fixture().unwrap());
let owner_account_id = owner_account(store.as_ref());
let service = WorkspaceCatalogService::new(
store.clone(),
@@ -747,7 +747,7 @@ mod tests {
WorkspaceRecord,
};
let store = std::sync::Arc::new(
SqliteWorkspaceStore::in_memory_for_test()
SqliteWorkspaceStore::in_memory_fixture()
.unwrap()
.with_db_executor(crate::db_executor::DbExecutor::new(Default::default()).unwrap()),
);