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:
| Field | Type | Description |
|---|---|---|
id | string | The artifact's id field value |
filename | string | Suggested filename for the local snapshot (must end in the correct extension) |
content | string | Full 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 status | ApiSource behaviour |
|---|---|
200 | Process response |
404 for optional endpoint | Skip silently |
401 / 403 | Fail fetch with credential error; print remediation hint |
429 | Retry once after Retry-After seconds; fail if second attempt also fails |
5xx | Fail fetch with server error message |
| Network error | Fail 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 type | File glob | ID field | Recommended prefix |
|---|---|---|---|
| Directives | *.directive.yaml | id | <org>-<seq>-<slug> |
| Tactics | *.tactic.yaml | id | <org>-tac-<seq> |
| Styleguides | *.styleguide.yaml | id | <org>-sty-<seq> |
| Toolguides | *.toolguide.yaml | id | <org>-tg-<seq> |
| Paradigms | *.paradigm.yaml | id | <org>-par-<seq> |
| Procedures | *.procedure.yaml | id | <org>-proc-<seq> |
| Agent profiles | *.agent.yaml | id | <org>-<role> |
| Mission step contracts | *.contract.yaml | id | <org>-msc-<seq> |
Validation Errors vs. Warnings
| Condition | Classification |
|---|---|
| Artifact YAML fails schema validation | Error |
| Duplicate artifact ID within the pack | Error |
| Dangling DRG edge (URN not in merged artifact set) | Error |
| DRG extension attempts to modify/remove a shipped node | Error |
| Artifact ID collides with a shipped artifact ID | Advisory warning |
| DRG fragment files contain duplicate edges (identical source+target+relation) | Advisory warning |
pack-manifest.yaml is present and author-modified | Advisory warning |