Contracts

config-schema.yaml

Contract: .kittify/config.yaml — doctrine.org block schema

Version: 1.1

Mission: layered-doctrine-org-layer-01KRNPEE

Updated: 2026-05-15 — multi-pack support; git-as-clone model

#

This file describes the valid shape of the doctrine.org configuration block.

Migrations that write config.yaml must conform to this schema.

$schema: "https://json-schema.org/draft/2020-12/schema" title: "DoctrineOrgBlock" description: > Optional configuration block for org-layer doctrine sources. Supports a list of named packs (preferred) or a single legacy local_path (backward compat). Projects without this block are completely unaffected by the org-layer feature.

type: object properties: doctrine: type: object properties: org: type: object oneOf: # --- Form A: multi-pack list (preferred) ---

properties: packs: type: array description: > Ordered list of org doctrine packs. Declaration order determines intra-org-layer precedence: the last entry has the highest precedence. items: type: object required: [name, local_path] properties: name: type: string description: > Unique name for this pack. Used by --pack flag and displayed in 'spec-kitty doctor doctrine' output. examples:

local_path: type: string description: > Filesystem path to the local pack directory. For git sources, this is the working tree of the git clone (.git/ present). For non-git sources, this is the snapshot directory. Tilde (~) is expanded to the current user's home directory. examples:

source_type: type: string enum: ["git", "https", "api"] description: > The type of remote source used by 'doctrine fetch'. Optional; omit if the pack is provisioned externally (e.g., by IT or a pre-cloned git repository). url: type: string description: > Remote URL for the doctrine source. Required if source_type is set. For 'git': SSH or HTTPS git remote URL. For 'https': URL to a tarball or zip bundle. For 'api': Base URL of the HTTP API server. examples:

ref: type: string description: > Optional version pin. For 'git': a tag name or full commit SHA. Branch names are accepted but discouraged for reproducibility. If omitted, HEAD of the default branch is used. For 'https': advisory (tarball URL typically encodes version). For 'api': passed as query parameter 'ref' if set. examples:

additionalProperties: false minItems: 1

  • required: [packs]
  • "security"
  • "architecture"
  • "org-distributable"
  • "~/.kittify/org/security/"
  • "/opt/company/doctrine/compliance/"
  • "git@internal.example.com:security/security-doctrine.git"
  • "https://releases.example.com/doctrine/bundle-v1.2.0.tar.gz"
  • "https://governance.internal.example.com/doctrine/v1"
  • "v2.1.0"
  • "abc123def456"

# --- Form B: single legacy local_path (backward compat) ---

properties: local_path: type: string description: > Legacy single-pack form. Treated as one anonymous pack with no name. Prefer Form A (packs list) for new configurations. source_type: type: string enum: ["git", "https", "api"] url: type: string ref: type: string additionalProperties: false

  • required: [local_path]

additionalProperties: true # other top-level doctrine keys may exist

--- Examples ---

Multi-pack (preferred):

#

doctrine:

org:

packs:

- name: security

local_path: "~/.kittify/org/security/"

source_type: git

url: "git@example.com:security/doctrine.git"

ref: "v2.1.0"

- name: architecture

local_path: "~/.kittify/org/architecture/"

source_type: git

url: "git@example.com:architecture/doctrine.git"

- name: compliance

local_path: "~/.kittify/org/compliance/"

source_type: api

url: "https://governance.example.com/compliance/v1"

IT-provisioned clones (no fetch config needed):

#

doctrine:

org:

packs:

- name: security

local_path: "~/.kittify/org/security/"

- name: architecture

local_path: "~/.kittify/org/architecture/"

Assembled distributable as a git repository (single pack):

#

doctrine:

org:

packs:

- name: org-distributable

local_path: "~/.kittify/org/distributable/"

source_type: git

url: "git@example.com:platform/org-doctrine-distributable.git"

ref: "stable"

Legacy single-pack (backward compat; treated as one anonymous pack):

#

doctrine:

org:

local_path: "~/.kittify/org/acme-corp/"

org-doctrine-source-api-contract.md

Contract: Org Doctrine Source — HTTP API Protocol

Version: 1.0 Mission: layered-doctrine-org-layer-01KRNPEE Status: Draft — normative for organisations implementing the api source type


Purpose

This document specifies the HTTP API contract that an organisation's doctrine service must implement for spec-kitty's ApiSource to fetch from it. Implementing this contract is only required if the organisation chooses source_type: api in their doctrine.org config.

