spec-driven-docs 0.4.8

Spec-driven documentation: current specs, immutable decision records, and executable gates kept coherent for people and coding agents.
Documentation
# Distribution Specification

<!--TOC-->

- [Purpose](#purpose)
- [Requirements](#requirements)
  - [`distribution:manifest-identifies-every-owned-file` — The manifest identifies every owned file](#distributionmanifest-identifies-every-owned-file--the-manifest-identifies-every-owned-file)
  - [`distribution:initialization-preserves-project-content` — Initialization preserves project content](#distributioninitialization-preserves-project-content--initialization-preserves-project-content)
  - [`distribution:instances-operate-offline` — Instances operate offline](#distributioninstances-operate-offline--instances-operate-offline)
  - [`distribution:upgrade-conflicts-are-atomic` — Upgrade conflicts are atomic](#distributionupgrade-conflicts-are-atomic--upgrade-conflicts-are-atomic)
  - [`distribution:skills-are-part-of-the-payload` — Skills are part of the payload](#distributionskills-are-part-of-the-payload--skills-are-part-of-the-payload)
  - [`distribution:a-skill-has-one-owner` — A skill has one owner](#distributiona-skill-has-one-owner--a-skill-has-one-owner)
  - [`distribution:a-skill-obeys-the-portable-format` — A skill obeys the portable format](#distributiona-skill-obeys-the-portable-format--a-skill-obeys-the-portable-format)
  - [`distribution:a-landing-classifies-its-target-first` — A landing classifies its target first](#distributiona-landing-classifies-its-target-first--a-landing-classifies-its-target-first)
  - [`distribution:a-skill-checks-its-host-before-it-plans` — A skill checks its host before it plans](#distributiona-skill-checks-its-host-before-it-plans--a-skill-checks-its-host-before-it-plans)
  - [`distribution:the-doctor-answers-for-the-installed-skills` — The doctor answers for the installed skills](#distributionthe-doctor-answers-for-the-installed-skills--the-doctor-answers-for-the-installed-skills)
  - [`distribution:a-skill-plans-before-it-acts` — A skill plans before it acts](#distributiona-skill-plans-before-it-acts--a-skill-plans-before-it-acts)
  - [`distribution:skill-install-previews-before-writing` — Skill install previews before writing](#distributionskill-install-previews-before-writing--skill-install-previews-before-writing)
  - [`distribution:a-stale-skill-is-not-a-conflict` — A stale skill is not a conflict](#distributiona-stale-skill-is-not-a-conflict--a-stale-skill-is-not-a-conflict)
  - [`distribution:a-skill-install-restores-on-failure` — A skill install restores on failure](#distributiona-skill-install-restores-on-failure--a-skill-install-restores-on-failure)
  - [`distribution:skill-uninstall-removes-only-what-it-wrote` — Skill uninstall removes only what it wrote](#distributionskill-uninstall-removes-only-what-it-wrote--skill-uninstall-removes-only-what-it-wrote)
  - [`distribution:an-install-sweeps-what-the-payload-dropped` — An install sweeps what the payload dropped](#distributionan-install-sweeps-what-the-payload-dropped--an-install-sweeps-what-the-payload-dropped)
  - [`distribution:user-scope-files-stay-unrecorded` — User-scope files stay unrecorded](#distributionuser-scope-files-stay-unrecorded--user-scope-files-stay-unrecorded)
  - [`distribution:shared-skill-artifacts-have-one-home` — Shared skill artifacts have one home](#distributionshared-skill-artifacts-have-one-home--shared-skill-artifacts-have-one-home)
  - [`distribution:the-payload-names-no-planning-tool` — The payload names no planning tool](#distributionthe-payload-names-no-planning-tool--the-payload-names-no-planning-tool)
  - [`distribution:the-payload-names-no-other-project` — The payload names no other project](#distributionthe-payload-names-no-other-project--the-payload-names-no-other-project)
  - [`distribution:the-payload-roots-are-declared-once` — The payload roots are declared once](#distributionthe-payload-roots-are-declared-once--the-payload-roots-are-declared-once)
  - [`distribution:a-seeded-rule-runs-no-canon-command` — A seeded rule runs no canon command](#distributiona-seeded-rule-runs-no-canon-command--a-seeded-rule-runs-no-canon-command)

<!--TOC-->

## Purpose

Rules governing installation, ownership classes, offline verification, and upgrades. The distribution is one installed binary, `sdd`, that carries the payload, and every rule here binds whoever authors that binary. No instance adopts this spec: its subject is the installer, so an instance holding these rules holds obligations it cannot violate and verifications it cannot run. What a project owes its own installation is stated in `SPEC-instance.md`. The rules the canon alone runs at release time are stated in `SPEC-release.md`.

## Requirements

### `distribution:manifest-identifies-every-owned-file` — The manifest identifies every owned file

The installer MUST record each installed file with its ownership class, destination, and SHA-256, and the record MUST state which canon version produced it.

#### Scenario: An agent encounters a local edit

- GIVEN an installed file differs from its baseline
- WHEN the verifier reads the manifest
- THEN it distinguishes managed drift from adopted reconciliation

Verify: `cargo nextest run -E 'binary(cmd_verify) + binary(cmd_status)'`

### `distribution:initialization-preserves-project-content` — Initialization preserves project content

When a target is non-empty, the installer MUST preview its changes before writing any file.

#### Scenario: A repository has a hand-commented hook configuration

- GIVEN comments outside the managed markers
- WHEN initialization inserts its block
- THEN every outside comment remains byte-identical

Verify: `cargo nextest run -E 'binary(cmd_init)'`

### `distribution:instances-operate-offline` — Instances operate offline

The installed binary MUST verify and upgrade an instance without a network or canon checkout.

#### Scenario: The canon repository is unreachable

- GIVEN a fully installed target
- WHEN `sdd verify` and `sdd upgrade` run with no network
- THEN they check hashes, the managed block, rule IDs, and the binary's own version against the manifest, from the payload the binary carries

Verify: `cargo nextest run -E 'binary(cmd_verify) + binary(cmd_upgrade)'`

### `distribution:upgrade-conflicts-are-atomic` — Upgrade conflicts are atomic

If a managed file differs from its installed hash, then the upgrader MUST abort without changing the target.

#### Scenario: One managed configuration is edited locally

- GIVEN a valid installed instance with one managed edit
- WHEN an upgrade is requested
- THEN it lists every conflict in one run and changes no target byte

Verify: `cargo nextest run -E 'binary(cmd_upgrade)'`

### `distribution:skills-are-part-of-the-payload` — Skills are part of the payload

The distribution MUST embed every skill authored under `skills/`, so a binary carries the skills of its own version and no instance fetches them.

#### Scenario: A skill is authored under the canon's skills directory

- GIVEN a skill directory added under `skills/`
- WHEN the binary is built
- THEN `sdd skill list` names it and `sdd skill show` prints it byte-identical to the authored file

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:a-skill-has-one-owner` — A skill has one owner

The distribution MUST install every skill at user scope alone. No profile can project a skill into an instance, because an agent resolves a skill by name. A second copy under one name is a second entry offering the same skill.

#### Scenario: A project is initialized inside a home that already carries the skills

- GIVEN a home directory holding the skills and a target repository with no instance
- WHEN `sdd init --apply` runs with either profile
- THEN the target carries no `.claude/skills/` or `.agents/skills/` file and the manifest records none, so each skill resolves to exactly one file

Verify: `cargo nextest run -E 'binary(cmd_init) + binary(cmd_skill)'`

### `distribution:a-skill-obeys-the-portable-format` — A skill obeys the portable format

Every skill MUST carry only the portable Agent Skills frontmatter fields, a `name` matching its directory name, and a body at or below 150 lines.

#### Scenario: A skill gains an agent-specific field

- GIVEN a skill edited to add a vendor-only frontmatter key
- WHEN the canon test suite runs
- THEN the conformance test fails and names the offending field

Verify: `cargo nextest run -E 'binary(canon)'`

### `distribution:a-landing-classifies-its-target-first` — A landing classifies its target first

Where a setup or migration task finds no instance at a target, the skills and the shared pre-flight gate MUST route by `sdd assess`. The command MUST report its evidence and exactly one verdict, write nothing, and exit 0 for every produced classification. The three verdicts are: `brownfield` where a documentation root is populated or a methodology marker exists, `greenfield` where no document beyond root metadata exists, and `needs-decision` otherwise.

#### Scenario: A documented target carries no instance

- GIVEN a repository holding a populated documentation root and no instance manifest
- WHEN `sdd assess --target . --json` runs
- THEN the report classifies `brownfield` and exits 0, so the routing skill loads the migration path instead of landing seeds beside the corpus

Verify: `cargo nextest run -E 'binary(cmd_assess) + binary(canon)'`

### `distribution:a-skill-checks-its-host-before-it-plans` — A skill checks its host before it plans

Every skill MUST direct the agent to run the shared pre-flight gate, which observes the host with `sdd doctor`, before planning, whatever the request's flags. The `--no-plan` flag changes only when the plan gate asks for approval.

#### Scenario: A request says to skip the checks

- GIVEN a request carrying `--no-plan` and an instruction to act immediately
- WHEN the agent follows the skill's opening section
- THEN the pre-flight still runs, because the task's steps have the same dependencies whatever the request says. Only the plan gate's approval turn is skipped

Verify: `cargo nextest run -E 'binary(canon)'`

### `distribution:the-doctor-answers-for-the-installed-skills` — The doctor answers for the installed skills

`sdd doctor` MUST run every cataloged probe and exit 0 whatever they find, and its skill probes MUST pick the remediation by the user-scope record: drift the record vouches for is a stale install corrected by a plain apply. Drift it cannot account for is the user's own, corrected only with `--force`.

#### Scenario: A home holds a skill an older release installed

- GIVEN an agent root holding a `SKILL.md` whose digest the user-scope record vouches for
- WHEN `sdd doctor --json` runs
- THEN the `skill-payload` probe fails naming `sdd skill install --apply` without `--force`, and the exit code is 0

Verify: `cargo nextest run -E 'binary(cmd_doctor)'`

### `distribution:a-skill-plans-before-it-acts` — A skill plans before it acts

Every skill MUST open its body with one section that precedes every other section. That section MUST direct the agent to read the two shared gates in order before the first action of a task: the pre-flight gate first, then the plan gate. It MUST also direct the agent to hold the plan gate's three phases, plan, validate, and execute, for the whole task.

#### Scenario: A skill gains a section above the gate

- GIVEN a skill edited so another section precedes the gate section
- WHEN the canon test suite runs
- THEN the conformance test fails naming the skill, because an agent acts on the first instruction it reads

Verify: `cargo nextest run -E 'binary(canon)'`

### `distribution:skill-install-previews-before-writing` — Skill install previews before writing

When run without `--apply`, `sdd skill install` MUST list every destination and write nothing. When a destination holds bytes neither the payload nor the user-scope record accounts for, an apply MUST refuse atomically, listing every conflict.

#### Scenario: A home directory already carries an edited skill

- GIVEN `~/.claude/skills/sdd-setup/SKILL.md` with bytes the user wrote
- WHEN `sdd skill install --apply` runs
- THEN it exits 73 listing every conflicting destination, writes no file, and states `--force` as the override

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:a-stale-skill-is-not-a-conflict` — A stale skill is not a conflict

Where a user-scope destination holds the bytes a previous apply recorded writing there, `sdd skill install --apply` MUST replace it without `--force`.

#### Scenario: A release edits a skill the user never touched

- GIVEN a home directory whose installed skills came from an older release
- WHEN a newer `sdd skill install --apply` runs
- THEN every destination is rewritten and none is reported as a conflict, because bytes this tool wrote are not the user's work

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:a-skill-install-restores-on-failure` — A skill install restores on failure

Where an apply fails partway, `sdd skill install` MUST restore every destination it backed up and name the path that failed.

#### Scenario: The second skill root cannot be written

- GIVEN two skill roots, the second holding a destination the process cannot write
- WHEN `sdd skill install --apply` has already rewritten the first root
- THEN it exits 73 naming the unwritable path and leaves both roots as found. It leaves them because one agent reading a newer skill than another is worse than neither being upgraded

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:skill-uninstall-removes-only-what-it-wrote` — Skill uninstall removes only what it wrote

When run without `--apply`, `sdd skill uninstall` MUST list every removal and delete nothing. When applied, it MUST remove each embedded skill's `SKILL.md`, every other destination the record vouches for, and a directory left holding nothing else. It MUST leave every file it did not write.

#### Scenario: A skill directory carries a user's own note

- GIVEN an installed `~/.claude/skills/sdd-setup/` holding `SKILL.md` and a hand-written `notes.md`
- WHEN `sdd skill uninstall --apply` runs
- THEN `SKILL.md` is removed, `notes.md` and its directory remain, and the kept directory is named in the output

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:an-install-sweeps-what-the-payload-dropped` — An install sweeps what the payload dropped

Where the user-scope record vouches for a destination the current payload no longer carries, `sdd skill install --apply` and `sdd skill uninstall --apply` MUST remove it and the directory it empties. Both MUST leave a destination the record cannot vouch for alone.

#### Scenario: A release renames a skill

- GIVEN a home directory holding a skill under its old name, recorded by the apply that wrote it
- WHEN a newer `sdd skill install --apply` runs
- THEN the old name's `SKILL.md` and its directory are gone, the new name is installed, and only one entry is offered under either name

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:user-scope-files-stay-unrecorded` — User-scope files stay unrecorded

Files `sdd skill install` writes outside an instance MUST NOT appear in any instance manifest. The payload and the user-scope record are the references the installer compares them against, and no verification reads either.

#### Scenario: An instance is verified after a user-scope install

- GIVEN an installed instance and a completed `sdd skill install --apply`
- WHEN `sdd verify` runs against the instance
- THEN the report is unchanged by anything under the home directory

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:shared-skill-artifacts-have-one-home` — Shared skill artifacts have one home

The distribution MUST install every artifact the skills share exactly once, at the state root the user-scope record lives in, whichever agent an install selects. An uninstall MUST retain those artifacts while any agent root still holds an installed skill.

#### Scenario: One agent family's skills are uninstalled

- GIVEN both agent roots holding installed skills and the shared artifacts landed
- WHEN `sdd skill uninstall --agent codex --apply` runs
- THEN the shared artifacts remain, because the other root's skills still name them. Only the uninstall that takes the last skills takes the artifacts along

Verify: `cargo nextest run -E 'binary(cmd_skill)'`

### `distribution:the-payload-names-no-planning-tool` — The payload names no planning tool

The author MUST keep every embedded payload root free of planning tool names, so instances can pair this framework with any or no work-record convention.

#### Scenario: A method chapter names the tool it was tested against

- GIVEN a chapter edited to illustrate the seam with one planning tool by name
- WHEN the canon test suite runs
- THEN the check fails naming the file and the term, because a framework that names one tool stops being pairable with another

Verify: `cargo nextest run -E 'binary(canon)'`

### `distribution:the-payload-names-no-other-project` — The payload names no other project

The author MUST keep every embedded payload root free of the name of any project, repository, or organization outside this one. The forges, agents, and reference works the method documents as integrations are the exception.

#### Scenario: A chapter carries an example from the repository it was drafted in

- GIVEN a chapter or skill that names a sibling project while illustrating a rule
- WHEN the canon test suite runs
- THEN the check fails naming the file and the term, because a reader who lacks that project meets a reference they cannot follow

Verify: `cargo nextest run -E 'binary(canon)'`

### `distribution:the-payload-roots-are-declared-once` — The payload roots are declared once

The author MUST declare the embedded payload roots in one place that the binary, the build script, and the canon suite all read.

#### Scenario: An eighth root is embedded

- GIVEN a new root added to the embedding module alone
- WHEN the canon suite scans the payload for what it must not carry
- THEN the scan walks a list that no longer describes the payload, so the root ships unscanned unless one declaration feeds all three

Verify: `cargo nextest run -E 'kind(lib)'`

### `distribution:a-seeded-rule-runs-no-canon-command` — A seeded rule runs no canon command

Where a spec is seeded into an instance, the author MUST keep `cargo` and `just` out of every shell command its verification lines carry.

#### Scenario: A canon-only rule is left in a seeded spec

- GIVEN a seeded spec carrying a rule verified by a cargo test
- WHEN the canon test suite runs
- THEN the check fails naming the spec and the command, because the adopter reads an unrunnable verification as work it owes

Verify: `cargo nextest run -E 'binary(canon)'`