Retain Machines provider causes instead of flattening them into strings
Class
G: lost causes. machines::identity::map_error dropped the provider cause:
ProviderError | Before |
|---|---|
Rejected(msg) | Invalid(msg), which can't be told apart from a malformed request |
Failed | Storage("Machines operation failed") |
Cancelled | Storage("Machines operation was cancelled") |
CommandIndeterminate(machine) | Storage(format!(.. {machine})); the machine id survives only as text |
Indeterminate(_) or Unavailable with no operation | Storage("Machines outcome is unknown") |
Storage is also the class that the accepted-effect arms treat as uncertain. So a definitive provider Failed was indistinguishable from a lost reply, and clients had to parse strings to recover anything.
Invariant
A Machines failure keeps its exact ProviderError until it reaches the wire.
- The cause is kept. New
Error::Machines(ProviderError)(behind themachinesfeature, with#[source]) carries every cause that has no operation receipt. - One exhaustive match derives the wire class:
contract_errors::machines_error_code.- NotFound, Conflict, Unsupported and Invalid map to themselves; Rejected maps to Invalid.
- All three indeterminate forms map to Indeterminate.
- Unavailable, Failed and Cancelled map to Storage. Cancelled becomes
Cancelledonce #864 lands.
- Only receipted uncertainty becomes
Error::Indeterminate. That meansOperationIndeterminate(op)(the provider operation), or an uncertain mutation (Indeterminate,Unavailable,CommandIndeterminate) whose harness operation is known. Everything else is retained, never turned into text. - Not a native failure:
Error::Machinesis excluded fromis_native_failure.
Why it can't recur
map_errorhas a single catch-all arm,(cause, _) => Error::Machines(cause), which can only keep the cause. It no longer builds any string.- A new
ProviderErrorvariant fails to compile inmachines_error_codeuntil it is classified.
Historical findings
- Same collapse as rev-833-verdict.md:3: a typed failure becomes text plus a generic code, so clients parse strings.
- The coordinator flagged this site after #864.
Deletions
16 lines deleted: 6 string-building conversions and a three-way, operation-dependent Storage fallback. 98 lines added, 62 of them tests.
Verification
All on bb1, debug, --features filesystem-local,native-execution,grpc,machines,test-support.
| Filter | Result | Time |
|---|---|---|
machines:: | 5 passed | 18 ms |
executor:: | 82 passed | 4.5 s |
grpc:: | 3 passed | 78 ms |
- New exhaustive table:
provider_causes_survive_and_only_receipted_uncertainty_becomes_indeterminateruns all tenProviderErrorvariants, each with and without a receipt. With no operation, each must be retained asError::Machines(cause)with the expected wire code. With an operation, exactly the three uncertain causes must becomeIndeterminate(receipt); every definitive cause,Failedincluded, must stay retained. MappingFailedtoIndeterminate, or any cause to a string variant, fails it. - Real-path check:
verifier_rejects_*reads a destroyed checkpoint throughSimulatedMachines. It now assertsError::Machines(ProviderError::NotFound(_))with codeNotFound. - Builds: the test build took 861 s. The Windows check (
cargo check --locked -p acyclic-harness --lib --tests --features <same> --target x86_64-pc-windows-gnu, 249 s) and the WASM check (--features wasm,filesystem --target wasm32-unknown-unknown, 36 s) passed. - Unrelated failure: the
forkfilter ran 46 tests and one failed,filesystem::memory::summary_tests::publication::summary_fork_captures_private_payloads_and_binds_publication. It asserts a success payload, and that path neither uses Machines nor goes throughmap_error.
Decisions
- Overlap with #864: #864 edits the old
Cancelledarm inidentity.rs, which this PR replaces. Whichever merges second needs a one-line resolution. If #864 merges first, I'll merge main and mapProviderError::CancelledtoErrorCode::Cancelledinmachines_error_code. Thecontract_errors.rshunks don't overlap with #864 or #833. - Kept as harness variants: local validation errors (foreign provider, bad key length) stay
Invalid, and the "wrong outcome" protocol violations inhost.rsstayStorage. Neither comes from a provider cause.
🤖 Generated with Claude Code
Summary by cubic
Stops flattening Machines provider causes into strings and generic storage errors. map_error now keeps the exact ProviderError in a new Error::Machines variant, so definitive failures are no longer indistinguishable from lost replies.
Key changes
- Adds
Error::Machines(ProviderError)behind themachinesfeature, carrying the cause with#[source]. machines_error_codederives the wire code from the cause via one exhaustive match:NotFound,Conflict,Unsupported, andInvalidmap to themselves;RejectedtoInvalid;CancelledtoCancelled; the three indeterminate forms toIndeterminate;UnavailableandFailedtoStorage.- Only an uncertain mutation with a known harness operation becomes
Error::Indeterminate;OperationIndeterminatekeeps its own operation receipt. - Retained causes are excluded from
is_native_failure, so they still propagate through the normal error path.