PtcRunner.Kernel.ProviderRegistry (PtcRunner v0.14.0)

Copy Markdown View Source

Host-owned mapping from manifest provider names to trusted builders.

A manifest can select a bounded provider name and JSON configuration; it cannot register a module, function, callback, command, or code URL. Builders receive a path-free application identity, requested workflow or mission destination, building owner, and installed limits. Acquisition returns a normalized provider build with one or more capabilities, an optional safe snapshot, and an optional idempotent close function. A close function must return exactly :ok; any other return, exception, or exit is a provider-cleanup failure. The Kernel still attempts every registered close function and may replace the run outcome with :provider_cleanup_error.

Trusted staged builders enforce a global preparation barrier. Every selected provider first performs pure selection checks, then every provider completes non-secret local preflight, and no provider is acquired before the union of declared credentials has been resolved exactly once.

When that resolution happens differs by caller, and the difference is the authority each one has. An active command resolves at phase-8 step 5 from its sealed declarations, before any provider callback runs, and supplies the map; resolve_credentials/2 is not called again below that point. Direct embedding has no sealed declarations to derive a union from before preparation, so it keeps resolving here, from what preparation reported, with the synchronous semantics its caller selected. Active command assembly bounds preparation, preflight, and acquisition by its shared run deadline; preflight releases share the provider-cleanup budget. Preparation also freezes the provider's data_class and accepts_data policy so run assembly can reject an incompatible information flow before preflight or credentials. A builder bound to the data policy declared by its sealed PtcRunner.Kernel.ProviderDescriptor cannot contradict it: preparation returning a different data_class or set of accepted classes than phase 5 admitted, and than sink authorization used, fails with :provider_declaration_mismatch before preflight, credentials, or acquisition. Explicit embedding builders registered without a declared policy have no such declaration, so their preparation remains authoritative for them. A staged provider may additionally require or provide a bounded, code-owned acquisition service. Services pass opaque values only between selected trusted providers after the barrier; they never enter Lisp environments, connector snapshots, traces, or result artifacts. There are no implicit built-ins. CLI applications receive exactly the aliases in their host installation, while trusted Elixir embedding can pass any explicit builder map. The registry also freezes the installed limit ceilings used when manifests are loaded, so every frontend applies the same host authority rather than reconstructing it in CLI-specific options. Builder exceptions are contained at their current lifecycle phase.

A staged acquisition context's owner is the private signal owner for that provider's resource scope. Any process or port root started by an acquisition must monitor that owner and synchronously call PtcRunner.Kernel.ResourceRegistrar.register_root/1 from the root's init callback before its start operation returns. The registrar's private controller forwards registration into the scope's authoritative cleanup owner. Ports must be owned by a registered process. Registration after start returns is unsupported. A root retaining an unsettled OAuth release or persistence fence may call PtcRunner.Kernel.ResourceRegistrar.handoff_root/2 only after it has stopped accepting work and the terminal operation has a bounded self-owner or adopter.

provides is not a general marker. It names an acquisition service, and normalize_build/2 requires the acquired build to export exactly the services declared here, so a provider that declares one without exporting it fails with :invalid_provider_build. Use a dedicated field for provider-kind facts, as workflow_llm? does.

Summary

Functions

Builds one entry through all three lifecycle phases.

Idempotently releases private host authority retained by this registry.

Creates a registry from explicit staged builders keyed by provider name.

Marks a trusted builder as staged, optionally bound to its declared policy.

Checks the complete inert registry shape accepted by the constructor.

Types

acquire()

@type acquire() ::
  (credential_values() -> {:ok, built_provider()} | {:error, term()})
  | (credential_values(), acquisition_services() ->
       {:ok, built_provider()} | {:error, term()})

acquisition_services()

@type acquisition_services() :: %{optional(atom()) => term()}

authority_owner()

@opaque authority_owner()

build_context()

@type build_context() :: %{
  optional(:deadline) => PtcRunner.Kernel.Deadline.t(),
  optional(:deadline_ms) => integer(),
  optional(:resource_registrar) => PtcRunner.Kernel.ResourceRegistrar.t(),
  application_content_digest: binary(),
  destination: :workflow | :mission,
  owner: pid(),
  limits: PtcRunner.Kernel.Limits.t(),
  installed_limits: PtcRunner.Kernel.Limits.t()
}

