utterplan 0.6.4__tar.gz → 0.6.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. {utterplan-0.6.4 → utterplan-0.6.5}/PKG-INFO +63 -3
  2. {utterplan-0.6.4 → utterplan-0.6.5}/README.md +62 -2
  3. {utterplan-0.6.4 → utterplan-0.6.5}/docs/architecture.md +25 -14
  4. {utterplan-0.6.4 → utterplan-0.6.5}/docs/changelog.md +17 -0
  5. {utterplan-0.6.4 → utterplan-0.6.5}/docs/cli.md +30 -3
  6. {utterplan-0.6.4 → utterplan-0.6.5}/docs/python-api.md +23 -0
  7. {utterplan-0.6.4 → utterplan-0.6.5}/docs/ssmdbook.md +2 -0
  8. {utterplan-0.6.4 → utterplan-0.6.5}/docs/ssmdoc.md +20 -4
  9. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_book_api.py +26 -1
  10. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_cli_render_book.py +60 -1
  11. utterplan-0.6.5/tests/test_cli_render_document.py +354 -0
  12. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_cli_ssmdoc.py +36 -17
  13. utterplan-0.6.5/tests/test_document_render.py +119 -0
  14. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_document_workspace.py +19 -12
  15. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_import_boundaries.py +1 -1
  16. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderplan_export.py +133 -0
  17. utterplan-0.6.5/tests/test_role_coverage.py +126 -0
  18. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_ssmdoc.py +53 -6
  19. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/__init__.py +22 -0
  20. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/_version.py +3 -3
  21. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/book.py +34 -28
  22. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/cli.py +105 -25
  23. utterplan-0.6.5/utterplan/document_workspace.py +460 -0
  24. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/renderplan_export.py +3 -2
  25. utterplan-0.6.5/utterplan/renderplan_workspace.py +23 -0
  26. utterplan-0.6.5/utterplan/role_coverage.py +99 -0
  27. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/source_input.py +2 -0
  28. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/ssmdoc.py +59 -38
  29. utterplan-0.6.5/utterplan/workspace_paths.py +27 -0
  30. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/PKG-INFO +63 -3
  31. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/SOURCES.txt +6 -0
  32. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/scm_file_list.json +22 -1
  33. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/scm_version.json +2 -2
  34. utterplan-0.6.4/utterplan/document_workspace.py +0 -194
  35. {utterplan-0.6.4 → utterplan-0.6.5}/.codecrate.toml +0 -0
  36. {utterplan-0.6.4 → utterplan-0.6.5}/.github/workflows/codecov.yml +0 -0
  37. {utterplan-0.6.4 → utterplan-0.6.5}/.github/workflows/pre-commit.yml +0 -0
  38. {utterplan-0.6.4 → utterplan-0.6.5}/.github/workflows/python-publish.yml +0 -0
  39. {utterplan-0.6.4 → utterplan-0.6.5}/.github/workflows/tests.yml +0 -0
  40. {utterplan-0.6.4 → utterplan-0.6.5}/.gitignore +0 -0
  41. {utterplan-0.6.4 → utterplan-0.6.5}/.pre-commit-config.yaml +0 -0
  42. {utterplan-0.6.4 → utterplan-0.6.5}/AGENTS.md +0 -0
  43. {utterplan-0.6.4 → utterplan-0.6.5}/LICENSE +0 -0
  44. {utterplan-0.6.4 → utterplan-0.6.5}/MANIFEST.in +0 -0
  45. {utterplan-0.6.4 → utterplan-0.6.5}/NOTICE +0 -0
  46. {utterplan-0.6.4 → utterplan-0.6.5}/docs/Makefile +0 -0
  47. {utterplan-0.6.4 → utterplan-0.6.5}/docs/conf.py +0 -0
  48. {utterplan-0.6.4 → utterplan-0.6.5}/docs/consumer-guide.md +0 -0
  49. {utterplan-0.6.4 → utterplan-0.6.5}/docs/coordinate-spaces.md +0 -0
  50. {utterplan-0.6.4 → utterplan-0.6.5}/docs/debugging.md +0 -0
  51. {utterplan-0.6.4 → utterplan-0.6.5}/docs/format.md +0 -0
  52. {utterplan-0.6.4 → utterplan-0.6.5}/docs/getting-started.md +0 -0
  53. {utterplan-0.6.4 → utterplan-0.6.5}/docs/index.md +0 -0
  54. {utterplan-0.6.4 → utterplan-0.6.5}/docs/make.bat +0 -0
  55. {utterplan-0.6.4 → utterplan-0.6.5}/docs/phase-a-completion.md +0 -0
  56. {utterplan-0.6.4 → utterplan-0.6.5}/docs/pykokoro-integration.md +0 -0
  57. {utterplan-0.6.4 → utterplan-0.6.5}/docs/requirements.txt +0 -0
  58. {utterplan-0.6.4 → utterplan-0.6.5}/examples/basic.py +0 -0
  59. {utterplan-0.6.4 → utterplan-0.6.5}/examples/chapter.ssmd +0 -0
  60. {utterplan-0.6.4 → utterplan-0.6.5}/examples/compile_file.py +0 -0
  61. {utterplan-0.6.4 → utterplan-0.6.5}/examples/inspect_plan.py +0 -0
  62. {utterplan-0.6.4 → utterplan-0.6.5}/examples/multilingual.py +0 -0
  63. {utterplan-0.6.4 → utterplan-0.6.5}/examples/ssmd_example.py +0 -0
  64. {utterplan-0.6.4 → utterplan-0.6.5}/pyproject.toml +0 -0
  65. {utterplan-0.6.4 → utterplan-0.6.5}/setup.cfg +0 -0
  66. {utterplan-0.6.4 → utterplan-0.6.5}/spec/schemas/v1.schema.json +0 -0
  67. {utterplan-0.6.4 → utterplan-0.6.5}/spec/schemas/v2.schema.json +0 -0
  68. {utterplan-0.6.4 → utterplan-0.6.5}/spec/schemas/v3.schema.json +0 -0
  69. {utterplan-0.6.4 → utterplan-0.6.5}/spec/schemas/v4.schema.json +0 -0
  70. {utterplan-0.6.4 → utterplan-0.6.5}/spec/utterplan.schema.json +0 -0
  71. {utterplan-0.6.4 → utterplan-0.6.5}/tests/__init__.py +0 -0
  72. {utterplan-0.6.4 → utterplan-0.6.5}/tests/compiler_helpers.py +0 -0
  73. {utterplan-0.6.4 → utterplan-0.6.5}/tests/fixtures/canonical_consumer_contract.expected.toml +0 -0
  74. {utterplan-0.6.4 → utterplan-0.6.5}/tests/fixtures/canonical_consumer_contract.ssmd +0 -0
  75. {utterplan-0.6.4 → utterplan-0.6.5}/tests/fixtures/ssmd_09_comprehensive.ssmd +0 -0
  76. {utterplan-0.6.4 → utterplan-0.6.5}/tests/fixtures/ssmdoc-v1/story.ssmdoc/document.ssmd.md +0 -0
  77. {utterplan-0.6.4 → utterplan-0.6.5}/tests/fixtures/ssmdoc-v1/story.ssmdoc/manifest.json +0 -0
  78. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/basic_en.utterplan.toml +0 -0
  79. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/directives.utterplan.toml +0 -0
  80. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/markers.utterplan.toml +0 -0
  81. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/multilingual.utterplan.toml +0 -0
  82. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/parenthetical.utterplan.toml +0 -0
  83. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/spokenform_offsets.utterplan.toml +0 -0
  84. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/ssmd_09_comprehensive.utterplan.toml +0 -0
  85. {utterplan-0.6.4 → utterplan-0.6.5}/tests/golden/ssmd_breaks.utterplan.toml +0 -0
  86. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/__init__.py +0 -0
  87. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/cases.py +0 -0
  88. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/basic_en.utterplan.json +0 -0
  89. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/directives.utterplan.json +0 -0
  90. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/markers.utterplan.json +0 -0
  91. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/multilingual.utterplan.json +0 -0
  92. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/parenthetical.utterplan.json +0 -0
  93. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/spokenform_offsets.utterplan.json +0 -0
  94. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/ssmd_09_comprehensive.utterplan.json +0 -0
  95. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/fixtures/v4/ssmd_breaks.utterplan.json +0 -0
  96. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/goldens/multilingual_preparation.json +0 -0
  97. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/goldens/spokenform_numbers.json +0 -0
  98. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/goldens/ssmd_break.json +0 -0
  99. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/goldens/ssmd_language_detection.json +0 -0
  100. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/goldens/ssmd_marker.json +0 -0
  101. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/goldens/ssmd_pronunciation.json +0 -0
  102. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/test_legacy_json_import.py +0 -0
  103. {utterplan-0.6.4 → utterplan-0.6.5}/tests/migration/test_semantic_migration_regressions.py +0 -0
  104. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/README.md +0 -0
  105. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/basic.json +0 -0
  106. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/directives.json +0 -0
  107. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/markers.json +0 -0
  108. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/multilingual.json +0 -0
  109. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/parenthetical.json +0 -0
  110. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/spokenform_offsets.json +0 -0
  111. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v1/ssmd_breaks.json +0 -0
  112. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v2/directives.json +0 -0
  113. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v3/multilingual.json +0 -0
  114. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v3/ssmd_09_comprehensive.json +0 -0
  115. {utterplan-0.6.4 → utterplan-0.6.5}/tests/schema_history/v5/canonical_consumer_contract.toml +0 -0
  116. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_atomic_io.py +0 -0
  117. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_batch.py +0 -0
  118. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_book_replan.py +0 -0
  119. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_boundaries.py +0 -0
  120. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_cli.py +0 -0
  121. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_cli_batch.py +0 -0
  122. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_cli_replan_book.py +0 -0
  123. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_cli_ssmdbook.py +0 -0
  124. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_compiler.py +0 -0
  125. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_consumer_contract.py +0 -0
  126. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_consumer_readiness.py +0 -0
  127. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_coordinates.py +0 -0
  128. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_dependency_architecture.py +0 -0
  129. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_directives.py +0 -0
  130. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_explain.py +0 -0
  131. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_fake_renderer.py +0 -0
  132. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_flow_compilation.py +0 -0
  133. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_format.py +0 -0
  134. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_golden.py +0 -0
  135. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_identity_units.py +0 -0
  136. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_language.py +0 -0
  137. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_linguistics.py +0 -0
  138. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_migration_framework.py +0 -0
  139. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_migration_v3_to_v4.py +0 -0
  140. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_migration_v4_to_v5.py +0 -0
  141. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_pauses.py +0 -0
  142. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_planner.py +0 -0
  143. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_preparation.py +0 -0
  144. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_preview.py +0 -0
  145. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_progress.py +0 -0
  146. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_public_identity.py +0 -0
  147. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderability.py +0 -0
  148. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderability_repair.py +0 -0
  149. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderplan_batch.py +0 -0
  150. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderplan_dependency_contract.py +0 -0
  151. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderplan_replan.py +0 -0
  152. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_renderplan_v7_contract.py +0 -0
  153. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_replacement_provenance.py +0 -0
  154. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_schema_history.py +0 -0
  155. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_schema_registry.py +0 -0
  156. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_schema_strict.py +0 -0
  157. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_schema_v6_source.py +0 -0
  158. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_schema_v7.py +0 -0
  159. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_segment_topology.py +0 -0
  160. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_segmentation_adapter.py +0 -0
  161. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_segmentation_recovery.py +0 -0
  162. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_semantic_boundaries.py +0 -0
  163. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_sentence_topology.py +0 -0
  164. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_source_input.py +0 -0
  165. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_ssmd_09_parser.py +0 -0
  166. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_ssmd_substitution_preparation.py +0 -0
  167. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_ssmdbook.py +0 -0
  168. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_token_topology.py +0 -0
  169. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_toml_format.py +0 -0
  170. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_ttsready_preparation_adapter.py +0 -0
  171. {utterplan-0.6.4 → utterplan-0.6.5}/tests/test_version.py +0 -0
  172. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/__main__.py +0 -0
  173. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/atomic_io.py +0 -0
  174. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/attempt_serialization.py +0 -0
  175. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/attempts.py +0 -0
  176. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/batch.py +0 -0
  177. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/book_replan.py +0 -0
  178. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/boundaries.py +0 -0
  179. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/codecs/__init__.py +0 -0
  180. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/codecs/v4_toml.py +0 -0
  181. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/codecs/v5_toml.py +0 -0
  182. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/codecs/v6_toml.py +0 -0
  183. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/codecs/v7_toml.py +0 -0
  184. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/compiler.py +0 -0
  185. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/config.py +0 -0
  186. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/diagnostics.py +0 -0
  187. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/directives.py +0 -0
  188. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/exceptions.py +0 -0
  189. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/explain.py +0 -0
  190. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/flow_projection.py +0 -0
  191. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/format.py +0 -0
  192. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/hashing.py +0 -0
  193. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/language.py +0 -0
  194. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/linguistics.py +0 -0
  195. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migration.py +0 -0
  196. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/__init__.py +0 -0
  197. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/registry.py +0 -0
  198. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/v1_to_v2.py +0 -0
  199. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/v2_to_v3.py +0 -0
  200. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/v3_to_v4.py +0 -0
  201. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/v4_to_v5.py +0 -0
  202. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/v5_to_v6.py +0 -0
  203. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/migrations/v6_to_v7.py +0 -0
  204. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/model.py +0 -0
  205. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/parsers.py +0 -0
  206. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/pauses.py +0 -0
  207. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/planner.py +0 -0
  208. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/preparation.py +0 -0
  209. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/preview.py +0 -0
  210. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/progress.py +0 -0
  211. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/py.typed +0 -0
  212. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/renderability.py +0 -0
  213. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/renderplan_batch.py +0 -0
  214. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/renderplan_grouping.py +0 -0
  215. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/renderplan_projection.py +0 -0
  216. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/renderplan_replan.py +0 -0
  217. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/schema_registry.py +0 -0
  218. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/schemas/v1.schema.json +0 -0
  219. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/schemas/v2.schema.json +0 -0
  220. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/schemas/v3.schema.json +0 -0
  221. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/schemas/v4.schema.json +0 -0
  222. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/segmentation.py +0 -0
  223. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/serialization.py +0 -0
  224. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/spacy_models.py +0 -0
  225. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/spans.py +0 -0
  226. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/ssmdbook.py +0 -0
  227. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/__init__.py +0 -0
  228. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/document/__init__.py +0 -0
  229. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/document/plain.py +0 -0
  230. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/document/ssmd.py +0 -0
  231. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/preparation/__init__.py +0 -0
  232. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/preparation/identity.py +0 -0
  233. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/preparation/ssmd.py +0 -0
  234. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/preparation/ttsready.py +0 -0
  235. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/stages/protocols.py +0 -0
  236. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/token_topology.py +0 -0
  237. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/toml_codec.py +0 -0
  238. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/trace_codec.py +0 -0
  239. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/units.py +0 -0
  240. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/utterplan.schema.json +0 -0
  241. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan/versioning.py +0 -0
  242. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/dependency_links.txt +0 -0
  243. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/entry_points.txt +0 -0
  244. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/requires.txt +0 -0
  245. {utterplan-0.6.4 → utterplan-0.6.5}/utterplan.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utterplan
