Skip to content

Commit 5d2ac66

Browse files
authored
Merge branch 'unstable' into feat/certus-server-yaml
2 parents a68cc4c + 139c20e commit 5d2ac66

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

modelling/creusot/creusot-progress.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ tested, but mathematically guaranteed.
2525
| Exactly ceil(total / max_transfer_size) segments produced | segment_io | us |
2626
| 64-bit LBA arithmetic never overflows | segment_io | us |
2727
| LBA adjacency: seg[i].lba + seg[i].length/ss == seg[i+1].lba | segment_io | us+Coq |
28+
| **Write-through lifecycle safety: entry not evictable during write-through, becomes evictable after** | dispatch-map | us |
2829

2930
---
3031

@@ -261,6 +262,61 @@ is the interface between two different proof systems.
261262

262263
---
263264

265+
## System-level property proof: Write-through lifecycle safety
266+
267+
### Origin: design specification
268+
269+
This proof started not from code inspection but from a formal design document:
270+
271+
**Source:** `components/dispatcher/specs/001-dispatcher-cache-interface/spec.md`
272+
273+
The relevant requirements:
274+
- **FR-003**: `populate()` creates a MemoryTier entry with `write_ref=1`
275+
- **FR-004**: The background writer downgrades `write_ref` to a `read_ref` after GPU copy, holds that read reference during SSD write, then releases it on completion via `convert_to_storage`
276+
- **FR-005**: The entry remains in the memory-tier after write-through completes
277+
- **Edge case**: *"The background writer holds a read reference on each entry while write-through is in progress. The reference is released after write completes or fails."*
278+
279+
The system-level invariant this implies: **an entry can only be cleanly evicted after its data is durably on SSD and no reference is held**. An entry being written through is never cleanly evictable.
280+
281+
### What was proved
282+
283+
The lifecycle proof `lifecycle_write_through_safety` in `components/dispatch-map/verif/src/lib.rs` traces the exact sequence the background writer follows and proves:
284+
285+
| Step | Operation | State | Evictable? |
286+
|---|---|---|---|
287+
| populate | `create_memory_tier_entry` | write_ref=1, read_ref=0, ssd_offset=None | No — write_ref > 0 |
288+
| GPU copy done | `downgrade_reference` | write_ref=0, read_ref=1, ssd_offset=None | No — read_ref > 0 |
289+
| Write-through done | `convert_to_storage` | write_ref=0, read_ref=0, ssd_offset=Some | **Yes** |
290+
291+
The postcondition proved: `no_active_refs(&result)` — both ref counts are zero after the full sequence.
292+
293+
### Components involved
294+
295+
- **Spec**: `components/dispatcher/specs/001-dispatcher-cache-interface/spec.md` (FR-003, FR-004, FR-005)
296+
- **Proof**: `components/dispatch-map/verif/src/lib.rs``lifecycle_write_through_safety` function
297+
- **Operations used**: `create_memory_tier_entry`, `downgrade_reference`, `convert_to_storage` — all previously verified by Daniel in the same crate
298+
- **Predicates used**: `no_active_refs`, `inv_write_binary` — existing logic predicates
299+
300+
### New postconditions required
301+
302+
Three existing functions needed additional postconditions to carry enough information through the proof chain — the SMT solver cannot chain what it cannot see:
303+
304+
1. **`create_memory_tier_entry`** — added: location is `MemoryTier{ssd_offset: None}`. Without this, the solver did not know the structural type of the location field after creation.
305+
306+
2. **`downgrade_reference`** — added: location is preserved (`(^entry).location == (*entry).location`). Without this, the solver could not confirm the entry was still MemoryTier before calling `convert_to_storage`.
307+
308+
3. **`convert_to_storage`** — added: when called on a MemoryTier entry with `read_ref=1` and `write_ref=0`, both ref counts are zero after the call. This is the targeted postcondition that closes the final step of the proof.
309+
310+
### Key lesson: system-level proofs require richer function specs
311+
312+
The individual operations were already proved correct in isolation (by Daniel). But proving the SYSTEM-LEVEL property required strengthening their postconditions to expose information that individual callers never needed but the lifecycle proof did. This is a general pattern: operation-level proofs and system-level proofs have different informational requirements, and moving from one to the other often reveals gaps in the existing specs.
313+
314+
### Coq was not needed
315+
316+
Unlike the LBA adjacency proof, this property was fully discharged by the SMT solvers (alt-ergo, z3, cvc5, cvc4). The key was providing the right structural information about the `location` field — once the prover could case-split on the enum variant, the arithmetic (1-1=0) was trivial.
317+
318+
---
319+
264320
## Verified properties — full detail
265321

266322
### dispatch-map/verif (`components/dispatch-map/verif/`)
@@ -307,6 +363,7 @@ is the interface between two different proof systems.
307363
| `lifecycle_cold_evicted_before_hot` | Cold entry (low TSC) always precedes hot entry |
308364
| `take_read_prevents_eviction` | After take_read, is_evictable is always false |
309365
| Size invariant | All creation paths guarantee size_blocks > 0 |
366+
| `lifecycle_write_through_safety` | **System-level (spec FR-003/FR-004/FR-005):** entry is NOT evictable during write-through; becomes evictable only after write-through completes and the background writer releases its read reference |
310367

311368
### certus-segment-verif (`tools/creusot/certus-segment-verif/`)
312369

0 commit comments

Comments
 (0)