Dispose the wave-6 class-1-window observations (release-contract-window-close)

The pre-release pass's last contract-text dispositions, each dated
in-place citing the 2026-10-11 close-out pass (ADR-017 §2's terminal
window):

- Publish→wake ordering tolerance: documented tolerance (decided with
  the user) — core-contract.md streams paragraph + delivery-obligations
  bullet, ADR-015 §4 companion annotation; the wake = offset-order
  ADR amendment declined (commit-ordered pg visibility; hint-shaped
  wake posture).
- ADR-023 §2: heartbeat(extend) and save_offset(offset) join the
  boundary/offset row (total — negatives well-defined), with the
  home rationale annotated; core-contract's domain paragraph
  mirrors; the backlog numeric-domain row registers the suite-duty
  follow-up.
- ADR-021 §5: EventReceiver::try_recv's closed shape pinned
  (Err(Closed), mirroring ADR-008 §3); core-contract subscribe arms
  sentence extended.
- get_job prose: core-contract queues section + queues.md's reads
  sentence now say the uniform id-global / get_job_tx-filtered split.
- Mem-arithmetic observation: no-action disposition recorded in the
  task Notes (byte-level transplants; equivalence suite-pinned
  three-way).

Suite stamps checked — no accumulation required (no cited § text
changed meaning). Docs-only; gates green: build, workspace tests,
clippy -D warnings, fmt, fuzz replay, rustdoc -D warnings,
taskgraph validate (79 tasks).
This commit is contained in:
glm-5.3-flash committed 2026-10-11 00:17:48 +00:00
1 parent 8e27f4b64b
commit a37c2887dc
6 files changed
+244 -31

No files matched your search

