ov-dev/skills/capabilities/SKILL.md
MUST be invoked before any work involving: OCI label contract, Capabilities / ImageMetadata struct, CapabilityLabelMap completeness check, LabelServices structured round-trip, source-less deploy via `ov deploy from-image`, or adding a new OCI label. Developer-facing; users author via `/ov-build:layer` and `/ov-build:image`.
npx skillsauth add overthinkos/overthink-plugins capabilitiesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Every overthink image carries a complete snapshot of "what can this image do, what does it need, what does it provide" as org.overthinkos.* OCI labels. This skill documents the contract, the single Go type behind it, the completeness test that keeps it honest, and the source-less deploy path it enables.
Source of truth: ov/capabilities.go + ov/labels.go. See also /ov-dev:go for the broader Go architecture and /ov-dev:install-plan for how this feeds the build/deploy IR.
Capabilities type alias// ov/capabilities.go:29
type Capabilities = ImageMetadata
Capabilities is a Go type alias — not a separate struct. Every existing consumer that holds an *ImageMetadata (there are many) transparently participates in the capabilities contract. New code (K8s generator, ov deploy from-image) uses the canonical Capabilities name for readability. Aliasing (rather than wrapping) means there's exactly one place fields are defined: ImageMetadata in ov/labels.go.
Why this matters: adding a field anywhere in ImageMetadata automatically becomes part of the capabilities contract, which means it MUST have a CapabilityLabelMap entry. Forget the entry and CI blocks the PR.
CapabilityLabelMap — field → OCI-label nameov/capabilities.go:35 names every label that participates in the contract. Entry grouping (identity / account / ports / security / networking / env / engine+init / distro+builder / hooks+vm / skills / data / dependency-graph / tests) mirrors the ImageMetadata field ordering so the map reads as a spec of the on-disk format.
Key entries (post-services-cutover):
| Field | Label const | What it stores |
|---|---|---|
| Services | LabelServices (org.overthinkos.services) | Structured JSON array of CapabilityService — not just names. 22 per-entry fields including kind, events, auto_start, start_retries, priority, init, layer. See "LabelServices" below. |
| Init | LabelInit | Init system name (supervisord / systemd / none). |
| ServiceNames | LabelInit | Per-init active-name list; baked alongside LabelInit for CLI ergonomics (e.g., ov service status). |
| Tests | LabelEval | Three-section {layer, image, deploy} JSON — the tests baked into the image, consumed by ov eval live / ov eval image. See /ov-build:eval. |
| Shell | LabelShell | Three-section {layer, image, deploy} JSON shell-init manifest. Each entry carries an Origin (layer name / "image" / "deploy"), an ID for overlay keying, an optional Generic body (intrinsic init + path_append) and a per-shell ByShell map (bash/zsh/fish/sh sub-blocks). Consumed by ov image inspect, ov deploy from-image, and MergeDeployShell for deploy.yml shell: overlay merging. See /ov-build:layer "Shell Init Surface". 2026-05 cutover. |
| EnvProvides / MCPProvides | LabelEnvProvides / LabelMCPProvides | Cross-container discovery: what env vars / MCP servers this image advertises to pod peers. |
| EnvRequires / MCPRequires | LabelEnvRequires / LabelMCPRequires | What this image needs from peers — validated at ov config time. |
TestCapabilityLabelCompleteness — the guardrailov/capabilities_test.go:TestCapabilityLabelCompleteness runs on every go test ./... invocation. It uses reflect.TypeOf(ImageMetadata{}) to enumerate every exported field and fails if any field is missing from CapabilityLabelMap:
// ov/capabilities.go:116
func checkCapabilityLabelCompleteness() error {
rt := reflect.TypeOf(ImageMetadata{})
var missing []string
for i := 0; i < rt.NumField(); i++ {
name := rt.Field(i).Name
if _, ok := CapabilityLabelMap[name]; !ok {
missing = append(missing, name)
}
}
// ...
}
This is the enforcement mechanism that keeps the OCI-label contract and the Go struct in sync. Workflow for adding a capability:
ImageMetadata in ov/labels.go with a JSON tag.LabelFoo = "org.overthinkos.foo") next to the other label consts.CapabilityLabelMap entry: "Foo": LabelFoo.EmitLabels / ExtractMetadata.go test ./... passes.Skip step 3 and the test fails with ImageMetadata fields without CapabilityLabelMap entry: [Foo].
LabelServices — structured per-entry service dataBefore the 2026-04 services-schema cutover, the services label was a flat list of names. After the cutover, it's a full structured round-trip:
// ov/labels.go (CapabilityService struct, paraphrased)
type CapabilityService struct {
Name string
Scope string // system / user
Enable bool
UsePackaged string // name of distro-shipped unit to reuse
Exec string
Env map[string]string
Restart string
WorkingDirectory string
User string
After []string
Before []string
Stdout string
StopTimeout string
Kind string // "program" (default) | "eventlistener"
Events string // required when Kind == "eventlistener"
AutoStart *bool // three-state; supervisord autostart=
StartRetries int
StartSecs int
StopSignal string
ExitCodes string
Priority int
Init string // which init owns it (supervisord / systemd)
Layer string // source layer name
}
Why this matters: ov deploy from-image (see below) reconstructs the full deploy surface from OCI labels alone. Pre-cutover, Services was just names — deploy-time K8s manifest generation couldn't know a process needed start_retries: 3 or was an eventlistener. Now the label carries every supervisord directive faithfully, and the K8s Kustomize generator (see /ov-advanced:kubernetes) reads from it without touching the source repo.
ov deploy from-imageCapabilitiesFromLabels(engine, imageRef) at ov/capabilities.go:136 is the source-less entry point: given an engine + image ref, it runs ExtractMetadata (which pulls labels via podman inspect / docker inspect), returns a fully-populated *Capabilities, and every downstream consumer (deploy target, K8s generator, quadlet generator) reads from that struct.
caps, err := CapabilitiesFromLabels("podman", "ghcr.io/overthinkos/fedora-coder:latest")
// caps.Services[0].Kind == "eventlistener" works — no source repo needed
This is what enables the "K8s deploy without access to overthink.yml" invariant: a Kustomize overlay can be generated from a published image alone, for dev/staging/prod clusters that never see the build repo.
The long-term direction (documented in ov/capabilities.go:17-22) is to split ImageConfig into three discriminated sections:
image.build: — Containerfile inputs (base image, layers, distro/builder selection). Consumed only by ov image build.image.capabilities: — the runtime contract documented here. Emitted as OCI labels.image.deployment: — target-specific defaults (K8s storage class, container-target port defaults). Consumed by ov deploy add.Today these co-exist in a single ImageConfig. The Capabilities alias is the stepping stone — once the schema split lands, Capabilities will point at the capabilities: subsection directly instead of aliasing the whole struct. The CapabilityLabelMap completeness test will keep the contract honest across the migration.
See /ov-build:image for current user-facing structure and /ov-build:migrate for the one-shot ov migrate unified converter that emits the new schema when the split lands.
ov/labels.go (LabelFoo = "org.overthinkos.foo").ImageMetadata with a matching JSON tag.CapabilityLabelMap entry: "Foo": LabelFoo.EmitLabels (label emission at build time).ExtractMetadata (label read-back at deploy time).mustMarshalJSON for consistent encoding (see how LabelServices, LabelEval, LabelVolumes do it).go test ./... — TestCapabilityLabelCompleteness passes./ov-dev:capabilities (this skill) with the new entry in the key-labels table./ov-build:image — user-facing image: entries in overthink.yml/ov-build:layer — user-facing layer: authoring, including service: which feeds LabelServices/ov-core:deploy — ov deploy add / from-image / sync commands/ov-advanced:kubernetes — K8s deploy target that reads LabelServices to generate Kustomize/ov-build:eval — three-section LabelEval (layer/image/deploy) — same label-contract pattern/ov-build:migrate — ov migrate unified — emits the schema that populates these labels/ov-dev:go — Go architecture overview, LoadUnified, parseLayerYAML/ov-dev:install-plan — internal IR shared across build and deploy pipelinestools
OpenCharly CLI (charly) binary installed into container/VM images for in-container use. Use when working with charly binary deployment inside containers, native D-Bus support, or the full charly toolchain (charly binary + virtualization + gocryptfs + socat).
development
Operator CachyOS workstation profile — a kind:local template + target:local deploy that installs the full dev stack (30 candies) onto a CachyOS host via ShellExecutor. Lives in the overthinkos/cachyos submodule. MUST be invoked before editing or applying the charly-cachyos workstation profile.
tools
Fedora box with the full charly toolchain using shared candies. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Same candy list as charly-arch. Includes NVIDIA GPU runtime. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-fedora box.
tools
Arch Linux box with the full charly toolchain. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Composes /charly-coder:charly-mcp so the box is reachable as an MCP gateway on port 18765. NVIDIA GPU runtime composed in. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-arch box.