# `PtcRunner.Lisp.Prelude`
[🔗](https://github.com/andreasronge/ptc_runner/blob/main/lib/ptc_runner/lisp/prelude.ex#L1)

Compiled, stateless deployment prelude artifact (Capability Prelude V1).

A deployment loads curated PTC-Lisp prelude source that declares protected
namespaces (e.g. `crm`) and exports functions/constants. The compiler
(`PtcRunner.Lisp.Prelude.Compiler`) turns that source into one of these
artifacts, which is then attached to a run and consulted — unchanged —
across direct Lisp execution, SubAgent execution, and the REPL (plan §1A).

## Fields

  * `namespaces` — sorted list of declared namespace-name strings. These are
    the prelude-protected namespaces (host-boundary string-backed).
  * `exports` — list of `%PtcRunner.Lisp.Prelude.Export{}` public export
    records (`:prompt` + `:discoverable`). Private helpers are NOT here.
  * `private_env` — the captured private prelude environment, keyed by
    namespace then bare symbol: `%{namespace => %{symbol => callable}}`
    (`{:closure, ...}` for `defn`). Namespace-scoping keeps same-named
    definitions in different namespaces distinct (e.g. `crm/who` vs
    `hr/who`). Public exports call their own namespace's private helpers
    through this env; user code cannot resolve private helpers by qualified
    symbol (plan §5). The CALLABLE values are the contract slice 2
    (evaluator threading) depends on.
  * `source_hash` — sha256 hex digest of the prelude source (plan §12
    traceability).
  * `source_index` — precomputed `%{full-ref => rendered-source}` map for the
    `(source ns/name)` discovery form (issue #1095). Keyed by full ref for
    public exports PLUS the private helpers transitively reachable from some
    public export; unreferenced privates stay out. Values are rendered strings
    (a labeled effective-metadata header + the Formatter-rendered defining
    form), so this cache leaks no captured closure or raw parser AST. NOTE: it
    exposes export IMPLEMENTATION, not just contract — deployments must keep
    secrets/credentials out of prelude bodies, not just docstrings.
  * `metadata` — small map of namespace-level facts for traces/debugging,
    e.g. per-namespace docstring and default visibility.

## Private-env capture seam (fact #6)

`defn`/`defn-` in the prelude desugar through the existing analyze+eval
pipeline to `{:closure, params, body, captured_env, turn_history, meta}`
tuples stored under their bare symbol in a `user_ns`-shaped map. The
compiler captures that whole map as `private_env`. Sibling helpers are NOT
folded into each closure's `captured_env` — they resolve by name through
`user_ns` at call time, and `private_env` is exactly that namespace. P2
therefore threads `private_env` as the user_ns layer (resolver position
between the mutable `user` namespace and built-ins) when invoking exports:
a public export resolves qualified (`crm/get-user`) to
`private_env[symbol]` and runs its body against `private_env`, so private
helpers resolve, while private symbols stay absent from `exports` and so
are unreachable by qualified user calls. Proven end-to-end during P0.

## Validation errors

Compile-time failures are returned as
`{:error, %PtcRunner.Lisp.Prelude.ValidationError{}}`, never raised.

# `export_summary`

```elixir
@type export_summary() :: %{
  ref: String.t(),
  namespace: String.t(),
  symbol: String.t(),
  arity: non_neg_integer() | :variadic,
  params: [String.t()],
  visibility: PtcRunner.Lisp.Prelude.Export.visibility(),
  effect: PtcRunner.Lisp.Prelude.Export.effect(),
  provider_ref: String.t() | nil,
  requires: [String.t()]
}
```

Trace/debug summary of a compiled prelude (plan §12 Traceability).

String/atom/list-only, JSON-serializable, and credential-free: it carries
enough to REPRODUCE the V1 capability environment without leaking captured
closures, the private prelude env, or any host secret.

  * `source_hash` — sha256 hex of the prelude source.
  * `artifact_hash` — sha256 hex over the compiled artifact's protected
    facts (namespaces + public export records). Lets a trace consumer tell
    whether two runs used the same compiled prelude even when source text is
    unavailable.
  * `protected_namespaces` — the selected protected namespace names (sorted).
  * `host_policy_hash` — host policy hash/id when available; `nil` in V1
    (no first-class host policy yet).
  * `exports` — one `export_summary` per PUBLIC export (no callables/env).
  * `components` — selected source component provenance when the artifact was
    produced by `PtcRunner.Lisp.Prelude.Bundle`; otherwise `[]`.

# `t`

```elixir
@type t() :: %PtcRunner.Lisp.Prelude{
  exports: [PtcRunner.Lisp.Prelude.Export.t()],
  metadata: map(),
  namespaces: [String.t()],
  private_env: %{required(String.t()) =&gt; %{required(String.t()) =&gt; term()}},
  source_hash: String.t(),
  source_index: %{required(String.t()) =&gt; String.t()}
}
```

# `trace_summary`

```elixir
@type trace_summary() :: %{
  source_hash: String.t(),
  artifact_hash: String.t(),
  protected_namespaces: [String.t()],
  host_policy_hash: String.t() | nil,
  exports: [export_summary()],
  components: [map()]
}
```

# `export_tool_refs`

```elixir
@spec export_tool_refs(t(), String.t()) :: [String.t()]
```

The typed-tool names a public export invokes (transitively over same-namespace
helpers), or `[]` when `ref` is not a public export.

The pre-execution tool guard unions these in so a prelude-wrapped
`(tool/call ...)` is validated before any side effect runs (plan §6/§7).

# `fetch_export`

```elixir
@spec fetch_export(t(), String.t()) ::
  {:ok, PtcRunner.Lisp.Prelude.Export.t()} | :error
```

Looks up a public export by its Lisp-facing ref (e.g. `"crm/get-user"`).

# `namespaces`

```elixir
@spec namespaces(t()) :: [String.t()]
```

The declared (protected) namespace names, sorted.

# `prompt_exports`

```elixir
@spec prompt_exports(t()) :: [PtcRunner.Lisp.Prelude.Export.t()]
```

Public export records visible in the prompt inventory (`:prompt` only).

# `trace_summary`

```elixir
@spec trace_summary(t() | nil) :: trace_summary() | nil
```

Builds the trace/debug summary for `prelude` (plan §12).

Returns `nil` for `nil` (no prelude attached). The result is JSON-serializable
and contains NO captured closures, private prelude env, or credentials — only
the protected facts needed to reproduce the capability environment.

The `host_policy_hash` slot is `nil` in V1; it is reserved for when a host
policy hash/id becomes available.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
