Files
executus/skillpack/subscription.go
T
steve bf0b67f9af
executus CI / test (pull_request) Successful in 1m6s
Adversarial Review (Gadfly) / review (pull_request) Successful in 15m50s
feat(skillpack): SKILL.md-subscription battery
New additive, nil-safe battery for subscribing to skill packages in the
Anthropic agent-skills format (SKILL.md manifest + bundled files):

- Manifest/ParseManifest: SKILL.md frontmatter+body parse & validation
  (name/description required, allowed-tools passthrough, kebab/length limits)
- Tree/Pack/LoadPack: self-contained file set, order-independent content
  digest (the pin identity + change signal), bundled-file listing, traversal-
  safe staging
- Source (DirSource, GitSource): Fetch returns tree + resolved ref; git clones
  to temp, reads subpath into memory, cleans up (self-contained tree)
- Subscription + Store + content-addressed PackCache, with Memory defaults
- Syncer: Subscribe pins; Check records a PENDING update but never moves the
  pin; Apply is the only re-pin (supply-chain guard — upstream can't silently
  change what an agent runs)
- Activate: resolved packs -> majordomo agent.Skill (catalog instructions +
  one skill_use tool) for progressive disclosure; Stage materializes files

Third distinct 'skill' concept, deliberately separate from executus/skill
(saved-agent noun) and majordomo/skill (eager capability bundle). Mort-side
wiring (convars, .skillpack commands, Agent.SkillPacks, allowed-tools shim)
is a later, separate step. Full unit + hermetic local-git tests; gofmt/vet
clean; race-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 16:46:37 -04:00

65 lines
2.4 KiB
Go

package skillpack
import "time"
// Subscription is a host's persisted "I track this pack, pinned here" record. It
// is metadata only — the pinned pack's bytes live in a PackCache keyed by
// PinnedDigest. A subscription is only ever advanced to new content by an
// explicit Apply (see Syncer): a sync records a PendingDigest, it never moves
// the pin. That is the supply-chain guard — a compromised or careless upstream
// cannot change what an agent runs without a human re-pin.
type Subscription struct {
// ID is a stable host-assigned identifier.
ID string
// Name is the pack's manifest name (unique per host); what an agent lists in
// its SkillPacks and what skill_use receives.
Name string
// Description is the pinned manifest's description, cached so the catalog
// renders without opening the PackCache.
Description string
// Source coordinates.
SourceKind string // "dir" | "git"
SourceURL string // dir path or git URL
Subpath string // git subpath, if any
// TrackRef is the git commit-ish the host follows (branch/tag/SHA); empty =
// default branch. Sync fetches THIS; the pin only moves on Apply.
TrackRef string
// Pinned* describe the currently-active content.
PinnedDigest string // content digest = PackCache key + change signal
PinnedSourceRef string // source's resolved ref (git SHA) — provenance
PinnedAt time.Time
PinnedBy string
// Pending* describe an update a sync found but has NOT applied. Empty
// PendingDigest = no pending update. A pending digest equal to the pinned
// one is impossible by construction (Syncer clears it).
PendingDigest string
PendingSourceRef string
PendingAt time.Time
// Enabled lets a host keep a subscription but deactivate it without
// deleting the pin/history.
Enabled bool
}
// HasPending reports whether a sync found an unapplied update.
func (s *Subscription) HasPending() bool {
return s.PendingDigest != "" && s.PendingDigest != s.PinnedDigest
}
// pinTo advances the active pin to a fetched pack and clears any pending state.
// Used by initial pin and by Apply.
func (s *Subscription) pinTo(p *Pack, sourceRef, by string, now time.Time) {
s.Name = p.Manifest.Name
s.Description = p.Manifest.Description
s.PinnedDigest = p.Digest
s.PinnedSourceRef = sourceRef
s.PinnedAt = now
s.PinnedBy = by
s.PendingDigest = ""
s.PendingSourceRef = ""
s.PendingAt = time.Time{}
}