From e75b7cd0fab769d23a88e34cfb36b3c4dea3b3a3 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:02:56 -0700 Subject: [PATCH 1/4] Make the build pass again: formatting, a lint, and two broken tests CI has been red on main since at least 20 September, and because `cargo fmt --check` is the first step of the job, everything after it went unseen. Four separate problems were stacked behind it. Formatting had drifted across the workspace and the fuzz targets, 121 diffs in all. Applied with the same commands the workflow runs, stable for the workspace and nightly for fuzz. `initial_origin` checked two options with `is_some` and then unwrapped one, which `clippy::unnecessary_unwrap` rejects under `-D warnings`. It now matches both options at once, which is the same behaviour. `tests/live_news.rs` included its card from `../../../../tools/octoscript-research/live-news.card`, a path outside the repository pointing at a directory that does not exist in any checkout; the file actually lives in Octoscript-AppCard under an older name. The card is now a test fixture beside the others in `tests/fixtures/`, which is how every neighbouring test already loads one, so the test compiles anywhere. `tests/nav_waypoint.rs` took a reference the compiler immediately dereferenced. Verified locally with the workflow's own commands: `cargo fmt --check`, `cargo +nightly fmt --manifest-path fuzz/Cargo.toml --check` and `cargo clippy --workspace --all-targets --all-features -- -D warnings` all pass. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/durable_audits.rs | 2 +- .../src/http_endpoint_catalog.rs | 2 +- crates/octoscript-capabilities/src/lib.rs | 4 +- .../src/linux_network_broker.rs | 4 +- crates/octoscript-capabilities/src/mobile.rs | 2 +- .../src/platform_keyring_secret_resolver.rs | 11 +- crates/octoscript-core/src/lib.rs | 38 +++- .../tests/language_regressions.rs | 2 +- crates/octoscript-lsp/src/main.rs | 81 ++++--- crates/octoscript-media-host/src/main.rs | 13 +- crates/octoscript-protocol/src/lib.rs | 2 +- .../src/bin/octoscript-landlock-runner.rs | 7 +- crates/octoscript-sandbox/src/bubblewrap.rs | 30 ++- .../octoscript-ui-l0/examples/l0validate.rs | 8 +- crates/octoscript-ui-l0/src/lib.rs | 93 ++++++-- crates/octoscript-ui-l0/src/value_origin.rs | 4 +- .../tests/fixtures/live-news.card | 80 +++++++ .../octoscript-ui-l0/tests/layers_review.rs | 2 +- crates/octoscript-ui-l0/tests/live_news.rs | 79 +++++-- crates/octoscript-ui-l0/tests/nav_waypoint.rs | 18 +- .../tests/origins_approval.rs | 2 +- crates/octoscript-ui-l0/tests/profile.rs | 205 +++++++++++++----- crates/octoscript-worker/src/lib.rs | 2 +- .../octoscript-workflow/src/durable_events.rs | 2 +- crates/octoscript-workflow/src/lib.rs | 24 +- crates/octoscript-workflow/src/mobile.rs | 9 +- .../src/multiplexed_worker.rs | 5 +- crates/octoscript-workflow/src/telemetry.rs | 2 +- .../src/telemetry/durable.rs | 2 +- fuzz/fuzz_targets/execution.rs | 4 +- fuzz/fuzz_targets/mobile_dataflow.rs | 2 +- fuzz/fuzz_targets/mobile_workflow.rs | 2 +- 32 files changed, 566 insertions(+), 177 deletions(-) create mode 100644 crates/octoscript-ui-l0/tests/fixtures/live-news.card diff --git a/crates/octoscript-capabilities/src/durable_audits.rs b/crates/octoscript-capabilities/src/durable_audits.rs index 69fcbc9..99a036f 100644 --- a/crates/octoscript-capabilities/src/durable_audits.rs +++ b/crates/octoscript-capabilities/src/durable_audits.rs @@ -10,10 +10,10 @@ use std::collections::VecDeque; use std::fmt::{self, Display, Formatter}; use std::num::{NonZeroU64, NonZeroUsize}; -use serde::{Deserialize, Serialize}; use octoscript_storage::{ AuthenticatedStore, AuthenticatedStoreError, RollbackProtectedStore, StorageRecordKey, }; +use serde::{Deserialize, Serialize}; use crate::{ is_valid_tool_name, AuditEvent, AuditEventBatch, AuditEventValidationError, AuditOutcome, diff --git a/crates/octoscript-capabilities/src/http_endpoint_catalog.rs b/crates/octoscript-capabilities/src/http_endpoint_catalog.rs index d95986e..8fc3d94 100644 --- a/crates/octoscript-capabilities/src/http_endpoint_catalog.rs +++ b/crates/octoscript-capabilities/src/http_endpoint_catalog.rs @@ -17,9 +17,9 @@ use std::io::Read; use std::str::FromStr; use std::time::Duration; -use serde_json::json; #[cfg(all(feature = "linux-network-broker", target_os = "linux"))] use octoscript_protocol::NetworkOriginAccess; +use serde_json::json; use ureq::{ http::{ header::{HeaderName, HeaderValue}, diff --git a/crates/octoscript-capabilities/src/lib.rs b/crates/octoscript-capabilities/src/lib.rs index 6eaf055..745c290 100644 --- a/crates/octoscript-capabilities/src/lib.rs +++ b/crates/octoscript-capabilities/src/lib.rs @@ -21,8 +21,6 @@ use makepad_script::{ LiveId, ScriptHandle, ScriptHandleGc, ScriptHandleType, ScriptIp, ScriptStringSink, ScriptThreadId, ScriptValue, NIL, }; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -pub use serde_json::{json, Value as JsonValue}; use octoscript_core::{ decode_bounded_script_json, encode_bounded_script_json, imported_module_call_hint_report_named, is_canonical_identifier, vm, Evaluation, ExecutionLimits, Runtime, RuntimeError, @@ -40,6 +38,8 @@ pub use octoscript_protocol::{ }; use octoscript_protocol::{EnvelopeFormat, SessionAuthorizer}; pub use octoscript_schema::{JsonSchema, SchemaError}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +pub use serde_json::{json, Value as JsonValue}; /// Host-enforced deadline and termination wrapper for worker transports. /// diff --git a/crates/octoscript-capabilities/src/linux_network_broker.rs b/crates/octoscript-capabilities/src/linux_network_broker.rs index 0c3e7e6..608acbf 100644 --- a/crates/octoscript-capabilities/src/linux_network_broker.rs +++ b/crates/octoscript-capabilities/src/linux_network_broker.rs @@ -19,8 +19,8 @@ use std::sync::Arc; use std::thread::{self, JoinHandle}; use std::time::Duration; -use serde::{Deserialize, Serialize}; use octoscript_protocol::{CapabilityGrant, NetworkOriginAccess, ResourceKind}; +use serde::{Deserialize, Serialize}; use crate::http_endpoint_catalog::{ HttpEndpointCatalog, HttpEndpointCatalogError, HttpEndpointSecretResolver, HttpOriginCatalog, @@ -753,8 +753,8 @@ mod tests { use std::sync::mpsc; use std::thread; - use serde_json::json; use octoscript_protocol::{CapabilityManifest, ResourceSelector}; + use serde_json::json; use super::*; use crate::http_endpoint_catalog::{ diff --git a/crates/octoscript-capabilities/src/mobile.rs b/crates/octoscript-capabilities/src/mobile.rs index cb997b9..716c2cd 100644 --- a/crates/octoscript-capabilities/src/mobile.rs +++ b/crates/octoscript-capabilities/src/mobile.rs @@ -16,11 +16,11 @@ use std::num::NonZeroUsize; -use serde::{de::DeserializeOwned, Serialize}; use octoscript_core::{ vm::ScriptValue, Evaluation, ExecutionLimits, RuntimeError, DEFAULT_MAX_JSON_DATA_BYTES, DEFAULT_MAX_JSON_DATA_DEPTH, }; +use serde::{de::DeserializeOwned, Serialize}; use crate::{ fixed_file_catalog::FixedFileCatalog, CapabilityCatalogLimits, CapabilityModule, diff --git a/crates/octoscript-capabilities/src/platform_keyring_secret_resolver.rs b/crates/octoscript-capabilities/src/platform_keyring_secret_resolver.rs index 2e498d7..a0a2824 100644 --- a/crates/octoscript-capabilities/src/platform_keyring_secret_resolver.rs +++ b/crates/octoscript-capabilities/src/platform_keyring_secret_resolver.rs @@ -396,7 +396,8 @@ mod tests { #[test] fn entries_reject_invalid_opaque_ids_and_credential_locators() { assert_eq!( - PlatformKeyringSecretEntry::new("bad/id", "com.example.octoscript", "release").unwrap_err(), + PlatformKeyringSecretEntry::new("bad/id", "com.example.octoscript", "release") + .unwrap_err(), PlatformKeyringSecretEntryError::InvalidSecretIdentifier ); assert_eq!( @@ -405,8 +406,12 @@ mod tests { PlatformKeyringSecretEntryError::InvalidService ); assert_eq!( - PlatformKeyringSecretEntry::new("release.auth", "com.example.octoscript", "Release User") - .unwrap_err(), + PlatformKeyringSecretEntry::new( + "release.auth", + "com.example.octoscript", + "Release User" + ) + .unwrap_err(), PlatformKeyringSecretEntryError::InvalidAccount ); } diff --git a/crates/octoscript-core/src/lib.rs b/crates/octoscript-core/src/lib.rs index 626380e..b7c5fcb 100644 --- a/crates/octoscript-core/src/lib.rs +++ b/crates/octoscript-core/src/lib.rs @@ -5538,7 +5538,8 @@ mod tests { max_syntax_tokens: 6, ..ExecutionLimits::default() }; - let report = check_syntax_named("boundary.octoscript", "let value = 1\nvalue", limits).unwrap(); + let report = + check_syntax_named("boundary.octoscript", "let value = 1\nvalue", limits).unwrap(); assert!(report.valid, "{:?}", report.diagnostics); } @@ -5677,7 +5678,8 @@ mod tests { ..ExecutionLimits::default() }; let accepted = - check_vm_compatibility_named("legacy.octoscript", "var value = (((42)))", limits).unwrap(); + check_vm_compatibility_named("legacy.octoscript", "var value = (((42)))", limits) + .unwrap(); assert!(accepted.valid, "{:?}", accepted.diagnostics); let rejected = @@ -6872,17 +6874,24 @@ compute(outer, 2) fn canonical_profile_rejects_bare_carriage_returns_before_vm_preflight() { let source = "true\r[]"; - let canonical = - check_syntax_named("line-endings.octoscript", source, ExecutionLimits::default()).unwrap(); + let canonical = check_syntax_named( + "line-endings.octoscript", + source, + ExecutionLimits::default(), + ) + .unwrap(); assert!(!canonical.valid); assert_eq!(canonical.diagnostics.len(), 1); assert!(canonical.diagnostics[0] .message .contains("bare carriage returns are not supported")); - let compatibility = - check_vm_compatibility_named("line-endings.octoscript", source, ExecutionLimits::default()) - .unwrap(); + let compatibility = check_vm_compatibility_named( + "line-endings.octoscript", + source, + ExecutionLimits::default(), + ) + .unwrap(); assert!(!compatibility.valid); } @@ -6905,8 +6914,12 @@ compute(outer, 2) #[test] fn canonical_profile_rejects_adjacent_numeric_field_access() { let source = "(5.ci)"; - let profile = - check_syntax_named("numeric-field.octoscript", source, ExecutionLimits::default()).unwrap(); + let profile = check_syntax_named( + "numeric-field.octoscript", + source, + ExecutionLimits::default(), + ) + .unwrap(); let compatibility = check_vm_compatibility_named( "numeric-field.octoscript", source, @@ -7199,7 +7212,8 @@ compute(outer, 2) let accepted = check_syntax_named("nesting.octoscript", "let value = (0)", limits).unwrap(); assert!(accepted.valid, "{:?}", accepted.diagnostics); - let rejected = check_syntax_named("nesting.octoscript", "let value = ((0))", limits).unwrap(); + let rejected = + check_syntax_named("nesting.octoscript", "let value = ((0))", limits).unwrap(); assert!(!rejected.valid); assert!(rejected.diagnostics.iter().any(|diagnostic| { diagnostic @@ -9560,7 +9574,9 @@ compute(outer, 2) fn preserves_the_llm_workflow_language_fixture() { let mut runtime = Runtime::default(); let report = runtime - .eval(include_str!("../tests/fixtures/workflow_language.octoscript")) + .eval(include_str!( + "../tests/fixtures/workflow_language.octoscript" + )) .unwrap(); assert!(report.succeeded(), "{:?}", report.diagnostics); diff --git a/crates/octoscript-core/tests/language_regressions.rs b/crates/octoscript-core/tests/language_regressions.rs index 5ed8dd1..d6b910c 100644 --- a/crates/octoscript-core/tests/language_regressions.rs +++ b/crates/octoscript-core/tests/language_regressions.rs @@ -1,6 +1,6 @@ //! Reproductions from the September 2026 language review (#9–#18). -use serde_json::{json, Value}; use octoscript_core::{ExecutionLimits, Runtime, RuntimeError, RuntimeJsonError}; +use serde_json::{json, Value}; fn evaluate(source: &str) -> Value { let mut runtime = Runtime::default(); diff --git a/crates/octoscript-lsp/src/main.rs b/crates/octoscript-lsp/src/main.rs index 96d8f65..b333984 100644 --- a/crates/octoscript-lsp/src/main.rs +++ b/crates/octoscript-lsp/src/main.rs @@ -1122,7 +1122,9 @@ impl OctoscriptLanguageServer { .get(uri) .ok_or_else(|| "the document is not open in this Octoscript session".to_owned())?; let source = state.source.as_deref().ok_or_else(|| { - format!("the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit") + format!( + "the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" + ) })?; let formatted = format_source_named(uri.as_str(), source, ExecutionLimits::default()) .map_err(|error| format!("cannot format canonical Octoscript source: {error}"))?; @@ -1143,7 +1145,9 @@ impl OctoscriptLanguageServer { .get(uri) .ok_or_else(|| "the document is not open in this Octoscript session".to_owned())?; let source = state.source.as_deref().ok_or_else(|| { - format!("the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit") + format!( + "the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" + ) })?; let declarations = top_level_declarations_named(uri.as_str(), source, ExecutionLimits::default()) @@ -1876,7 +1880,8 @@ impl OctoscriptLanguageServer { } if !is_canonical_identifier(new_name) { return Err( - "the requested name is not a non-reserved canonical Octoscript identifier".to_owned(), + "the requested name is not a non-reserved canonical Octoscript identifier" + .to_owned(), ); } @@ -1884,7 +1889,9 @@ impl OctoscriptLanguageServer { let syntax = check_syntax_named(uri.as_str(), &renamed_source, ExecutionLimits::default()) .map_err(|error| format!("cannot validate renamed Octoscript source: {error}"))?; if !syntax.valid { - return Err("the requested rename does not produce canonical Octoscript source".to_owned()); + return Err( + "the requested rename does not produce canonical Octoscript source".to_owned(), + ); } let renamed_report = @@ -1918,7 +1925,9 @@ impl OctoscriptLanguageServer { .get(uri) .ok_or_else(|| "the document is not open in this Octoscript session".to_owned())?; let source = state.source.as_deref().ok_or_else(|| { - format!("the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit") + format!( + "the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" + ) })?; let report = state.lexical_report.get_or_init(|| { lexical_symbol_report_named(uri.as_str(), source, ExecutionLimits::default()) @@ -1936,7 +1945,9 @@ impl OctoscriptLanguageServer { .get(uri) .ok_or_else(|| "the document is not open in this Octoscript session".to_owned())?; let source = state.source.as_deref().ok_or_else(|| { - format!("the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit") + format!( + "the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" + ) })?; let report = state.completion_report.get_or_init(|| { lexical_completion_report_named(uri.as_str(), source, ExecutionLimits::default()) @@ -1954,7 +1965,9 @@ impl OctoscriptLanguageServer { .get(uri) .ok_or_else(|| "the document is not open in this Octoscript session".to_owned())?; let source = state.source.as_deref().ok_or_else(|| { - format!("the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit") + format!( + "the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" + ) })?; let report = state.module_import_report.get_or_init(|| { module_import_report_named(uri.as_str(), source, ExecutionLimits::default()) @@ -1972,7 +1985,9 @@ impl OctoscriptLanguageServer { .get(uri) .ok_or_else(|| "the document is not open in this Octoscript session".to_owned())?; let source = state.source.as_deref().ok_or_else(|| { - format!("the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit") + format!( + "the document exceeds Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" + ) })?; let report = state.static_record_shape_report.get_or_init(|| { static_record_shape_report_named(uri.as_str(), source, ExecutionLimits::default()) @@ -2459,7 +2474,11 @@ fn fuzz_exercise_advisory_input_field_requests( } #[cfg(fuzzing)] -fn fuzz_exercise_named_function_requests(server: &OctoscriptLanguageServer, uri: &Uri, source: &str) { +fn fuzz_exercise_named_function_requests( + server: &OctoscriptLanguageServer, + uri: &Uri, + source: &str, +) { let Some(declaration_start) = source.find("fn summarize") else { return; }; @@ -8728,7 +8747,9 @@ fn rewrite_symbol_occurrences( let rewritten_len = removed_bytes .and_then(|removed| source.len().checked_sub(removed)) .and_then(|retained| replacement_bytes.and_then(|added| retained.checked_add(added))) - .ok_or_else(|| "the requested rename exceeds Octoscript's source-size arithmetic".to_owned())?; + .ok_or_else(|| { + "the requested rename exceeds Octoscript's source-size arithmetic".to_owned() + })?; if rewritten_len > DEFAULT_MAX_SOURCE_BYTES { return Err(format!( "the renamed document would exceed Octoscript's {DEFAULT_MAX_SOURCE_BYTES}-byte source limit" @@ -8989,7 +9010,12 @@ mod tests { } fn document(version: i32, text: &str) -> TextDocumentItem { - TextDocumentItem::new(test_uri(), "octoscript".to_owned(), version, text.to_owned()) + TextDocumentItem::new( + test_uri(), + "octoscript".to_owned(), + version, + text.to_owned(), + ) } fn tool_catalog(value: serde_json::Value) -> ToolCompletionCatalog { @@ -12358,7 +12384,8 @@ mod tests { } let input_source = "workflow.input.le"; - let mut input_server = OctoscriptLanguageServer::with_workflow_data_catalog(catalog.clone()); + let mut input_server = + OctoscriptLanguageServer::with_workflow_data_catalog(catalog.clone()); input_server.open_document(document(1, input_source)); let input_completion = input_server .completion( @@ -12486,10 +12513,11 @@ mod tests { assert_eq!(step_context.completed_output_count, 1); let outputs_source = "workflow.outputs."; - let mut outputs_server = OctoscriptLanguageServer::with_workflow_data_catalog_and_step_context( - catalog.clone(), - step_context.clone(), - ); + let mut outputs_server = + OctoscriptLanguageServer::with_workflow_data_catalog_and_step_context( + catalog.clone(), + step_context.clone(), + ); outputs_server.open_document(document(1, outputs_source)); let outputs_completion = outputs_server .completion( @@ -12576,10 +12604,11 @@ mod tests { .is_empty()); let hover_source = "workflow.outputs.prepare.total\nworkflow.outputs.calculate.sum"; - let mut hover_server = OctoscriptLanguageServer::with_workflow_data_catalog_and_step_context( - catalog, - step_context, - ); + let mut hover_server = + OctoscriptLanguageServer::with_workflow_data_catalog_and_step_context( + catalog, + step_context, + ); hover_server.open_document(document(1, hover_source)); let completed_member = hover_source.find("total").expect("completed member exists"); let completed_hover = hover_server @@ -12804,10 +12833,11 @@ mod tests { assert!(invalid_catalog.unavailable); assert!(invalid_context.unavailable); let source = "workflow.outputs."; - let mut invalid_server = OctoscriptLanguageServer::with_workflow_data_catalog_and_step_context( - invalid_catalog, - invalid_context, - ); + let mut invalid_server = + OctoscriptLanguageServer::with_workflow_data_catalog_and_step_context( + invalid_catalog, + invalid_context, + ); invalid_server.open_document(document(1, source)); let completion = invalid_server .completion(&test_uri(), position_at_byte(source, source.len())) @@ -17353,7 +17383,8 @@ mod tests { ); } - let other_uri = Uri::from_str("file:///workspace/other.octoscript").expect("valid file URI"); + let other_uri = + Uri::from_str("file:///workspace/other.octoscript").expect("valid file URI"); let other_source = "let index = 9\nindex"; server.open_document(TextDocumentItem::new( other_uri.clone(), diff --git a/crates/octoscript-media-host/src/main.rs b/crates/octoscript-media-host/src/main.rs index 2398a5e..f7f33ff 100644 --- a/crates/octoscript-media-host/src/main.rs +++ b/crates/octoscript-media-host/src/main.rs @@ -27,7 +27,6 @@ use std::process::{Command, ExitCode}; use std::sync::{Arc, Mutex}; use std::time::Instant; -use serde_json::{json, Value}; use octoscript_capabilities::{ http_endpoint_catalog::{HttpEndpointMethod, HttpOrigin, HttpOriginCatalog}, CapabilityLeaseGrant, JsonToolContract, JsonToolRequest, ToolDescriptor, ToolError, @@ -38,6 +37,7 @@ use octoscript_workflow::{ mobile::{MobileWorkflowBuilder, MobileWorkflowRuntime}, WorkflowData, WorkflowDraft, WorkflowPlan, WorkflowStepCapabilityPolicy, }; +use serde_json::{json, Value}; /// Opaque-handle -> host-chosen file path. Shared by both adapters so a /// generated image can feed a transcode WITHOUT the script ever seeing a path. @@ -70,7 +70,8 @@ fn run() -> Result { .collect::, _>>()?; let dir = PathBuf::from( - std::env::var("OCTOSCRIPT_MEDIA_DIR").unwrap_or_else(|_| "/tmp/octoscript-media".to_owned()), + std::env::var("OCTOSCRIPT_MEDIA_DIR") + .unwrap_or_else(|_| "/tmp/octoscript-media".to_owned()), ); // (2a) The check gate: preflight every generated step through the canonical @@ -195,9 +196,11 @@ fn build_runtime(dir: PathBuf) -> Result { hard_timeout: std::time::Duration::from_secs(120), ..ExecutionLimits::default() }; - let mut builder = - MobileWorkflowBuilder::with_limits(limits, octoscript_capabilities::DEFAULT_MAX_PENDING_TOOLS) - .map_err(|error| error.to_string())?; + let mut builder = MobileWorkflowBuilder::with_limits( + limits, + octoscript_capabilities::DEFAULT_MAX_PENDING_TOOLS, + ) + .map_err(|error| error.to_string())?; { let registry = registry.clone(); diff --git a/crates/octoscript-protocol/src/lib.rs b/crates/octoscript-protocol/src/lib.rs index c0caf06..0c6b9aa 100644 --- a/crates/octoscript-protocol/src/lib.rs +++ b/crates/octoscript-protocol/src/lib.rs @@ -3561,11 +3561,11 @@ mod tests { use std::io::{self, Cursor, Read, Write}; use super::*; - use serde_json::json; use octoscript_storage::{ AuthenticatedStore, StorageKey, StorageKeyId, StorageKeyring, StorageRecordKey, VolatileMemoryStore, STORAGE_KEY_BYTES, }; + use serde_json::json; fn json_grant() -> CapabilityGrant { let mut grant = CapabilityGrant::json("math.add"); diff --git a/crates/octoscript-sandbox/src/bin/octoscript-landlock-runner.rs b/crates/octoscript-sandbox/src/bin/octoscript-landlock-runner.rs index cb946d9..bf648cb 100644 --- a/crates/octoscript-sandbox/src/bin/octoscript-landlock-runner.rs +++ b/crates/octoscript-sandbox/src/bin/octoscript-landlock-runner.rs @@ -36,11 +36,11 @@ mod linux { AccessFs, CompatLevel, Compatible, PathBeneath, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetStatus, }; - use rustix::fs::{fstat, open, FileType, Mode, OFlags}; use octoscript_linux_seccomp::{ install_filter, validate_filter, SeccompInstallError, ValidatedFilter, FILTER_INSTRUCTION_BYTES, MAX_FILTER_INSTRUCTIONS, }; + use rustix::fs::{fstat, open, FileType, Mode, OFlags}; const EXIT_USAGE: i32 = 64; const EXIT_LANDLOCK: i32 = 125; @@ -437,7 +437,10 @@ mod linux { ]) ); assert!(configuration.staged_seccomp_filter.is_none()); - assert_eq!(configuration.command, Path::new("/opt/octoscript/limit-runner")); + assert_eq!( + configuration.command, + Path::new("/opt/octoscript/limit-runner") + ); assert_eq!( configuration.arguments, arguments(&["--open-files", "16", "--", "/opt/octoscript/worker"]) diff --git a/crates/octoscript-sandbox/src/bubblewrap.rs b/crates/octoscript-sandbox/src/bubblewrap.rs index c38008f..ce17e61 100644 --- a/crates/octoscript-sandbox/src/bubblewrap.rs +++ b/crates/octoscript-sandbox/src/bubblewrap.rs @@ -31,17 +31,17 @@ use std::os::unix::net::UnixStream; #[cfg(target_os = "linux")] use std::process::{ChildStderr, Command, Stdio}; -#[cfg(target_os = "linux")] -use rustix::{ - fs::{fstat, open, openat, FileType, Mode, OFlags}, - io::{fcntl_dupfd_cloexec, fcntl_setfd, FdFlags}, -}; #[cfg(target_os = "linux")] use octoscript_linux_project_quota::inspect_project_quota; use octoscript_protocol::{ CapabilityManifest, NetworkOriginAccess, NetworkOriginAccessError, PrivatePipeWorkerBootstrap, PrivatePipeWorkerBootstrapError, ProtocolError, ResourceKind, ResourceSelector, }; +#[cfg(target_os = "linux")] +use rustix::{ + fs::{fstat, open, openat, FileType, Mode, OFlags}, + io::{fcntl_dupfd_cloexec, fcntl_setfd, FdFlags}, +}; const MAX_TMPFS_BYTES: usize = usize::MAX >> 1; const MAX_FINITE_RESOURCE_LIMIT: u64 = u64::MAX - 1; @@ -6141,7 +6141,8 @@ mod tests { fs::set_permissions(&socket, socket_permissions).unwrap(); drop(listener); - LinuxNetworkBrokerMount::new(source, "/run/octoscript-network", "broker.sock", access).unwrap() + LinuxNetworkBrokerMount::new(source, "/run/octoscript-network", "broker.sock", access) + .unwrap() } fn argument_strings(plan: &BubblewrapCommand) -> Vec { @@ -7565,7 +7566,9 @@ print("seccomp-active") let root = TestDirectory::new(); let mut policy = base_policy(&root); let mut runner = landlock_executable_runner(&root); - runner.add_allowed_executable("/opt/octoscript/worker").unwrap(); + runner + .add_allowed_executable("/opt/octoscript/worker") + .unwrap(); policy.set_landlock_executable_runner(runner); #[cfg(target_os = "linux")] assert!(matches!( @@ -7690,7 +7693,10 @@ print("seccomp-active") #[test] fn resource_limit_configuration_rejects_empty_or_unbounded_values() { assert!(matches!( - ResourceLimitRunner::new("/opt/octoscript/limit-runner", WorkerResourceLimits::default()), + ResourceLimitRunner::new( + "/opt/octoscript/limit-runner", + WorkerResourceLimits::default() + ), Err(BubblewrapPolicyError::EmptyResourceLimits) )); @@ -8314,9 +8320,11 @@ print("seccomp-active") #[test] fn active_file_root_limit_bounds_union_before_source_resolution() { let root = TestDirectory::new(); - let mut policy = - BubblewrapWorkerPolicy::new(root.path().join("missing-bwrap"), "/opt/octoscript/worker") - .unwrap(); + let mut policy = BubblewrapWorkerPolicy::new( + root.path().join("missing-bwrap"), + "/opt/octoscript/worker", + ) + .unwrap(); let mut ids = Vec::new(); for index in 0..=DEFAULT_MAX_BUBBLEWRAP_ACTIVE_FILE_ROOTS { let id = format!("scratch-{index}"); diff --git a/crates/octoscript-ui-l0/examples/l0validate.rs b/crates/octoscript-ui-l0/examples/l0validate.rs index f8b57ca..f25b2f0 100644 --- a/crates/octoscript-ui-l0/examples/l0validate.rs +++ b/crates/octoscript-ui-l0/examples/l0validate.rs @@ -10,7 +10,9 @@ fn main() { let data = if data_path.exists() { serde_json::from_str(&std::fs::read_to_string(data_path).expect("read fixture data")) .expect("valid fixture JSON") - } else { serde_json::json!({}) }; + } else { + serde_json::json!({}) + }; let report = realize(&src, &data, RealizeLimits::default()); let msgs: Vec = report .diagnostics @@ -23,5 +25,7 @@ fn main() { serde_json::json!({"ok": ok, "root": report.root.is_some(), "diagnostics": msgs, "state_initials": octoscript_ui_l0::state_initials(&src)}) ); - if !ok { std::process::exit(1); } + if !ok { + std::process::exit(1); + } } diff --git a/crates/octoscript-ui-l0/src/lib.rs b/crates/octoscript-ui-l0/src/lib.rs index 785b5b5..f00a7e8 100644 --- a/crates/octoscript-ui-l0/src/lib.rs +++ b/crates/octoscript-ui-l0/src/lib.rs @@ -3656,7 +3656,10 @@ pub mod catalog { ("sys.moonphase", &["lat", "lon"]), ("sys.photo", &["query", "cond"]), ("sys.locale", &[]), - ("sys.convert", &["amount", "from", "to", "direction", "fields"]), + ( + "sys.convert", + &["amount", "from", "to", "direction", "fields"], + ), ("sys.gps", &[]), ("sys.search", &["query", "count", "fields"]), // COORDINATES, not places. A route needs four numbers and an argument @@ -3814,11 +3817,39 @@ pub mod catalog { "sys.news", &["id", "title", "author", "points", "comments", "url"], ), - ("sys.news_digest", &["id", "title", "summary", "publisher", "url", "published_at"]), - ("sys.dataset", &["title", "subtitle", "summary", "coverage", "status", "as_of", - "metric1_label", "metric1_value", "metric2_label", "metric2_value", - "pick1_title", "pick1_body", "pick1_source", "url1", "pick2_title", "pick2_body", "pick2_source", "url2", - "pick3_title", "pick3_body", "pick3_source", "url3", "evidence_title", "evidence_body"]), + ( + "sys.news_digest", + &["id", "title", "summary", "publisher", "url", "published_at"], + ), + ( + "sys.dataset", + &[ + "title", + "subtitle", + "summary", + "coverage", + "status", + "as_of", + "metric1_label", + "metric1_value", + "metric2_label", + "metric2_value", + "pick1_title", + "pick1_body", + "pick1_source", + "url1", + "pick2_title", + "pick2_body", + "pick2_source", + "url2", + "pick3_title", + "pick3_body", + "pick3_source", + "url3", + "evidence_title", + "evidence_body", + ], + ), ("sys.news_status", &["status", "message", "count"]), ( "sys.quakes", @@ -3905,7 +3936,17 @@ pub mod catalog { ( "sys.cities", &[ - "name", "lat", "lon", "temp", "feels", "feels_delta", "hi", "lo", "cond", "humidity", "wind", + "name", + "lat", + "lon", + "temp", + "feels", + "feels_delta", + "hi", + "lo", + "cond", + "humidity", + "wind", ], ), ]; @@ -7540,8 +7581,14 @@ pub mod makepad { let from = text("from")?; let to = text("to")?; let direction = text("direction").unwrap_or_else(|| "\"fwd\"".into()); - let field = if binding.field == "amount" { ", \"amount\"" } else { "" }; - Some(format!("sys.convert({amount}, {from}, {to}, {direction}{field})")) + let field = if binding.field == "amount" { + ", \"amount\"" + } else { + "" + }; + Some(format!( + "sys.convert({amount}, {from}, {to}, {direction}{field})" + )) } // THE FOUR THAT ANSWERED NOTHING. Each is in the catalog, so a card may // declare it and the checker accepts it — and each fell through to @@ -7760,19 +7807,30 @@ pub mod makepad { } "sys.dataset" => { let id = text("id")?; - if !crate::catalog::answers("sys.dataset")?.contains(&binding.field.as_str()) { return None; } + if !crate::catalog::answers("sys.dataset")?.contains(&binding.field.as_str()) { + return None; + } Some(format!("sys.dataset({id}, {:?})", binding.field)) } "sys.news_digest" | "sys.news_status" => { let query = text("query")?; let language = text("language").unwrap_or_else(|| "\"en\"".into()); let field = if binding.helper == "sys.news_status" { - if !matches!(binding.field.as_str(), "status" | "message" | "count") { return None; } + if !matches!(binding.field.as_str(), "status" | "message" | "count") { + return None; + } binding.field.clone() } else { let (row, field) = binding.field.split_once('.')?; let row: usize = row.parse().ok()?; - if row >= 3 || !matches!(field, "id" | "title" | "summary" | "publisher" | "url" | "published_at") { return None; } + if row >= 3 + || !matches!( + field, + "id" | "title" | "summary" | "publisher" | "url" | "published_at" + ) + { + return None; + } format!("items.{row}.{field}") }; Some(format!("sys.news_digest({query}, {language}, {field:?})")) @@ -8130,8 +8188,8 @@ pub mod makepad { let (index, field) = binding.field.split_once('.')?; index.parse::().ok()?; let key = match field { - f @ ("name" | "lat" | "lon" | "temp" | "feels" | "feels_delta" | "hi" | "lo" | "cond" - | "humidity" | "wind") => f, + f @ ("name" | "lat" | "lon" | "temp" | "feels" | "feels_delta" | "hi" + | "lo" | "cond" | "humidity" | "wind") => f, _ => return None, }; Some(match text("unit") { @@ -11977,7 +12035,12 @@ pub mod kit { // A ticker is text even when it comes from a live source. // Numeric coercion turned a mover's "BLTE" into a missing // symbol, while literal state-selected tickers still worked. - let _ = write!(out, "{f}({}, {})", scalar_of(node, "symbol"), scalar_of(node, "range")); + let _ = write!( + out, + "{f}({}, {})", + scalar_of(node, "symbol"), + scalar_of(node, "range") + ); } "TempBar" | "SunArc" | "MoonPhase" | "AqiContour" | "Satellite" => { let params: &[&str] = match node.kind.as_str() { diff --git a/crates/octoscript-ui-l0/src/value_origin.rs b/crates/octoscript-ui-l0/src/value_origin.rs index fdbc42b..7d603e3 100644 --- a/crates/octoscript-ui-l0/src/value_origin.rs +++ b/crates/octoscript-ui-l0/src/value_origin.rs @@ -81,13 +81,13 @@ impl ValueScope<'_> { } pub(super) fn initial_origin(state: &StateDecl, card: &Card) -> ValueOrigin { - if state.initial.is_none() && state.initial_path.is_some() { + if let (None, Some(path)) = (&state.initial, &state.initial_path) { ValueScope { frames: Vec::new(), data: &serde_json::Value::Null, copies: &card.copies, } - .path_origin(state.initial_path.as_ref().unwrap(), card) + .path_origin(path, card) } else if matches!(state.shape, Shape::Bool | Shape::Enum(_)) { ValueOrigin::Vocabulary } else { diff --git a/crates/octoscript-ui-l0/tests/fixtures/live-news.card b/crates/octoscript-ui-l0/tests/fixtures/live-news.card new file mode 100644 index 0000000..7e8ff0a --- /dev/null +++ b/crates/octoscript-ui-l0/tests/fixtures/live-news.card @@ -0,0 +1,80 @@ +# ledger live-news@1.0.0 +theme taskplan_light + +source stories sys.news_digest(query: state.query, language: state.language, count: 3, + fields: [id, title, summary, publisher, url, published_at]) +source progress sys.news_status(query: state.query, language: state.language, + fields: [status, message, count]) +source page sys.link(fields: [url]) + +state query { shape: text, initial: "technology" } +state draft { shape: text, initial: "" } +state language { shape: text, initial: "en" } +state editing { shape: enum[none, search], initial: .none } + +event edit_search { editing: set(.search), draft: clear } +event typing { draft: set($value) } +event search { query: set($value), editing: set(.none), draft: clear } +event cancel { editing: set(.none), draft: clear } +event translate { language: set($value) } +event read_story { page: set($value) } + +copy title { class: vocabulary, en: "News" } +copy coverage { class: vocabulary, en: "BBC · THE GUARDIAN" } +copy search { class: vocabulary, en: "Search" } +copy placeholder { class: vocabulary, en: "Search a person, place or topic" } +copy cancel { class: vocabulary, en: "Cancel" } +copy loading { class: vocabulary, en: "Finding articles and preparing your brief…" } +copy failed { class: vocabulary, en: "News is unavailable. Try another search." } +copy read { class: vocabulary, en: "Read article ↗" } +copy english { class: vocabulary, en: "English" } +copy chinese { class: vocabulary, en: "中文" } +copy technology { class: vocabulary, en: "Tech" } +copy world { class: vocabulary, en: "World" } +copy business { class: vocabulary, en: "Business" } + +view root Surface(pad: .page) { + Col(gap: 14) { + Row(align: .center, gap: 12) { + TextTitle(text: copy.title, width: .fill) + Chip(text: copy.search, on_tap: edit_search) + } + TextCaption(text: copy.coverage) + when editing == .search { + Panel { + Col(gap: 10) { + Field(text: draft, placeholder: copy.placeholder, on_commit: search, + on_change: typing, width: .fill) + Row(gap: 10) { + Chip(text: copy.search, on_tap: search, value: draft) + Chip(text: copy.cancel, on_tap: cancel) + } + } + } + } + Row(gap: 8) { + Chip(text: copy.technology, on_tap: search, value: "technology") + Chip(text: copy.world, on_tap: search, value: "world") + Chip(text: copy.business, on_tap: search, value: "business") + } + Row(gap: 8) { + Chip(text: copy.english, on_tap: translate, value: "en") + Chip(text: copy.chinese, on_tap: translate, value: "zh-CN") + } + TextRow(text: query, width: .fill) + when progress.$state == .pending { TextBody(text: copy.loading) } + when progress.$state == .failed { TextBody(text: copy.failed) } + TextCaption(text: progress.message, width: .fill) + for story in stories key story.id { + Card { + Col(gap: 8) { + TextCaption(text: story.publisher) + TextRow(text: story.title, width: .fill) + TextBody(text: story.summary, width: .fill) + TextCaption(text: story.published_at) + Chip(text: copy.read, on_tap: read_story, value: story.url) + } + } + } + } +} diff --git a/crates/octoscript-ui-l0/tests/layers_review.rs b/crates/octoscript-ui-l0/tests/layers_review.rs index 48be575..18deb0f 100644 --- a/crates/octoscript-ui-l0/tests/layers_review.rs +++ b/crates/octoscript-ui-l0/tests/layers_review.rs @@ -1,6 +1,6 @@ //! Regressions for the completed L0–L3 boundary review. -use serde_json::json; use octoscript_ui_l0::*; +use serde_json::json; fn card(expr: &str) -> String { format!("# level: L1\nsource q sys.quote(ticker: \"N\", fields: [last, open])\nview root Surface {{ TextHero(value: {expr}) }}") diff --git a/crates/octoscript-ui-l0/tests/live_news.rs b/crates/octoscript-ui-l0/tests/live_news.rs index 2e7b586..c8fb305 100644 --- a/crates/octoscript-ui-l0/tests/live_news.rs +++ b/crates/octoscript-ui-l0/tests/live_news.rs @@ -2,23 +2,43 @@ use octoscript_ui_l0::{check_ui_l0_named, makepad, SourceBinding}; #[test] fn live_news_card_is_l0_and_query_changes_invalidate_both_sources() { - let card = include_str!("../../../../tools/octoscript-research/live-news.card"); + let card = include_str!("fixtures/live-news.card"); let report = check_ui_l0_named("live-news", card); assert!(report.valid, "{:?}", report.diagnostics); for field in ["query", "language"] { - assert_eq!(octoscript_ui_l0::stale_sources(card, &[field]), vec!["stories", "progress"]); + assert_eq!( + octoscript_ui_l0::stale_sources(card, &[field]), + vec!["stories", "progress"] + ); } - assert!(!check_ui_l0_named("live-news", &card.replace("count: 3,", "endpoint: \"https://untrusted.invalid\", count: 3,")).valid); + assert!( + !check_ui_l0_named( + "live-news", + &card.replace( + "count: 3,", + "endpoint: \"https://untrusted.invalid\", count: 3," + ) + ) + .valid + ); } #[test] fn news_binding_quotes_user_text_and_bounds_rows_and_fields() { let mut binding = SourceBinding { - helper: "sys.news_digest".into(), field: "0.summary".into(), nested: vec![], - args: vec![("query".into(), "Apple \" OR sys.stock(1)".into()), ("language".into(), "zh-CN".into())], + helper: "sys.news_digest".into(), + field: "0.summary".into(), + nested: vec![], + args: vec![ + ("query".into(), "Apple \" OR sys.stock(1)".into()), + ("language".into(), "zh-CN".into()), + ], }; let call = makepad::vm_call(&binding).unwrap(); - assert_eq!(call, "sys.news_digest(\"Apple \\\" OR sys.stock(1)\", \"zh-CN\", \"items.0.summary\")"); + assert_eq!( + call, + "sys.news_digest(\"Apple \\\" OR sys.stock(1)\", \"zh-CN\", \"items.0.summary\")" + ); for field in ["3.summary", "0.password", "../../url"] { binding.field = field.into(); assert!(makepad::vm_call(&binding).is_none()); @@ -27,26 +47,51 @@ fn news_binding_quotes_user_text_and_bounds_rows_and_fields() { #[test] fn chinese_search_uses_the_real_field_and_preserves_news_sources() { - use octoscript_ui_l0::{NodeValue, UiNode, InstanceStore, RealizeLimits, ValueOrigin}; + use octoscript_ui_l0::{InstanceStore, NodeValue, RealizeLimits, UiNode, ValueOrigin}; fn find<'a>(node: &'a UiNode, prop: &str, event: &str) -> Option<&'a UiNode> { - if node.args.iter().any(|(p,v)| p==prop && matches!(v, NodeValue::Event(e) if e==event)) { return Some(node); } + if node + .args + .iter() + .any(|(p, v)| p == prop && matches!(v, NodeValue::Event(e) if e==event)) + { + return Some(node); + } node.children.iter().find_map(|c| find(c, prop, event)) } - let card = include_str!("../../../../tools/octoscript-research/live-news.card"); + let card = include_str!("fixtures/live-news.card"); let mut store = InstanceStore::default(); let data = serde_json::json!({}); - for (prop, event, value) in [("on_tap", "edit_search", ""), ("on_commit", "search", "美国伊朗最新冲突")] { - let report = octoscript_ui_l0::realize_with_state(card, &data, &store, RealizeLimits::default()); + for (prop, event, value) in [ + ("on_tap", "edit_search", ""), + ("on_commit", "search", "美国伊朗最新冲突"), + ] { + let report = + octoscript_ui_l0::realize_with_state(card, &data, &store, RealizeLimits::default()); let root = report.complete_root().unwrap(); let control = find(root, prop, event).unwrap(); let origin = octoscript_ui_l0::event_payload_origin(root, &control.key, event).unwrap(); - if event == "search" { assert_eq!(origin, ValueOrigin::UserInput); } - let outcome = octoscript_ui_l0::dispatch_reporting_with_origin(card, &mut store, &control.key, - event, Some(&serde_json::json!(value)), &data, origin); + if event == "search" { + assert_eq!(origin, ValueOrigin::UserInput); + } + let outcome = octoscript_ui_l0::dispatch_reporting_with_origin( + card, + &mut store, + &control.key, + event, + Some(&serde_json::json!(value)), + &data, + origin, + ); assert!(outcome.applied); - if event == "search" { assert!(outcome.stale.iter().any(|s| s == "stories")); } + if event == "search" { + assert!(outcome.stale.iter().any(|s| s == "stories")); + } } - let report = octoscript_ui_l0::realize_with_state(card, &data, &store, RealizeLimits::default()); + let report = + octoscript_ui_l0::realize_with_state(card, &data, &store, RealizeLimits::default()); assert!(report.complete_root().is_ok()); - assert!(octoscript_ui_l0::source_plan(card).requests.iter().any(|s| s.helper == "sys.news_digest")); + assert!(octoscript_ui_l0::source_plan(card) + .requests + .iter() + .any(|s| s.helper == "sys.news_digest")); } diff --git a/crates/octoscript-ui-l0/tests/nav_waypoint.rs b/crates/octoscript-ui-l0/tests/nav_waypoint.rs index 9ab24e9..a69c070 100644 --- a/crates/octoscript-ui-l0/tests/nav_waypoint.rs +++ b/crates/octoscript-ui-l0/tests/nav_waypoint.rs @@ -20,12 +20,24 @@ view root Surface { let checked = check_ui_l0_named("nav", card); assert!(checked.valid, "{:?}", checked.diagnostics); let report = realize(card, &serde_json::json!({}), RealizeLimits::default()); - let lowered = kit::lower(&report.complete_root().unwrap()); + let lowered = kit::lower(report.complete_root().unwrap()); assert!(lowered.contains("sys.navprog(")); assert!(lowered.contains("sys.navstep(")); // Each live instruction asks navstep and navprog about the same waypoint; // map geometry needs it too. A direct-route progress value cannot be mixed // with a route through the stop. - assert!(lowered.matches("sys.searchnum(\"Apple Park\", 0, \"lat\")").count() >= 7, "{lowered}"); - assert!(lowered.matches("sys.searchnum(\"Apple Park\", 0, \"lon\")").count() >= 7, "{lowered}"); + assert!( + lowered + .matches("sys.searchnum(\"Apple Park\", 0, \"lat\")") + .count() + >= 7, + "{lowered}" + ); + assert!( + lowered + .matches("sys.searchnum(\"Apple Park\", 0, \"lon\")") + .count() + >= 7, + "{lowered}" + ); } diff --git a/crates/octoscript-ui-l0/tests/origins_approval.rs b/crates/octoscript-ui-l0/tests/origins_approval.rs index 1a49afa..e9203da 100644 --- a/crates/octoscript-ui-l0/tests/origins_approval.rs +++ b/crates/octoscript-ui-l0/tests/origins_approval.rs @@ -1,5 +1,5 @@ -use serde_json::json; use octoscript_ui_l0::{approval::ArtifactApproval, *}; +use serde_json::json; fn tree(source: &str, data: serde_json::Value, store: &InstanceStore) -> UiNode { realize_with_state(source, &data, store, RealizeLimits::default()) diff --git a/crates/octoscript-ui-l0/tests/profile.rs b/crates/octoscript-ui-l0/tests/profile.rs index d0838d8..48c2b58 100644 --- a/crates/octoscript-ui-l0/tests/profile.rs +++ b/crates/octoscript-ui-l0/tests/profile.rs @@ -25,10 +25,19 @@ fn l0_migration_conversion_binding_tracks_amount_pair_and_direction() { let data = serde_json::json!({"amount": amount, "from":"c", "to":"f", "dir": direction}); let realized = octoscript_ui_l0::realize(card, &data, Default::default()); let lowered = octoscript_ui_l0::kit::lower(realized.complete_root().unwrap()); - assert!(lowered.contains(&format!("sys.convert({amount}, \"c\", \"f\", \"{direction}\")")), "{lowered}\n{:?}", realized.complete_root().unwrap()); + assert!( + lowered.contains(&format!( + "sys.convert({amount}, \"c\", \"f\", \"{direction}\")" + )), + "{lowered}\n{:?}", + realized.complete_root().unwrap() + ); } for changed in ["amount", "from", "to", "dir"] { - assert_eq!(octoscript_ui_l0::stale_sources(card, &[changed]), vec!["result"]); + assert_eq!( + octoscript_ui_l0::stale_sources(card, &[changed]), + vec!["result"] + ); } } @@ -36,12 +45,24 @@ fn l0_migration_conversion_binding_tracks_amount_pair_and_direction() { fn l0_migration_conversion_rejects_undeclared_results_and_executable_arguments() { let card = "source result sys.convert(amount: 20, from: \"c\", to: \"f\", fields: [value])\nview root TextHero(value: result.value)"; assert!(check_ui_l0_named("convert", card).valid); - assert!(!check_ui_l0_named("convert", &card.replace("result.value)", "result.value + 1)")).valid); + assert!( + !check_ui_l0_named( + "convert", + &card.replace("result.value)", "result.value + 1)") + ) + .valid + ); assert!(!check_ui_l0_named("convert", &card.replace("result.value)", "result.factor)")).valid); for bad in ["1 + 1", "sys.navsecs(1)", "NaN", "inf"] { let binding = octoscript_ui_l0::SourceBinding { - helper: "sys.convert".into(), field: "value".into(), nested: vec![], - args: vec![("amount".into(), bad.into()), ("from".into(), "c".into()), ("to".into(), "f".into())], + helper: "sys.convert".into(), + field: "value".into(), + nested: vec![], + args: vec![ + ("amount".into(), bad.into()), + ("from".into(), "c".into()), + ("to".into(), "f".into()), + ], }; assert_eq!(octoscript_ui_l0::makepad::vm_call(&binding), None, "{bad}"); } @@ -56,14 +77,25 @@ fn l0_migration_city_difference_is_l0_and_unit_changes_rebind_the_source() { assert!(checked.valid, "{:?}", checked.diagnostics); assert_eq!(checked.level, Level::L0); for unit in ["c", "f"] { - let data = serde_json::json!({"units":unit,"picks":[{"name":"Test","temp":20,"feels_delta":2}]}); + let data = + serde_json::json!({"units":unit,"picks":[{"name":"Test","temp":20,"feels_delta":2}]}); let realized = octoscript_ui_l0::realize(card, &data, Default::default()); let lowered = octoscript_ui_l0::kit::lower(realized.complete_root().unwrap()); - assert!(lowered.contains(&format!("sys.cities(0, \"feels_delta\", \"{unit}\")")), "{lowered}"); - assert!(lowered.contains(&format!("sys.cities(0, \"temp\", \"{unit}\")")), "{lowered}"); + assert!( + lowered.contains(&format!("sys.cities(0, \"feels_delta\", \"{unit}\")")), + "{lowered}" + ); + assert!( + lowered.contains(&format!("sys.cities(0, \"temp\", \"{unit}\")")), + "{lowered}" + ); } - assert_eq!(octoscript_ui_l0::stale_sources(card, &["units"]), vec!["picks"]); - let empty = octoscript_ui_l0::realize(card, &serde_json::json!({"picks":[]}), Default::default()); + assert_eq!( + octoscript_ui_l0::stale_sources(card, &["units"]), + vec!["picks"] + ); + let empty = + octoscript_ui_l0::realize(card, &serde_json::json!({"picks":[]}), Default::default()); assert!(empty.complete_root().is_ok()); } @@ -501,7 +533,11 @@ fn weather_data() -> serde_json::Value { }) } -fn find<'a>(node: &'a octoscript_ui_l0::UiNode, kind: &str, out: &mut Vec<&'a octoscript_ui_l0::UiNode>) { +fn find<'a>( + node: &'a octoscript_ui_l0::UiNode, + kind: &str, + out: &mut Vec<&'a octoscript_ui_l0::UiNode>, +) { if node.kind == kind { out.push(node); } @@ -788,7 +824,8 @@ fn local_state_is_per_instance() { let mut store = octoscript_ui_l0::InstanceStore::default(); let data = two_rows_data(); - let first = octoscript_ui_l0::realize_with_state(TWO_ROWS, &data, &store, RealizeLimits::default()); + let first = + octoscript_ui_l0::realize_with_state(TWO_ROWS, &data, &store, RealizeLimits::default()); let root = first.root.expect("root"); let mut rows = Vec::new(); find(&root, "Row", &mut rows); @@ -824,7 +861,8 @@ fn local_state_is_per_instance() { fn a_schema_change_resets_instance_state() { let mut store = octoscript_ui_l0::InstanceStore::default(); let data = two_rows_data(); - let first = octoscript_ui_l0::realize_with_state(TWO_ROWS, &data, &store, RealizeLimits::default()); + let first = + octoscript_ui_l0::realize_with_state(TWO_ROWS, &data, &store, RealizeLimits::default()); let mut rows = Vec::new(); let first_root = first.root.unwrap(); find(&first_root, "Row", &mut rows); @@ -835,7 +873,8 @@ fn a_schema_change_resets_instance_state() { "state open { shape: bool, initial: false }", "state open { shape: bool, initial: false }\n state seen { shape: bool, initial: false }", ); - let after = octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); + let after = + octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); let mut captions = Vec::new(); let after_root = after.root.unwrap(); find(&after_root, "TextCaption", &mut captions); @@ -867,17 +906,20 @@ fn set_applies_the_event_payload() { let mut store = octoscript_ui_l0::InstanceStore::default(); let data = serde_json::json!({ "items": [{"id":"a","name":"A"},{"id":"b","name":"B"}] }); - let before = octoscript_ui_l0::realize_with_state(SETTER, &data, &store, RealizeLimits::default()); + let before = + octoscript_ui_l0::realize_with_state(SETTER, &data, &store, RealizeLimits::default()); let root = before.root.unwrap(); let mut rows = Vec::new(); find(&root, "Row", &mut rows); assert_eq!(rows.len(), 2, "the list should show both items"); let payload = serde_json::json!("b"); - let applied = octoscript_ui_l0::dispatch_with(SETTER, &mut store, "root", "choose", Some(&payload)); + let applied = + octoscript_ui_l0::dispatch_with(SETTER, &mut store, "root", "choose", Some(&payload)); assert!(applied, "choose should apply"); - let after = octoscript_ui_l0::realize_with_state(SETTER, &data, &store, RealizeLimits::default()); + let after = + octoscript_ui_l0::realize_with_state(SETTER, &data, &store, RealizeLimits::default()); let root = after.root.unwrap(); let mut titles = Vec::new(); find(&root, "TextTitle", &mut titles); @@ -929,7 +971,8 @@ fn unmounted_instances_are_pruned() { let mut store = octoscript_ui_l0::InstanceStore::default(); let two = serde_json::json!({ "items": [{"id":"a","name":"A"},{"id":"b","name":"B"}] }); - let report = octoscript_ui_l0::realize_with_state(TWO_ROWS, &two, &store, RealizeLimits::default()); + let report = + octoscript_ui_l0::realize_with_state(TWO_ROWS, &two, &store, RealizeLimits::default()); let root = report.root.unwrap(); let mut rows = Vec::new(); find(&root, "Row", &mut rows); @@ -940,7 +983,8 @@ fn unmounted_instances_are_pruned() { // `b` goes away. let one = serde_json::json!({ "items": [{"id":"a","name":"A"}] }); - let report = octoscript_ui_l0::realize_with_state(TWO_ROWS, &one, &store, RealizeLimits::default()); + let report = + octoscript_ui_l0::realize_with_state(TWO_ROWS, &one, &store, RealizeLimits::default()); store.prune(&report.live_keys); assert_eq!(store.len(), 1, "the departed instance's cell is dropped"); } @@ -1747,7 +1791,8 @@ view root Panel { for it in items key it.id { Rowy(item: it) } } "event flip", "state seen { shape: bool, initial: false }\n event flip", ); - let after = octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); + let after = + octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); let mut captions = Vec::new(); let after_root = after.root.unwrap(); find(&after_root, "TextCaption", &mut captions); @@ -1794,7 +1839,8 @@ view root Panel { for it in items key it.id { Rowy(item: it) } } "event flip { open: toggle }", "event flip { open: cycle(.shut, .ajar) }", ); - let after = octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); + let after = + octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); let mut captions = Vec::new(); let after_root = after.root.unwrap(); find(&after_root, "TextCaption", &mut captions); @@ -1812,7 +1858,8 @@ fn without_keep_a_schema_change_still_resets() { // for the two tests above — the opt-in must not have become the default. let mut store = octoscript_ui_l0::InstanceStore::default(); let data = two_rows_data(); - let first = octoscript_ui_l0::realize_with_state(TWO_ROWS, &data, &store, RealizeLimits::default()); + let first = + octoscript_ui_l0::realize_with_state(TWO_ROWS, &data, &store, RealizeLimits::default()); let mut rows = Vec::new(); let first_root = first.root.unwrap(); find(&first_root, "Row", &mut rows); @@ -1822,7 +1869,8 @@ fn without_keep_a_schema_change_still_resets() { "event flip", "state seen { shape: bool, initial: false }\n event flip", ); - let after = octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); + let after = + octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); let mut captions = Vec::new(); let after_root = after.root.unwrap(); find(&after_root, "TextCaption", &mut captions); @@ -1981,7 +2029,8 @@ fn state_survives_an_element_inserted_above() { let data = serde_json::json!({}); let mut store = octoscript_ui_l0::InstanceStore::default(); - let first = octoscript_ui_l0::realize_with_state(TOGGLY, &data, &store, RealizeLimits::default()); + let first = + octoscript_ui_l0::realize_with_state(TOGGLY, &data, &store, RealizeLimits::default()); let root = first.root.unwrap(); let mut rows = Vec::new(); find(&root, "Row", &mut rows); @@ -1995,7 +2044,8 @@ fn state_survives_an_element_inserted_above() { ); assert_ne!(edited, TOGGLY, "the edit must actually apply"); - let after = octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); + let after = + octoscript_ui_l0::realize_with_state(&edited, &data, &store, RealizeLimits::default()); let after_root = after.root.unwrap(); let mut rows_after = Vec::new(); find(&after_root, "Row", &mut rows_after); @@ -3730,7 +3780,8 @@ fn changing_an_initial_resets_live_component_state() { let data = serde_json::json!({"items": [{"id": "a", "name": "A"}]}); let mut store = octoscript_ui_l0::InstanceStore::default(); - let first = octoscript_ui_l0::realize_with_state(BEFORE, &data, &store, RealizeLimits::default()); + let first = + octoscript_ui_l0::realize_with_state(BEFORE, &data, &store, RealizeLimits::default()); let root = first.root.unwrap(); let mut rows = Vec::new(); find(&root, "Row", &mut rows); @@ -3743,7 +3794,8 @@ fn changing_an_initial_resets_live_component_state() { // Prove the caption is PRESENT before the edit. Without this, a dispatch // regression alone satisfies the assertion below and masks the schema // regression the test is named for. - let live = octoscript_ui_l0::realize_with_state(BEFORE, &data, &store, RealizeLimits::default()); + let live = + octoscript_ui_l0::realize_with_state(BEFORE, &data, &store, RealizeLimits::default()); let live_root = live.root.unwrap(); let mut before_caps = Vec::new(); find(&live_root, "TextCaption", &mut before_caps); @@ -8947,7 +8999,11 @@ fn a_value_guard_reports_the_call_that_answers_it() { assert!(checked.valid, "{:#?}", checked.diagnostics); let store = octoscript_ui_l0::InstanceStore::default(); - let bindings = octoscript_ui_l0::guard_bindings(GUARDED, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), &store); + let bindings = octoscript_ui_l0::guard_bindings( + GUARDED, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + &store, + ); // Every field under a `when`, once each — and nothing else. `place.lat` is // read by a source ARGUMENT, not by a guard, so it is not asked for @@ -8975,7 +9031,8 @@ fn a_value_guard_reports_the_call_that_answers_it() { .iter() .find(|g| g.field == "precip") .expect("precip is guarded"); - let call = octoscript_ui_l0::makepad::vm_call(&precip.binding).expect("this backend answers it"); + let call = + octoscript_ui_l0::makepad::vm_call(&precip.binding).expect("this backend answers it"); assert_eq!( call, "sys.weather(sys.geocodenum(\"Kyoto\", \"lat\"), sys.geocodenum(\"Kyoto\", \"lon\"), \ @@ -9003,7 +9060,12 @@ fn a_card_with_no_value_guards_asks_for_nothing() { assert!(checked.valid, "{:#?}", checked.diagnostics); let store = octoscript_ui_l0::InstanceStore::default(); assert!( - octoscript_ui_l0::guard_bindings(PLAIN, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), &store).is_empty(), + octoscript_ui_l0::guard_bindings( + PLAIN, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + &store + ) + .is_empty(), "`$state` is already injected and `units` is card state — neither is a fetch" ); } @@ -9023,9 +9085,13 @@ fn a_source_argument_is_not_rounded_to_one_decimal() { let checked = check_ui_l0_named("weather", PINNED); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(PINNED, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + PINNED, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); assert!( dsl.contains("sys.weather(37.7749, -122.4194,"), @@ -9057,9 +9123,13 @@ fn a_text_argument_that_names_another_source_stays_a_call() { let checked = check_ui_l0_named("weather", CARD); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(CARD, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + CARD, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); assert!( dsl.contains("sys.photo(sys.geocode(\"kyoto\", \"name\"))"), @@ -9086,9 +9156,13 @@ fn a_typed_query_is_never_executable() { let checked = check_ui_l0_named("youtube", CARD); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(CARD, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + CARD, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); assert!( dsl.contains("sys.video(\"sys.gps(lat)\""), @@ -9128,7 +9202,11 @@ fn a_card_that_gates_its_rows_on_a_state_can_still_learn_that_state() { assert!(checked.valid, "{:#?}", checked.diagnostics); let store = octoscript_ui_l0::InstanceStore::default(); - let probes = octoscript_ui_l0::guarded_state_bindings(GATED, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), &store); + let probes = octoscript_ui_l0::guarded_state_bindings( + GATED, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + &store, + ); assert_eq!(probes.len(), 1, "one source is gated: {probes:#?}"); assert_eq!(probes[0].source, "parks"); // EMPTY, deliberately: a lifecycle is a property of the fetch, so which field @@ -9166,7 +9244,12 @@ fn a_card_that_gates_its_rows_on_a_state_can_still_learn_that_state() { let checked = check_ui_l0_named("activity", PLAIN); assert!(checked.valid, "{:#?}", checked.diagnostics); assert!( - octoscript_ui_l0::guarded_state_bindings(PLAIN, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), &store).is_empty(), + octoscript_ui_l0::guarded_state_bindings( + PLAIN, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + &store + ) + .is_empty(), "an ungated source needs no probe — the tree walk already answers it" ); } @@ -9191,9 +9274,13 @@ fn the_weather_icon_is_given_a_number_and_the_text_a_word() { let checked = check_ui_l0_named("weather", CARD); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(CARD, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + CARD, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); // The icon gets the CODE. assert!( @@ -9235,9 +9322,13 @@ fn a_source_argument_follows_a_chain_of_sources() { let checked = check_ui_l0_named("weather-activity", CARD); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(CARD, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + CARD, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); // Three levels deep: photo <- weather <- geocode <- state. assert!( @@ -9285,9 +9376,13 @@ fn a_future_day_shifts_the_daily_fields_and_refuses_the_current_ones() { let checked = check_ui_l0_named("weather-activity", CARD); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(CARD, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + CARD, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); // The daily fields read tomorrow's row… for path in [ @@ -9328,9 +9423,13 @@ fn a_forecast_loop_rides_on_top_of_the_day() { let checked = check_ui_l0_named("weather", CARD); assert!(checked.valid, "{:#?}", checked.diagnostics); let dsl = octoscript_ui_l0::kit::lower( - &realize(CARD, &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), RealizeLimits::default()) - .root - .expect("realizes"), + &realize( + CARD, + &serde_json::json!({"sa":11,"sb":4242,"sc":-7}), + RealizeLimits::default(), + ) + .root + .expect("realizes"), ); assert!( dsl.contains("daily.temperature_2m_max.1") && dsl.contains("daily.temperature_2m_max.3"), diff --git a/crates/octoscript-worker/src/lib.rs b/crates/octoscript-worker/src/lib.rs index b5ce549..ee20593 100644 --- a/crates/octoscript-worker/src/lib.rs +++ b/crates/octoscript-worker/src/lib.rs @@ -17,7 +17,6 @@ use std::marker::PhantomData; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use serde::{de::DeserializeOwned, Serialize}; use octoscript_protocol::{ AuthenticatedWorkerMessage, CapabilityGrant, CapabilityManifest, OperationCompensationRequest, OperationCompensationResult, OperationDispatchRequest, OperationReconcileRequest, @@ -29,6 +28,7 @@ use octoscript_storage::{ AuthenticatedStore, AuthenticatedStoreError, FencedRollbackProtectedStore, StorageRecordKey, StorageRecordKeyError, }; +use serde::{de::DeserializeOwned, Serialize}; /// Default maximum reconciliation requests one worker accepts for one tool in /// a session. diff --git a/crates/octoscript-workflow/src/durable_events.rs b/crates/octoscript-workflow/src/durable_events.rs index c3e9638..38ce13e 100644 --- a/crates/octoscript-workflow/src/durable_events.rs +++ b/crates/octoscript-workflow/src/durable_events.rs @@ -12,10 +12,10 @@ use std::collections::VecDeque; use std::fmt::{self, Display, Formatter}; use std::num::NonZeroUsize; -use serde::{Deserialize, Serialize}; use octoscript_storage::{ AuthenticatedStore, AuthenticatedStoreError, RollbackProtectedStore, StorageRecordKey, }; +use serde::{Deserialize, Serialize}; use crate::{ is_valid_operation_token, WorkflowEvent, WorkflowEventBatch, WorkflowEventBatchError, diff --git a/crates/octoscript-workflow/src/lib.rs b/crates/octoscript-workflow/src/lib.rs index 421b768..5ee8ce7 100644 --- a/crates/octoscript-workflow/src/lib.rs +++ b/crates/octoscript-workflow/src/lib.rs @@ -42,8 +42,6 @@ use std::num::NonZeroUsize; use std::ops::Index; use std::sync::atomic::{AtomicU64, Ordering}; -use serde::de::{self, SeqAccess, Visitor}; -use serde::{Deserialize, Serialize}; use octoscript_capabilities::{ CapabilityLease, CapabilityLeaseError, CapabilityLeaseEvaluationError, CapabilityLeaseGrant, CapabilityRuntime, ExternalToolCancellationRequest, ExternalToolError, ExternalToolId, @@ -61,6 +59,8 @@ use octoscript_protocol::{ ProtocolError, SessionAuthenticator, SessionRole, ToolPayload, WorkerMessage, }; use octoscript_schema::{JsonSchema, SchemaViolation}; +use serde::de::{self, SeqAccess, Visitor}; +use serde::{Deserialize, Serialize}; static NEXT_ENGINE_ID: AtomicU64 = AtomicU64::new(1); @@ -7697,7 +7697,10 @@ mod tests { schema["x-octoscript"]["source_profile"], serde_json::json!(CANONICAL_PROFILE_ID) ); - assert_eq!(schema["x-octoscript"]["authority"], serde_json::json!("none")); + assert_eq!( + schema["x-octoscript"]["authority"], + serde_json::json!("none") + ); } #[test] @@ -8480,10 +8483,19 @@ mod tests { )); let audit = engine.runtime().audit(); assert_eq!(audit.len(), 3); - assert_eq!(audit[0].outcome, octoscript_capabilities::AuditOutcome::Allowed); - assert_eq!(audit[1].outcome, octoscript_capabilities::AuditOutcome::Allowed); + assert_eq!( + audit[0].outcome, + octoscript_capabilities::AuditOutcome::Allowed + ); + assert_eq!( + audit[1].outcome, + octoscript_capabilities::AuditOutcome::Allowed + ); assert_eq!(audit[2].tool, "text.current"); - assert_eq!(audit[2].outcome, octoscript_capabilities::AuditOutcome::Denied); + assert_eq!( + audit[2].outcome, + octoscript_capabilities::AuditOutcome::Denied + ); } #[test] diff --git a/crates/octoscript-workflow/src/mobile.rs b/crates/octoscript-workflow/src/mobile.rs index 3cdd27e..b586fda 100644 --- a/crates/octoscript-workflow/src/mobile.rs +++ b/crates/octoscript-workflow/src/mobile.rs @@ -14,7 +14,6 @@ use std::num::NonZeroUsize; -use serde::{de::DeserializeOwned, Serialize}; use octoscript_capabilities::{ fixed_file_catalog::FixedFileCatalog, AuditEventBatch, AuditEventCursorError, AuditLog, CapabilityCatalogLimits, CapabilityModule, CapabilityModuleCallHintReport, @@ -23,6 +22,7 @@ use octoscript_capabilities::{ ToolError, ToolMetadata, ToolPolicy, ToolRegistrationError, ToolRequest, }; use octoscript_core::{ExecutionLimits, RuntimeError}; +use serde::{de::DeserializeOwned, Serialize}; use crate::{ Approval, WorkflowCheckpoint, WorkflowData, WorkflowDataContract, WorkflowDraft, @@ -272,7 +272,8 @@ impl MobileWorkflowBuilder { handler: F, ) -> Result<(), ToolRegistrationError> where - F: FnMut(&octoscript_capabilities::JsonToolRequest) -> Result + 'static, + F: FnMut(&octoscript_capabilities::JsonToolRequest) -> Result + + 'static, { self.runtime .register_validated_json_tool(policy, metadata, contract, handler) @@ -1023,7 +1024,9 @@ mod tests { path: "mod.arithmetic.add".to_owned(), description: "Adds two reviewed integer fields.".to_owned(), call_mode: Some(octoscript_capabilities::CapabilityModuleMethodMode::Deferred), - call_shape: Some(octoscript_capabilities::CapabilityModuleCallShape::SingleJson), + call_shape: Some( + octoscript_capabilities::CapabilityModuleCallShape::SingleJson + ), input_fields: Some(vec![ octoscript_capabilities::CapabilityModuleInputFieldDescriptor { name: "left".to_owned(), diff --git a/crates/octoscript-workflow/src/multiplexed_worker.rs b/crates/octoscript-workflow/src/multiplexed_worker.rs index 827ec73..e377e98 100644 --- a/crates/octoscript-workflow/src/multiplexed_worker.rs +++ b/crates/octoscript-workflow/src/multiplexed_worker.rs @@ -178,7 +178,10 @@ mod tests { use super::*; use crate::{WorkflowEvent, WorkflowStep}; - fn suspended_engine() -> (WorkflowEngine, octoscript_capabilities::ExternalToolInvocation) { + fn suspended_engine() -> ( + WorkflowEngine, + octoscript_capabilities::ExternalToolInvocation, + ) { let mut runtime = CapabilityRuntime::default(); runtime .register_external_tool(ToolPolicy::new("work.run")) diff --git a/crates/octoscript-workflow/src/telemetry.rs b/crates/octoscript-workflow/src/telemetry.rs index f3b4882..51c295a 100644 --- a/crates/octoscript-workflow/src/telemetry.rs +++ b/crates/octoscript-workflow/src/telemetry.rs @@ -11,8 +11,8 @@ use std::fmt::{self, Display, Formatter}; use std::num::NonZeroUsize; use std::ops::Index; -use serde::{Deserialize, Serialize}; use octoscript_capabilities::{AuditEvent, AuditEventBatch}; +use serde::{Deserialize, Serialize}; use crate::{WorkflowEvent, WorkflowEventBatch}; diff --git a/crates/octoscript-workflow/src/telemetry/durable.rs b/crates/octoscript-workflow/src/telemetry/durable.rs index e8b93c0..4849467 100644 --- a/crates/octoscript-workflow/src/telemetry/durable.rs +++ b/crates/octoscript-workflow/src/telemetry/durable.rs @@ -9,13 +9,13 @@ use std::collections::{BTreeMap, VecDeque}; use std::fmt::{self, Display, Formatter}; use std::num::NonZeroUsize; -use serde::{Deserialize, Serialize}; use octoscript_capabilities::{ AuditEvent, AuditEventBatch, AuditEventValidationError, AuditOutcome, RetryClass, }; use octoscript_storage::{ AuthenticatedStore, AuthenticatedStoreError, RollbackProtectedStore, StorageRecordKey, }; +use serde::{Deserialize, Serialize}; use super::{ CrossStreamTelemetryBatch, CrossStreamTelemetryCursorError, CrossStreamTelemetryEvent, diff --git a/fuzz/fuzz_targets/execution.rs b/fuzz/fuzz_targets/execution.rs index e6be65f..82348b2 100644 --- a/fuzz/fuzz_targets/execution.rs +++ b/fuzz/fuzz_targets/execution.rs @@ -3,7 +3,9 @@ use std::time::Duration; use libfuzzer_sys::fuzz_target; -use octoscript_core::{check_syntax_named, ExecutionLimits, Runtime, DEFAULT_MAX_SCRIPT_HEAP_BYTES}; +use octoscript_core::{ + check_syntax_named, ExecutionLimits, Runtime, DEFAULT_MAX_SCRIPT_HEAP_BYTES, +}; const MAX_FUZZ_SOURCE_BYTES: usize = 8 * 1024; const MAX_FUZZ_SYNTAX_TOKENS: usize = 1_024; diff --git a/fuzz/fuzz_targets/mobile_dataflow.rs b/fuzz/fuzz_targets/mobile_dataflow.rs index b992077..1bdd6a3 100644 --- a/fuzz/fuzz_targets/mobile_dataflow.rs +++ b/fuzz/fuzz_targets/mobile_dataflow.rs @@ -3,9 +3,9 @@ use std::time::Duration; use libfuzzer_sys::fuzz_target; -use serde_json::{Map, Value}; use octoscript_capabilities::mobile::MobileRuntimeBuilder; use octoscript_core::{ExecutionLimits, DEFAULT_MAX_SCRIPT_HEAP_BYTES}; +use serde_json::{Map, Value}; const MAX_FUZZ_CASE_BYTES: usize = 16 * 1024; const MAX_FUZZ_SOURCE_BYTES: usize = 8 * 1024; diff --git a/fuzz/fuzz_targets/mobile_workflow.rs b/fuzz/fuzz_targets/mobile_workflow.rs index 3cfe180..2849a61 100644 --- a/fuzz/fuzz_targets/mobile_workflow.rs +++ b/fuzz/fuzz_targets/mobile_workflow.rs @@ -3,12 +3,12 @@ use std::time::Duration; use libfuzzer_sys::fuzz_target; -use serde_json::{Map, Value}; use octoscript_capabilities::CapabilityLeaseGrant; use octoscript_core::{ExecutionLimits, DEFAULT_MAX_SCRIPT_HEAP_BYTES}; use octoscript_workflow::{ mobile::MobileWorkflowBuilder, WorkflowData, WorkflowStep, WorkflowStepCapabilityPolicy, }; +use serde_json::{Map, Value}; const MAX_FUZZ_CASE_BYTES: usize = 16 * 1024; const MAX_FUZZ_SOURCE_BYTES: usize = 8 * 1024; From 2f193844cd0d2605a31fee5758e5cf0c298cd530 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:07:49 -0700 Subject: [PATCH 2/4] Give the network broker tests a socket path that fits Both linux_network_broker tests failed on every run with "path must be shorter than SUN_LEN". The broker appends `.octoscript-network-<32 hex>/broker.sock` to whatever directory it is given, 64 bytes, and a Unix socket path cannot reach 108. The test directory name spent 48 bytes on its own, so the bind could never succeed. Shortened to `octoscript-nb--`, which leaves the worst case at 93 bytes. Test code only; the broker's own naming is untouched. Co-Authored-By: Claude Opus 5 (1M context) --- crates/octoscript-capabilities/src/linux_network_broker.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/octoscript-capabilities/src/linux_network_broker.rs b/crates/octoscript-capabilities/src/linux_network_broker.rs index 608acbf..b3c9de0 100644 --- a/crates/octoscript-capabilities/src/linux_network_broker.rs +++ b/crates/octoscript-capabilities/src/linux_network_broker.rs @@ -766,8 +766,11 @@ mod tests { impl TestDirectory { fn new() -> Self { static NEXT: AtomicUsize = AtomicUsize::new(0); + // The broker appends `.octoscript-network-<32 hex>/broker.sock`, 64 + // bytes, and a Unix socket path cannot reach SUN_LEN (108). The old + // name spent 48 bytes here and the bind failed on every run. let path = std::env::temp_dir().join(format!( - "octoscript-linux-network-broker-test-{}-{}", + "octoscript-nb-{}-{}", std::process::id(), NEXT.fetch_add(1, Ordering::Relaxed) )); From 4ae8f99c16b3a088613dbc56a293df18e9569d11 Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:13:56 -0700 Subject: [PATCH 3/4] Document the three capabilities the TOML spec was missing `the_source_catalog_matches_the_toml_spec` and `the_field_vocabularies_match_the_toml_spec` both failed: Rust declares 32 capabilities and docs/ui-l0-constructors.toml described 29. The TOML is what the agent-facing catalog is generated from, so a capability that lives only in Rust is one the model writing cards never sees, and a card naming its fields would be refused for using documentation that was never offered. That is the drift these tests exist to catch, and it had happened to sys.news_digest, sys.news_status and sys.dataset. Each now carries its arguments, its answerable fields and a note on what it is for and why its shape differs from its neighbours. Co-Authored-By: Claude Opus 5 (1M context) --- docs/ui-l0-constructors.toml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/ui-l0-constructors.toml b/docs/ui-l0-constructors.toml index d3c06e9..360cc06 100644 --- a/docs/ui-l0-constructors.toml +++ b/docs/ui-l0-constructors.toml @@ -522,6 +522,29 @@ answers = ["id", "mag", "place", "depth", "ago", "lat", "lon"] args = ["id", "fields"] answers = ["id", "title", "author", "points", "comments", "url"] +[sources."sys.news_digest"] +# A search-shaped news feed: `query` and `language` choose what is fetched, +# where `sys.news` only pages a fixed feed. Row-shaped, so a card lists it with +# a `for` over `id`. +args = ["query", "language", "count", "fields"] +answers = ["id", "title", "summary", "publisher", "url", "published_at"] + +[sources."sys.news_status"] +# The state of the fetch behind `sys.news_digest`, for the same query. A digest +# arrives after the card is already on screen, so without this a card has no +# way to say whether it is waiting, empty or failed, and would render a blank +# list either way. +args = ["query", "language", "fields"] +answers = ["status", "message", "count"] + +[sources."sys.dataset"] +# One prepared research dataset, addressed by `id`. Flat rather than row-shaped: +# the fields are the slots of a single briefing (a headline pair of metrics, +# three picks with their sources, one evidence note), so a card reads them +# directly instead of looping. +args = ["id", "fields"] +answers = ["title", "subtitle", "summary", "coverage", "status", "as_of", "metric1_label", "metric1_value", "metric2_label", "metric2_value", "pick1_title", "pick1_body", "pick1_source", "url1", "pick2_title", "pick2_body", "pick2_source", "url2", "pick3_title", "pick3_body", "pick3_source", "url3", "evidence_title", "evidence_body"] + [sources."sys.movers"] # `symbols` names the universe to rank; omitted means the whole market. Without # it a themed request ("top AI movers") could only render market-wide gainers From dcd90375a27c267ca2c5bab9c417eb70f5ea0b8b Mon Sep 17 00:00:00 2001 From: ymote <151983+ymote@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:21:41 -0700 Subject: [PATCH 4/4] Build makepad-stitch optimised in the test profile, as makepad does `cargo test -p makepad-script` aborted with a stack overflow in math_aot's batch test. It is not a flaky limit: raising RUST_MIN_STACK to 32 MB does not help, because the depth follows the number of points in a chunk, and the same test passes in makepad's own workspace at the very same commit. The difference is a profile. makepad's root manifest carries `[profile.test.package.makepad-stitch] opt-level = 1`, and we build the same crate without it. makepad-stitch is a WebAssembly interpreter; unoptimised its frames are large enough that one chunk of a batched expression, 5461 points for a vec3, runs the test thread out of stack. The passing neighbour test uses 4097 points and stays under the cliff, which is why only one test showed it. Mirrors upstream rather than inventing a workaround. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f905b2a..eec66e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,12 @@ rust-version = "1.85" [workspace.lints.rust] unsafe_code = "forbid" unexpected_cfgs = { level = "warn", check-cfg = ["cfg(fuzzing)"] } + +# makepad's own workspace carries this and we build the same crate, so without +# it `cargo test -p makepad-script` differs from upstream's. makepad-stitch is a +# WebAssembly interpreter; unoptimised its frames are large enough that one +# chunk of a batched math expression (5461 points for a vec3) overflows a test +# thread's stack, and math_aot's batch test aborts. Raising the stack does not +# help, because the depth follows the point count. +[profile.test.package.makepad-stitch] +opt-level = 1