V04-01 — Separate extension packaging
Implementation updates: controlled invocation and local installation/inventory now provide reusable operations. The record below describes the original packaging experiment.
Status: proposed 0.4 architecture, demonstrated by a private experiment; not a production plugin runtime. See the roadmap and qualification record. The package version remains 0.3.0. No public CLI command is added.
Decision and boundaries
Keep Python analysis, canonical contracts, selection, exposure planning and extension-result validation in the core. Lightweight included adapters load only when used; the planned official Git adapter requires an explicitly installed Git. Optional OCI, MCP, Postman and Continuum Attest integrations belong in separately installed extensions. One OCI integration serves compatible destinations, including Docker Hub, GHCR and Trunx. These integrations are roadmap items, not delivered here.
CLI, CI and a future Apizr MCP server call the same typed core operations. The path is MCP transport → core operation and local authorization → approved extension; no interface scrapes human CLI output or duplicates compilation. Apizr's own MCP server differs from the business MCP servers it generates. The extension protocol is local IPC, not MCP, and has no dependency on the MCP SDK.
Use an already installed uv executable as the installation backend: uv venv
with an explicit interpreter, then uv pip install --python <plugin interpreter>.
No backend/bootstrap download, interpreter download or pip fallback is implicit.
If uv is absent, diagnose the prerequisite before changing anything. Downloads of
approved plugin packages are a separate, explicit installation operation. This
experiment installs a locally built wheel offline with --no-index --no-deps.
It adds no dependency to Apizr's runtime or universal lock.
Installing into the core with pip, uv tool --with, or pipx inject would couple
resolution and mutate the core, so is rejected for the new protocol. In-process
entry points remain only for the historical API. A new package resolver is
unnecessary; uv already provides the environment and installation primitives.
Using pip inside each venv would also work, but would require bootstrapping pip and
a second installer workflow. uv is an explicit external prerequisite, not a
transitive core dependency.
Minimal versioned contract
The experimental module apizr._prototypes.extension is installed in the wheel,
but is absent from production dispatch and entry points. It creates a canonical
Capability IR through the installed core, hashes it and invokes a reviewed absolute
plugin interpreter using -I -B -m apizr_extension_probe. The demo package imports
only the standard library. One JSON request is sent on stdin; one JSON response is
returned on stdout; stderr is diagnostic only. Exit failure or a ten-second timeout
fails the operation, without fallback or retry.
| Field | Request | Response |
|---|---|---|
protocol |
apizr.extension-probe/v1 |
Same exact version |
request_id |
packaging-proof (one-shot experiment) |
Same value |
operation |
describe |
Same operation |
source_digest |
Core canonical artifact digest | Nested under result |
result.message |
— | demo extension reached |
The core strictly validates types, required fields, extra fields, exact protocol,
operation, request identity and source digest correspondence. Non-JSON, incompatible
versions and unexpected results fail. This is a prototype contract, not a promise
that describe will become a production operation. A breaking wire change uses a
new protocol version; there is no permissive downgrade or version coercion.
Future operations must specify their own typed payloads, canonical artifacts and
explicit permissions. An extension cannot change the meaning of existing contracts.
Environments and lifecycle
The proposed user-owned data root is $XDG_DATA_HOME/apizr/plugins (default
~/.local/share/apizr/plugins) on Linux and
~/Library/Application Support/apizr/plugins on macOS. A future explicit local
override may support CI. Each approved distribution/version/interpreter combination
gets its own environment; updates create a new environment before switching an
explicit activation record. Failed staging can be removed; uninstall removes only
the selected plugin environment after deactivation. Never write into the core's
sys.prefix, uv tool environment, pipx environment or Homebrew Cellar.
The experiment implements none of the registry, activation persistence, upgrade or garbage collection machinery. Its caller supplies a fresh external work directory, retained with evidence for review, and removes it explicitly later. Core and extension installations are separate from activation (selecting the operation's extension) and authorization (operator approval of code and effects). Installed does not imply active or authorized.
uv tool and pipx installations can launch an external interpreter without changing
their environments. Homebrew owns the core formula's libexec; extensions stay
outside the formula prefix and survive independently of a formula replacement.
Interpreter removal/upgrades can break a plugin venv: report it and require an
explicit rebuild, never repair or download during invocation. The current
experiment is POSIX-only; Windows paths and process behavior remain unqualified.
apizr.pipeline.v1 keeps its existing trusted, explicitly selected, in-process
contract and same-environment installation. Existing notebook, http, mcp and
legacy extras and public commands remain unchanged. Moving optional functionality
in future tickets requires compatibility decisions; this ADR does not remove extras.
Dependencies of generated applications remain distinct from compiler/extension
dependencies. Static analysis never installs application dependencies.
Trust and limits
A venv and child process are not a sandbox. Plugins are explicitly approved code with the user's filesystem/network privileges. The experiment demonstrates that a benign plugin needs no core mutation; it does not prevent malicious mutation. The immutable before/after evidence hashes bytes, modes, symlink targets and the file set, plus installed distributions. It does not establish provenance or code innocuity, and does not hash shared interpreter files outside the core prefix.
A remote repository must never authorize plugin installation, execution of its
code, credentials, or artifact publication. No remote configuration is consumed
by the experiment. Publication is not service deployment. Attestation verifies
specific provenance claims, not safety. Production output bounds, process-tree
cleanup, cancellation, structured failures, concurrent requests, authorization
storage and crash recovery remain future work. capture_output here assumes the
reviewed tiny demonstration, not an untrusted or unbounded extension.
Qualification and reproduction
Run from the checkout with an already installed uv and Python 3.11–3.14:
uv run --locked pytest tests/test_extension_probe.py tests/test_pipeline_plugins.py tests/test_optional.py
uv run --locked python scripts/smoke_extension_packaging.py --work-dir /tmp/apizr-v04-uv
Use a fresh directory each time. The driver builds the wheel, uses real
uv tool install, builds and installs the demo separately, launches the installed
core with isolated imports from an external cwd, rejects incompatible protocol in
both directions and writes complete evidence.json. No import from src/ can
satisfy the proof. Unit tests additionally verify missing uv causes no process or
filesystem operation and that the integrity inventory detects changes.
For real Homebrew, prepare a local-only fixture using Python 3.14 (wheel tags must match Homebrew's Python and host architecture):
uv run --locked --python 3.14 python scripts/prepare_extension_homebrew.py /tmp/apizr-v04-brew
HOMEBREW_NO_AUTO_UPDATE=1 brew tap apizr-v04/prototype file:///tmp/apizr-v04-brew/tap
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install --ignore-dependencies --build-from-source apizr-v04/prototype/apizr-v04-probe
HOMEBREW_DEVELOPER=1 brew test apizr-v04/prototype/apizr-v04-probe
uv run --locked python scripts/smoke_extension_packaging.py \
--work-dir /tmp/apizr-v04-brew-proof \
--core-python "$(brew --prefix apizr-v04-probe)/libexec/bin/python" \
--core-root "$(brew --prefix apizr-v04-probe)"
brew info --json=v2 apizr-v04/prototype/apizr-v04-probe > /tmp/apizr-v04-brew-proof/homebrew-receipt.json
On an existing workstation, first verify brew list --versions python@3.14 uv
and their dependencies are installed. --ignore-dependencies is a Homebrew
development-only option for this test fixture: it avoids upgrading workstation
dependencies. Omit it only on a disposable runner where dependency installation
and upgrades are intended. HOMEBREW_NO_AUTO_UPDATE alone does not prevent them.
Preparation downloads only five base wheels referenced in the existing lock and
verifies their SHA-256 hashes. Homebrew installs the core and those wheels offline
into a real formula libexec. The fixture is local, never published; it is not a
release formula or evidence of admission to Homebrew. The source archive contains
reviewed third-party wheels only for local testing, not for redistribution. The
local tap commit uses the configured identity and DCO sign-off. Retain evidence,
then clean up with brew uninstall apizr-v04-probe and
brew untap apizr-v04/prototype; remove only the selected temporary directories.
| Configuration | Testable scope |
|---|---|
| Linux / Python 3.11–3.14 / uv tool | Dedicated CI matrix, real wheel and plugin installs |
| macOS / Python 3.11 and 3.14 / uv tool | Dedicated CI matrix |
| macOS / Homebrew Python 3.14 | Real local formula installation and full prefix inventory |
| Linux Homebrew, pipx, Windows | Compatible design where applicable, not yet qualified |
The qualification record distinguishes actual results from prepared CI. A simulated Cellar directory is never Homebrew evidence.
References: uv tool environment ownership, Homebrew Python guidance.