cluxion-agentplugin-preprocessing 0.3.10__tar.gz → 0.3.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/PKG-INFO +1 -1
  2. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/pyproject.toml +1 -1
  3. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/Cargo.lock +12 -1
  4. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/Cargo.toml +2 -1
  5. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/pyproject.toml +1 -1
  6. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/dispatch.rs +93 -66
  7. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/queue.rs +14 -5
  8. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/__init__.py +1 -1
  9. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/doctor/probes.py +20 -7
  10. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/.github/profile/README.md +0 -0
  11. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/.gitignore +0 -0
  12. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/Docs/README.md +0 -0
  13. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/LICENSE +0 -0
  14. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/README.md +0 -0
  15. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/adapters/claude/.claude-plugin/plugin.json +0 -0
  16. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/adapters/claude/skills/preprocess/SKILL.md +0 -0
  17. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/adapters/codex/config-snippet.toml +0 -0
  18. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/cluxion-Docs/README.md +0 -0
  19. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/cluxion-Docs/architecture.md +0 -0
  20. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/cluxion-Docs/harness-logic.md +0 -0
  21. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/cluxion-Docs/honesty-preprocessing.md +0 -0
  22. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/cluxion-Docs/install-and-operations.md +0 -0
  23. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/cluxion-Docs/security.md +0 -0
  24. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/context.rs +0 -0
  25. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/guard.rs +0 -0
  26. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/lib.rs +0 -0
  27. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/main.rs +0 -0
  28. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/rust/cluxion_queue/src/types.rs +0 -0
  29. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/cli.py +0 -0
  30. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/doctor/__init__.py +0 -0
  31. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/doctor/catalog.json +0 -0
  32. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/doctor/framework.py +0 -0
  33. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/guard_watch.py +0 -0
  34. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/hermes_config.py +0 -0
  35. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/plugin.py +0 -0
  36. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/plugin.yaml +0 -0
  37. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/runner.py +0 -0
  38. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_agentplugin_preprocessing/schemas.py +0 -0
  39. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/__init__.py +0 -0
  40. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/__main__.py +0 -0
  41. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/adapters/__init__.py +0 -0
  42. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/adapters/contract.py +0 -0
  43. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/adapters/grok_build.py +0 -0
  44. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/adapters/hermes.py +0 -0
  45. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/adapters/spec.py +0 -0
  46. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/bootstrap.py +0 -0
  47. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/cli.py +0 -0
  48. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/__init__.py +0 -0
  49. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/clarification.py +0 -0
  50. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/context_compress.py +0 -0
  51. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/dispatch_store.py +0 -0
  52. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/harness.py +0 -0
  53. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/intent.py +0 -0
  54. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/ledger.py +0 -0
  55. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/ledger_codec.py +0 -0
  56. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/plan_codec.py +0 -0
  57. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/preprocess.py +0 -0
  58. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/types.py +0 -0
  59. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/core/work_queue.py +0 -0
  60. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/guard_daemon_host.py +0 -0
  61. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/models/__init__.py +0 -0
  62. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/models/supervisor.py +0 -0
  63. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/models/vllm_mlx.py +0 -0
  64. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/resources/__init__.py +0 -0
  65. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/resources/guard_bridge.py +0 -0
  66. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/resources/py_queue.py +0 -0
  67. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/resources/queue_bridge.py +0 -0
  68. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/resources/rust_bridge.py +0 -0
  69. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/web/__init__.py +0 -0
  70. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/src/cluxion_runtime/web/browser_bridge.py +0 -0
  71. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_browser_bridge.py +0 -0
  72. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_clarification.py +0 -0
  73. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_cluxion_runtime_spine.py +0 -0
  74. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_context_compress.py +0 -0
  75. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_contract.py +0 -0
  76. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_dispatch_store.py +0 -0
  77. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_guard.py +0 -0
  78. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_ledger.py +0 -0
  79. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_py_queue_concurrency.py +0 -0
  80. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_queue_backends.py +0 -0
  81. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_runtime_adapter_cli.py +0 -0
  82. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_rust_queue.py +0 -0
  83. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/runtime/test_supervisor.py +0 -0
  84. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_bootstrap.py +0 -0
  85. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_doctor.py +0 -0
  86. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_guard_watch.py +0 -0
  87. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_hermes_config.py +0 -0
  88. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_packaging_policy.py +0 -0
  89. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_plugin.py +0 -0
  90. {cluxion_agentplugin_preprocessing-0.3.10 → cluxion_agentplugin_preprocessing-0.3.12}/tests/test_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cluxion-agentplugin-preprocessing
