Skip to main content

submilli_engine/stdlib/
shared.rs

1//! Helpers shared across stdlib libraries.
2
3use std::io::Write;
4use std::sync::Arc;
5
6use crate::runtime::decision::{
7    CallOrigin, CallSite, CallTicket, EntryPath, PackageLine, SourceLine,
8};
9use crate::runtime::fs::{ContainError, ContentPath, LinkPath, resolve_content, resolve_link};
10use crate::runtime::fuel;
11use crate::runtime::host::{
12    permission_denied, permission_denied_invariant, permission_denied_read_only,
13    quota_exceeded_error, write_boxed_number_struct,
14};
15use crate::runtime::security::{AuditDecision, CheckOutcome, SecurityCheck};
16use crate::runtime::vfs::{Access, Placement};
17use crate::runtime::{DiskQuota, QuotaCharge, QuotaExceeded, StoreData};
18
19/// The package whose code is executing, read off the innermost wasm frame's owning module.
20///
21/// This answers "whose code is running", which is the question a capability check needs —
22/// not "whose export did we last enter", which is what a shadow stack records. The two
23/// diverge whenever a package invokes caller-supplied code: a vtable-dispatched `toJson`, a
24/// closure passed across a package boundary.
25///
26/// Fails closed on both ways the principal can come back unknown, and neither may fall back
27/// to a permissive default:
28///
29/// - **No frames.** No wasm is executing, so this is an embedder-driven call with no guest
30///   principal to speak of.
31/// - **Frames, but the innermost module has no name.** A module this compiler produced always
32///   carries one. One that does not is not a principal we can name.
33///
34/// Attribution visits the innermost module directly, independently of the
35/// diagnostic backtrace setting, without capturing or symbolizing outer frames.
36pub(crate) fn running_package(
37    store: &impl wasmtime::AsContext<Data = StoreData>,
38) -> Result<String, PrincipalError> {
39    let mut principal = None;
40    wasmtime::WasmBacktrace::visit_modules(store, |module| {
41        principal = Some(match module.name() {
42            Some(name) => owned_principal(name).map_err(PrincipalError::Internal),
43            None => Err(PrincipalError::Unknown(UnknownPrincipal {
44                label: "<unnamed module>",
45                reason: "the running module declares no package name, so its caller cannot be identified",
46            })),
47        });
48        std::ops::ControlFlow::Break(())
49    }).map_err(|error| PrincipalError::Internal(crate::runtime::host::fatal_host_error(error)))?;
50    principal.unwrap_or_else(|| {
51        Err(PrincipalError::Unknown(UnknownPrincipal {
52            label: "<no wasm frame>",
53            reason: "no wasm frame is executing, so the call has no caller to attribute it to",
54        }))
55    })
56}
57
58pub(crate) fn owned_principal(name: &str) -> wasmtime::Result<String> {
59    let mut result = String::new();
60    result
61        .try_reserve_exact(name.len())
62        .map_err(crate::runtime::host::fatal_host_error)?;
63    result.push_str(name);
64    Ok(result)
65}
66
67pub(crate) enum PrincipalError {
68    Unknown(UnknownPrincipal),
69    Internal(wasmtime::Error),
70}
71impl PrincipalError {
72    pub fn into_denial(self, capability: &str) -> wasmtime::Error {
73        match self {
74            Self::Unknown(unknown) => {
75                permission_denied_invariant(unknown.label, capability, unknown.reason)
76            }
77            Self::Internal(error) => error,
78        }
79    }
80
81    pub fn label_or_error(self) -> wasmtime::Result<String> {
82        match self {
83            Self::Unknown(unknown) => owned_principal(unknown.label),
84            Self::Internal(error) => Err(error),
85        }
86    }
87}
88
89/// An unknown principal label cannot collide with an actual package name.
90pub(crate) struct UnknownPrincipal {
91    pub label: &'static str,
92    pub reason: &'static str,
93}
94
95/// Call at the top of each gated host fn body — after argument parsing,
96/// before any I/O. Attributes the call to whoever's code is running, via
97/// [`running_package`]. A denial surfaces to the guest as a catchable
98/// `PermissionDeniedError`.
99///
100/// A capability the catalog marks `main_denial` is refused to `main` here,
101/// ahead of the policy. `submilli:security#check` does not route through this
102/// function, so the invariant does not extend to it; that path attributes to
103/// the caller and would deny anyway.
104pub fn check_security(
105    store: impl wasmtime::AsContextMut<Data = StoreData>,
106    capability: &str,
107    context: serde_json::Value,
108) -> wasmtime::Result<()> {
109    check_security_call(store, capability, context).map(|_| ())
110}
111
112/// [`check_security`], returning the recorder's ticket for the call when one is installed,
113/// for host functions whose later decisions (redirect hops) belong to the same call.
114pub(crate) fn check_security_call(
115    mut store: impl wasmtime::AsContextMut<Data = StoreData>,
116    capability: &str,
117    context: serde_json::Value,
118) -> wasmtime::Result<Option<CallTicket>> {
119    // Direct caller attribution and the policy check have one flat gate charge.
120    fuel::charge_host_fuel(&mut store, fuel::GATE)?;
121    let caller = running_package(&store).map_err(|error| {
122        if let PrincipalError::Unknown(ref unknown) = error {
123            audit_entry_denial(
124                &store,
125                unknown.label,
126                capability,
127                &context,
128                "invariant",
129                unknown.reason,
130            );
131        }
132        error.into_denial(capability)
133    })?;
134    // Uncharged on purpose; see `DecisionLogConfig::max_line_capture_frames`.
135    let ticket = begin_recorded_call(&store, &caller, capability);
136    authorize_capability(
137        &caller,
138        store.as_context().data().security_check.as_ref(),
139        capability,
140        &context,
141        store.as_context().data().vfs.cwd(),
142        CallSite::new(ticket, EntryPath::GatedOp),
143    )?;
144    Ok(ticket)
145}
146
147/// Begins the recorder's call for a host function at its entry, with where in the
148/// submitted program, and in a package, it came from. `None` when no recorder is installed.
149pub(crate) fn begin_recorded_call(
150    store: &impl wasmtime::AsContext<Data = StoreData>,
151    caller: &str,
152    capability: &str,
153) -> Option<CallTicket> {
154    let recorder = store.as_context().data().security_check.recorder()?;
155    Some(recorder.begin_call(caller, capability, call_origin(store)))
156}
157
158/// Where the running call came from: the line in the submitted program, and the line
159/// inside a package when the program reached the call through one. Each is `None` when
160/// its lookup fails.
161///
162/// Walks a full backtrace, which costs more than [`running_package`]'s module visit, so
163/// it runs only while a recorder is installed and still wants lines, uncharged to guest
164/// fuel (see `DecisionLogConfig::max_line_capture_frames`). The program's line is its own
165/// call into the package; the package's line is the innermost frame of package code.
166pub(crate) fn call_origin(store: &impl wasmtime::AsContext<Data = StoreData>) -> CallOrigin {
167    let Some(recorder) = store.as_context().data().security_check.recorder() else {
168        return CallOrigin::default();
169    };
170    if !recorder.wants_line() {
171        return CallOrigin::default();
172    }
173    let backtrace = wasmtime::WasmBacktrace::force_capture(store);
174    recorder.note_line_capture(backtrace.frames().len());
175    let frames = backtrace.frames();
176    let program = frames
177        .iter()
178        .position(|frame| frame.module().name() == Some(crate::mangle::USER_PACKAGE));
179    let line = program.and_then(|at| {
180        let symbol = frames[at].symbols().first()?;
181        Some(SourceLine {
182            line: symbol.line()?,
183            column: symbol.column(),
184        })
185    });
186    // Frames inside the program's call: a package's frames carry its name on their path.
187    let package = frames[..program.unwrap_or(frames.len())]
188        .iter()
189        .find_map(package_line);
190    CallOrigin { line, package }
191}
192
193/// The package line a frame is at, when it is package code with a known line.
194fn package_line(frame: &wasmtime::FrameInfo) -> Option<PackageLine> {
195    let package = frame.module().name()?;
196    let symbol = frame.symbols().first()?;
197    let module = symbol.file()?.strip_prefix(package)?.strip_prefix('/')?;
198    Some(PackageLine {
199        package: package.to_owned(),
200        module: module.to_owned(),
201        function: symbol.name().map(ToString::to_string),
202        line: symbol.line()?,
203        column: symbol.column(),
204    })
205}
206
207/// Marks the latest recorded decision as a denial this host function swallowed to filter a
208/// listing. A no-op without a recorder.
209pub(crate) fn mark_filtered(store: &impl wasmtime::AsContext<Data = StoreData>) {
210    if let Some(recorder) = store.as_context().data().security_check.recorder() {
211        recorder.mark_last_filtered();
212    }
213}
214
215/// [`check_security`] for a caller resolved earlier, such as the principal of a
216/// request whose redirect hops are checked after the host fn has suspended.
217pub(crate) fn authorize_capability(
218    caller: &str,
219    security_check: &dyn SecurityCheck,
220    capability: &str,
221    context: &serde_json::Value,
222    cwd: &str,
223    site: CallSite,
224) -> wasmtime::Result<()> {
225    // The ordering is the invariant. This must precede both the delegation
226    // below and any work the host fn does after we return — a reorder that
227    // consults the policy first makes the refusal conditional on a policy
228    // existing, and the default check allows everything.
229    if caller == crate::mangle::USER_PACKAGE
230        && let Some(reason) =
231            crate::stdlib::capabilities::find_any(capability).and_then(|entry| entry.main_denial)
232    {
233        audit_denial_at(
234            security_check,
235            caller,
236            capability,
237            context,
238            "invariant",
239            reason,
240            site,
241        );
242        return Err(permission_denied_invariant(caller, capability, reason));
243    }
244    check_and_audit(security_check, caller, capability, context, cwd, site)
245        .map_err(|reason| permission_denied(caller, capability, reason))
246}
247
248/// Asks the policy whether `caller` may use `capability`, and audits its answer with the
249/// recorder's explanation. A denial returns the policy's reason.
250pub(crate) fn check_and_audit(
251    security_check: &dyn SecurityCheck,
252    caller: &str,
253    capability: &str,
254    context: &serde_json::Value,
255    cwd: &str,
256    site: CallSite,
257) -> Result<(), String> {
258    let outcome = security_check.check_with_cwd(caller, capability, context, cwd);
259    // The explanation is for the recorder alone; enforcement never reads it.
260    let explanation = security_check
261        .recorder()
262        .and_then(|_| security_check.explain(caller, capability, context, cwd));
263    let audit_context = security_check.audit_context(capability, context, cwd);
264    let context = audit_context.as_ref();
265    match outcome {
266        CheckOutcome::Allow { rule } => {
267            security_check.audit(
268                AuditDecision::new(caller, capability, context, true, "policy", rule, None)
269                    .with_explanation(explanation.as_ref())
270                    .with_site(site),
271            );
272            Ok(())
273        }
274        CheckOutcome::Deny { reason, rule } => {
275            security_check.audit(
276                AuditDecision::new(
277                    caller,
278                    capability,
279                    context,
280                    false,
281                    "policy",
282                    rule,
283                    Some(&reason),
284                )
285                .with_explanation(explanation.as_ref())
286                .with_site(site),
287            );
288            Err(reason)
289        }
290    }
291}
292
293pub(crate) fn audit_denial_at(
294    security: &dyn SecurityCheck,
295    caller: &str,
296    capability: &str,
297    context: &serde_json::Value,
298    source: &str,
299    reason: &str,
300    site: CallSite,
301) {
302    security.audit(
303        AuditDecision::new(
304            caller,
305            capability,
306            context,
307            false,
308            source,
309            None,
310            Some(reason),
311        )
312        .with_site(site),
313    );
314}
315
316/// Audits a refusal at the entry of a host call as a call of its own, with the program
317/// line that led to it. For a refusal after the call's gate, see [`audit_denial_in`].
318pub(crate) fn audit_entry_denial(
319    store: &impl wasmtime::AsContext<Data = StoreData>,
320    caller: &str,
321    capability: &str,
322    context: &serde_json::Value,
323    source: &str,
324    reason: &str,
325) {
326    let security = store.as_context().data().security_check.as_ref();
327    let ticket = security
328        .recorder()
329        .map(|recorder| recorder.begin_call(caller, capability, call_origin(store)));
330    audit_denial_at(
331        security,
332        caller,
333        capability,
334        context,
335        source,
336        reason,
337        CallSite::new(ticket, EntryPath::GatedOp),
338    );
339}
340
341/// Audits a further refusal inside the host call `ticket` names, such as a read-only or
342/// quota refusal after the policy allowed it. Without a ticket the refusal is its own call.
343pub(crate) fn audit_denial_in(
344    store: &impl wasmtime::AsContext<Data = StoreData>,
345    ticket: Option<CallTicket>,
346    caller: &str,
347    capability: &str,
348    context: &serde_json::Value,
349    source: &str,
350    reason: &str,
351) {
352    audit_denial_at(
353        store.as_context().data().security_check.as_ref(),
354        caller,
355        capability,
356        context,
357        source,
358        reason,
359        CallSite::new(ticket, EntryPath::GatedOp),
360    );
361}
362
363/// Resolve a guest path for an operation that reaches its contents — every component,
364/// including the last, traversed under containment.
365pub fn resolve_content_or_trap(
366    data: &StoreData,
367    guest_path: &str,
368    op: &str,
369) -> wasmtime::Result<ContentPath> {
370    resolve_content(&data.vfs, data.vfs.cwd(), guest_path)
371        .map_err(|err| contain_trap(op, guest_path, &err))
372}
373
374/// Resolve a guest path for an operation that acts on the link itself — the final
375/// component is never followed.
376pub fn resolve_link_or_trap(
377    data: &StoreData,
378    guest_path: &str,
379    op: &str,
380) -> wasmtime::Result<LinkPath> {
381    resolve_link(&data.vfs, data.vfs.cwd(), guest_path)
382        .map_err(|err| contain_trap(op, guest_path, &err))
383}
384
385/// Refuse a write into a volume mounted read-only, attributed to the running
386/// package. Call after the capability check and resolution, before any other
387/// work, so the policy sees every attempt and a refused call changes nothing.
388/// `ticket` is the host call's gate, which the refusal continues.
389pub(crate) fn require_writable(
390    store: impl wasmtime::AsContext<Data = StoreData>,
391    ticket: Option<CallTicket>,
392    placement: &Placement,
393    capability: &str,
394    guest_path: &str,
395) -> wasmtime::Result<()> {
396    if placement.access() == Access::ReadWrite {
397        return Ok(());
398    }
399    let caller = running_package(&store).map_err(|error| {
400        if let PrincipalError::Unknown(ref unknown) = error {
401            audit_denial_in(
402                &store,
403                ticket,
404                unknown.label,
405                capability,
406                &serde_json::json!({ "path": guest_path }),
407                "invariant",
408                unknown.reason,
409            );
410        }
411        error.into_denial(capability)
412    })?;
413    audit_denial_in(
414        &store,
415        ticket,
416        &caller,
417        capability,
418        &serde_json::json!({ "path": guest_path }),
419        "read_only",
420        "the destination volume is read-only",
421    );
422    Err(read_only_denial(
423        &caller,
424        capability,
425        guest_path,
426        placement.mount_point(),
427    ))
428}
429
430/// Refuse a write aimed at the VFS root or a mount point: each is a directory,
431/// and replacing one would take a volume with it.
432pub(crate) fn refuse_volume_root(
433    resolved: &ContentPath,
434    op: &str,
435    guest_path: &str,
436) -> wasmtime::Result<()> {
437    if resolved.is_root() {
438        wasmtime::bail!(
439            "{op} {guest_path}: the VFS root or a mount point is a directory, not a file"
440        );
441    }
442    Ok(())
443}
444
445fn read_only_denial(
446    caller: &str,
447    capability: &str,
448    guest_path: &str,
449    mount: &str,
450) -> wasmtime::Error {
451    permission_denied_read_only(
452        caller,
453        capability,
454        format!("{guest_path} is in the volume mounted read-only at {mount}"),
455    )
456}
457
458/// The single translation from a containment failure to a guest-visible trap. Every
459/// escape reaches the guest as the same diagnostic regardless of which operation hit it,
460/// so an LLM reading one recognises the rest.
461///
462/// A read-only refusal that got past [`require_writable`] still surfaces as a
463/// `PermissionDeniedError`, attributed to `<vfs>` since the caller is not at hand.
464pub fn contain_trap(op: &str, guest_path: &str, err: &ContainError) -> wasmtime::Error {
465    if let ContainError::ReadOnly(mount) = err {
466        return read_only_denial("<vfs>", op, guest_path, mount);
467    }
468    wasmtime::Error::msg(format!("{op} {guest_path}: {err}"))
469}
470
471/// Distinguish a missing parent from an escaping one at the point a write first touches
472/// the filesystem.
473///
474/// Both surface as a failure to create, and keeping them apart is what an LLM needs: one
475/// is fixable with `mkdir`, the other can never succeed. The `parent.is_dir()` pre-check
476/// this replaced conflated them the other way — it reported every escape as a missing
477/// parent, costing a turn on a `mkdir` that could never work.
478pub fn write_target_trap(op: &str, guest_path: &str, err: &ContainError) -> wasmtime::Error {
479    match err {
480        ContainError::Io(e) if e.kind() == std::io::ErrorKind::NotFound => {
481            wasmtime::Error::msg(format!(
482                "{op} {guest_path}: parent directory does not exist \
483                 (create it with mkdir(path, true))"
484            ))
485        }
486        _ => contain_trap(op, guest_path, err),
487    }
488}
489
490/// A write refused because it would pass the VFS's size limit, as a `QuotaExceededError`
491/// the program can catch.
492pub(crate) fn quota_refusal(
493    op: &str,
494    guest_path: &str,
495    exceeded: QuotaExceeded,
496) -> wasmtime::Error {
497    quota_exceeded_error(format!("{op} {guest_path}: {exceeded}"))
498}
499
500/// No program code runs before the rename, so the write draws on the size of the file it
501/// replaces and reserves only the growth: a rewrite that ends within the size limit
502/// succeeds even though the old file and the temporary one briefly coexist.
503pub(crate) fn atomic_write(
504    final_path: &ContentPath,
505    bytes: &[u8],
506    permissions: Option<cap_std::fs::Permissions>,
507    guest_path: &str,
508    op: &str,
509    quota: Option<Arc<DiskQuota>>,
510) -> wasmtime::Result<()> {
511    let mut disk_charge = QuotaCharge::new(quota, final_path.regular_file());
512    disk_charge
513        .reserve(bytes.len() as u64)
514        .map_err(|exceeded| quota_refusal(op, guest_path, exceeded))?;
515    write_then_rename(final_path, bytes, permissions, guest_path, op)?;
516    disk_charge.commit();
517    Ok(())
518}
519
520/// No `parent.is_dir()` pre-check: `Dir` reports a missing parent as `NotFound` the way
521/// `std` does, and the old gate turned an escape into "parent directory does not exist" —
522/// costing an LLM a turn on a `mkdir` that could never succeed.
523fn write_then_rename(
524    final_path: &ContentPath,
525    bytes: &[u8],
526    permissions: Option<cap_std::fs::Permissions>,
527    guest_path: &str,
528    op: &str,
529) -> wasmtime::Result<()> {
530    let tmp = final_path.temp_sibling();
531    let mut f = tmp
532        .create_new()
533        .map_err(|e| write_target_trap(op, guest_path, &e))?;
534    let result = (|| {
535        if let Some(permissions) = permissions {
536            f.set_permissions(permissions).map_err(|e| {
537                wasmtime::Error::msg(format!("{op} {guest_path}: permissions: {e}"))
538            })?;
539        }
540        f.write_all(bytes)
541            .map_err(|e| wasmtime::Error::msg(format!("{op} {guest_path}: {e}")))?;
542        f.sync_all()
543            .map_err(|e| wasmtime::Error::msg(format!("{op} {guest_path}: fsync: {e}")))?;
544        drop(f);
545        tmp.rename_to(final_path)
546            .map_err(|e| contain_trap(op, guest_path, &e))
547    })();
548    if result.is_err() {
549        let _ = tmp.remove_file();
550    }
551    result
552}
553
554// --- shared by the model-listing libraries (`submilli:llm`, `submilli:embedding`) ---
555
556/// Characters a `Model.description` may carry into a guest model's
557/// model-selection reasoning.
558///
559/// The bound is not the whole defense — [`sanitize_description`] strips control
560/// characters and collapses the text to one line first, because a length bound
561/// alone does not stop an injection short enough to fit. The bound is what stops
562/// a long one, and what keeps a listing's size independent of how much prose an
563/// operator wrote.
564pub const MAX_DESCRIPTION_CHARS: usize = 280;
565
566/// Whether a per-candidate check's answer removes the candidate (`Ok(false)`),
567/// keeps it (`Ok(true)`), or must propagate (`Err`).
568///
569/// Only the policy's own answer filters. An invariant denial means the check
570/// could not be made at all — the caller could not be named, or a runtime rule
571/// refused ahead of the policy — and swallowing it would turn a runtime refusal
572/// into a silently short listing that reads as "the operator configured fewer
573/// models."
574pub(crate) fn filters_candidate(checked: wasmtime::Result<()>) -> wasmtime::Result<bool> {
575    let Err(err) = checked else {
576        return Ok(true);
577    };
578    match err.downcast_ref::<crate::runtime::host::PermissionDenied>() {
579        Some(denial) if denial.is_policy() => Ok(false),
580        _ => Err(err),
581    }
582}
583
584/// The candidates of a listing the caller may use. `gate` runs the capability
585/// check one candidate's use would; a policy denial leaves the candidate out and
586/// marks the recorded decision filtered, and any other refusal propagates, the
587/// [`filters_candidate`] rule.
588pub(crate) fn permitted_candidates<T>(
589    caller: &mut wasmtime::Caller<'_, StoreData>,
590    candidates: Vec<T>,
591    name_of: impl Fn(&T) -> &str,
592    mut gate: impl FnMut(&mut wasmtime::Caller<'_, StoreData>, &str) -> wasmtime::Result<()>,
593) -> wasmtime::Result<Vec<T>> {
594    let mut permitted = Vec::with_capacity(candidates.len());
595    for candidate in candidates {
596        if filters_candidate(gate(caller, name_of(&candidate)))? {
597            permitted.push(candidate);
598        } else {
599            mark_filtered(&*caller);
600        }
601    }
602    Ok(permitted)
603}
604
605/// At most the first 200 bytes of a program-supplied value, for an error that
606/// echoes it: a refused value may be refused for its size.
607pub(crate) fn truncated(value: &str) -> std::borrow::Cow<'_, str> {
608    const MAX_ECHO_BYTES: usize = 200;
609    if value.len() <= MAX_ECHO_BYTES {
610        return std::borrow::Cow::Borrowed(value);
611    }
612    let end = value.floor_char_boundary(MAX_ECHO_BYTES);
613    std::borrow::Cow::Owned(format!("{}…", value.get(..end).unwrap_or_default()))
614}
615
616/// Reduce an operator-authored `description` to inert single-line data.
617///
618/// This is sanitization, not merely a bound. The text flows verbatim into a
619/// guest model's model-selection reasoning, so an injection that redirects which
620/// model a program calls fits comfortably inside any length limit — a bound
621/// alone stops only the long ones. Control characters and line breaks are what
622/// let injected text present itself as a new instruction block, so they are
623/// removed first; the bound then stops the rest.
624pub(crate) fn sanitize_description(description: &str) -> Option<String> {
625    let flattened: String = description
626        .chars()
627        .map(|c| if c.is_control() { ' ' } else { c })
628        .collect();
629    let collapsed = flattened.split_whitespace().collect::<Vec<_>>().join(" ");
630    if collapsed.is_empty() {
631        return None;
632    }
633    Some(collapsed.chars().take(MAX_DESCRIPTION_CHARS).collect())
634}
635
636/// Charge for the check and establish caller attribution even for an empty
637/// catalog, without asking policy about a model that does not exist.
638/// `count_field` names the filter context's count (`prompt_count`, `input_count`).
639pub(crate) fn preflight_models(
640    caller: &mut wasmtime::Caller<'_, StoreData>,
641    capability: &str,
642    count_field: &str,
643) -> wasmtime::Result<()> {
644    preflight_listing(
645        caller,
646        capability,
647        &serde_json::json!({ "model": "", count_field: 0 }),
648    )
649}
650
651/// [`preflight_models`] for any listing filtered per candidate: `context` is what
652/// an invariant denial is audited with, the context of an empty candidate.
653pub(crate) fn preflight_listing(
654    caller: &mut wasmtime::Caller<'_, StoreData>,
655    capability: &str,
656    context: &serde_json::Value,
657) -> wasmtime::Result<()> {
658    fuel::charge_host_fuel(&mut *caller, fuel::GATE)?;
659    running_package(caller).map(|_| ()).map_err(|error| {
660        if let PrincipalError::Unknown(ref unknown) = error {
661            audit_entry_denial(
662                &*caller,
663                unknown.label,
664                capability,
665                context,
666                "invariant",
667                unknown.reason,
668            );
669        }
670        error.into_denial(capability)
671    })
672}
673
674/// Audit a token-budget refusal against the running package, inside the host call
675/// `ticket` names. `Err` is the denial to throw instead when the package cannot
676/// be named.
677pub(crate) fn audit_quota_denial(
678    caller: &wasmtime::Caller<'_, StoreData>,
679    ticket: Option<CallTicket>,
680    capability: &str,
681    model: &str,
682    reason: &str,
683) -> Result<(), wasmtime::Error> {
684    let who = running_package(caller).or_else(PrincipalError::label_or_error)?;
685    audit_denial_in(
686        caller,
687        ticket,
688        &who,
689        capability,
690        &serde_json::json!({ "model": model }),
691        "quota",
692        reason,
693    );
694    Ok(())
695}
696
697/// A boxed number, or `undefined` when absent.
698pub(crate) fn optional_number(
699    caller: &mut wasmtime::Caller<'_, StoreData>,
700    number: Option<f64>,
701) -> wasmtime::Result<wasmtime::Val> {
702    match number {
703        Some(number) => Ok(wasmtime::Val::AnyRef(Some(
704            write_boxed_number_struct(caller, number)?.to_anyref(),
705        ))),
706        None => crate::runtime::prelude::undefined::value(caller),
707    }
708}