Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7e0dd20
fix(scheduler): box runtime execution intent payload
MrScripty Oct 3, 2026
5218c1a
fix(media): accept named conversion result input
MrScripty Oct 3, 2026
cac290b
fix(inference): preserve behavior while clearing annotation and style…
MrScripty Oct 3, 2026
58df816
fix(inference): box managed diagnostic and planned image payloads
MrScripty Oct 3, 2026
1cae42e
fix(ci): execute feature-gated PyTorch projection tests
MrScripty Oct 3, 2026
4bbf55e
refactor(inference): group private gateway lifecycle contexts
MrScripty Oct 3, 2026
23c0c14
fix(inference): box two concrete error return boundaries
MrScripty Oct 3, 2026
fb61592
refactor(inference): name PyTorch text generation inputs
MrScripty Oct 3, 2026
9458fac
refactor(inference): reuse effective llama runtime settings
MrScripty Oct 3, 2026
c277a0b
docs(inference): clarify default-deny remote code policy
MrScripty Oct 3, 2026
92ca205
refactor(node-engine): group validation lifecycle attribution
MrScripty Oct 3, 2026
a6fa075
test(node-engine): disambiguate mock backend result type
MrScripty Oct 3, 2026
3c4dbbd
refactor(runtime-host): implement standard borrowed accessors
MrScripty Oct 3, 2026
395596f
refactor(diagnostics): reuse projection state input
MrScripty Oct 3, 2026
4433482
refactor(diagnostics): box inference event payload
MrScripty Oct 3, 2026
3d8de84
refactor(diagnostics): box artifact event payload
MrScripty Oct 3, 2026
89ffaef
refactor(workflow): box private no-selection error payload
MrScripty Oct 3, 2026
25de27e
refactor(workflow): preserve contracts while clearing style lints
MrScripty Oct 3, 2026
b4637a4
refactor(workflow): box private request and completion payloads
MrScripty Oct 3, 2026
09ae318
refactor(workflow): box ready inference projection payload
MrScripty Oct 3, 2026
0c08539
refactor(workflow): group terminal diagnostic inputs
MrScripty Oct 3, 2026
48b728c
refactor(workflow): group private session run context
MrScripty Oct 3, 2026
533a80b
refactor(workflow): clarify private projection and summary types
MrScripty Oct 3, 2026
c369cbe
refactor(registry): box private selection diagnostic errors
MrScripty Oct 3, 2026
2a4d5c8
style(rust): apply pinned formatter to repair milestones
MrScripty Oct 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/quality-gates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,108 @@ jobs:
- name: Check scheduler standard-trait accessor lint
run: cargo clippy -p pantograph-scheduler --all-targets -- -D clippy::should_implement_trait

- name: Run runtime-host contract and accessor compatibility tests
run: cargo test -p pantograph-runtime-host-contracts
- name: Check runtime-host standard-trait accessor lint
run: cargo clippy -p pantograph-runtime-host-contracts --all-targets -- -D clippy::should_implement_trait
- name: Run diagnostics ledger contract tests
run: cargo test -p pantograph-diagnostics-ledger
- name: Run media conversion contract tests
run: cargo test -p pantograph-media-conversion

- name: Run inference compatibility and contract tests
run: |
cargo test -p inference --lib backend::compatibility::tests
cargo test -p inference --lib model_contracts::tests
cargo test -p inference --lib server::tests::runtime_matchers_preserve_path_component_comparison

- name: Run inference payload serialization and planner tests
run: |
cargo test -p inference --lib managed_runtime::contracts::tests
cargo test -p inference --lib image_generation_planner_tests
cargo test -p inference --features backend-pytorch --lib pytorch_worker_image_contract_tests -- --list > "$RUNNER_TEMP/pytorch-image-contract-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("backend::pytorch::pytorch_worker_image_contract_tests::") and line.endswith(": test")]; print("PyTorch image contract tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/pytorch-image-contract-tests.list"
cargo test -p inference --features backend-pytorch --lib pytorch_worker_image_contract_tests

- name: Run gateway attribution and warmup regression tests
run: |
cargo test -p inference --lib gateway::tests::private_lifecycle_context
cargo test -p inference --lib gateway::tests::test_runtime_lifecycle_snapshot
cargo test -p inference --lib gateway::tests::test_failed_restart_clears_active_runtime_config_and_attempted_modes

- name: Run typed error boundary regression tests
run: |
cargo test -p inference --lib managed_binaries::tests
cargo test -p inference --test model_contracts task_registry_resolution_