Organisations using git or HTTPS bundle sources do not need to implement anything; they just publish a pack directory. This contract is for organisations that have an existing governance API (e.g., a policy service or a Confluence-backed governance hub) and want spec-kitty to fetch doctrine artifacts from it directly.


Base URL

The url field in doctrine.org config is the base URL. All endpoints are relative to it.

doctrine.org.url: "https://governance.internal.example.com/doctrine/v1"

Authentication

Authentication uses bearer token via the Authorization header:

Authorization: Bearer <token>

The token is sourced from the SPEC_KITTY_ORG_TOKEN environment variable. If the variable is not set, ApiSource proceeds without an Authorization header (for public endpoints).

For custom authentication schemes, the SPEC_KITTY_ORG_AUTH_HEADER environment variable can provide the full value of the Authorization header (e.g., SPEC_KITTY_ORG_AUTH_HEADER="Basic dXNlcjpwYXNz").


Endpoints

List artifact types

GET /artifact-types

Response 200 OK:

{
  "types": ["directives", "tactics", "styleguides", "toolguides",
            "paradigms", "procedures", "agent_profiles",
            "mission_step_contracts"]
}

ApiSource calls this endpoint first to discover which artifact types the server exposes. Types not in the response are skipped (no error).


Fetch artifacts by type

GET /artifacts/{artifact_type}

{artifact_type} is one of the values returned by /artifact-types.

Response 200 OK:

{
  "artifacts": [
    {
      "id": "acme-sec-001-threat-modelling",
      "filename": "acme-sec-001-threat-modelling.directive.yaml",
      "content": "... YAML string ..."
    }
  ]
}

Each item in artifacts:

FieldTypeDescription
idstringThe artifact's id field value
filenamestringSuggested filename for the local snapshot (must end in the correct extension)
contentstringFull YAML content of the artifact

ApiSource writes each artifact's content to <target_dir>/<artifact_type>/<filename>.


Fetch DRG extensions (optional)

GET /drg-extensions

If the server exposes DRG extensions, this endpoint returns them. If the endpoint returns 404, ApiSource skips DRG extensions (no error).

Response 200 OK:

{
  "fragments": [
    {
      "filename": "010-security.graph.yaml",
      "content": "... YAML string ..."
    }
  ]
}

ApiSource writes each fragment to <target_dir>/drg/<filename>.


Pack version (optional)

GET /version

Response 200 OK:

{ "version": "v1.2.0" }

If 404, ApiSource uses the HTTP response date as the version string in pack-manifest.yaml.


Error handling

HTTP statusApiSource behaviour
200Process response
404 for optional endpointSkip silently
401 / 403Fail fetch with credential error; print remediation hint
429Retry once after Retry-After seconds; fail if second attempt also fails
5xxFail fetch with server error message
Network errorFail fetch with connection error message

On any failure, the existing local snapshot is not modified (atomic fetch guarantee).


Versioning

This contract is versioned. The spec-kitty release notes will note any breaking changes. Non-breaking additions (new optional endpoints) do not increment the contract version.

Implementors SHOULD expose the /version endpoint so that operators can audit which version of governance artifacts their snapshot contains.


Example server implementation sketch

A minimal Flask/FastAPI server satisfying this contract:

# Pseudocode — not production code
@app.get("/artifact-types")
def artifact_types():
    return {"types": ["directives", "agent_profiles"]}

@app.get("/artifacts/{artifact_type}")
def artifacts(artifact_type: str):
    items = load_from_governance_store(artifact_type)
    return {"artifacts": [
        {"id": a.id, "filename": a.filename, "content": a.to_yaml()}
        for a in items
    ]}

@app.get("/version")
def version():
    return {"version": governance_store.current_version()}

pack-layout.md

Contract: Org Doctrine Pack Layout

Version: 1.0 Mission: layered-doctrine-org-layer-01KRNPEE Status: Draft — normative for pack authors and pack validate


Purpose

This document specifies the canonical directory layout that a spec-kitty org doctrine pack must conform to. Any tool that produces a pack (including doctrine fetch, pack assemble, or a custom build pipeline) must produce output matching this layout. The doctrine pack validate command enforces this contract.


Directory Layout

