Skip to content

Settings Schema

Capsem has two schema families. Service Settings V2 is the service/app control plane contract used by corp admins and capsem-admin. The guest/UI descriptor schema is the build-time contract for generated setting descriptors and frontend rendering. They are intentionally separate.

Key files:

FileRole
src/capsem/builder/service_settings.pyPydantic Service Settings V2 admin model
schemas/capsem.service-settings.v2.schema.jsonGenerated JSON Schema for capsem.service-settings.v2
schemas/fixtures/service-settings-v2-*.jsonValid, invalid, and defaults contract fixtures shared with Rust/Python tests
crates/capsem-core/src/settings_profiles/mod.rsRust ServiceSettings runtime model and validation
src/capsem/admin/cli.pycapsem-admin settings schema/validate/doctor
src/capsem/builder/schema.pyGuest/UI descriptor Pydantic models
config/settings-schema.jsonGuest/UI descriptor JSON Schema
frontend/src/lib/types/settings.tsTypeScript settings and Policy wire types
crates/capsem-core/tests/settings_spec.rsRust conformance tests
frontend/src/lib/__tests__/settings_spec.test.tsTypeScript conformance tests
tests/test_settings_spec.pyPython schema + conformance tests
tests/settings_spec/golden.jsonGolden fixture (shared by all three)

Service settings configure the service control plane:

SectionPurpose
appHost app behavior and appearance defaults
profilesBuilt-in, corp, and user profile roots plus selected default profile
assetsService asset/cache locations and optional download base URL
credentialsCredential backend and credential references
telemetryExport endpoint, headers, retry, redaction, and failure mode
remote_policyRemote policy plugin endpoint, timeout, token reference, and failure behavior
profile_catalogSigned profile catalog URL, payload public key, and check interval
corp_directivesCorp-applied profile overrides after profile inheritance

The schema id is capsem.service-settings.v2; the artifact is schemas/capsem.service-settings.v2.schema.json.

Admin validation is through capsem-admin:

Terminal window
capsem-admin settings schema
capsem-admin settings validate service.toml
capsem-admin settings validate service.toml --json
capsem-admin settings doctor service.toml --json

JSON input uses Pydantic model_validate_json(). JSON output uses model_dump_json(). TOML is parsed once and immediately validated through the same model. Raw nested JSON or TOML dictionaries are not a public admin API.

Cross-runtime drift is pinned by:

TestProof
tests/test_service_settings.pyPython validates/dumps fixtures and checks schema/default stability
crates/capsem-core/src/settings_profiles/tests.rsRust parses the same fixtures and rejects the same invalid shapes
schemas/fixtures/service-settings-v2-defaults.jsonShared defaults contract; Python dumps it and Rust compares it to ServiceSettings::default()

The remaining sections describe the guest/UI descriptor schema. It is not the Service Settings V2 runtime contract and is not a compatibility layer for old v1 service settings.

The settings tree has exactly two node types, discriminated by the kind field:

graph TD
    ROOT["SettingsRoot"]
    ROOT --> G1["GroupNode\nkind=group"]
    ROOT --> G2["GroupNode\nkind=group"]
    G1 --> S1["SettingNode\nkind=setting\nsetting_type=bool"]
    G1 --> S2["SettingNode\nkind=setting\nsetting_type=text"]
    G1 --> S3["SettingNode\nkind=setting\nsetting_type=action"]
    G2 --> G3["GroupNode\nkind=group"]
    G3 --> S4["SettingNode\nkind=setting\nsetting_type=mcp_tool"]

GroupNode (kind="group"): container with children.

FieldTypeRequiredDescription
keystringyesDot-separated path (e.g. ai.anthropic)
namestringyesDisplay name
descriptionstringnoHelp text
enabled_bystringnoKey of a bool setting that gates this group
enabledboolnoEffective enabled state (default true)
collapsedboolyesWhether the UI renders this group collapsed
childrenSettingsNode[]yesNested groups and settings

SettingNode (kind="setting"): everything else — regular settings, actions, and MCP tools. The setting_type field determines which subfields are relevant.

