Skill Registry & Delivery
How OpenCrane catalogs, scans, entitles, and delivers skill bundles to tenant agents. Like Obot, it is split: the control plane is the source of truth (catalog, scanning, entitlements); the in-cluster Skill Registry app is the delivery gate that tenant pods pull from.
See also: obot.md (the sibling MCP plane), auth.md (token audiences), and hosting-architecture.md.
Planes at a glance
author/promote ──▶ Control plane (SkillBundle rows, scan, grants)
│ GET /api/internal/bundles/:digest/content?tenantName=…
▼ (scanStatus=passed + live entitlement check)
Skill Registry app ──serves /bundles/:digest──▶ tenant pod
▲ (OpenClaw)
aud=skill-registry projected token ─────────────────────────────┘The agent pulls only entitled, scan-passed bundle content over HTTP, per read. There is no shared-skills volume and no list/search verb for pods — delivery is existence-hiding (a non-entitled or unknown digest returns 404, never 403).
The catalog (control plane)
SkillBundle Prisma model (apps/control-plane/prisma/schema.prisma): id, name, description, version, digest (unique on name+version+digest), content (the raw skill markdown), scope (org/department/project/personal), status (Draft/Review/Published), tags, optional sourceId, and the scan fields scanStatus / scanFindings / scannedAt. Related rows: SkillEntitlement (grant-backed access), SkillPromotion (scope-transition history).
CRUD + lifecycle live at /api/v1/skills/catalog (skill-catalog.ts) and via oc skills ….
Lifecycle: scan → validate → register → entitle → promote
- Author / ingest. Create a bundle (Draft); third-party sources (Anthropic skills, git, manual) ingest through the same pipeline. Creating a bundle directly as
publishedis rejected — it must be scanned first. - Scan.
POST /api/v1/skills/catalog/:id/scanruns a vulnerability scan (scan-bundle.ts): probes the PATH for Grype, then Trivy; if neither is present it returnsscanner-unavailable(graceful — does not crash). Findings of critical/high severity fail the scan. Outcome persists toscanStatus/scanFindings/scannedAtand is audited. - Promote gate.
PUT /api/v1/skills/catalog/:idrejects promotion topublishedunlessscanStatus = passed. Lower-severity findings are recorded but non-blocking. - Entitle. Entitlements are grants (
GrantCompilerPayloadType.SkillBundle) at a scope (org/department/project/personal) for a subject (group/tenant/user), allow or deny, with priority. The grant compiler resolves the effective decision per tenant. - Promote across scopes.
SkillPromotionrecords scope transitions (e.g. project → org) with an approval status; informational history, not a runtime gate.
Delivery (how a bundle reaches a pod)
- The operator injects a projected token with audience
skill-registryat/var/run/opencrane/tokens/skill-registry.token, plusOPENCRANE_SKILL_REGISTRY_URL(3-deployment.ts). There is no shared-skills volume — the entrypoint's old_link_shared_skillssymlink path is inert legacy; the live mechanism is the per-entitlement HTTP pull below. - The pod calls
GET /bundles/:digeston the Skill Registry service with that token (apps/skill-registry/src). - The Skill Registry validates the token via Kubernetes TokenReview (audience
skill-registry, tenant name parsed from thesystem:serviceaccount:<ns>:<tenant>subject), then proxies to the control plane. - The control plane's
GET /api/internal/bundles/:digest/content?tenantName=…(skill-bundles.ts) gates on: bundle exists →scanStatus = passed(else422 SCAN_FAILED) → a live grant-compiler allow for that tenant (else404, existence-hiding) → returns the content withX-Skill-Name/X-Skill-Digestheaders.
Both internal endpoints are NetworkPolicy-gated (in-cluster only) rather than behind ___AuthMiddleware; the Skill Registry's TokenReview adds defence in depth. Entitlement is checked live per request, so a revoked grant stops delivery on the next pull.
Contract surface
The effective contract (re-pulled by the pod) lists entitled bundle ids under skills.entitled. This is advisory for the pod; the authoritative gate is the live per-request entitlement check at delivery time. (The MCP skills server toggle in the runtime contract governs whether the skill mechanism is active at all — see obot.md.)
Current state & gaps
- ✅ Catalog CRUD, scan (Grype/Trivy) + promote gate, entitlement grants, audit.
- ✅ Skill Registry delivery app: TokenReview + proxy, scan/entitlement gates, existence-hiding, per-read delivery.
- 🔶 OCI/ORAS (Zot) digest-pinned bundle storage is the longer-term design; today bundle
contentis served from the control-plane DB through the registry, not an OCI registry. Thedigestfield already pins identity, so the storage backend can change without altering the delivery contract.