- name: Run named PyTorch text request contract tests
run: |
cargo test -p inference --features backend-pytorch --lib pytorch_named_text_request -- --list > "$RUNNER_TEMP/pytorch-named-text-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("backend::pytorch::tests::pytorch_named_text_request") and line.endswith(": test")]; print("Named PyTorch text tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/pytorch-named-text-tests.list"
cargo test -p inference --features backend-pytorch --lib pytorch_named_text_request
cargo test -p inference --features backend-pytorch --lib test_pytorch_generate_text_

- name: Run llama server startup and reuse contract tests
run: |
for test_filter in server::tests backend::llamacpp::tests; do
cargo test -p inference --features backend-llamacpp --lib "$test_filter" -- --list > "$RUNNER_TEMP/llama-settings-tests.list"
python3 -c 'import pathlib, sys; prefix = sys.argv[2] + "::"; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith(prefix) and line.endswith(": test")]; print(prefix, "tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/llama-settings-tests.list" "$test_filter"
cargo test -p inference --features backend-llamacpp --lib "$test_filter"
done

- name: Run contract-only node lifecycle tests
run: |
cargo test -p node-engine --features inference-nodes --lib rejects_contract_only_with_lifecycle -- --list > "$RUNNER_TEMP/node-validation-lifecycle-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("core_executor::tests::inference_tests::") and "rejects_contract_only_with_lifecycle" in line and line.endswith(": test")]; print("Contract-only lifecycle tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/node-validation-lifecycle-tests.list"
cargo test -p node-engine --features inference-nodes --lib rejects_contract_only_with_lifecycle

- name: Run node-engine tests
run: cargo test -p node-engine --lib

- name: Run workflow-nodes tests
run: cargo test -p workflow-nodes --lib

