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
| Need | Command | Notes |
|---|---|---|
| OCI toolchain | tools/oci/fetch.sh | oras 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/trivy | opa 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/promtool | promtool 3.15.1, fetched by tools/oci/fetch.sh (pinned, sha256 verified) |
| Pack interpreter | packs/bootstrap.sh | /tmp/toolchains/packs-venv, dependencies of packs/requirements.lock installed with --require-hashes (MCP Python SDK 2.3.0, MIT) |
| SDK and CLI | pip 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 timeSOURCE_DATE_EPOCH, default 0): building twice gives the same sha256 digest.<name>-<version>.spdx.jsonis an SPDX 2.3 document listing every file with its SHA-256 and the pinned dependencies ofrequirements.txt.sign:cosign sign-blob --key(offline key pair,--tlog-upload=false) over the digest string and over the SHA-256 of everySKILL.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 typeapplication/vnd.enterprise-ai.pack.v1), signs the manifest digest withcosign sign --key, then sendsPOST /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
- 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://orenv://) for the connectors, no name conflict. Dry run:POST /v1/marketplace/preflight. - Approval through the approvals service (
marketplace.install): quorum ofapprovals.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 isprod. The requester never approves (403separation_of_duties). - 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 (statusblockedwhen their scopes were not granted), connectors (secret references), prompts, OPA policies (asdraft: activating a tenant policy stays an explicit action), MCP servers registered in the MCP gateway whenconfig.mcp_servers.<name>.urlis given, one knowledge base per RAG template. The rules, OpenFGA models, MCP servers, workflows, RAG templates, alerts and migrations of the pack are recorded inassets(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 theconfigSchemaof the pack (422config_invalid). - 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
initializethen the MCP tools listing (tools list method) with every declared tool, every active workflow only uses approved tools. A failure undoes the change (422smoke_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}/rollbackreinstalls the previous version with its previous grant and removes what the newer version added. - Uninstall:
POST /v1/marketplace/installations/{id}/uninstallremoves 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: withpermissionsonly those permissions are refused (403permission_suspended) and their tools blocked; without it every tool is blocked, the agents suspended, the MCP servers disabled and the contents suspended (403installation_suspended). - Resume:
{status: installed}opens amarketplace.resumeapproval (quorum 2, never the requester, 202), then the same call with the approvedapproval_idrestores 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 (409package_suspended);{status: active}lifts it with amarketplace.package.resumeapproval. - Supervision:
GET /v1/marketplace/installations/{id}/supervisionaggregates 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 (403external_export_denied) becauseexternalExportDefaultisdeny.
Local repository for an air-gapped site
- Connected side:
enterprise-ai plugin publish <pack> --key cosign.key --oci <registry>/packs/<pack> --body-out <pack>-<version>.pack-body.jsonwrites 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>andoras cp <registry>/packs/<pack>:sha256-<hex>.sig --to-oci-layout transfer/<pack>:sha256-<hex>.sig. - Carry the
transfer/folder and the.pack-body.jsonfile on the approved medium. - 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 withclient.marketplace.import_publication(body, oci_ref="<mirror>/packs/<pack>:<version>")(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
| Pack | Content |
|---|---|
packs/hr | directory with masked contact data, leave balances and policy, idempotent leave request, own payslip only; simulated HRIS on fictitious employees |
packs/finance | invoices, 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).
-
Register the publisher key. Without an active publisher key, the
signaturegate 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
forbiddenmeans you are neithertenant_adminnorsecurity_officer. -
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.0Expected 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_failedlists every failed gate indetails.gates; 409marketplace_version_existsmeans the version is already published (publish a new version). -
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.
-
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 installcommand above.Expected result: an installation in status
pending_approval.If it fails: 409
package_suspendedmeans the package kill switch is on; 409marketplace_object_conflictmeans an object of the same name already exists in the environment. -
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_dutieswhen the requester tries to approve. -
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 theconfigSchema) or 422smoke_failed(the change is undone). -
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, thenmonitoring.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 rulesat publication (part of thetestsgate); the tests do not load them into a running Prometheus.
Troubleshooting
| Status | Code | Cause | Fix |
|---|---|---|---|
| 422 | publication_gate_failed | one gate failed or its input is missing | read details.gates, fix the pack, publish again |
| 409 | marketplace_version_exists | the version is already published | raise the version in plugin.yaml |
| 422 | config_invalid | configuration outside the configSchema of the pack | fix the configuration |
| 422 | smoke_failed | a smoke test failed after apply; the change is undone | read the failed test, fix the grant or the configuration |
| 403 | separation_of_duties | the requester tried to approve | have other approvers decide |
| 409 | package_suspended | kill switch of the package on | lift it with a marketplace.package.resume approval |
| 409 | marketplace_object_conflict | an object of the same name already exists | rename or remove the existing object |
| 403 | permission_not_granted, tool_blocked, skill_permission_denied | a permission requested but not granted | upgrade the installation with the permission in approved_permissions |
| 403 | permission_suspended, installation_suspended | installation or permission suspended | resume with an approved marketplace.resume approval |
| 403 | external_export_denied | a permission that exports pack data | keep the data inside the platform |