# The skaile dependency standard (canonical-identity)

Deep reference for the dependency grammar used by `@skaile/workspaces`. The
authoritative source is `docs/concepts/manifest-schema.md` and the spec
`_devlog/specs/2026-05-31-manifest-canonical-identity.md` (scoped-grammar follow-up:
`_devlog/specs/2026-06-02-scoped-asset-ref-grammar.md`). This file restates it for
asset authors and flags where the current code is **stricter than the older docs**.

## 1. Identity

Every asset has a global identity tuple:

```
(publisher, kind, name, version)
```

- **Asset-intrinsic.** Declared in the *source's* `skaile.yaml`, never by the
  consuming project. There is no project-local renaming.
- **publisher** — canonical, GitHub-shaped: 1–39 chars, alphanumeric + single
  hyphens, no leading/trailing hyphen, no double hyphen. Auto-derived from a
  GitHub source URL's org/user; **must be declared** for any non-GitHub URL.
- **kind** — one of: `skill`, `agent`, `prompt`, `bundle`, `flow`, `contract`,
  `mcp-server`, `persona`, `ruleset`, `knowledge`, `connector`.
- **version** — SemVer (see § Version waterfall). Two assets with the same tuple
  MUST be byte-identical.

## 2. The canonical reference grammar

```
kind:@<publisher>/name[#version]
```

| Token | Role |
|---|---|
| `kind:` | asset kind + colon. `mcp:` is shorthand for `mcp-server:`. |
| `@` | **scope sigil** — introduces the publisher, npm-style. |
| `<publisher>/` | publisher namespace + slash. |
| `name` | asset name. Permissive: anything except `/`, `#`, whitespace. |
| `#version` | optional **version sigil** + pin. |

Reads as an npm scoped package: `@skaile-ai/audit`.

### Examples

```
skill:@skaile-ai/audit
skill:@skaile-ai/audit#^1.4.0
bundle:@skaile-ai/skaile-development#~2.0
agent:@vercel-labs/reviewer
mcp:@skaile-ai/excel                  # == mcp-server:@skaile-ai/excel
contract:@skaile-ai/implementation-contract#1.0.0
skill:@skaile-ai/audit#6a5266fdda9d1c697f537968532691f6d70c779a   # 40-char SHA pin
```

## 3. Pins

| Pin form | Meaning |
|---|---|
| `#1.4.0` | exact SemVer |
| `#^1.4.0`, `#^1` | caret range |
| `#~1.4.0`, `#~1.4` | tilde range |
| `#1.x`, `#1.4.x` | wildcard |
| `#<40-char-sha>` | exact source-commit pin |
| absent | resolver picks the highest SemVer-sorted candidate |
| `#main`, `#latest`, `#HEAD`, any floating ref | **parse-time error** |

Floating refs are rejected by design — resolution must be reproducible.

## 4. SHA-synthetic versions

When no version can be derived (no `assets[].version`, no source `version:`, no
SemVer git tag), the resolver mints `0.0.0-sha.<7char>` from the commit. These:

- participate in resolution,
- satisfy **only** their exact literal pin (`#0.0.0-sha.abc1234`) or the
  commit-SHA pin that produced them,
- never match caret/tilde/wildcard ranges.

Consequence: an untagged source resolves only when pinned exactly. **Tag
releases** to enable ranged (`^`, `~`, `x`) installs.

## 5. `requires:` vs `dependencies:` — the split that trips authors

There are two distinct dependency channels. They use **different grammars**.

### `requires:` — content frontmatter, intra-source, bare `kind:name`

In a `SKILL.md` / `agent.yaml` / `MCP.md`, `requires:` lists peer assets the
asset needs. Form is **`kind:name`** — no `@publisher`, no `#version`. The
publisher is **inherited from the same source** (same-source assumption).

```yaml
# SKILL.md frontmatter
metadata:
  requires:
    - contract:implementation-contract
    - skill:use-exa
```

Accepted as a comma-separated string or a YAML list. Read from either the
top level or under `metadata:`.

### `dependencies:` — bundle & project manifests, full canonical refs

