Skip to main content

Marketplace of complete packs

French version: ../../fr/guides/marketplace.md

The Marketplace distributes complete business packs per tenant and per environment. A pack is signed, carries its SBOM, its vulnerability scan, its test and evaluation reports, and is installed only after a preflight and a human approval. The permissions a pack requests are never the permissions it gets: an administrator grants a subset, and the platform enforces the granted set only.

Prerequisites​

NeedCommandNotes
OCI toolchaintools/oci/fetch.shoras 1.3.6, cosign 2.6.5, zot 2.1.22 (minimal), official releases, pinned, sha256 verified, into /tmp/toolchains
Policy tools/tmp/toolchains/opa, /tmp/toolchains/fga, /tmp/toolchains/trivyopa 1.21.4, fga 0.8.2, trivy 0.76.1, fetched by tools/oci/fetch.sh (pinned, sha256 verified); opa test, fga model test, trivy fs (offline database, downloaded once by trivy image --download-db-only)
Observability checks/tmp/toolchains/promtoolpromtool 3.15.1, fetched by tools/oci/fetch.sh (pinned, sha256 verified)
Pack interpreterpacks/bootstrap.sh/tmp/toolchains/packs-venv, dependencies of packs/requirements.lock installed with --require-hashes (MCP Python SDK 2.3.0, MIT)
SDK and CLIpip install -e 'sdks/python[dev]'command enterprise-ai

A missing tool stops the command with the fetch hint (cosign not found: run tools/oci/fetch.sh ...); nothing is skipped silently.

Pack layout​