+80 -23
View File
@@ -1,8 +1,6 @@
---
status: draft
last_updated: "2026-10-10 (ADR-024 — the mem engine joins the family;
the Store concept prose admits the engine-native constructor
exception)"
last_updated: "2026-10-11 (the pre-release class-1 close-out pass — the publish→wake ordering tolerance recorded; ADR-023 §2's `heartbeat(extend)`/`save_offset` classifications; `EventReceiver::try_recv`'s closed shape; `get_job`'s id-global prose)"
---
# Core contract
@@ -138,9 +136,18 @@ the engine's storage layer):
chain); an exclusion mechanism must not silently hand back a lease
the caller believes it holds.
- **Boundaries/offsets** (`trim_to(horizon)`, enqueue `delay`/
`run_at`/`expires`) — total: any `i64` is well-defined (a negative
horizon deletes nothing; a negative delay resolves to a ready
time in the past, i.e. ready now).
`run_at`/`expires`, `heartbeat(extend)`, `save_offset(offset)`) —
total: any `i64` is well-defined (a negative horizon deletes
nothing; a negative delay resolves to a ready time in the past,
i.e. ready now; a negative `heartbeat(extend)` — the renewed claim
deadline is `now + extend` — shortens or voids the claim, and the
validity predicate then refuses the ops, never an error; a negative
offset save is stored, reads resume forward, and saves at-or-below
the checkpoint are the monotone no-ops)
*(the two 2026-10-11 classifications annotated in
[ADR-023](decisions/023-fourth-review-round.md) §2 by the
close-out pass; before that date the table covered only the first
three arguments)*.
```text
store.notify(channel, payload) handle.notify_tx(channel, payload)
@@ -259,12 +266,40 @@ business-tx shape). Depth pinned by
publish, informational, **not an ordering field** (offset is the
only one); `payload_as<T>` decodes (the error is `Codec`).
- **Ordering guarantee row** ([ADR-015](decisions/015-streams-depth.md)
§4, extending [ADR-006](decisions/006-wake-and-delivery-contract.md)
§2's streams row): `read_since`/`subscribe` yield `offset ASC` —
global FIFO per stream; committed-event visibility and
replay-on-attach as already pinned; offsets immutable. The
contract suite pins cross-engine equivalence against this row
(same publish sequence → same offset sequence → same read order).
§4, extending [ADR-006](decisions/006-wake-and-delivery-contract.md)
§2's streams row): `read_since`/`subscribe` yield `offset ASC` —
global FIFO per stream; committed-event visibility and
replay-on-attach as already pinned; offsets immutable. The
contract suite pins cross-engine equivalence against this row
(same publish sequence → same offset sequence → same read order).
- **Wake-fire ordering tolerance** *(documented tolerance, 2026-10-11
— the pre-release class-1 close-out pass,
[ADR-017](decisions/017-contract-versioning.md) §2; disposes the
wave-6 review gate's recorded observation)*: nothing in the
contract pins wake-fire order to offset order. Offset allocation
precedes insert+wake on a multi-threaded runtime (the mem engine;
its documented current-thread posture never exercises it) and the
offset is sequence-assigned pre-commit with the wake fired
post-commit (the Postgres engine — commit order can invert
sequence order), so two concurrent publishes can complete out of
offset-allocation order. A subscriber's wake-driven drain that
finishes between the two completions can checkpoint past the
later-completing, lower-offset event: **the event stays in the
log** — direct `read_since` always sees it, and a fresh
`subscribe(consumer)` whose saved checkpoint is below it consumes
it normally — but the live subscriber whose cursor passed it is
not re-delivered it by the later wake. This is a documented
consequence of the wake contract's best-effort posture (hints, not
delivery promises); the offset-ASC read guarantee above is
unaffected — reads always run over the committed log in offset
order regardless of wake timing. Consumers requiring per-event
completeness across concurrent same-stream publishers need the
defensive re-read recipe (a lagged checkpoint, or a direct
`read_since` sweep below it). Pinned as a tolerance, not an
enforced guarantee (the heavier wake = offset-order amendment was
weighed and declined with the user: enforcement against commit-
ordered visibility is a redesign, and the wake mechanism's posture
is hint-shaped by construction).
- `read_since` / `read_from_consumer(offset)` — cursor-based reads;
`get_offset(consumer)` — checkpoint inspection. A read from a
trimmed-away offset region (below) resumes at the trim horizon's
@@ -304,10 +339,14 @@ business-tx shape). Depth pinned by
consumption: attach, read to current tail, resume after restart
from the stored offset; explicit `save_offset` on the receiver
(shape in [ADR-008](decisions/008-contract-v1-pinning.md) §8).
**Error and close arms pinned**
([ADR-021](decisions/021-tx-reads-and-value-shape-fixes.md) §5):
`recv()`'s `Err` arm carries `Database` only (payloads cross raw;
`Codec` lives at `payload_as<T>`; `Closed` is the `None` arm);
**Error and close arms pinned**
([ADR-021](decisions/021-tx-reads-and-value-shape-fixes.md) §5):
`recv()`'s `Err` arm carries `Database` only (payloads cross raw;
`Codec` lives at `payload_as<T>`; `Closed` is the `None` arm);
the poll form's arms pin identically — `try_recv()`: `Ok(None)` =
idle (no event pending right now), `Err(Closed)` = the receiver
closed, `Database` otherwise (the closed shape annotated
2026-10-11, the close-out pass);
close is terminal and its *cause* is the documented engine
asymmetry — SQLite's receiver closes on watcher death (POC-pinned),
the pg receiver stays open across forwarder reconnects (reconnect
@@ -399,9 +438,17 @@ obligations:
([ADR-010](decisions/010-queue-semantics-depth.md) §2). Dead letters:
move-to-dead storage, `get_job` sees dead rows (with
`last_error`/`died_at`), retention via `dead_letter_retention_s`
(default forever), no redrive API
([ADR-010](decisions/010-queue-semantics-depth.md) §1–§4).
`QueueOpts` stamp onto the job row at enqueue (§3a) — queues are
(default forever), no redrive API
([ADR-010](decisions/010-queue-semantics-depth.md) §1–§4).
`get_job(job_id)` on the handle is an **id-global lookup** — the
handle carries the queue, but the lookup keys on the id alone and
resolves rows in any queue (uniform on all engines; annotated
2026-10-11, the close-out pass — the wave-6 review's recorded
prose ambiguity); the tx-side `get_job_tx(queue, job_id)` takes
the queue name and **is** queue-filtered
([ADR-019](decisions/019-mechanism-handle-surfaces.md) §1,
[ADR-021](decisions/021-tx-reads-and-value-shape-fixes.md) §1).
`QueueOpts` stamp onto the job row at enqueue (§3a) — queues are
names, not config owners. The remaining v1-skeleton ops (`ack_batch`
— the batch ack applies the uniform predicate per id, lapsed or
non-claimed ids silently not counted; `cancel` — unconditional
@@ -522,7 +569,12 @@ row is [ADR-009](decisions/009-scheduler-collapse.md) §4's. This spec
inherits them and adds the consumer obligations:
- wake idempotence (notify's best-effort, coalescing behavior);
- explicit offset saves (streams);
- explicit offset saves (streams); wake-fire ordering is a
documented tolerance, not a pinned guarantee — wake order need not
match offset order and a wake-driven subscriber can drain past an
event whose publish completed out of offset order (the streams
section's tolerance paragraph, 2026-10-11); recovery rides the
offset-ordered re-read the wake contract already obligates;
- visibility-timeout budgeting — heartbeat inside the deadline for
long work; the dual-execution window and reclaim-eats-attempt rules
are documented consumer obligations
@@ -734,9 +786,14 @@ before the engine specs are called `stable`:
clamp to empty (`claim_batch(n <= 0)` claims nothing; stream reads
with `limit <= 0` read nothing, never the whole queue/stream), so
the SQLite `LIMIT -1` dialect artifact is dead on both engines;
durations reject (`try_lock`/`renew` with `ttl <= 0` are opaque
`Err`s on both engines); boundaries are total (a negative
`trim_to` horizon deletes nothing, idempotently).
durations reject (`try_lock`/`renew` with `ttl <= 0` are opaque
`Err`s on both engines); boundaries are total (a negative
`trim_to` horizon deletes nothing, idempotently; the same
totality over non-positive values covers `heartbeat(extend)`'s
relative-deadline argument and `save_offset`'s offset — both
classified 2026-10-11 by the close-out pass, the ADR-023 §2
annotation; their suite rows are the follow-up that
classification implies).
- **`encode_payload` typed failure**
([ADR-023](decisions/023-fourth-review-round.md) §1) — the helper
returns `Result<Vec<u8>, Error::Codec>`; enqueue/publish store and
@@ -201,6 +201,19 @@ never compared.)* It is deliberately *not* stronger than both engines can
implement by a shared SQL shape (`ORDER BY offset ASC` is already
both engines' claim path) and not weaker than the mechanism's purpose
(replay demands monotone positions).
*(Annotation, 2026-10-11 — the pre-release class-1 close-out pass
([ADR-017](017-contract-versioning.md) §2), disposing the wave-6
review gate's recorded ordering observation with the user's
documented-tolerance call: this row pins *read* order — `offset ASC`
over the committed log — and pins nothing about **wake-fire order**;
wake-fire order need not match offset order, and a wake-driven
subscriber's drain can checkpoint past an event whose publish
completed out of offset order (the event stays in the log; a fresh
subscribe whose checkpoint is below it sees it). The tolerance's full
text and the consumer recipe live in
[core-contract.md](../../architecture/core-contract.md)'s streams
section — the delivery-guarantee obligations bullet carries it too.
The heavier wake = offset-order amendment was weighed and declined.)*
### 5. Retention: `trim_to` on the stream handle — a consumer-invoked bounded-growth op
@@ -258,6 +258,20 @@ The pinned shape's unpinned arms:
absent-consumer = 0 rule,
[ADR-019](019-mechanism-handle-surfaces.md) §1, already defines
the pre-save state).
- *(Annotation, 2026-10-11 — the pre-release class-1 close-out pass
([ADR-017](017-contract-versioning.md) §2), disposing the wave-6
review gate's recorded unpinned shape: **`EventReceiver::try_recv`'s
closed shape is `Err(Closed)`.** §5 pinned only `recv()`'s arms;
the poll form's arms are the same taxonomy applied without the
wait — `Ok(None)` = no event pending right now (idle), `Err(Closed)`
= the receiver closed (terminal — the same causes the `None` arm
documents, engine asymmetry included), and the rest of the `Err`
arm carries `Database` only, exactly like `recv()`. The shape
mirrors `WakeReceiver::try_recv`
([ADR-008](008-contract-v1-pinning.md) §3, where closed and idle are
distinguishable states by the same construction) and is uniform on
all three engines; core's rustdoc pinned it from landing, and the
mem engine's suite pins it in test.)*
## Consequences
@@ -117,7 +117,7 @@ classified by argument **kind**, not call site:
|---|---|---|
| **Extent** ("at most this many") | `claim_batch(n)`, `read_since(limit)`, `read_from_consumer(limit)` | `n <= 0` → **empty value result** (claims nothing / reads nothing), not an error |
| **Duration** (seconds of lease/TTL semantics) | `try_lock(ttl)`, `renew(ttl)` | `ttl <= 0` → **`Err`, opaque** (engine detail in the source chain, `Database`) — the substrate's existing guard (`ttl_s <= 0`), now pinned contract-side |
| **Boundary / offset** (`<=` predicates, absolute or relative instants) | `trim_to(horizon)`, enqueue `delay`/`run_at`/`expires` | total — any `i64` is well-defined (negative horizon deletes nothing; negative delay → ready in the past → ready now); no clamping, no rejection |
| **Boundary / offset** (`<=` predicates, absolute or relative instants) | `trim_to(horizon)`, enqueue `delay`/`run_at`/`expires`; `heartbeat(extend)`, `save_offset(offset)` *(annotated 2026-10-11)* | total — any `i64` is well-defined (negative horizon deletes nothing; negative delay → ready in the past → ready now; negative extension → the renewed deadline `now + extend` is in the past, voiding the claim through the validity predicate, never an error; negative offset → stored, reads resume forward); no clamping, no rejection |
The rule the table pins: **extents clamp to empty (no-work is a
value, the pinned claims vocabulary), durations error, boundaries and
@@ -149,6 +149,28 @@ offsets are total.** Each disposition answers to a decided principle:
- **Boundaries/offsets.** The remaining numerics are total by
accident of their SQL today; the table makes it rule, not accident
— an implementer reading the table has nothing to improvise.
*(Annotation, 2026-10-11 — the pre-release class-1 close-out pass
([ADR-017](017-contract-versioning.md) §2), disposing the wave-6
review gate's recorded under-classifications. Two arguments join
the boundary/offset row, and deliberately not the duration row:
`heartbeat(extend)`'s seconds-shaped argument is not lease/TTL —
`extend` is the new full deadline in seconds from now, so a
non-positive value is not a handed-back lease the caller believes
it holds (the duration rule's rejection rationale never applies);
it is a relative instant like `delay`, and the three engines'
uniform arithmetic (`saturating_add` / plain SQL integer add —
code of record, mem `queue.rs`, substrate `queue_ops.rs`, pg
`queue.rs`) is total over non-positive values: a negative extension
shortens the deadline, a past one is voided by the uniform
validity predicate on the *next* op, and the heartbeat itself
reports the successful renewal (`true`) exactly when the row was
processing and unexpired at call time. And `save_offset`'s offset
is literally an offset: a negative is stored as the checkpoint,
reads resume forward past it, and saves at-or-below the stored
checkpoint are the [ADR-019](019-mechanism-handle-surfaces.md) §6
monotone no-ops — rejections were never the behavior, so pinning
the duration row's shape here would have contradicted all three
engines.)*
This is a **core-contract amendment** (doc text on `claim_batch`,
both `read` forms, `try_lock`/`renew`, and a shared domain-rule
+8 -4
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: "2026-10-07 (ADR-021 + third-round follow-through: handle-form sweep_expired, ack_batch predicate, opts types)"
last_updated: "2026-10-11 (close-out pass: the reads sentence now says the uniform shapes — get_job id-global on the handle, get_job_tx queue-filtered)"
---
# Queues, scheduler, outbox — semantics depth
@@ -44,9 +44,13 @@ made under; the ADRs carry the WHY.
[ADR-020](decisions/020-enqueue-opt-semantics-and-bridges.md) §1/§2);
`ack / retry / fail / heartbeat` on the boxed `JobHandle` a claim
returns ([ADR-019](decisions/019-mechanism-handle-surfaces.md));
reads (`get_job`, and tx-side `get_job_tx` —
[ADR-021](decisions/021-tx-reads-and-value-shape-fixes.md) §1) are
name-taking pure reads, tx-shaped on the handle.
reads are pure reads, in two shapes — `get_job` on the queue handle
is an **id-global lookup** (the handle carries the queue, the
lookup keys on the id alone), the tx-side `get_job_tx`
([ADR-021](decisions/021-tx-reads-and-value-shape-fixes.md) §1) is
name-taking and **queue-filtered** (2026-10-11 close-out pass —
core-contract's queues section carries the split; the wave-6
review's recorded prose ambiguity).
- **Cut-flag context**: result storage is
[ADR-002](decisions/002-feature-scope.md)'s cut-flag row;
[ADR-010](decisions/010-queue-semantics-depth.md) §7 reconsidered it