FieldTypeRequiredDescription
keystringyesDot-separated path
namestringyesDisplay name
descriptionstringyesHelp text
setting_typeSettingTypeyesData type (see enum table below)
default_valueanynoDefault from guest config
effective_valueanynoResolved value (corp > user > default)
sourcePolicySourcenoWhere effective value came from
modifiedstringnoISO timestamp of last user change
corp_lockedboolnoWhether corp.toml overrides this
enabled_bystringnoKey of a bool setting that gates this
enabledboolnoEffective enabled state
collapsedboolnoUI collapse state
metadataSettingMetadatanoExtra fields (defaults to empty)
historyHistoryEntry[]noAudit trail of value changes

Actions (check_update, preset_select, rerun_wizard) and MCP tools are SettingNode variants. They use setting_type="action" or setting_type="mcp_tool" with the relevant metadata fields. Consumers check setting_type, not kind.

13 values. The first 11 are data types with stored values. The last two are structural variants.

ValueCategoryDescription
textvalueFree-form string
numbervalueInteger with optional min/max
urlvalueURL string
emailvalueEmail address
apikeyvalueAPI key (masked input, prefix hint)
boolvalueBoolean toggle
filevalue{ path, content } object
kv_mapvalue{ key: value } dictionary
string_listvalueArray of strings
int_listvalueArray of integers
float_listvalueArray of floats
actionstructuralUI button/widget, no stored value
mcp_toolstructuralMCP tool definition

All metadata lives in a single SettingMetadata object. Most fields are optional with sensible defaults. Fields are grouped by purpose.

FieldTypeDefaultDescription
domainsstring[][]Domain patterns for network policy
choicesstring[][]Valid options (drives select widget)
minintnullMinimum value (number types)
maxintnullMaximum value (number types)
rulesdict{}HTTP method permissions per rule
env_varsstring[][]Environment variables injected into guest
collapsedboolfalseDefault collapse state
formatstringnullValue format hint (e.g. domain_list)
docs_urlstringnullLink to external documentation
prefixstringnullExpected value prefix (e.g. sk-ant-)
filetypestringnullFile syntax type (e.g. json)
widgetWidgetnullOverride default UI widget
side_effectSideEffectnullFrontend action on value change
hiddenboolfalseExclude from UI, keep for policy
builtinboolfalseNon-removable (system setting)
maskboolfalseMask display value
validatorstringnullRegex pattern for validation
FieldTypeDefaultDescription
actionActionKindnullAction identifier (check_update, preset_select, rerun_wizard)
FieldTypeDefaultDescription
originMcpToolOriginnullWhere the tool runs (builtin, remote, in_vm)
FieldTypeDefaultDescription
transportMcpTransportnullProtocol (stdio, sse)
commandstringnullExecutable path (stdio transport)
urlstringnullServer URL (sse transport)
argsstring[][]Command arguments
envdict{}Environment variables for the server process
headersdict{}HTTP headers (sse transport)

Settings/profile rule storage is now structural input to the Security Engine. Runtime HTTP, DNS, MCP, model, file, and process decisions no longer flow through the removed named PolicyConfig evaluator. Author rules through the typed enforcement and detection APIs and schemas; settings saves only carry profile-owned configuration that those APIs can validate and compile. The TypeScript model preserves profile rule objects during export/import and stages them without flattening them into setting leaves.

See Rule Authoring for the rule body schema and examples.

The schema generation pipeline runs from Pydantic models to the guest/UI descriptor schema:

flowchart LR
    PM["schema.py\nPydantic models"] --> MSJ["model_json_schema()"]
    MSJ --> SCH["config/settings-schema.json"]

just schema regenerates the descriptor schema:

just schema
# Runs: uv run python scripts/generate_schema.py
# Outputs:
# config/settings-schema.json (JSON Schema from Pydantic)

The JSON Schema is derived from SettingsRoot.model_json_schema(). It contains $defs for all model types (GroupNode, SettingNode, SettingMetadata, enums) and a properties.settings array at the root.

A golden fixture at tests/settings_spec/golden.json is the contract. Three test suites parse the same fixture and verify identical structure:

flowchart TD
    GOLDEN["tests/settings_spec/golden.json\n(shared fixture)"]
    EXPECTED["tests/settings_spec/expected.json\n(expected counts + fields)"]

    GOLDEN --> PY["Python\ntests/test_settings_spec.py\n73 tests"]
    GOLDEN --> RS["Rust\ncrates/capsem-core/tests/settings_spec.rs\n12 tests"]
    GOLDEN --> TS["TypeScript\nfrontend/.../settings_spec.test.ts\n14 tests"]

    EXPECTED --> PY
    EXPECTED --> RS
    EXPECTED --> TS

    PY --> V["All three agree on:\n- total setting count\n- per-type counts\n- group count\n- setting fields\n- roundtrip serialization"]
    RS --> V
    TS --> V

99 tests total (73 Python, 12 Rust, 14 TypeScript). Every test suite checks:

AssertionVerified by
Golden fixture parsesAll three
Total setting count matches expected.jsonAll three
Per-type counts match expected.jsonAll three
Group count matches expected.jsonAll three
Setting key, name, type, enabled_by matchAll three
Roundtrip serialize/deserializePython, Rust
All 13 setting types presentAll three
Action settings have metadata.actionAll three
MCP tool settings have metadata.originAll three
File settings have { path, content }All three
Hidden/builtin settings existAll three
enabled_by references a valid boolPython, TypeScript

Any schema change requires updating the golden fixture, expected.json, and all three test suites. just test runs all of them.

Three typed paths define settings/profile behavior. Service Settings V2 is the runtime control-plane contract, Profile V2 is the VM/session contract, and the guest/UI descriptor schema is a development-time rendering contract. The descriptor schema is not runtime authority and does not inject settings into VMs.

flowchart TD
    subgraph "Service Settings Path"
        SPM["service_settings.py\nPydantic model"] --> SSJ["model_json_schema()"]
        SSJ --> SSS["schemas/capsem.service-settings.v2.schema.json"]
        SPM --> SSA["capsem-admin settings validate"]
        SSA --> SSR["Rust ServiceSettings validation"]
    end

    subgraph "Profile Path"
        PPM["profile Pydantic models"] --> PSJ["model_json_schema()"]
        PSJ --> PSS["schemas/capsem.profile.v2.schema.json"]
        PPM --> PVA["capsem-admin profile validate"]
        PVA --> PIN["VM profile/revision/asset pin"]
    end

    subgraph "Guest/UI Descriptor Path"
        PM["schema.py\nPydantic models"] --> JSG["model_json_schema()"]
        JSG --> SCHEMA["config/settings-schema.json"]
        SCHEMA --> TESTS["Conformance tests\n(Python + Rust + TypeScript)"]
    end

    subgraph "Golden Fixture Path (test time)"
        GOLDEN2["tests/settings_spec/golden.json"] --> PY2["Python tests"]
        GOLDEN2 --> RS2["Rust tests"]
        GOLDEN2 --> TS2["TypeScript tests"]
    end

The service and profile paths use Pydantic for admin validation and JSON Schema publication, then Rust validates the same typed contract. JSON input and output must pass through Pydantic model_validate_json() / TypeAdapter.validate_json() and model_dump_json() boundaries. Raw JSON dictionaries are not an admin or runtime API.

The descriptor path remains useful for UI rendering and cross-language fixture tests. It does not resurrect v1 defaults, standalone MCP settings, or generated runtime authority.

The original schema had four node types:

Old typeDiscriminant
Groupkind="group"
Leafkind="leaf"
Actionkind="action"
McpServerkind="mcp_server"

This was simplified to two:

New typeDiscriminantCovers
GroupNodekind="group"Containers with children
SettingNodekind="setting"Regular settings, actions, MCP tools

The four-type design forced consumers to match on kind with four arms, even though actions and MCP servers share nearly all fields with regular settings. The two-type design uses setting_type as the discriminant for behavior:

  • Regular settings: setting_type in {text, number, bool, ...} — value fields populated
  • Actions: setting_type="action"metadata.action specifies the action kind
  • MCP tools: setting_type="mcp_tool"metadata.origin specifies where the tool runs

Consumers match on kind (two arms: group vs. setting), then check setting_type when they need type-specific behavior. MCP servers are GroupNodes containing server config settings and MCP tool SettingNodes as children. Tool categories (snapshots, network) are nested sub-groups within the server GroupNode.

The Rust conformance tests use local test-only structs with the two-node schema. Runtime settings/profile authority is the typed Service Settings V2 and Profile V2 model, not a compatibility enum or generated defaults file.