Wave-3 review gate: contract conformance clean; two findings fixed inline — substrate boundary move (open_writer_connection → seam.rs), writer-slot release on with_writer/begin/commit error arms + regression tests (task review-wave-3)

This commit is contained in:
glm-5.3-flash committed 2026-10-08 20:57:10 +00:00
1 parent a82c543b40
commit ecb8211694
8 files changed
+290 -34

No files matched your search

+41
View File
@@ -118,6 +118,47 @@ async fn notify_never_produces_payload_too_large() {
cleanup(&dir);
}
/// Acceptance: an auto-commit op's *error* arm releases the writer
/// slot — the lease-out-both-arms property (ADR-007's seam posture):
/// after a notify whose statement fails at the storage layer, the slot
/// is free again and a subsequent writer op succeeds without parking.
#[tokio::test(flavor = "multi_thread")]
async fn failed_notify_releases_the_writer_slot() {
let dir = temp_dir("err-arm-release");
let path = temp_path("err-arm-release");
let store = open_store(path.to_str().unwrap(), Default::default()).unwrap();
let side = rusqlite::Connection::open(&path).unwrap();
side.execute_batch(
"CREATE TRIGGER block_notify BEFORE INSERT ON __alkstore_notifications
BEGIN SELECT RAISE(ABORT, 'notify insert blocked'); END",
)
.unwrap();
let err = store.notify("orders", json!({"x": 1})).await.unwrap_err();
let source = match &err {
Error::Database(source) => source.to_string(),
other => panic!("the induced failure surfaces as Database, got {other:?}"),
};
assert!(
source.contains("notify insert blocked"),
"the source chain carries the trigger's message, got: {source}"
);
side.execute_batch("DROP TRIGGER block_notify;").unwrap();
drop(side);
let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
let landed = tokio::time::timeout_at(deadline, store.notify("orders", json!({"x": 2})))
.await
.expect(
"the failed op must release the writer slot — the slot is free again, \
never parked",
);
landed.expect("the notify lands after a failed op released the slot");
store.close();
cleanup(&dir);
}
/// Acceptance: `listen` returns a working receiver — wakes arrive
/// after commits by other connections, and `Wake { channel }` carries
/// the channel name only. The honest posture: the watcher fans out on
+43
View File
@@ -1015,6 +1015,49 @@ async fn failed_ops_inside_a_tx_keep_the_lease() {
cleanup(&dir);
}
/// Acceptance: a failed `BEGIN IMMEDIATE` does not strand the writer
/// slot — the acquired connection returns to the slot on the begin's
/// error arm (the lease-out-both-arms property at the seam's entry)
/// and the next `begin_tx` succeeds without parking. Induced with a
/// concurrent exclusive holder at a shortened busy timeout, so the
/// begin fails fast with `SQLITE_BUSY` instead of parking.
#[tokio::test(flavor = "multi_thread")]
async fn failed_begin_releases_the_writer_slot() {
let dir = temp_dir("begin-err-release");
let concrete = open_store(dir.join("store.db").to_str().unwrap(), Default::default()).unwrap();
{
let writer_conn = concrete.writer.acquire().unwrap();
writer_conn
.execute_batch("PRAGMA busy_timeout = 100;")
.unwrap();
concrete.writer.release(writer_conn);
}
let holder = rusqlite::Connection::open(dir.join("store.db")).unwrap();
holder.execute_batch("BEGIN EXCLUSIVE; SELECT 1;").unwrap();
let begin = tokio::time::timeout(std::time::Duration::from_secs(5), concrete.begin_tx()).await;
match begin {
Err(_) => panic!("begin must fail fast at the 100 ms busy timeout, not park"),
Ok(Ok(_handle)) => {
panic!("begin must refuse under the exclusive holder, got a lease")
}
Ok(Err(e)) => assert!(
matches!(e, Error::Database(_)),
"a busy-refused begin errs opaque Database, got: {e:?}"
),
}
drop(holder);
let next = tokio::time::timeout(std::time::Duration::from_secs(5), concrete.begin_tx())
.await
.expect("the slot must be grantable after the failed begin")
.unwrap();
drop(next);
drop(concrete);
cleanup(&dir);
}
/// A concurrent writer's commit blocks while a lease holds the slot —
/// the parked-writer posture (ADR-007 negative consequence, honest).
#[tokio::test(flavor = "multi_thread")]