Attach-time validation for a compiled deployment prelude (Capability Prelude V1, plan §3 / §6A).
Prelude validation is split in two phases:
compile-time (
PtcRunner.Lisp.Prelude.Compiler) checks source syntax,(ns ...)directives, reserved-namespace declarations, duplicate refs, visibility, and arity/signature metadata — facts that do NOT depend on a selected runtime. It also infers each export'srequiresbacking ids from literal(tool/call {:server "x" :tool "y" ...})patterns. Dynamic server/tool values yield:unknowneffect and no requires.attach-time (this module) checks each public export's
requiresagainst the SELECTED upstream runtime BEFORE user code is analyzed. If a public export requires an upstream operation that is not configured or not present on the selected runtime, attachment fails — naming the missing operation — so a run never starts against a prelude whose backing operations are unavailable.
This split lets a single compiled prelude artifact be reused across runs while still failing fast when the selected runtime cannot back it.
Where the attach hook lives
attach/2 is the seam P2 wires at the TOP of PtcRunner.Lisp.run (and the
SubAgent / REPL surfaces), before parsing/analyzing user code. The prelude:
option may be either a compiled %PtcRunner.Lisp.Prelude{} artifact or raw
prelude source (a binary), which attach/2 compiles first. On success it
yields the compiled artifact for the analyzer/evaluator path; on failure it
yields a %PtcRunner.Lisp.Prelude.ValidationError{} that the call site maps
to {:error, %PtcRunner.Step{fail: %{reason: :prelude_attach_failed}}}.
Genuine programmer misuse (passing a value that is neither a prelude
artifact nor source) raises ArgumentError; a missing/ungranted upstream op
is a recoverable {:error, ...}, never a raise.
Requires id format
Two backing id shapes are recognized:
"upstream:<server>/<tool>"— validated against the selected upstream runtime.<server>must be a configured upstream;<tool>must be present in that upstream's tool list (mirroring the upstream runtime's configured-tool checks). When an upstream's tool list is not yet materialized (lazy MCP transports reportniltools), the specific tool cannot be checked and the requirement passes on the configured server alone. When no upstream runtime is configured for the run, upstream requirements are skipped (there is no runtime to check; the granted(tool/call ...)closure pluscheck_undefined_toolsstill guard the actual surface) — preserving direct-Lisp.run-with-stub use."tool:<name>"— validated against the run's grantedtools:map (a host-bound typed-tool capability). Fails closed when the host did not grant a tool of that name. This is checked regardless of whether an upstream runtime is configured.
Any other requires id shape fails attachment (fail-closed).
Summary
Types
A selected upstream runtime handle (a %PtcRunner.Upstream.Runtime{}
struct, a pid, or a registered name), or nil when no upstream runtime is
configured for the run. Mirrors the handle shapes
PtcRunner.Upstream.Runtime.upstream/2 accepts.
Functions
Resolves prelude_or_source to a compiled artifact, then validates its
requires against the attach context (%PtcRunner.Lisp.Prelude.AttachContext{},
bundling the upstream runtime and the granted tools: map).
Validates every public export's requires against the attach context.
Types
A selected upstream runtime handle (a %PtcRunner.Upstream.Runtime{}
struct, a pid, or a registered name), or nil when no upstream runtime is
configured for the run. Mirrors the handle shapes
PtcRunner.Upstream.Runtime.upstream/2 accepts.
Functions
@spec attach( PtcRunner.Lisp.Prelude.t() | String.t() | [PtcRunner.Lisp.Prelude.Bundle.selection()], PtcRunner.Lisp.Prelude.AttachContext.t() ) :: {:ok, PtcRunner.Lisp.Prelude.t()} | {:error, PtcRunner.Lisp.Prelude.ValidationError.t()}
Resolves prelude_or_source to a compiled artifact, then validates its
requires against the attach context (%PtcRunner.Lisp.Prelude.AttachContext{},
bundling the upstream runtime and the granted tools: map).
Returns {:ok, %PtcRunner.Lisp.Prelude{}} on success.
Returns {:error, %ValidationError{}} when:
- the source fails compile-time validation (any compile reason), or
- attach-time
requiresvalidation fails (:prelude_attach_failed).
Raises ArgumentError for genuine programmer misuse: a value that is neither
a %PtcRunner.Lisp.Prelude{}, prelude source (binary), nor a list of
source-bearing prelude selection maps.
@spec validate_requires( PtcRunner.Lisp.Prelude.t(), PtcRunner.Lisp.Prelude.AttachContext.t() ) :: :ok | {:error, PtcRunner.Lisp.Prelude.ValidationError.t()}
Validates every public export's requires against the attach context.
Returns :ok when all required backing operations are provided (or when
there are no requires to check — e.g. dynamic-backed exports). Returns
{:error, %ValidationError{reason: :prelude_attach_failed}} naming the first
missing operation and the export that needs it.