3
- Version: 0.6.4
3
+ Version: 0.6.5
4
4
  Summary: Engine-independent text-to-speech planning compiler and interchange format
5
5
  Author-email: Holger Nahrstaedt <nahrstaedt@gmail.com>
6
6
  License: Apache License
@@ -253,7 +253,7 @@ UtterPlan is the canonical, engine-independent semantic compiler from one SSMD
253
253
  document (or explicitly selected plain-text document) to an executable speech
254
254
  plan. It produces deterministic prepared text, paragraph-local flow units, safe boundary opportunities, authored pause intents, and segment-local
255
255
  semantics, portable document metadata, and typed directives for
256
- independent downstream consumers such as Readio, VoiceRender, and other FlowPlan users. Its book commands read the SSMDBook manifest only for chapter discovery, never modify the manifest or source chapters, and write derived plans to separate directories. It uses TTSReady for written-to-spoken preparation and stops before G2P, synthesis, and audio.
256
+ independent downstream FlowPlan consumers. Its book commands read the SSMDBook manifest only for chapter discovery, never modify the manifest or source chapters, and write derived plans to separate directories. It uses TTSReady for written-to-spoken preparation and stops before G2P, synthesis, and audio.
257
257
 
258
258
  ## CLI
259
259
 
@@ -293,7 +293,15 @@ Compile one editable `.ssmdoc` directory as one SSMD document and one FlowPlan:
293
293
  utterplan compile-document story.ssmdoc