3
- Version: 0.3.10
3
+ Version: 0.3.12
4
4
  Summary: Universal agent plugin for Cluxion preprocessing, honesty contracts, clarification, Rust work queue, and resource-aware harness handoff.
5
5
  Project-URL: Homepage, https://github.com/cluxion/cluxion-Agentplugin-preprocessing
6
6
  Project-URL: Repository, https://github.com/cluxion/cluxion-Agentplugin-preprocessing
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cluxion-agentplugin-preprocessing"
7
- version = "0.3.10"
7
+ version = "0.3.12"
8
8
  description = "Universal agent plugin for Cluxion preprocessing, honesty contracts, clarification, Rust work queue, and resource-aware harness handoff."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -53,8 +53,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
53
53
 
54
54
  [[package]]
55
55
  name = "cluxion_queue"
56
- version = "0.2.0"
56
+ version = "0.2.1"
57
57
  dependencies = [
58
+ "fs2",
58
59
  "pyo3",
59
60
  "rusqlite",
60
61
  "serde",
@@ -148,6 +149,16 @@ version = "0.1.9"
148
149
  source = "registry+https://github.com/rust-lang/crates.io-index"
149
150
  checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
150
151
 
152
+ [[package]]
153
+ name = "fs2"
154
+ version = "0.4.3"
155
+ source = "registry+https://github.com/rust-lang/crates.io-index"
156
+ checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
157
+ dependencies = [
158
+ "libc",
159
+ "winapi",
160
+ ]
161
+
151
162
  [[package]]
152
163
  name = "generic-array"
153
164
  version = "0.14.7"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cluxion_queue"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  edition = "2021"
5
5
  description = "Durable work queue and dispatch store for Cluxion agent preprocessing"
6
6
 
@@ -20,6 +20,7 @@ rusqlite = { version = "0.32", features = ["bundled"] }
20
20
  sysinfo = "0.33"
21
21
  thiserror = "2"
22
22
  pyo3 = { version = "0.23", features = ["abi3-py311"], optional = true }
23
+ fs2 = "0.4"
23
24
 
24
25
  [features]
25
26
  default = []
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "cluxion-queue-native"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "In-process Rust queue engine for cluxion-agentplugin-preprocessing"
9
9
  requires-python = ">=3.11"
10
10
  license = "Apache-2.0"
@@ -1,6 +1,7 @@
1
- use std::fs;
1
+ use std::fs::{self, OpenOptions};
2
2
  use std::path::{Path, PathBuf};
3
3
 
4
+ use fs2::FileExt;
4
5
  use serde_json::{json, Value};
5
6
 
6
7
  use crate::types::{ok_payload, require_str, QueueError};
@@ -14,49 +15,55 @@ pub fn persist_bundle(store_dir: &Path, payload: &Value) -> Result<Value, QueueE
14
15
  let dispatch_dir = dispatch_dir(store_dir);
15
16
  fs::create_dir_all(&dispatch_dir)?;
16
17
  let path = bundle_path(&dispatch_dir, work_id)?;
17
- write_atomic_json(&path, &bundle)?;
18
- Ok(ok_payload(json!({
19
- "stored": true,
20
- "path": path.to_string_lossy(),
21
- })))
18
+ with_dispatch_lock(&dispatch_dir, || {
19
+ write_atomic_json(&path, &bundle)?;
20
+ Ok(ok_payload(json!({
21
+ "stored": true,
22
+ "path": path.to_string_lossy(),
23
+ })))
24
+ })
22
25
  }
