Files
alkstore/tasks/sqlite-engine-scheduler-outbox.md
T

4.6 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
sqlite-engine-scheduler-outbox SQLite engine — scheduler (`schedule`/`unschedule`/`run_schedules`) and outbox helper pending
sqlite-engine-queues
sqlite-engine-locks
broad high component implementation
wave-3
sqlite-engine

Description

Implement the scheduler collapse (ADR-009) and the outbox helper (ADR-019 §1, ADR-014) — the last mechanisms, and the only ones with their own long-running machinery:

Scheduler:

  • schedule(name, spec, queue, payload, opts) — upsert by name via the substrate's scheduler_register; validate name AND queue argument (shared-namespace rules, ADR-021 §3); validate the spec against the v1 grammar (@every <n><unit>, s|m|h|d) before storage — InvalidSpec, no round trip (the substrate's parse_every_interval is the parser; map its rejection to InvalidSpec). Returns the Schedule read-back (via the core constructor).
  • unschedule(name) — substrate's scheduler_unregister; true = removed, false = absent.
  • run_schedules(stop) — the leader loop (ADR-009 §1), engine-side async over the sync substrate tick:
    1. Acquire the leadership lock (__alkstore_scheduler — the reserved-prefix derived name; use the engine's own lock machinery with a derived TTL, engine-internal detail).
    2. Loop: renew the lock — on loss, return Err(LeadershipLost) before ticking (never fire on a stolen lease); call scheduler_tick (fires due boundaries, catch-up-capped at 64, skip-forward — all substrate-side); sleep until scheduler_soonest or stop.
    3. A cancelled StopToken ends the sleep and returns the clean Ok(()). The loop runs in spawn_blocking (it's a blocking sleep loop) with the stop token checked between iterations — or a tokio-select shape over a watch channel wrapping the token (implementer's choice; the token's flip-everywhere semantics are core's).
  • The runner must be honest about the wake path: after a tick fires jobs, queue claims proceed through the ordinary queue machinery — the scheduler adds no delivery machinery (ADR-009 §3).

Outbox:

  • outbox(name) — validated constructor; derives the backing queue name __alkstore_outbox:{name} (reserved prefix — unreachable by enqueue_tx by design).
  • Outbox::enqueue — auto-commit enqueue into the backing queue, stamping the outbox's derived 60/5/5 set (ADR-014 §1) — the one non-plain defaults set.
  • Outbox::run_once(worker_id, delivery) — claim one job from the backing queue, run delivery.deliver(job): Ok ⇒ ack; Err(e) ⇒ retry(Some(e), None) (the queue's curve). true = claimed and processed; false = no work. No engine-issued heartbeat inside delivery (the boxed JobHandle's own heartbeat is the consumer's renewal path).

Acceptance Criteria

  • schedule upserts; spec/name/queue validation all pinned (InvalidSpec pre-storage, InvalidName/ReservedName on both name and queue argument); Schedule read-back correct
  • unschedule returns true/false correctly
  • run_schedules: fires due boundaries into the named queue (jobs claimable via the ordinary queue surface); leadership lock held; a second concurrent runner does not double-fire (one-firer-per-boundary — the writer serialization floor)
  • Leadership loss returns Err(LeadershipLost) before any tick; clean stop returns Ok(()); catch-up cap + skip-forward observable through the substrate's pinned behavior
  • Scheduler-fired jobs stamp the target queue's derived defaults (300/3/5/none) with ScheduleOpts applied over them (ADR-020 §3) — get_job-visible
  • Outbox: derived backing queue unreachable by enqueue_tx (reserved-prefix rejection); outbox_enqueue_tx (from the seam task) commit-atomic; enqueue stamps 60/5/5; run_once acks on Ok, retries on Err, returns false on empty
  • cargo test -p alkstore-sqlite, clippy -D warnings, fmt clean

References

  • docs/architecture/decisions/009-scheduler-collapse.md
  • docs/architecture/decisions/014-outbox-tx-enqueue.md
  • docs/architecture/decisions/019-mechanism-handle-surfaces.md §1, §4, §5
  • docs/architecture/decisions/020-enqueue-opt-semantics-and-bridges.md §3
  • docs/architecture/decisions/021-tx-reads-and-value-shape-fixes.md §3
  • docs/architecture/engine-sqlite.md (Mapping the contract: scheduler/outbox)
  • alkstore-sqlite/src/substrate/queue_ops.rs (scheduler_* fns)

Notes

To be filled by implementation agent

Summary

To be filled on completion