294
294
  ```
295
295
 
296
- By default this writes `story.ssmdoc/.readio/utterplan/document-0001.utterplan.toml` and a `compile-report.toml`. The current `document.ssmd.md` is authoritative even if its manifest digest is stale; UtterPlan leaves the source and manifest untouched. This is separate from the multi-chapter SSMDBook workflow below. See the [SSMDDoc workspace guide](docs/ssmdoc.md) for manifest safety, output options, and the Python API.
296
+ By default this writes `story.ssmdoc/utterplan/document.utterplan.toml` and `story.ssmdoc/utterplan/compile-report.toml`. The current `document.ssmd.md` is authoritative even if its manifest digest is stale; UtterPlan leaves the source and manifest untouched. This is separate from the multi-chapter SSMDBook workflow below. See the [SSMDDoc workspace guide](docs/ssmdoc.md) for manifest safety, output options, and the Python API.
297
+
298
+ Project the saved plan with an explicit RenderProfile:
299
+
300
+ ```bash
301
+ utterplan render-document story.ssmdoc --profile narration.renderprofile.toml
302
+ ```
303
+
304
+ The profile-specific RenderPlan is written to a matching variant directly under `story.ssmdoc/renderplan/`.
297
305
 
298
306
  Compile a selected chapter range directly from an editable SSMDBook workspace:
299
307
 
@@ -316,6 +324,58 @@ utterplan/
316
324
 
317
325
  Omit `--chapters` to compile all manifest chapters. Use comma-separated source-number selections for non-contiguous ranges, for example `utterplan compile-book chapters --chapters 1,3-5`. `--diagnostics warnings|all|none` defaults to warnings; failures are always shown and reports retain complete diagnostics. The book root itself is also accepted as input; see the [SSMDBook guide](docs/ssmdbook.md).
318
326
 
327
+ ## Logical roles and complete RenderProfiles
328
+
329
+ An SSMD `voice` reference is a **logical role**: a renderer-independent name such as `narrator`, `host`, or `guest`. A **role binding** is the concrete renderer configuration for that name in a RenderProfile. One RenderProfile is the complete cast for one render variant, together with shared planning and pause policy; it may bind different roles to different engines. UtterPlan accepts one `--profile` per projection, not one profile per role, and does not discover engines or voices.
330
+
331
+ A profile can define a default for speech without an explicit role reference and explicit bindings for other roles:
332
+
333
+ In SSMD, the source remains renderer-independent; for example:
334
+
335
+ ```markdown
336
+ :::{voice="host"}
337
+ Host line.
338
+ :::
339
+ ```
340
+
341
+ ```toml
342
+ default_role = "narrator"
343
+
344
+ [roles.narrator]
345
+ language = "en-US"
346
+
347
+ [roles.narrator.binding]
348
+ renderer = "kokoro"
349
+ target_id = "v1.0"
350
+ voice = "af_heart"
351
+
352
+ [roles.host]
353
+ language = "en-US"
354
+
355
+ [roles.host.binding]
356
+ renderer = "kitten"
357
+ target_id = "mini-0.8"
358
+ voice = "Jasper"
359
+
360
+ [roles.guest]
361
+ language = "en-US"
362
+
363
+ [roles.guest.binding]
364
+ renderer = "piper"
365
+ target_id = "en_US-amy-medium"
366
+ voice = "en_US-amy-medium"
367
+ ```
368
+
369
+ Inspect symbolic role usage without a profile, then project a document or selected book chapters with the same complete cast:
370
+
371
+ ```bash
372
+ utterplan inspect story.utterplan.toml --roles
373
+ utterplan render-document story.ssmdoc --profile printer-cast.renderprofile.toml
374
+ utterplan render-book novel.ssmdbook --profile novel-cast.renderprofile.toml
375
+ ```
376
+
377
+ Projection reports required and available roles and rejects incomplete coverage before writing the RenderPlan variant. The profile and its concrete role bindings remain separate from the source SSMD.
378
+
319
379
  ## From an SSMDBook to RenderPlans
320
380
 
321
381
  UtterPlan keeps one canonical semantic plan per chapter. A RenderProfile is a
@@ -9,7 +9,7 @@ UtterPlan is the canonical, engine-independent semantic compiler from one SSMD
9
9
  document (or explicitly selected plain-text document) to an executable speech
10
10
  plan. It produces deterministic prepared text, paragraph-local flow units, safe boundary opportunities, authored pause intents, and segment-local
11
11
  semantics, portable document metadata, and typed directives for
12
- independent downstream consumers such as Readio, VoiceRender, and other FlowPlan users. Its book commands read the SSMDBook manifest only for chapter discovery, never modify the manifest or source chapters, and write derived plans to separate directories. It uses TTSReady for written-to-spoken preparation and stops before G2P, synthesis, and audio.
12
+ independent downstream FlowPlan consumers. Its book commands read the SSMDBook manifest only for chapter discovery, never modify the manifest or source chapters, and write derived plans to separate directories. It uses TTSReady for written-to-spoken preparation and stops before G2P, synthesis, and audio.
13
13
 
14
14
  ## CLI
15
15
 
@@ -49,7 +49,15 @@ Compile one editable `.ssmdoc` directory as one SSMD document and one FlowPlan:
49
49
  utterplan compile-document story.ssmdoc
50
50
  ```