plugin.yaml (Plugin manifest, spec.format: pack), README.md, LICENSE, CHANGELOG.md, and the folders agents, subagents, skills/<name>/SKILL.md, prompts, rules, policies/opa (with _test.rego), policies/openfga (model and *.fga.yaml tests), mcp/servers, mcp/tools, mcp/schemas, connectors, workflows, rag, evaluations (promptfoo config, Ragas JSONL), security, observability/alerts (Prometheus rules and SLO), tests/unit, tests/integration, tests/authz, tests/security, docs/en, docs/fr, examples. Optional folders (subagents, rag, workflows, mcp/*, connectors, examples, policies/openfga) may be omitted only with a reason in spec.layout.omitted. A pack ships no dashboard (ADR-0169): a dashboards folder under observability is refused by plugin validate and by the schema gate; the platform dashboards live in the Navin Manager (machines, services) and the portal (agents, LLM gateway, FinOps, SLO, security).

The manifest declares license, compatibility {platform, sdk, runtimes}, dependencies, requestedPermissions [{scope, reason, sensitive, classification}], dataProtection, approvals {install, sensitive, production}, docs {en, fr}, evaluations and tests. signedArtifact is filled by the build and sign steps in the published descriptor.

Develop, build, sign, publish​

enterprise-ai plugin create packs/my-legal --domain legal --name my-legal --lock packs/requirements.lock
enterprise-ai plugin validate packs/my-legal
enterprise-ai plugin build packs/my-legal --out /tmp/my-legal-dist
COSIGN_PASSWORD=... enterprise-ai plugin sign packs/my-legal --out /tmp/my-legal-dist --key cosign.key --signer acme
enterprise-ai plugin publish packs/my-legal --out /tmp/my-legal-dist --key cosign.key \
--oci <harbor-host>/packs/my-legal:1.0.0
  • build: reproducible gzip tar (sorted entries, fixed owner and time SOURCE_DATE_EPOCH, default 0): building twice gives the same sha256 digest. <name>-<version>.spdx.json is an SPDX 2.3 document listing every file with its SHA-256 and the pinned dependencies of requirements.txt.
  • sign: cosign sign-blob --key (offline key pair, --tlog-upload=false) over the digest string and over the SHA-256 of every SKILL.md; writes <name>-<version>.pack.json.
  • publish: runs the pytest suites (unit, integration, authz, security) with the pack interpreter, opa test, fga model test, promtool check rules, the promptfoo and Ragas evaluations, trivy fs, pushes the archive, the SBOM and the descriptor as one OCI artifact with ORAS (artifact type application/vnd.enterprise-ai.pack.v1), signs the manifest digest with cosign sign --key, then sends POST /v1/marketplace/packages (or writes the body with --body-out).

Register the publisher public key first (tenant_admin or security_officer):

POST /v1/marketplace/publishers {"name": "acme", "public_key": "<content of cosign.pub>"}

Publication gates​

Publication is refused (422 publication_gate_failed, every gate in details.gates) when one gate fails or its input is missing: schema (manifest, layout, every agent, tool and connector manifest), signature (pack digest and every skill against an active publisher key), sbom (every file and checksum), vulnerability_scan (Trivy report bound to the digest, no CRITICAL or HIGH, no secret), tests, evaluations (score above the threshold), licence (MIT, Apache-2.0, or another licence declared in licenseDeclaration with a reason), permissions (no unsafe scope: wildcards, secrets, platform, tenants, keys, audit, policies, authz, identity, billing, licenses, kill-switch, or an admin, impersonate, bypass, delete-all action; every scope used by an agent, a skill or a tool is requested), docs, secrets, provenance (in-toto Statement v1 with the SLSA provenance v1 predicate in provenance: {statement, signature}, subject = the pack digest, SBOM and reports bound as byproducts, signed by the pack publisher; enterprise-ai plugin publish --key produces it), privacy (G6: PII detection and masking when the pack handles PII, DLP, residency, externalExportDefault: deny), security (signedArtifactRequired, sandboxRequired, dynamicAuthorization, auditRequired set to true), approvals (requiredFor covers install, upgrade, sensitivePermissions, production and resume), configuration (configSchema is a JSON Schema of an object), resilience (G9: bounded timeout of every tool, a tool with side effects is idempotent with an idempotency key, a workflow stops or compensates on failure). The manifest also carries namespace, category, publisher and displayName {en, fr}, recorded in the catalogue, and testFlags (sdkConformance, authorization, security): the SDK conformance of every object is a result of the test report. A signed pack with an unsafe permission is refused. A published version is immutable (409 marketplace_version_exists).

Production: POST /v1/marketplace/packages/{name}/versions/{version}/production checks the stored gates, the current signature (a revoked key fails) and the OCI digest, then opens a four eyes approval. Installing in prod needs a version in status production.

Install per tenant and environment​

enterprise-ai plugin install hr --version 1.0.0 --env dev --grant hr.employees.read --grant hr.leave.read \
--secret-ref hr-hris=openbao://tenants/acme/hr/hris
  1. Preflight: environment, version status, platform compatibility, dependencies installed in the same environment, digest and signature verified again, granted permissions requested by the pack and safe, secret references (openbao:// or env://) for the connectors, no name conflict. Dry run: POST /v1/marketplace/preflight.
  2. Approval through the approvals service (marketplace.install): quorum of approvals.install, at least two distinct approvers when a granted permission is sensitive (declared sensitive, write, pay, execute, approve, transfer or delete action, restricted or secret data) or when the environment is prod. The requester never approves (403 separation_of_duties).
  3. Apply (POST /v1/marketplace/installations/{id}/apply): objects are created through the existing registries and named <object>-<environment>: agents (granted scopes only), skills (signature verified by the Skill Registry), tools (status blocked when their scopes were not granted), connectors (secret references), prompts, OPA policies (as draft: activating a tenant policy stays an explicit action), MCP servers registered in the MCP gateway when config.mcp_servers.<name>.url is given, one knowledge base per RAG template. The rules, OpenFGA models, MCP servers, workflows, RAG templates, alerts and migrations of the pack are recorded in assets (eai.marketplace_assets) with their digest and status: active, blocked (a workflow with a tool not granted), pending_endpoint (MCP server without an address), suspended. The configuration is validated against the configSchema of the pack (422 config_invalid).
  4. Smoke tests: every object registered, agents hold granted scopes only, granted skills load for their agents, tools outside the grant are blocked, every registered MCP server answers a real initialize then the MCP tools listing (tools list method) with every declared tool, every active workflow only uses approved tools. A failure undoes the change (422 smoke_failed).

At use time a requested but not granted permission is refused by the Skill Loader (403 skill_permission_denied), by the tool gateway (403 tool_blocked) and by POST /v1/marketplace/installations/{id}/authorize, which the pack MCP servers call before each tool (403 permission_not_granted).

Upgrade, rollback, uninstall​

  • Upgrade: POST /v1/marketplace/installations/{id}/upgrade {version, approved_permissions} (preflight and approval, then /apply); the previous version and grant are kept.
  • Rollback: POST /v1/marketplace/installations/{id}/rollback reinstalls the previous version with its previous grant and removes what the newer version added.
  • Uninstall: POST /v1/marketplace/installations/{id}/uninstall removes every object the installation created, with its relationship tuples. History: GET /v1/marketplace/installations/{id}/events.

Kill switch, suspension and supervision​

  • Suspend an installation or some of its capabilities: POST /v1/marketplace/installations/{id}/status {status: suspended, permissions?: [...], reason}. The effect is immediate: with permissions only those permissions are refused (403 permission_suspended) and their tools blocked; without it every tool is blocked, the agents suspended, the MCP servers disabled and the contents suspended (403 installation_suspended).
  • Resume: {status: installed} opens a marketplace.resume approval (quorum 2, never the requester, 202), then the same call with the approved approval_id restores the granted state. No bypass: without an approved approval the resume is refused.
  • Kill switch of a package: POST /v1/marketplace/packages/{name}/status {status: suspended} suspends every installation of the tenant at once and refuses installs, upgrades, publications, promotions and resumes (409 package_suspended); {status: active} lifts it with a marketplace.package.resume approval.
  • Supervision: GET /v1/marketplace/installations/{id}/supervision aggregates activity (tool calls, runs, refusals), quality (evaluations, smoke tests, success rate), run costs and the incidents naming the created objects.
  • A permission that sends pack data outside the platform (action export, external, share) is refused at use time (403 external_export_denied) because externalExportDefault is deny.

Local repository for an air-gapped site​

  1. Connected side: enterprise-ai plugin publish &lt;pack&gt; --key cosign.key --oci &lt;registry&gt;/packs/&lt;pack&gt; --body-out &lt;pack&gt;-&lt;version&gt;.pack-body.json writes the complete publication body (archive, signature, SBOM, reports, SLSA provenance). Copy the artifact and its cosign signature into an OCI layout: oras cp <registry>/packs/<pack>@<digest> --to-oci-layout transfer/<pack>:<version> and oras cp <registry>/packs/<pack>:sha256-<hex>.sig --to-oci-layout transfer/<pack>:sha256-<hex>.sig.
  2. Carry the transfer/ folder and the .pack-body.json file on the approved medium.
  3. Air-gapped side: push the layout into the local registry (the client zot or Harbor) with oras cp --from-oci-layout transfer/<pack>:<version> <mirror>/packs/<pack>:<version> (and the signature), check the digest is identical (oras manifest fetch --descriptor) and the signature (cosign verify --key cosign.pub --insecure-ignore-tlog=true), then import the body with client.marketplace.import_publication(body, oci_ref="&lt;mirror&gt;/packs/&lt;pack&gt;:&lt;version&gt;") (Python SDK). The Control Plane checks every gate again offline with the registered publisher key.

Every route takes a policy decision first (actions marketplace.*) and writes an audit event, refusals included.

Reference packs and templates​

PackContent
packs/hrdirectory with masked contact data, leave balances and policy, idempotent leave request, own payslip only; simulated HRIS on fictitious employees
packs/financeinvoices, ledger balances, payment exactly once (idempotency key, no double payment, concurrent retries), reconciliation; simulated ERP on fictitious ledger
packs/templates/<domain>banking, risk-compliance, legal, it, cybersecurity, sales-crm, marketing, procurement, support, document-intelligence

Each connector has an adapter interface, a simulated implementation on fictitious data and a brokered implementation that goes through the platform Connector Broker: no real system is reached until the tenant configures the secret reference and the egress rule.

Procedures in the portal​

The screens are Marketplace > Pack catalog (tabs Packages, Preflight, Trusted publishers), Marketplace > Installations and Marketplace > Change pipeline (portal/src/screens/kit-platform.tsx).

  1. Register the publisher key. Without an active publisher key, the signature gate refuses every pack of that publisher.

    Screen: Pack catalog > Trusted publishers > Add publisher, paste the content of cosign.pub. API: POST /v1/marketplace/publishers.

    Expected result: the publisher is listed as active.

    If it fails: 403 forbidden means you are neither tenant_admin nor security_officer.

  2. Publish the pack. The publication runs every gate, so a pack that reaches the catalog is complete and signed.

    enterprise-ai plugin publish packs/my-legal --out /tmp/my-legal-dist --key cosign.key --oci <harbor-host>/packs/my-legal:1.0.0

    Expected result: the version shows in Pack catalog > Packages with its SBOM and reports (GET /v1/marketplace/packages/{name}/versions/{version}).

    If it fails: 422 publication_gate_failed lists every failed gate in details.gates; 409 marketplace_version_exists means the version is already published (publish a new version).

  3. Run the preflight. It shows, before anything changes, what the pack requests and what the installation needs.

    Screen: Pack catalog > Preflight. API: POST /v1/marketplace/preflight.

    Expected result: the requested permissions, the required configuration and every check of the preflight passed.

    If it fails: each failed check is named; fix the environment, the dependencies or the secret references.

  4. Install the pack. The install wizard grants a subset of the requested permissions and opens the approval.

    Screen: Marketplace > Installations > Install a pack: package, version, environment, granted permissions, configuration and secret references. CLI: the enterprise-ai plugin install command above.

    Expected result: an installation in status pending_approval.

    If it fails: 409 package_suspended means the package kill switch is on; 409 marketplace_object_conflict means an object of the same name already exists in the environment.

  5. Have the installation approved. Other people decide in Agents > Approvals inbox; the requester never approves.

    Expected result: the approval reaches its quorum.

    If it fails: 403 separation_of_duties when the requester tries to approve.

  6. Apply. Apply creates the objects, then runs the smoke tests.

    Screen: Installations, action Apply on the installation. API: POST /v1/marketplace/installations/{id}/apply.

    Expected result: status installed; the actions events and supervision show the history and the activity.

    If it fails: 422 config_invalid (configuration outside the configSchema) or 422 smoke_failed (the change is undone).

  7. Follow a change of a pack object. The change pipeline moves a new version stage by stage, with its evidence: draft, test, security review, business approval, staging, production, monitoring.

    Screen: Marketplace > Change pipeline > New change, then advance it with its evidence.

    Expected result: the change reaches production, then monitoring.

    If it fails: a stage that needs an approval waits for its approval_id; reject the change with a reason to stop it.

Acceptance tests​

cd services/control-plane && .venv/bin/python -m pytest -q tests/test_marketplace.py tests/test_marketplace_governance.py
cd sdks/python && .venv/bin/python -m pytest -q tests/test_packs.py

Limits​

  • Signatures use offline cosign key pairs without a transparency log; keyless signing is not used.
  • The promptfoo configurations run with exec: providers and deterministic assertions; model graded metrics run in the platform evaluation service. The Ragas datasets are scored offline with context recall.
  • Alert rules are checked with promtool check rules at publication (part of the tests gate); the tests do not load them into a running Prometheus.

Troubleshooting​

StatusCodeCauseFix
422publication_gate_failedone gate failed or its input is missingread details.gates, fix the pack, publish again
409marketplace_version_existsthe version is already publishedraise the version in plugin.yaml
422config_invalidconfiguration outside the configSchema of the packfix the configuration
422smoke_faileda smoke test failed after apply; the change is undoneread the failed test, fix the grant or the configuration
403separation_of_dutiesthe requester tried to approvehave other approvers decide
409package_suspendedkill switch of the package onlift it with a marketplace.package.resume approval
409marketplace_object_conflictan object of the same name already existsrename or remove the existing object
403permission_not_granted, tool_blocked, skill_permission_denieda permission requested but not grantedupgrade the installation with the permission in approved_permissions
403permission_suspended, installation_suspendedinstallation or permission suspendedresume with an approved marketplace.resume approval
403external_export_denieda permission that exports pack datakeep the data inside the platform

Next steps​