tatara_process/time.rs
1//! Wall-clock time primitives — the small typed layer over the
2//! `chrono::DateTime<Utc>` → `std::time::Duration` bridge every timed
3//! decision in the workspace passes through, plus the K8s wire-form
4//! composers ([`tombstone_now`], [`tombstone_at`]) that lift a
5//! `DateTime<Utc>` anchor into the `Option<Time>` shape
6//! `ObjectMeta::deletion_timestamp` (and its metadata-Time peers)
7//! carry.
8//!
9//! Kubernetes exposes wall-clock anchors on the wire as
10//! `k8s_openapi::apimachinery::pkg::apis::meta::v1::Time`
11//! (`DateTime<Utc>` after `.0`) — `metadata.creationTimestamp`,
12//! `metadata.deletionTimestamp`, `status.phaseSince`,
13//! `PoolMember.enteredStateAt`, etc. Every timed decision (TTL
14//! expiry, sleep-budget picker, staleness gate) then projects
15//! `(now, anchor)` onto an `Option<std::time::Duration>` so it can
16//! be compared to a `humantime`-parsed budget (also
17//! `std::time::Duration`). This module owns the one-line chain that
18//! projection reduces to on the READ side, and — via [`tombstone_now`]
19//! and [`tombstone_at`] — the 5-token `Some(Time(<anchor>))` wire
20//! wrap every WRITE-side fixture that seeds a tombstone-present
21//! corner stamps on the metadata slot.
22
23use chrono::{DateTime, FixedOffset, Utc};
24use k8s_openapi::apimachinery::pkg::apis::meta::v1::Time;
25use std::time::Duration;
26
27/// Elapsed wall-clock time between `anchor` and `now`, or `None` if
28/// `anchor` is in `now`'s future (a clock rewind or a mis-sequenced
29/// anchor). The one-line `now.signed_duration_since(anchor).to_std()
30/// .ok()` chain lifted to ONE typed owner past the ★★ PRIME-DIRECTIVE
31/// ≥ 2 duplication threshold, and the peer of every timed-decision
32/// gate that compares an anchor to a `humantime`-parsed budget.
33///
34/// Pre-lift the SAME chain was hand-authored at THREE workspace-wide
35/// consumer sites, each projecting a `(now, anchor)` pair onto an
36/// `Option<std::time::Duration>` for comparison against a
37/// `humantime`-parsed TTL/free-TTL:
38///
39/// * [`crate::lifetime_clock::evaluate`] — the ephemeral-lifetime
40/// TTL-expiry gate. Reads
41/// `now.signed_duration_since(creation).to_std().ok()` inside the
42/// non-terminal-phase guard, fires `AutoTerminate::Now { TtlExpired }`
43/// iff the elapsed duration is `>= ttl`.
44/// * [`crate::lifetime_clock::requeue_with_ttl`] — the sleep-budget
45/// picker for the reconciler's next requeue, choosing the smaller
46/// of HEARTBEAT and TTL-remaining so the controller doesn't oversleep
47/// past a TTL boundary. Reads the SAME two-link chain via a `match`
48/// that maps the `Err` arm onto the caller's `default` fallback.
49/// * `tatara-pool-reconciler::pool_decide::decide_pool_reconcile` —
50/// the Free-member staleness gate. Reads
51/// `now.signed_duration_since(m.entered_state_at).to_std().ok()` per
52/// `MemberState::Free` row and pushes the member's process-name onto
53/// the stale-Free list iff the elapsed duration exceeds the pool's
54/// `free_ttl`.
55///
56/// All THREE sites walked the SAME two-link chain — take the signed
57/// chrono delta, then discard the negative-anchor arm — differing
58/// only in the tail (`if let Some(elapsed)` guard, `match` with a
59/// per-fn `default` fallback, `if let Some` composed with a per-member
60/// push). Post-lift each callsite reads `elapsed_since(now, anchor)`
61/// and applies its own tail at its own site.
62///
63/// Return-form axis: `Option<std::time::Duration>` matches the
64/// downstream comparator's type. `humantime::parse_duration` returns
65/// `Result<std::time::Duration, _>`, so the elapsed-side projection
66/// yielding the SAME `std::time::Duration` puts both operands of the
67/// comparator on the same axis without a per-consumer conversion.
68///
69/// The `None` arm is the "clock ran backwards or the anchor is in the
70/// future" corner — a Kubelet clock skew, a `Time` slot stamped with
71/// `.0 == Utc::now() + Δ`, or a test that fixes `now` before the
72/// anchor to prove the timed decision short-circuits. Every consumer
73/// interprets the corner as "no elapsed data → don't fire the timed
74/// action"; the pins below bind that shape.
75///
76/// A future normalization (a monotonic-clock cross-check, a
77/// millisecond-precision truncation for cross-node determinism, a
78/// per-fleet skew tolerance that bumps a small `Δ` past a negative
79/// signed delta before the `to_std().ok()` cast) lands at THIS ONE
80/// substrate primitive and every downstream timed-decision consumer
81/// inherits the upgrade mechanically — no per-site edit at any of
82/// the THREE listed callers or at future consumers (an
83/// allocation-TTL expiry gate, a stable-name claim-arbiter age
84/// tie-break, a pool member's Allocated-state max-age reap probe).
85#[must_use]
86pub fn elapsed_since(now: DateTime<Utc>, anchor: DateTime<Utc>) -> Option<Duration> {
87 now.signed_duration_since(anchor).to_std().ok()
88}
89
90/// A wall-clock anchor `secs` seconds before the current instant — the
91/// one-line `Utc::now() - chrono::Duration::seconds(secs)` chain lifted
92/// to ONE typed owner past the ★★ PRIME-DIRECTIVE ≥ 2 duplication
93/// threshold, and the composition partner of every timed-decision test
94/// (or production caller) that needs a "recently-past" anchor to feed
95/// [`elapsed_since`] or a `humantime`-parsed budget comparator.
96///
97/// Pre-lift the SAME chain was hand-authored at 21 workspace-wide
98/// consumer sites across 6 files, each restating `Utc::now() -
99/// chrono::Duration::seconds(<N>)` verbatim to seed a
100/// `DateTime<Utc>` `N` seconds in the past:
101///
102/// * `tatara-process` — 15 sites across `crd.rs` + `lib.rs` +
103/// `lifetime_clock.rs` seeding TTL-expiry, staleness-gate, and
104/// observed-anchor tests.
105/// * `tatara-reconciler::claim` — 4 sites in the stable-name claim
106/// arbiter's pure decision tests, each seeding a `granted_at` or
107/// `created_at` anchor for tie-break arithmetic.
108/// * `tatara-pool-reconciler` — 2 sites in `pool_decide` +
109/// `desired.rs` seeding per-member `entered_state_at` /
110/// `created_at` for pool-convergence dwell-time decisions.
111///
112/// All 21 sites walked the SAME two-link chain — read the wall clock,
113/// then subtract a whole-second `chrono::Duration` — differing only in
114/// the second-count `N` (`age_secs` parameter, `500`, `720`, `42`,
115/// etc.). Post-lift each callsite reads `seconds_ago(N)` and the
116/// wall-clock read + subtraction sink lives at ONE substrate owner.
117///
118/// Return-form axis: `DateTime<Utc>` — the copy-form anchor every
119/// consumer's downstream `signed_duration_since` / `[`elapsed_since`]`
120/// / `Time(anchor)` composer takes as its second operand. The `i64`
121/// `secs` parameter matches `chrono::Duration::seconds`'s own signature
122/// so a negative value (rare but permitted) yields a future anchor,
123/// mirroring the pre-lift semantics.
124///
125/// Sibling to [`elapsed_since`] on the same `(now, anchor) → Δ` axis —
126/// `elapsed_since` reads the delta between two given anchors,
127/// `seconds_ago` produces the anchor `N` seconds before now that the
128/// delta consumer needs.
129///
130/// A future normalization (a monotonic-clock cross-check, an injectable
131/// `time_source: impl Fn() -> DateTime<Utc>` for deterministic tests,
132/// a per-fleet skew Δ that clamps the wall-clock read past a known-bad
133/// range) lands at THIS ONE substrate primitive and every downstream
134/// consumer (production callers, test helpers, future timed-decision
135/// gates) inherits the upgrade mechanically — no per-site edit at any
136/// of the 21 listed callers or at future consumers.
137#[must_use]
138pub fn seconds_ago(secs: i64) -> DateTime<Utc> {
139 Utc::now() - chrono::Duration::seconds(secs)
140}
141
142/// A tombstone stamp for a K8s [`metadata.deletionTimestamp`][kdel]
143/// slot at the current wall-clock instant — the wire shape K8s
144/// stamps once the API server has received a DELETE request but the
145/// finalizer chain has not yet released the object for GC. The
146/// `Option<Time>` return form matches the slot's own type
147/// (`ObjectMeta::deletion_timestamp: Option<Time>`) so the tombstone
148/// composes directly into the metadata without a per-caller `Some(...)`
149/// wrap or a per-caller `Time(...)` wrap of the `Utc::now()` read.
150///
151/// Pre-lift the SAME
152/// `Some(k8s_openapi::apimachinery::pkg::apis::meta::v1::Time(Utc::now()))`
153/// / `Some(Time(chrono::Utc::now()))` 5-token wire shape was hand-
154/// authored at 11 workspace-wide fixture sites across four files,
155/// each stamping the tombstone slot on one of the three tatara-owned
156/// CRDs to seed a deletion-in-progress fixture:
157///
158/// * [`crate::crd`] `crd::deletion_tombstoned_tests::tombstoned_process`
159/// — the shared `Process` fixture the [`crate::crd::Process::is_being_deleted`]
160/// inherent-forwarder pin family (7 test cases at `crd.rs` line 4956)
161/// destructures for its tombstone-present corner.
162/// * [`crate::pool`] `pool::deletion_tombstoned_tests::tombstoned_pool`
163/// — the peer `EphemeralPool` fixture the sibling
164/// [`crate::pool::EphemeralPool::is_being_deleted`] inherent-forwarder
165/// pin family destructures (at `pool.rs` line 2768).
166/// * `tatara-pool-reconciler::allocation_decide::tests::
167/// deletion_timestamp_releases_assigned_process` — the allocation
168/// reconciler's tombstone-releases-bind pin (at `allocation_decide.rs`
169/// line 609).
170/// * `tatara-pool-reconciler::pool_decide::tests::
171/// deletion_stamp_triggers_drain` — the pool reconciler's
172/// tombstone-triggers-Drain pin (at `pool_decide.rs` line 343).
173/// * [`crate::deletion_tombstoned_tests`] — 7 pins in `lib.rs` (lines
174/// 1588, 1595, 1602, 1621, 1649, 1663, 1688, 1701) covering the
175/// trait's blanket-impl behavior across all three CRDs plus the
176/// two inherent-forwarder coherence pins.
177///
178/// Every callsite walked the SAME 5-token chain — take the wall-clock
179/// instant, wrap it in the K8s Time newtype, wrap that in `Some` — and
180/// wanted the `Option<Time>` form for direct assignment to the
181/// `metadata.deletion_timestamp` slot. Post-lift each callsite reads
182/// `tombstone_now()` and the wall-clock read + K8s Time wrap + Option
183/// wrap sinks live at ONE substrate owner.
184///
185/// Return-form axis: `Option<Time>` — the exact type
186/// `ObjectMeta::deletion_timestamp` carries. A caller wanting the bare
187/// [`Time`] (e.g. seeding a `LastTransitionTime` on a `Condition`,
188/// where the field is `Time` and not `Option<Time>`) unwraps via
189/// `tombstone_now().unwrap()` at the callsite — but this primitive's
190/// contract is the `Option<Time>` slot, matching the pre-lift shape
191/// every one of the 11 hand-authored callsites walked. The peer
192/// [`tombstone_at`] takes an explicit anchor for callers that need a
193/// past-anchored tombstone (e.g. a "stamped an hour ago" fixture for
194/// a stale-tombstone garbage-collection probe).
195///
196/// Peer to [`crate::DeletionTombstoned`] on the (WRITE, READ) axis:
197/// [`crate::DeletionTombstoned::is_being_deleted`] is the READ probe
198/// (the trait's blanket impl reads `.metadata.deletion_timestamp.
199/// is_some()` on any tatara CRD); [`tombstone_now`] is the WRITE
200/// composer (the substrate owner for the 5-token wire shape every
201/// fixture that seeds a tombstone-present corner stamps). The two
202/// primitives partition the deletion-timestamp surface at the (read,
203/// write) axis and cover it end-to-end at the substrate.
204///
205/// A future normalization (a monotonic-clock cross-check on the wall
206/// read, a per-fleet skew Δ that biases the tombstone anchor past a
207/// known-bad range, a widening of the K8s Time wire form under a
208/// future k8s-openapi crate bump, a debug-build assertion that the
209/// caller has admission privileges to stamp a tombstone at all) lands
210/// at THIS ONE substrate primitive and every downstream fixture / seed
211/// / stamp callsite inherits the upgrade mechanically — no per-site
212/// edit at any of the 11 listed callers or at future consumers (a
213/// stable-name claim-arbiter's tombstoned-generation seed, a
214/// tatara-testing helper that stamps a tombstone on a mock-server
215/// object, an admission-webhook fixture that fires the tombstone
216/// stamp itself).
217///
218/// [kdel]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#objectmeta-v1-meta
219#[must_use]
220pub fn tombstone_now() -> Option<Time> {
221 Some(Time(Utc::now()))
222}
223
224/// A tombstone stamp for a K8s [`metadata.deletionTimestamp`][kdel]
225/// slot at the operator-supplied `when` anchor — the peer of
226/// [`tombstone_now`] on the anchor-explicit axis. Composes directly
227/// with [`seconds_ago`] so a callsite needing a "stamped `N` seconds
228/// ago" tombstone (e.g. a stale-tombstone garbage-collection probe, a
229/// fixture that seeds a tombstone predating the reconciler's `now` by
230/// enough to trip a `deletion_grace_period_seconds` cutoff) reads
231/// `tombstone_at(seconds_ago(N))` and routes through ONE substrate
232/// owner for both the anchor construction and the wire-form wrap.
233///
234/// Pre-lift the SAME `Some(Time(<anchor>))` wire shape was hand-
235/// authored at 1 workspace-wide site — the tombstone-present corner
236/// of [`crate::deletion_tombstoned_tests::is_being_deleted_matches_pre_lift_deletion_timestamp_is_some_chain_on_ephemeral_allocation`],
237/// which sweeps three corners of the (absent, present-at-now,
238/// present-at-past) input matrix and stamps `Some(Time(seconds_ago(3600)))`
239/// on the present-at-past corner. Together with [`tombstone_now`]'s
240/// 11 callsites, the pair covers the 12-site `Some(Time(<anchor>))`
241/// family the substrate opens ownership over.
242///
243/// The `DateTime<Utc>` parameter form encodes the invariant "the caller
244/// has already chosen the anchor" at the type level — a caller wanting
245/// the current-instant tombstone routes through [`tombstone_now`]
246/// instead of `tombstone_at(Utc::now())`, keeping the wall-clock read
247/// at ONE substrate owner and avoiding the "did the caller mean the
248/// clock at the seed-instant or the clock at the assertion-instant"
249/// ambiguity a `DateTime<Utc>::default()` form would open.
250///
251/// A future normalization at the wire form (see the doc-comment on
252/// [`tombstone_now`] for the full rationale) lands at THIS primitive
253/// alongside [`tombstone_now`] so both anchor shapes inherit the
254/// upgrade mechanically at the same substrate site.
255///
256/// [kdel]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#objectmeta-v1-meta
257#[must_use]
258pub fn tombstone_at(when: DateTime<Utc>) -> Option<Time> {
259 Some(Time(when))
260}
261
262/// Parse an `Option<&str>` as an RFC-3339 wall-clock stamp, discarding
263/// the `ParseError` arm on the parseable-input axis. The one-line
264/// `<opt>.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())`
265/// chain lifted to ONE typed owner past the ★★ PRIME-DIRECTIVE ≥ 2
266/// duplication threshold, and the sibling of every timed-decision
267/// assertion / API-input parser that reads an RFC-3339 stamp out of a
268/// JSON slot, an HTTP query param, or a K8s `status`-subresource value.
269///
270/// Pre-lift the SAME chain was hand-authored at 7 workspace-wide sites
271/// across `tatara-reconciler::patch` — the wire-body substrate tests
272/// pinning the `phase_status_base` / `phase_status_msg` /
273/// `phase_status_with` sibling family's `phaseSince` slot at
274/// fail-before-pass-after granularity:
275///
276/// * 3 sites walk `.and_then(Value::as_str).and_then(|s|
277/// chrono::DateTime::parse_from_rfc3339(s).ok())` to lift the parsed
278/// `DateTime<FixedOffset>` for a `[before, after]` bracket check that
279/// proves the primitive stamped at call time.
280/// * 4 sites walk `.and_then(Value::as_str).is_some_and(|s|
281/// chrono::DateTime::parse_from_rfc3339(s).is_ok())` inside an
282/// `assert!` that pins the slot's shape as "present + parses as
283/// RFC-3339". The `bool` derives from `parse_rfc3339_opt(<opt>)
284/// .is_some()`.
285///
286/// All 7 sites walked the SAME two-link chain — take an `Option<&str>`
287/// (typically from a `serde_json::Value::as_str` cast), then parse the
288/// inner `&str` as RFC-3339 and discard the `Err` arm. Post-lift each
289/// callsite reads `parse_rfc3339_opt(<opt>)` (with `.is_some()` at the
290/// 4 predicate sites) and the parser + `Result::ok()` discard sinks
291/// live at ONE substrate owner.
292///
293/// Return-form axis: `Option<DateTime<FixedOffset>>` matches
294/// `chrono::DateTime::parse_from_rfc3339`'s own return type — every
295/// consumer that wants a `DateTime<Utc>` composes `.map(|dt| dt
296/// .with_timezone(&chrono::Utc))` at its own site (the shape the
297/// production `list_events` handlers at `tatara-api::rest::list_events`
298/// + `tatara-testing::server::list_events` already walk), keeping the
299/// timezone-normalization axis at the caller rather than baking a
300/// specific `Utc` cast into the primitive.
301///
302/// A future normalization (a relaxed RFC-3339 profile that accepts a
303/// `space`-separated date/time separator, a per-fleet clock-skew Δ
304/// that rejects stamps too far in the future, a debug-build assertion
305/// that the caller has already trimmed surrounding whitespace) lands
306/// at THIS ONE substrate primitive and every downstream consumer
307/// inherits the upgrade mechanically — no per-site edit at any of the
308/// 7 listed callers or at future consumers (a `/proc`-table READ-side
309/// timestamp reader, a compliance-binding freshness gate, a probe-
310/// receipt `verified_at` field parser).
311#[must_use]
312pub fn parse_rfc3339_opt(s: Option<&str>) -> Option<DateTime<FixedOffset>> {
313 s.and_then(|s| DateTime::parse_from_rfc3339(s).ok())
314}
315
316/// A `DateTime<Utc>` anchor at the whole-second Unix epoch offset
317/// `secs` — the one-line `DateTime::<Utc>::from_timestamp(secs, 0)
318/// .expect("valid epoch second")` chain lifted to ONE typed owner past
319/// the ★★ PRIME-DIRECTIVE ≥ 2 duplication threshold, and the
320/// deterministic-anchor peer of the wall-clock-reading composers
321/// [`seconds_ago`] / [`tombstone_now`] on the same `→ DateTime<Utc>`
322/// axis.
323///
324/// Pre-lift the SAME chain was hand-authored at 10 workspace-wide
325/// fixture / helper sites within `tatara-process`, each restating the
326/// whole-second `DateTime::<Utc>::from_timestamp(<secs>, 0)
327/// .unwrap()` / `.expect(...)` shape to seed a deterministic anchor
328/// that a fanout / composition / preservation pin can compare
329/// verbatim (differing only in the whole-second `secs` argument and
330/// in the `.unwrap()` vs `.expect("valid epoch second")` failure
331/// arm):
332///
333/// * `tatara-process::time::tests::t` — the private test helper
334/// `fn t(secs: i64) -> DateTime<Utc>` inside this module's own
335/// tests, called out of every `elapsed_since` composition pin;
336/// swept the `secs = 100 / 160 / 200 / 500` corners.
337/// * `tatara-process::pool::tests::member` — the 3rd-arg
338/// `entered_state_at` seed for the `PoolMember::unallocated` test
339/// helper feeding the `state_count_fanout` / `process_names_set`
340/// pin family; pinned at `secs = 0`.
341/// * `tatara-process::pool::tests::named_member` — the peer
342/// `named_member(process_name, state)` helper feeding the
343/// `process_names_set` deduplication pins; pinned at `secs = 0`.
344/// * `tatara-process::pool::tests::pool_status_observed_composes_
345/// pre_lift_status_seed_verbatim` — the `now` anchor for the
346/// `PoolStatus::observed` composition pin; pinned at `secs =
347/// 1_700_000_000` (a mid-2023 wall-clock timestamp).
348/// * `tatara-process::pool::tests::pool_status_observed_moves_
349/// members_by_value_without_extra_clone` — the `now` anchor for
350/// the ownership pin; pinned at `secs = 0`.
351/// * `tatara-process::pool::tests::pool_member_unallocated_fills_
352/// every_slot_verbatim` — the positional-axis pin anchor; pinned
353/// at `secs = 1_700_000_000`.
354/// * `tatara-process::pool::tests::pool_member_unallocated_accepts_
355/// owned_string_and_str_at_the_same_signature` — the `impl
356/// Into<String>` axis pin anchor; pinned at `secs = 0`.
357/// * `tatara-process::pool::tests::pool_member_unallocated_matches_
358/// pre_lift_struct_literal_bytewise` — the byte-shape parity pin
359/// anchor swept across every `MemberState` variant; pinned at
360/// `secs = 1_700_000_000`.
361/// * `tatara-process::pool::tests::pool_member_unallocated_
362/// preserves_caller_clock_anchor` — TWO anchors on the
363/// epoch-vs-future axis, pinned at `secs = 0` and `secs =
364/// 2_000_000_000` (a mid-2033 wall-clock timestamp).
365///
366/// All 10 sites walked the SAME two-link chain — build a UTC
367/// `DateTime` from a whole-second Unix epoch offset, then discard
368/// the `None` arm via `.unwrap()` / `.expect(...)` — differing only
369/// in the second-count `secs` and in the failure-arm phrasing.
370/// Post-lift each callsite reads `at_epoch_second(N)` and the
371/// `chrono::DateTime::<Utc>::from_timestamp` construction + `None`-
372/// arm discard sinks live at ONE substrate owner.
373///
374/// Return-form axis: `DateTime<Utc>` — the copy-form anchor every
375/// consumer's downstream `signed_duration_since` / [`elapsed_since`]
376/// / [`tombstone_at`] / `PoolMember::unallocated(<name>, <state>,
377/// <anchor>)` composer takes as its `DateTime<Utc>`-typed operand.
378/// The `i64` `secs` parameter matches `chrono::DateTime::<Utc>::
379/// from_timestamp`'s own signature so a negative value (rare but
380/// permitted for pre-epoch anchors) or a value past `i64::MAX / 2`
381/// (also rare) yields whatever chrono itself yields, preserving the
382/// pre-lift semantics verbatim on every corner every caller cared
383/// about.
384///
385/// Failure-arm axis: `.expect("valid epoch second")` — matches the
386/// module-internal helper's phrasing (which every existing
387/// `elapsed_since` composition pin already routed through) rather
388/// than pool.rs's `.unwrap()` phrasing. The composer is
389/// `#[must_use]` and consumers write `at_epoch_second(N)` inline; a
390/// caller that specifically needed the `.unwrap()` message can still
391/// panic via the primitive because `chrono::DateTime::<Utc>::
392/// from_timestamp` returns `Option<Self>` on the exact SAME
393/// out-of-range corner — the `.expect(...)` phrasing sharpens the
394/// panic message without changing the panic condition.
395///
396/// Sibling to [`seconds_ago`] on the `→ DateTime<Utc>` axis:
397/// `seconds_ago` reads the wall clock at call time and returns an
398/// anchor `N` seconds before it (a production timed-decision seed),
399/// `at_epoch_second` reads a caller-supplied whole-second Unix epoch
400/// offset and returns the anchor deterministically (a test-fixture /
401/// deterministic-composition seed). The two composers partition the
402/// `→ DateTime<Utc>` axis at the (wall-clock, deterministic) split
403/// and cover it end-to-end at the substrate.
404///
405/// A future normalization (a per-fleet millisecond-precision
406/// truncation on the anchor, a debug-build assertion that `secs` is
407/// in a permitted window, a swap of the underlying
408/// `chrono::DateTime::<Utc>::from_timestamp` for a
409/// `TryFrom<UnixEpochSeconds>` typed alternative that pushes the
410/// out-of-range corner into the type system) lands at THIS ONE
411/// substrate primitive and every downstream consumer inherits the
412/// upgrade mechanically — no per-site edit at any of the 10 listed
413/// callers or at future consumers (a stable-name claim-arbiter's
414/// deterministic-anchor fixture, a compliance-binding freshness-gate
415/// test seed, a probe-receipt `verified_at` field's deterministic
416/// fixture).
417#[must_use]
418pub fn at_epoch_second(secs: i64) -> DateTime<Utc> {
419 DateTime::<Utc>::from_timestamp(secs, 0).expect("valid epoch second")
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 fn t(secs: i64) -> DateTime<Utc> {
427 // The whole-second epoch anchor rides through the ONE
428 // substrate owner `at_epoch_second` (peer of the 10 workspace-
429 // wide restatements of the SAME
430 // `DateTime::<Utc>::from_timestamp(<secs>, 0).expect(...)` /
431 // `.unwrap()` fixture chain that pre-lift lived at every
432 // deterministic-anchor test helper in this crate — this
433 // module's own tests + `pool::tests::{member, named_member,
434 // pool_status_observed_composes_pre_lift_status_seed_verbatim,
435 // pool_status_observed_moves_members_by_value_without_extra_
436 // clone, pool_member_unallocated_*}`).
437 at_epoch_second(secs)
438 }
439
440 #[test]
441 fn anchor_before_now_returns_positive_delta() {
442 // The canonical shape every consumer rides through — `anchor`
443 // stamped in the past, `now` fixed later, the elapsed duration
444 // available for comparison against a `humantime`-parsed budget.
445 // Pin: the returned duration is exactly the second-count delta
446 // between the two anchors, in `std::time::Duration` so the
447 // downstream `elapsed >= ttl` / `elapsed > free_ttl` comparator
448 // works without a per-consumer conversion.
449 let anchor = t(100);
450 let now = t(160);
451 assert_eq!(elapsed_since(now, anchor), Some(Duration::from_secs(60)));
452 }
453
454 #[test]
455 fn anchor_equals_now_returns_zero_duration() {
456 // Boundary corner: `now == anchor` yields `Some(Duration::ZERO)`
457 // rather than `None`. Every consumer needs the "just-stamped"
458 // moment to count as elapsed=0 (not as "no elapsed data"): the
459 // TTL-expiry gate at `evaluate` fires iff `elapsed >= ttl`, so
460 // a zero-ttl ephemeral must expire on its own creation instant
461 // — swapping this arm to `None` would silently keep every
462 // zero-TTL Process alive.
463 let same = t(500);
464 assert_eq!(elapsed_since(same, same), Some(Duration::ZERO));
465 }
466
467 #[test]
468 fn anchor_after_now_returns_none() {
469 // The clock-skew / mis-sequenced-anchor corner: `anchor > now`
470 // yields `None`. Every consumer interprets `None` as "don't
471 // fire the timed action this tick" — the TTL-expiry gate skips
472 // the `AutoTerminate::Now` branch, the sleep-budget picker
473 // returns the caller's `default`, the pool staleness gate
474 // leaves the member off the stale-Free list. A regression that
475 // returned a saturating `Duration::ZERO` for this corner would
476 // silently fire a zero-TTL ephemeral's expiry the moment its
477 // creation timestamp landed one Kubelet clock-skew millisecond
478 // ahead of the reconciler's `now`.
479 let anchor = t(200);
480 let now = t(100);
481 assert_eq!(elapsed_since(now, anchor), None);
482 }
483
484 #[test]
485 fn subsecond_precision_survives_the_to_std_cast() {
486 // The `chrono::Duration → std::time::Duration` cast preserves
487 // subsecond precision — a regression that silently truncated
488 // to whole seconds would compare an "elapsed = 500ms" against
489 // a `humantime::parse_duration("1s")` budget as "0s < 1s"
490 // rather than "500ms < 1s" and misfire on any decision whose
491 // budget straddles a second boundary. Pin the cast at the
492 // primitive so a future normalization can't silently drop the
493 // subsecond bits.
494 let anchor = DateTime::<Utc>::from_timestamp(100, 0).expect("valid epoch second");
495 let now = DateTime::<Utc>::from_timestamp(100, 500_000_000).expect("valid epoch nanos");
496 assert_eq!(elapsed_since(now, anchor), Some(Duration::from_millis(500)));
497 }
498
499 #[test]
500 fn one_nanosecond_backwards_returns_none() {
501 // The `.to_std().ok()` cast rejects negative chrono deltas by
502 // returning `Err` — one nanosecond of backwards skew is enough
503 // to reach the `None` arm. Pin the boundary at exactly the
504 // point the wire-shape flips so a future normalization that
505 // widens the tolerance (a per-fleet skew Δ, a monotonic-clock
506 // cross-check) has to move THIS pin rather than silently
507 // trampling every consumer's negative-anchor short-circuit.
508 let anchor = DateTime::<Utc>::from_timestamp(100, 1).expect("valid epoch nano");
509 let now = DateTime::<Utc>::from_timestamp(100, 0).expect("valid epoch second");
510 assert_eq!(elapsed_since(now, anchor), None);
511 }
512
513 // ─── seconds_ago substrate pins ────────────────────────────────────
514 //
515 // Bind [`seconds_ago`] at fail-before-pass-after granularity so a
516 // regression that flipped the sign (`+` instead of `-`), swapped
517 // the unit (`minutes` instead of `seconds`), dropped the wall-clock
518 // read to a stale module-load constant, or reshaped the return
519 // form surfaces HERE rather than as silent operator-visible drift
520 // at the 21 downstream consumers.
521
522 #[test]
523 fn seconds_ago_returns_anchor_in_the_past() {
524 // Primary shape asserted end-to-end: the returned anchor lies
525 // between `before` and `after`, offset back by exactly `secs`.
526 // A regression that flipped the sign to `+` would land the
527 // anchor in the future and this window check would fail; a
528 // regression that swapped the unit (minutes / hours) would
529 // land the anchor far outside the sub-second window.
530 let secs = 42_i64;
531 let before = Utc::now();
532 let anchor = seconds_ago(secs);
533 let after = Utc::now();
534 assert!(
535 anchor <= before - chrono::Duration::seconds(secs) + chrono::Duration::milliseconds(50),
536 "anchor {anchor} must be ≤ before − {secs}s (within 50ms scheduler jitter)"
537 );
538 assert!(
539 anchor >= after - chrono::Duration::seconds(secs) - chrono::Duration::milliseconds(50),
540 "anchor {anchor} must be ≥ after − {secs}s (within 50ms scheduler jitter)"
541 );
542 }
543
544 #[test]
545 fn seconds_ago_composes_with_elapsed_since_at_ttl_gate_shape() {
546 // The canonical downstream composition: a consumer seeds an
547 // anchor with `seconds_ago(N)` and immediately feeds it to
548 // `elapsed_since(Utc::now(), anchor)`, expecting the returned
549 // duration to be ~N seconds. A regression that reshaped either
550 // primitive so the two no longer round-trip would surface HERE
551 // rather than as silent skew at the TTL-expiry gate, the pool
552 // staleness gate, or the requeue-budget picker downstream.
553 let secs = 30_i64;
554 let anchor = seconds_ago(secs);
555 let elapsed = elapsed_since(Utc::now(), anchor).expect("elapsed is Some for past anchor");
556 assert!(
557 elapsed >= Duration::from_secs(secs as u64),
558 "elapsed {elapsed:?} must be ≥ {secs}s — the anchor was stamped {secs}s ago"
559 );
560 assert!(
561 elapsed <= Duration::from_secs(secs as u64) + Duration::from_millis(500),
562 "elapsed {elapsed:?} must be within 500ms of {secs}s — a wider drift means the primitive is no longer wall-clock reading"
563 );
564 }
565
566 #[test]
567 fn seconds_ago_matches_hand_authored_pre_lift_chain_shape() {
568 // Byte-identical parity with the pre-lift `Utc::now() -
569 // chrono::Duration::seconds(N)` block that all 21 hand-
570 // authored callsites restated verbatim, swept across the four
571 // representative second-counts every pre-lift consumer used
572 // (small: 5s, medium: 42s, large: 500s, hour-scale: 3600s).
573 // Both blocks read the wall clock at DIFFERENT instants so the
574 // two anchors CAN differ by the wall-clock delta between
575 // calls — bound the divergence at 100ms scheduler jitter.
576 for secs in [5_i64, 42, 500, 3_600] {
577 let composed = seconds_ago(secs);
578 let hand_authored = Utc::now() - chrono::Duration::seconds(secs);
579 let delta = (hand_authored - composed).abs();
580 assert!(
581 delta <= chrono::Duration::milliseconds(100),
582 "composed {composed} and hand-authored {hand_authored} must agree within 100ms scheduler jitter for secs={secs}"
583 );
584 }
585 }
586
587 #[test]
588 fn seconds_ago_zero_returns_anchor_at_current_instant() {
589 // Boundary corner: `secs = 0` yields the current wall-clock
590 // instant — the "just-created" moment. A regression that
591 // synthesized a small offset (`Duration::from_secs(1)` for
592 // clock skew, a per-fleet Δ) would land the anchor 1 second
593 // in the past and every zero-age test seed would be off by
594 // that offset. Pin the identity so a future normalization
595 // has to explicitly move this pin.
596 let before = Utc::now();
597 let anchor = seconds_ago(0);
598 let after = Utc::now();
599 assert!(anchor >= before && anchor <= after);
600 }
601
602 #[test]
603 fn seconds_ago_negative_returns_anchor_in_the_future() {
604 // Corner: a negative `secs` yields a future anchor. Matches
605 // `chrono::Duration::seconds`'s own signed semantics — a
606 // consumer that wants a future-offset anchor (rare, but the
607 // few tests that stamp `Utc::now() + chrono::Duration::
608 // seconds(...)` for `fallback` construction can route through
609 // this primitive with a negative argument). A regression that
610 // clamped the negative arm to `Utc::now()` (or panicked) would
611 // silently break future callers.
612 let anchor = seconds_ago(-10);
613 let now = Utc::now();
614 assert!(
615 anchor >= now,
616 "negative secs must yield a future anchor: anchor {anchor} vs now {now}"
617 );
618 assert!(
619 anchor <= now + chrono::Duration::seconds(11),
620 "anchor {anchor} must be within (10s + jitter) after now {now}"
621 );
622 }
623
624 // ─── tombstone_now + tombstone_at substrate pins ──────────────────
625 //
626 // Bind the two K8s-wire tombstone composers at fail-before-pass-
627 // after granularity so a regression that dropped the `Some` wrap
628 // (yielding `Option<Time>` = `None`, which would silently un-
629 // tombstone every fixture), swapped the `Time` newtype for a raw
630 // `DateTime<Utc>` (breaking the `metadata.deletion_timestamp: Option<Time>`
631 // slot's shape), or diverged the two composers on the anchor axis
632 // (a `tombstone_at(when)` that ignored `when` and read the wall
633 // clock, an anchor-invariant that clamped a future anchor into
634 // the past) surfaces HERE rather than as silent operator-facing
635 // skew at the 12 downstream fixture consumers.
636 //
637 // Each pin is fail-before-pass-after: the primitives did not exist
638 // pre-lift, so any test that invokes them fails to compile pre-
639 // lift and passes post-lift; the byte-identity pins below then
640 // bind the specific shape choice.
641
642 #[test]
643 fn tombstone_now_returns_some_time_at_current_instant() {
644 // Primary shape asserted end-to-end: the returned option is
645 // `Some(Time(anchor))` with the anchor bracketed by two
646 // wall-clock reads taken immediately before + after the call.
647 // A regression that dropped the `Some` wrap would fail the
648 // outer `is_some()` probe; a regression that stamped a
649 // constant (module-load `Utc::now()`, a `DateTime::<Utc>::
650 // default()` = epoch) would fail the bracket check.
651 let before = Utc::now();
652 let stamp = tombstone_now();
653 let after = Utc::now();
654 let stamped = stamp.expect("tombstone_now must return Some(Time(...))");
655 assert!(
656 stamped.0 >= before && stamped.0 <= after,
657 "tombstone anchor {} must fall in [{before}, {after}]",
658 stamped.0,
659 );
660 }
661
662 #[test]
663 fn tombstone_now_matches_hand_authored_pre_lift_chain_shape() {
664 // Byte-identical parity with the pre-lift
665 // `Some(k8s_openapi::apimachinery::pkg::apis::meta::v1::Time(
666 // Utc::now()))` block that all 11 hand-authored fixture sites
667 // restated verbatim (differing only in `chrono::Utc` vs `Utc`
668 // module-path prefix). Both blocks read the wall clock at
669 // DIFFERENT instants so the two anchors CAN differ by the
670 // wall-clock delta between calls — bound the divergence at
671 // 100ms scheduler jitter, matching the peer
672 // `seconds_ago_matches_hand_authored_pre_lift_chain_shape`
673 // pin's tolerance.
674 let composed = tombstone_now().expect("tombstone_now returns Some");
675 let hand_authored = Some(Time(Utc::now())).expect("hand-authored fixture");
676 let delta = (hand_authored.0 - composed.0).abs();
677 assert!(
678 delta <= chrono::Duration::milliseconds(100),
679 "composed {} and hand-authored {} must agree within 100ms scheduler jitter",
680 composed.0,
681 hand_authored.0,
682 );
683 }
684
685 #[test]
686 fn tombstone_at_returns_some_time_preserving_the_operator_anchor() {
687 // Primary shape for the anchor-explicit peer: the returned
688 // option is `Some(Time(when))` and the anchor is exactly the
689 // `when` argument — no wall-clock read, no normalization, no
690 // clamp. A regression that fell through to the current instant
691 // (`tombstone_at` ignoring `when` and re-reading the wall
692 // clock) would fail the identity check.
693 let epoch = DateTime::<Utc>::from_timestamp(1_700_000_000, 0).expect("valid epoch second");
694 let stamp = tombstone_at(epoch).expect("tombstone_at returns Some");
695 assert_eq!(stamp.0, epoch, "anchor must be preserved verbatim");
696 }
697
698 #[test]
699 fn tombstone_at_composes_with_seconds_ago_at_stale_fixture_shape() {
700 // The canonical downstream composition: a fixture that needs a
701 // "stamped N seconds ago" tombstone composes `tombstone_at(
702 // seconds_ago(N))` and expects the returned anchor to be ~N
703 // seconds in the past. Matches the single pre-lift site at
704 // `lib.rs::deletion_tombstoned_tests::is_being_deleted_matches_pre_lift_deletion_timestamp_is_some_chain_on_ephemeral_allocation`
705 // which stamps `Some(Time(crate::time::seconds_ago(3600)))`.
706 // A regression that reshaped either primitive so the two no
707 // longer round-trip would surface HERE rather than as silent
708 // skew at the stale-tombstone fixture family.
709 let secs = 3_600_i64;
710 let anchor = seconds_ago(secs);
711 let stamp = tombstone_at(anchor).expect("tombstone_at returns Some");
712 assert_eq!(
713 stamp.0, anchor,
714 "tombstone_at must preserve the seconds_ago-produced anchor verbatim",
715 );
716 // And the anchor is ~N seconds in the past — this is the
717 // downstream property every fixture using the composition
718 // relies on.
719 let elapsed = elapsed_since(Utc::now(), stamp.0).expect("elapsed is Some for past anchor");
720 assert!(
721 elapsed >= Duration::from_secs(secs as u64),
722 "elapsed {elapsed:?} must be ≥ {secs}s — the anchor was stamped {secs}s ago",
723 );
724 }
725
726 #[test]
727 fn tombstone_now_and_tombstone_at_agree_at_the_current_instant() {
728 // Cross-composer coherence pin: `tombstone_now()` and
729 // `tombstone_at(Utc::now())` produce the SAME shape (`Some(
730 // Time(...))`) with anchors that agree within scheduler jitter.
731 // A future refactor that consolidated one composer onto the
732 // other (or split them further) cannot land any anchor-axis
733 // drift because this pin binds them at the current-instant
734 // corner where both callsites converge.
735 let a = tombstone_now().expect("tombstone_now returns Some");
736 let b = tombstone_at(Utc::now()).expect("tombstone_at returns Some");
737 let delta = (b.0 - a.0).abs();
738 assert!(
739 delta <= chrono::Duration::milliseconds(100),
740 "tombstone_now anchor {} and tombstone_at(Utc::now()) anchor {} must agree within 100ms scheduler jitter",
741 a.0,
742 b.0,
743 );
744 }
745
746 // ─── parse_rfc3339_opt substrate pins ───────────────────────────
747 //
748 // Bind [`parse_rfc3339_opt`] at fail-before-pass-after granularity
749 // so a regression that swallowed the `Some(_)` arm (yielding
750 // `None` on a well-formed stamp), swapped the parser for a
751 // rfc2822/naive/ISO-8601-only variant (silently rejecting the
752 // `+00:00` offset every K8s `metadata.Time.0` serialiser emits),
753 // or flipped the `.ok()` discard for a `.unwrap()` (panicking on
754 // malformed input rather than short-circuiting at the caller's
755 // `.expect(...)`) surfaces HERE rather than as silent operator-
756 // facing drift at the 7 downstream `phaseSince`-slot pins.
757 //
758 // Each pin is fail-before-pass-after: the primitive did not exist
759 // pre-lift, so any test that invokes it fails to compile pre-lift
760 // and passes post-lift; the byte-identity pins below then bind
761 // the specific shape choice.
762
763 #[test]
764 fn parse_rfc3339_opt_returns_some_datetime_on_well_formed_utc_stamp() {
765 // Primary shape asserted end-to-end: a well-formed `+00:00`
766 // stamp (the exact wire form every K8s `metadata.Time`
767 // serialiser emits, and every `phaseSince`-stamped
768 // `Utc::now()` produces once serialised via `serde_json` into
769 // a `Value`) parses to `Some(dt)` with the anchor preserved
770 // verbatim.
771 let stamp = "2026-05-01T12:34:56+00:00";
772 let parsed = parse_rfc3339_opt(Some(stamp)).expect("well-formed RFC-3339 stamp");
773 let expected =
774 DateTime::parse_from_rfc3339(stamp).expect("hand-authored fixture is well-formed");
775 assert_eq!(parsed, expected);
776 }
777
778 #[test]
779 fn parse_rfc3339_opt_passes_none_through_verbatim() {
780 // The `Option::and_then` short-circuit: a `None` input flows
781 // straight to `None` output without touching the parser. A
782 // regression that expected `Some(_)` unconditionally
783 // (`unwrap_or_default` on the input, an early `.unwrap()`)
784 // would panic HERE rather than at the caller's `.expect(...)`.
785 assert_eq!(parse_rfc3339_opt(None), None);
786 }
787
788 #[test]
789 fn parse_rfc3339_opt_discards_the_err_arm_on_malformed_input() {
790 // The `.ok()` discard arm: a `Some(&str)` that isn't RFC-3339
791 // flows to `None` — the caller's `.is_some()` predicate then
792 // returns `false` for the "present-but-malformed" corner. A
793 // regression that panicked (`unwrap`) or bubbled the
794 // `ParseError` (a `Result` return form) would break every
795 // predicate-site assertion that reads the return as a bool.
796 assert_eq!(parse_rfc3339_opt(Some("not-a-timestamp")), None);
797 assert_eq!(parse_rfc3339_opt(Some("")), None);
798 // A naive-only shape (no offset) — RFC-3339 requires the
799 // offset, so this must land on the `None` arm.
800 assert_eq!(parse_rfc3339_opt(Some("2026-05-01T12:34:56")), None);
801 }
802
803 #[test]
804 fn parse_rfc3339_opt_matches_hand_authored_pre_lift_chain_shape() {
805 // Byte-identical parity with the pre-lift `<opt>.and_then(|s|
806 // chrono::DateTime::parse_from_rfc3339(s).ok())` block all 7
807 // hand-authored callsites restated verbatim, swept across the
808 // four representative corners: a well-formed `Some(&str)`, a
809 // `None`, a `Some("")`, and a `Some(<malformed>)`. Both blocks
810 // must project the SAME `Option<DateTime<FixedOffset>>` on
811 // every corner so the collapse is observationally invisible.
812 for (opt, name) in [
813 (Some("2026-05-01T12:34:56+00:00"), "well-formed +00:00"),
814 (Some("2026-05-01T12:34:56.789012345+00:00"), "sub-second"),
815 (Some("2026-05-01T12:34:56-05:00"), "non-UTC offset"),
816 (Some("2026-05-01T12:34:56Z"), "Z-form UTC"),
817 (Some(""), "empty string"),
818 (Some("not-a-timestamp"), "malformed"),
819 (None, "none input"),
820 ] {
821 let composed = parse_rfc3339_opt(opt);
822 let hand_authored = opt.and_then(|s| DateTime::parse_from_rfc3339(s).ok());
823 assert_eq!(
824 composed, hand_authored,
825 "corner `{name}` must round-trip through both shapes",
826 );
827 }
828 }
829
830 #[test]
831 fn parse_rfc3339_opt_is_some_derives_the_predicate_shape() {
832 // The `.is_some()` composition on the primitive's return is
833 // the byte-identical replacement for the pre-lift `<opt>
834 // .is_some_and(|s| chrono::DateTime::parse_from_rfc3339(s)
835 // .is_ok())` chain the 4 predicate-site pins walked. Sweep
836 // the four representative corners: present-and-well-formed,
837 // present-but-malformed, present-but-empty, absent. A
838 // regression that flipped the primitive's `None`-on-malformed
839 // arm to `Some(default)` would silently pass the malformed
840 // corner past every predicate-site assertion.
841 for (opt, expected, name) in [
842 (Some("2026-05-01T12:34:56+00:00"), true, "well-formed"),
843 (Some("not-a-timestamp"), false, "malformed"),
844 (Some(""), false, "empty string"),
845 (None, false, "none input"),
846 ] {
847 assert_eq!(
848 parse_rfc3339_opt(opt).is_some(),
849 expected,
850 "corner `{name}` must project to `{expected}` through the predicate shape",
851 );
852 }
853 }
854
855 #[test]
856 fn parse_rfc3339_opt_round_trips_a_freshly_stamped_utc_now() {
857 // The canonical downstream composition: a `Utc::now()` stamp
858 // serialised via `chrono::DateTime::to_rfc3339` — the shape
859 // every `phaseSince` slot the reconciler stamps writes onto
860 // the wire — parses back through this primitive to a
861 // `DateTime<FixedOffset>` whose UTC-normalised anchor agrees
862 // with the source stamp bytewise. Pin the round-trip identity
863 // so a future normalization (a millisecond-precision
864 // truncation, a per-fleet skew Δ) cannot silently drift the
865 // reader off the writer at the reconciler's own `phaseSince`
866 // wire.
867 let source = Utc::now();
868 let wire = source.to_rfc3339();
869 let parsed = parse_rfc3339_opt(Some(&wire)).expect("Utc::now → to_rfc3339 must round-trip");
870 assert_eq!(parsed.with_timezone(&Utc), source);
871 }
872
873 #[test]
874 fn tombstone_at_preserves_a_future_anchor_without_clamping() {
875 // Corner: `tombstone_at` accepts a future anchor verbatim —
876 // matches the pre-lift `Some(Time(<future>))` shape a caller
877 // wanting a future-offset tombstone would hand-author. Pin the
878 // identity so a future normalization that clamps the anchor
879 // into the past (a "no tombstone can be in the future" policy)
880 // has to explicitly move this pin rather than silently
881 // trampling future callers.
882 let future = Utc::now() + chrono::Duration::seconds(3_600);
883 let stamp = tombstone_at(future).expect("tombstone_at returns Some");
884 assert_eq!(stamp.0, future);
885 }
886
887 // ─── at_epoch_second substrate pins ───────────────────────────────
888 //
889 // Bind [`at_epoch_second`] at fail-before-pass-after granularity so
890 // a regression that drifted the nanosecond slot off the whole-
891 // second axis (a `from_timestamp(secs, 1)` typo that stamps a
892 // 1-ns offset into every deterministic fixture), swapped the
893 // `.expect(...)` for a saturating fallback (yielding
894 // `DateTime::<Utc>::default()` = epoch on the out-of-range corner
895 // rather than panicking), or reshaped the return form off
896 // `DateTime<Utc>` (e.g., to `DateTime<FixedOffset>`) surfaces HERE
897 // rather than as silent operator-invisible drift at the 10
898 // downstream fixture consumers.
899 //
900 // Each pin is fail-before-pass-after: the primitive did not exist
901 // pre-lift, so any test that invokes it fails to compile pre-lift
902 // and passes post-lift; the byte-identity pins below then bind the
903 // specific shape choice.
904
905 #[test]
906 fn at_epoch_second_returns_utc_datetime_at_whole_second_offset() {
907 // Primary shape asserted end-to-end: the returned anchor is
908 // exactly `secs` whole seconds past the Unix epoch, with no
909 // nanosecond component. A regression that stamped a stray
910 // nanosecond slot (a `from_timestamp(secs, 1)` typo, a
911 // per-fleet skew Δ added below the composer) would surface
912 // here as a non-zero `.timestamp_subsec_nanos()`.
913 let anchor = at_epoch_second(1_700_000_000);
914 assert_eq!(anchor.timestamp(), 1_700_000_000);
915 assert_eq!(anchor.timestamp_subsec_nanos(), 0);
916 }
917
918 #[test]
919 fn at_epoch_second_matches_hand_authored_pre_lift_chain_shape() {
920 // Byte-identical parity with the pre-lift `DateTime::<Utc>::
921 // from_timestamp(<secs>, 0).unwrap()` / `.expect(...)` block
922 // that all 10 hand-authored callsites restated verbatim,
923 // swept across the four representative second-counts every
924 // pre-lift consumer used: `0` (the epoch anchor), `100` (the
925 // `elapsed_since` composition pin anchor), `1_700_000_000`
926 // (the mid-2023 wall-clock anchor), and `2_000_000_000` (the
927 // mid-2033 future anchor). Both blocks must project the SAME
928 // `DateTime<Utc>` on every corner so the collapse is
929 // observationally invisible.
930 for secs in [0_i64, 100, 1_700_000_000, 2_000_000_000] {
931 let composed = at_epoch_second(secs);
932 let hand_authored = DateTime::<Utc>::from_timestamp(secs, 0)
933 .expect("hand-authored fixture is well-formed");
934 assert_eq!(
935 composed, hand_authored,
936 "corner `secs={secs}` must round-trip through both shapes",
937 );
938 }
939 }
940
941 #[test]
942 fn at_epoch_second_is_deterministic_across_repeated_calls() {
943 // Determinism pin: `at_epoch_second` does NOT read the wall
944 // clock — every call with the SAME `secs` produces byte-
945 // identical anchors, in contrast to the sibling
946 // [`seconds_ago`] / [`tombstone_now`] wall-clock-reading
947 // peers. A regression that started stamping the composer's
948 // own `Utc::now()` (a fallback default, a per-call skew) would
949 // silently defeat every fanout / composition / preservation
950 // pin that relies on the anchor being replayable.
951 let first = at_epoch_second(500);
952 let second = at_epoch_second(500);
953 assert_eq!(
954 first, second,
955 "at_epoch_second must be deterministic — no wall-clock read",
956 );
957 }
958
959 #[test]
960 fn at_epoch_second_partitions_the_datetime_axis_against_seconds_ago() {
961 // Cross-composer partition pin: `at_epoch_second` and
962 // [`seconds_ago`] both produce `DateTime<Utc>` but partition
963 // the axis at the (deterministic, wall-clock-reading) split —
964 // `at_epoch_second(0)` is byte-identical across two calls,
965 // `seconds_ago(0)` drifts by the scheduler jitter between two
966 // calls. A regression that merged either primitive onto the
967 // other (a `seconds_ago` that started reading a module-load
968 // constant, an `at_epoch_second` that started subtracting from
969 // `Utc::now`) would collapse the partition and surface here.
970 let a = at_epoch_second(0);
971 let b = at_epoch_second(0);
972 assert_eq!(a, b, "at_epoch_second is deterministic");
973 let live_1 = seconds_ago(0);
974 // Do not compare `live_1` to `a` bytewise — the two live at
975 // different points on the `DateTime<Utc>` axis by design.
976 // Instead, pin that the deterministic anchor equals the epoch
977 // (a live wall-clock read is unambiguously past the epoch on
978 // any post-2020 system clock).
979 assert_eq!(a.timestamp(), 0, "at_epoch_second(0) is the Unix epoch");
980 assert!(
981 live_1.timestamp() > 0,
982 "seconds_ago(0) reads the wall clock, which is unambiguously past the epoch",
983 );
984 }
985
986 #[test]
987 fn at_epoch_second_composes_with_tombstone_at_at_deterministic_fixture_shape() {
988 // The canonical downstream composition: a fixture that needs a
989 // deterministic tombstone at a fixed epoch offset composes
990 // `tombstone_at(at_epoch_second(N))` and expects the returned
991 // anchor to be exactly `N` seconds past the Unix epoch. A
992 // regression that reshaped either primitive so the two no
993 // longer round-trip would surface HERE rather than as silent
994 // skew at the deterministic-tombstone fixture family.
995 let anchor = at_epoch_second(1_700_000_000);
996 let stamp = tombstone_at(anchor).expect("tombstone_at returns Some");
997 assert_eq!(
998 stamp.0, anchor,
999 "tombstone_at must preserve the at_epoch_second-produced anchor verbatim",
1000 );
1001 assert_eq!(stamp.0.timestamp(), 1_700_000_000);
1002 assert_eq!(stamp.0.timestamp_subsec_nanos(), 0);
1003 }
1004}