fix(plugins): Python plugins see the host document again

Plugins read the host's document through a shared reference across the
dynamic-library boundary, and the entity index hashed with foldhash's
per-process seed, so every handle lookup from the plugin missed. The
codec now uses a fixed hasher (opencadcodec f385f75; kernel 9841749,
graph e9c23ad).

The Python IPC suite also caught up with intended changes: ATTDEF
renders its tag, DXF now keeps the lock-position flag, and the modify
audit turns associative arrays off since it checks loose copies.
The rate-limiter test waits for the host's check instead of a fixed
sleep that raced on macOS, and ENV_LOCK no longer cascades a poisoned
lock into every later test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-15 12:54:03 +03:00
1 parent c48fac7ae9
commit ee2898095e
11 files changed
+43 -42

No files matched your search

+2 -2
View File
@@ -15,7 +15,7 @@ serde_json = { version = "1", optional = true }
# Pulled in only by the `host` feature, which adds the `opencadcodec`-typed
# `HostApi` runtime surface. The default crate stays dependency-free so engine
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d36331d", optional = true, features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "f385f75", optional = true, features = ["serde"] }
# Runtime IPC and serialization (host feature only).
interprocess = { version = "2", optional = true }
@@ -39,7 +39,7 @@ serde_json = "1"
serde = { version = "1", features = ["derive"] }
cargo-lock = "11"
# opencadcodec is scanned at build time to generate the embedded type registry.
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d36331d", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "f385f75", features = ["serde"] }
[dev-dependencies]
serde_json = "1"
+2 -2
View File
@@ -1210,7 +1210,7 @@ mod tests {
#[test]
fn v4_client_notification_queue_size_env_is_honored() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev = std::env::var("OCS_PLUGIN_NOTIFY_QUEUE_SIZE").ok();
std::env::set_var("OCS_PLUGIN_NOTIFY_QUEUE_SIZE", "7");
assert_eq!(notify_queue_size(), 7);
@@ -1222,7 +1222,7 @@ mod tests {
#[test]
fn v4_client_notification_queue_drops_newest_on_overflow() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev = std::env::var("OCS_PLUGIN_NOTIFY_QUEUE_SIZE").ok();
std::env::set_var("OCS_PLUGIN_NOTIFY_QUEUE_SIZE", "1");
let (host_stream, mut runner_stream) = connect_pair();
+5 -5
View File
@@ -108,14 +108,14 @@ mod tests {
#[test]
fn effective_max_defaults_to_api_version() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var(MAX_API_VERSION_ENV);
assert_eq!(effective_max_api_version(), API_VERSION);
}
#[test]
fn effective_max_can_disable_v4() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::set_var(MAX_API_VERSION_ENV, "3");
assert_eq!(effective_max_api_version(), 3);
assert!(!host_accepts_plugin_version(4));
@@ -126,7 +126,7 @@ mod tests {
#[test]
fn effective_max_below_minimum_is_v3_only_mode() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::set_var(MAX_API_VERSION_ENV, "2");
assert_eq!(effective_max_api_version(), 3);
assert!(!host_accepts_plugin_version(4));
@@ -137,7 +137,7 @@ mod tests {
#[test]
fn effective_max_clamps_to_supported_range() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::set_var(MAX_API_VERSION_ENV, "99");
assert_eq!(effective_max_api_version(), API_VERSION);
std::env::set_var(MAX_API_VERSION_ENV, "1");
@@ -147,7 +147,7 @@ mod tests {
#[test]
fn host_accepts_supported_range() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var(MAX_API_VERSION_ENV);
assert!(host_accepts_plugin_version(API_VERSION_MIN_SUPPORTED));
assert!(host_accepts_plugin_version(API_VERSION));
+4 -4
View File
@@ -1497,7 +1497,7 @@ mod timeout_tests {
#[test]
fn runner_handshake_correct_token_is_accepted() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var("OCS_PLUGIN_MAX_API_VERSION");
let result = verify_runner_handshake(
RunnerHandshake::Token("correct-token".to_string()),
@@ -1512,7 +1512,7 @@ mod timeout_tests {
#[test]
fn runner_handshake_tokenv4_selects_v4_path() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var("OCS_PLUGIN_MAX_API_VERSION");
let result = verify_runner_handshake(
RunnerHandshake::TokenV4 {
@@ -1530,7 +1530,7 @@ mod timeout_tests {
#[test]
fn runner_handshake_tokenv4_mismatched_version_is_rejected() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::remove_var("OCS_PLUGIN_MAX_API_VERSION");
let result = verify_runner_handshake(
RunnerHandshake::TokenV4 {
@@ -1547,7 +1547,7 @@ mod timeout_tests {
#[test]
fn runner_handshake_tokenv4_rejected_when_v4_disabled() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
std::env::set_var("OCS_PLUGIN_MAX_API_VERSION", "3");
let result = verify_runner_handshake(
RunnerHandshake::TokenV4 {
+13 -11
View File
@@ -482,7 +482,7 @@ mod tests {
#[test]
fn execute_code_timeout_floor_is_at_least_60s_and_env_override() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let prev_call_timeout = std::env::var("OCS_PLUGIN_CALL_TIMEOUT_SECS").ok();
let prev_test_floor = std::env::var("OCS_PLUGIN_TEST_FLOOR_SECS").ok();
let prev_execute_timeout = std::env::var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS").ok();
@@ -519,7 +519,7 @@ mod tests {
#[test]
fn v4_connection_request_response() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
let (host_stream, mut runner_stream) = connect_pair();
let handler: Arc<dyn Fn(Option<u64>, PluginNotification) + Send + Sync> =
@@ -565,7 +565,7 @@ mod tests {
#[test]
fn v4_connection_handles_nested_plugin_request() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
let (host_stream, mut runner_stream) = connect_pair();
let handler: Arc<dyn Fn(Option<u64>, PluginNotification) + Send + Sync> =
@@ -635,7 +635,7 @@ mod tests {
#[test]
fn v4_connection_routes_entity_transaction_over_nested_ipc() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
let (host_stream, mut runner_stream) = connect_pair();
let handler: Arc<dyn Fn(Option<u64>, PluginNotification) + Send + Sync> =
@@ -705,7 +705,7 @@ mod tests {
#[test]
fn v4_connection_notification_invokes_handler() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
use crate::host::LogLevel;
@@ -744,7 +744,7 @@ mod tests {
#[test]
fn v4_connection_handler_panic_is_isolated() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
use crate::host::LogLevel;
@@ -792,7 +792,7 @@ mod tests {
#[test]
fn v4_connection_notification_rate_limiter_drops_excess() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
std::env::set_var("OCS_PLUGIN_NOTIFY_RATE_LIMIT", "2");
use crate::host::LogLevel;
@@ -805,6 +805,7 @@ mod tests {
*received2.lock().unwrap() += 1;
});
let conn = V4Connection::new(host_stream, handler).unwrap();
let (checked_tx, checked_rx) = mpsc::channel::<()>();
let runner = thread::spawn(move || {
for _ in 0..10 {
@@ -820,9 +821,9 @@ mod tests {
)
.unwrap();
}
// Keep the stream open long enough for the host to verify the
// reader thread is still alive after the flood.
std::thread::sleep(Duration::from_millis(300));
// Keep the stream open until the host has checked the reader
// thread survived the flood; a fixed sleep raced on slow runners.
let _ = checked_rx.recv_timeout(Duration::from_secs(30));
});
std::thread::sleep(Duration::from_millis(200));
@@ -835,6 +836,7 @@ mod tests {
conn.is_alive(),
"connection should survive rate-limited flood"
);
checked_tx.send(()).unwrap();
runner.join().unwrap();
drop(conn);
std::env::remove_var("OCS_PLUGIN_NOTIFY_RATE_LIMIT");
@@ -843,7 +845,7 @@ mod tests {
#[test]
fn execute_code_returns_delayed_result_with_tab_index() {
let _guard = ENV_LOCK.lock().unwrap();
let _guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
set_test_env();
let (host_stream, mut runner_stream) = connect_pair();
+1 -1
View File
@@ -10,6 +10,6 @@ publish = false
# exactly one implementation. No UI/app dependencies — only the codec + serde.
[dependencies]
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d36331d", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "f385f75", features = ["serde"] }
bincode = "1.3"
serde = { version = "1", features = ["derive"] }
+1 -1
View File
@@ -8,7 +8,7 @@ publish = false
crate-type = ["cdylib"]
[dependencies]
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "d36331d", features = ["serde"] }
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "f385f75", features = ["serde"] }
bincode = "1.3"
serde = { version = "1", features = ["derive"] }
console_error_panic_hook = "0.1"