> ## Documentation Index
> Fetch the complete documentation index at: https://obversa.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Plan Admission

> Bind a graph description to a host's admission record and freeze what the run may use.

Freeze what a run may use before it starts: the package, the permissions,
the engine lane for every seat, and the checks that run before the first
step. Use `resolveGraphPlan(description, resolution)` after you compile a
graph; it returns a frozen plan, its canonical JSON and a SHA-256 digest.
For what the plan is resolved from, read [Outside graph types](/docs/graphs/contract);
for what runs under it, the [graph executor](/docs/graphs/executor). The plan is
data: it can't execute code or grant a permission.

The smallest resolution names the package once, admits it with no
permissions, and resolves no lanes:

```ts examples/custom-graph.ts (excerpt) {6-8} theme={null}
const identity = {
  source: 'npm:@example/draft-review',
  version: '1.0.0',
  digest: 'sha256:1111111111111111111111111111111111111111111111111111111111111111',
} as const;
const resolution: PlanResolution = {
  package: identity,
  admission: { package: identity, permissions: [] },
  executionLanes: [],
};
```

`resolveGraphPlan(compiled.describe(), resolution)` then produces the plan.
A `PlanResolution` needs:

* **The graph package's source, version and digest**, and the same
  identity in the host admission record. They must match exactly.
* **The permissions the host admits.**
* **One effective execution target for each declared lane**, with an
  optional ordered fallback list per lane. An omitted list means no
  fallback.
* **An optional preflight policy**: a `timeoutMs` for the checks before the
  first step, and one entry per lane with `live: 'required'` or `'skip'`
  and `unsupportedStatic: 'block'` or `'allow'`. An omitted policy means no
  checks.

## Admit permissions

The graph package asks for permissions in `describe()`. Each requested
permission must match an admitted permission by name and JSON scope;
otherwise `resolveGraphPlan` throws `GraphValidationError` with
`PERMISSION_NOT_ADMITTED`. Extra admitted permissions don't become graph
permissions: the resolved plan keeps requested and admitted permissions as
separate lists.

When a preflight policy is present, every resolved lane needs exactly one
entry, no entry may name a lane that doesn't exist, and `timeoutMs` must be
a whole number of milliseconds from 1 to 2147483647. A policy that breaks
either rule is rejected before the plan is frozen.

## Resolve execution lanes

Each described lane names a requested adapter, provider, model family,
model and tools, and a graph can declare known substitutions. The host
resolves every described lane exactly once. An effective target must equal
the requested target or one declared substitution; unknown, missing or
extra lane resolutions are rejected.

The resolved plan records the requested target, the effective target and
the fallback order. A change to an adapter, provider, model family, model,
tools, allowed substitution, fallback order or preflight policy changes the
plan digest.

## Snapshot the plan

Resolve the plan before a host starts work. The returned snapshot is frozen:
its package identity, permissions, lanes, policies, preflight policy and
bounds stay in the snapshot its digest identifies. Run parameters aren't
part of the plan or its digest; the run record keeps resolved inputs as part
of the frozen run.

`run(job, { params })` accepts a JSON object. If `params` is `undefined`,
the run uses a frozen empty object. `run` rejects `null`, arrays and other
non-object values with `JsonValueError` before work or environment setup
starts. For a valid object it clones and freezes `params` before work
starts, and the root job and each child job receive the same frozen object
as `ctx.params`, so a later change to your object can't change it. A nested
value that isn't JSON also throws `JsonValueError`, with `path` pointing at
the value.

## Failure

* **`PERMISSION_NOT_ADMITTED`**: a requested permission has no admitted
  match by name and JSON scope.
* **Rejected before the plan is frozen**: a package identity that differs
  between the resolution and the admission record, a lane resolution that
  is unknown, missing or extra, or a preflight policy with a bad entry or
  timeout.
* **`JsonValueError`** from `run` for `params` that aren't a JSON object.

## Limits

* **The conformance kit isn't a security sandbox.** An outside graph type
  is trusted package code. The host owns package discovery, signature
  checks and sandboxing; the contract validates only the package identity,
  admission record and plan data the host provides.
* **Plan admission doesn't schedule nodes, store runs, provide built-in
  forms or execute work on another machine.**

## Next steps

* [Graph executor](/docs/graphs/executor): what runs under the frozen plan, and
  the checks the preflight policy turns on.
* [Supervised local runs](/docs/driving/runner): a worker that checks every seat
  before the first step.
* [Outside graph types](/docs/graphs/contract): the description a plan is
  resolved from.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.