Add thread attachment operations with coordinated deletion (#44350)

## Why

Attachment mutations must coordinate with thread deletion so queued requests cannot use metadata belonging to a deleted thread.

## What changed

- Add typed `ThreadStore` operations for idempotent attachment creation, paginated listing, and removal, with capability detection and unsupported defaults.
- Implement attachment operations for SQLite-backed `LocalThreadStore`, including unloaded threads, and preserve invalid-request and missing-thread errors.
- Coordinate attachment mutations with lifecycle locks and move SQLite cleanup into thread-store deletion. Remove state rows only after rollout deletion, preserving retries after cleanup failures.
- Give the in-memory store ownership of its associated SQLite cleanup and remove duplicate cleanup from the app-server.

## Testing

Add coverage for attachment pagination and idempotency, unsupported stores, error categories, lifecycle exclusion, mutations queued behind single and batch deletion, cleanup failure retries, and in-memory state cleanup. Extend the app-server descendant deletion test to verify SQLite rows are removed.

GitOrigin-RevId: f0157538c7c5c2ee1362ce40eac157101c5d4505
This commit is contained in:
joeytrasatti-openai authored and copyberry committed 2026-09-10 00:30:27 +00:00
1 parent e444aa99d7
commit 2df0b747ba
11 files changed
+834 -20

No files matched your search

@@ -149,6 +149,7 @@ async fn thread_delete_deletes_spawned_descendants() -> Result<()> {
assert_eq!(deleted_ids, vec![grandchild_id, child_id, parent_id]);
for thread_id in [parent_thread_id, child_thread_id, grandchild_thread_id] {
assert_eq!(state_db.get_thread(thread_id).await?, None);
let rollout_path = find_thread_path_by_id_str(
codex_home.path(),
&thread_id.to_string(),