51
51
 
52
- By default this writes `story.ssmdoc/.readio/utterplan/document-0001.utterplan.toml` and a `compile-report.toml`. The current `document.ssmd.md` is authoritative even if its manifest digest is stale; UtterPlan leaves the source and manifest untouched. This is separate from the multi-chapter SSMDBook workflow below. See the [SSMDDoc workspace guide](docs/ssmdoc.md) for manifest safety, output options, and the Python API.
52
+ By default this writes `story.ssmdoc/utterplan/document.utterplan.toml` and `story.ssmdoc/utterplan/compile-report.toml`. The current `document.ssmd.md` is authoritative even if its manifest digest is stale; UtterPlan leaves the source and manifest untouched. This is separate from the multi-chapter SSMDBook workflow below. See the [SSMDDoc workspace guide](docs/ssmdoc.md) for manifest safety, output options, and the Python API.
53
+
54
+ Project the saved plan with an explicit RenderProfile:
55
+
56
+ ```bash
57
+ utterplan render-document story.ssmdoc --profile narration.renderprofile.toml
58
+ ```
59
+
60
+ The profile-specific RenderPlan is written to a matching variant directly under `story.ssmdoc/renderplan/`.
53
61
 
54
62
  Compile a selected chapter range directly from an editable SSMDBook workspace:
55
63
 
@@ -72,6 +80,58 @@ utterplan/
72
80
 
73
81
  Omit `--chapters` to compile all manifest chapters. Use comma-separated source-number selections for non-contiguous ranges, for example `utterplan compile-book chapters --chapters 1,3-5`. `--diagnostics warnings|all|none` defaults to warnings; failures are always shown and reports retain complete diagnostics. The book root itself is also accepted as input; see the [SSMDBook guide](docs/ssmdbook.md).
74
82
 
83
+ ## Logical roles and complete RenderProfiles
84
+
85
+ An SSMD `voice` reference is a **logical role**: a renderer-independent name such as `narrator`, `host`, or `guest`. A **role binding** is the concrete renderer configuration for that name in a RenderProfile. One RenderProfile is the complete cast for one render variant, together with shared planning and pause policy; it may bind different roles to different engines. UtterPlan accepts one `--profile` per projection, not one profile per role, and does not discover engines or voices.
86
+
87
+ A profile can define a default for speech without an explicit role reference and explicit bindings for other roles:
88
+
89
+ In SSMD, the source remains renderer-independent; for example:
90
+
91
+ ```markdown
92
+ :::{voice="host"}
93
+ Host line.
94
+ :::
95
+ ```
96
+
97
+ ```toml
98
+ default_role = "narrator"
99
+
100
+ [roles.narrator]
101
+ language = "en-US"
102
+
103
+ [roles.narrator.binding]
104
+ renderer = "kokoro"
105
+ target_id = "v1.0"
106
+ voice = "af_heart"
107
+
108
+ [roles.host]
109
+ language = "en-US"
110
+
111
+ [roles.host.binding]
112
+ renderer = "kitten"
113
+ target_id = "mini-0.8"
114
+ voice = "Jasper"
115
+
116
+ [roles.guest]
117
+ language = "en-US"
118
+
119
+ [roles.guest.binding]
120
+ renderer = "piper"
121
+ target_id = "en_US-amy-medium"
122
+ voice = "en_US-amy-medium"
123
+ ```
124
+
125
+ Inspect symbolic role usage without a profile, then project a document or selected book chapters with the same complete cast:
126
+
127
+ ```bash
128
+ utterplan inspect story.utterplan.toml --roles
129
+ utterplan render-document story.ssmdoc --profile printer-cast.renderprofile.toml
130
+ utterplan render-book novel.ssmdbook --profile novel-cast.renderprofile.toml
131
+ ```
132
+
133
+ Projection reports required and available roles and rejects incomplete coverage before writing the RenderPlan variant. The profile and its concrete role bindings remain separate from the source SSMD.
134
+
75
135
  ## From an SSMDBook to RenderPlans
