# Exploration: Official Skills Catalog (Phase 2)

## Current State

Phase 1 (archived at `openspec/changes/archive/2026-03-24-trusted-skills-distribution/`) established
the security foundation for trusted skills distribution:

- **Trust model**: `SkillTrust` enum (`Official`, `Local`, `ThirdParty`) derived from `SkillSource`
  at load time — never independently mutable (`skills/trust.rs`).
- **Open-skills deprecation**: Default OFF with `skills.legacy_open_skills` config escape hatch and
  env var support. Deprecation warnings emitted when enabled (`skills/mod.rs:195-224`).
- **Lockfile**: `skills.lock` (TOML) written on install with trust tier, source, pinned ref,
  SHA-256 content hash, install timestamp, and allowed-tools (`skills/lockfile.rs`).
- **Frontmatter parser**: Minimal hand-rolled YAML parser for `name`, `description`,
  `allowed-tools` fields from SKILL.md — no `serde_yaml` dependency (`skills/frontmatter.rs`).
- **Trust-aware prompt rendering**: Skills sorted by trust tier in prompt output.
- **Tool gating**: `filter_tools_by_trust()` — Official/Local expose all tools; ThirdParty only
  exposes tools declared in `allowed-tools` (`skills/mod.rs:436-451`).
- **Install trust gate**: ThirdParty skills with tools require `--trust` flag or interactive
  confirmation (`skills/mod.rs:615-721`).

Phase 2 builds on this to deliver the official skills catalog and related improvements. The
`SkillSource::Official { repo, path }` variant exists but no skills qualify yet — no code path
currently produces it.

### SkillForge Status

The SkillForge pipeline (`skillforge/`) is functional but disabled by default:

- **Scout** (`scout.rs`): GitHub search API discovery, deduplicated by URL.
- **Evaluate** (`evaluate.rs`): Scores on compatibility, quality, security (weighted total).
- **Integrate** (`integrate.rs`): Generates `SKILL.toml` + `SKILL.md` from scout results.

SkillForge has no trust integration — discovered skills get default trust, not `ThirdParty`.
The integrator still generates `SKILL.toml` files (Phase 2 deprecation candidate).

### Key Implementation Details

- No `build.rs` exists — the crate has no build-time code generation.
- `sha2` + `hex` are already in the dependency tree (used by lockfile).
- `toml` crate is already available (used for lockfile and config).
- The `Skill` struct carries `trust`, `origin`, and `allowed_tools` fields (`skills/mod.rs:20-41`).
- `SkillCommands` enum has `List`, `Install { source, trust }`, `Remove { name }`
  (`lib.rs:136-153`).
- `SkillsConfig` currently only has `legacy_open_skills: bool` (`config/schema.rs:237-243`).

## Affected Areas

- `clients/agent-runtime/src/skills/mod.rs` — Catalog install path, `load_skills` for official
  skills, `SkillCommands` expansion for search/catalog
- `clients/agent-runtime/src/skills/trust.rs` — Official source detection logic
- `clients/agent-runtime/src/skills/lockfile.rs` — `lock_entry_to_origin` needs Official variant,
  `skills lock repair` command
- `clients/agent-runtime/src/skills/frontmatter.rs` — SKILL.toml→SKILL.md migration, additional
  frontmatter fields (version, author, tags)
- `clients/agent-runtime/src/skillforge/mod.rs` — Trust boundary enforcement
- `clients/agent-runtime/src/skillforge/integrate.rs` — Stop generating SKILL.toml
- `clients/agent-runtime/src/lib.rs` — New CLI subcommands (search, lock repair)
- `clients/agent-runtime/src/config/schema.rs` — `SkillsConfig` expansion (catalog URL, embedded
  index path)
- `clients/agent-runtime/Cargo.toml` — No new dependencies expected
- `clients/agent-runtime/build.rs` — **New file** for embedded index generation

## Findings

### Q1: Official Repository Design

**Recommendation**: Create `dallay/corvus-skills` as a public GitHub repository.

**Structure**:

```
dallay/corvus-skills/
├── index.toml              # Machine-readable catalog index
├── README.md               # Human-readable catalog overview
├── skills/
│   ├── git-expert/
│   │   └── SKILL.md        # Skill content with YAML frontmatter
│   ├── rust-expert/
│   │   └── SKILL.md
│   ├── docker-expert/
│   │   └── SKILL.md
│   └── ...
└── .github/
    └── workflows/
        ├── validate.yml    # CI: lint frontmatter, check index consistency
        └── publish.yml     # CI: update index.toml on merge to main
```