built_provider()

@type built_provider() :: %{
  capabilities: [PtcRunner.Kernel.Capability.t()],
  snapshot: map() | nil,
  close: close() | nil,
  data_class: :normal | :private_inspection,
  accepts_data: [:normal | :private_inspection],
  exports: %{optional(atom()) => term()}
}

close()

@type close() :: (-> term())

A zero-arity cleanup function returned by a provider builder.

Declared here rather than borrowed from the internal cleanup module, so the public contract does not depend on an implementation detail's documentation.

context()

@type context() :: %{
  optional(:deadline) => PtcRunner.Kernel.Deadline.t(),
  optional(:deadline_ms) => integer(),
  optional(:resource_registrar) => PtcRunner.Kernel.ResourceRegistrar.t(),
  application_content_digest: binary(),
  destination: :workflow | :mission,
  owner: pid(),
  limits: PtcRunner.Kernel.Limits.t(),
  installed_limits: PtcRunner.Kernel.Limits.t(),
  provider: binary()
}

credential_resolver()

@type credential_resolver() :: ([binary()] ->
                            {:ok, credential_values()} | {:error, term()})

credential_values()

@type credential_values() :: %{required(binary()) => binary()}

preflighted()

@type preflighted() :: %{
  acquire: acquire(),
  release: (-> :ok | {:error, term()}) | nil,
  data_class: :normal | :private_inspection,
  accepts_data: [:normal | :private_inspection],
  requires: [atom()],
  provides: [atom()]
}

prepared()

@type prepared() :: %{
  credential_names: [binary()],
  data_class: :normal | :private_inspection,
  accepts_data: [:normal | :private_inspection],
  requires: [atom()],
  provides: [atom()],
  workflow_llm?: boolean(),
  workflow_llm_route:
    nil
    | %{source: binary(), installation_revision: binary(), default: boolean()}
    | %{
        source: binary(),
        installation_revision: binary(),
        default: boolean(),
        max_calls: pos_integer() | nil
      },
  preflight: (-> {:ok, acquire()}
                 | {:ok, acquire(), (-> :ok | {:error, term()})}
                 | {:error, term()})
}

registry_builder()

@type registry_builder() :: staged_builder()

staged_builder()

@type staged_builder() ::
  {:staged, (map(), context() -> {:ok, prepared()} | {:error, term()}),
   PtcRunner.Kernel.ProviderDescriptor.data_policy() | nil}

t()

@type t() :: %PtcRunner.Kernel.ProviderRegistry{
  authority_owner: authority_owner() | nil,
  builders: %{required(binary()) => registry_builder()},
  credential_resolver: credential_resolver(),
  installed_limits: PtcRunner.Kernel.Limits.t()
}

Functions

build(registry, name, config, context)

@spec build(t(), binary(), map(), build_context()) ::
  {:ok, built_provider()} | {:error, term()}

Builds one entry through all three lifecycle phases.

Run assembly uses the individual phase functions so the barrier spans every selected provider. This convenience path remains useful for embedding and focused provider tests. It rejects registrar-backed contexts because scoped acquisition requires PtcRunner.Kernel.ProviderAcquisition's complete multi-provider lifecycle.

close(arg1)

@spec close(t()) :: :ok

Idempotently releases private host authority retained by this registry.

new(additional_builders \\ %{}, opts \\ [])

@spec new(
  map(),
  keyword()
) :: {:ok, t()} | {:error, :invalid_provider_registry}

Creates a registry from explicit staged builders keyed by provider name.

staged(prepare, declared_policy \\ nil)

@spec staged(
  (map(), context() -> {:ok, prepared()} | {:error, term()}),
  PtcRunner.Kernel.ProviderDescriptor.data_policy() | nil
) :: staged_builder()

Marks a trusted builder as staged, optionally bound to its declared policy.

The preparation callback must be side-effect free. Its returned preflight callback may perform only non-secret local checks; the acquire callback is the first phase allowed to use resolved credentials or open a provider. Optional :data_class and :accepts_data fields default to normal-only and must exactly match the acquired build. A builder bound to a declared policy must also prepare exactly that policy; an installed catalog projects one from the sealed descriptor of every builder it selects.

valid?(registry)

@spec valid?(term()) :: boolean()

Checks the complete inert registry shape accepted by the constructor.