76
136
 
77
137
  UtterPlan keeps one canonical semantic plan per chapter. A RenderProfile is a
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Architecture Documentation"
3
- version: 2
3
+ version: 3
4
4
  generator: "archledger 0.4.0"
5
5
  arc42_template_version: "9.0-EN"
6
6
  ---
@@ -50,7 +50,7 @@ FlowPlan v7 (paragraph-local units, segments, boundaries, authored pauses)
50
50
  Downstream chunking / token-budget / pause choices
51
51
  |
52
52
  v
53
- Readio / VoiceRender / other FlowPlan consumers
53
+ Downstream FlowPlan consumers
54
54
  |
55
55
  v
56
56
  G2P / synthesis / audio (downstream)
@@ -62,7 +62,7 @@ workspaces without changing book content.
62
62
 
63
63
  ## Document workspaces
64
64
 
65
- An editable `ssmdconvert.document` v1 workspace (`.ssmdoc`) is another source path for the one-document compiler. `compile-document` discovers its canonical current SSMD document and writes one FlowPlan plus an operational report under `.readio/utterplan/`; it does not create a book or alter workspace source material.
65
+ An editable `ssmdconvert.document` v1 workspace (`.ssmdoc`) is another source path for the one-document compiler. `compile-document` discovers its canonical current SSMD document and writes one FlowPlan plus an operational report under `<workspace>/utterplan/`; `render-document` projects that saved plan with an explicit profile into `<workspace>/renderplan/<label>--<profile-id-prefix>/`. Neither command creates book-set semantics or alters workspace source material.
66
66
 
67
67
  ## Requirements Overview
68
68
 
@@ -125,7 +125,7 @@ TTSReady
125
125
  ^
126
126
  UtterPlan
127
127
  ^
128
- Readio / VoiceRender / other FlowPlan consumers
128
+ Downstream FlowPlan consumers
129
129
  ```
130
130
 
131
131
  UtterPlan depends directly on SSMD, PhraseSplit, and TTSReady:
@@ -140,9 +140,9 @@ UtterPlan depends directly on SSMD, PhraseSplit, and TTSReady:
140
140
  - **spaCy** optionally enriches tokens (best effort, `sm`/`md`/`lg`/`trf` preference;
141
141
  UtterPlan never downloads models and falls back to deterministic tokens).
142
142
  - **RenderPlan** defines the shared profile/plan contract used by the optional
143
- projection; **VoiceRender** creates engine-backed profiles and owns runtime
144
- validation, preflight, and speech execution; **Readio** and other consumers
145
- consume FlowPlan downstream.
143
+ projection; downstream renderers consume portable RenderProfiles and RenderPlans,
144
+ while other downstream consumers consume FlowPlans. Engine selection, preflight,
145
+ and speech execution are outside UtterPlan.
146
146
 
147
147
  ## In scope
148
148
 
@@ -346,12 +346,23 @@ not calculate renderer-specific metrics.
346
346
  ```text
347
347
  ssmdconvert.document v1 manifest + current document.ssmd.md
348
348
  -> utterplan compile-document
349
- -> one FlowPlan at .readio/utterplan/document-0001.utterplan.toml
349
+ -> one FlowPlan at utterplan/document.utterplan.toml
350
350
  + one compile-report.toml
351
351
  ```
352
352
 
353
353
  The manifest validates the workspace contract and safe canonical path; SSMD 0.9 content is compiled by the existing single-document path. A stale stored digest does not block editable-directory compilation, and UtterPlan does not modify source material or model the document as a chapter.
354
354
 
355
+ ## Scenario: project a RenderPlan from an editable SSMDDoc workspace
356
+
357
+ ```text
358
+ saved FlowPlan + explicit RenderProfile
359
+ -> utterplan render-document
360
+ -> one profile-specific RenderPlan at renderplan/<label>--<profile-id-prefix>/document.renderplan.toml
361
+ + canonical renderprofile.toml snapshot + render-report.toml
362
+ ```
363
+
364
+ The command projects the existing plan without compiling source or invoking a renderer. Profile identity is embedded in the variant path, and a mismatched or ambiguous profile snapshot is rejected; source files and the SSMDDoc manifest remain unchanged.
365
+
355
366
  <!-- archledger: no accepted records for this section yet -->
356
367
 
357
368
  # Deployment View
@@ -363,8 +374,8 @@ or audio dependencies:
363
374
  dynamically derived from Git tags by setuptools-scm and is independent of the
364
375
  plan schema version.
365
376
  - **Installation shapes.** Installed as a library (`pip install utterplan`) for
366
- consumer pipelines such as Readio, or with the `utterplan` CLI for direct SSMDBook
367
- workflows. Development installs use `pip install -e '.[dev,docs]'`.
377
+ downstream FlowPlan consumers, or with the `utterplan` CLI for direct SSMDBook and
378
+ SSMDDoc workflows. Development installs use `pip install -e '.[dev,docs]'`.
368
379
  - **Optional linguistic enrichment.** spaCy models are preferred in `sm`, `md`,
369
380
  `lg`, `trf` order when already installed; UtterPlan never downloads models and
370
381
  continues with deterministic fallback tokens when spaCy or every compatible local
@@ -372,15 +383,15 @@ or audio dependencies:
372
383
  - **No runtime services.** Everything runs in-process; there is no server, daemon,
373
384
  network fetch, or audio device access. Media references and extension names in
374
385
  plans are data only.
375
- - **Downstream placement.** Consumers (Readio, VoiceRender, PyKokoro-based
376
- renderers) import UtterPlan, never the reverse. Renderers add engines, model
377
- runtimes, G2P, synthesis, and audio on their side of the boundary.
386
+ - **Downstream placement.** Consumers of UtterPlan's portable FlowPlan and optional
387
+ RenderPlan contracts add engines, model runtimes, G2P, synthesis, and audio on their
388
+ side of the boundary; UtterPlan does not import those packages.
378
389
  - **Documentation.** Sphinx docs build from `docs/` with
379
390
  `sphinx-build -W --keep-going -b html docs docs/_build/html`.
380
391
  - **Quality gate.** `python -m pytest -q`, `ruff check .`, and `mypy utterplan` run
381
392
  against the self-contained repository test suite.
382
393
 
383
- - **Editable document workspaces.** `.ssmdoc` v1 is consumed as a stable file-format contract with the standard library; `ssmdconvert` is not a runtime dependency. `compile-document` writes derived plans under the reserved `.readio/utterplan/` directory without changing source files.
394
+ - **Editable document workspaces.** `.ssmdoc` v1 is consumed as a stable file-format contract with the standard library; `ssmdconvert` is not a runtime dependency. `compile-document` writes semantic plans and its report directly under `<workspace>/utterplan/`; `render-document` writes profile-specific artifacts directly under `<workspace>/renderplan/` without changing source files.
384
395
 
385
396
  <!-- archledger: no accepted records for this section yet -->
386
397
 
@@ -4,6 +4,23 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [v0.6.5] - 2026-10-19
8
+
9
+ ### Added
10
+
11
+ - Added render-document for projecting compiled SSMDDoc FlowPlans into profile-specific RenderPlans
12
+ - Added logical role coverage inspection and complete-profile validation for document and book rendering
13
+
14
+ ### Changed
15
+
16
+ - Changed editable SSMDDoc workspaces to use stable utterplan and renderplan output directories
17
+
18
+ ### Fixed
19
+
20
+ - Fixed documentation links for logical role and profile coverage guidance
21
+
22
+ <!-- generated by releaseledger -->
23
+
7
24
  ## [v0.6.4] - 2026-10-19
8
25
 
9
26
  ### Added
@@ -50,6 +50,18 @@ require `--force`. `--explain` formats the generated plan and replan decisions;
50
50
  does not invoke planning a second time. `--json` emits a versioned JSON wrapper to
51
51
  stdout while the RenderPlan artifact remains TOML at `--output`.
52
52
 
53
+ ## Logical roles and complete-profile coverage
54
+
55
+ Role coverage is validated before grouping or replanning. The selected profile must contain every explicit speech role and, when any speech omits a role reference, its `default_role`. Missing roles are reported together as `renderplan.role.missing`; extra profile roles are allowed. The check is renderer-independent and does not add role data to the RenderPlan schema.
56
+
57
+ Use source-only role inspection without loading a RenderProfile:
58
+
59
+ ```bash
60
+ utterplan inspect chapter.utterplan.toml --roles
61
+ ```
62
+
63
+ The human `render-plan` summary reports required roles and `coverage: complete`. With `--json`, the versioned CLI wrapper includes a `role_coverage` object (`default_role`, `required`, `available`, `missing`, and `uses_default_role`) alongside, but outside, the RenderPlan payload.
64
+
53
65
  ## Input resolution
54
66
 
55
67
  The rules are deterministic:
@@ -135,10 +147,24 @@ utterplan compile-document INPUT [--output-dir PATH] [--language LANGUAGE]
135
147
  [--force] [--report PATH] [--diagnostics {warnings,all,none}]
136
148
  ```
