Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Docs

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: docs/package-lock.json

- name: Install docs dependencies
working-directory: docs
run: npm ci

- name: Prepare logo asset
run: cp assets/clockworks-display-resized.png docs/public/logo.png

- name: Build VitePress site
working-directory: docs
run: npm run docs:build

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist

deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

4 changes: 4 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
.vitepress/dist/
.vitepress/cache/
public/logo.png
89 changes: 89 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { defineConfig } from 'vitepress'

export default defineConfig({
base: '/Clockworks/',
title: 'Clockworks',
description: 'Deterministic, fully controllable time for distributed-system simulations and testing.',

head: [
['meta', { property: 'og:title', content: 'Clockworks' }],
['meta', { property: 'og:description', content: 'Deterministic, fully controllable time for distributed-system simulations and testing.' }],
['meta', { property: 'og:type', content: 'website' }],
],

lastUpdated: true,

markdown: {
theme: { light: 'github-light', dark: 'github-dark' },
lineNumbers: true,
},

themeConfig: {
// Absolute paths are resolved relative to `base`
logo: '/logo.png',
siteTitle: 'Clockworks',

nav: [
{ text: 'Guide', link: '/guide/' },
{ text: 'Concepts', link: '/concepts/why-clockworks' },
{
text: 'API Reference',
link: 'https://www.nuget.org/packages/Clockworks',
target: '_blank',
},
{ text: 'Changelog', link: '/changelog' },
{
text: 'v1.3.0',
link: 'https://github.com/dexcompiler/Clockworks/releases',
target: '_blank',
},
],

sidebar: {
'/guide/': [
{
text: 'Guide',
items: [
{ text: 'Getting Started', link: '/guide/' },
{ text: 'Installation', link: '/guide/#installation' },
{ text: 'Simulated Time', link: '/guide/simulated-time' },
{ text: 'Timeouts', link: '/guide/timeouts' },
{ text: 'UUIDv7 Generation', link: '/guide/uuidv7' },
{ text: 'Hybrid Logical Clock', link: '/guide/hlc' },
{ text: 'Vector Clock', link: '/guide/vector-clock' },
{ text: 'Instrumentation', link: '/guide/instrumentation' },
],
},
],
'/concepts/': [
{
text: 'Concepts',
items: [
{ text: 'Why Clockworks?', link: '/concepts/why-clockworks' },
{ text: 'HLC vs Vector Clocks', link: '/concepts/hlc-vs-vector' },
{ text: 'Determinism Model', link: '/concepts/determinism' },
{ text: 'Security Considerations', link: '/concepts/security' },
],
},
],
},

socialLinks: [
{ icon: 'github', link: 'https://github.com/dexcompiler/Clockworks' },
],

footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2024–present Dexter Ajoku (CloudyBox)',
},

search: {
provider: 'local',
},

editLink: {
pattern: 'https://github.com/dexcompiler/Clockworks/edit/main/docs/:path',
text: 'Edit this page on GitHub',
},
},
})
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import DefaultTheme from 'vitepress/theme'
export default DefaultTheme
37 changes: 37 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Docs README
---

# Documentation site (VitePress)

This directory contains the Clockworks documentation site, built with VitePress 1.x.

## Local development

1) Install dependencies:

```bash
cd docs
npm install
```

2) Copy the logo into `docs/public/`:

```bash
cp ../assets/clockworks-display-resized.png public/logo.png
```

3) Start the dev server:

```bash
npm run docs:dev
```

## Notes on GitHub Pages

The site is deployed as a GitHub Pages **project site** under `/Clockworks/`, so the VitePress `base` is set to:

- `base: '/Clockworks/'`

In CI, the workflow copies `assets/clockworks-display-resized.png` to `docs/public/logo.png` before running the build. The generated `logo.png` is intentionally excluded from git.

59 changes: 59 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Changelog
---

# Changelog

This page mirrors the repository root `CHANGELOG.md`.

<!-- NOTE: kept in sync manually with /CHANGELOG.md -->

## [Unreleased]

## [1.3.0] - 2026-02-19

### Fixed
- Fix packed HLC decode for high-bit wall times.
- Enforce HLC drift bounds when the clock moves backwards.
- Harden `VectorClock` string parsing and coordinator locking.
- Fix `VectorClock` overflow behavior.
- Fix thread safety in demo `FailureInjector`.
- Preserve correlation IDs correctly in at-least-once demo flows.
- Prevent a template dictionary memory leak in the integration demo.
- Stabilize demo `MessageId` generation.

### Changed
- Optimize HLC timestamp serialization/parsing, and simplify witness max selection.
- Make HLC message header `TryParse` non-exceptional.
- Split HLC coordinator supporting types into separate files.
- Optimize `VectorClock` merge/compare/increment and serialization.
- Use `ArrayPool<T>` for vector clock merge buffers.
- Use `CollectionsMarshal` for vector clock canonicalization.
- UUIDv7 packing now uses `BinaryPrimitives` and a tighter packing path.
- UUIDv7 factory batch generation throughput improvements.
- Adopt C# 14 extension member blocks.

### Added
- Add high-value property tests for HLC and vector clocks.
- Demo: add distributed-systems at-least-once simulation with HLC/VC stats.

### Documentation
- Update README with HLC and `VectorClock` wire formats.
- Clarify HLC drift bounds and ordering scope.

### Build
- Infrastructure scripts: improve setup/maintenance harness and dotnet installation step.

