- Apply migration 0008_audio_source_import (additive schema for source import) - Apply migration 0009_translation_import_runs (ledger table for import runs) - Wire PostgresAudioImportStore into HTTP commit path with org from node credential - Commit path uses PG store with org from node, no payload org - Bundle hash 21ce5687… remains frozen until v2; no authority transferred
337 lines
14 KiB
JSON
337 lines
14 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://umbra.invalid/schemas/umbracontrol/audio-bundle/v1/manifest.schema.json",
|
|
"title": "UmbraAudio metadata bundle, v1, as accepted by UmbraTrans",
|
|
"description": "Validates the wrapper the controller builds from a bundle directory: {bundle: <bundle.json>, records: [<manifest.jsonl lines>]}. This is the bundle you already produce, narrowed - field names are unchanged, and nothing is renamed on the wire. Required fields here are a superset of the ones your own validator enforces, because these are the fields the mapping reads.",
|
|
"type": "object",
|
|
"required": ["bundle", "records"],
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"bundle": { "$ref": "#/$defs/envelope" },
|
|
"records": {
|
|
"type": "array",
|
|
"minItems": 1,
|
|
"items": { "$ref": "#/$defs/record" }
|
|
}
|
|
},
|
|
|
|
"$defs": {
|
|
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
"iso8601": { "type": "string", "format": "date-time" },
|
|
"relativeTo": { "const": "book_directory" },
|
|
|
|
"envelope": {
|
|
"title": "bundle.json",
|
|
"type": "object",
|
|
"required": [
|
|
"bundle_format_version",
|
|
"content_hash",
|
|
"generated_at",
|
|
"source",
|
|
"counts",
|
|
"record_kinds",
|
|
"scope"
|
|
],
|
|
"properties": {
|
|
"bundle_format_version": { "const": 1 },
|
|
"content_hash": {
|
|
"type": "object",
|
|
"required": ["algorithm", "of", "value"],
|
|
"properties": {
|
|
"algorithm": { "const": "sha256" },
|
|
"of": { "type": "string" },
|
|
"value": { "$ref": "#/$defs/sha256" }
|
|
}
|
|
},
|
|
"generated_at": { "$ref": "#/$defs/iso8601" },
|
|
"source": {
|
|
"type": "object",
|
|
"required": ["system", "node_id"],
|
|
"properties": {
|
|
"system": { "type": "string", "minLength": 1 },
|
|
"node_id": { "type": "string", "minLength": 1 },
|
|
"product_version": { "type": "string" },
|
|
"database_schema_fingerprint": { "type": "string" }
|
|
}
|
|
},
|
|
"counts": {
|
|
"type": "object",
|
|
"required": ["books", "chapters", "utterances", "speakers", "render_records", "records_total"],
|
|
"properties": {
|
|
"books": { "type": "integer", "minimum": 1 },
|
|
"chapters": { "type": "integer", "minimum": 0 },
|
|
"utterances": { "type": "integer", "minimum": 0 },
|
|
"speakers": { "type": "integer", "minimum": 0 },
|
|
"render_records": { "type": "integer", "minimum": 0 },
|
|
"records_total": { "type": "integer", "minimum": 1 }
|
|
}
|
|
},
|
|
"record_kinds": {
|
|
"type": "object",
|
|
"required": ["discriminator", "values"],
|
|
"properties": {
|
|
"discriminator": { "const": "kind" },
|
|
"values": {
|
|
"type": "array",
|
|
"allOf": [
|
|
{ "contains": { "const": "book" } },
|
|
{ "contains": { "const": "chapter" } },
|
|
{ "contains": { "const": "speaker" } },
|
|
{ "contains": { "const": "utterance" } },
|
|
{ "contains": { "const": "render" } }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"scope": {
|
|
"type": "object",
|
|
"required": ["books_full_detail"],
|
|
"properties": {
|
|
"books_full_detail": { "type": "array", "minItems": 1, "items": { "type": "string" } },
|
|
"books_summary_only": { "type": "array", "items": { "type": "string" } },
|
|
"note": { "type": "string" }
|
|
}
|
|
},
|
|
"required_fields": { "type": "object" }
|
|
}
|
|
},
|
|
|
|
"record": {
|
|
"title": "one manifest.jsonl record",
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/book" },
|
|
{ "$ref": "#/$defs/chapter" },
|
|
{ "$ref": "#/$defs/speaker" },
|
|
{ "$ref": "#/$defs/utterance" },
|
|
{ "$ref": "#/$defs/render" }
|
|
]
|
|
},
|
|
|
|
"book": {
|
|
"type": "object",
|
|
"required": [
|
|
"id", "kind", "generated_at", "name", "chapters",
|
|
"chapter_count", "utterance_count", "language_designations"
|
|
],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"kind": { "const": "book" },
|
|
"generated_at": { "$ref": "#/$defs/iso8601" },
|
|
"name": { "type": "string", "minLength": 1 },
|
|
"title": { "type": ["string", "null"] },
|
|
"status": { "type": ["string", "null"] },
|
|
"chapters": { "type": "array", "minItems": 1, "items": { "type": "string" } },
|
|
"chapter_count": { "type": "integer", "minimum": 0 },
|
|
"utterance_count": { "type": "integer", "minimum": 0 },
|
|
"speaker_count": { "type": "integer", "minimum": 0 },
|
|
"novel_chapter_count": { "type": "integer" },
|
|
"front_and_back_matter_count": { "type": "integer" },
|
|
"published_artifact_ids": { "type": "array", "items": { "type": "string" } },
|
|
"language_designations": {
|
|
"type": "array",
|
|
"items": { "type": "string" },
|
|
"description": "Empty is an accepted answer, not an error. Target languages are decided on the translation side."
|
|
},
|
|
"language_designation_note": { "type": "string" },
|
|
"extension_container_mismatch_count": { "type": "integer", "minimum": 0 },
|
|
"extension_container_mismatches": { "type": "array" },
|
|
"speaker_names_without_a_profile": { "type": "array" },
|
|
"declared_total_chapters": { "type": ["integer", "null"] },
|
|
"declared_total_words": { "type": ["integer", "null"] },
|
|
"declared_vs_measured_note": { "type": ["string", "null"] },
|
|
"numbering": { "type": "object" },
|
|
"path_relative_to": { "type": ["string", "null"] },
|
|
"source_document_format": { "type": ["string", "null"] },
|
|
"source_path_redacted": { "type": ["string", "null"] },
|
|
"created_at": { "type": ["string", "null"] },
|
|
"updated_at": { "type": ["string", "null"] }
|
|
}
|
|
},
|
|
|
|
"chapter": {
|
|
"type": "object",
|
|
"required": [
|
|
"id", "kind", "generated_at", "book_id", "chapter_index",
|
|
"database_row_id", "title", "source_text_sha256", "renders",
|
|
"numbering_basis", "utterance_count"
|
|
],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"kind": { "const": "chapter" },
|
|
"generated_at": { "$ref": "#/$defs/iso8601" },
|
|
"book_id": { "type": "string", "minLength": 1 },
|
|
"chapter_index": {
|
|
"type": "integer",
|
|
"description": "Reading order. Negative is front matter. NEVER identity."
|
|
},
|
|
"database_row_id": { "type": "integer", "description": "Source row id. Provenance only." },
|
|
"title": { "type": ["string", "null"] },
|
|
"source_text_sha256": { "$ref": "#/$defs/sha256" },
|
|
"source_text_char_count": { "type": "integer", "minimum": 0 },
|
|
"word_count": { "type": "integer", "minimum": 0 },
|
|
"utterance_count": { "type": "integer", "minimum": 0 },
|
|
"renders": { "type": "array", "items": { "type": "string" } },
|
|
"numbering_basis": {
|
|
"type": "object",
|
|
"description": "Required so a derived order can never be mistaken for identity."
|
|
},
|
|
"status": { "type": ["string", "null"] }
|
|
}
|
|
},
|
|
|
|
"speaker": {
|
|
"type": "object",
|
|
"required": [
|
|
"id", "kind", "generated_at", "primary_name", "has_profile",
|
|
"is_narrator", "aliases", "utterance_count", "utterance_count_casefold_grouped"
|
|
],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"kind": { "const": "speaker" },
|
|
"generated_at": { "$ref": "#/$defs/iso8601" },
|
|
"primary_name": { "type": "string", "minLength": 1 },
|
|
"aliases": { "type": "array", "items": { "type": "string" } },
|
|
"has_profile": { "type": "boolean" },
|
|
"is_narrator": { "type": "boolean" },
|
|
"is_group": { "type": "boolean" },
|
|
"unidentified": { "type": "boolean" },
|
|
"utterance_count": { "type": "integer", "minimum": 0 },
|
|
"utterance_count_casefold_grouped": { "type": "integer", "minimum": 0 },
|
|
"utterance_count_including_implicit_narrator": { "type": "integer", "minimum": 0 },
|
|
"casefold_collision_group": { "type": ["string", "null"] },
|
|
"chapters_appeared": { "type": "array" },
|
|
"name_is_distinct_from_spellings": { "type": "array", "items": { "type": "string" } },
|
|
"voice_review_status": { "type": ["string", "null"] },
|
|
"voice_review_recorded": { "type": ["boolean", "null"] },
|
|
"tts_engine_used": { "type": ["string", "null"] },
|
|
"tts_engine_decided_at": { "type": ["string", "null"] },
|
|
"tts_engine_fallback_reason": { "type": ["string", "null"] },
|
|
"assigned_edge_voice": { "type": ["string", "null"] }
|
|
}
|
|
},
|
|
|
|
"utterance": {
|
|
"type": "object",
|
|
"required": [
|
|
"id", "kind", "generated_at", "book_id", "chapter_id",
|
|
"utterance_index", "text", "text_sha256", "utterance_type",
|
|
"speaker_id", "speaker_name_raw", "speaker_name_resolved",
|
|
"speaker_resolution", "render_id", "needs_review"
|
|
],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"kind": { "const": "utterance" },
|
|
"generated_at": { "$ref": "#/$defs/iso8601" },
|
|
"book_id": { "type": "string", "minLength": 1 },
|
|
"chapter_id": { "type": "string", "minLength": 1 },
|
|
"database_row_id": { "type": "integer" },
|
|
"utterance_index": { "type": "integer", "minimum": 0 },
|
|
"utterance_type": { "enum": ["narration", "dialogue", "scene_break"] },
|
|
"text": { "type": "string" },
|
|
"text_sha256": { "$ref": "#/$defs/sha256" },
|
|
"text_char_count": { "type": "integer", "minimum": 0 },
|
|
"speaker_id": { "type": "string", "minLength": 1 },
|
|
"speaker_name_raw": {
|
|
"type": ["string", "null"],
|
|
"description": "Exactly what is stored. null means narrator, not missing."
|
|
},
|
|
"speaker_name_resolved": { "type": "string", "minLength": 1 },
|
|
"speaker_resolution": { "enum": ["explicit", "implicit_narrator"] },
|
|
"render_id": { "type": ["string", "null"] },
|
|
"attribution_confidence": { "type": ["number", "null"] },
|
|
"attribution_uncertain": { "type": ["boolean", "null"] },
|
|
"attribution_evidence_kind": { "type": ["string", "null"] },
|
|
"needs_review": { "type": "boolean" },
|
|
"review_reason": { "type": ["string", "null"] },
|
|
"emotion": { "type": ["string", "null"] },
|
|
"pace": { "type": ["string", "null"] },
|
|
"instruct_is_set": { "type": ["boolean", "null"] },
|
|
"status": { "type": ["string", "null"] }
|
|
},
|
|
"allOf": [
|
|
{
|
|
"if": {
|
|
"properties": { "speaker_resolution": { "const": "implicit_narrator" } },
|
|
"required": ["speaker_resolution"]
|
|
},
|
|
"then": {
|
|
"properties": { "speaker_name_raw": { "type": "null" } }
|
|
}
|
|
},
|
|
{
|
|
"if": {
|
|
"properties": { "speaker_resolution": { "const": "explicit" } },
|
|
"required": ["speaker_resolution"]
|
|
},
|
|
"then": {
|
|
"properties": { "speaker_name_raw": { "type": "string", "minLength": 1 } }
|
|
}
|
|
}
|
|
]
|
|
},
|
|
|
|
"render": {
|
|
"type": "object",
|
|
"required": [
|
|
"id", "kind", "generated_at", "render_role", "path", "relative_to",
|
|
"path_is_absolute", "audio_format", "duration_ms", "byte_size",
|
|
"content_sha256", "book_id", "inputs"
|
|
],
|
|
"properties": {
|
|
"id": { "type": "string", "minLength": 1 },
|
|
"kind": { "const": "render" },
|
|
"generated_at": { "$ref": "#/$defs/iso8601" },
|
|
"render_role": {
|
|
"enum": [
|
|
"utterance_audio", "chapter_audio", "distribution_track",
|
|
"assembled_book", "voice_reference"
|
|
]
|
|
},
|
|
"render_stage": { "type": ["string", "null"] },
|
|
"book_id": { "type": ["string", "null"] },
|
|
"chapter_id": {
|
|
"type": ["string", "null"],
|
|
"description": "Absent for project-level renders (voice_reference, assembled_book). Absent and null are both 'no chapter'; the ingest treats them the same."
|
|
},
|
|
"path": { "type": "string", "minLength": 1 },
|
|
"relative_to": { "$ref": "#/$defs/relativeTo" },
|
|
"path_is_absolute": { "type": "boolean" },
|
|
"audio_format": {
|
|
"type": ["object", "null"],
|
|
"required": ["container", "codec", "extension_matches_container", "detected_by"],
|
|
"properties": {
|
|
"container": { "type": "string", "minLength": 1 },
|
|
"codec": { "type": ["string", "null"] },
|
|
"sample_rate_hz": { "type": ["integer", "null"] },
|
|
"channel_count": { "type": ["integer", "null"] },
|
|
"bit_rate_bps": { "type": ["integer", "null"] },
|
|
"file_extension": { "type": ["string", "null"] },
|
|
"extension_matches_container": { "type": ["boolean", "null"] },
|
|
"detected_by": { "enum": ["wave-module", "ffprobe", "none"] }
|
|
}
|
|
},
|
|
"duration_ms": {
|
|
"type": ["integer", "null"],
|
|
"minimum": 0,
|
|
"description": "Unit in the name. null is unknown, which is not zero."
|
|
},
|
|
"byte_size": { "type": ["integer", "null"], "minimum": 0 },
|
|
"content_sha256": { "$ref": "#/$defs/sha256" },
|
|
"inputs": { "type": "array", "items": { "type": "string" } },
|
|
"file_present": { "type": ["boolean", "null"] },
|
|
"file_modified_at": { "type": ["string", "null"] },
|
|
"stale": { "type": ["boolean", "null"] },
|
|
"voice_id": { "type": ["string", "null"] },
|
|
"track_number_in_name": {
|
|
"type": ["string", "integer", "null"],
|
|
"description": "A string here, e.g. \"01\". Never the reading order."
|
|
},
|
|
"title_in_name": { "type": ["string", "null"] },
|
|
"embedded_metadata_language": { "type": ["string", "null"] },
|
|
"embedded_metadata_narrator": { "type": ["string", "null"] },
|
|
"embedded_metadata_title": { "type": ["string", "null"] }
|
|
}
|
|
}
|
|
}
|
|
}
|