137
149
 
138
- The default output directory is `<workspace>/.readio/utterplan/`, containing `document-0001.utterplan.toml` and `compile-report.toml`. `--output-dir` and `--report` override those locations. Existing plan files require `--force`; output collisions with the source or manifest are rejected. A document header's language is authoritative; `--language`/`--lang` is a fallback when it is absent.
150
+ The default output directory is `<workspace>/utterplan/`, containing `document.utterplan.toml` and `compile-report.toml`. `--output-dir` and `--report` override those locations. Existing plan files require `--force`; output collisions with the source or manifest are rejected. A document header's language is authoritative; `--language`/`--lang` is a fallback when it is absent.
139
151
 
140
152
  Editable content wins over the stored manifest digest: compilation reads the current `document.ssmd.md` but never modifies the manifest or source. The command has no `--input-format`, chapter selection, or `--fail-fast`; it handles one document. The existing `compile` and `validate` commands also accept the workspace root or canonical document path, while `compile` retains its normal stdout/`-o` behavior. This is not `compile-book`: an SSMDDoc creates one plan, while an SSMDBook orchestrates independent chapter plans. ZIP bundles are not supported and `ssmdconvert` is not a runtime dependency. See the [SSMDDoc guide](ssmdoc.md) for format validation and the `compile_ssmdoc()` Python API.
141
153
 
154
+ ## Project an SSMDDoc RenderPlan
155
+
156
+ `render-document` projects the already-saved single-document UtterPlan through an explicit RenderProfile. It never compiles implicitly:
157
+
158
+ ```text
159
+ utterplan render-document INPUT --profile PROFILE
160
+ [--utterplan-dir PATH] [--output-root PATH] [--variant LABEL]
161
+ [--force] [--report PATH]
162
+ ```
163
+
164
+ By default it reads `<workspace>/utterplan/document.utterplan.toml` and writes a profile-fingerprinted variant under `<workspace>/renderplan/<label>--<profile-id-prefix>/`, containing `renderprofile.toml`, `document.renderplan.toml`, and `render-report.toml`. `--variant` changes only the readable label; the profile fingerprint remains part of the directory identity. A profile mismatch or ambiguous existing snapshot is rejected even with `--force`. This is a one-document projection, not a book-set workflow. See the [SSMDDoc guide](ssmdoc.md).
165
+
166
+ The start summary also lists the document's required roles and coverage. Coverage is checked before a new variant directory, profile snapshot, or RenderPlan is written; a missing role leaves no partial variant.
167
+
142
168
  ## Compile chapters from an SSMDBook workspace
143
169
 
144
170
  `compile-book` discovers selected SSMD chapters from an editable `.ssmdbook` manifest and compiles each chapter independently through the batch compiler. Pass either the book directory or its canonical `chapters/` directory:
@@ -185,8 +211,9 @@ utterplan render-book INPUT --profile PROFILE [--chapters SELECTOR]
185
211
  `chapters/` directory. `--chapters` uses the same manifest `source_number`
186
212
  selector as `compile-book`: `all` (default), one number, an inclusive range, or
187
213
  comma-separated selections such as `1,3-5,9`. Chapters remain in manifest order.
188
- `--profile` is required and is loaded once through the shared RenderPlan codec;
189
- UtterPlan does not call VoiceRender or probe engines.
214
+ `--profile` is required and is loaded once through the shared RenderPlan codec; UtterPlan does not call VoiceRender or probe engines.
215
+
216
+ Before initializing a new variant, `render-book` checks role coverage across all readable selected chapter plans. A single incomplete chapter fails the projection with the missing role names and affected chapter IDs; no new profile snapshot or `renderplan-set.toml` is written. The start summary lists the aggregate required roles and complete coverage. Other missing/invalid source-plan behavior is unchanged.
190
217
 
191
218
  Defaults and behavior:
192
219
 
@@ -79,6 +79,29 @@ prior partitions and replaces only exact failed speech occurrences. Backend metr
79
79
  remain diagnostics supplied by preflight; UtterPlan does not estimate them. Use
80
80
  `utterplan render-plan --explain` to format the already-built decisions.
81
81
 
82
+ ### Inspect logical role coverage
83
+
84
+ The public role-coverage helpers expose source inventory independently of projection or renderer bindings. `logical_role_inventory()` counts explicit role references and speech using the profile default; `profile_role_coverage()` compares those requirements with one complete RenderProfile, preserving first-occurrence order for required roles and deterministic sorting for available roles. The returned `LogicalRoleInventory` and `ProfileRoleCoverage` types separate source inventory from profile coverage. Both functions are available from the package root:
85
+
86
+ ```python
87
+ from renderplan.codec import load_profile
88
+ from utterplan import (
89
+ FlowPlan,
90
+ logical_role_inventory,
91
+ profile_role_coverage,
92
+ assert_profile_role_coverage,
93
+ )
94
+
95
+ source = FlowPlan.load("chapter.utterplan.toml")
96
+ inventory = logical_role_inventory(source)
97
+ profile = load_profile("cast.renderprofile.toml")
98
+ coverage = assert_profile_role_coverage(source, profile)
99
+ print(inventory.explicit_roles, inventory.explicit_occurrences)
100
+ print(coverage.required_roles, coverage.available_roles, coverage.missing_roles)
101
+ ```
102
+
103
+ Empty and audio-only segments do not require a role. `assert_profile_role_coverage()` raises the normal `renderplan.role.missing` projection error with all missing role names; `build_render_plan()` performs this check before grouping or replanning.
104
+
82
105
  ## Persistable planning attempts
83
106
 
84
107
  `compile_attempt` and `UtterancePlanner.compile_attempt` expose a finalized planning
@@ -102,6 +102,8 @@ utterplan render-book . --profile amy.renderprofile.toml --chapters 5-17
102
102
  utterplan render-book . --profile joe.renderprofile.toml --chapters 5-17
103
103
  ```
104
104
 
105
+ One RenderProfile is the complete cast shared by every selected chapter, not a profile per character or role. Logical role names live in SSMD; the profile's role bindings can target different renderers. Before creating a new variant, `render-book` checks all readable selected UtterPlans together and reports every uncovered role with the affected chapter IDs. See the [README role example](https://github.com/buchwandler/utterplan/blob/main/README.md#logical-roles-and-complete-renderprofiles).
106
+
105
107
  The result has one shared semantic collection and distinct renderer-specific
106
108
  variants, for example:
107
109
 
@@ -6,7 +6,7 @@ UtterPlan can compile an editable `.ssmdoc` workspace as one SSMD 0.9 document.
6
6
 
7
7
  A workspace is identified by a `manifest.json` whose `format` is `ssmdconvert.document` and whose `schema_version` is `1`. UtterPlan supports the v1 contract and expects the canonical document entry to identify `document-0001` at the normalized relative path `document.ssmd.md`. The source is SSMD 0.9.
8
8
 
9
- The current `document.ssmd.md` is authoritative. Its manifest digest and character count are metadata, not a lock: if you edit the document and its recorded digest becomes stale, UtterPlan compiles the current file. It never rewrites the manifest, refreshes hashes, or edits source content. The `.readio/utterplan/` directory is reserved for derived UtterPlan artifacts.
9
+ The current `document.ssmd.md` is authoritative. Its manifest digest and character count are metadata, not a lock: if you edit the document and its recorded digest becomes stale, UtterPlan compiles the current file. It never rewrites the manifest, refreshes hashes, or edits source content. Semantic plans and reports are written directly under the workspace's visible `utterplan/` directory; profile-specific RenderPlans are written directly under `renderplan/`. These are derived artifacts, not manifest members.
10
10
 
11
11
  Discovery and path handling are deliberately strict. The manifest must be valid UTF-8 JSON within the 2 MiB limit, and the document is limited to 64 MiB. UtterPlan checks the canonical path and workspace containment, rejects unsupported manifest versions and unsafe paths, and does not follow a document path outside the workspace. It reads no source-media files. `ssmdconvert` is not a runtime dependency: UtterPlan consumes the published file-format contract directly.
12
12
 
@@ -23,8 +23,8 @@ utterplan compile-document story.ssmdoc
23
23
  It writes one plan and one operational report by default:
24
24
 
25
25
  ```text
26
- story.ssmdoc/.readio/utterplan/
27
- ├── document-0001.utterplan.toml
26
+ story.ssmdoc/utterplan/
27
+ ├── document.utterplan.toml
28
28
  └── compile-report.toml
29
29
  ```
30
30
 
@@ -55,10 +55,26 @@ if result.failed:
55
55
  print(result.plan_path)
56
56
  ```
57
57
 
58
- The result provides the workspace and source paths, output and report paths, and the one-item batch outcome. Options include `output_dir`, `report`, `language`, `planner_config`, `force`, `diagnostics`, and progress/start callbacks. The default artifacts match the CLI: `.readio/utterplan/document-0001.utterplan.toml` and `compile-report.toml`.
58
+ The result provides the workspace and source paths, output and report paths, and the one-item batch outcome. Options include `output_dir`, `report`, `language`, `planner_config`, `force`, `diagnostics`, and progress/start callbacks. The default artifacts match the CLI: `utterplan/document.utterplan.toml` and `utterplan/compile-report.toml`.
59
59
 
60
60
  ## SSMDDoc is not SSMDBook
61
61
 
62
62
  An SSMDDoc contains one editable document and produces one canonical FlowPlan. An SSMDBook manifest discovers multiple independent chapter documents; `compile-book` writes one plan per selected chapter and never builds a book-level combined plan. Use [SSMDBook workflows](ssmdbook.md) for book compilation and RenderPlan projection.
63
63
 
64
64
  Neither workspace workflow changes its source manifest or source documents. UtterPlan stops at semantic FlowPlans; engine selection, G2P, synthesis, and audio remain downstream.
65
+
66
+ ## Projecting a document RenderPlan
67
+
68
+ After `utterplan compile-document .`, project the existing canonical FlowPlan without recompiling it:
69
+
70
+ ```bash
71
+ utterplan render-document . --profile narration.renderprofile.toml
72
+ ```
73
+
74
+ This writes `renderplan/<label>--<profile-fingerprint>/renderprofile.toml`, `document.renderplan.toml`, and `render-report.toml` directly under the workspace. The printed exact variant can be passed to `voicerender render-document`. A document is never wrapped in a one-chapter book or a RenderPlanSet. Re-run `compile-document` and this projection after source edits; VoiceRender intentionally renders only the selected explicit RenderPlan.
75
+
76
+ ### Logical roles and profile coverage
77
+
78
+ A source-level `voice` name such as `narrator`, `host`, or `guest` is a **logical role**; it does not name an engine voice. The selected RenderProfile's **role binding** supplies the concrete renderer configuration. Treat one RenderProfile as the complete cast for a rendering variant, including its default role and shared planning/pause policy. UtterPlan accepts exactly one profile for this projection; it does not accept one profile per role or discover engines.
79
+
80
+ Use `utterplan inspect <plan> --roles` to see explicit speech-role counts and whether unqualified speech needs the selected profile's default. `render-document` reports required roles and complete coverage, and rejects any missing roles before creating a new variant directory. The same multi-role RenderProfile format and example are documented in the [README](https://github.com/buchwandler/utterplan/blob/main/README.md#logical-roles-and-complete-renderprofiles).
@@ -27,7 +27,7 @@ from utterplan import (
27
27
  replan_book,
28
28
  )
29
29
  from utterplan.config import LinguisticsConfig, PlannerConfig
30
- from utterplan.model import FlowPlan
30
+ from utterplan.model import FlowPlan, FlowSegment, FlowUnit, SegmentDirectives, VoiceDirective
31
31
 
32
32
 
33
33
  def _ssmd(text: str) -> str:
@@ -481,3 +481,28 @@ def test_public_book_services_refuse_outputs_inside_source_chapters(tmp_path: Pa
481
481
  with pytest.raises(BookServiceError, match="inside source chapters"):
482
482
  render_book(root, profile=profile_path, output_root=root / "chapters" / "renderplans")
483
483
  assert not (root / "chapters/renderplans").exists()
484
+
485
+
486
+ def test_render_book_aggregates_missing_roles_before_variant_initialization(tmp_path: Path) -> None:
487
+ root = _book(tmp_path / "role-coverage.ssmdbook", (5, 6, 7))
488
+ utterplan_dir = root / "utterplan"
489
+ utterplan_dir.mkdir()
490
+ for number, role in ((5, None), (6, "host"), (7, "guest")):
491
+ text = f"Chapter {number}."
492
+ directives = (
493
+ SegmentDirectives(voice=VoiceDirective(reference=role)) if role else SegmentDirectives()
494
+ )
495
+ FlowPlan(
496
+ language="en-US",
497
+ flow=(FlowUnit((FlowSegment(text, "en-US", directives=directives),)),),
498
+ ).save(utterplan_dir / f"chapter-{number:04d}.utterplan.toml")
499
+ profile_path = root / "narration.renderprofile.toml"
500
+ _profile(profile_path)
501
+
502
+ with pytest.raises(BookServiceError) as raised:
503
+ render_book(root, profile=profile_path)
504
+
505
+ message = str(raised.value)
506
+ assert "host: chapter-0006" in message
507
+ assert "guest: chapter-0007" in message
508
+ assert not (root / "renderplan").exists()
@@ -16,7 +16,14 @@ from renderplan import (
16
16
  )
17
17
  from renderplan import codec as renderplan_codec
18
18
 
19
- from utterplan import FlowPlan, FlowSegment, FlowUnit, TokenView
19
+ from utterplan import (
20
+ FlowPlan,
21
+ FlowSegment,
22
+ FlowUnit,
23
+ SegmentDirectives,
24
+ TokenView,
25
+ VoiceDirective,
26
+ )
20
27
  from utterplan.cli import build_parser, main
21
28
 
22
29
 
@@ -95,6 +102,32 @@ def _profile(path: Path, voice: str = "amy") -> RenderProfile:
95
102
  return renderplan_codec.load_profile(path)
96
103
 
97
104
 
105
+ def _write_role_plan(root: Path, chapter: int, role: str | None) -> None:
106
+ text = f"Chapter {chapter} {role or 'narration'}."
107
+ directives = (
108
+ SegmentDirectives(voice=VoiceDirective(reference=role)) if role else SegmentDirectives()
109
+ )
110
+ FlowPlan(
111
+ language="en-US",
112
+ flow=(FlowUnit((FlowSegment(text, "en-US", directives=directives),)),),
113
+ ).save(root / "utterplan" / f"chapter-{chapter:04d}.utterplan.toml")
114
+
115
+
116
+ def _multi_role_profile(path: Path) -> RenderProfile:
117
+ profile = RenderProfile(
118
+ roles={
119
+ "narrator": RoleProfile(language="en-US", binding={"engine": "kokoro"}),
120
+ "host": RoleProfile(language="en-US", binding={"engine": "kitten"}),
121
+ "guest": RoleProfile(language="en-US", binding={"engine": "piper"}),
122
+ },
123
+ default_role="narrator",
124
+ planning=PlanningPolicy(split="sentence"),
125
+ pauses=PausePolicy(mode="manual"),
126
+ )
127
+ renderplan_codec.save_profile(profile, path)
128
+ return renderplan_codec.load_profile(path)
129
+
130
+
98
131
  def _render(
99
132
  root: Path,
100
133
  profile_path: Path,
@@ -208,6 +241,32 @@ def test_render_book_success_matches_single_file_projection(tmp_path: Path, caps
208
241
  assert batch_plan.plan_id == single_plan.plan_id
209
242
 
210
243
 
244
+ def test_render_book_reports_multi_role_coverage(tmp_path: Path, capsys) -> None:
245
+ root = _write_book(tmp_path / "multi-role.ssmdbook", (5, 6, 7))
246
+ _write_role_plan(root, 5, "host")
247
+ _write_role_plan(root, 6, "guest")
248
+ _write_role_plan(root, 7, None)
249
+ profile_path = root / "cast.renderprofile.toml"
250
+ profile = _multi_role_profile(profile_path)
251
+
252
+ assert _render(root, profile_path) == 0
253
+
254
+ output = capsys.readouterr().err
255
+ assert "roles: host, guest, narrator" in output
256
+ assert "coverage: complete" in output
257
+ variant = _variant(root, profile, label="cast")
258
+ collection = load_renderplan_set(variant / "renderplan-set.toml")
259
+ assert len(collection.chapters) == 3
260
+ plans = [
261
+ renderplan_codec.load_plan(variant / chapter.renderplan) for chapter in collection.chapters
262
+ ]
263
+ assert {speech.role for plan in plans for speech in plan.speech} == {
264
+ "host",
265
+ "guest",
266
+ "narrator",
267
+ }
268
+
269
+
211
270
  def test_render_book_accumulates_successful_chapters_and_replaces_latest_report(
212
271
  tmp_path: Path, capsys
213
272
  ) -> None: