Files
esh-pfi-infrastructure/docs/asset-engine/services.schema.json
T
vh c98a12baf4 catalog(fish-s2): opt references into togglable; catalog_version 1->2
asset-engine shipped the per-field enable-toggle (v0.1.14/.15) — the
durable fix for the "form submits untouched fields" family. A field
marked togglable:true renders with an OFF-by-default switch: while off
the control is disabled (excluded from submission) AND the server skips
injecting its default, so it is genuinely not sent until the user opts
in.

Per operator direction, opt fish-s2's `references` (inline-base64
Custom-clone) field in — it already satisfies the togglable-requires-
optional validator (optional:true, no default). The advanced clone
field now renders dormant and can never silently override the Voice
dropdown again.

This is a SCHEMA change (new CatalogField property), so:
- services.schema.json: add `togglable` (boolean, default false),
  mirroring the asset_engine Pydantic model that generates this schema.
- catalog_version 1 -> 2 (header: bump on schema changes).
- CATALOG-CONTRACT.md: consumer pin note -> catalog_version=2.

Scoped to `references` only. The chatterbox/dia2 clone fields are the
same family but NOT toggled: dia2 deliberately defaults voice_mode=clone
+ a clone ref as its stable out-of-box voice, and toggling that field
would change dia2's default-voice behavior (the earlier 404 fix).

Validated: jsonschema accepts togglable; additionalProperties:false
guard still rejects unknown props.
2026-06-01 18:05:56 -07:00

766 lines
19 KiB
JSON

{
"$defs": {
"CatalogAuditEntry": {
"additionalProperties": false,
"properties": {
"service": {
"title": "Service",
"type": "string"
},
"seedable": {
"title": "Seedable",
"type": "boolean"
},
"model_deterministic": {
"title": "Model Deterministic",
"type": "boolean"
},
"image_tag_mutable": {
"title": "Image Tag Mutable",
"type": "boolean"
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Notes"
}
},
"required": [
"service",
"seedable",
"model_deterministic",
"image_tag_mutable"
],
"title": "CatalogAuditEntry",
"type": "object"
},
"CatalogField": {
"additionalProperties": false,
"description": "One request parameter, rendered as one form control.",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"type": {
"enum": [
"text",
"textarea",
"number",
"slider",
"select",
"bool",
"file",
"json"
],
"title": "Type",
"type": "string"
},
"label": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Label"
},
"required": {
"default": true,
"title": "Required",
"type": "boolean"
},
"optional": {
"default": false,
"title": "Optional",
"type": "boolean"
},
"togglable": {
"default": false,
"description": "When true, the consumer renders this field with an OFF-by-default enable switch; while off the control is disabled (excluded from submission) AND the field's default is NOT injected, so it is genuinely not sent until the user opts in. Requires optional=true. Used to keep advanced override fields (e.g. inline clone references) from silently overriding primary controls.",
"title": "Togglable",
"type": "boolean"
},
"default": {
"default": null,
"title": "Default"
},
"min": {
"anyOf": [
{
"type": "integer"
},
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Min"
},
"max": {
"anyOf": [
{
"type": "integer"
},
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Max"
},
"step": {
"anyOf": [
{
"type": "integer"
},
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Step"
},
"options": {
"anyOf": [
{
"items": {
"anyOf": [
{
"type": "string"
},
{
"$ref": "#/$defs/FieldOption"
}
]
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Options"
},
"source_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Source Url"
},
"source_jsonpath": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Source Jsonpath"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"max_length": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Max Length"
},
"accepted_types": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"title": "Accepted Types"
},
"section": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Section"
}
},
"required": [
"name",
"type"
],
"title": "CatalogField",
"type": "object"
},
"CatalogLatency": {
"additionalProperties": false,
"properties": {
"cold_start_s": {
"anyOf": [
{
"type": "integer"
},
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"title": "Cold Start S"
},
"warm_per_unit": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Warm Per Unit"
}
},
"title": "CatalogLatency",
"type": "object"
},
"CatalogLifecycle": {
"additionalProperties": false,
"description": "Per-service orchestration metadata.\n\nPresence means \"asset_engine can start/stop this service from the UI\".\nAbsence (default) means \"managed externally \u2014 UI surfaces no controls.\"\nSee docs/contracts/orchestration.contract.md.\n\n - stack: directory name under /opt/docker/compose/ on the\n inference host (compose path derived).\n - vram_gb: VRAM the model needs at typical operating params.\n Used by the memory gate before docker compose up.\n - gpu_device_id: 0 or 1 to pin to a specific card, None if the\n service uses both GPUs / picks dynamically. None\n means the gate must clear on each device.",
"properties": {
"stack": {
"minLength": 1,
"title": "Stack",
"type": "string"
},
"vram_gb": {
"exclusiveMinimum": 0,
"title": "Vram Gb",
"type": "number"
},
"gpu_device_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"title": "Gpu Device Id"
}
},
"required": [
"stack",
"vram_gb"
],
"title": "CatalogLifecycle",
"type": "object"
},
"CatalogModel": {
"additionalProperties": false,
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"revision": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Revision"
},
"image": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Image"
}
},
"required": [
"id"
],
"title": "CatalogModel",
"type": "object"
},
"CatalogReproducibility": {
"additionalProperties": false,
"properties": {
"seedable": {
"title": "Seedable",
"type": "boolean"
},
"deterministic": {
"title": "Deterministic",
"type": "boolean"
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Notes"
},
"seed_field": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Seed Field"
}
},
"required": [
"seedable",
"deterministic"
],
"title": "CatalogReproducibility",
"type": "object"
},
"CatalogResponse": {
"additionalProperties": false,
"description": "How to interpret the inference response.\n\nThe `type` is the *renderer dispatch* \u2014 what kind of asset the user\nultimately sees (audio player, text block, etc). The other fields\ndescribe how to *extract* that asset from the wire shape:\n\n - Raw-bytes wire (e.g. /v1/audio/speech returns raw audio):\n type: audio\n mime: audio/wav (or mime_from_field for dynamic)\n\n - JSON-envelope wire with base64-encoded asset (e.g. captioned\n speech returns {audio: <b64>, audio_format: ..., timestamps: ...}):\n type: audio\n audio_field: audio # JSON key with the base64 bytes\n audio_format_field: audio_format # JSON key with the MIME\n timestamps_field: timestamps # optional structured aside\n\n - JSON-envelope wire with text payload (e.g. ASR transcript):\n type: text\n output_field: text # JSON key with the text body\n timestamps_field: segments # optional, for ASR-with-timestamps\n\nRules:\n - When audio_field is set, the renderer expects a JSON envelope and\n will base64-decode that key. The wire MIME is application/json\n regardless of `mime`; `mime_from_field` is incompatible.\n - timestamps_field is independent of type \u2014 any service that\n emits structured timestamps alongside its primary output can\n declare it; the renderer extends accordingly.",
"properties": {
"type": {
"enum": [
"audio",
"image",
"video",
"text",
"json",
"file"
],
"title": "Type",
"type": "string"
},
"mime": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mime"
},
"mime_from_field": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Mime From Field"
},
"output_field": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Output Field"
},
"audio_field": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Audio Field"
},
"audio_format_field": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Audio Format Field"
},
"timestamps_field": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Timestamps Field"
}
},
"required": [
"type"
],
"title": "CatalogResponse",
"type": "object"
},
"CatalogSectionGroup": {
"additionalProperties": false,
"description": "A named group of fields, for progressive disclosure in form rendering.\n\nServices with many fields (ace-step has 27) declare section_groups and\ntag each field with a `section: <id>`. Consumers render fields grouped\nunder tabs / accordions / etc. Fields without a section go into an\nimplicit 'main' group rendered first.",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"label": {
"title": "Label",
"type": "string"
},
"hint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Hint"
}
},
"required": [
"id",
"label"
],
"title": "CatalogSectionGroup",
"type": "object"
},
"CatalogService": {
"additionalProperties": false,
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"description": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Description"
},
"category": {
"enum": [
"tts",
"asr",
"sfx",
"music",
"image",
"multimodal"
],
"title": "Category",
"type": "string"
},
"version": {
"title": "Version",
"type": "integer"
},
"status": {
"default": "ready",
"enum": [
"ready",
"catalog-deferred",
"experimental",
"down"
],
"title": "Status",
"type": "string"
},
"host": {
"title": "Host",
"type": "string"
},
"endpoint": {
"title": "Endpoint",
"type": "string"
},
"method": {
"default": "POST",
"title": "Method",
"type": "string"
},
"content_type": {
"default": "application/json",
"title": "Content Type",
"type": "string"
},
"model": {
"$ref": "#/$defs/CatalogModel"
},
"fields": {
"items": {
"$ref": "#/$defs/CatalogField"
},
"title": "Fields",
"type": "array"
},
"section_groups": {
"items": {
"$ref": "#/$defs/CatalogSectionGroup"
},
"title": "Section Groups",
"type": "array"
},
"response": {
"$ref": "#/$defs/CatalogResponse"
},
"reproducibility": {
"$ref": "#/$defs/CatalogReproducibility"
},
"estimated_latency": {
"anyOf": [
{
"$ref": "#/$defs/CatalogLatency"
},
{
"type": "null"
}
],
"default": null
},
"lifecycle": {
"anyOf": [
{
"$ref": "#/$defs/CatalogLifecycle"
},
{
"type": "null"
}
],
"default": null
},
"license": {
"title": "License",
"type": "string"
},
"license_warning": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "License Warning"
},
"notes": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Notes"
}
},
"required": [
"id",
"name",
"category",
"version",
"host",
"endpoint",
"model",
"response",
"reproducibility",
"license"
],
"title": "CatalogService",
"type": "object"
},
"FieldOption": {
"additionalProperties": false,
"description": "A select option. May be just the value (when label = value) or {value, label}.",
"properties": {
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
},
{
"type": "number"
},
{
"type": "boolean"
}
],
"title": "Value"
},
"label": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Label"
}
},
"required": [
"value"
],
"title": "FieldOption",
"type": "object"
}
},
"additionalProperties": false,
"description": "Schema for the canonical inference-services catalog. The Python Pydantic model in asset_engine/catalog.py is the source of truth; this JSON Schema is a derived view for non-Python consumers.",
"properties": {
"catalog_version": {
"default": 1,
"title": "Catalog Version",
"type": "integer"
},
"generated_at": {
"anyOf": [
{
"format": "date-time",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Generated At"
},
"services": {
"items": {
"$ref": "#/$defs/CatalogService"
},
"title": "Services",
"type": "array"
},
"reproducibility_audit": {
"items": {
"$ref": "#/$defs/CatalogAuditEntry"
},
"title": "Reproducibility Audit",
"type": "array"
}
},
"required": [
"services"
],
"title": "Asset Engine \u2014 services.yaml catalog",
"type": "object",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://eshpfi.local/schemas/services.schema.json"
}