general review 004 (pre-release, whole workspace): one HIGH slot-poisoning fix, two contract-observable divergence fixes, robustness + doc corrections — all gates re-run green

- sqlite scheduler tick: a failed COMMIT (or ROLLBACK) left the connection
  released with a stuck-open transaction, wedging the writer slot forever —
  the one commit site missing the drop-+-reopen-replenish arm; tick now
  takes the conn by value with a poisoned disposition (drop + replenish),
  the commit-fault seam reaches the tick, and
  failed_tick_commit_replenishes_the_writer_slot pins the arm
- pg save_offset_tx stored GREATEST($3,0) where the wave-8 gate's
  negative-seed fix (raw seed, ADR-023 §2 2026-10-11 annotation) covered
  only the auto-commit form; mem's merged in-tx read clamped a staged
  negative seed against a phantom zero; both fixed and the tx-form
  totality pinned by a new stamped suite leg (in_tx_reads_see_own_writes)
  green on all three columns vs the live harness server
- pg: a failed forwarder register leaked the channel refcount forever
  (no receiver to drop it) — listen/spawn_bridge now unregister on the
  register error, mirroring the subscribe-failure arm; register doc
  corrected to put the failure disposition at the call site
- pg forwarder: nine poisoned-mutex expect() panics converted to the
  mem crate's into_inner recovery idiom (no-expect-outside-tests rule)
- core encode_payload: a deeply-nested Value aborted the process in
  serde_json's unguarded serializer recursion and the fuzz pin blessed
  storing bytes no decoder could read back; encode now rejects nesting
  past 127 containers (the empirically-verified decode limit) with typed
  Codec — decode-aligned boundary pins added to the fuzz invariant and
  value_type_tests, applied pre-publish within the open class-1 window
- scheduler sleep deadlines: Instant + Duration overflow caps (MAX_SLEEP_S,
  tick-and-resleep) on all three engines for far-future boundaries;
  sqlite tick claim stamp now saturating_add
- docs read true again: Error::Closed scope (stream try_recv arm),
  validation whitespace wording, sqlite lib.rs phantom durability knobs,
  pg lock.rs honest autocommit wording, mem tx/stream merge + wake-
  granularity wording; wakes.rs stale allow(dead_code) removed
- tests: value_type_tests exercised its own map_err instead of the
  function under test (fixed); unwrap_or_default reference encodes
  panic with cause
- docs/reviews/004-pre-release-general-review.md records nine fixed +
  sixteen parked findings (sqlite seam panic-safety armor, watcher
  post-death-subscribe hang + initial-stat self-kill, pg forwarder
  TOCTOU/hang/diagnosability, pg BIGINT server-side stamp overflow, mem
  growth/outbox notes, pg identifier-truncation doc note, smaller) and
  two false positives caught on verification; implementation.md
  review-rounds entry + core-contract backlog rows updated
- gates: build, workspace tests (sqlite 194, core 27, suite legs new),
  full live pg column 127+25+9, clippy -D warnings, fmt, fuzz replay
  6/6, wasm32 check, doc gate -D warnings — all green
This commit is contained in:
glm-5.3-flash committed 2026-10-11 09:25:59 +00:00
1 parent 5385f6bee3
commit 029f885aa1
24 files changed
+738 -103

No files matched your search

