Skip to content

fix(engine): report unimplemented workflow capabilities instead of refusing to load - #1790

Merged
justinchuby merged 3 commits into
mainfrom
fix/capability-gate-warning
Aug 23, 2026
Merged

justinchuby merged 3 commits into
mainfrom
fix/capability-gate-warning

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 23, 2026 •

Copy link
Copy Markdown
Owner

Problem

admit_inference_metadata failed the load whenever the package declared a capability this runtime has not implemented. In practice that rejected every newly-exported mobius package, which declares eight workflow capabilities:

bounded_state_recurrence, emit_valid_length, linear_effects, loop_induction_values,
nested_control_flow, serving_service_contract, typed_emit, workflow_ssa

The rule this PR applies

Not "capabilities are advisory" — that would be wrong, and review caught exactly why. The split is on one question:

Does the capability describe the document, or prescribe computation this path must perform?

derived from on the bare-decode path verdict
Descriptive — workflow_ssa, serving_service_contract, bounded_state_recurrence, typed_emit, emit_valid_length, linear_effects, loop_induction_values, nested_control_flow the workflow spec this path derives its IO from the graph and never interprets the manifest warn, continue
Prescriptive — parameter_adapters, heterogeneous_adapter_batching metadata.adapters or the workflow spec this path never applies the overlay; decoding anyway emits base-model output fatal

Structural defects remain fatal throughout.

Evidence for the descriptive half

I relaxed the gate locally and ran a previously-rejected package end-to-end on the native CUDA backend:

profile_native --model hy-mt2-b128 --ep cuda --runs 1 --tokens 24 --prompt "The capital of France is"
throughput: 7.86 tok/s, 127.251 ms/step
generated_token_ids: [13847, 13, 185, 66410, 316, 244, 5069, 280, 9391, 13, 185, 66410, ...]

It loads, builds a native decode session, executes, and produces a coherent stream whose first token (13847) matches the same model exported without a workflow manifest. The gate was refusing work that runs.

Review then confirmed against the code that all eight derive from workflow spec shape (validation.rs:157-330), and that bounded_state_recurrence in particular comes from workflow.state[..].recurrence — not from the model's KV/attention geometry, which the decode path takes from metadata.decoder_io() / graph derivation without ever reading workflow.state.

Why the prescriptive half stays fatal

The two adapter capabilities are a concrete weight overlay, not a description. The only consumers of .adapters are pipeline/adapters.rs and pipeline/mod.rs — the separate PipelineRuntime, not this path. A well-formed LoRA package would otherwise have loaded here and silently produced base-model output: wrong output, not degraded output, with nothing for the caller to notice.

They are keyed on capability name, not on metadata.adapters.is_some(), because workflow_required_capabilities derives the same two capabilities from the workflow spec with no adapter service present (validation.rs:201 for adapter-segment/count/scale input roles, validation.rs:232 for an onnx-genai.parameter-overlay component). An is_some() check would let those workflow-declared overlays through with only a warning — the exact bug this guards against. That case now has its own test.

Scope

Both admit_inference_metadata callers are bare-decode (native load.rs:370, ORT load.rs:1188). The workflow/serving runtime (PipelineRuntime) does not call it, so this does not weaken a serving path.

Complementary to #1723 (native backend for the universal pipeline.workflow runtime): as that lands these capabilities become genuinely implemented and the warnings stop firing on their own. No file overlap — this touches only engine/load.rs.

Per maintainer decision, no kind: discriminator is added: the workflow shape is the direction for all packages, so there is nothing to discriminate between.

Tests

cargo test -p onnx-genai-engine --lib → 435 passed, 0 failed.

  • unsupported_declared_capability_is_reported_but_does_not_block_decode
  • workflow_manifest_capabilities_do_not_block_the_bare_decoder
  • declared_adapters_fail_closed_because_this_path_cannot_apply_them — structurally valid adapter service (real target manifest, selection contract, artifact)
  • workflow_declared_parameter_overlay_also_fails_closed_without_an_adapter_service — asserts metadata.adapters.is_none(), pinning the case that makes the explicit capability list correct
  • structural_defect_still_fails_closed — asserts the specific unknown-component defect, not just the generic prefix

The two adapter tests each assert the error is not a structural one before asserting the guard message. That guard was not cosmetic: four successive drafts of those fixtures were structurally invalid (wrong field names, component: decoder instead of model, empty artifacts, artifact paths outside adapters/{name}/, unpinned adapter ABI) and would otherwise have "passed" for the wrong reason.