**`index.toml` structure**:

```toml
# Auto-generated by CI — do not edit manually
[meta]
version = 1
generated_at = "2026-03-24T12:00:00Z"
commit = "abc123def456"

[skills.git-expert]
description = "Git operations, branching strategies, and conflict resolution"
version = "1.0.0"
content_hash = "sha256:e3b0c44..."
path = "skills/git-expert"
tags = ["git", "vcs"]

[skills.rust-expert]
description = "Rust patterns, ownership, lifetimes, and cargo workflows"
version = "1.0.0"
content_hash = "sha256:a1b2c3..."
path = "skills/rust-expert"
tags = ["rust", "systems"]
```

**Review process**: Standard GitHub PR workflow. CI validates frontmatter, checks `index.toml`
consistency, and verifies content hashes. The `publish.yml` workflow regenerates `index.toml` on
merge to `main`.

**Key decision**: Keep `index.toml` auto-generated by CI (not hand-edited) to prevent hash
desync. Contributors only edit `SKILL.md` files; CI regenerates the index.

### Q2: Embedded Index Strategy

**Recommendation**: Use `build.rs` with `include_str!` on a committed snapshot file.

**Approach**:

1. The `dallay/corvus-skills` repo CI generates `index.toml` on every merge.
2. A periodic sync (manual or CI-driven) copies the latest `index.toml` into the agent-runtime
   crate as `src/skills/catalog_index.toml`.
3. A new `build.rs` generates `catalog_index.rs` containing:
   ```rust
   pub const EMBEDDED_INDEX: &str = include_str!("skills/catalog_index.toml");
   pub const EMBEDDED_INDEX_COMMIT: &str = "abc123def456";
   ```
4. At runtime, the embedded index is parsed lazily on first use.

**Alternatives considered**:

- **build.rs fetches from network** — Rejected. Builds must be deterministic and offline-capable.
  A network fetch in build.rs breaks reproducible builds and CI environments without internet.
- **Git submodule** — Rejected. Adds friction for contributors and complicates CI. A committed
  snapshot file is simpler and more explicit.
- **No embedded index (always fetch)** — Rejected. Zero-network cold start is a key requirement.
  Users must be able to `corvus skills search git` without network on first run.

**Staleness handling**: The embedded index is a floor, not a ceiling. When network is available,
`corvus skills search` and `corvus skills install <name>` SHOULD fetch the latest `index.toml`
from GitHub raw content (with cache headers). The embedded index is the fallback when:

- Network is unavailable
- GitHub is unreachable
- This is the first run (no cached index yet)

**Cache location**: `{workspace}/catalog-index-cache.toml` with an `If-Modified-Since` header
strategy. Cache TTL: 24 hours.

### Q3: Catalog Install UX

**Recommendation**: Three install modes distinguished by source format.

**By catalog name** (new):

```bash
corvus skills install git-expert
# → Looks up "git-expert" in catalog index (embedded or cached)
# → Resolves to dallay/corvus-skills repo, path skills/git-expert
# → Clones sparse checkout of that path
# → Sets SkillSource::Official { repo, path }
# → Trust: Official (no --trust gate needed)
```

**By URL** (existing):

```bash
corvus skills install https://github.com/user/my-skill
# → Existing git clone flow
# → Sets SkillSource::GitRepo { url }
# → Trust: ThirdParty (--trust gate if tools declared)
```

**By local path** (existing):

```bash
corvus skills install ./my-skill
# → Existing symlink/copy flow
# → Sets SkillSource::Local or LinkedLocal
# → Trust: Local
```

**Search command** (new):

```bash
corvus skills search <query>
# → Fuzzy-match against catalog index (name, description, tags)
# → Shows matching skills with name, description, trust tier
# → Works offline using embedded index as fallback

corvus skills search git
#   git-expert v1.0.0 [official] — Git operations, branching, conflict resolution
#   git-flow   v1.0.0 [official] — Git Flow workflow automation
```

**Version pinning**: The lockfile already has `pinned_ref` (git SHA). For official skills, the
lockfile entry records the commit SHA from the index. `corvus skills update <name>` checks for
newer versions in the catalog.

**New CLI subcommands**:

```rust
enum SkillCommands {
    List,
    Install { source: String, #[arg(long)] trust: bool },
    Remove { name: String },
    Search { query: String },              // NEW
    Update { name: Option<String> },       // NEW — update one or all
    Lock { #[command(subcommand)] cmd: LockCommands }, // NEW
}

enum LockCommands {
    Repair,  // Rebuild lockfile from disk state
}
```

