Documentation
¶
Index ¶
- func BindEffectiveNow(ctx context.Context, r Resolver, p Pin) (context.Context, bool)
- func EffectiveNow(ctx context.Context) (time.Time, bool)
- func Now(ctx context.Context) time.Time
- func WithEffectiveNow(ctx context.Context, t time.Time) context.Context
- func WithSim(ctx context.Context, s Sim) context.Context
- type Clock
- type Fake
- type Pin
- type Resolver
- type Sim
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BindEffectiveNow ¶
BindEffectiveNow resolves a Pin via the resolver and returns a ctx carrying that entity's simulated-time context: its effective-now AND, when the entity is clock-pinned, the clock id. On resolver error, returns the original ctx unmodified — failing the operator's action on a dangling pin is worse than stamping the wrong domain on an edge-case row. The caller can log the error if they care; most don't.
Binding the clock id here (not just the instant) is what makes audit sim-axis stamping TOTAL: every emitter downstream of a pin resolution inherits the clock, so no emitter has to know test clocks exist. The name is unchanged because the call sites' intent is unchanged — "bind this entity's time domain onto ctx"; the clock id is part of that domain, and always was.
The returned bool is true when the ctx now carries effective-now, false when the binding was skipped (no resolver, no ids, or error).
func EffectiveNow ¶
EffectiveNow returns the bound instant and true when ctx carries any binding (WithEffectiveNow or WithSim); zero time and false otherwise.
func Now ¶
Now is the package-level shortcut for "the simulated time if ctx is bound, wall-clock otherwise" — same semantics as Real().Now(ctx) but with zero allocation overhead and no clock-field dep on the caller.
Used by postgres stores and other infrastructure code that doesn't own a Clock struct field but still needs ctx-aware timestamps. Service-layer code should keep using s.clock.Now(ctx) so tests can pin a Fake clock; stores read directly from ctx because their production behavior is identical to Real() and tests bind ctx directly.
func WithEffectiveNow ¶
WithEffectiveNow binds an effective-now instant whose clock is unknown. Every downstream Clock.Now(ctx) reads this value instead of wall-clock.
It deliberately CLEARS any clock id already bound on ctx: rebinding a bare instant means "this path's time no longer comes from a clock I can name," and carrying the previous clock id forward would stamp rows with a clock the new instant does not belong to. Callers that know the clock call WithSim.
func WithSim ¶ added in v0.2.0
WithSim binds a full simulated-time context (instant + clock) to ctx. Called where the clock is KNOWN: the ForClock/catchup drivers (which hold the clock id and its frozen_time) and BindEffectiveNow (which resolves an entity's pin). Every downstream Clock.Now(ctx) reads s.At, and every audit emission on this ctx is stamped onto the sim axis (audit_log.sim_effective_at / test_clock_id) by the Logger itself — ADR-090 §5.
The contract is the one ADR-029 already established for time: "binding at the boundary, inheritance below it." A new emitter does not have to remember to stamp the sim axis — which is exactly why stamping was partial while every emitter had to opt in by hand.
Types ¶
type Clock ¶
type Clock interface {
// Now returns ctx's effective-now if bound (clock.WithEffectiveNow),
// otherwise wall-clock UTC. The ctx-binding shape replaces the
// per-callsite ClockResolver pattern shipped in earlier ADR-029
// fixes — ctx propagation gives compositional correctness without
// every callsite having to remember to resolve.
Now(ctx context.Context) time.Time
}
Clock provides the current time. The ctx parameter lets callers ride a request-scoped effective-now (a test clock's frozen_time) plumbed via WithEffectiveNow at operator entry points; without one, callers get wall-clock.
Why ctx-aware, not bare Now(): on a clock-pinned billing entity, every business-logic timestamp must be in the test clock's frozen_time domain (Stripe's "no semantic change" guarantee). A bare wall-clock Now() forces every callsite to remember to resolve the pin manually — exactly the leak we hit shipping ADR-029. With a ctx-aware Now(ctx), services bind effective-now once at the operator entry point and every downstream callsite (including stores) inherits automatically.
Callers that legitimately want wall-clock regardless of pinning (cron tick scheduler, webhook delivery timestamps, postgres updated_at columns, audit-log recorded_at) call time.Now().UTC() directly — we don't go through Clock for those, by design.
type Fake ¶
type Fake struct {
// contains filtered or unexported fields
}
Fake is a controllable clock for testing. The bound effective-now from ctx still wins over Fake's `current` field — tests that want to exercise the ctx-binding path can do so without losing Fake's per-test deterministic value.
type Pin ¶
Pin describes which entity to resolve effective-now from. Exactly one of CustomerID / SubscriptionID / InvoiceID must be non-empty; callers pick the most specific id available. TenantID is always required.
type Resolver ¶
type Resolver interface {
SimForCustomer(ctx context.Context, tenantID, customerID string) (Sim, error)
SimForSubscription(ctx context.Context, tenantID, subscriptionID string) (Sim, error)
SimForInvoice(ctx context.Context, tenantID, invoiceID string) (Sim, error)
}
Resolver maps an entity reference to its simulated-time context — the clock it is pinned to and that clock's frozen_time, or a bare wall-clock Sim (TestClockID "") when the entity is not pinned. Used at operator entry points to bind ctx via BindEffectiveNow, and at boundary code paths (Stripe webhook handlers, async workers) where there's no inherited ctx binding.
It returns the instant and the clock id TOGETHER, from one resolution, because they are two halves of one fact: the audit sim axis stamped from a clock id resolved separately from its instant is how you get a row claiming "clock C at wall-clock now" (ADR-090 §5).
Implemented by *billing.Engine. The platform/clock package owns the interface to avoid an import cycle (every domain that needs binding can depend on clock without depending on billing).
type Sim ¶ added in v0.2.0
Sim is the SIMULATED-TIME CONTEXT of a code path: which test clock's world it is acting in (TestClockID) and the simulated instant it lands at (At). The halves travel together and are only ever set from one read of the clock — "clock C, at wall-clock now" is a lie, and the audit log it would be stamped into is append-only, so the pairing is enforced by the type rather than by remembering.
TestClockID == "" means "not in any simulation": At is wall-clock, or a bare WithEffectiveNow binding whose clock is not known, and nothing is stamped on the sim axis.
func SimOf ¶ added in v0.2.0
SimOf returns the bound simulated-time context. ok is true ONLY for a complete binding on a real clock (see Sim.Simulated): a bare WithEffectiveNow binding reports false, because its clock is unknown and inventing one would fabricate evidence in an append-only log.
func (Sim) Simulated ¶ added in v0.2.0
Simulated reports whether this context is inside a test clock's world. BOTH halves are required: a clock id without an instant (or an instant without a clock) is a partial binding, and every consumer treats it as absent — a half-stamped audit row would land in the partial clock index and then answer sim-time queries with a wall-clock timestamp.