1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Error type for hostlib host calls.
//!
//! Builtins translate this into VM-level errors via [`Into<harn_vm::VmError>`]
//! so that Harn scripts see structured exceptions rather than panics.
use harn_vm::VmDictExt;
use harn_vm::{VmError, VmValue};
/// All errors a hostlib builtin can surface.
///
/// Variants intentionally describe the *kind* of failure rather than the
/// specific module — every module routes its missing-implementation errors
/// through [`HostlibError::Unimplemented`] so embedders and tests can
/// distinguish intentionally scaffolded contracts from runtime failures.
#[derive(Debug, thiserror::Error)]
pub enum HostlibError {
/// The method exists in the registration table but has no implementation
/// yet. This is the canonical scaffold-stage error: it tells callers
/// "the contract is stable, but this module has not been implemented."
#[error(
"hostlib: {builtin} is not implemented yet (scaffolded contract without an implementation)"
)]
Unimplemented {
/// Fully-qualified builtin name, e.g. `"hostlib_ast_parse_file"`.
builtin: &'static str,
},
/// A required parameter was missing from the call payload.
#[error("hostlib: {builtin}: missing required parameter '{param}'")]
MissingParameter {
/// Fully-qualified builtin name.
builtin: &'static str,
/// Name of the missing parameter.
param: &'static str,
},
/// A parameter was present but had the wrong shape (wrong type, malformed).
#[error("hostlib: {builtin}: invalid parameter '{param}': {message}")]
InvalidParameter {
/// Fully-qualified builtin name.
builtin: &'static str,
/// Name of the invalid parameter.
param: &'static str,
/// Human-readable description of the violation.
message: String,
},
/// Catch-all wrapper for I/O, parsing, or other backend failures.
#[error("hostlib: {builtin}: {message}")]
Backend {
/// Fully-qualified builtin name.
builtin: &'static str,
/// Human-readable failure description.
message: String,
},
/// A path the builtin resolved fell outside the session's workspace
/// roots under a restricted sandbox profile. The mirror of the
/// `harness.fs.*` `tool_rejected` rejection — both surfaces reject an
/// out-of-root path with the same message.
#[error("{message}")]
SandboxViolation {
/// Fully-qualified builtin name.
builtin: &'static str,
/// The normalized path that was rejected, for telemetry.
path: String,
/// The canonical rejection message (see
/// [`harn_vm::process_sandbox::SandboxViolation::message`]).
message: String,
},
/// A never-approvable UNIVERSAL catastrophic command (machine/disk/data
/// destruction) was rejected by the floor BEFORE spawning, at the shared
/// [`crate::process::spawn_process`] chokepoint. Enforced unconditionally
/// (no `command_policy` required), so it is universal across every hostlib
/// process tool, embedders, and standalone Harn. Mirrors the
/// `catastrophic_floor` disposition the `process.exec` command-policy
/// preflight surfaces. See
/// [`harn_vm::orchestration::universal_catastrophic_reason`].
#[error("{message}")]
CatastrophicFloor {
/// Fully-qualified builtin name.
builtin: &'static str,
/// The verbatim floor rationale (never-approvable reason).
message: String,
},
}
impl HostlibError {
/// The fully-qualified builtin name this error came from. Useful for
/// embedder logging and for the routing tests in `tests/`.
pub fn builtin(&self) -> &'static str {
match self {
HostlibError::Unimplemented { builtin }
| HostlibError::MissingParameter { builtin, .. }
| HostlibError::InvalidParameter { builtin, .. }
| HostlibError::Backend { builtin, .. }
| HostlibError::SandboxViolation { builtin, .. }
| HostlibError::CatastrophicFloor { builtin, .. } => builtin,
}
}
}
impl From<HostlibError> for VmError {
fn from(err: HostlibError) -> VmError {
// Surface as a `Thrown` dict so Harn `try`/`catch` can pattern-match
// on `kind`, `builtin`, and `message`. This matches how the existing
// `host_call` error path shapes its exceptions.
let kind = match err {
HostlibError::Unimplemented { .. } => "unimplemented",
HostlibError::MissingParameter { .. } => "missing_parameter",
HostlibError::InvalidParameter { .. } => "invalid_parameter",
HostlibError::Backend { .. } => "backend_error",
HostlibError::SandboxViolation { .. } => "tool_rejected",
HostlibError::CatastrophicFloor { .. } => "catastrophic_floor",
};
// Carry the offending path on sandbox violations so `catch` blocks
// and telemetry can branch on it without re-parsing the message.
let path = match &err {
HostlibError::SandboxViolation { path, .. } => Some(path.clone()),
_ => None,
};
let builtin = err.builtin();
let message = err.to_string();
let mut dict: harn_vm::value::DictMap = harn_vm::value::DictMap::new();
dict.put_str("kind", kind);
dict.put_str("builtin", builtin);
dict.put_str("message", message);
if let Some(path) = path {
dict.put_str("path", path);
}
VmError::Thrown(VmValue::dict(dict))
}
}