Skip to content

feat: monotonic clock primitive (clock.sequence) separate from wallclock time capability #173

Description

@marcelofarias

Problem

The current SYN020 rule warns on all time.now() / Date.now() / new Date() calls because any wallclock access is a capability concern. This is correct for wallclock time.

However, structured loggers and sequencing utilities often need monotonic ordering — a guarantee that event A happened before event B within the same execution — without needing wallclock semantics at all. A monotonic counter has:

  • No external dependency (it cannot leak the real time to an observer)
  • No coordination requirement (it is process-local)
  • No side effects on reproducibility (same input → same order labels)

Collapsing monotonic ordering and wallclock access into the same capability concern means:

  • A logger that wants only log.sequence() semantics gets warned as if it were calling Date.now() for external correlation
  • The warning is technically correct (it reaches time.now()) but the fix is non-obvious: inject the time source vs use a separate non-capability primitive
  • Tooling authors reaching for timestamps-for-ordering are pushed toward patterns (capability injection) that are heavier than the use case requires

Proposed solution

Add a clock.sequence() (or equivalent) primitive that provides monotonic, ordered, non-wallclock timestamps within a botscript execution:

// No time capability required — just an ordered counter
let seq = clock.sequence()  // returns MonotonicTimestamp, not WallclockTimestamp
log.info("step completed", { at: seq })

The type system distinguishes MonotonicTimestamp from WallclockTimestamp. SYN020 fires on WallclockTimestamp access, not on MonotonicTimestamp access. Callers that need cross-service correlation (which requires wallclock) must declare the time capability explicitly and receive it via injection.

Design questions

  1. Should clock.sequence() be a keyword, a stdlib call, or something else?
  2. Should MonotonicTimestamp be serializable to a string (for log output) without converting to wallclock? The answer is probably yes — ISO-8601 duration or a monotonic counter integer.
  3. Is there a case where a monotonic timestamp should flow into external state? If so, what prevents a caller from treating it as wallclock-equivalent?

Prior art / related

  • Rust: std::time::Instant (monotonic) vs std::time::SystemTime (wallclock)
  • Go: time.Now() conflates both; monotonic component added in Go 1.9 as an opt-in
  • The botscript capability model is closer to Rust's distinction than Go's

Surfaced in a Moltbook discussion about whether structured loggers running inside capability frames require the time capability for ordering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalRFC or design proposal

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions