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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Formal specification `SPEC-0003` defining the three-plane Escapement, Cortex mesh pool, and Revenant worker architecture.
- Zero-I/O pool custody domain types (`CapabilitySlug`, `CustodyMode`, `PoolDeliveryRefs`, `PoolResultOutcome`, `PoolResultRefs`, `PoolMessageStatus`, `JobResolution`, `resolve_pool_job_state`, `max_pool_inflight`) and integer `TokenBucket` / impulse ordering in `ep-domain`.
- `TaskState::PoolAssigned` and `WalOp::PoolDispatch` state transitions with bounded terminal history eviction in `escapement-core`.
- `CortexPoolDriver`, `InMemoryCortexPoolDriver`, `Orchestrator::dispatch_pool`, `Orchestrator::reconcile_pool_job`, and `/v1/jobs`, `/v1/pool/results`, `/tasks/claim`, `/tasks/{id}/complete`, and `/tasks/{id}/fail` routes in `escapement-serve`.
- `ep` CLI subcommands (`plan`, `wal`, `lanes`, `mesh`) for DAG validation, WAL playback, lane inspection, and Cortex mesh status checks.
- `CortexPoolDriver`, `InMemoryCortexPoolDriver`, `HttpCortexPoolDriver` (`POST /v1/mesh/messages` to `pool:<capability>` and `GET /v1/mesh/message-status`), `Orchestrator::dispatch_next_to_pool`, `Orchestrator::poll_pool_status`, `Orchestrator::dispatch_pool`, `Orchestrator::reconcile_pool_job`, and `/jobs`, `/pool/results`, `/tasks/claim`, `/tasks/complete`, and `/tasks/fail` routes in `escapement-serve`.
- `WakerProbe`, `TcpWakerProbe`, and `WakerHealthGate` (`CrashLoopBackOff` circuit breaker) in `escapement-core::waker` and `Orchestrator::wake_capability` in `escapement-serve`.
- `ep` CLI subcommands (`plan`, `wal`, `lanes`, `mesh`, `serve`) with `--json` output mode and `--mermaid` DAG rendering.
- Deterministic property-based invariant test suites for `ep-domain` (`TokenBucket`, `resolve_pool_job_state`, `max_pool_inflight`) and `escapement-core` (Kahn DAG topological & permutation invariants, `Changeset` CRDT merge idempotence and associativity).
- Clockwork Conclave 7-specialist roster in `.claude/agents/` and sovereign lane topologies in `.saga/`.

### Changed
Expand Down
19 changes: 9 additions & 10 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,19 @@

See [`specs/SPEC-0002-roadmap.md`](specs/SPEC-0002-roadmap.md) for full formal specifications and operational invariants.

### Phase 2: Unified CLI & Ergonomics (In Progress)
- Interactive CLI subcommands in `crates/ep`: `ep plan validate`, `ep plan render`, `ep wal inspect`, `ep wal replay`, `ep lanes status`.
- Dual output modes: ASD-STE100 calibrated plain text and structured JSON.
### Phase 2: Unified CLI & Ergonomics (Shipped)
- Interactive CLI subcommands in `crates/ep`: `ep plan validate`, `ep plan render` (including `--mermaid`), `ep wal inspect`, `ep wal replay`, `ep lanes status`, `ep mesh status`, and `ep serve`.
- Dual output modes: ASD-STE100 calibrated plain text and structured JSON (`--json`).

### Phase 3: Scale-to-Zero & Waker Integration (Planned)
- Direct integration with `unsigned-gg/waker` (`portolan`) TCP wake proxy via `wake_and_wait(target)`.
- CrashLoopBackOff detection and health-gated task dispatch.
### Phase 3: Scale-to-Zero & Waker Integration (Shipped)
- Direct integration with `unsigned-gg/waker` (`portolan`) TCP wake proxy via `TcpWakerProbe` and `WakerProbe`.
- `CrashLoopBackOff` detection and health-gated task dispatch via `WakerHealthGate` in `escapement-core::waker` and `Orchestrator::wake_capability`.
- Multi-cluster lane distribution across Cygnus and Orion topologies.

### Phase 4: Formal Verification & Algebraic Lattices (Planned)
- Conflict-Free Replicated Data Types (CRDTs) in `merge.rs` for distributed task merging.
### Phase 4: Formal Verification & Algebraic Lattices (Shipped)
- Conflict-Free Replicated Data Types (CRDTs) in `merge.rs` for distributed task merging with verified idempotence, associativity, and domain union invariants.
- Type-state lifecycle constraints enforcing compile-time custody guarantees.
- Stateright / Loom model checking for lock-free concurrency and Proptest invariant testing.
- Undefined behavior validation under Miri.
- Deterministic property-based verification across `ep-domain` (`TokenBucket` conservation, `resolve_pool_job_state` totality, `max_pool_inflight` bounds) and `escapement-core` (Kahn DAG topological & permutation invariants).

### Phase 5: Crates.io Publication & Ecosystem Federation (Planned)
- Protocol freeze at `"1.0"`.
Expand Down
144 changes: 144 additions & 0 deletions crates/ep-domain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1515,4 +1515,148 @@ mod tests {
assert_eq!(max_pool_inflight(4, 100, 2), 8);
assert_eq!(max_pool_inflight(4, 6, 2), 6);
}

fn next_u64(state: &mut u64) -> u64 {
let mut x = *state;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
*state = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}

#[test]
fn prop_token_bucket_never_exceeds_capacity_or_underflows() {
let mut seed = 0xDEAD_BEEF_CAFE_BABEu64;
for _ in 0..2000 {
let cap = (next_u64(&mut seed) % 500) + 1;
let refill = next_u64(&mut seed) % 50;
let init = next_u64(&mut seed) % (cap + 50);
let mut bucket = TokenBucket::with_initial(cap, init, refill, Tick(0));
assert!(bucket.available <= cap);

let mut now = 0u64;
for _ in 0..20 {
// Sometimes advance clock, sometimes keep same or earlier tick
let delta = (next_u64(&mut seed) % 10) as i64 - 2;
let probe_tick = if delta < 0 {
now.saturating_sub(delta.unsigned_abs())
} else {
now.saturating_add(delta as u64)
};
if probe_tick > now {
now = probe_tick;
}
let cost = next_u64(&mut seed) % (cap + 20);
let before = bucket.tokens(probe_tick);
assert!(before <= cap);
match bucket.try_acquire(Tick(probe_tick), cost) {
ValveDecision::Permit { remaining } => {
assert!(cost > 0);
assert!(cost <= before);
assert_eq!(remaining, before - cost);
assert_eq!(bucket.available, remaining);
}
ValveDecision::Hold { .. } => {
assert!(cost == 0 || cost > before);
assert_eq!(bucket.available, before);
}
}
}
}
}

#[test]
fn prop_resolve_pool_job_state_totality_and_retry_bounds() {
let statuses = [
PoolMessageStatus::Unread,
PoolMessageStatus::Pending,
PoolMessageStatus::Acked,
PoolMessageStatus::DeadLettered,
];
let outcomes = [
None,
Some(PoolResultOutcome::Ok),
Some(PoolResultOutcome::Transient),
Some(PoolResultOutcome::Permanent),
];

for status in statuses {
for outcome in outcomes {
for attempt in 1..=6u32 {
for max_retries in 1..=5u32 {
let res_refs = outcome.map(|o| {
PoolResultRefs::new(1, "task-prop", attempt, o, None, None).unwrap()
});
let resolution =
resolve_pool_job_state(status, res_refs.as_ref(), attempt, max_retries);
match (outcome, status, resolution) {
(Some(PoolResultOutcome::Ok), _, JobResolution::Completed) => {}
(
Some(PoolResultOutcome::Permanent),
_,
JobResolution::DeadLetter { .. },
) => {}
(
Some(PoolResultOutcome::Transient),
_,
JobResolution::Requeue { next_attempt },
) => {
assert!(attempt < max_retries);
assert_eq!(next_attempt, attempt + 1);
}
(
Some(PoolResultOutcome::Transient),
_,
JobResolution::DeadLetter { .. },
) => {
assert!(attempt >= max_retries);
}
(
None,
PoolMessageStatus::Unread
| PoolMessageStatus::Pending
| PoolMessageStatus::Acked,
JobResolution::InFlight,
) => {}
(
None,
PoolMessageStatus::DeadLettered,
JobResolution::Requeue { next_attempt },
) => {
assert!(attempt < max_retries);
assert_eq!(next_attempt, attempt + 1);
}
(
None,
PoolMessageStatus::DeadLettered,
JobResolution::DeadLetter { .. },
) => {
assert!(attempt >= max_retries);
}
other => panic!("unexpected state resolution: {other:?}"),
}
}
}
}
}
}

#[test]
fn prop_max_pool_inflight_bounded_by_provider_cap() {
let mut seed = 0x1234_5678_9ABC_DEF0u64;
for _ in 0..2000 {
let members = (next_u64(&mut seed) % 64) as usize;
let cap = (next_u64(&mut seed) % 128) as usize;
let mult = (next_u64(&mut seed) % 8) as usize;
let bound = max_pool_inflight(members, cap, mult);
assert!(bound <= cap);
if cap == 0 {
assert_eq!(bound, 0);
} else {
assert!(bound >= 1);
assert_eq!(bound, members.max(1).saturating_mul(mult.max(1)).min(cap));
}
}
}
}
1 change: 1 addition & 0 deletions crates/ep/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ workspace = true
[dependencies]
ep-domain = { path = "../ep-domain" }
escapement-core = { path = "../escapement-core" }
escapement-serve = { path = "../escapement-serve" }
blackwall-bridge = { path = "../blackwall-bridge" }
anyhow.workspace = true
thiserror.workspace = true
Expand Down
Loading
Loading