23
26
 
24
27
  pub fn next_step(store_dir: &Path, payload: &Value) -> Result<Value, QueueError> {
25
28
  let work_id = require_str(payload, "work_id")?;
26
- let path = bundle_path(&dispatch_dir(store_dir), work_id)?;
27
- let mut bundle = read_bundle(&path)?;
28
- let mut selected: Option<Value> = None;
29
- {
30
- let steps = steps_mut(&mut bundle)?;
31
- for step in steps.iter_mut() {
32
- let status = step.get("status").and_then(Value::as_str).unwrap_or("");
33
- if status == "queued" || status == "retry_wait" {
34
- step["status"] = json!("running");
35
- step["updated_at"] = json!(now_secs());
36
- selected = Some(public_step(step));
37
- break;
29
+ let dispatch_dir = dispatch_dir(store_dir);
30
+ fs::create_dir_all(&dispatch_dir)?;
31
+ let path = bundle_path(&dispatch_dir, work_id)?;
32
+ with_dispatch_lock(&dispatch_dir, || {
33
+ let mut bundle = read_bundle(&path)?;
34
+ let mut selected: Option<Value> = None;
35
+ {
36
+ let steps = steps_mut(&mut bundle)?;
37
+ for step in steps.iter_mut() {
38
+ let status = step.get("status").and_then(Value::as_str).unwrap_or("");
39
+ if status == "queued" || status == "retry_wait" {
40
+ step["status"] = json!("running");
41
+ step["updated_at"] = json!(now_secs());
42
+ selected = Some(public_step(step));
43
+ break;
44
+ }
38
45
  }
39
46
  }
40
- }
41
- if let Some(step) = selected {
42
- write_atomic_json(&path, &bundle)?;
43
- let remaining = remaining_count(steps_ref(&bundle)?);
44
- return Ok(ok_payload(json!({
47
+ if let Some(step) = selected {
48
+ write_atomic_json(&path, &bundle)?;
49
+ let remaining = remaining_count(steps_ref(&bundle)?);
50
+ return Ok(ok_payload(json!({
51
+ "work_id": work_id,
52
+ "ready": true,
53
+ "step": step,
54
+ "remaining": remaining,
55
+ "synthesis_ready": false,
56
+ })));
57
+ }
58
+ let steps = steps_ref(&bundle)?;
59
+ Ok(ok_payload(json!({
45
60
  "work_id": work_id,
46
- "ready": true,
47
- "step": step,
48
- "remaining": remaining,
49
- "synthesis_ready": false,
50
- })));
51
- }
52
- let steps = steps_ref(&bundle)?;
53
- Ok(ok_payload(json!({
54
- "work_id": work_id,
55
- "ready": false,
56
- "step": json!({}),
57
- "remaining": remaining_count(steps),
58
- "synthesis_ready": steps.iter().all(|step| step.get("status") == Some(&json!("succeeded"))),
59
- })))
61
+ "ready": false,
62
+ "step": json!({}),
63
+ "remaining": remaining_count(steps),
64
+ "synthesis_ready": steps.iter().all(|step| step.get("status") == Some(&json!("succeeded"))),
65
+ })))
66
+ })
60
67
  }
61
68
 
62
69
  pub fn record_step(store_dir: &Path, payload: &Value) -> Result<Value, QueueError> {
@@ -68,37 +75,41 @@ pub fn record_step(store_dir: &Path, payload: &Value) -> Result<Value, QueueErro
68
75
  .get("failed")
69
76
  .and_then(Value::as_bool)
70
77
  .unwrap_or(false);
71
- let path = bundle_path(&dispatch_dir(store_dir), work_id)?;
72
- let mut bundle = read_bundle(&path)?;
73
- let mut recorded_status = None;
74
- {
75
- let steps = steps_mut(&mut bundle)?;
76
- for step in steps.iter_mut() {
77
- if step.get("step_id") == Some(&json!(step_id)) {
78
- step["status"] = json!(if failed { "failed" } else { "succeeded" });
79
- step["result"] = json!(result);
80
- step["error"] = json!(error);
81
- step["updated_at"] = json!(now_secs());
82
- recorded_status = step.get("status").cloned();
83
- break;
78
+ let dispatch_dir = dispatch_dir(store_dir);
79
+ fs::create_dir_all(&dispatch_dir)?;
80
+ let path = bundle_path(&dispatch_dir, work_id)?;
81
+ with_dispatch_lock(&dispatch_dir, || {
82
+ let mut bundle = read_bundle(&path)?;
83
+ let mut recorded_status = None;
84
+ {
85
+ let steps = steps_mut(&mut bundle)?;
86
+ for step in steps.iter_mut() {
87
+ if step.get("step_id") == Some(&json!(step_id)) {
88
+ step["status"] = json!(if failed { "failed" } else { "succeeded" });
89
+ step["result"] = json!(result);
90
+ step["error"] = json!(error);
91
+ step["updated_at"] = json!(now_secs());
92
+ recorded_status = step.get("status").cloned();
93
+ break;
94
+ }
84
95
  }
85
96
  }
86
- }
87
- if let Some(status) = recorded_status {
88
- write_atomic_json(&path, &bundle)?;
89
- let steps = steps_ref(&bundle)?;
90
- return Ok(ok_payload(json!({
91
- "work_id": work_id,
92
- "step_id": step_id,
93
- "recorded": true,
94
- "status": status,
95
- "remaining": remaining_count(steps),
96
- "synthesis_ready": steps.iter().all(|item| item.get("status") == Some(&json!("succeeded"))),
97
- })));
98
- }
99
- Err(QueueError::Store(format!(
100
- "dispatch step not found: {work_id}/{step_id}"
101
- )))
97
+ if let Some(status) = recorded_status {
98
+ write_atomic_json(&path, &bundle)?;
99
+ let steps = steps_ref(&bundle)?;
100
+ return Ok(ok_payload(json!({
101
+ "work_id": work_id,
102
+ "step_id": step_id,
103
+ "recorded": true,
104
+ "status": status,
105
+ "remaining": remaining_count(steps),
106
+ "synthesis_ready": steps.iter().all(|item| item.get("status") == Some(&json!("succeeded"))),
107
+ })));
108
+ }
109
+ Err(QueueError::Store(format!(
110
+ "dispatch step not found: {work_id}/{step_id}"
111
+ )))
112
+ })
102
113
  }
103
114
 
104
115
  pub fn build_brief(store_dir: &Path, payload: &Value) -> Result<Value, QueueError> {
@@ -133,6 +144,22 @@ pub fn build_brief(store_dir: &Path, payload: &Value) -> Result<Value, QueueErro
133
144
  })))
134
145
  }
