Skip to content

Commit bd5b4a5

Browse files
committed
feat(skills): skill marketplace v0 — sell + rate skills as units (bundle download + skill-as-a-service)
- ServiceOffer type=skill (spec.skill: name/version/sha256/bundleConfigMap, CEL guard); controller validates bundle CM (existence/900KB cap/sha256 match) and renders a restricted-PSS busybox httpd bundle server behind the standard Middleware+HTTPRoute publish path; anti-spoof: upstream pinned to the controller-rendered workload name - 402 extra.skill {name,version,sha256} surfaced free pre-purchase (additive) - obol sell skill (--from/--from-embedded, deterministic tar via internal/skillpkg, warn-only secret scan, server-side CM apply, resume-ledger bundle) + --as-service sugar over type=agent - obol skills calldata set-hash|feedback (tag1=asr:skill, ERC-8239 PR#1704 interim tag2), reputation (getSummary + --raters whitelist), verify (on-chain sha256 vs local bundle); operator submits, controller never signs - hermes-skill-publish Role (namespace-scoped CM create/get/update/patch) gated by new VAP rule: agent ConfigMap writes must be *-skill-bundle — hermes-config untouchable; agent-side publish documented in sell/monetize-guide skills - flows/flow-19-skill-sale.sh + docs/guides/skill-marketplace.md - review: high (binary-corrupting buy.py download instructions) fixed in-flow; post-review hand fixes: 63-char Service/label cap on workload names, VAP ConfigMap guard, sell delete cleans bundle CM, secret-scan errors surfaced, doc drift
1 parent b76098e commit bd5b4a5

38 files changed

Lines changed: 8180 additions & 7 deletions

cmd/obol/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}
325325
openclawCommand(cfg),
326326
sellCommand(cfg),
327327
buyCommand(cfg),
328+
skillsCommand(cfg),
328329
modelCommand(cfg),
329330
{
330331
Name: "app",

cmd/obol/sell.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func sellCommand(cfg *config.Config) *cli.Command {
5353
sellHTTPCommand(cfg),
5454
sellMCPCommand(cfg),
5555
sellAgentCommand(cfg),
56+
sellSkillCommand(cfg),
5657
sellDemoCommand(cfg),
5758
sellListCommand(cfg),
5859
sellStatusCommand(cfg),
@@ -2811,10 +2812,30 @@ func sellDeleteCommand(cfg *config.Config) *cli.Command {
28112812
// controller renders an active:false / x402Support:false tombstone
28122813
// document while keeping the agentId.
28132814

2815+
// For type=skill offers the bundle ConfigMap is CLI/agent-created
2816+
// (not controller-owned, so no ownerRef GC). Capture its name
2817+
// before the offer disappears and delete it afterwards.
2818+
bundleCM := ""
2819+
{
2820+
bin, kubeconfig := kubectl.Paths(cfg)
2821+
if out, err := kubectl.Output(bin, kubeconfig, "get", "serviceoffers.obol.org", name, "-n", ns,
2822+
"-o", "jsonpath={.spec.type}/{.spec.skill.bundleConfigMap}"); err == nil {
2823+
if typ, cm, ok := strings.Cut(strings.TrimSpace(out), "/"); ok && typ == "skill" && cm != "" {
2824+
bundleCM = cm
2825+
}
2826+
}
2827+
}
2828+
28142829
if err := kubectlRun(cfg, "delete", "serviceoffers.obol.org", name, "-n", ns); err != nil {
28152830
return err
28162831
}
28172832

2833+
if bundleCM != "" {
2834+
if err := kubectlRun(cfg, "delete", "configmap", bundleCM, "-n", ns, "--ignore-not-found"); err != nil {
2835+
u.Warnf("could not delete skill bundle ConfigMap %s/%s: %v", ns, bundleCM, err)
2836+
}
2837+
}
2838+
28182839
// Drop the offer's manifest from the resume ledger so the next
28192840
// `obol stack up` / `obol sell resume` doesn't replay an offer
28202841
// the operator just deleted. Covers every ledger-persisted type
@@ -4793,7 +4814,10 @@ func resumePersistedServiceOffers(cfg *config.Config, u *ui.UI) error {
47934814
u.Blank()
47944815
u.Infof("Resuming %d locally-persisted sell offer(s)...", len(manifests))
47954816
for _, m := range manifests {
4796-
if err := kubectlApply(cfg, m.Manifest); err != nil {
4817+
// resumeApplyManifest (sell_skill.go) routes ConfigMap items in
4818+
// List bundles through server-side apply; skill bundle payloads
4819+
// overflow the client-side last-applied annotation otherwise.
4820+
if err := resumeApplyManifest(cfg, m.Manifest); err != nil {
47974821
u.Warnf("resume %s %s/%s: %v", m.label(), m.Namespace, m.Name, err)
47984822
continue
47994823
}

0 commit comments

Comments
 (0)