**Catalog name detection heuristic**: If the source string contains no `/`, `\`, `.`, or `:`
characters, treat it as a catalog name lookup. Otherwise, treat as URL or path.

### Q4: SKILL.toml Deprecation Path

**Recommendation**: Soft deprecation with auto-migration tooling.

**Current state**: `load_skills_from_directory()` tries `SKILL.toml` first, then `SKILL.md`
(`skills/mod.rs:140-152`). SKILL.toml is a TOML manifest with `[skill]` table, `[[tools]]`
arrays, and `[[prompts]]` lists. The frontmatter parser currently extracts `name`, `description`,
and `allowed-tools`.

**Gap analysis — fields in SKILL.toml not in frontmatter**:

| SKILL.toml field    | Frontmatter equivalent | Status                 |
|---------------------|------------------------|------------------------|
| `skill.name`        | `name:`                | Covered                |
| `skill.description` | `description:`         | Covered                |
| `skill.version`     | `version:`             | **Missing**            |
| `skill.author`      | `author:`              | **Missing**            |
| `skill.tags`        | `tags:`                | **Missing**            |
| `[[tools]]`         | N/A                    | **Not in frontmatter** |
| `[[prompts]]`       | Body of SKILL.md       | Covered (implicitly)   |
| `allowed-tools`     | `allowed-tools:`       | Covered                |

**Plan**:

1. **Extend frontmatter parser** to recognize `version`, `author`, `tags` fields. These are simple
   scalar/list types the existing parser can handle without `serde_yaml`.
2. **Tools**: Skills should declare tools via MCP tool definitions or structured markdown sections
   in the SKILL.md body, not via frontmatter. The `[[tools]]` TOML array was a
   Corvus-specific extension that doesn't align with the Agent Skills standard. For Phase 2,
   official skills should not use `[[tools]]` — they provide instructions for the agent to use
   existing MCP tools.
3. **Warn on SKILL.toml load**: When `load_skill_toml()` is called, emit a deprecation warning:
   `"SKILL.toml is deprecated. Migrate to SKILL.md with YAML frontmatter."`.
4. **Migration command**: `corvus skills migrate <name>` converts SKILL.toml → SKILL.md with
   frontmatter. Low priority — can be deferred to Phase 3.
5. **Timeline**: Warn in Phase 2. Remove SKILL.toml support in Phase 3 (or later, based on
   adoption).
6. **SkillForge integrator**: Update `integrate.rs` to generate only SKILL.md (not SKILL.toml).

### Q5: SkillForge Trust Boundaries

**Recommendation**: Restrict SkillForge to explicit discovery mode with ThirdParty trust.

**Current state**: SkillForge is disabled by default (`enabled: false`). When enabled, it runs a
background pipeline: Scout → Evaluate → Integrate, auto-writing SKILL.toml + SKILL.md to the
output directory. There is no trust integration — discovered skills get default Local trust.

**Problems**:

1. Auto-integration bypasses the trust gate entirely — discovered skills land on disk without
   consent.
2. Discovered skills should always be `ThirdParty` but currently get `Local` default.
3. The background pipeline model is at odds with the explicit-consent trust model.

**Plan**:

1. **Replace background pipeline with explicit command**: `corvus skills discover` runs the
   Scout → Evaluate pipeline and **displays results** without auto-installing. The user then
   explicitly installs via `corvus skills install <url>`.
2. **Remove auto-integrate**: The `auto_integrate` config option and the `integrate_results()`
   auto-write path should be deprecated. Discovery is read-only.
3. **Discovered skills always ThirdParty**: If a user installs a discovered skill via
   `corvus skills install <url>`, it goes through the standard ThirdParty install path with
   trust gating.
4. **Interaction with catalog**: `corvus skills search` searches the official catalog (zero
   network if using embedded index). `corvus skills discover` searches external sources (GitHub,
   etc.) for third-party skills. These are distinct operations with different trust implications.
5. **Timeline**: Phase 2 adds the `discover` command and deprecates `auto_integrate`. Phase 3
   can remove the auto-integrate code path entirely.

### Q6: Lockfile Repair

**Recommendation**: `corvus skills lock repair` scans disk and rebuilds the lockfile.

**What it does**:

1. **Scan** `{workspace}/skills/` for directories containing SKILL.md (or SKILL.toml).
2. **Read existing lockfile** (if any, tolerating corruption).
3. **For each skill on disk**:
    - If a lockfile entry exists and is valid: keep it unchanged.
    - If a lockfile entry exists but content hash doesn't match disk: update the hash, log a
      warning ("hash mismatch for X, updated").
    - If no lockfile entry exists: create a new entry with `trust = "local"`,
      `source = "local"`, fresh content hash, current timestamp.
4. **Remove orphaned entries**: Lockfile entries for skills not found on disk are removed,
   with a log warning ("removed orphaned entry for X").
5. **Write the repaired lockfile**.

**Edge cases**:

- Symlinked skills: Resolve the symlink to check existence; set `source = "linked-local"`.
- Corrupt TOML lockfile: Start from empty, rebuild everything from disk (all entries become
  Local).
- Empty skills directory: Write an empty lockfile.

**Output**: Summary of actions taken:

```
skills lock repair:
  ✓ 3 entries verified
  ⚠ 1 entry updated (hash mismatch: docker-expert)
  + 2 entries added (my-notes, quick-fix)
  - 1 entry removed (deleted-skill)
