pub fn require_sandboxed_lisp_path<E>(
path: &Path,
on_empty: impl FnOnce() -> E,
on_absolute: impl FnOnce() -> E,
on_parent_escape: impl FnOnce() -> E,
on_non_lisp: impl FnOnce() -> E,
) -> Result<(), E>Expand description
Bracket a sandboxed-relative .lisp-terminating path axis with the
shared “empty → absolute → parent-escape → non-.lisp-extension”
four-arm gate every author-supplied M2 tatara-lisp source-path slot
on the caixa surface carries. Delegates to
is_sandboxed_relative_path for the three structural arms and to
is_lisp_extension for the extension arm; returns each arm’s
caller-owned error variant via the four FnOnce closures.
The arm ordering (Empty → Absolute → ParentEscape → NonLisp) is
canonical across every existing per-axis site — a path that is
both sandbox-escaping and non-.lisp surfaces the more
fundamental sandbox-shape diagnostic first (the .lisp remediation
would be misleading when the offending path can never resolve under
the caixa root anyway; the canonical fix collapses both into “pin a
relative .lisp path under the caixa root”). Same
smallest-scope-arm-fires-last posture the peer
require_positive_bounded_u32 /
require_positive_canonical_bounded_duration chains follow on the
integer / duration axes, and the same posture every per-axis inline
pre-lift block already applied by hand
(crate::behavior::BehaviorError’s EmptyPath → AbsolutePath
→ ParentEscape → NonLispExtension chain,
crate::upgrade::UpgradeError’s EmptyScript → AbsoluteScript
→ ParentEscapeScript → NonLispExtensionScript chain).
Two identical-shape call sites collapse onto this helper — one for
each M2 typed path-slot the wasm-engine reads through
tatara_lisp::read:
crate::behavior::BehaviorSpec::validateon:behavior :on-*callback paths — every arm carries the slot name verbatim through the closure’s caller-side capture (empty →crate::behavior::BehaviorError::EmptyPath, absolute →crate::behavior::BehaviorError::AbsolutePath, parent-escape →crate::behavior::BehaviorError::ParentEscape, non-.lisp→crate::behavior::BehaviorError::NonLispExtension);crate::upgrade::UpgradeInstruction::validate’sStateChangearm on:upgrade-from :state-change :script(empty →crate::upgrade::UpgradeError::EmptyScript, absolute →crate::upgrade::UpgradeError::AbsoluteScript, parent-escape →crate::upgrade::UpgradeError::ParentEscapeScript, non-.lisp→crate::upgrade::UpgradeError::NonLispExtensionScript).
Peer of the sibling require_positive_bounded_u32 /
require_positive_bounded_u64 /
require_positive_canonical_bounded_duration /
require_valid_versao_requirement / require_valid_dns_1123_label
helpers on the same closure-based caller-error-variant discipline —
the caller owns the enum variant + its self-locating discriminator
fields (slot, path, script), this helper only sequences the
four gate arms in canonical order and invokes the caller’s closure
on the offending arm.
PRIME DIRECTIVE promotion: the two-consumer duplication budget
(THEORY.md §I.3.5: “every recurring shape becomes a generator
before it becomes a pattern; every pattern becomes a library before
it becomes duplicated code. The duplication budget is zero.”)
promotes the four-step cascade to a typed substrate-side helper on
the same trajectory the is_sandboxed_relative_path /
is_lisp_extension primitives already follow. A future third
consumer — the :bibliotecas per-entry tatara-lisp source-file
axis, the :exe :kind Binario entry-point axis, the M2.5
wasm-engine pre-warm hook axis, the future mesh.pleme.io/v1alpha1/Caixa
CR materializer’s per-path validator — lands as a thin
four-closure wrapper rather than re-inlining the same four-arm
cascade.
§Errors
Returns on_empty() when path is empty; returns on_absolute()
when path is absolute; returns on_parent_escape() when path
carries a std::path::Component::ParentDir component anywhere;
returns on_non_lisp() when path’s terminating extension is not
exactly LISP_SOURCE_EXTENSION; returns Ok(()) otherwise.