Skip to content

Commit f612c94

Browse files
runtime+context: Arm the external-taint fence around coordination
Prevent failures during Agent Trace DB acquisition and the protected mutation-trace boundary from losing the worktree-local durability signal. Reshape `coordinate()` to accept a DB provider, arm the marker after acquiring the worktree lock and before checkout identity or DB access, clear it only after success, and report marker and DB-provider failures explicitly. Add end-to-end coverage for success, failure, and fail-closed marker arming, and document the fence ordering and concrete marker refinement. Plan: mutation-cursor-external-taint (T02) Co-authored-by: SCE <sce@crocoder.dev>
1 parent 5e404c5 commit f612c94

8 files changed

Lines changed: 615 additions & 125 deletions

File tree

cli/src/services/mutation_trace/runtime/coordinator.rs

Lines changed: 379 additions & 26 deletions
Large diffs are not rendered by default.

cli/src/services/mutation_trace/runtime/tests.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,20 @@ fn linked_worktrees_have_independent_locks_and_worktree_ids() {
8181
"a linked worktree must resolve its own worktree-specific git dir, giving it a distinct lock and identity path"
8282
);
8383

84-
let main_outcome = coordinate(&main_root, &db_main, &RuntimeBoundary::Flush)
85-
.expect("first observation on the main worktree should succeed");
84+
let main_outcome = coordinate(&main_root, &RuntimeBoundary::Flush, || {
85+
RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&db_path)
86+
})
87+
.expect("first observation on the main worktree should succeed");
8688

8789
let held = WorktreeLock::acquire(&main_git_dir, Duration::from_secs(5))
8890
.expect("the main worktree's runtime lock should be acquirable");
8991

90-
let db_linked = RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&db_path).expect(
91-
"a second handle to the same caller-supplied repository-scoped DB path should open",
92-
);
93-
let linked_outcome = coordinate(&linked_root, &db_linked, &RuntimeBoundary::Flush).expect(
92+
let linked_outcome = coordinate(&linked_root, &RuntimeBoundary::Flush, || {
93+
// A second handle to the same caller-supplied repository-scoped DB path,
94+
// opened through the provider while the main worktree's lock is held.
95+
RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&db_path)
96+
})
97+
.expect(
9498
"coordinate() on the linked worktree must acquire its own distinct runtime lock while the main worktree's lock is still held",
9599
);
96100

@@ -139,8 +143,8 @@ fn agent_trace_storage_and_coordinator_observe_the_same_checkout_id() {
139143
std::fs::create_dir_all(&state_root).expect("state root should be created");
140144

141145
let coordinator_db_path = repo_root.join("coordinator.db");
142-
let db = RepositoryAgentTraceDb::new_at(&coordinator_db_path)
143-
.expect("the coordinator's repository DB should open");
146+
RepositoryAgentTraceDb::new_at(&coordinator_db_path)
147+
.expect("the coordinator's repository DB should open with schema");
144148

145149
let barrier = Arc::new(Barrier::new(2));
146150

@@ -162,8 +166,10 @@ fn agent_trace_storage_and_coordinator_observe_the_same_checkout_id() {
162166
};
163167

164168
barrier.wait();
165-
let outcome = coordinate(&repo_root, &db, &RuntimeBoundary::Flush)
166-
.expect("the coordinator's first observation should succeed");
169+
let outcome = coordinate(&repo_root, &RuntimeBoundary::Flush, || {
170+
RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&coordinator_db_path)
171+
})
172+
.expect("the coordinator's first observation should succeed");
167173

168174
let storage_checkout_id = storage_thread
169175
.join()
@@ -193,8 +199,10 @@ fn a_snapshot_failure_then_recovery_cycle_runs_through_the_public_api() {
193199
let db_path = repo_root.join("agent-trace.db");
194200
let db = RepositoryAgentTraceDb::new_at(&db_path).expect("repository DB should open");
195201

196-
let baseline = coordinate(&repo_root, &db, &RuntimeBoundary::Flush)
197-
.expect("the baseline observation should materialize the worktree");
202+
let baseline = coordinate(&repo_root, &RuntimeBoundary::Flush, || {
203+
RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&db_path)
204+
})
205+
.expect("the baseline observation should materialize the worktree");
198206
let worktree_id = baseline.worktree_id.clone();
199207

200208
let git_dir = resolve_git_dir(&repo_root).expect("git dir should resolve");
@@ -207,12 +215,12 @@ fn a_snapshot_failure_then_recovery_cycle_runs_through_the_public_api() {
207215
let scope = ScopeId("scope-recovery".to_string());
208216
let failure = coordinate(
209217
&repo_root,
210-
&db,
211218
&RuntimeBoundary::Start {
212219
scope: scope.clone(),
213220
event: EventId("evt-during-failure".to_string()),
214221
actor_kind: ActorKind::ClaudeCode,
215222
},
223+
|| RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&db_path),
216224
)
217225
.expect_err("a Git snapshot failure against a materialized worktree should be reported");
218226
match failure {
@@ -240,8 +248,10 @@ fn a_snapshot_failure_then_recovery_cycle_runs_through_the_public_api() {
240248
std::fs::remove_file(&tmp_index_dir)
241249
.expect("removing the planted file should let the snapshot service recreate its temp dir");
242250

243-
let recovered = coordinate(&repo_root, &db, &RuntimeBoundary::Flush)
244-
.expect("the coordinator should recover from the taint and process the boundary");
251+
let recovered = coordinate(&repo_root, &RuntimeBoundary::Flush, || {
252+
RepositoryAgentTraceDb::open_for_hooks_without_migrations_at(&db_path)
253+
})
254+
.expect("the coordinator should recover from the taint and process the boundary");
245255
assert_eq!(
246256
recovered.worktree_id, worktree_id,
247257
"recovery must operate on the same worktree identity"

context/cli/mutation-trace-external-taint.md

Lines changed: 70 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

context/cli/mutation-trace-protocol.md

Lines changed: 17 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)