135
146
 
147
+ fn with_dispatch_lock<T>(
148
+ dispatch_dir: &Path,
149
+ f: impl FnOnce() -> Result<T, QueueError>,
150
+ ) -> Result<T, QueueError> {
151
+ let lock_path = dispatch_dir.join(".dispatch.lock");
152
+ let lockfile = OpenOptions::new()
153
+ .read(true)
154
+ .write(true)
155
+ .create(true)
156
+ .open(&lock_path)?;
157
+ lockfile.lock_exclusive()?;
158
+ let result = f();
159
+ let _ = lockfile.unlock();
160
+ result
161
+ }
162
+
136
163
  fn dispatch_dir(store_dir: &Path) -> PathBuf {
137
164
  store_dir.join("dispatch")
138
165
  }
@@ -95,6 +95,8 @@ pub fn enqueue(store_dir: &Path, payload: &Value) -> Result<Value, QueueError> {
95
95
  pub fn dequeue(store_dir: &Path, _payload: &Value) -> Result<Value, QueueError> {
96
96
  let now = now_secs();
97
97
  with_db(store_dir, |conn| {
98
+ // Use BEGIN IMMEDIATE for atomic SELECT-then-UPDATE claim
99
+ conn.execute_batch("BEGIN IMMEDIATE;")?;
98
100
  let row = conn.query_row(
99
101
  "SELECT work_id, prompt, surface, priority, metadata_json
100
102
  FROM work_queue
@@ -118,6 +120,7 @@ pub fn dequeue(store_dir: &Path, _payload: &Value) -> Result<Value, QueueError>
118
120
  "UPDATE work_queue SET status='running', updated_at=?2 WHERE work_id=?1",
119
121
  params![work_id, now],
120
122
  )?;
123
+ conn.execute_batch("COMMIT;")?;
121
124
  Ok(ok_payload(json!({
122
125
  "ready": true,
123
126
  "item": {
@@ -129,11 +132,17 @@ pub fn dequeue(store_dir: &Path, _payload: &Value) -> Result<Value, QueueError>
129
132
  }
130
133
  })))
131
134
  }
132
- Err(rusqlite::Error::QueryReturnedNoRows) => Ok(ok_payload(json!({
133
- "ready": false,
134
- "item": Value::Null,
135
- }))),
136
- Err(err) => Err(QueueError::Sqlite(err)),
135
+ Err(rusqlite::Error::QueryReturnedNoRows) => {
136
+ conn.execute_batch("COMMIT;")?;
137
+ Ok(ok_payload(json!({
138
+ "ready": false,
139
+ "item": Value::Null,
140
+ })))
141
+ }
142
+ Err(err) => {
143
+ let _ = conn.execute_batch("ROLLBACK;");
144
+ Err(QueueError::Sqlite(err))
145
+ }
137
146
  }
138
147
  })