A **bundle** (`*.bundle.yaml`) and a **project** `skaile.yaml` use
`dependencies:` with the **full canonical grammar** and can cross publishers and
pin versions:

```yaml
# a bundle, or a project skaile.yaml
dependencies:
  - skill:@skaile-ai/audit#^1.4
  - agent:@skaile-ai/impl-build-implement
  - skill:@vercel-labs/agent-skills
```

> Putting a full canonical ref in a content `requires:` (or a bare `kind:name`
> in a project `dependencies:`) is the most common authoring mistake. Keep them
> in their lanes: **`requires:` = bare same-source; `dependencies:` = canonical
> cross-source.**

### Transitive expansion

Installing a bundle resolves every `dependencies:` ref, recursing into nested
bundles to arbitrary depth. A shared dep is de-duped; cycles are broken by a
`seen` set. A bare transitive ref inherits its parent candidate's publisher.

## 6. Resolution algorithm (per dep)

1. Gather **source candidates** from every cloned `sources[]` repo (provenance
   index, keyed `<publisher>/<kind>:<name>`).
2. Optionally gather **store candidates** from every `stores[]` catalog.
3. Filter by the pin (SemVer range / SHA / exact).
4. Pick the **highest** matching version.
5. **Conflict check:** if ≥2 finalists at that version have divergent content
   `sha256`, raise `CanonicalRefConflictError` with the dep chain — unless an
   `overrides[]` entry selects one source.
6. Cross-check: when a source and a store both produced the chosen version,
   probe `catalog.getCanonicalDigest` and compare `sha256`.

### Conflict error shape

```
error: divergent sha256 for skill:@skaile-ai/audit#1.4.0
  pulled in via:
    bundle:@skaile-ai/skaile-development#^2.0
      skill:@skaile-ai/audit#~1.4
  candidates:
    https://github.com/skaile-ai/ai-assets @ 6a5266fd   sha256: 1da6294a...
    https://github.com/mortegro/ai-assets-fork @ ab12cd34  sha256: 9e3f7c01...
resolve by:
  1. removing one source from skaile.yaml,
  2. configuring a store and using its canonical digest, or
  3. adding to overrides: (with a non-empty reason:)
```

## 7. Overrides

```yaml
overrides:
  - ref: skill:@skaile-ai/audit#1.4.0      # the canonical, fully-pinned ref
    source: https://github.com/skaile-ai/ai-assets   # which candidate wins
    reason: "vendored patch for #42; revert when upstream merges"   # REQUIRED, non-empty
```

`reason:` is mandatory — empty/missing is a parse error. Use overrides only to
break a genuine sha conflict; the right long-term fix is to stop publishing
divergent bytes under the same coordinates.

## 8. Version waterfall (recap)

1. `assets[].version` in the source `skaile.yaml`
2. source top-level `version:`
3. SemVer git tag (`git describe --tags`, `v` stripped)
4. synthetic `0.0.0-sha.<7char>`

## 9. What changed from legacy (migration cheatsheet)

| Legacy | Canonical (now) |
|---|---|
| `skill:audit@skaile-ai` | `skill:@skaile-ai/audit` |
| `skill:audit@skaile-ai#1.4.0` | `skill:@skaile-ai/audit#1.4.0` |
| bare `skill:audit` in a project dep | `skill:@<publisher>/audit` (publisher now required) |
| `@skaile` publisher | `@skaile-ai` (rebrand) |
| `repositories:` / `ai_resources:` top-level keys | `sources:` / `dependencies:` |

**The legacy `kind:name@<publisher>` grammar no longer parses** — it is a hard
error (`publisher required in asset ref`). The older `manifest-schema.md` line
about "still parses for one release with a deprecation warning" is stale; the
aliases were removed. Run the `migrate-skaile-manifest` skill to convert.

## 10. Lock key

The lock file (`skaile.lock.yaml`, schema v3) keys each entry by the
fully-pinned canonical ref `kind:@<publisher>/name#version` and records the
contributing source URL + commit and per-file `sha256`. Regenerate, never
hand-edit: `rm skaile.lock.yaml && skaile install`.
