Data Model: ToolSurfaceContract -- Unified Tool Surface Registry

Mission: tool-surface-contract-01KV2K2P Date: 2026-06-14

Overview

The data model is entirely in-process (no database). All persistent state is in YAML/JSON files under .kittify/ and the filesystem. The registry itself is a computed in-memory object built from built-in definitions and configuration.

Enumerations

SurfaceKind

Classifies what type of artifact a surface is. Use the full enum; do not collapse distinct kinds.

ValueMeaning
context_fileAlways-on orientation or context file (CLAUDE.md, AGENTS.md)
ruleRules/steering file loaded by the tool (e.g., .cursor/rules/*.mdc)
command_fileSlash-command file written to a global command directory
command_skillSlash-command invocation skill (.agents/skills/spec-kitty.{cmd}/SKILL.md)
doctrine_skillManaged knowledge/mission-step skill surface
workflowWorkflow definition file (e.g., Windsurf/Kilocode .workflows/)
agent_profileHost-native agent/subagent file projected from a Spec Kitty profile
plugin_manifestPlugin bundle manifest for distribution/packaging
mcp_serverMCP server config entry (.mcp.json or equivalent)
hookTool hook entry (e.g., .claude/settings.json hook section)
native_configTool-specific config glue (vibe skill_paths, MCP config, etc.)
memoryPersistent memory file (tool-native memory store)
settingTool settings entry (e.g., VS Code .vscode/settings.json entry)

> Important: session_presence is NOT a SurfaceKind. Session presence is a provider that expands into context_file, hook, and rule instances. This distinction matters for --kind filtering, docs validation, and plugin mapping.

SourceKind

ValueMeaning
checked_inCommitted to the repository; never generated
generatedProduced from a source; gitignored; repairable on demand
user_globalLives in the user's home/global config
team_globalTeam/org-managed global config
packageBundled with Spec Kitty itself
pluginProvided by a plugin bundle
external_registryFrom an external plugin or package registry

InstallScope

ValueMeaning
projectInstalled into the current repository
user_globalInstalled into the user's home/global config
teamTeam/org-managed install
plugin_bundleStaged into a plugin package root (not a project install)

ActivationMode

ValueMeaning
alwaysLoaded unconditionally at session start
globActivated by file path matching
model_decisionActivated by the model's autonomous decision
manualRequires explicit user invocation
user_invokedUser types the slash command
skills_invocableInvocable via the skills protocol
eventTriggered by a tool lifecycle event
disabledKnown but explicitly inactive

MutabilityPolicy

ValueMeaning
generated_overwrite_if_hash_matchesSafe to overwrite if on-disk hash matches manifest hash
preserve_user_editsDo not overwrite; user edits are canonical
user_editableUser may edit; repair regenerates from source
read_only_packageOwned by a package; never mutate directly

RequiredPolicy

ValueMeaning
requiredMust exist; absence is a hard failure
repairable_requiredMust exist; absence is a finding with a repair command
optionalMay exist; absence is not reported
research_gapKnown gap; absence produces a research-gap-surface finding, not a failure

Stable Finding Codes

Finding codes are kebab-case strings. They are stable across releases: a code that has appeared in any released version of doctor tool-surfaces --json cannot be renamed or removed without a deprecation cycle.

CodeMeaning
configured-tool-unknownConfig references unsupported harness key
surface-provider-missingRegistry entry has no provider
generated-surface-missingGenerated file expected by contract is absent
managed-file-driftManifest hash differs from on-disk bytes
managed-file-modifiedProvider refuses mutation due to user edits
unsafe-managed-pathPath escapes root or uses unsafe symlink
unmanaged-spec-kitty-surfaceSpec-kitty-looking file not owned by manifest
stale-generated-surfaceOld canonical command/skill still manifest-owned
configured-tool-surface-uninstalledTool configured but install manifest has no entries for its required surface
native-config-missingRequired glue config entry is absent
native-config-driftGlue config entry differs from expected value
native-agent-profile-missingGenerated native agent profile file is absent
native-agent-profile-driftGenerated native agent profile differs from manifest hash/source projection
profile-source-invalidCanonical profile YAML fails schema or repository validation
profile-projection-unsupportedConfigured tool has no verified native profile target
profile-name-invalidProfile ID/name is invalid for target native format
profile-overlay-conflictOverlay profile resolution is ambiguous or unsafe
profile-sentinel-skippedSentinel/internal profile intentionally not projected
context-file-missingRequired always-on context surface absent
session-presence-incompleteContext file or hook entry missing for session presence
research-gap-surfaceHarness has no known implementation for this surface kind
docs-ref-staleDocs reference a path not in the contract
plugin-manifest-stale-pathPlugin manifest lists a component path that is absent
plugin-skill-name-invalidPlugin skill name violates host naming requirements
bundle-component-missingBundle should include a component but projection omitted it
tool-api-assumption-mismatchSkill references an unavailable host tool capability
trust-unverifiedExternal package lacks trust metadata

Core Data Structures

ManifestRef

Reference from a SurfaceDefinition to the manifest that tracks its installation state.

FieldTypeDescription
idstrStable manifest identifier
pathstrPath relative to project root
ownerstrPython module that owns this manifest
schema_versionintManifest schema version
manifest_kindstrType of manifest (e.g., hash_refcount)

InvocationSpec

How the tool invokes this surface.

FieldTypeDescription
pattern`str \None`
examplestuple[str, ...]Example invocations
command_surfaceCommandSurfaceCapabilityWhether this is an adapter, skills-invocable, or none
argument_delivery`str \None`

ProfileProjectionSpec

How a surface produces native agent profile files.

FieldTypeDescription
source_layerstuple[str, ...]Which profile layers to project: builtin, org, project
native_formatstrTarget format: claude-agent, claude-plugin-agent, copilot-agent, vscode-agent, generic-markdown
path_templatestrPath template for output files (e.g., .claude/agents/{profile_id}.md)
include_sentinel_profilesboolWhether to project internal/sentinel profiles
name_templatestrTemplate for the native agent name (default: {profile_id})

SurfaceDefinition

Abstract contract entry: the policy for what a surface should be.

FieldTypeDescription
idstrStable unique identifier (e.g., codex.command_skills)
tool_keystrWhich tool harness this definition belongs to
variantstrHarness variant (e.g., default, plugin, user-global)
kindSurfaceKindWhat type of artifact this is
providerstrWhich SurfaceProvider handles this surface kind
source_kindSourceKindHow the artifact is produced
install_scopeInstallScopeWhere it lives
activationActivationModeHow the tool activates this surface
requiredRequiredPolicyWhether absence is an error, repairable gap, or optional
path_template`str \None`
expected_set`str \None`
manifest`ManifestRef \None`
invocation`InvocationSpec \None`
profile_projection`ProfileProjectionSpec \None`
mutabilityMutabilityPolicyWhether the file may be overwritten on repair
package_idstrPackage that owns this surface definition (e.g., spec-kitty-core)
projection_idstrStable projection group identifier
docs_refstuple[str, ...]Doc files that may reference this surface path
test_refstuple[str, ...]Test files that verify this surface
depends_ontuple[str, ...]Surface IDs that must exist before this one
plugin_componentstuple[str, ...]Plugin component types this surface contributes (e.g., skill, agent, hook)

SurfaceInstance

One concrete artifact on disk.

FieldTypeDescription
definition_idstrID of the SurfaceDefinition this instance satisfies
tool_keystrTool harness this instance belongs to
variantstrHarness variant
kindSurfaceKindSurface kind of this instance
path`str \None`
manifest_path`str \None`
owner_keystrProvider key that owns this instance
source_kindSourceKindHow this instance is produced
install_scopeInstallScopeScope of this installation
requiredRequiredPolicyRequired policy from the definition
expected_hash`str \None`
metadataMapping[str, object]Provider-specific metadata

SurfaceStatus

Result of probing one SurfaceInstance against actual on-disk state.

FieldTypeDescription
instanceSurfaceInstanceThe instance that was probed
statestrOne of: present, missing, drifted, stale, orphaned, unsafe, unsupported, not_applicable
findingstuple[SurfaceFinding, ...]Zero or more findings for this instance

SurfaceFinding

A single finding from probing actual state vs. planned state.

FieldTypeDescription
codestrStable finding code (see Finding Codes table above)
severitystrerror, warning, or info
messagestrHuman-readable explanation
tool_key`str \None`
surface_id`str \None`
path`str \None`
repair_command`str \None`
docs_ref`str \None`
detailsMapping[str, object]Provider-specific structured detail

SurfacePlan

The computed set of surface instances that should exist for configured tools.

FieldTypeDescription
configAgentConfigThe resolved agent configuration
definitionstuple[SurfaceDefinition, ...]All definitions applicable to this config
instancestuple[SurfaceInstance, ...]All expanded instances

SurfaceReport

The output of SurfaceStatusService.collect().

FieldTypeDescription
okboolTrue when no error-severity findings
schema_versionintSchema version of this report (currently 1)
project_rootstrAbsolute path to the project root
configured_toolslist[str]Tool keys from config
summarySurfaceSummaryAggregate counts
surfaceslist[SurfaceStatus]Per-instance probe results
findingslist[SurfaceFinding]All findings across all surfaces

SurfaceSummary

FieldTypeDescription
surfacesintTotal number of surface instances checked
presentintInstances with state present
missingintInstances with state missing
driftedintInstances with state drifted
warningsintFinding count at warning severity
errorsintFinding count at error severity

RepairResult

Returned by SurfaceRepairService.repair().

FieldTypeDescription
repairedlist[str]Surface IDs successfully repaired
skippedlist[str]Surface IDs skipped (e.g., user edits preserved)
failedlist[str]Surface IDs where repair failed
dry_runboolWhether this was a dry-run (no mutations made)
findings_afterlist[SurfaceFinding]Remaining findings after repair (empty on full success)

ToolHarness

FieldTypeDescription
keystrStable machine identifier (e.g., codex, claude)
display_namestrHuman-readable name
variantstuple[str, ...]Supported variants (default: ("default",))
supportedboolWhether this harness is fully supported
docs_url`str \None`
requires_cliboolWhether a CLI tool must be installed
product_family`str \None`

Manifest Files (Installation State)

These files are not policy; they are snapshots of installation state. The registry is policy.

FileOwnerContents
.kittify/command-skills-manifest.jsoncommand_installer (existing)Installed command skill files, hashes, owners
.kittify/skills-manifest.jsonskills/installer (existing)Installed doctrine skill files, hashes, owners
.kittify/agent-profiles-manifest.jsonNEW: profiles/manifest.pyProjected native agent profile files, hashes, owners

State Transitions

The ToolSurfaceContract bounded context is stateless at the registry level. State transitions apply only to manifests:

[absent] --repair--> [installed] --hash-check--> [hash-matches | hash-mismatch]
                                                          |
                                               [hash-mismatch] --repair--> [installed]

SurfaceRepairService.repair() MUST operate on provider-owned SurfaceStatus objects, not reconstruct SurfaceInstance from SurfaceFinding. This preserves manifest/source/hash/refcount context.

Invariants

1. Registry is policy; manifests are state. A surface in the manifest but absent from the registry is orphaned (not an error, but not managed). A surface in the registry but absent from the manifest is a repairable gap. 2. Finding codes are immutable once published. Codes that have appeared in any released version of doctor tool-surfaces --json cannot be renamed or removed without a deprecation cycle. Codes are kebab-case, not SCREAMING_SNAKE. 3. Provider wrapping preserves installer invariants. No provider may bypass the ref-count, hash-check, or shared-root safety logic of the underlying installer. 4. Existing manifests remain valid after the registry is introduced. No migration step rewrites or invalidates existing .kittify/command-skills-manifest.json or .kittify/skills-manifest.json content. 5. doctor skills --json output is frozen. doctor skills delegates to doctor tool-surfaces --kind command-skill --kind command-file internally; its JSON output structure must not change. 6. session_presence is a provider, not a SurfaceKind. It expands into context_file, hook, and rule SurfaceKind instances depending on the harness. 7. Repair operates on SurfaceStatus objects. Never reconstruct SurfaceInstance from SurfaceFinding for repair; doing so loses manifest/source/hash/refcount context.