139
148
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.3.10"
5
+ __version__ = "0.3.12"
6
6
 
7
7
  __all__ = ["__version__"]
@@ -252,16 +252,29 @@ def abi3_wheel_compatible(ctx: DoctorContext) -> tuple[str, str]:
252
252
  @_register("sqlite_wal_mode_compatible")
253
253
  def sqlite_wal_mode_compatible(ctx: DoctorContext) -> tuple[str, str]:
254
254
  try:
255
+ import contextlib
256
+ import os
255
257
  import sqlite3
256
- conn = sqlite3.connect(":memory:")
258
+ import tempfile
259
+
260
+ # WAL is a file-database feature; an in-memory db always reports "memory",
261
+ # so probe on a real temp file (mirrors how the queue/forgetforge dbs run).
262
+ fd, path = tempfile.mkstemp(suffix=".sqlite", prefix="cluxion-doctor-")
263
+ os.close(fd)
257
264
  try:
258
- mode = conn.execute("PRAGMA journal_mode=WAL").fetchone()[0]
259
- ver = sqlite3.sqlite_version
260
- if str(mode).lower() == "wal":
261
- return "pass", f"wal supported (sqlite {ver})"
262
- return "warn", f"got {mode} (sqlite {ver})"
265
+ conn = sqlite3.connect(path)
266
+ try:
267
+ mode = conn.execute("PRAGMA journal_mode=WAL").fetchone()[0]
268
+ ver = sqlite3.sqlite_version
269
+ if str(mode).lower() == "wal":
270
+ return "pass", f"wal supported (sqlite {ver})"
271
+ return "warn", f"got {mode} (sqlite {ver})"
272
+ finally:
273
+ conn.close()
263
274
  finally:
264
- conn.close()
275
+ for suffix in ("", "-wal", "-shm"):
276
+ with contextlib.suppress(OSError):
277
+ os.unlink(path + suffix)
265
278
  except Exception as e:
266
279
  return "skip", f"uncertainty: {type(e).__name__}"
267
280