## [1.2.0] - 2026-01-27

### Changed
- HLC receive semantics now witness the full remote `HlcTimestamp` `(wallTime, counter, nodeId)` (including nodeId tie-breaking) rather than only the remote wall time.
- HLC coordinator receive statistics now treat "remote ahead" based on full timestamp ordering.

### Added
- Additional property tests for `HlcCoordinator` covering remote-ahead by counter, nodeId tie-breaking, remote-behind behavior, and mixed send/receive/time interleavings.
- Unit test coverage for receiving a remote timestamp with higher counter at the same wall time.

### Build
- Centralized common build properties in `Directory.Build.props`.

40 changes: 40 additions & 0 deletions docs/concepts/determinism.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: Determinism Model
---

# Determinism Model

Clockworks is designed so **simulated scheduler time deterministically drives timers/timeouts**, while **wall time can be manipulated independently** to model clock skew, rewinds, and other wall-clock effects.

## Two notions of time

When using `SimulatedTimeProvider`, there are two separate “clocks”:

- **Wall time** (`GetUtcNow()`): controllable, may move backwards via `SetUtcNow(...)`.
- **Scheduler time** (timers + `GetTimestamp()`): monotonic, advances only via `Advance(...)`.

This split is intentional:

- Timers/timeouts are driven by scheduler time so they’re reproducible and ordered deterministically.
- Wall time remains available for scenarios where you want to simulate clock drift, skew, or rewinds without changing timer ordering.

## Deterministic timer behavior

Timers created via `SimulatedTimeProvider.CreateTimer(...)` fire when scheduler time reaches their due time. Advancing time:

```csharp
var tp = new SimulatedTimeProvider();
var fired = 0;

using var timer = tp.CreateTimer(_ => fired++, null, TimeSpan.FromSeconds(1), Timeout.InfiniteTimeSpan);

tp.Advance(TimeSpan.FromSeconds(1));
// fired == 1
```

Periodic timers default to **coalescing** on large time jumps: if you advance by a long duration, the timer is rescheduled from “now” (rather than firing repeatedly for every missed period). This keeps simulations fast and avoids unbounded callback loops.

## Timeout determinism

`Timeouts.CreateTimeout(...)` and `Timeouts.CreateTimeoutHandle(...)` schedule cancellation using the provided `TimeProvider`. If that provider is simulated, timeouts are fast-forwardable and fully deterministic.

55 changes: 55 additions & 0 deletions docs/concepts/hlc-vs-vector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: HLC vs Vector Clocks
---

# HLC vs Vector Clocks

Clockworks provides two complementary approaches for tracking ordering/causality in distributed systems and simulations:

- **Hybrid Logical Clock (HLC)**: a *total order* that stays close to physical time with \(O(1)\) metadata.
- **Vector Clock (VC)**: a *partial order* with exact causality and *concurrency detection*.

## Hybrid Logical Clock (HLC)

HLC timestamps combine a wall-clock time with a logical counter (and node ID tie-breaking). In Clockworks, drift bounds are configurable via `HlcOptions.MaxDriftMs`, and strict enforcement can be enabled with `ThrowOnExcessiveDrift`.

Best for:

- Systems where wall-clock proximity matters (time-based SLAs, time-window queries)
- High-throughput paths where \(O(1)\) overhead is critical
- “Good enough” ordering when you don’t need to detect concurrency

Trade-offs:

- ✅ \(O(1)\) space/time overhead per event
- ✅ Close to physical time; bounded drift enforcement is configurable
- ❌ Cannot detect concurrency (only ordering)
- ❌ Benefits from reasonably synchronized clocks

## Vector Clock (VC)

Vector clocks track a counter per node. They can prove that one event happened-before another, and can also detect concurrency.

Best for:

- Exact happens-before relationships
- Conflict/concurrency detection in replicated state
- Debugging distributed races (seeing where concurrency occurred)
- Scenarios where relying on physical time is undesirable

Trade-offs:

- ✅ Exact causality
- ✅ Detects concurrency
- ✅ No dependency on physical time
- ❌ Metadata grows with number of nodes
- ❌ Merge/compare costs scale with entries present in the clock

## Practical guidance

- Prefer **HLC** if you primarily want a cheap, monotonic ordering that correlates with wall time.
- Prefer **Vector Clocks** when concurrency detection is important (e.g., last-writer-wins vs merge, replicated workflows, debugging).
- In simulations, you can use both:
- HLC for cheap global ordering / time-ordered IDs
- Vector clocks for precise causality assertions

20 changes: 20 additions & 0 deletions docs/concepts/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: Security Considerations
---

# Security Considerations

Clockworks focuses on deterministic time and causality tooling. As with any identifiers and timestamps that cross trust boundaries, be deliberate about what you expose.

## UUIDv7 time exposure

UUIDv7 values embed a millisecond-resolution timestamp by design (RFC 9562). As a result, UUIDv7 generated by `UuidV7Factory` can often be decoded to reveal an approximate creation time, and ordering/rate information can sometimes be inferred from sequences of IDs.

If you are issuing identifiers across **untrusted/public boundaries** (URLs, externally-visible resource IDs, third-party logs), do not treat UUIDv7 as opaque.

Common mitigations:

- Use a random UUID (UUIDv4) for externally-visible identifiers.
- Keep UUIDv7 as an internal primary key, and expose a separate opaque token externally.
- Wrap/encrypt identifiers for external presentation if you need internal ordering but external opacity.

Loading
Loading