<pack-root>/
│
├── pack-manifest.yaml          [written by fetch/assemble; read-only for authors]
│
├── directives/                 [optional]
│   └── *.directive.yaml
│
├── tactics/                    [optional]
│   └── *.tactic.yaml
│
├── styleguides/                [optional]
│   └── **/*.styleguide.yaml    [subdirectory nesting allowed, same as project layer]
│
├── toolguides/                 [optional]
│   └── *.toolguide.yaml
│
├── paradigms/                  [optional]
│   └── *.paradigm.yaml
│
├── procedures/                 [optional]
│   └── *.procedure.yaml
│
├── agent_profiles/             [optional]
│   └── *.agent.yaml
│
├── mission_step_contracts/     [optional]
│   └── *.contract.yaml
│
├── drg/                        [optional]
│   └── *.graph.yaml            [DRG graph extension fragments]
│
└── org-charter.yaml            [optional] [org governance policy: interview defaults,
                                            required directives, advisory policies]

Rules

General rules

1. The <pack-root> directory MUST be readable as a filesystem directory. 2. All artifact subdirectories are OPTIONAL. A valid pack may contain any non-empty subset. 3. org-charter.yaml is OPTIONAL at the pack root. Packs without it contribute only doctrine artifacts; their governance policy is treated as empty (no interview defaults, no required directives, no advisory policies). 4. An entirely empty pack (no artifact files, no DRG extensions, no org-charter.yaml) is valid but produces no governance effect.

Artifact files

4. Each artifact file MUST conform to the spec-kitty YAML schema for its type. 5. Each artifact MUST have a unique id field within its type. Duplicate IDs within the same pack are a validation error. 6. IDs in an org pack SHOULD be namespaced to reduce collision risk with shipped IDs. Recommended convention: prefix with an org-specific code (e.g., acme-sec-001-...). This is advisory; collisions with shipped IDs are permitted but result in full-replace semantics (the org artifact substitutes the shipped artifact) and produce an advisory warning in charter lint.

DRG extensions

7. Graph extension files in drg/ MUST NOT remove or modify existing shipped graph nodes or edges. Extensions are additive only. 8. Every artifact URN referenced in a DRG extension MUST resolve to an artifact present in the merged shipped + org artifact set. Dangling edges are a validation error. 9. Multiple fragment files in drg/ are merged in alphabetical filename order. Fragment authors SHOULD name files to make ordering explicit (e.g., 010-security.graph.yaml).

org-charter.yaml

10. org-charter.yaml is OPTIONAL. Its absence is not a validation error. 11. When present, org-charter.yaml MUST conform to the OrgCharterPolicy schema (schema_version, interview_defaults, required_directives, governance_policies). Schema violations are a validation error. 12. enforcement values other than "advisory" are accepted with an advisory warning (future values may be added; unknown values SHOULD NOT fail validation). 13. required_directives entries that reference artifact IDs not present in the pack's own directives/ directory are not an error — the directive may exist in another configured pack or in the built-in set. Unresolvable IDs produce an advisory warning at runtime.

pack-manifest.yaml

14. pack-manifest.yaml is written by doctrine fetch (for non-git sources) and doctrine pack assemble. Pack authors MUST NOT create or modify this file manually. 15. For git-managed packs, pack-manifest.yaml is not written. Version information is read from git describe --tags --always on the local clone at display time. 16. The absence of pack-manifest.yaml is not a validation error for pack validate (authors run validate before the manifest is written).


Artifact ID Conventions

Artifact typeFile globID fieldRecommended prefix
Directives*.directive.yamlid<org>-<seq>-<slug>
Tactics*.tactic.yamlid<org>-tac-<seq>
Styleguides*.styleguide.yamlid<org>-sty-<seq>
Toolguides*.toolguide.yamlid<org>-tg-<seq>
Paradigms*.paradigm.yamlid<org>-par-<seq>
Procedures*.procedure.yamlid<org>-proc-<seq>
Agent profiles*.agent.yamlid<org>-<role>
Mission step contracts*.contract.yamlid<org>-msc-<seq>

Validation Errors vs. Warnings

ConditionClassification
Artifact YAML fails schema validationError
Duplicate artifact ID within the packError
Dangling DRG edge (URN not in merged artifact set)Error
DRG extension attempts to modify/remove a shipped nodeError
Artifact ID collides with a shipped artifact IDAdvisory warning
DRG fragment files contain duplicate edges (identical source+target+relation)Advisory warning
pack-manifest.yaml is present and author-modifiedAdvisory warning