diff --git a/AGENTS.md b/AGENTS.md index 34f81b8..33b61f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,6 +93,17 @@ cargo clippy --all-targets -- -D warnings cargo fmt --check ``` +Doc gates (task `release-crate-docs`, 2026-10-11): + +``` +RUSTDOCFLAGS="-D warnings" cargo doc --no-deps # zero rustdoc warnings (broken/redundant/private-intra-doc links) +``` + +The four published crates (`alkstore`, `alkstore-sqlite`, +`alkstore-postgres`, `alkstore-mem`) carry `#![warn(missing_docs)]` in +their roots — enforced through the clippy `-D warnings` gate above +(rustc lints promote to errors there). + Fuzzing is adopted (2026-10-10 — wave 7; `docs/research/fuzzing.md`), modeled on the alkcall/alktunnels/alksocks/alktty pattern: the `fuzz/` sidecar workspace pins nightly inside `fuzz/` only, the stable diff --git a/alkstore-contract-suite/src/properties.rs b/alkstore-contract-suite/src/properties.rs index 8eba8c9..2036d2d 100644 --- a/alkstore-contract-suite/src/properties.rs +++ b/alkstore-contract-suite/src/properties.rs @@ -87,7 +87,7 @@ fn expect_ok(label: &str, out: alkstore::Result) { /// **Exemplar row** — name validation at every entry point, /// auto-commit and tx paths alike. /// -/// Every name-bearing entry point on the [`Store`](alkstore::Store) +/// Every name-bearing entry point on the [`Store`] /// surface — and every name-bearing `*_tx` twin on the /// [`TxHandle`](alkstore::TxHandle) — rejects, before any engine round /// trip: the empty (or whitespace-only) name with @@ -1143,7 +1143,7 @@ async fn await_claimable( /// equivalence pin (ADR-012 §2: engine-side arithmetic, suite-pinned /// identical outputs). The row drives claim → `retry(err, None)` /// cycles on a queue with `backoff_base_s = 2` (integer-second -/// resolution: the ranges [1,2], [2,4], [4,8] land across the first +/// resolution: the ranges \[1,2\], \[2,4\], \[4,8\] land across the first /// three attempts), reading each computed delay back through /// `get_job`'s resolved `run_at` minus a clock read taken before the /// retry, asserting range membership per attempt — with a diff --git a/alkstore-mem/src/lib.rs b/alkstore-mem/src/lib.rs index 93d5ab9..1c16d91 100644 --- a/alkstore-mem/src/lib.rs +++ b/alkstore-mem/src/lib.rs @@ -29,13 +29,46 @@ //! # Architecture //! //! The store instance *is* the state: guarded per-mechanism sections -//! (`crate::state`) behind short-held synchronous internal locks, +//! behind short-held synchronous internal locks, //! tokio primitives for cross-await coordination. The tx seam is an -//! overlay ([`crate::tx`] — stage in the handle-private overlay, commit +//! overlay (`tx` — stage in the handle-private overlay, commit //! merges atomically + wakes at commit, drop discards); wakes route -//! through in-process channels (`crate::wakes`) — delivered at the +//! through in-process channels (`wakes`) — delivered at the //! commit boundary directly to each open receiver, per-subscriber FIFO //! by commit order, never coalesced (no batching boundary exists). +//! +//! # Example +//! +//! The honest-ephemeral engine is the zero-config one, so this crate +//! carries the contract round-trip example: open with one call, +//! enqueue a job, claim it back (the other engines construct through +//! their `open` form with a connection target and opts — the surface +//! round-trip below is identical there). +//! +//! ``` +//! use alkstore::{EnqueueOpts, JobHandle, QueueOpts, Store}; +//! use alkstore_mem::MemStore; +//! +//! # fn main() -> Result<(), Box> { +//! let rt = tokio::runtime::Builder::new_current_thread().build()?; +//! let store = MemStore::new(); +//! let queue = rt.block_on(store.queue("demo", QueueOpts::default()))?; +//! let job_id = rt.block_on(queue.enqueue( +//! serde_json::json!("hello"), +//! EnqueueOpts::default(), +//! ))?; +//! let claimed = rt.block_on(queue.claim_one("worker-1"))? +//! .expect("the enqueued job is claimable"); +//! assert_eq!(claimed.job().id, job_id); +//! assert_eq!(claimed.job().state, alkstore::JobState::Processing); +//! # Ok(()) +//! # } +//! ``` +//! +//! Every public item carries doc text; the missing-docs lint is part +//! of the standing gates. + +#![warn(missing_docs)] mod clock; mod lock; diff --git a/alkstore-postgres/src/lib.rs b/alkstore-postgres/src/lib.rs index 4048b89..576e8f3 100644 --- a/alkstore-postgres/src/lib.rs +++ b/alkstore-postgres/src/lib.rs @@ -34,6 +34,11 @@ //! module's bootstrap the DDL ground. The verification backlog's pg //! column (the ADR-022 contract suite rows this engine owns) runs in //! this crate's `contract_suite` test target. +//! +//! Every public item carries doc text; the missing-docs lint is part +//! of the standing gates. + +#![warn(missing_docs)] mod forwarder; mod lock; diff --git a/alkstore-postgres/src/opts.rs b/alkstore-postgres/src/opts.rs index 194e4a3..9ddee54 100644 --- a/alkstore-postgres/src/opts.rs +++ b/alkstore-postgres/src/opts.rs @@ -35,7 +35,8 @@ pub(crate) const LISTENER_APPLICATION_NAME: &str = "alkstore-pg-listener"; /// are `open`'s first argument (the tokio-postgres /// [`Config`](::tokio_postgres::Config)-parseable form, ADR-008 §6's /// `open(url, PgOpts)` pin); the engine never invents defaults for -/// them. `open` fails with [`Error::Database`] if the config is +/// them. `open` fails with +/// [`Error::Database`](alkstore::Error::Database) if the config is /// unparseable or the server unreachable. TLS: the engine hardwires /// `NoTls` on every connection path regardless of the config's /// sslmode — v1 TLS is a post-v1 deployment concern @@ -54,7 +55,8 @@ pub struct PgOpts { /// Default [`DEFAULT_MAX_SIZE`] = 8 (the deployment matrix's /// budget line; the listener adds +1 per LISTEN-ing process /// *outside* the pool — sizing rule `max_size + 1`). A value of - /// `0` fails [`open`](crate::open) with [`Error::Database`] + /// `0` fails [`open`](crate::open) with + /// [`Error::Database`](alkstore::Error::Database) /// immediately (validated at entry — no round trip; deadpool /// cannot grant checkouts from an empty pool). pub max_size: usize, diff --git a/alkstore-postgres/src/tx.rs b/alkstore-postgres/src/tx.rs index 343cd92..cce854e 100644 --- a/alkstore-postgres/src/tx.rs +++ b/alkstore-postgres/src/tx.rs @@ -1,6 +1,6 @@ //! The transactional seam's pg arm (ADR-007) — `begin_tx`, the -//! [`PgTxHandle`] (the pooled-object handle), and all eleven `*_tx` -//! methods (`pg-engine-seam-tx`). +//! [`PgTxHandle`] (the pooled-object handle), and all eleven +//! transaction-handle methods (ten `*_tx` ops plus `commit`). (`pg-engine-seam-tx`). //! //! # No bridge, no `spawn_blocking` //! diff --git a/alkstore-sqlite/src/lib.rs b/alkstore-sqlite/src/lib.rs index 2d26cd4..4c40d83 100644 --- a/alkstore-sqlite/src/lib.rs +++ b/alkstore-sqlite/src/lib.rs @@ -5,8 +5,10 @@ //! # Posture //! //! **Single-host** (ADR-016): this engine is file-backed, one machine, -//! no runtime capability surface, no multi-host mode — NFS -//! two-writers-unsupported is the inherited lineage honesty, stated +//! no runtime capability surface, no multi-host mode — cross-process +//! *on one host* is verified ground (`data_version` is cross-process +//! by nature; deployment.md's matrix line), NFS two-writers +//! unsupported is the inherited lineage honesty, stated //! here as the crate's deployment boundary (compile-time engine //! identity is the only engine-discovery mechanism — pick this crate, //! get this posture). Durability knobs, pool sizing, and the watcher @@ -20,8 +22,14 @@ //! points holds it; consumers budget transactions accordingly. //! //! Every engine call round-trips a blocking thread (the -//! `spawn_blocking` seam — rusqlite connections are not -//! `Send`-across-await; the family REQ-TTY-01 posture). +//! `spawn_blocking` seam — the substrate is sync machinery, std +//! threads and blocking iterators, bridged at the trait seam per the +//! family REQ-TTY-01 posture). +//! +//! Every public item carries doc text; the missing-docs lint is part +//! of the standing gates. + +#![warn(missing_docs)] mod lock; mod notify; diff --git a/alkstore-sqlite/src/store.rs b/alkstore-sqlite/src/store.rs index 277d08b..2a9e732 100644 --- a/alkstore-sqlite/src/store.rs +++ b/alkstore-sqlite/src/store.rs @@ -26,9 +26,12 @@ //! [`SqliteStore::close`]. //! //! Every engine call round-trips the `spawn_blocking` seam -//! ([`crate::seam`]); rusqlite connections are not -//! `Send`-across-await. Single-host by nature: file-backed, one -//! machine; NFS two-writers-unsupported (the lineage's honesty +//! ([`crate::seam`]) — the substrate is sync machinery (std threads, +//! blocking iterators) bridged at the trait seam, per the family +//! REQ-TTY-01 posture. Single-host by nature: file-backed, one +//! machine; cross-process on one host is verified ground +//! (`data_version` is cross-process by nature); NFS +//! two-writers-unsupported (the lineage's honesty //! posture, inherited — ADR-016). use alkstore::Store; diff --git a/alkstore-sqlite/src/substrate/queue_ops.rs b/alkstore-sqlite/src/substrate/queue_ops.rs index 3768199..cc60281 100644 --- a/alkstore-sqlite/src/substrate/queue_ops.rs +++ b/alkstore-sqlite/src/substrate/queue_ops.rs @@ -487,6 +487,8 @@ fn now_unix(conn: &Connection) -> rusqlite::Result { conn.query_row("SELECT unixepoch()", [], |r| r.get(0)) } +/// The substrate's `@every` interval parser (`@every `, +/// `` in `s|m|h|d`) — fuzz/test-only surface. pub fn parse_every_interval(spec: &str) -> Result { let body = spec .strip_prefix("@every ") @@ -524,6 +526,8 @@ fn next_every_boundary(spec: &str, after: i64) -> Result { .ok_or_else(|| format!("next boundary overflows for {spec:?}")) } +/// The substrate's schedule skip-forward arithmetic — fuzz/test-only +/// surface. pub fn skip_forward(next_fire_at: i64, now: i64, interval: i64) -> i64 { let gap = now as i128 - next_fire_at as i128; let interval = interval.max(1) as i128; diff --git a/alkstore-sqlite/src/tx.rs b/alkstore-sqlite/src/tx.rs index da128dc..3d6d5a1 100644 --- a/alkstore-sqlite/src/tx.rs +++ b/alkstore-sqlite/src/tx.rs @@ -1,6 +1,6 @@ //! The transactional seam's SQLite arm — `begin_tx`, the //! [`SqliteTxHandle`] (the writer-slot lease, ADR-007), and all eleven -//! `*_tx` methods (`sqlite-engine-seam-tx`). +//! transaction-handle methods (ten `*_tx` ops plus `commit`) (`sqlite-engine-seam-tx`). //! //! `begin_tx` acquires the writer slot and opens `BEGIN IMMEDIATE` on //! the acquired connection; the handle owns connection + slot. Every diff --git a/alkstore/src/error.rs b/alkstore/src/error.rs index 7f36017..e9cd2eb 100644 --- a/alkstore/src/error.rs +++ b/alkstore/src/error.rs @@ -1,3 +1,7 @@ +/// The crate's error taxonomy (ADR-008 §5): a small closed set of +/// contract-meaningful variants plus the opaque [`Error::Database`] +/// fallback. Nothing engine-specific ever rides the surface; driver +/// detail lives in the source chain of the opaque variant. #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { @@ -11,16 +15,25 @@ pub enum Error { /// outbox shape — the id in the notification, the payload in a /// table row. #[error("payload exceeds the engine's notification payload limit (limit: {limit} bytes)")] - PayloadTooLarge { limit: usize }, + PayloadTooLarge { + /// The engine's byte limit that was exceeded. + limit: usize, + }, /// A name carrying the reserved `__alkstore_` prefix was supplied /// to a shared-namespace entry point. #[error( "reserved name `{name}`: names carrying the reserved `__alkstore_` prefix are engine-owned" )] - ReservedName { name: String }, + ReservedName { + /// The rejected name, as supplied. + name: String, + }, /// A name failed entry-point validation. #[error("invalid name `{name}`")] - InvalidName { name: String }, + InvalidName { + /// The rejected name, as supplied. + name: String, + }, /// A schedule spec that doesn't parse under the v1 grammar /// (`@every ` with `` a positive integer and `` /// in `s|m|h|d`). @@ -28,7 +41,10 @@ pub enum Error { /// Rejected by `schedule()` before storage, with no engine round /// trip; the caller fixes the spec string (ADR-009 §2, §6). #[error("invalid schedule spec `{spec}`")] - InvalidSpec { spec: String }, + InvalidSpec { + /// The spec string that failed the grammar. + spec: String, + }, /// The `run_schedules` runner's leadership lock was lost mid-run. /// /// A distinct, matchable outcome from the clean `Ok(())` a stop diff --git a/alkstore/src/lib.rs b/alkstore/src/lib.rs index fa2fb5f..093c689 100644 --- a/alkstore/src/lib.rs +++ b/alkstore/src/lib.rs @@ -4,6 +4,12 @@ //! This crate is the contract artifact: no driver dependencies, ever. //! Engine crates (`alkstore-sqlite`, `alkstore-postgres`) depend on //! this crate and implement its traits. +//! +//! This crate's docs are part of the contract artifact (ADR-001 §1) — +//! every public item carries doc text, and the missing-docs lint is +//! part of the standing gates. + +#![warn(missing_docs)] mod error; mod event_receiver; diff --git a/alkstore/src/lock.rs b/alkstore/src/lock.rs index 8f454b1..1082053 100644 --- a/alkstore/src/lock.rs +++ b/alkstore/src/lock.rs @@ -13,7 +13,9 @@ pub trait Lock: Send { /// namespace — validated at the constructor). fn name(&self) -> &str; - /// Renew: a new **full** TTL window from now. `false` = lost it + /// Renew: a new **full** TTL window from now. `ttl` is a duration + /// argument (ADR-023 §2): `ttl <= 0` is a rejection (opaque `Err`, + /// never a silent immediately-expired lease). `false` = lost it /// (expired and re-acquired elsewhere — exclusivity lapses /// silently on expiry; no revocation event; ADR-008 §7). Holders /// of long work must renew within TTL. diff --git a/alkstore/src/opts.rs b/alkstore/src/opts.rs index fed23be..56975d6 100644 --- a/alkstore/src/opts.rs +++ b/alkstore/src/opts.rs @@ -51,9 +51,11 @@ pub struct EnqueueOpts { /// Opts changes apply to *future enqueues only*. /// /// The numeric fields are trusted as given, engine-side — no domain -/// validation (ADR-023 §2's domain table covers the trait-surface -/// arguments, not these consumer-constructed constants; a -/// consumer-obligation note, not a defect). A non-positive +/// validation: ADR-023 §2's domain table enumerates its covered +/// arguments by call-site kind (extents, `try_lock`/`renew` TTLs, +/// `trim_to`'s horizon, enqueue `delay`/`run_at`/`expires`) and does +/// not cover these queue-stamp fields (a consumer-obligation note, +/// not a defect). A non-positive /// `visibility_timeout_s` stamps claims instantly reclaimable, and /// `max_attempts <= 0` stamps rows that are never claimed — see /// deployment.md's consumer-obligation notes for the per-field diff --git a/alkstore/src/queue.rs b/alkstore/src/queue.rs index d83a0c1..74ae675 100644 --- a/alkstore/src/queue.rs +++ b/alkstore/src/queue.rs @@ -41,7 +41,9 @@ pub trait Queue: Send + Sync { /// Claim up to `n` rows, same ordering and exclusivity as /// [`Queue::claim_one`]. `worker_id` is consumer-local, non-empty - /// only (`InvalidName`). + /// only (`InvalidName`). `n` is an extent argument (ADR-023 §2): + /// `n <= 0` claims nothing — the empty `Vec`, a value (never an + /// error, never the whole queue). fn claim_batch<'a>( &'a self, worker_id: &str, diff --git a/alkstore/src/store.rs b/alkstore/src/store.rs index 8f9d915..8bfaf64 100644 --- a/alkstore/src/store.rs +++ b/alkstore/src/store.rs @@ -4,7 +4,25 @@ //! Constructors and their option structs live in the engine crates //! (ADR-008 §6): the engine's `open` returns this trait, and a store //! handed out by an engine crate honors the full v1 surface. Consumer -//! code never branches on engine type. +//! code never branches on engine type. (The mem engine's engine-native +//! constructor — `MemStore::new()`, no connection string — is the one +//! documented exception to the open-prose, ADR-024 §4; the engine +//! choice remains a dependency-graph fact made before any store +//! exists.) +//! +//! # Numeric-argument domains (ADR-023 §2) +//! +//! Caller-supplied numeric arguments are classified by argument kind, +//! uniform on every engine (validation at the trait-impl entry point): +//! **extents** (`claim_batch`, the stream `read` forms, on the direct +//! and tx paths alike) clamp to the empty value result on non-positive +//! values — a value, not an error; **durations** (`try_lock`/ +//! `ttl`, `renew`/`ttl`) reject non-positive values (`Err`) — never a +//! silent immediately-expired lease; **boundaries/offsets** +//! (`trim_to`'s horizon; enqueue `delay`/`run_at`/`expires`) are +//! total — any `i64` is well-defined. Queue/schedule opts-stamp fields +//! are not in the domain table (trusted as given; deployment.md's +//! consumer-obligation notes carry the per-field symptoms). use serde_json::Value; @@ -70,7 +88,9 @@ pub trait Store: Send + Sync { /// Acquire a named TTL lock, or fail: `None` = someone holds it /// (no-work is a value, not an error). `owner` is consumer-local - /// (non-empty only); `ttl` is seconds. Exclusion lapses silently + /// (non-empty only); `ttl` is seconds — a duration argument + /// (ADR-023 §2): `ttl <= 0` is a rejection (opaque `Err`, never a + /// silent immediately-expired lease). Exclusion lapses silently /// at TTL expiry — holders renew within TTL (ADR-008 §7). fn try_lock<'a>( &'a self, diff --git a/alkstore/src/stream_handle.rs b/alkstore/src/stream_handle.rs index e13538f..00e4708 100644 --- a/alkstore/src/stream_handle.rs +++ b/alkstore/src/stream_handle.rs @@ -35,7 +35,9 @@ pub trait StreamHandle: Send + Sync { ) -> BoxedFuture<'a, Result>; /// Cursor read: events with `offset > ` cursor, `offset ASC` - /// (global FIFO — ADR-015 §4), up to `limit`. A read from a + /// (global FIFO — ADR-015 §4), up to `limit`. `limit` is an + /// extent argument (ADR-023 §2): `limit <= 0` reads nothing — + /// the empty `Vec`. A read from a /// trimmed-away region resumes at the trim horizon's first /// remaining row. fn read_since<'a>( @@ -46,7 +48,9 @@ pub trait StreamHandle: Send + Sync { /// Cursor read from a consumer's stored checkpoint (absent /// consumer = 0). `consumer` is a consumer-local identifier - /// (non-empty only — `InvalidName`). + /// (non-empty only — `InvalidName`). `limit` is an extent + /// argument (ADR-023 §2): `limit <= 0` reads nothing — the + /// empty `Vec`. fn read_from_consumer<'a>( &'a self, consumer: &str, @@ -66,9 +70,14 @@ pub trait StreamHandle: Send + Sync { fn get_offset<'a>(&'a self, consumer: &str) -> BoxedFuture<'a, Result>; /// Bounded growth, consumer-invoked: delete events with - /// `offset <= horizon` (exact boundary; ADR-015 §5). Surviving + /// `offset <= horizon` (exact boundary; ADR-015 §5). The horizon + /// is a boundary argument (ADR-023 §2) — total: a negative + /// horizon deletes nothing, idempotently. Surviving /// events keep their offsets (gaps legal, never renumbered); trim - /// emits no wake. Returns the deleted-row count. + /// emits no dedicated wake and no notify (on SQLite the + /// `data_version` watcher may still fire on the trim commit — any + /// committed write bumps it; a spurious hint, contract-legal). + /// Returns the deleted-row count. fn trim_to<'a>(&'a self, horizon: i64) -> BoxedFuture<'a, Result>; /// Durable consumption: attach a consumer that reads to the diff --git a/alkstore/src/tx.rs b/alkstore/src/tx.rs index 9bf198d..3add0a4 100644 --- a/alkstore/src/tx.rs +++ b/alkstore/src/tx.rs @@ -107,7 +107,8 @@ pub trait TxHandle: Send { /// Read stream events from `offset`, inside the caller's /// transaction (`offset ASC`, global FIFO — ADR-015 §4). Pure - /// read. Validates `stream`. + /// read. Validates `stream`. `limit` is an extent argument + /// (ADR-023 §2): `limit <= 0` reads nothing — the empty `Vec`. fn read_since_tx<'a>( &'a mut self, stream: &str, @@ -117,7 +118,9 @@ pub trait TxHandle: Send { /// Read stream events from a consumer's stored checkpoint, inside /// the caller's transaction. Pure read. Validates `stream`; - /// `consumer` is consumer-local (non-empty only). + /// `consumer` is consumer-local (non-empty only). `limit` is an + /// extent argument (ADR-023 §2): `limit <= 0` reads nothing — the + /// empty `Vec`. fn read_from_consumer_tx<'a>( &'a mut self, stream: &str, diff --git a/alkstore/src/validation.rs b/alkstore/src/validation.rs index 971b8ed..e9abc1d 100644 --- a/alkstore/src/validation.rs +++ b/alkstore/src/validation.rs @@ -36,11 +36,13 @@ pub enum NameKind { /// /// - non-empty (after no trimming — whitespace-only counts as /// non-empty content but is nonetheless rejected below) → -/// [`Error::InvalidName`] when empty +/// [`Error::InvalidName`](crate::Error::InvalidName) when empty /// - `NameKind::Shared` additionally rejects names carrying -/// [`RESERVED_PREFIX`] with [`Error::ReservedName`] +/// [`RESERVED_PREFIX`] with +/// [`Error::ReservedName`](crate::Error::ReservedName) /// -/// Whitespace-only names are rejected as [`Error::InvalidName`] for +/// Whitespace-only names are rejected as +/// [`Error::InvalidName`](crate::Error::InvalidName) for /// both kinds: they are never a meaningful name on any engine. pub fn validate_name(kind: NameKind, name: &str) -> crate::Result<()> { if name.is_empty() || name.trim().is_empty() { diff --git a/docs/architecture/engine-mem.md b/docs/architecture/engine-mem.md index b5852b0..b2731bc 100644 --- a/docs/architecture/engine-mem.md +++ b/docs/architecture/engine-mem.md @@ -58,7 +58,10 @@ daemons, the mem engine realizes with process-owned state: - **No background machinery to bootstrap** — no schema bootstrap, no watcher thread (nothing cross-connection to poll), no forwarder. The only async machinery is tokio-time-driven: lock TTLs, the - scheduler's boundary waits, and wake delivery. + scheduler's boundary waits, and wake delivery. All timestamps read + the process clock through one internal accessor — no server clock + to defer to, no per-mechanism clock reads (the wasm-posture note, + [ADR-024](decisions/024-mem-engine.md) §5). - **Wake substrate**: in-process channels. Wakes deliver at the commit boundary directly to each open receiver — no batching boundary exists, so coalescing is not applicable; per-subscriber diff --git a/docs/architecture/engine-postgres.md b/docs/architecture/engine-postgres.md index 7c2d815..4e1e81c 100644 --- a/docs/architecture/engine-postgres.md +++ b/docs/architecture/engine-postgres.md @@ -27,7 +27,8 @@ obligations live in the core spec. family as design reference ([ADR-005](decisions/005-dependency-ownership.md)); semantics depth pinned by [ADR-010](decisions/010-queue-semantics-depth.md) — all - engine-owned tables (job, dead, stream, offsets, schedule) in one + engine-owned tables (job, dead, stream, offsets, schedule, locks) + in one PostgreSQL schema (default `alkstore`), queues as rows, no per-queue tables. @@ -66,7 +67,7 @@ obligations live in the core spec. | notify / listen | `pg_notify(...)` inside the caller's tx (delivers at commit — native commit-atomicity, [ADR-007](decisions/007-transactional-seam.md)); `listen()` via LISTEN on the forwarder's connection, fanout to receivers | | streams | durable event table + per-consumer offset cursors; `pg_notify` as the wake trigger ([ADR-006](decisions/006-wake-and-delivery-contract.md) mechanism split: durable row, LISTEN wake — the pg-boss-family shape). Depth per [ADR-015](decisions/015-streams-depth.md): the event table carries the nullable key column (carried metadata), reads yield `offset ASC` (global FIFO — bigserial offsets, immutable), `publish_with_key_tx` validates + inserts inside the caller's tx, `trim_to` is a `DELETE … WHERE offset <= ?` on a pool connection (wakes nothing) | | queues | re-derived queue table + `FOR UPDATE SKIP LOCKED` claim + LISTEN-driven wake with re-poll safety net (default consumption posture, measured 5–16× vs 50 ms poll; poll-only fallback); states/dead-letter/backoff/visibility per [ADR-010](decisions/010-queue-semantics-depth.md), all in the engine-owned schema; the curve/stamps arithmetic is computed engine-side per [ADR-012](decisions/012-forked-substrate-design.md) §2 (equivalence with the SQLite engine pinned by the contract suite) | -| named locks | advisory-lock-semantics TTL locks (pg-boss-family design reference; guarantee row pinned by [ADR-008](decisions/008-contract-v1-pinning.md) §7) | +| named locks | row-backed TTL locks (a `locks` table in the engine-owned schema — ADR-008 §7 pins row-backed machinery, not pg advisory locks; the pg-boss family is the design reference; guarantee row pinned by [ADR-008](decisions/008-contract-v1-pinning.md) §7) | | scheduler / outbox | collapse shape ([ADR-009](decisions/009-scheduler-collapse.md)): schedule rows in the engine-owned schema, tick re-derived (boundary advance + 64-boundary catch-up cap, honker parity), leadership via the engine's lock machinery on `__alkstore_scheduler`; outbox = helper over queues | | begin_tx | pool checkout + `BEGIN`, returning the caller-held handle ([ADR-007](decisions/007-transactional-seam.md)) | | handle ops | straight `.await`s through the held object; commit/rollback returns the object to the pool | diff --git a/tasks/release-crate-docs.md b/tasks/release-crate-docs.md index 61f4941..c614dd5 100644 --- a/tasks/release-crate-docs.md +++ b/tasks/release-crate-docs.md @@ -1,7 +1,7 @@ --- id: release-crate-docs name: Crate docs final pass — rustdoc across the four published crates -status: pending +status: completed depends_on: [] scope: moderate risk: low @@ -50,17 +50,17 @@ polish. ## Acceptance Criteria -- [ ] `cargo doc --no-deps` (workspace) zero warnings; the +- [x] `cargo doc --no-deps` (workspace) zero warnings; the `-D warnings` doc gate posture recorded -- [ ] Missing-docs audit recorded (public items × 4 crates); gaps +- [x] Missing-docs audit recorded (public items × 4 crates); gaps filled; the `#![deny(missing_docs)]` decision made and recorded -- [ ] Core doc text spot-checked against core-contract.md; engine +- [x] Core doc text spot-checked against core-contract.md; engine lib-doc posture claims spot-checked against deployment.md — drifts fixed or recorded -- [ ] The doc-hidden fuzz re-exports documented as fuzz/test-only at +- [x] The doc-hidden fuzz re-exports documented as fuzz/test-only at all three engine roots -- [ ] The example decision made (landed or recorded not-taken) -- [ ] Workspace gates green (`cargo build`, `cargo test`, clippy +- [x] The example decision made (landed or recorded not-taken) +- [x] Workspace gates green (`cargo build`, `cargo test`, clippy `-D warnings`, fmt) ## References @@ -77,8 +77,116 @@ polish. ## Notes -> To be filled by implementation agent +- **`#![deny(missing_docs)]` decision**: **`#![warn(missing_docs)]`** + adopted at the four published crate roots instead of deny — with + rationale: the standing merge gate (`cargo clippy --all-targets -- + -D warnings`) promotes rustc lints to errors anyway, so the lint is + *enforced* while plain `cargo build`/`cargo test` remain unblocked + mid-task (a deny attribute fails every build the moment a pub item + lacks text, punishing incremental work). Recorded as the standing + posture in AGENTS.md's Verification Commands section alongside the + doc-gate line. +- **Doc gate posture recorded**: `RUSTDOCFLAGS="-D warnings" cargo + doc --no-deps` — zero warnings, added to AGENTS.md's Verification + Commands block as the doc gate. +- **Missing-docs audit results** (full-surface probe via + `RUSTFLAGS='-Wmissing_docs' RUSTDOCFLAGS='-Wmissing_docs' cargo doc + --no-deps`, probe reports 0 after fixes): every public item across + the four crates carries doc text. Gaps found and filled: the + `Error` enum's own doc + its four struct-variant fields + (`alkstore/src/error.rs`); the sqlite substrate's two fuzz-surface + functions (`substrate/queue_ops.rs` — their source-side docs had + never been written; the pg/mem scheduler twins already had them). +- **Core doc fixes against core-contract.md** (drifts fixed): (1) + `StreamHandle::trim_to`'s flat "trim emits no wake" corrected to the + contract's exact row — no *dedicated wake and no notify*, with the + SQLite `data_version` spurious-hint nuance (a trim commit does wake + the watcher, contract-legal); (2) the ADR-023 §2 numeric-domain + rules were missing from every affected method's doc text — added the + extent rules (`claim_batch`/`read_since`/`read_from_consumer` and + their tx twins: `n/limit <= 0` → empty value), duration rules + (`try_lock`/`renew`: `ttl <= 0` rejects, opaque `Err`), the + boundary-totality rule for `trim_to`'s horizon, and the shared + domain-rule paragraph (store.rs module doc, per ADR-023 §2's own + amendment text); (3) the `QueueOpts` "trusted as given" gloss drew + the wrong criterion (opts-struct constants it names *are* + trait-surface arguments) — reworded to the domain table's real + boundary (enumeration by call-site kind); (4) store.rs's + constructor prose gained the ADR-024 §4 `MemStore::new()` exception + clause (amended into the contract 2026-10-10). +- **Engine doc fixes**: (1) "all eleven `*_tx` methods" was an + off-by-one — the trait carries ten `*_tx` ops plus `commit`; + reworded in both `alkstore-sqlite/src/tx.rs` and + `alkstore-postgres/src/tx.rs` module docs; (2) the SQLite + `spawn_blocking` rationale misstated the driver property (rusqlite + `Connection` *is* `Send`; the honest reason is that the substrate is + sync machinery — std threads, blocking iterators) — reworded in + `alkstore-sqlite/src/lib.rs` + `store.rs` per engine-sqlite.md; (3) + the SQLite lib doc gained the missing third element of ADR-016 §3's + mandated single-host prose — one-host cross-process *supported* + (`data_version` is cross-process by nature), matching deployment.md; + (4) cosmetic: a malformed doc-comment indent line on `PgOpts::max_size`. +- **Spec-side alignments** (code docs + ADR win, spec aligned to + match): `docs/architecture/engine-postgres.md` now lists `locks` in + the schema-family enumeration and rewords "advisory-lock-semantics + TTL locks" to the ADR-008 §7 row-backed realization; + `docs/architecture/engine-mem.md` gained the one-clock-accessor line + (no server clock to defer to) that the mem lib doc's wasm identity + and `clock.rs` already cited — the claim now has its spec home. +- **Recorded, not fixed — two boundary-wording drifts for + `release-contract-window-close`** (the task that owns the class-1 + window's last dispositions): (1) `EventReceiver::try_recv`'s error + arms (`Database` or `Closed`) are claimed in core doc text but not + yet pinned in contract text — that task's §2 already decides + `Err(Closed)` mirroring `WakeReceiver::try_recv` (mem-test-pinned), + so the doc text is a forward-claim of the decided disposition, left + as-is; (2) the notify payload boundary: ADR-004/core-contract say + "≤ 8000 bytes" loosely, but the engine (POC + test-pinned against a + live server) rejects a serialization of exactly 8000 bytes — the + server's NUL-inclusive budget — so acceptance is ≤ 7999; the pg + engine's docs state the truthful predicate; a normative-spec + wording amendment is the window task's call. +- **Example decision: landed** — the honest-ephemeral engine is the + zero-config one, so `alkstore-mem`'s crate docs now carry the + contract round-trip example (a compile-checked doctest: + `MemStore::new()` → `queue` → `enqueue` → `claim_one` → + `job().id`/`JobState` asserts), with a pointer that the other + engines' round-trip is identical via their `open` form. The mem + lib's architecture paragraph dropped its `crate::state`/`crate::wakes` + intra-doc links (private modules — the private-intra-doc-link + warning class) in favor of plain code spans. +- **Fuzz re-exports (AC 4)**: verified all six `#[doc(hidden)]` + re-exports at the three engine roots carry the four-part + fuzz/test-only disclosure, name the correct module of origin, and + match the re-exported function's semantics — no changes needed. + +- Status of record: 2026-10-11, gates green (`cargo build`, `cargo + test` incl. the new doctest, `cargo clippy --all-targets -- -D + warnings`, `cargo fmt --check`, `RUSTDOCFLAGS="-D warnings" cargo + doc --no-deps`, missing-docs probe 0). ## Summary -> To be filled on completion \ No newline at end of file +The release doc pass over the four published crates: `RUSTDOCFLAGS="-D +warnings" cargo doc --no-deps` is now the standing doc gate (recorded +in AGENTS.md) and the workspace builds docs with zero warnings (8 +baseline rustdoc-warning classes fixed: broken `Error::*` links, +redundant explicit links, bracket-escape escapes, and a +private-intra-doc link). The `#![warn(missing_docs)]` enforcement +posture landed on all four crate roots (clippy gate enforces); +audit-probe reports zero undocumented public items after filling the +`Error` enum/field docs and the sqlite substrate's fuzz-surface fn +docs. Accuracy spot-checks ran core docs against core-contract.md and +engine lib-doc posture claims against deployment.md/ADR-016/ADR-024: +eight drifts fixed (trim_to wake nuance, the full ADR-023 §2 +numeric-domain doc text + shared paragraph, the QueueOpts gloss, the +`MemStore::new()` constructor exception, the eleven-vs-ten tx-method +count, the SQLite `Send`-misstatement rationale, the missing +one-host-cross-process honesty element, a cosmetic indent), two +spec-side alignments made (pg locks table family + row-backed +wording; mem clock-accessor spec home), and two boundary-wording +dispositions recorded for `release-contract-window-close` +(`try_recv` arm pinning; the 8000-byte boundary's "≤" reading). The +example decision landed: alkstore-mem's crate docs carry the +zero-config queue round-trip doctest (green in the workspace test +run). All workspace gates re-verified green. \ No newline at end of file