+5 -3
View File
@@ -54,9 +54,11 @@ pub enum Error {
LeadershipLost,
/// An operation against a closed receiver/source.
///
/// The `try_recv`/`recv_timeout` form of wake close (never
/// returned by `recv_timeout`'s timeout expiry, which yields
/// `Ok(None)`); never a timeout signal.
/// The closed arms of the wake receivers' non-`recv` forms
/// (`try_recv`/`recv_timeout` — never
/// `recv_timeout`'s timeout expiry, which yields `Ok(None)`) and
/// the event receiver's `try_recv` on a `Closed`-caused stream
/// receiver; never a timeout signal.
#[error("the receiver or source is closed")]
Closed,
/// Payload serialization or deserialization failure.
+50 -3
View File
@@ -23,13 +23,60 @@ use crate::Error;
/// Fallible per ADR-023 §1: serialization failure is
/// [`Error::Codec`](crate::Error::Codec), typed at this helper — no
/// silent fallback form exists (an empty-byte store would decode to
/// `Null`, silent data corruption). Unreachable for `serde_json::Value`
/// in practice (its variants all serialize), pinned against an
/// encoding posture change.
/// `Null`, silent data corruption). Failures here are two-fold: a
/// `Value` nested deeper than the depth limit (`PAYLOAD_DEPTH_LIMIT`)
/// is rejected — the stored bytes could never be read back, and the
/// serializer's own unguarded recursion would otherwise abort deep
/// enough — and genuine serialization failure maps through the same
/// typed arm.
pub fn encode_payload(value: &serde_json::Value) -> Result<Vec<u8>, Error> {
if value_depth(value) > PAYLOAD_DEPTH_LIMIT {
return Err(Error::Codec(format!(
"payload nesting exceeds the depth limit ({PAYLOAD_DEPTH_LIMIT})"
)));
}
serde_json::to_vec(value).map_err(|e| Error::Codec(e.to_string()))
}
/// The payload nesting cap: aligned with serde_json's deserialization
/// recursion limit (a nested-open at container depth 128 fails to
/// decode; depths through 127 read back). A `Value` nested deeper than
/// the cap produces stored bytes no decoder can read — silent data
/// loss at read time — and deep enough the (unguarded) serializer
/// itself would overflow the stack abortably; the encode side refuses
/// it instead.
const PAYLOAD_DEPTH_LIMIT: usize = 127;
/// The maximum number of containers on any path through `value`
/// (iterative, not recursive — a recursive walk would itself overflow
/// the stack on exactly the deep inputs this guards against).
fn value_depth(value: &serde_json::Value) -> usize {
let mut max = 0;
let mut stack: Vec<(&serde_json::Value, usize)> = vec![(value, 1)];
while let Some((current, depth)) = stack.pop() {
match current {
serde_json::Value::Array(items) => {
if depth > max {
max = depth;
}
for item in items {
stack.push((item, depth + 1));
}
}
serde_json::Value::Object(map) => {
if depth > max {
max = depth;
}
for item in map.values() {
stack.push((item, depth + 1));
}
}
_ => {}
}
}
max
}
/// Decode stored payload bytes into a concrete target type.
///
/// Used by [`Job::payload_as`](crate::Job::payload_as) and
+8 -6
View File
@@ -26,17 +26,19 @@ pub enum NameKind {
/// rejected.
Shared,
/// Consumer-local identifiers — stream-consumer names, `owner` on
/// `try_lock`, `worker_id` on claims. Non-empty only: a leading
/// `__alkstore_` is legal (they are not a shared engine namespace,
/// so there is nothing to collide with).
/// `try_lock`, `worker_id` on claims. Non-empty (whitespace-only
/// rejected like any empty): a leading `__alkstore_` is legal
/// (they are not a shared engine namespace, so there is nothing to
/// collide with).
Local,
}
/// Validate a name per its [`NameKind`].
///
/// - non-empty (after no trimming — whitespace-only counts as
/// non-empty content but is nonetheless rejected below) →
/// [`Error::InvalidName`](crate::Error::InvalidName) when empty
/// - non-empty → [`Error::InvalidName`](crate::Error::InvalidName)
/// when empty; the emptiness check is the raw string's (`""`
/// rejects) with whitespace-only strings rejected by the same arm
/// (they are never a meaningful name on any engine — see below)
/// - `NameKind::Shared` additionally rejects names carrying
/// [`RESERVED_PREFIX`] with
/// [`Error::ReservedName`](crate::Error::ReservedName)
+45 -4
View File
@@ -87,7 +87,10 @@ fn sample_event(payload: Vec<u8>) -> StreamEvent {
fn payload_as_round_trips_json_value_through_job() {
let v = json!({"id": 9, "nested": {"ok": [true, null, 1.5]}, "s": "ünï"});
let bytes = crate::encode_payload(&v).unwrap_or_else(|e| panic!("encode failed: {e}"));
assert_eq!(bytes, serde_json::to_vec(&v).unwrap_or_default());
assert_eq!(
bytes,
serde_json::to_vec(&v).unwrap_or_else(|e| panic!("reference encode failed: {e}"))
);
let job = sample_job(bytes);
let decoded: Value = job
.payload_as()
@@ -128,16 +131,54 @@ fn payload_as_undecodable_target_yields_codec() {
Err(crate::Error::Codec(msg)) => assert!(!msg.is_empty()),
other => panic!("expected Codec, got {other:?}"),
}
let r2: crate::Result<bool> =
serde_json::from_slice(b"not json").map_err(|e| crate::Error::Codec(e.to_string()));
let r2: crate::Result<bool> = {
let undecodable = sample_job(b"not json".to_vec());
undecodable.payload_as()
};
assert!(matches!(r2, Err(crate::Error::Codec(_))));
}
#[test]
fn encode_payload_rejects_nesting_below_the_decode_limit_as_typed_codec() {
let boundary = nest_value(Value::Null, 127);
assert!(
crate::encode_payload(&boundary).is_ok(),
"the deepest decodable nesting encodes and reads back"
);
let past = nest_value(Value::Null, 128);
match crate::encode_payload(&past) {
Err(crate::Error::Codec(msg)) => assert!(!msg.is_empty()),
other => panic!("expected Codec on past-limit nesting, got {other:?}"),
}
}
fn nest_value(inner: Value, depth: usize) -> Value {
let mut v = inner;
for _ in 0..depth {
v = Value::Array(vec![v]);
}
v
}
#[test]
fn encode_payload_boundary_nesting_round_trips() {
let v = nest_value(Value::from(0u8), 127);
let bytes = crate::encode_payload(&v).unwrap_or_else(|e| panic!("encode failed: {e}"));
let job = sample_job(bytes);
let back: Value = job
.payload_as()
.unwrap_or_else(|e| panic!("boundary decode failed: {e}"));
assert_eq!(back, v);
}
#[test]
fn encode_payload_success_carries_exact_serialization() {
let v = json!({"k": [1, 2, 3]});
let bytes = crate::encode_payload(&v).unwrap_or_else(|e| panic!("encode failed: {e}"));
assert_eq!(bytes, serde_json::to_vec(&v).unwrap_or_default());
assert_eq!(
bytes,
serde_json::to_vec(&v).unwrap_or_else(|e| panic!("reference encode failed: {e}"))
);
}
#[test]