textflowkit 0.1.5__tar.gz → 0.1.7__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 (185) hide show
  1. textflowkit-0.1.7/.dockerignore +54 -0
  2. textflowkit-0.1.7/.env.example +20 -0
  3. textflowkit-0.1.7/.github/workflows/publish-pypi.yml +211 -0
  4. {textflowkit-0.1.5 → textflowkit-0.1.7}/.gitignore +2 -0
  5. textflowkit-0.1.7/CHANGELOG.md +100 -0
  6. {textflowkit-0.1.5 → textflowkit-0.1.7}/CONTRIBUTING.md +7 -0
  7. textflowkit-0.1.7/Dockerfile +115 -0
  8. {textflowkit-0.1.5 → textflowkit-0.1.7}/LEGAL.md +12 -3
  9. {textflowkit-0.1.5 → textflowkit-0.1.7}/PKG-INFO +105 -17
  10. {textflowkit-0.1.5 → textflowkit-0.1.7}/README.md +100 -16
  11. {textflowkit-0.1.5 → textflowkit-0.1.7}/SECURITY.md +57 -5
  12. textflowkit-0.1.7/compose.yaml +111 -0
  13. textflowkit-0.1.7/docs/404.html +111 -0
  14. textflowkit-0.1.7/docs/adapters.md +819 -0
  15. textflowkit-0.1.7/docs/architecture.md +357 -0
  16. {textflowkit-0.1.5 → textflowkit-0.1.7}/docs/index.html +15 -9
  17. {textflowkit-0.1.5 → textflowkit-0.1.7}/docs/install.md +120 -25
  18. textflowkit-0.1.7/docs/release-checklist.md +278 -0
  19. textflowkit-0.1.7/docs/roadmap.md +168 -0
  20. {textflowkit-0.1.5 → textflowkit-0.1.7}/docs/site-deployment.md +22 -0
  21. {textflowkit-0.1.5 → textflowkit-0.1.7}/docs/sources.md +8 -0
  22. textflowkit-0.1.7/docs/user-manual.md +274 -0
  23. textflowkit-0.1.7/pyproject.toml +122 -0
  24. textflowkit-0.1.7/scripts/fetch_published_fonts.py +481 -0
  25. {textflowkit-0.1.5 → textflowkit-0.1.7}/scripts/smoke_live_youtube.py +80 -1
  26. textflowkit-0.1.7/scripts/verify_readme_release.py +219 -0
  27. textflowkit-0.1.7/scripts/verify_release_ci.py +171 -0
  28. textflowkit-0.1.7/scripts/verify_release_versions.py +145 -0
  29. textflowkit-0.1.7/scripts/write_release_manifest.py +236 -0
  30. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/__init__.py +1 -1
  31. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/adapters/http_server.py +227 -36
  32. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/adapters/mcp_server.py +337 -45
  33. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/cli.py +138 -16
  34. textflowkit-0.1.7/src/textflowkit/core/batch.py +170 -0
  35. textflowkit-0.1.7/src/textflowkit/core/bind.py +321 -0
  36. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/checkpoint.py +115 -20
  37. textflowkit-0.1.7/src/textflowkit/core/engine.py +410 -0
  38. textflowkit-0.1.7/src/textflowkit/core/executor.py +791 -0
  39. textflowkit-0.1.7/src/textflowkit/core/jobs.py +675 -0
  40. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/model.py +30 -10
  41. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/paths.py +18 -2
  42. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/pipeline.py +208 -15
  43. textflowkit-0.1.7/src/textflowkit/core/runner.py +264 -0
  44. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/service.py +55 -10
  45. textflowkit-0.1.7/src/textflowkit/core/sqlite_store.py +585 -0
  46. textflowkit-0.1.7/src/textflowkit/core/startup.py +136 -0
  47. textflowkit-0.1.7/src/textflowkit/core/submission.py +540 -0
  48. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/translate.py +42 -5
  49. textflowkit-0.1.7/src/textflowkit/render/__init__.py +941 -0
  50. textflowkit-0.1.7/src/textflowkit/render/_cue_layout.py +352 -0
  51. textflowkit-0.1.7/src/textflowkit/render/_cue_text.py +74 -0
  52. textflowkit-0.1.7/src/textflowkit/render/_rename_excl.py +163 -0
  53. textflowkit-0.1.7/src/textflowkit/render/_rename_noreplace.py +163 -0
  54. textflowkit-0.1.7/src/textflowkit/render/srt.py +31 -0
  55. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/render/txt.py +10 -1
  56. textflowkit-0.1.7/src/textflowkit/render/vtt.py +34 -0
  57. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/sources/acquire.py +216 -6
  58. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/sources/detect.py +16 -8
  59. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_acquire.py +178 -0
  60. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_adapters.py +154 -29
  61. textflowkit-0.1.7/tests/test_admission_ownership.py +375 -0
  62. textflowkit-0.1.7/tests/test_atomic_resume.py +615 -0
  63. textflowkit-0.1.7/tests/test_atomic_resume_aba.py +478 -0
  64. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_batch.py +61 -30
  65. textflowkit-0.1.7/tests/test_batch_item_boundary.py +645 -0
  66. textflowkit-0.1.7/tests/test_batch_resume_shared.py +213 -0
  67. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_bind.py +48 -0
  68. textflowkit-0.1.7/tests/test_cancellation_finalization.py +1470 -0
  69. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_checkpoint.py +1 -1
  70. textflowkit-0.1.7/tests/test_checkpoint_single_copy.py +517 -0
  71. textflowkit-0.1.7/tests/test_cli_batch_feedback.py +234 -0
  72. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_cli_resume_batch.py +30 -2
  73. textflowkit-0.1.7/tests/test_cli_stage_feedback.py +373 -0
  74. textflowkit-0.1.7/tests/test_cli_stdout_format.py +181 -0
  75. textflowkit-0.1.7/tests/test_confined_decoder_boundary.py +248 -0
  76. textflowkit-0.1.7/tests/test_confined_indirect_media.py +211 -0
  77. textflowkit-0.1.7/tests/test_confined_resume_boundary.py +153 -0
  78. textflowkit-0.1.7/tests/test_container_example.py +756 -0
  79. textflowkit-0.1.7/tests/test_cue_layout.py +427 -0
  80. textflowkit-0.1.7/tests/test_diarize_example_flags.py +140 -0
  81. textflowkit-0.1.7/tests/test_diarize_token_docs.py +181 -0
  82. textflowkit-0.1.7/tests/test_docx_publication_identity.py +994 -0
  83. textflowkit-0.1.7/tests/test_done_output_integrity.py +611 -0
  84. textflowkit-0.1.7/tests/test_empty_formats_resume.py +135 -0
  85. textflowkit-0.1.7/tests/test_engine_adapters.py +581 -0
  86. textflowkit-0.1.7/tests/test_engine_faster.py +479 -0
  87. textflowkit-0.1.7/tests/test_engine_quiet.py +142 -0
  88. textflowkit-0.1.7/tests/test_executor_store_failure.py +1000 -0
  89. textflowkit-0.1.7/tests/test_export_file_mode.py +226 -0
  90. textflowkit-0.1.7/tests/test_export_preflight.py +683 -0
  91. textflowkit-0.1.7/tests/test_faster_whisper_docs.py +108 -0
  92. textflowkit-0.1.7/tests/test_fetch_published_fonts.py +779 -0
  93. textflowkit-0.1.7/tests/test_font_notices.py +280 -0
  94. textflowkit-0.1.7/tests/test_http_host_boundary.py +279 -0
  95. textflowkit-0.1.7/tests/test_http_trusted_proxy.py +333 -0
  96. textflowkit-0.1.7/tests/test_job_progress.py +237 -0
  97. textflowkit-0.1.7/tests/test_landing_commands.py +216 -0
  98. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_landing_page.py +3 -1
  99. textflowkit-0.1.7/tests/test_legal_summary.py +134 -0
  100. textflowkit-0.1.7/tests/test_linux_no_hardlink.py +740 -0
  101. textflowkit-0.1.7/tests/test_live_smoke_script.py +250 -0
  102. textflowkit-0.1.7/tests/test_macos_no_hardlink.py +619 -0
  103. textflowkit-0.1.7/tests/test_mcp_http_boundary.py +457 -0
  104. textflowkit-0.1.7/tests/test_mcp_protocol_cleanliness.py +215 -0
  105. textflowkit-0.1.7/tests/test_mcp_terminal_guidance.py +222 -0
  106. textflowkit-0.1.7/tests/test_mcp_tool_descriptions.py +298 -0
  107. textflowkit-0.1.7/tests/test_mcp_work_root.py +193 -0
  108. textflowkit-0.1.7/tests/test_model.py +150 -0
  109. textflowkit-0.1.7/tests/test_partial_output_resume.py +273 -0
  110. textflowkit-0.1.7/tests/test_partial_pdf_resume.py +362 -0
  111. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_pipeline_integrity.py +1 -1
  112. textflowkit-0.1.7/tests/test_production_browser_cookies.py +257 -0
  113. textflowkit-0.1.7/tests/test_publish_ci_gate.py +276 -0
  114. textflowkit-0.1.7/tests/test_publish_fonts_wiring.py +351 -0
  115. textflowkit-0.1.7/tests/test_publish_manifest_wiring.py +158 -0
  116. textflowkit-0.1.7/tests/test_publish_workflow_gate.py +118 -0
  117. textflowkit-0.1.7/tests/test_readme_layout.py +219 -0
  118. textflowkit-0.1.7/tests/test_readme_release_guard.py +393 -0
  119. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_release_boundaries.py +153 -7
  120. textflowkit-0.1.7/tests/test_release_manifest.py +420 -0
  121. textflowkit-0.1.7/tests/test_release_surfaces.py +209 -0
  122. textflowkit-0.1.7/tests/test_render.py +253 -0
  123. textflowkit-0.1.7/tests/test_sdist_allowlist.py +237 -0
  124. textflowkit-0.1.7/tests/test_service_profile.py +390 -0
  125. textflowkit-0.1.7/tests/test_site_not_found.py +162 -0
  126. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_sources.py +84 -0
  127. textflowkit-0.1.7/tests/test_stage_reuse_resume.py +1315 -0
  128. textflowkit-0.1.7/tests/test_startup_recovery.py +622 -0
  129. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_submission.py +40 -17
  130. textflowkit-0.1.7/tests/test_submission_validation.py +311 -0
  131. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_translate.py +71 -0
  132. textflowkit-0.1.7/tests/test_translate_completeness.py +351 -0
  133. textflowkit-0.1.7/tests/test_verify_release_versions.py +225 -0
  134. textflowkit-0.1.7/tests/test_windows_no_hardlink.py +319 -0
  135. textflowkit-0.1.5/.github/workflows/publish-pypi.yml +0 -114
  136. textflowkit-0.1.5/docs/adapters.md +0 -433
  137. textflowkit-0.1.5/docs/release-checklist.md +0 -86
  138. textflowkit-0.1.5/docs/roadmap.md +0 -101
  139. textflowkit-0.1.5/pyproject.toml +0 -68
  140. textflowkit-0.1.5/src/textflowkit/core/batch.py +0 -186
  141. textflowkit-0.1.5/src/textflowkit/core/bind.py +0 -66
  142. textflowkit-0.1.5/src/textflowkit/core/engine.py +0 -135
  143. textflowkit-0.1.5/src/textflowkit/core/executor.py +0 -301
  144. textflowkit-0.1.5/src/textflowkit/core/jobs.py +0 -241
  145. textflowkit-0.1.5/src/textflowkit/core/runner.py +0 -146
  146. textflowkit-0.1.5/src/textflowkit/core/sqlite_store.py +0 -233
  147. textflowkit-0.1.5/src/textflowkit/core/submission.py +0 -222
  148. textflowkit-0.1.5/src/textflowkit/render/__init__.py +0 -206
  149. textflowkit-0.1.5/src/textflowkit/render/srt.py +0 -22
  150. textflowkit-0.1.5/src/textflowkit/render/vtt.py +0 -20
  151. textflowkit-0.1.5/tests/test_live_smoke_script.py +0 -106
  152. textflowkit-0.1.5/tests/test_model.py +0 -57
  153. textflowkit-0.1.5/tests/test_render.py +0 -99
  154. textflowkit-0.1.5/tests/test_service_profile.py +0 -206
  155. {textflowkit-0.1.5 → textflowkit-0.1.7}/.gitattributes +0 -0
  156. {textflowkit-0.1.5 → textflowkit-0.1.7}/.github/dependabot.yml +0 -0
  157. {textflowkit-0.1.5 → textflowkit-0.1.7}/.github/workflows/ci.yml +0 -0
  158. {textflowkit-0.1.5 → textflowkit-0.1.7}/.pre-commit-config.yaml +0 -0
  159. {textflowkit-0.1.5 → textflowkit-0.1.7}/LICENSE +0 -0
  160. {textflowkit-0.1.5 → textflowkit-0.1.7}/docs/.nojekyll +0 -0
  161. {textflowkit-0.1.5 → textflowkit-0.1.7}/scripts/smoke_installed_wheel.py +0 -0
  162. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/adapters/__init__.py +0 -0
  163. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/assets/README.md +0 -0
  164. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/assets/selftest-speech.wav +0 -0
  165. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/__init__.py +0 -0
  166. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/cancel.py +0 -0
  167. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/diarize.py +0 -0
  168. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/retrieval.py +0 -0
  169. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/core/timeutil.py +0 -0
  170. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/render/docx.py +0 -0
  171. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/render/markdown.py +0 -0
  172. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/render/pdf.py +0 -0
  173. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/sources/__init__.py +0 -0
  174. {textflowkit-0.1.5 → textflowkit-0.1.7}/src/textflowkit/sources/scratch.py +0 -0
  175. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/__init__.py +0 -0
  176. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/ollama_stub.py +0 -0
  177. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_cli.py +0 -0
  178. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_diarize.py +0 -0
  179. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_engine_cache.py +0 -0
  180. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_executor.py +0 -0
  181. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_export.py +0 -0
  182. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_jobs.py +0 -0
  183. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_paths.py +0 -0
  184. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_retrieval.py +0 -0
  185. {textflowkit-0.1.5 → textflowkit-0.1.7}/tests/test_stdio_protocol.py +0 -0
@@ -0,0 +1,54 @@
1
+ # What the container image build must not receive.
2
+ #
3
+ # The image is built from this repository root, so anything not named here lands
4
+ # in the build context. Only local state and secrets are named: the licence, the
5
+ # source, the docs, and the packaging metadata stay in, because the wheel build
6
+ # reads them (`license-files` and `readme` in pyproject.toml) and because a
7
+ # context that quietly dropped them would produce an image that builds but is not
8
+ # this project.
9
+
10
+ # Version control, environments, caches, build output.
11
+ .git
12
+ .venv
13
+ venv
14
+ __pycache__
15
+ *.py[cod]
16
+ *.egg-info
17
+ .pytest_cache
18
+ .ruff_cache
19
+ .mypy_cache
20
+ build
21
+ dist
22
+
23
+ # Local work products and media, as in .gitignore. The one exception below is
24
+ # load-bearing: pyproject.toml's `artifacts` packs `src/textflowkit/assets/*.wav`
25
+ # into the wheel, so a media filter without the negation would ship an image
26
+ # whose asset directory is silently empty.
27
+ work
28
+ out
29
+ outputs
30
+ *.wav
31
+ !src/textflowkit/assets/selftest-speech.wav
32
+ *.mp3
33
+ *.m4a
34
+ *.mp4
35
+ *.webm
36
+ *.srt
37
+ *.vtt
38
+
39
+ # Secrets and local configuration. The production token comes from the operator's
40
+ # environment at run time, never from the image.
41
+ .env
42
+ .env.*
43
+ *.local.toml
44
+
45
+ # Host-side media for the Compose example, if the operator placed it in-tree.
46
+ # Anchored: only the repository-root `data/` the Compose file mounts, not any
47
+ # fixture directory a package might legitimately ship.
48
+ /data
49
+
50
+ # OS and editor debris.
51
+ .DS_Store
52
+ Thumbs.db
53
+ .vscode
54
+ .idea
@@ -0,0 +1,20 @@
1
+ # Template for the Compose example. Copy it to `.env`, which is git-ignored:
2
+ #
3
+ # cp .env.example .env
4
+ #
5
+ # There is no default token, and Compose will not start without one. Generate a
6
+ # value and paste it in:
7
+ #
8
+ # openssl rand -hex 32
9
+ #
10
+ # An environment variable keeps the token out of the command line, so it does not
11
+ # appear in `ps`. That is all it buys. Docker records the value in the container's
12
+ # configuration, where `docker inspect` and `docker compose config` render it for
13
+ # anyone who can reach the daemon; inside the container it is readable through
14
+ # `/proc/<pid>/environ`; and here it sits in plain text in `.env`, which git
15
+ # ignores but does not encrypt. Restrict access to the daemon, this Compose file,
16
+ # and this env file, and do not mistake one shared token for secret-store
17
+ # isolation - a real secret store is a separate decision this example does not
18
+ # make for you. Keep the value out of the repository and out of any image layer.
19
+
20
+ TEXTFLOWKIT_API_TOKEN=
@@ -0,0 +1,211 @@
1
+ name: Publish PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ if: github.repository == 'scottconverse/textflowkit'
13
+ runs-on: ubuntu-latest
14
+ # Actions read is needed to verify that this exact commit passed main CI.
15
+ # It is granted to this job only: no upload job can read run results.
16
+ permissions:
17
+ contents: read
18
+ actions: read
19
+ outputs:
20
+ # The upload jobs act on this: only a new fonts version may be uploaded.
21
+ fonts_mode: ${{ steps.fonts_release.outputs.fonts_mode }}
22
+ steps:
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+ with:
25
+ persist-credentials: false
26
+ fetch-depth: 0
27
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
28
+ with:
29
+ python-version: '3.12'
30
+ - run: python -m pip install packaging
31
+ - name: Verify release tag and package versions
32
+ env:
33
+ RELEASE_TAG: ${{ github.ref_name }}
34
+ run: |
35
+ python scripts/verify_release_versions.py
36
+ git merge-base --is-ancestor "$GITHUB_SHA" origin/main || {
37
+ echo 'Release tag is not merged into main' >&2
38
+ exit 1
39
+ }
40
+ # A distribution's long description is built from the README at this commit
41
+ # and frozen on upload, so a README that still names an older tag publishes
42
+ # a permanently wrong version claim - the v0.1.5 mistake. This runs before
43
+ # anything is built or uploaded.
44
+ - name: Require the README to name the release being published
45
+ env:
46
+ RELEASE_TAG: ${{ github.ref_name }}
47
+ run: python scripts/verify_readme_release.py
48
+ - name: Require successful CI for this exact commit
49
+ env:
50
+ GITHUB_TOKEN: ${{ github.token }}
51
+ run: python scripts/verify_release_ci.py
52
+ # The fonts package is versioned on its own contract: when the version this
53
+ # commit declares is already on PyPI, the release reuses those published
54
+ # files instead of rebuilding them, and the fonts upload job is skipped.
55
+ # The script decides, downloads and verifies, and stops the release on
56
+ # anything it cannot verify. It runs before the build so a reuse can never
57
+ # be overwritten by a local build, and before any upload.
58
+ - name: Resolve the fonts release
59
+ id: fonts_release
60
+ run: |
61
+ python scripts/fetch_published_fonts.py \
62
+ --package-dir packages/textflowkit-fonts \
63
+ --out-dir dist/fonts \
64
+ --github-output "$GITHUB_OUTPUT"
65
+ - name: Build distributions
66
+ env:
67
+ FONTS_MODE: ${{ steps.fonts_release.outputs.fonts_mode }}
68
+ run: |
69
+ python -m pip install --upgrade build packaging
70
+ python -m build --sdist --wheel --outdir dist/main .
71
+ case "$FONTS_MODE" in
72
+ new)
73
+ # The declared fonts version is not on PyPI, so this release
74
+ # publishes it.
75
+ python -m build --sdist --wheel --outdir dist/fonts packages/textflowkit-fonts
76
+ ;;
77
+ reuse)
78
+ # dist/fonts holds the published files, fetched and verified
79
+ # against PyPI above. Rebuilding here would replace them with
80
+ # different bytes and break the hashes the manifest publishes.
81
+ echo "reusing the textflowkit-fonts files fetched from PyPI"
82
+ ;;
83
+ *)
84
+ echo "no fonts mode was resolved; refusing to build" >&2
85
+ exit 1
86
+ ;;
87
+ esac
88
+ python -m pip install twine
89
+ python -m twine check dist/main/* dist/fonts/*
90
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
91
+ with:
92
+ name: distributions
93
+ path: dist/
94
+ if-no-files-found: error
95
+
96
+ publish-fonts:
97
+ needs: build
98
+ # Only a new fonts version is uploaded. A reused one is already on PyPI, and
99
+ # PyPI rejects a duplicate filename; this workflow deliberately does not
100
+ # skip such an upload, so the job must not run at all.
101
+ if: needs.build.outputs.fonts_mode == 'new'
102
+ runs-on: ubuntu-latest
103
+ environment: pypi
104
+ permissions:
105
+ id-token: write
106
+ steps:
107
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
108
+ with:
109
+ name: distributions
110
+ path: dist/
111
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
112
+ with:
113
+ packages-dir: dist/fonts/
114
+
115
+ publish-main:
116
+ # The core upload needs the build as well as the fonts job. On a reuse the
117
+ # fonts job is *skipped*, and GitHub skips a job whose `needs:` job was
118
+ # skipped unless the job's `if` uses a status check function - `cancelled()`
119
+ # below. That is why the condition is written out instead of left implicit:
120
+ # an implicit `success()` would lose to the skip propagation and the reuse
121
+ # path would upload nothing and publish no release.
122
+ #
123
+ # `always()` would also defeat the skip propagation, but it runs the job in
124
+ # a cancelled run too, so it is not used.
125
+ #
126
+ # Core may continue past the fonts job only when that job uploaded a new
127
+ # fonts version or was skipped because the release reuses published files. A
128
+ # failed or cancelled fonts upload, a failed build, a cancelled run, and a
129
+ # skipped fonts job that was not a reuse all leave core unpublished, so a
130
+ # core release cannot advertise a fonts version that is not on PyPI.
131
+ needs: [build, publish-fonts]
132
+ if: >-
133
+ ${{ !cancelled()
134
+ && needs.build.result == 'success'
135
+ && (needs.publish-fonts.result == 'success'
136
+ || (needs.publish-fonts.result == 'skipped'
137
+ && needs.build.outputs.fonts_mode == 'reuse')) }}
138
+ runs-on: ubuntu-latest
139
+ environment: pypi
140
+ permissions:
141
+ id-token: write
142
+ steps:
143
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
144
+ with:
145
+ name: distributions
146
+ path: dist/
147
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
148
+ with:
149
+ packages-dir: dist/main/
150
+
151
+ publish-github-release:
152
+ # Deliberately no `if:` here. The implicit `success()` plus the default skip
153
+ # propagation is the gate that matters: a core upload that failed or was
154
+ # skipped for any reason skips this job too, so a failed release cannot end
155
+ # in a public GitHub release. Adding a status check function here would
156
+ # weaken that, not strengthen it.
157
+ needs: publish-main
158
+ runs-on: ubuntu-latest
159
+ permissions:
160
+ contents: write
161
+ steps:
162
+ # The manifest step runs this repository's own script, so the tagged
163
+ # commit must be present. Its bytes are the ones already required to have
164
+ # passed CI on main, and `gh` takes the token from the environment, not
165
+ # from git credentials.
166
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
167
+ with:
168
+ persist-credentials: false
169
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
170
+ with:
171
+ name: distributions
172
+ path: dist/
173
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
174
+ with:
175
+ python-version: '3.12'
176
+ # The manifest pins each project's own version. Core is the tag; fonts is
177
+ # the version the tagged commit declares, which is the version of the
178
+ # files in dist/fonts whether they were built or fetched.
179
+ - name: Read the declared fonts version
180
+ id: fonts_version
181
+ run: |
182
+ python - <<'PY' >> "$GITHUB_OUTPUT"
183
+ import tomllib
184
+ from pathlib import Path
185
+
186
+ project = tomllib.loads(
187
+ Path('packages/textflowkit-fonts/pyproject.toml').read_text(encoding='utf-8')
188
+ )['project']
189
+ print(f"version={project['version']}")
190
+ PY
191
+ - name: Hash the downloaded distributions
192
+ env:
193
+ RELEASE_TAG: ${{ github.ref_name }}
194
+ FONTS_VERSION: ${{ steps.fonts_version.outputs.version }}
195
+ run: |
196
+ python scripts/write_release_manifest.py \
197
+ --output SHA256SUMS --version "$RELEASE_TAG" --fonts-version "$FONTS_VERSION" \
198
+ dist/main/* dist/fonts/*
199
+ - name: Create draft with the exact PyPI distributions
200
+ env:
201
+ GH_TOKEN: ${{ github.token }}
202
+ RELEASE_TAG: ${{ github.ref_name }}
203
+ run: |
204
+ gh release create "$RELEASE_TAG" dist/main/* dist/fonts/* SHA256SUMS \
205
+ --repo "$GITHUB_REPOSITORY" --verify-tag --draft \
206
+ --title "$RELEASE_TAG" --generate-notes
207
+ - name: Publish complete GitHub release
208
+ env:
209
+ GH_TOKEN: ${{ github.token }}
210
+ RELEASE_TAG: ${{ github.ref_name }}
211
+ run: gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --draft=false
@@ -16,6 +16,8 @@ venv/
16
16
  work/
17
17
  out/
18
18
  outputs/
19
+ # Host-side media for the Compose example (`compose.yaml` mounts ./data/input).
20
+ data/
19
21
  *.wav
20
22
  !src/textflowkit/assets/selftest-speech.wav
21
23
  *.mp3
@@ -0,0 +1,100 @@
1
+ # Changelog
2
+
3
+ ## v0.1.7 — 2026-10-01
4
+
5
+ The 2026-10-01 audit repair release. Core version: 0.1.7; unchanged optional fonts: 0.1.6. Release artifacts and publication status are recorded on [GitHub Releases](https://github.com/scottconverse/textflowkit/releases/tag/v0.1.7). Local source-candidate verification does not establish installed-package or individual harness compatibility.
6
+
7
+ ### Engineering fixes
8
+
9
+ - **Confined decoder boundary.** With `TEXTFLOWKIT_INPUT_ROOT` set, a confined
10
+ local input is staged and its decode is restricted to FFmpeg's self-contained
11
+ demuxers (`-format_whitelist`), and the `ffprobe` duration probe carries the
12
+ same whitelist. A playlist/manifest that would make the decoder open referenced
13
+ files is refused. The restriction covers a resume that re-decodes; a stage with
14
+ durable output is reused, not re-decoded. Owner runs without an input root stay
15
+ unrestricted.
16
+ - **Atomic attempt ownership.** A worker pins the attempt generation it claimed;
17
+ every progress write, checkpoint write, and terminal finalize is guarded on it,
18
+ so a stale run cannot overwrite a newer attempt's label or verdict. The reopen
19
+ of a terminal row for resume is an atomic claim pinned to the observed attempt —
20
+ two callers racing one job id cannot both reopen it.
21
+ - **Atomic worker startup and cancellation.** Worker-thread startup is atomic, and
22
+ the completion/cancellation race is decided in one place against the owned
23
+ attempt: the row becomes CANCELLED only if that generation carries an accepted
24
+ cancellation, DONE otherwise, so a finished DONE row can never still carry an
25
+ accepted cancel.
26
+ - **DOCX identity.** Completed-output reuse for DOCX compares the archive bytes
27
+ with only the local and central ZIP time/date pairs blanked; every other byte
28
+ (member names and order, compression, flags, extra fields, comments, compressed
29
+ content) must be equal, and the comparison never inflates a member.
30
+ - **Completed-stage reuse.** A run leaves a durable per-stage marker for each
31
+ expensive stage it finishes; resume reuses only stages finished for the
32
+ **requested configuration**, and a legacy coarse `postprocess` marker is honoured
33
+ only for the optional stages that record actually requested. The rule keys on
34
+ the marker, never on transcript metadata.
35
+ - **Aggregate export preflight.** The whole requested format set is validated
36
+ (unsupported/duplicate names, then every dependency) and the aggregate rendered
37
+ bytes are bounded **before any destination file is written**, for every surface
38
+ that publishes output.
39
+ - **Translation completeness.** The translator's result length is checked against
40
+ its input and every result is validated **before mutating any segment**: a
41
+ blank result for a non-blank segment raises and leaves the transcript exactly as
42
+ it was, rather than half-writing a set.
43
+ - **Per-item batch validation.** Each batch entry is constructed and admitted
44
+ independently (HTTP and MCP): a non-object entry, a non-string/empty source, an
45
+ unsupported format, or an unusable engine is **that** entry's error, carrying its
46
+ original zero-based `index` and best-effort `source`, while the other entries
47
+ still queue, in submitted order. There is no whole-batch engine preflight.
48
+ - **Terminal feedback.** A non-quiet CLI run streams live stage progress to
49
+ **stderr, flushed** (stdout stays clean for `--stdout`); quiet suppresses it.
50
+ Batch prints each item's result as it finishes. Job status exposes the recorded
51
+ stage.
52
+
53
+ ### Documentation corrections (DOC-001 – DOC-006)
54
+
55
+ - **DOC-001** — the user manual now states that canonical saved/exported JSON
56
+ retains hidden segments and is an archive, not a redaction mechanism, and adds
57
+ a worked safe-publication-copy example that filters the copy without mutating
58
+ the stored transcript.
59
+ - **DOC-002** — the adapter guide and manual now distinguish response shapes:
60
+ HTTP JSON returns a `transcript` object with page counts but no `next`, MCP
61
+ returns a JSON string under `content` and adds a `next` instruction, and HTTP
62
+ text reads carry no paging metadata. Time selection is described as
63
+ overlapping rather than clipped.
64
+ - **DOC-003** — "same options" is replaced with a per-surface parameter matrix
65
+ (HTTP `formats` array vs MCP comma-separated string; HTTP batch options are
66
+ **per item**, MCP batch options are shared and expose **no**
67
+ `cookies_from_browser` parameter), and the batch contract now states that 202
68
+ means admission, not completion, with per-item indexed errors. MCP
69
+ `sources` is documented as a list of **strings**: a non-string entry (including
70
+ a nested object or a number) is that entry's own error, not a whole-call
71
+ refusal, while a string remains valid.
72
+ - **DOC-004** — a full production settings reference (names, defaults, units,
73
+ required fields) is added to the adapter guide, and the production refusal of
74
+ browser-cookie submissions is stated across the adapter guide, the sources
75
+ guide, and `SECURITY.md`, including resume.
76
+ - **DOC-005** — the install guide chooses the diarization install path (preserve
77
+ a working ROCm stack vs fresh CPU) **before** the first pip command, and the
78
+ manual's ROCm deep link now resolves to the actual heading.
79
+ - **DOC-006** — new [architecture and lifecycle guide](docs/architecture.md)
80
+ maps the core/CLI/MCP/HTTP doors, single-owning-process SQLite, startup
81
+ recovery via the HTTP/MCP lifespans and the lazy executor, checkpoint reuse,
82
+ and output publication; the adapter guide's "reaped at startup" wording is
83
+ qualified to match. Linked from the README and `CONTRIBUTING.md`.
84
+
85
+ The production settings reference lives in
86
+ [docs/adapters.md](docs/adapters.md#production-settings-reference).
87
+
88
+ ### Deferred audit follow-ups — later fixes
89
+
90
+ On 2026-10-01, the owner explicitly deferred the following findings from the
91
+ full five-role audit. They remain open; they are not completed fixes, new release
92
+ claims, or additions to the current core-bug repair goal. No release date is set.
93
+
94
+ See [Later fixes in the roadmap](docs/roadmap.md#later-fixes--2026-10-01-audit)
95
+ for the complete list and completion criteria. The current repair goal still
96
+ covers decoder confinement, job lifecycle, publication/resume, translation and
97
+ batch correctness, major terminal guidance/documentation, and its final gate.
98
+
99
+ This changelog starts with the v0.1.7 release notes; published historical
100
+ release notes remain on [GitHub Releases](https://github.com/scottconverse/textflowkit/releases).
@@ -55,6 +55,13 @@ build of torch; see [docs/install.md](docs/install.md) for the torch-pinning tra
55
55
  Most tests do **not** need a GPU or a model download. If you add one that does,
56
56
  mark it so it can be skipped in CI.
57
57
 
58
+ ## How the pieces fit together
59
+
60
+ [docs/architecture.md](docs/architecture.md) maps the core, CLI, MCP, and HTTP
61
+ doors, the single-owning-process job lifecycle (including startup recovery),
62
+ checkpoint reuse, and output publication. Read it before changing submission,
63
+ executor, runner, or checkpoint behaviour.
64
+
58
65
  ## Pull requests
59
66
 
60
67
  - Keep the change scoped; state what it fixes.
@@ -0,0 +1,115 @@
1
+ # syntax=docker/dockerfile:1
2
+ #
3
+ # Optional Linux deployment surface for the JSON HTTP adapter. The product stays
4
+ # native Windows; nothing here is needed for a local install, and the Compose
5
+ # file beside this one is the supported way to run it.
6
+ #
7
+ # What this image does NOT provide, and cannot:
8
+ #
9
+ # * A public service. `compose.yaml` publishes the host port to 127.0.0.1 only,
10
+ # so the container is reachable from its own host and nowhere else.
11
+ # Terminating TLS, authenticating callers beyond the Bearer token, and
12
+ # enforcing an independent rate and egress policy belong to a trusted gateway
13
+ # in front of it. The built-in limiter is per process, not a distributed
14
+ # quota.
15
+ # * A working URL-job path on its own. Production URL acquisition requires an
16
+ # SSRF-filtering TEXTFLOWKIT_EGRESS_PROXY and refuses to run without one, so
17
+ # URL jobs fail closed until an operator supplies a proxy that blocks
18
+ # private/loopback destinations and DNS rebinding. No proxy is bundled. Local
19
+ # files and /health need no egress.
20
+ # * More than one process. The durable SQLite store has one owning process; a
21
+ # second one sharing TEXTFLOWKIT_DB would reap the first one's live jobs.
22
+ #
23
+ # The token is deliberately not here. The server fails at startup with a named
24
+ # error when TEXTFLOWKIT_API_TOKEN is missing, which is better than an
25
+ # image-embedded default that every puller would share.
26
+
27
+ # Pinned to a Python the CI matrix covers (3.10-3.13, linted on 3.12) and to a
28
+ # named Debian release rather than to `latest`, which would move under a rebuild.
29
+ # Not a digest pin: resolving a digest needs a registry lookup that could not be
30
+ # performed where this was written, and an invented one would be worse than an
31
+ # honest tag.
32
+ FROM python:3.12-slim-bookworm
33
+
34
+ # ffmpeg is required in every profile: decode, and the ffprobe duration bound the
35
+ # production profile enforces before a full decode.
36
+ # ca-certificates is the trust store URL acquisition needs.
37
+ # libstdc++6 is installed unconditionally so the Node binary copied below has a
38
+ # C++ runtime to link against whatever the slim base happens to carry; the gate
39
+ # below would fail the build if that binary could not run at all.
40
+ RUN set -eux; \
41
+ apt-get update; \
42
+ apt-get install -y --no-install-recommends ca-certificates ffmpeg libstdc++6; \
43
+ rm -rf /var/lib/apt/lists/*
44
+
45
+ # The JavaScript runtime yt-dlp uses to solve some YouTube player challenges.
46
+ # It comes from the official Node image rather than from the base distribution,
47
+ # because the installed yt-dlp requires Node >= 22 (yt_dlp/utils/_jsruntime.py,
48
+ # `NodeJsRuntime.MIN_SUPPORTED_VERSION`) and the version the base distribution
49
+ # carries was not measured here. Taking the runtime from a tag that names the
50
+ # major version does not by itself prove the floor either, which is why the gate
51
+ # below runs the binary and fails the build rather than trusting a tag name.
52
+ COPY --from=node:22-bookworm-slim /usr/local/bin/node /usr/local/bin/node
53
+
54
+ # yt-dlp's Node floor, taken from the installed package and re-checked against it
55
+ # by tests/test_container_example.py, so a yt-dlp upgrade that raises the floor
56
+ # fails the suite rather than only the build.
57
+ ARG NODE_MIN_MAJOR=22
58
+ RUN set -eux; \
59
+ node --version; \
60
+ node -e "const v = parseInt(process.versions.node, 10); if (v < ${NODE_MIN_MAJOR}) { console.error('node ' + process.versions.node + ' is below the yt-dlp Node floor of ${NODE_MIN_MAJOR}.0.0 (yt_dlp/utils/_jsruntime.py); use a newer node image, or raise NODE_MIN_MAJOR deliberately'); process.exit(1) }"; \
61
+ ffmpeg -version | head -n 1; \
62
+ ffprobe -version | head -n 1
63
+
64
+ # Nonroot runtime. The uid and gid are fixed so the Compose file can name them
65
+ # and the volumes below initialize with the right ownership.
66
+ RUN set -eux; \
67
+ groupadd --gid 10001 textflowkit; \
68
+ useradd --uid 10001 --gid 10001 --create-home --shell /usr/sbin/nologin textflowkit; \
69
+ install -d -o textflowkit -g textflowkit \
70
+ /srv/textflowkit \
71
+ /srv/textflowkit/input \
72
+ /srv/textflowkit/output \
73
+ /srv/textflowkit/work \
74
+ /srv/textflowkit/state \
75
+ /home/textflowkit/.cache
76
+
77
+ WORKDIR /app
78
+ COPY pyproject.toml README.md LICENSE ./
79
+ COPY src ./src
80
+ # The base dependencies are yt-dlp and openai-whisper, so this image is large:
81
+ # openai-whisper pulls a torch build. That is the product's own requirement, not
82
+ # something this file adds.
83
+ RUN python -m pip install --no-cache-dir --upgrade pip \
84
+ && python -m pip install --no-cache-dir ".[http]"
85
+
86
+ # Non-secret defaults only. The roots are the directories created above; Compose
87
+ # repeats them so the volumes it mounts land on the paths the service reads.
88
+ # Concurrency stays at the documented default of 1: the store has one owner.
89
+ ENV PYTHONUNBUFFERED=1 \
90
+ PYTHONDONTWRITEBYTECODE=1 \
91
+ PIP_NO_CACHE_DIR=1 \
92
+ TEXTFLOWKIT_PROFILE=production \
93
+ TEXTFLOWKIT_INPUT_ROOT=/srv/textflowkit/input \
94
+ TEXTFLOWKIT_OUTPUT_ROOT=/srv/textflowkit/output \
95
+ TEXTFLOWKIT_WORK_ROOT=/srv/textflowkit/work \
96
+ TEXTFLOWKIT_DB=/srv/textflowkit/state/jobs.db \
97
+ TEXTFLOWKIT_MAX_CONCURRENCY=1
98
+
99
+ USER 10001:10001
100
+
101
+ # Liveness only, and it has to carry the Bearer token: the production middleware
102
+ # covers /health like every other route. The token is read from the process
103
+ # environment, never passed as an argument, so `ps` cannot show it. That is not
104
+ # secrecy: Docker records an environment variable in the container's
105
+ # configuration (`docker inspect`), and `/proc/<pid>/environ` exposes it inside
106
+ # the container to anything running as the same user. A missing token makes this
107
+ # probe fail rather than pass - the same fail-closed direction as the server.
108
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
109
+ CMD ["python", "-c", "import os, urllib.request; request = urllib.request.Request('http://127.0.0.1:8767/health', headers={'Authorization': 'Bearer ' + os.environ['TEXTFLOWKIT_API_TOKEN']}); body = urllib.request.urlopen(request, timeout=3).read(); assert b'status' in body, body"]
110
+
111
+ # 0.0.0.0 inside the container, so the published host port reaches the server;
112
+ # `--allow-remote` is the explicit opt-in the adapter requires for a bind beyond
113
+ # loopback, and the host side of that publication stays loopback-only in Compose.
114
+ EXPOSE 8767
115
+ CMD ["textflowkit-http", "--host", "0.0.0.0", "--port", "8767", "--allow-remote"]
@@ -7,9 +7,18 @@ This document is plain-language guidance, not legal advice.
7
7
  textflowkit is distributed under the **Apache License 2.0**, which includes:
8
8
 
9
9
  - **§7 — Disclaimer of Warranty:** the work is provided on an "AS IS" BASIS,
10
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
- - **§8 — Limitation of Liability:** in no event shall any contributor be liable for
12
- damages arising from use of the work.
10
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied —
11
+ unless required by applicable law or agreed to in writing.
12
+ - **§8 — Limitation of Liability:** in no event shall any contributor be liable
13
+ for damages arising from use of the work — unless required by applicable law
14
+ (deliberate and grossly negligent acts are the example the license gives) or
15
+ agreed to in writing.
16
+
17
+ Neither of those is an absolute rule: the license sets aside the disclaimer and
18
+ the liability limit where applicable law requires otherwise, or where a separate
19
+ written agreement says otherwise. This section is a plain-language summary; the
20
+ [`LICENSE`](LICENSE) file is the controlling text and is what you should read for
21
+ the terms that apply to you.
13
22
 
14
23
  The project ships no warranty of accuracy. **Transcripts produced by speech-to-text
15
24
  models contain errors.** Do not rely on output for legal, medical, safety-critical,