```

### Q7: Official Source Detection

**Recommendation**: Match the lockfile source string against a known official repo identifier.

**How `SkillSource::Official` gets set**:

The critical security invariant from Phase 1 is that trust is derived from `SkillSource` variant,
not from content or user claims. A `GitRepo { url }` always maps to `ThirdParty` regardless of
the URL (see `trust.rs:150-162` privilege escalation test).

For Phase 2, `SkillSource::Official` must be set through a **controlled code path** — only the
catalog install flow produces it:

1. User runs `corvus skills install git-expert` (bare name, no URL).
2. Runtime looks up `git-expert` in the catalog index (embedded or cached).
3. Index entry confirms the skill exists in `dallay/corvus-skills` repo.
4. Runtime fetches from the known official repo URL (hardcoded constant, not user-provided).
5. `SkillSource::Official { repo: "dallay/corvus-skills", path: "skills/git-expert" }` is set.

**The key invariant**: Only the catalog install code path can produce `SkillSource::Official`.
A user cannot create an Official skill by:

- Cloning the official repo manually and running `corvus skills install ./git-expert` — this
  produces `Local` trust.
- Running `corvus skills install https://github.com/dallay/corvus-skills` — this produces
  `ThirdParty` trust (URL-based installs always map to `GitRepo`).
- Editing the lockfile to say `trust = "official"` — trust is re-derived from source at load
  time, and `lock_entry_to_origin()` currently maps non-"local" sources to `GitRepo`.

**`lock_entry_to_origin()` update needed**: Currently (`lockfile.rs:101-115`), the function only
distinguishes "local" vs everything-else-is-GitRepo. It needs to recognize official sources:

```rust
fn lock_entry_to_origin(entry: &LockEntry) -> SkillOrigin {
    let source = if entry.source == "local" {
        SkillSource::Local
    } else if entry.source.starts_with("official:") {
        // e.g. "official:dallay/corvus-skills"
        let repo = entry.source.strip_prefix("official:").unwrap_or("").to_string();
        let path = entry.path.clone().unwrap_or_default();
        SkillSource::Official { repo, path }
    } else {
        SkillSource::GitRepo { url: entry.source.clone() }
    };
    // ...
}
```

**Lockfile entry for official skills**:

```toml
[skills.git-expert]
trust = "official"
source = "official:dallay/corvus-skills"
path = "skills/git-expert"
ref = "abc123"
content_hash = "sha256:..."
installed_at = "2026-03-24T10:00:00Z"
```

### Q8: Offline/Network Model

**Recommendation**: Network required only for install/update; everything else works offline.

| Operation                      | Network required?     | Fallback                                                      |
|--------------------------------|-----------------------|---------------------------------------------------------------|
| `corvus skills list`           | No                    | Always local — reads disk + lockfile                          |
| `corvus skills search <query>` | No (preferred online) | Embedded index → cached index → stale cache                   |
| `corvus skills install <name>` | Yes (catalog fetch)   | Error with message "network required to install from catalog" |
| `corvus skills install <url>`  | Yes (git clone)       | Error (existing behavior)                                     |
| `corvus skills install <path>` | No                    | Existing local behavior                                       |
| `corvus skills update <name>`  | Yes                   | Error with message                                            |
| `corvus skills remove <name>`  | No                    | Always local                                                  |
| `corvus skills lock repair`    | No                    | Always local                                                  |
| `corvus skills discover`       | Yes                   | Error (GitHub API required)                                   |
| `corvus skills list --catalog` | No                    | Embedded index → cached index                                 |

**`corvus skills list --catalog`**: Shows all available official skills (from index), marking
which ones are installed. Works offline using embedded index. Example output:

```
Official skills catalog (12 available, 3 installed):

  git-expert      v1.0.0 [installed] — Git operations and branching
  rust-expert     v1.0.0 [installed] — Rust patterns and cargo workflows
  docker-expert   v1.0.0 [installed] — Docker containerization
  kotlin-expert   v1.0.0             — Kotlin conventions and KMP patterns
  python-expert   v1.0.0             — Python best practices
  ...
```

**Network fetch strategy for search/catalog**:

1. Check if cached index exists and is < 24h old → use it.
2. If stale or missing, attempt HTTP GET of `index.toml` from GitHub raw content.
3. On success: update cache, use fresh index.
4. On failure: fall back to embedded index (always available).
5. Never block on network for search — timeout after 3 seconds, fall back to embedded.

## Approaches

### Approach A: Monolithic Phase 2 (all 6 items together)

- Pros: Single coherent release, all pieces designed together
- Cons: Large scope (~2-3 weeks), harder to review, risk of delays blocking everything
- Effort: High

### Approach B: Phased Sub-Releases (recommended)

Split Phase 2 into three sub-phases:

**Phase 2a — Foundation**: Embedded index, frontmatter extensions, SKILL.toml deprecation warning,
`lock_entry_to_origin` Official variant support.

**Phase 2b — Catalog UX**: Official repo creation, `skills search`, `skills install <name>` from
catalog, `skills list --catalog`, `skills update`.

**Phase 2c — Cleanup**: SkillForge trust boundaries (`skills discover` command), `skills lock
repair`, SkillForge integrator SKILL.toml removal.

- Pros: Incremental delivery, easier review, lower risk per PR
- Cons: More PRs to manage, need to design interfaces across sub-phases
- Effort: Medium per sub-phase, same total

### Approach C: Minimal Viable Catalog (skip embedded index)

Skip the embedded index and build.rs entirely. Catalog search always requires network.

- Pros: Simpler implementation, no build.rs complexity
- Cons: No offline search, worse cold-start experience, doesn't meet "zero-network cold start"
  requirement from Phase 1 proposal
- Effort: Low

## Recommendation

**Approach B (Phased Sub-Releases)** is recommended. It delivers value incrementally while
keeping each change reviewable. The sub-phases have clean boundaries:

- 2a is pure runtime code (no external repo needed)
- 2b depends on the external repo being created
- 2c is cleanup that can happen any time after 2b

Start with Phase 2a since it requires no external infrastructure and unblocks 2b.

## Risks

- **Official repo governance**: Who reviews and merges skill PRs? Need to establish a lightweight
  review process before creating the repo. Recommendation: Start with a single maintainer
  (project lead) and add reviewers as the catalog grows.

- **Embedded index staleness**: If the sync from `dallay/corvus-skills` to the agent-runtime crate
  is manual, the embedded index could fall weeks behind. Mitigation: CI workflow that auto-opens
  a PR to update the embedded index when the skills repo changes.

- **Catalog name squatting**: Anyone can submit a skill with a desirable name. Mitigation: Review
  process for the official repo; reserved namespace for core skills.

- **Sparse checkout complexity**: `git clone --filter=blob:none --sparse` may not work reliably
  across all git versions. Mitigation: Fall back to shallow clone of the entire repo if sparse
  checkout fails; only the target skill directory is copied to the workspace.

- **Build.rs maintenance**: Adding a build.rs introduces build-time complexity. Mitigation: Keep
  it minimal — just const generation from a committed file. No network, no complex logic.

- **SKILL.toml deprecation disruption**: Users with existing SKILL.toml skills see warnings.
  Mitigation: Warnings are informational only; SKILL.toml continues to work. Migration tooling
  available (Phase 2 or 3).

## Key Decisions Needed

1. **Official repo name**: `dallay/corvus-skills` vs `dallay/skills` vs other?
2. **Embedded index sync**: Manual PR vs automated CI sync?
3. **Catalog name format**: Flat (`git-expert`) vs namespaced (`corvus/git-expert`)?
4. **SKILL.toml tools migration**: Drop `[[tools]]` entirely or convert to structured markdown?
5. **SkillForge future**: Keep as optional discovery or deprecate entirely?
6. **Sub-phase ordering**: Start with 2a (foundation) or 2b (repo creation)?

## Ready for Proposal

**Yes** — with the recommended Approach B (phased sub-releases). The exploration has identified
clear boundaries, implementation paths for each question, and the security invariants that must
be preserved. The proposal should focus on Phase 2a first (foundation work that requires no
external infrastructure), with 2b and 2c scoped as follow-up changes.
