Core: #[doc(hidden)] engine-side constructors for Job, StreamEvent, Schedule, Wake

Task core-engine-value-constructors (wave-3 pre-work). All four value
types are #[non_exhaustive] (ADR-017 §3), so downstream engine crates
cannot struct-literal-construct them (E0639). Give engines a sanctioned
construction path without weakening the consumer posture: pub
#[doc(hidden)] full-field constructors (Job::from_row, StreamEvent::
from_row, Schedule::new, Wake::new), each doc-commented as engine-
construction-only — not contract surface, not covered by ADR-017's
semver-minor field-addition promise; a field addition changes the
signature and is a lockstep-duty event (ADR-017 §5). Core tests now
construct through the new constructors; no behavior change.
This commit is contained in:
glm-5.3-flash committed 2026-10-08 11:07:53 +00:00
1 parent 1269246faf
commit 5474141f2b
7 files changed
+190 -37

No files matched your search

+56
View File
@@ -80,6 +80,62 @@ pub struct Job {
}
impl Job {
/// Engine-construction-only constructor taking the full field set.
///
/// # Contract posture
///
/// Not consumer API and not part of the contract surface: engines
/// (in-repo, lockstep-updated with core) use it to build [`Job`]
/// values from row data, since `#[non_exhaustive]` (ADR-017 §3)
/// bans struct-literal construction downstream. Not covered by
/// ADR-017's semver-minor field-addition promise — a `Job` field
/// addition changes this signature, which is an engine breaking
/// change under a core change and a lockstep-duty event
/// (ADR-017 §5). Hidden from consumer-facing docs.
#[doc(hidden)]
#[allow(clippy::too_many_arguments)]
pub fn from_row(
id: i64,
queue: String,
state: JobState,
payload: Vec<u8>,
priority: i64,
run_at: i64,
attempts: i64,
max_attempts: i64,
worker_id: Option<String>,
claimed_at: Option<i64>,
claim_expires_at: Option<i64>,
created_at: i64,
expires_at: Option<i64>,
visibility_timeout_s: i64,
backoff_base_s: i64,
dead_letter_retention_s: Option<i64>,
last_error: Option<String>,
died_at: Option<i64>,
) -> Self {
Self {
id,
queue,
state,
payload,
priority,
run_at,
attempts,
max_attempts,
worker_id,
claimed_at,
claim_expires_at,
created_at,
expires_at,
visibility_timeout_s,
backoff_base_s,
dead_letter_retention_s,
last_error,
died_at,
}
}
/// Decode the stored payload bytes into a concrete target type.
///
/// The stored bytes are the serde_json serialization of the
+24
View File
@@ -20,3 +20,27 @@ pub struct Schedule {
/// engine defaults at each boundary fire (ADR-020 §3).
pub opts: ScheduleOpts,
}
impl Schedule {
/// Engine-construction-only constructor taking the full field set.
///
/// # Contract posture
///
/// Not consumer API and not part of the contract surface: engines
/// (in-repo, lockstep-updated with core) use it to build
/// [`Schedule`] values from row data, since `#[non_exhaustive]`
/// (ADR-017 §3) bans struct-literal construction downstream. Not
/// covered by ADR-017's semver-minor field-addition promise — a
/// `Schedule` field addition changes this signature, which is an
/// engine breaking change under a core change and a lockstep-duty
/// event (ADR-017 §5). Hidden from consumer-facing docs.
#[doc(hidden)]
pub fn new(name: String, spec: String, queue: String, opts: ScheduleOpts) -> Self {
Self {
name,
spec,
queue,
opts,
}
}
}
+30
View File
@@ -32,6 +32,36 @@ pub struct StreamEvent {
}
impl StreamEvent {
/// Engine-construction-only constructor taking the full field set.
///
/// # Contract posture
///
/// Not consumer API and not part of the contract surface: engines
/// (in-repo, lockstep-updated with core) use it to build
/// [`StreamEvent`] values from row data, since
/// `#[non_exhaustive]` (ADR-017 §3) bans struct-literal
/// construction downstream. Not covered by ADR-017's
/// semver-minor field-addition promise — a `StreamEvent` field
/// addition changes this signature, which is an engine breaking
/// change under a core change and a lockstep-duty event
/// (ADR-017 §5). Hidden from consumer-facing docs.
#[doc(hidden)]
pub fn from_row(
offset: i64,
stream: String,
key: Option<String>,
payload: Vec<u8>,
created_at: i64,
) -> Self {
Self {
offset,
stream,
key,
payload,
created_at,
}
}
/// Decode the stored payload bytes into a concrete target type.
///
/// The stored bytes are the serde_json serialization of the
+6 -6
View File
@@ -330,12 +330,12 @@ impl Store for ProbeStore {
_payload: Value,
_opts: ScheduleOpts,
) -> BoxedFuture<'a, Result<Schedule>> {
Box::pin(std::future::ready(Ok(Schedule {
name: "probe".into(),
spec: "@every 60s".into(),
queue: "q".into(),
opts: ScheduleOpts::default(),
})))
Box::pin(std::future::ready(Ok(Schedule::new(
"probe".into(),
"@every 60s".into(),
"q".into(),
ScheduleOpts::default(),
))))
}
fn unschedule<'a>(&'a self, _name: &str) -> BoxedFuture<'a, Result<bool>> {
Box::pin(std::future::ready(Ok(false)))
+26 -28
View File
@@ -51,36 +51,36 @@ fn schedule_opts_default_matches_enqueue_opts_twin_types() {
}
fn sample_job(payload: Vec<u8>) -> Job {
Job {
id: 42,
queue: "orders".to_string(),
state: JobState::Dead,
Job::from_row(
42,
"orders".to_string(),
JobState::Dead,
payload,
priority: 7,
run_at: 1000,
attempts: 3,
max_attempts: 3,
worker_id: Some("w1".to_string()),
claimed_at: Some(999),
claim_expires_at: Some(1299),
created_at: 900,
expires_at: Some(5000),
visibility_timeout_s: 300,
backoff_base_s: 5,
dead_letter_retention_s: None,
last_error: Some("max attempts exceeded".to_string()),
died_at: Some(1100),
}
7,
1000,
3,
3,
Some("w1".to_string()),
Some(999),
Some(1299),
900,
Some(5000),
300,
5,
None,
Some("max attempts exceeded".to_string()),
Some(1100),
)
}
fn sample_event(payload: Vec<u8>) -> StreamEvent {
StreamEvent {
offset: 17,
stream: "events".to_string(),
key: Some("k".to_string()),
StreamEvent::from_row(
17,
"events".to_string(),
Some("k".to_string()),
payload,
created_at: 1234,
}
1234,
)
}
#[test]
@@ -173,9 +173,7 @@ fn stop_token_debug_shows_cancelled_state() {
#[test]
fn wake_carries_only_the_channel() {
let w = Wake {
channel: "ch-1".to_string(),
};
let w = Wake::new("ch-1".to_string());
assert_eq!(w.channel, "ch-1");
assert_eq!(w.clone(), w);
}
+20
View File
@@ -20,3 +20,23 @@ pub struct Wake {
/// The channel the wake was delivered on.
pub channel: String,
}
impl Wake {
/// Engine-construction-only constructor taking the full field set.
///
/// # Contract posture
///
/// Not consumer API and not part of the contract surface: engines
/// (in-repo, lockstep-updated with core) use it to build [`Wake`]
/// values (including the Postgres engine's synthetic reconnect
/// wake), since `#[non_exhaustive]` (ADR-017 §3) bans
/// struct-literal construction downstream. Not covered by
/// ADR-017's semver-minor field-addition promise — a `Wake` field
/// addition changes this signature, which is an engine breaking
/// change under a core change and a lockstep-duty event
/// (ADR-017 §5). Hidden from consumer-facing docs.
#[doc(hidden)]
pub fn new(channel: String) -> Self {
Self { channel }
}
}