Closes #1767

…fusing to load

The canonical package describes a workflow, and the capabilities it declares
(workflow_ssa, serving_service_contract, bounded_state_recurrence, typed_emit,
emit_valid_length, linear_effects, loop_induction_values, nested_control_flow)
describe that manifest -- not extra computation the bare-decoder path performs.
That path derives its IO from the graph and never interprets the manifest, so
failing admission over a capability the runtime has not implemented rejects
packages that decode correctly.

Measured: a mobius-exported package declaring all eight capabilities was
rejected at admission; with the gate relaxed it loads, builds a native CUDA
decode session, and produces a coherent token stream whose first token matches
the same model exported without a workflow manifest.

Structural defects stay fatal -- a document that does not describe a runnable
model still fails closed, and that case keeps dedicated coverage.

Closes #1767

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d60eb808-7cc6-4abc-b48d-2a6dd3841624
@codecov

codecov Bot commented Aug 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.28%. Comparing base (b9d9c48) to head (1f4afb8).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1790      +/-   ##
==========================================
+ Coverage   80.19%   80.28%   +0.09%     
==========================================
  Files         411      411              
  Lines      199146   200160    +1014     
  Branches   199146   200160    +1014     
==========================================
+ Hits       159706   160702     +996     
+ Misses      33996    33994       -2     
- Partials     5444     5464      +20     
Flag Coverage Δ
cli-ort-linux 72.47% <ø> (ø)
cli-ort-windows 71.97% <ø> (-0.10%) ⬇️
mlas 85.20% <ø> (ø)
offline 80.42% <ø> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 17 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI added 2 commits August 22, 2026 18:18
… warn

Review of the first commit found a real defect: relaxing the whole capability
set also relaxed `parameter_adapters` / `heterogeneous_adapter_batching`, which
`metadata_only_required_capabilities` derives whenever `metadata.adapters` is
present. The bare-decode path never reads `metadata.adapters`, so an adapter
package would have loaded and silently emitted BASE-MODEL output instead of the
adapted output it describes -- wrong output, not degraded output, with nothing
for the caller to notice.

Split the set on one question: does the capability describe the document, or
prescribe computation this path must perform?

- Descriptive (workflow manifest: workflow_ssa, serving_service_contract,
  bounded_state_recurrence, typed_emit, emit_valid_length, linear_effects,
  loop_induction_values, nested_control_flow) -> warn and continue. These are
  derived from the workflow spec, and this path derives its IO from the graph
  and never interprets that manifest.
- Prescriptive (parameter_adapters, heterogeneous_adapter_batching) -> fatal.
  These are a concrete weight overlay, not a description.

Adds a regression test using a structurally VALID adapter service, so the
adapter guard is provably what rejects it rather than an unrelated structural
defect, and tightens the structural test to assert the specific unknown-component
defect instead of only the generic prefix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d60eb808-7cc6-4abc-b48d-2a6dd3841624
Review pointed out that nothing in the suite distinguished the explicit
PRESCRIPTIVE capability list from a `metadata.adapters.is_some()` check, even
though that distinction is the whole reason the list is keyed on capability
names.

`workflow_required_capabilities` also derives `parameter_adapters` (from a
component whose adapter ABI or contract id is `onnx-genai.parameter-overlay`)
and `heterogeneous_adapter_batching` (from adapter-segment/count/scale input
roles) with `metadata.adapters` unset. Keying off the service would let those
workflow-declared overlays through with only a warning, which carries the
identical silent base-model-output risk.

Adds a fixture that declares such an overlay with no adapter service, asserts
`metadata.adapters.is_none()` so it provably exercises the workflow-derived
path, and asserts the bail names `parameter_adapters`. Expands the code comment
to record this rationale.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: d60eb808-7cc6-4abc-b48d-2a6dd3841624
@justinchuby
justinchuby enabled auto-merge (squash) August 23, 2026 01:36
@justinchuby
justinchuby merged commit 752398b into main Aug 23, 2026
16 of 17 checks passed
@justinchuby
justinchuby deleted the fix/capability-gate-warning branch August 23, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Capability gate rejects mobius packages that decode correctly (regression of #1715); metadata also ships two shapes under one schema_version

2 participants