- name: Run runtime registry contract tests
run: cargo test -p pantograph-runtime-registry
- name: Run task summary contract tests
run: |
cargo test -p pantograph-workflow-service --lib workflow::task_run_summary::tests:: -- --list > "$RUNNER_TEMP/task-summary-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("workflow::task_run_summary::tests::") and line.endswith(": test")]; print("Task summary tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/task-summary-tests.list"
cargo test -p pantograph-workflow-service --lib workflow::task_run_summary::tests::
- name: Run complete session execution regression suite
run: |
cargo test -p pantograph-workflow-service --lib workflow::tests::session_execution:: -- --list > "$RUNNER_TEMP/session-execution-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("workflow::tests::session_execution::") and line.endswith(": test")]; print("Session execution tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/session-execution-tests.list"
cargo test -p pantograph-workflow-service --lib workflow::tests::session_execution::
- name: Run terminal diagnostic regression suite
run: |
cargo test -p pantograph-workflow-service --lib workflow::runtime_branch_run_finalization::tests:: -- --list > "$RUNNER_TEMP/terminal-diagnostic-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("workflow::runtime_branch_run_finalization::tests::") and line.endswith(": test")]; print("Terminal diagnostic tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/terminal-diagnostic-tests.list"
cargo test -p pantograph-workflow-service --lib workflow::runtime_branch_run_finalization::tests::
- name: Run workflow inference projection regression suites
run: |
for module in workflow::tests::task_graph:: workflow::tests::task_binding_resolution:: workflow::executable_validation_snapshot::tests::; do
cargo test -p pantograph-workflow-service --lib "$module" -- --list > "$RUNNER_TEMP/projection-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith(sys.argv[2]) and line.endswith(": test")]; print(sys.argv[2], "tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/projection-tests.list" "$module"
cargo test -p pantograph-workflow-service --lib "$module"
done
- name: Run private workflow owner regression suites
run: |
for module in graph::inference_validation_state::tests:: workflow::task_execution_worker::tests::; do
cargo test -p pantograph-workflow-service --lib "$module" -- --list > "$RUNNER_TEMP/private-owner-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith(sys.argv[2]) and line.endswith(": test")]; print(sys.argv[2], "tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/private-owner-tests.list" "$module"
cargo test -p pantograph-workflow-service --lib "$module"
done
- name: Run workflow style contract regressions
run: |
cargo test -p pantograph-workflow-service --lib lint_style_ -- --list > "$RUNNER_TEMP/workflow-style-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if "lint_style_regressions::lint_style_" in line and line.endswith(": test")]; print("Workflow style tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/workflow-style-tests.list"
cargo test -p pantograph-workflow-service --lib lint_style_
- name: Run workflow scheduler orchestrator regression tests
run: |
cargo test -p pantograph-workflow-service --lib scheduler::task_orchestrator::tests:: -- --list > "$RUNNER_TEMP/orchestrator-tests.list"
python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("scheduler::task_orchestrator::tests::") and line.endswith(": test")]; print("Orchestrator tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/orchestrator-tests.list"
cargo test -p pantograph-workflow-service --lib scheduler::task_orchestrator::tests::
- name: Run workflow-service contract tests
run: cargo test -p pantograph-workflow-service --test contract

Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ The format is based on Keep a Changelog.
canonicalization records.

### Changed
- Rust source API: `LlamaServer::start_sidecar_inference` and
`matches_inference_runtime` now borrow the existing `LlamaCppRuntimeSettings`
for effective device/context/thread/batch values. Model/mmproj paths, process
spawner and port override retain their existing roles and ownership.
- Rust source API: PyTorch `generate_with_top_k` and `generate_stream_with_top_k`
now accept `PyTorchTextGenerationRequest` with the same seven named inputs.
Legacy `generate`/`generate_stream` signatures and the worker wire contract
remain unchanged.
- Rust source API: `resolve_managed_binary_command` and
`resolve_task_registry_entry_from_evidence` now return boxed concrete errors.
Error variants, diagnostic JSON and Display text remain unchanged; callers
matching an owned error must dereference the box.
- Rust source API: `ManagedRuntimeCommandResolutionError::MissingRuntimeVariant.diagnostic`
and `ImageGenerationPlanningOutcome::Planned.plan` now hold boxed payloads.
Direct constructors must box their values and consuming plan callers must unbox;
tagged JSON, diagnostic fields, and error Display text are unchanged.
- Rust source API: `MediaConversionResult::try_new` now accepts one
`MediaConversionResultInput` with the same eight named fields instead of eight
positional arguments. Result fields and validation behavior are unchanged.
- Rust source API: `SchedulerTaskExecutionIntent::Runtime.task_intent` is now boxed.
Construct runtime variants with `SchedulerTaskExecutionIntent::runtime(intent)`;
direct struct-variant callers must pass `Box::new(intent)`. Serialized JSON and
borrowed `runtime_task_intent()` access remain unchanged.
- Root project `README.md` reorganized around install, usage, development, and contribution workflows.
- Documentation consolidated around current guides, accepted decisions, audits,
and active plans; superseded narration remains available in Git history.
Expand All @@ -24,3 +47,19 @@ The format is based on Keep a Changelog.

### Security
- Canonical path validation enforced at file-boundary entry points to block traversal and symlink escape paths.

### Runtime-host borrowed accessor compatibility

The seven validated runtime-host contract wrappers now implement standard `AsRef<Raw>` in place of inherent `as_ref` methods. Borrowed values and lifetimes, wrapper types, validation and `into_inner` are unchanged. Ordinary method syntax, qualified calls and function pointers continue to resolve through the standard prelude. Rust callers using `no_implicit_prelude` must explicitly import `std::convert::AsRef`. The workspace crate remains unpublished (`publish = false`).

### Diagnostic event inference payload construction

`DiagnosticEventPayload::InferenceExecutionDiagnosticObserved` now stores `Box<InferenceExecutionDiagnosticObservedPayload>`. Rust callers constructing this public variant must wrap the existing raw payload in `Box::new`; owned pattern bindings now contain a box. Tagged serialized JSON, raw payload fields and validation remain unchanged. This adds one allocation for this event variant; no measured performance improvement is claimed. The crate remains `publish = false`.

### Diagnostic event artifact payload construction

`DiagnosticEventPayload::IoArtifactObserved` now stores `Box<IoArtifactObservedPayload>`. Rust callers constructing the public variant use `Box::new`, and owned pattern bindings contain a box. Raw DTO fields, validation and tagged JSON stay unchanged; this adds one allocation for this variant without a measured performance claim. The crate remains `publish = false`.

### Workflow ready inference projection construction

`WorkflowSchedulerInferenceTaskProjection::Ready` now holds `Box<WorkflowSchedulerReadyInferenceTaskProjection>`. Rust constructors use `Box::new`, and owned pattern bindings contain a box; the raw record, equality, borrowed readers and downstream scheduler intent serialization remain unchanged. The projection enum itself has no Serde implementation. This adds one allocation for ready projections without a measured performance claim; the crate remains `publish = false`.
49 changes: 31 additions & 18 deletions crates/inference/src/backend/compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,21 +181,25 @@ impl BackendCapabilities {
&mut issues,
);
let option_diagnostics = self.option_compatibility_diagnostics(backend_key, &request);
issues.extend(option_diagnostics.iter().filter_map(|diagnostic| {
matches!(
&diagnostic.state,
OptionSupportState::Unsupported | OptionSupportState::Rejected
)
.then(|| BackendCompatibilityIssue {
kind: BackendCompatibilityIssueKind::UnsupportedOption,
phase: InferenceLifecyclePhase::TaskValidation,
message: diagnostic.message.clone().unwrap_or_else(|| {
format!("option {} is not supported", diagnostic.option_path)
issues.extend(
option_diagnostics
.iter()
.filter(|diagnostic| {
matches!(
&diagnostic.state,
OptionSupportState::Unsupported | OptionSupportState::Rejected
)
})
.map(|diagnostic| BackendCompatibilityIssue {
kind: BackendCompatibilityIssueKind::UnsupportedOption,
phase: InferenceLifecyclePhase::TaskValidation,
message: diagnostic.message.clone().unwrap_or_else(|| {
format!("option {} is not supported", diagnostic.option_path)
}),
model_id: Some(request.package_facts.model_ref.model_id.clone()),
path: None,
}),
model_id: Some(request.package_facts.model_ref.model_id.clone()),
path: None,
})
}));
);
let compatible = [task, model_source, preprocessing, postprocessing]
.into_iter()
.all(|status| status == BackendCompatibilityStatus::Supported)
Expand Down Expand Up @@ -452,10 +456,7 @@ fn compatibility_report_status_label(report: &BackendCompatibilityReport) -> &'s
report.preprocessing,
report.postprocessing,
];
if statuses
.iter()
.any(|status| *status == BackendCompatibilityStatus::Unsupported)
{
if statuses.contains(&BackendCompatibilityStatus::Unsupported) {
"rejected"
} else {
"degraded"
Expand Down Expand Up @@ -909,6 +910,7 @@ mod tests {
Some("llama_cpp"),
BackendCompatibilityRequest::new(&task, &package).with_options(
BackendCompatibilityOptions {
streaming: true,
cache: CacheGenerationOptions {
use_cache: Some(true),
kv_cache_checkpoint_requested: Some(true),
Expand All @@ -919,6 +921,17 @@ mod tests {
);

assert!(!report.compatible);
assert_eq!(
report
.issues
.iter()
.map(|issue| issue.message.as_str())
.collect::<Vec<_>>(),
vec![
"option cache.use_cache is not supported by this backend",
"option cache.kv_cache_checkpoint_requested is not supported by this backend",
],
);
assert!(report.option_diagnostics.iter().any(|diagnostic| {
diagnostic.option_path == "cache.use_cache"
&& matches!(&diagnostic.state, OptionSupportState::Unsupported)
Expand Down
13 changes: 2 additions & 11 deletions crates/inference/src/backend/llamacpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ impl InferenceBackend for LlamaCppBackend {

let runtime_settings = LlamaCppRuntimeSettings::try_from_backend_config(config)?;
let device_config = runtime_settings.device_config();
let context_size = runtime_settings.context_size;

if config.embedding_mode {
// Start in embedding mode
Expand Down Expand Up @@ -289,11 +288,7 @@ impl InferenceBackend for LlamaCppBackend {
if self.server.matches_inference_runtime(
&model_path.to_string_lossy(),
mmproj_path.as_deref(),
&device_config,
context_size,
runtime_settings.cpu_threads,
runtime_settings.batch_size,
runtime_settings.ubatch_size,
&runtime_settings,
config.port_override,
) {
return Ok(BackendStartOutcome {
Expand All @@ -307,11 +302,7 @@ impl InferenceBackend for LlamaCppBackend {
spawner,
&model_path.to_string_lossy(),
mmproj_path.as_deref(),
&device_config,
context_size,
runtime_settings.cpu_threads,
runtime_settings.batch_size,
runtime_settings.ubatch_size,
&runtime_settings,
config.port_override,
)
.await
Expand Down
2 changes: 1 addition & 1 deletion crates/inference/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub use llamacpp::LlamaCppBackend;
pub use candle::CandleBackend;

#[cfg(feature = "backend-pytorch")]
pub use pytorch::PyTorchBackend;
pub use pytorch::{PyTorchBackend, PyTorchTextGenerationRequest};

pub use compatibility::{
BackendCompatibilityIssue, BackendCompatibilityIssueKind, BackendCompatibilityOptions,
Expand Down
Loading
Loading