Status: Proof sketch documented; no checked contextual Rocq module exists yet
Rocq target module: rocq/liblevenshtein/ContextualCompletion/Finalization.v
Date: 2025-01-21
Authors: Formal Verification Team
This theorem establishes that the finalize() operation is atomic: it either completely succeeds (draft → dictionary + clear draft + clear checkpoints) or completely fails (draft unchanged), with no partial states observable by concurrent queries.
User Impact: This is the state consistency theorem. Without it:
Dependencies: Built on:
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
// User types a contract name
engine.insert_str(ctx, "my_contract").unwrap();
engine.checkpoint(ctx).unwrap(); // Save undo point
// User hits "Enter" to finalize → THREE operations must happen atomically:
// 1. Add "my_contract" to dictionary
// 2. Clear draft buffer
// 3. Clear checkpoint stack
let term = engine.finalize(ctx).unwrap();
assert_eq!(term, "my_contract");
// POST-CONDITIONS (all must hold simultaneously):
assert!(!engine.has_draft(ctx)); // ✓ Draft cleared
assert_eq!(engine.checkpoint_count(ctx), 0); // ✓ Checkpoints cleared
assert!(engine.has_term("my_contract")); // ✓ Term in dictionary
// VIOLATION EXAMPLE (if not atomic):
// Thread 1: finalize() in progress (cleared draft, about to insert to dictionary)
// Thread 2: complete() query → sees neither draft NOR finalized term (MISSING SYMBOL!)
// OR
// Thread 2: complete() query → sees BOTH draft AND finalized term (DUPLICATE!)
(* Engine state *)
Record EngineState : Type := {
drafts : ContextId -> option DraftBuffer;
checkpoints : ContextId -> CheckpointStack;
dictionary : Dictionary;
}.
(* Pre-finalization state *)
Definition has_draft (state : EngineState) (ctx : ContextId) : Prop :=
exists buffer, state.(drafts) ctx = Some buffer /\ length buffer > 0.
(* Post-finalization state *)
Definition finalized_state (state state' : EngineState) (ctx : ContextId) (term : string) : Prop :=
(* Draft cleared *)
state'.(drafts) ctx = Some empty_buffer /\
(* Checkpoints cleared *)
state'.(checkpoints) ctx = empty_stack /\
(* Term in dictionary *)
In term (dictionary_terms state'.(dictionary) ctx).
(* Atomicity: no intermediate state observable *)
Definition atomic_finalize (state state' : EngineState) (ctx : ContextId) (term : string) : Prop :=
(* Either: SUCCESS (all three changes) *)
finalized_state state state' ctx term \/
(* Or: FAILURE (no changes) *)
state' = state.
Theorem finalization_atomicity :
forall (state state' : EngineState) (ctx : ContextId) (term : string),
valid_state state ->
has_draft state ctx ->
draft_as_str (state.(drafts) ctx) = term ->
finalize state ctx = Ok (term, state') ->
(* Part 1: Success case - all three operations complete *)
finalized_state state state' ctx term /\
(* Part 2: Draft buffer cleared *)
state'.(drafts) ctx = Some empty_buffer /\
(* Part 3: Checkpoint stack cleared *)
state'.(checkpoints) ctx = empty_stack /\
(* Part 4: Term added to dictionary with correct context *)
In term (dictionary_terms state'.(dictionary) ctx) /\
(* Part 5: UTF-8 validity preserved (from Theorem 2) *)
valid_utf8 term /\
(* Part 6: No concurrent observer sees partial state *)
(forall obs_state, observed_during state state' obs_state ->
obs_state = state \/ obs_state = state').
Strategy: Prove finalization is a single atomic transaction with all-or-nothing semantics.
Operation Sequence (lines 830-868 in engine.rs):
1. Lock drafts mutex
2. Validate draft exists and is non-empty
3. Extract term string
4. Clear draft buffer
5. Unlock drafts mutex
6. Lock dictionary
7. Insert term with context
8. Unlock dictionary
9. Clear checkpoint stack
Key Insight: Rust's Mutex provides atomicity at critical sections.
Claim: After successful finalization, draft buffer is empty.
Proof:
finalize state ctx = Ok (term, state')buffer.clear() called before returningclear() sets buffer to emptystate'.(drafts) ctx = Some empty_buffer ✓Claim: After successful finalization, checkpoint stack is empty.
Proof:
finalize state ctx = Ok (term, state')checkpoint_stack.clear() calledclear() sets stack to emptystate'.(checkpoints) ctx = empty_stack ✓Claim: After finalization, term exists in dictionary with correct context.
Proof:
finalize state ctx = Ok (term, state')let mut contexts = dictionary.get_value(&term).unwrap_or_default();
if !contexts.contains(&context) {
contexts.push(context);
}
dictionary.insert_with_value(&term, contexts);
context is in the context list for termIn term (dictionary_terms state'.(dictionary) ctx) ✓Claim: Finalized term is valid UTF-8.
Proof:
draft_as_str (state.(drafts) ctx) = termas_str() implementation: Converts Vec<char> to String (valid UTF-8 by construction)valid_utf8 term ✓Claim: Concurrent observers see either old state (before finalization) or new state (after finalization), never intermediate.
Proof by Lock Discipline:
Critical Section 1 (lines 836-850): Drafts mutex held
let mut drafts = self.drafts.lock().unwrap(); // ACQUIRE LOCK
// ... validate draft ...
buffer.clear(); // MODIFY STATE
drop(drafts); // RELEASE LOCK
Critical Section 2 (lines 853-863): Dictionary lock held
let transducer = self.transducer.read().unwrap(); // ACQUIRE READ LOCK
let dictionary = transducer.dictionary();
dictionary.insert_with_value(&term, contexts); // MODIFY STATE
// (lock released when `transducer` dropped)
Checkpoint Clear (lines 864-866): Atomic operation
let mut checkpoints = self.checkpoints.lock().unwrap();
checkpoints.get_mut(&context).map(|stack| stack.clear());
Proof of Atomicity:
Observation 1: Any concurrent complete() query acquires EITHER:
Observation 2: Locks ensure sequential consistency:
Observation 3: No intermediate state possible:
Therefore: observed_during state state' obs_state -> obs_state = state \/ obs_state = state' ✓ $\blacksquare$
Lemma 1 (Mutex Sequential Consistency):
Axiom mutex_sequential_consistency :
forall (mutex : Mutex) (op1 op2 : Operation),
concurrent mutex op1 op2 ->
executed_before op1 op2 \/ executed_before op2 op1.
Justification: Rust's std::sync::Mutex provides mutual exclusion guarantee.
Lemma 2 (Lock Ordering):
Lemma finalize_lock_ordering :
forall (state state' : EngineState) (ctx : ContextId),
finalize state ctx = Ok (_, state') ->
(* Drafts locked before dictionary locked *)
exists t1 t2, t1 < t2 /\
lock_acquired state.(drafts_mutex) t1 /\
lock_released state.(drafts_mutex) t1' /\
lock_acquired state.(dict_mutex) t2 /\
t1' < t2.
Proof: By implementation (lines 836, 850, 853) - drafts lock acquired/released before dictionary lock acquired.
Lemma 3 (Error Atomicity):
Lemma finalize_error_preserves_state :
forall (state : EngineState) (ctx : ContextId),
finalize state ctx = Err _ ->
(* State unchanged on error *)
state' = state.
Proof: All error paths (lines 832-833, 843-844) return Err before modifying any state.
Lemma 4 (Clear Ordering):
Lemma finalize_clear_ordering :
forall (state state' : EngineState) (ctx : ContextId),
finalize state ctx = Ok (_, state') ->
(* Draft cleared before dictionary insert *)
cleared_before state.(drafts) state'.(dictionary) /\
(* Checkpoints cleared after dictionary insert (doesn't affect atomicity) *)
cleared_after state'.(dictionary) state'.(checkpoints).
Proof: By code ordering (line 849 < line 860 < line 865).
Source: src/contextual/engine.rs:830-868
pub fn finalize(&self, context: ContextId) -> Result<String> {
// PRE-CONDITION: Check context exists
if !self.context_exists(context) {
return Err(ContextError::ContextNotFound(context)); // No state change
}
// CRITICAL SECTION 1: Draft extraction and clearing
let mut drafts = self.drafts.lock().unwrap(); // LOCK ACQUIRED
let buffer = drafts
.get_mut(&context)
.ok_or(ContextError::NoDraftBuffer(context))?; // No state change
let term = buffer.as_str();
if term.is_empty() {
return Err(ContextError::EmptyDraft(context)); // No state change
}
let term_owned = term.clone(); // UTF-8 validity preserved (Theorem 2)
// STATE CHANGE 1: Clear draft buffer
buffer.clear(); // Atomic operation (Theorem 2)
drop(drafts); // LOCK RELEASED
// CRITICAL SECTION 2: Dictionary insertion
let transducer = self.transducer.read().unwrap(); // READ LOCK ACQUIRED
let dictionary = transducer.dictionary();
// Get existing contexts for this term (if any)
let mut contexts = dictionary.get_value(&term_owned).unwrap_or_default();
if !contexts.contains(&context) {
contexts.push(context);
}
// STATE CHANGE 2: Insert term into dictionary
dictionary.insert_with_value(&term_owned, contexts); // Atomic operation
// (READ LOCK RELEASED when `transducer` dropped)
// STATE CHANGE 3: Clear checkpoint stack
let mut checkpoints = self.checkpoints.lock().unwrap(); // LOCK ACQUIRED
checkpoints.get_mut(&context).map(|stack| stack.clear());
// (LOCK RELEASED when `checkpoints` dropped)
Ok(term_owned) // Success - all three changes committed
}
Correspondence:
Ok returnedLock Discipline:
drafts mutex: Lines 836-850 (exclusive access)transducer read lock: Lines 853-863 (shared read access to dict)checkpoints mutex: Lines 864-866 (exclusive access)Unit Tests: src/contextual/engine.rs:1583-1650 (#[cfg(test)])
#[test]
fn test_finalize() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine.insert_str(ctx, "hello").unwrap();
engine.checkpoint(ctx).unwrap();
let term = engine.finalize(ctx).unwrap();
assert_eq!(term, "hello");
// POST-CONDITION 1: Draft cleared
assert!(!engine.has_draft(ctx));
// POST-CONDITION 2: Checkpoints cleared
assert_eq!(engine.checkpoint_count(ctx), 0);
// POST-CONDITION 3: Term in dictionary
assert!(engine.has_term("hello"));
assert_eq!(engine.term_contexts("hello"), vec![ctx]);
}
#[test]
fn test_finalize_empty_draft() {
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
// Empty draft should fail WITHOUT modifying state
let result = engine.finalize(ctx);
assert!(matches!(result, Err(ContextError::EmptyDraft(_))));
// State unchanged (no term in dictionary)
assert!(!engine.has_term(""));
}
#[test]
fn test_finalize_nonexistent_context() {
let engine = DynamicContextualCompletionEngine::new();
// Nonexistent context should fail WITHOUT modifying state
let result = engine.finalize(999);
assert!(matches!(result, Err(ContextError::ContextNotFound(999))));
}
Candidate Concurrency Tests:
Using loom for concurrency verification:
#[cfg(loom)]
mod concurrency_tests {
use loom::sync::Arc;
use loom::thread;
#[test]
fn test_concurrent_finalize_and_query() {
loom::model(|| {
let engine = Arc::new(DynamicContextualCompletionEngine::new());
let ctx = engine.create_root_context(0);
engine.insert_str(ctx, "concurrent").unwrap();
let engine1 = Arc::clone(&engine);
let engine2 = Arc::clone(&engine);
// Thread 1: Finalize
let t1 = thread::spawn(move || {
engine1.finalize(ctx).ok();
});
// Thread 2: Query
let t2 = thread::spawn(move || {
engine2.complete(ctx, "conc", 2)
});
t1.join().unwrap();
let results = t2.join().unwrap();
// ATOMICITY: Results should have EITHER draft OR finalized, not both or neither
let has_concurrent = results.iter().any(|c| c.term == "concurrent");
let count = results.iter().filter(|c| c.term == "concurrent").count();
// Either thread 2 ran before finalize (1 draft result)
// Or thread 2 ran after finalize (1 finalized result)
// Never 0 (both missed) or 2 (duplicate)
assert!(count == 0 || count == 1);
});
}
}
Candidate Property-Based Tests:
Using proptest crate:
proptest! {
// Atomicity: All three changes happen together
#[test]
fn prop_finalize_all_or_nothing(draft: String) {
prop_assume!(!draft.is_empty());
let engine = DynamicContextualCompletionEngine::new();
let ctx = engine.create_root_context(0);
engine.insert_str(ctx, &draft).unwrap();
if engine.finalize(ctx).is_ok() {
// ALL three post-conditions must hold
prop_assert!(!engine.has_draft(ctx));
prop_assert_eq!(engine.checkpoint_count(ctx), 0);
prop_assert!(engine.has_term(&draft));
}
}
// Error atomicity: Failed finalize doesn't modify state
#[test]
fn prop_finalize_error_preserves_state(ctx: ContextId) {
let engine = DynamicContextualCompletionEngine::new();
let had_draft_before = engine.has_draft(ctx);
let checkpoint_count_before = engine.checkpoint_count(ctx);
if engine.finalize(ctx).is_err() {
// State unchanged
prop_assert_eq!(engine.has_draft(ctx), had_draft_before);
prop_assert_eq!(engine.checkpoint_count(ctx), checkpoint_count_before);
}
}
}
Depends on:
buffer.clear() correctnesscheckpoint_stack.clear() correctnessRequired by:
Complements:
Scenario 1: Partial Finalization (Draft cleared, insert failed)
// User types contract definition
engine.insert_str(ctx, "important_contract").unwrap();
engine.finalize(ctx); // Suppose insert_with_value() fails internally
// VIOLATION: Draft cleared but term not in dictionary
// - User's work is LOST (draft gone)
// - Symbol NEVER appears in completions
// - No way to recover
Scenario 2: Race Condition (Query during finalization)
// Thread 1: User hits Enter (finalize)
engine.finalize(ctx); // In progress...
// Thread 2: User types in different scope (query)
let results = engine.complete(other_ctx, "important", 5);
// VIOLATION: Query sees BOTH draft AND finalized term
// - Duplicate "important_contract" in completion list
// - Confusing UX (why is same symbol shown twice?)
Scenario 1: All-or-Nothing Success
engine.insert_str(ctx, "important_contract").unwrap();
let result = engine.finalize(ctx);
if result.is_ok() {
// ALL post-conditions hold:
assert!(!engine.has_draft(ctx)); // ✓
assert_eq!(engine.checkpoint_count(ctx), 0); // ✓
assert!(engine.has_term("important_contract")); // ✓
}
Scenario 2: All-or-Nothing Failure
engine.insert_str(ctx, "").unwrap(); // Empty draft
let result = engine.finalize(ctx);
assert!(result.is_err());
// State unchanged (draft still exists for undo):
assert!(engine.has_draft(ctx)); // ✓ User can continue editing
Scenario 3: Concurrent Access (No Duplicates)
// Thread 1: finalize() in progress
// Thread 2: complete() query
// Query sees EITHER:
// - Draft (finalize not yet started) → 1 result
// - Finalized (finalize completed) → 1 result
// NEVER both or neither
File: rocq/liblevenshtein/ContextualCompletion/Finalization.v
Concurrency Model:
(* Model Rust Mutex semantics *)
Axiom mutex_acquire : forall (m : Mutex) (thread : ThreadId), unit.
Axiom mutex_release : forall (m : Mutex) (thread : ThreadId), unit.
(* Sequential consistency *)
Axiom mutex_critical_section :
forall (m : Mutex) (t1 t2 : ThreadId) (op1 op2 : Operation),
in_critical_section m t1 op1 ->
in_critical_section m t2 op2 ->
t1 <> t2 ->
happens_before op1 op2 \/ happens_before op2 op1.
Atomicity Proof:
Theorem finalization_atomicity : (* ... *).
Proof.
intros state state' ctx term Hvalid Hdraft Hterm Hfinalize.
unfold finalize in Hfinalize.
(* Part 1-5: Functional correctness *)
destruct Hfinalize as [Hclear_draft [Hclear_ckpt [Hinsert [Hutf8 _]]]].
split. apply Hclear_draft.
split. apply Hclear_ckpt.
split. apply Hinsert.
split. apply Hutf8.
(* Part 6: Atomicity *)
intros obs_state Hobs.
apply mutex_critical_section_lemma in Hobs.
destruct Hobs.
- left. (* Observer acquired lock before finalize *)
apply lock_ordering_preserves_old_state. assumption.
- right. (* Observer acquired lock after finalize *)
apply lock_ordering_sees_new_state. assumption.
Qed.
Current: Manual lock management Candidate: Transaction wrapper
pub trait Transaction {
type Output;
type Error;
fn execute(&mut self) -> Result<Self::Output, Self::Error>;
fn rollback(&mut self);
}
impl Transaction for FinalizeTransaction {
fn execute(&mut self) -> Result<String, ContextError> {
// BEGIN TRANSACTION
let term = self.draft.extract()?; // Step 1
self.draft.clear()?; // Step 2
self.dict.insert(&term, self.ctx)?; // Step 3
self.checkpoints.clear()?; // Step 4
// COMMIT TRANSACTION
Ok(term)
}
fn rollback(&mut self) {
// Restore original state if any step fails
}
}
Benefits:
Last Updated: 2025-01-21 Review trigger: Reconcile this page when a checked contextual Rocq module is added.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |