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.
12 KiB
services.yaml — contract for downstream consumers
services.yaml in this directory is the canonical, versioned
description of the inference services this fleet exposes. It is a
first-class artifact: external tools (the asset-engine UI is the
first; CLIs, monitoring dashboards, and other internal services may
follow) consume it programmatically, and changes to its shape or
contents have downstream effect.
This document is the consumer contract.
Where the data lives
- Canonical source:
docs/asset-engine/services.yamlin theeshpfi-managementrepo (this file's directory). - Schema:
docs/asset-engine/services.schema.json(JSON Schema draft 2020-12). Generated from the Pydantic model in~/development/asset_engine/src/asset_engine/catalog.py— regenerate viacd ~/development/asset_engine && uv run scripts/dump_schema.pywhenever the model changes. - Change history: git log on this directory.
Data model — high level
catalog_version: 1 # bump on SCHEMA changes
services: [Service] # the entries; closed list
reproducibility_audit: [Audit] # one entry per service
Service entry
| field | type | required | meaning |
|---|---|---|---|
id |
string | yes | stable URL-safe identifier; never reused |
name |
string | yes | display name |
description |
string | no | one-paragraph summary |
category |
enum: tts, asr, sfx, music, image | yes | groups in pickers |
version |
int | yes | per-service schema version; bump on field changes |
status |
enum: ready, catalog-deferred, experimental, down | no | default: ready |
Status semantics:
| value | meaning | picker behavior |
|---|---|---|
ready |
Deployed, exercised, current default | clickable, primary surface |
experimental |
Deployed, may break under unusual params or while a custom renderer is being built (e.g. kokoro-captioned pre-renderer) | clickable, surfaced with a "experimental" tag |
catalog-deferred |
By-design out of scope this catalog version (e.g. ComfyUI requires per-asset workflow templates the catalog can't express today) | greyed, non-clickable, "deferred" tooltip |
down |
Temporarily unreachable on the host fleet — deployment gap, scheduled maintenance, container reaped, etc. Distinct from catalog-deferred because the intent is to flip back to ready once redeployed; the catalog row still describes a real intended service. |
greyed, non-clickable, "service down" tooltip |
host |
string (host alias) | yes |
endpoint |
URL | yes |
method |
string | yes |
content_type |
MIME type | yes |
model.id |
string | yes |
model.revision |
string | null | no |
model.image |
string | yes |
fields |
list[Field] | no |
section_groups |
list[SectionGroup] | no |
response.type |
enum: audio, image, video, text, json, file | yes |
response.mime |
string | no |
response.mime_from_field |
string | no |
response.output_field |
string | no |
response.audio_field |
string | no |
response.audio_format_field |
string | no |
response.timestamps_field |
string | no |
reproducibility.seedable |
bool | yes |
reproducibility.deterministic |
bool | yes |
reproducibility.notes |
string | no |
estimated_latency.cold_start_s |
int | float | no |
estimated_latency.warm_per_unit |
string | no |
license |
string | yes |
license_warning |
string | no |
notes |
string | no |
Field entry
The field-type vocabulary is closed. Adding a new type requires:
(a) updating the Pydantic model in asset_engine/catalog.py,
(b) bumping catalog_version, (c) coordinating with all consumers.
type |
meaning |
|---|---|
text |
single-line string |
textarea |
multi-line string |
number |
int or float, no bounded range |
slider |
bounded numeric — requires min, max, optional step |
select |
enum — options: list OR remote source_url: + source_jsonpath: |
bool |
true/false |
file |
file upload — requires accepted_types: list of MIME |
json |
freeform JSON value (arrays of floats, ref objects, etc) |
Optional/required: a field is required UNLESS it has either
required: false or optional: true. (Both ways are accepted; new
entries should prefer required: false.)
Response renderer vocabulary
Closed set: audio, image, video, text, json, file.
Same change-management as field types.
Versioning policy
| Change | Bump |
|---|---|
| Add a new service | nothing |
| Add a non-required field to an existing service | service version: |
Add an optional key to the response: schema (e.g. audio_field, timestamps_field) |
nothing — additive, backward-compatible |
| Add an optional key to a Field (e.g. section) or to Service (e.g. section_groups) | nothing — additive, backward-compatible |
| Extend an existing enum (StatusT, FieldTypeT, ResponseTypeT, CategoryT) with a non-conflicting new value, with the consumer updated in the same coordinated change | nothing — older consumers don't encounter the new value; updated consumers parse it correctly |
| Change a field's type, range, or default | service version: |
| Remove a service | service version: (sentinel: removed=true), then drop in next catalog_version bump |
| Add a new entry to the field-type vocabulary | catalog_version: |
| Add a new entry to the response-type vocabulary | catalog_version: |
| Drop a field-type or response-type | catalog_version: (BREAKING) |
| Pure prose edit (description, notes) | nothing |
Consumers should pin their tested catalog_version. When this repo
bumps it, downstreams should re-test before consuming the new shape.
Sync workflow for downstream consumers
The recommended pattern is vendor + drift-check, not fetch-at-runtime. Reasons: deployments stay reproducible; airgapped or network-flaky deploys still work; schema changes surface in PRs instead of breaking production silently.
A consumer should:
- Vendor a copy of
services.yamlin their repo (e.g. atdata/services.yaml). - Record the source SHA they synced from in a
services.yaml.lockalongside (path, source SHA, sync timestamp). - Provide a
sync_catalogscript that pulls the latest from the canonical path and updates both files. - Validate the vendored file against
services.schema.jsonon load — refuse to start if validation fails. - CI: re-validate on every commit.
The asset-engine repo at ~/development/asset_engine/ is the
reference implementation of this pattern; copy the script shape from
scripts/sync_catalog.py over there.
Known consumers
| Consumer | Vendored at | Pin policy |
|---|---|---|
asset_engine (FastAPI/HTMX UI; ~/development/asset_engine/) |
data/services.yaml + data/services.yaml.lock |
catalog_version=2 |
When you add a consumer, add a row here in the same PR that lands
the consumer. This list is what we audit when planning a
catalog_version bump.
Service authoring notes
When adding a service to the catalog:
- Source-of-truth precedence. OpenAPI is convenient but often lies
by omission (bare
stringtypes where the dispatch chain enforces a specific enum, missing required-only-in-the-handler fields, etc). The actual ground truth is, in order:- The Pydantic input model — for field shape, types, required-ness.
- The handler / pipeline code — for enum dispatches and runtime validation.
- The Gradio UI / client code (when present) — for blessed defaults and slider ranges, since the model authors picked these for human-facing UX.
- The README — least reliable; rots fastest. Every catalog change should cite which of these was read. "OpenAPI said X" is not enough on its own — past audits have caught three bugs in a single service (ace-step) that all looked correct in OpenAPI.
- The reproducibility audit at the bottom of
services.yamlMUST get a matching entry for any new service. - If a service can't produce reproducible output (no seed AND non-deterministic model), that's a bug in the service — fix it there before adding to the catalog. The asset-engine relies on reproducibility to power "regenerate" / "fork" affordances.
- If the service has a non-permissive license, populate
license_warning:with the user-facing language. The UI is required to surface it before a generation; missing it is a contract violation. - Image refs SHOULD be digest-pinned (
@sha256:...) for true reproducibility. Tag-pinned (:v1.2.3) is acceptable but vulnerable to upstream re-pushes.
When removing a service: see the versioning policy table. Removal is a two-step process; surprise removal breaks consumers' asset libraries (regenerate-from-history breaks).