focus-data-toolkit 0.11.0__tar.gz → 0.12.0__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 (258) hide show
  1. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/CHANGELOG.md +144 -1
  2. focus_data_toolkit-0.12.0/PKG-INFO +196 -0
  3. focus_data_toolkit-0.12.0/README.md +143 -0
  4. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/SECURITY.md +2 -2
  5. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/compatibility.md +4 -0
  6. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/releasing.md +16 -5
  7. focus_data_toolkit-0.12.0/docs/runner.md +554 -0
  8. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/security-model.md +10 -8
  9. focus_data_toolkit-0.12.0/docs/studio.md +196 -0
  10. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/pyproject.toml +4 -2
  11. focus_data_toolkit-0.12.0/scripts/regenerate_golden_fixtures.py +89 -0
  12. focus_data_toolkit-0.12.0/scripts/validate_official_samples.py +353 -0
  13. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/_version.py +1 -1
  14. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/__init__.py +8 -1
  15. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/contract_applied.py +64 -21
  16. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/cost_and_usage.py +40 -7
  17. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/streaming.py +6 -1
  18. focus_data_toolkit-0.12.0/src/focus_data_toolkit/generators/engine/allocation_math.py +54 -0
  19. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/determinism.py +43 -1
  20. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/json_focus.py +34 -20
  21. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/ladder.py +1 -1
  22. focus_data_toolkit-0.12.0/src/focus_data_toolkit/generators/engine/scenarios_core.py +502 -0
  23. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/serialize.py +80 -8
  24. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/aws.py +4 -3
  25. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/azure.py +4 -3
  26. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/gcp.py +1 -1
  27. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/scenarios.py +5 -17
  28. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/adapter.py +8 -4
  29. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/v1_2.py +20 -12
  30. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/v1_3.py +58 -18
  31. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_1_4_decimal_scale.json +2 -2
  32. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_json_keys.py +8 -2
  33. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/codes.py +4 -0
  34. focus_data_toolkit-0.12.0/src/focus_data_toolkit.egg-info/PKG-INFO +196 -0
  35. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/SOURCES.txt +5 -0
  36. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/requires.txt +1 -1
  37. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/README.md +6 -2
  38. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows100_seed42.csv +101 -0
  39. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7.csv +26 -0
  40. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7_credits.csv +26 -0
  41. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_contract_commitment_rows100_seed42.csv +11 -0
  42. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows100_seed42.csv +101 -0
  43. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7.csv +26 -0
  44. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7_credits.csv +26 -0
  45. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows100_seed42.csv +101 -0
  46. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7.csv +26 -0
  47. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7_credits.csv +26 -0
  48. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_contract_commitment_rows100_seed42.csv +11 -0
  49. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows100_seed42.csv +101 -0
  50. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7.csv +26 -0
  51. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7_credits.csv +26 -0
  52. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows100_seed42.csv +101 -0
  53. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7.csv +26 -0
  54. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7_credits.csv +26 -0
  55. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_contract_commitment_rows100_seed42.csv +12 -0
  56. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows100_seed42.csv +101 -0
  57. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7.csv +26 -0
  58. focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7_credits.csv +26 -0
  59. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_contract_applied.py +62 -3
  60. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_cross_provider.py +22 -3
  61. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_fake_provider.py +8 -1
  62. focus_data_toolkit-0.12.0/tests/test_generated_conformance.py +614 -0
  63. focus_data_toolkit-0.12.0/tests/test_official_validator.py +81 -0
  64. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_parquet.py +4 -3
  65. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_streaming.py +4 -0
  66. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_synthetic_conversion.py +37 -0
  67. focus_data_toolkit-0.11.0/PKG-INFO +0 -519
  68. focus_data_toolkit-0.11.0/README.md +0 -466
  69. focus_data_toolkit-0.11.0/docs/runner.md +0 -129
  70. focus_data_toolkit-0.11.0/docs/studio.md +0 -70
  71. focus_data_toolkit-0.11.0/src/focus_data_toolkit/generators/engine/scenarios_core.py +0 -380
  72. focus_data_toolkit-0.11.0/src/focus_data_toolkit.egg-info/PKG-INFO +0 -519
  73. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows100_seed42.csv +0 -101
  74. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7.csv +0 -26
  75. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -26
  76. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_contract_commitment_rows100_seed42.csv +0 -7
  77. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows100_seed42.csv +0 -101
  78. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7.csv +0 -26
  79. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -26
  80. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows100_seed42.csv +0 -101
  81. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7.csv +0 -26
  82. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -26
  83. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_contract_commitment_rows100_seed42.csv +0 -9
  84. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows100_seed42.csv +0 -101
  85. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7.csv +0 -26
  86. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -26
  87. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows100_seed42.csv +0 -101
  88. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7.csv +0 -26
  89. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -26
  90. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_contract_commitment_rows100_seed42.csv +0 -9
  91. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows100_seed42.csv +0 -101
  92. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7.csv +0 -26
  93. focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -26
  94. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/CONTRIBUTING.md +0 -0
  95. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/LICENSE +0 -0
  96. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/LICENSES/CC-BY-4.0.txt +0 -0
  97. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/MANIFEST.in +0 -0
  98. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/NOTICE +0 -0
  99. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/audit/2026-07-improvement-plan.md +0 -0
  100. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/audit/2026-07-repo-audit.md +0 -0
  101. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/model-provenance.md +0 -0
  102. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/supplements.md +0 -0
  103. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/versioning.md +0 -0
  104. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/schema/model_provenance.schema.json +0 -0
  105. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/check_pinned_actions.py +0 -0
  106. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/generate_resolved_sbom.py +0 -0
  107. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/generate_sbom.py +0 -0
  108. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/verify_model_provenance.py +0 -0
  109. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/verify_release.py +0 -0
  110. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/setup.cfg +0 -0
  111. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/__init__.py +0 -0
  112. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/__main__.py +0 -0
  113. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/cli.py +0 -0
  114. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/context/__init__.py +0 -0
  115. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/context/billing.py +0 -0
  116. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/context/provider.py +0 -0
  117. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/billing_period.py +0 -0
  118. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/contract_commitment.py +0 -0
  119. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/detect.py +0 -0
  120. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/invoice_detail.py +0 -0
  121. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/errors.py +0 -0
  122. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/focus_json.py +0 -0
  123. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/__init__.py +0 -0
  124. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/_shim.py +0 -0
  125. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/__init__.py +0 -0
  126. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/context.py +0 -0
  127. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/generate_aws_focus_1_2.py +0 -0
  128. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/generate_aws_focus_1_3.py +0 -0
  129. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/generate_azure_focus_1_2.py +0 -0
  130. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/generate_azure_focus_1_3.py +0 -0
  131. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/generate_gcp_focus_1_2.py +0 -0
  132. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/generate_gcp_focus_1_3.py +0 -0
  133. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/__init__.py +0 -0
  134. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/profile.py +0 -0
  135. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/__init__.py +0 -0
  136. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/__init__.py +0 -0
  137. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/atomic_writer.py +0 -0
  138. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/csv_io.py +0 -0
  139. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/parquet_io.py +0 -0
  140. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/records.py +0 -0
  141. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/row_source.py +0 -0
  142. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/lifecycle.py +0 -0
  143. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/manifest.py +0 -0
  144. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/__init__.py +0 -0
  145. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/capabilities.py +0 -0
  146. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_1_4_model.json +0 -0
  147. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_1_4_servicesubcategory.json +0 -0
  148. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/iso_4217_currencies.json +0 -0
  149. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schema_check.py +0 -0
  150. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/allocatedmethoddetailsobjectschema.json +0 -0
  151. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/commitmentprogrameligibilitydetailsobjectschema.json +0 -0
  152. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/contractappliedobjectschema.json +0 -0
  153. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/contractcommitmentapplicabilityobjectschema.json +0 -0
  154. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/json_schemas_provenance.json +0 -0
  155. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/model_provenance.json +0 -0
  156. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/validator.py +0 -0
  157. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/modes.py +0 -0
  158. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/official_validator.py +0 -0
  159. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/progress.py +0 -0
  160. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/provenance.py +0 -0
  161. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/py.typed +0 -0
  162. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/runtime.py +0 -0
  163. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/schema/__init__.py +0 -0
  164. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/schema/detection.py +0 -0
  165. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/schema/registry.py +0 -0
  166. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/storage/__init__.py +0 -0
  167. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/storage/external_index.py +0 -0
  168. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/storage/spill.py +0 -0
  169. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/__init__.py +0 -0
  170. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/app.py +0 -0
  171. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/config.py +0 -0
  172. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/frontend/app.js +0 -0
  173. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/frontend/index.html +0 -0
  174. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/frontend/style.css +0 -0
  175. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/jobs.py +0 -0
  176. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/preview.py +0 -0
  177. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/security.py +0 -0
  178. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/server.py +0 -0
  179. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/__init__.py +0 -0
  180. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/__init__.py +0 -0
  181. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/adapters_provenance.json +0 -0
  182. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/aws_invoice_summary.json +0 -0
  183. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/aws_savings_plans.json +0 -0
  184. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/azure_invoice.json +0 -0
  185. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/gcp_compute_commitments.json +0 -0
  186. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/registry.py +0 -0
  187. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/apply.py +0 -0
  188. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/gaps.py +0 -0
  189. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/kinds.py +0 -0
  190. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/loader.py +0 -0
  191. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/spec.py +0 -0
  192. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/validate.py +0 -0
  193. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/__init__.py +0 -0
  194. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/allocation.py +0 -0
  195. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/bundle.py +0 -0
  196. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/corrections.py +0 -0
  197. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/reconciliation.py +0 -0
  198. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/referential.py +0 -0
  199. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/dependency_links.txt +0 -0
  200. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/entry_points.txt +0 -0
  201. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/top_level.txt +0 -0
  202. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/conftest.py +0 -0
  203. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/client_like/SOURCES.md +0 -0
  204. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/client_like/consolidated_multi_provider_1_3.csv +0 -0
  205. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_correction_set.csv +0 -0
  206. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_equal.csv +0 -0
  207. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_negative.csv +0 -0
  208. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_weighted.csv +0 -0
  209. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/official/SOURCES.md +0 -0
  210. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/official/invoice_detail_grain_example.json +0 -0
  211. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/official/numeric_format_examples.json +0 -0
  212. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_adapters_aws.py +0 -0
  213. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_adapters_azure_gcp.py +0 -0
  214. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_atomic_write.py +0 -0
  215. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_billing_lifecycle.py +0 -0
  216. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_bundle_gate.py +0 -0
  217. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_bundle_validation.py +0 -0
  218. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_capabilities.py +0 -0
  219. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_cli.py +0 -0
  220. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_client_like.py +0 -0
  221. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_container.py +0 -0
  222. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_contract_commitment_semantics.py +0 -0
  223. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_convert_roundtrip.py +0 -0
  224. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_corrections.py +0 -0
  225. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_detect.py +0 -0
  226. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_fixtures_are_synthetic.py +0 -0
  227. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_focus_json.py +0 -0
  228. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_gaps.py +0 -0
  229. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_generator_golden.py +0 -0
  230. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_generators.py +0 -0
  231. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_grouping_keys.py +0 -0
  232. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_lifecycle_chains.py +0 -0
  233. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_lineage_counters.py +0 -0
  234. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_lint_focus.py +0 -0
  235. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_manifest.py +0 -0
  236. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_model_provenance.py +0 -0
  237. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_multi_provider.py +0 -0
  238. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_official_json_schemas.py +0 -0
  239. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_packaging.py +0 -0
  240. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_parquet_input.py +0 -0
  241. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_participant_entities.py +0 -0
  242. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_partitioning.py +0 -0
  243. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_progress_cancel.py +0 -0
  244. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_release_tooling.py +0 -0
  245. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_replace_recovery.py +0 -0
  246. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_runtime.py +0 -0
  247. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_schema_detection.py +0 -0
  248. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_single_source_focus_rules.py +0 -0
  249. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_spill.py +0 -0
  250. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_split_allocation.py +0 -0
  251. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_strict_conversion.py +0 -0
  252. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_studio.py +0 -0
  253. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_supplement_apply.py +0 -0
  254. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_supplement_loader.py +0 -0
  255. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_supplement_streaming.py +0 -0
  256. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_validator.py +0 -0
  257. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_workflow_pins.py +0 -0
  258. {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tools/extract_focus_1_4_model.py +0 -0
@@ -9,6 +9,148 @@ policy.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.12.0] — 2026-08-21
13
+
14
+ Back-ports the reviewed FOCUS-Sample-Data conformance fixes into the generator engine.
15
+
16
+ > This release carries a **deliberate reproducibility break**: every golden fixture was
17
+ > regenerated once, so synthetic output bytes differ from `0.11.0` for identical generation
18
+ > parameters. That is a new byte baseline, not a regression — see
19
+ > [docs/versioning.md](docs/versioning.md); pin an exact version if you need byte stability.
20
+
21
+ ### Fixed
22
+
23
+ - **Exact cost arithmetic in the generators.** Costs are now exact products of their
24
+ factors: the unit price (10 dp) and quantity (4 dp) are quantised, never the product,
25
+ so `ListCost == ListUnitPrice × PricingQuantity` and `ContractedCost ==
26
+ ContractedUnitPrice × PricingQuantity` hold under exact `Decimal` equality on every
27
+ priced row (previously ~46% of usage rows carried a ≤ 5e-7 rounding delta). Products
28
+ are display-trimmed to 6 decimals only when lossless. The Parquet decimal registry
29
+ default widens from `(38, 12)` to `(38, 14)` to hold these products exactly.
30
+ - **Three prices kept apart on commitment-covered usage.** `ContractedUnitPrice` now
31
+ carries only the negotiated rate; the commitment discount shows only between
32
+ `ContractedCost` and `EffectiveCost`, giving the strict `EffectiveCost <
33
+ ContractedCost ≤ ListCost` ordering on Used rows (previously the commitment rate was
34
+ folded into the contracted price and `ContractedCost == EffectiveCost`).
35
+ - **FOCUS erratum #3 `ContractApplied` casing.** The 1.3 generators emit the canonical
36
+ `ContractId` / `ContractCommitmentId` element keys of the 1.3.0.1 rule model, and the
37
+ 1.3 parser accepts **both** casings — legacy pre-erratum `ContractID` /
38
+ `ContractCommitmentID` input is normalized and surfaced once per conversion as the
39
+ new catalogued `FDT-CA-001` warning (compatibility is never silent equivalence; mixed
40
+ casings in one element are rejected as ambiguous). `to_json(..., version="1.3")` emits
41
+ the canonical casing. Supersedes the 0.2.0 note that recorded the uppercase casing as
42
+ the fix.
43
+ - **Full 1.2 billing identity on commitment groups.** 1.2 commitment usage rows now copy
44
+ all seven identity keys from the purchase (as 1.3 already did), so each
45
+ `BillingAccountId` maps to exactly one `BillingAccountName` and one `InvoiceId`
46
+ (previously name and invoice diverged within every 1.2 commitment group).
47
+ - **`PricingCurrency` on 1.2 Tax/Credit rows.** Tax and Credit rows carry
48
+ `PricingCurrency` and `PricingCurrencyEffectiveCost` in 1.2 as in 1.3, and
49
+ same-currency pricing columns are exact mirrors instead of re-quantised views.
50
+
51
+ ### Added
52
+
53
+ - **Generated-data conformance suite** (`tests/test_generated_conformance.py`): the
54
+ upstream FOCUS-Sample-Data checker catalogue (24 assertions per provider for 1.2,
55
+ 36-37 for 1.3) ported onto the toolkit's generators and run against both fresh
56
+ generation and the committed golden fixtures — exact `Decimal` equality throughout.
57
+ - **Official-validator CI gate** (`scripts/validate_official_samples.py` + the
58
+ `official-validation` job in `ci.yml`): the nine generated outputs (3 providers ×
59
+ 1.2 Cost and Usage, × 1.3 Cost and Usage + Contract Commitment) are validated by the
60
+ official FinOps `focus-validator`, pinned to `2.2.1`, with
61
+ `--applicability-criteria ALL` so the conditional rules for the capabilities these
62
+ datasets exercise run too, offline via the packaged / SHA-256-pinned release rule
63
+ models. Each `(version, provider, dataset)` run is compared against its **exact**
64
+ expected artifact set — per-rule justifications printed on every run, each claim
65
+ pinned by a data-side conformance test; an unexpected failure *or* a stale
66
+ allowlist entry (an expected artifact that stops failing) breaks the build. The
67
+ 1.3 Contract Commitment dataset validates with zero artifacts.
68
+ - **`scripts/regenerate_golden_fixtures.py`**: replays the exact golden grid of
69
+ `tests/test_generator_golden.py`, replacing the ad-hoc regeneration procedure.
70
+ - **CI: `dependency-review` job** in `.github/workflows/security.yml` (PR-only, informational).
71
+ Now that the repository Dependency Graph is enabled, `actions/dependency-review-action` gates a
72
+ PR's dependency **diff** against the GitHub Advisory database and fails on a HIGH+ vulnerability.
73
+ It complements `pip-audit` (which scans the installed Linux/py3.12 environment every run) by
74
+ covering the full locked graph at PR time — including platform-/version-conditional packages
75
+ (e.g. the Windows-only `tzdata`) that the single-platform install never exercises — ahead of
76
+ Dependabot's post-merge alerts. Least privilege (`contents: read`), guarded to `pull_request`
77
+ events, SHA-pinned; deliberately not a required check.
78
+
79
+ ### Changed
80
+
81
+ - **Commitment discounts are modelled per charge period and reconcile exactly.** Each
82
+ commitment now emits whole per-period blocks: a `Recurring` Purchase row
83
+ (`BilledCost` = the per-period fee, `EffectiveCost` = 0, explicit
84
+ `CommitmentDiscountQuantity`/`Unit` — committed spend in USD or capacity in the
85
+ native unit), Used rows for consumed capacity, and one `Unused` row absorbing the
86
+ use-it-or-lose-it remainder — so `sum(Usage.EffectiveCost) ==
87
+ sum(Purchase.BilledCost)` holds under exact equality per charge period and per
88
+ billing period (previously one One-Time all-upfront purchase with a handful of
89
+ covered hours and no reconciliation). The provider terms are `NoUpfront`
90
+ accordingly (payment-option metadata, SKU names and purchase descriptions — a
91
+ recurring fee contradicts all-upfront). Spend commitments price a **monetary
92
+ block** on their Purchase and Unused rows: `PricingUnit` is the currency, the
93
+ unit prices are exactly 1.00 and the priced quantity is the committed/unused
94
+ spend itself (Used rows keep the consuming resource's native pricing).
95
+ `ContractApplied` is attached to every 1.3 commitment row — Purchase
96
+ (`ContractCommitmentId == ResourceId` per rule `O-039-C`), Used and Unused alike
97
+ — with all five element keys always present (rule `O-007-M`) and exactly one
98
+ metric branch per category: a spend commitment applies a cost alone, a usage
99
+ commitment applies the measured quantity in its native unit alone, so the
100
+ quantity branch survives the 1.4 `oneOf` migration instead of being demoted to
101
+ `x_` custom keys.
102
+ - **The Contract Commitment dataset carries term totals and negotiated terms.** Costs
103
+ and quantities are the 1-year term totals; Spend commitments leave quantity/unit
104
+ empty while Usage commitments carry a real quantity in its native unit; the contract
105
+ period encloses the commitment period by 90 days. Three negotiated non-discount
106
+ terms per provider (minimum spend, negotiated rate card, usage commitment) share one
107
+ multi-commitment `ContractId` and are reachable from Cost and Usage **exclusively**
108
+ through `ContractApplied` — the FOCUS-defined dataset relationship — never via
109
+ `CommitmentDiscountId` equality. On-demand 1.3 usage rows reference them with
110
+ cross-dataset unit coherence: the rate card and minimum spend apply a cost on every
111
+ row (unit-agnostic), while the usage commitment — contracted in Hours — receives
112
+ quantities only from usage of the commitment-eligible compute service, measured in
113
+ that same unit (an element applied to a Usage-category commitment always matches
114
+ its `ContractCommitmentUnit`).
115
+ - **Split Cost Allocation rows are coherent groups.** One shared host charge is fully
116
+ allocated to 2-3 distinct workloads in a single charge period: `AllocatedRatio`
117
+ values sum to exactly 1 and every cost column conserves the host amount exactly
118
+ (quantity shares absorb the residue; each row's costs stay exact unit-price ×
119
+ quantity products). The residue arithmetic is shared with
120
+ `generators/scenarios.py` via the new `generators/engine/allocation_math` module.
121
+ - **Docs: `docs/runner.md` rewritten as a step-by-step guide, and several claims corrected** (no
122
+ runtime code changed). The page now walks a newcomer from `docker pull` to a validated FOCUS 1.4
123
+ output — prerequisites, a mount-free first run, directory setup, a six-step walkthrough with the
124
+ real command output, Podman, GitHub Actions / Kubernetes / cron examples, and a troubleshooting
125
+ table — before the reference sections. Corrections to previously published statements: the disk
126
+ budgets apply to the **streaming path only** (the eager CSV conversion ignores them and cannot
127
+ exit 5); the exit-code table is the **`convert`** contract, not a global one; the image ships the
128
+ `[parquet]` extra only, so `validate --official` and `ui` are unavailable in the container; the
129
+ image's CycloneDX SBOM is retained as a **workflow artifact**, not attached to the release or
130
+ pushed to the registry; trivy fails on HIGH/CRITICAL findings **that have a fix available**
131
+ (`ignore-unfixed: true`). Also documents `FOCUS_TOOLKIT_LOG_LEVEL` (with its current lack of
132
+ effect in the Runner), the `_run.json` sidecar, `focus-toolkit clean` after a hard kill, and the
133
+ `TMPDIR` vs `FOCUS_TOOLKIT_WORK_DIR` distinction for `validate-bundle`. Two further scope
134
+ corrections: the cooperative cancel (SIGTERM → exit 130, nothing published) is a property of the
135
+ **streaming** path — the eager CSV conversion installs no signal handler, so `docker stop` there
136
+ terminates it mid-flight (exit 143) and can leave staging behind; and `clean` takes only `--out`,
137
+ so it does **not** sweep `fdt-<run_id>` scratch orphaned under `FOCUS_TOOLKIT_WORK_DIR` by a
138
+ killed streaming run. `docs/compatibility.md` now points Windows readers to the Runner for the
139
+ streaming path.
140
+ - **CI (risk-based audit follow-up; no runtime code changed).** The container SIGTERM smoke
141
+ is now deterministic: it waits for the conversion to observably start before stopping, and
142
+ a run that finishes before the signal lands **fails** as inconclusive instead of passing
143
+ silently. The bounded-memory streaming test (`-m slow`, ~10 min under tracemalloc) now runs
144
+ automatically in a new `scale.yml` — on streaming-engine PRs, every push to main, and on
145
+ demand — instead of never. `reproducibility.yml` uses the exact locked build procedure of
146
+ `release-build.yml` (hash-pinned backend, `--no-isolation`). `release-dry-run.yml` also
147
+ triggers on PRs touching release-relevant paths (informational). One macOS combo
148
+ (`macos-15`/3.13) joins the test matrix. pip-audit now also audits the `[validator]` extra,
149
+ and the `package` job smoke-installs `[all]`. All jobs set `timeout-minutes`; the container
150
+ workflow gained `concurrency` cancellation; `actions/checkout` and `astral-sh/setup-uv`
151
+ pins are converged to one version repo-wide. Coverage floor raised 80 → 85. New unit tests
152
+ cover the `official_validator` subprocess wrapper.
153
+
12
154
  ## [0.11.0] — 2026-07-18
13
155
 
14
156
  First **stable** release. Same feature set as `0.11.0rc1`, promoted to a final release now that the
@@ -552,7 +694,8 @@ conformance defects.
552
694
 
553
695
  <!-- Reference links. 0.2.0/0.3.0 were pre-release development milestones and were never tagged
554
696
  or published, so only the first public release (0.9.0) has a tag link. -->
555
- [Unreleased]: https://github.com/guymano/focus-data-toolkit/compare/v0.11.0...HEAD
697
+ [Unreleased]: https://github.com/guymano/focus-data-toolkit/compare/v0.12.0...HEAD
698
+ [0.12.0]: https://github.com/guymano/focus-data-toolkit/compare/v0.11.0...v0.12.0
556
699
  [0.11.0]: https://github.com/guymano/focus-data-toolkit/compare/v0.11.0rc1...v0.11.0
557
700
  [0.11.0rc1]: https://github.com/guymano/focus-data-toolkit/compare/v0.9.0...v0.11.0rc1
558
701
  [0.9.0]: https://github.com/guymano/focus-data-toolkit/releases/tag/v0.9.0
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: focus-data-toolkit
3
+ Version: 0.12.0
4
+ Summary: Generate provider-realistic FOCUS 1.2/1.3 sample data (AWS, Azure, GCP) and convert it to the four FOCUS 1.4 datasets — strictly from source facts, completed by client supplements including native AWS/Azure/GCP exports — with schema detection, gap analysis, a cross-dataset validation gate and atomic writes.
5
+ Author: Guy-Hermann Adiko
6
+ License-Expression: MIT AND CC-BY-4.0
7
+ Project-URL: Homepage, https://github.com/guymano/focus-data-toolkit
8
+ Project-URL: Source, https://github.com/guymano/focus-data-toolkit
9
+ Project-URL: Issues, https://github.com/guymano/focus-data-toolkit/issues
10
+ Project-URL: Changelog, https://github.com/guymano/focus-data-toolkit/blob/main/CHANGELOG.md
11
+ Project-URL: Documentation, https://github.com/guymano/focus-data-toolkit#readme
12
+ Keywords: finops,focus,billing,cost,sample-data
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Office/Business :: Financial
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ License-File: NOTICE
25
+ License-File: LICENSES/CC-BY-4.0.txt
26
+ Provides-Extra: parquet
27
+ Requires-Dist: pyarrow<26,>=15; extra == "parquet"
28
+ Requires-Dist: tzdata>=2024.1; sys_platform == "win32" and extra == "parquet"
29
+ Provides-Extra: scale
30
+ Requires-Dist: focus-data-toolkit[parquet]; extra == "scale"
31
+ Provides-Extra: validator
32
+ Requires-Dist: focus-validator<3,>=2.1; python_version >= "3.12" and extra == "validator"
33
+ Provides-Extra: studio
34
+ Requires-Dist: fastapi<1,>=0.110; extra == "studio"
35
+ Requires-Dist: uvicorn[standard]<1,>=0.29; extra == "studio"
36
+ Requires-Dist: python-multipart<1,>=0.0.9; extra == "studio"
37
+ Provides-Extra: studio-all
38
+ Requires-Dist: focus-data-toolkit[parquet,studio]; extra == "studio-all"
39
+ Provides-Extra: all
40
+ Requires-Dist: focus-data-toolkit[parquet,studio,validator]; extra == "all"
41
+ Provides-Extra: release
42
+ Requires-Dist: build<2,>=1; extra == "release"
43
+ Requires-Dist: twine<8,>=5; extra == "release"
44
+ Provides-Extra: dev
45
+ Requires-Dist: focus-data-toolkit[parquet,release,studio]; extra == "dev"
46
+ Requires-Dist: pytest>=8; extra == "dev"
47
+ Requires-Dist: pytest-cov<8,>=5; extra == "dev"
48
+ Requires-Dist: ruff>=0.6; extra == "dev"
49
+ Requires-Dist: mypy<2.4,>=1.11; extra == "dev"
50
+ Requires-Dist: jsonschema<5,>=4.21; extra == "dev"
51
+ Requires-Dist: httpx<1,>=0.27; extra == "dev"
52
+ Dynamic: license-file
53
+
54
+ # focus-data-toolkit
55
+
56
+ Generate realistic **FOCUS 1.2 / 1.3** cost & usage data (AWS, Azure, GCP), convert it to
57
+ **FOCUS 1.4**, and validate the result — from a single dependency-free Python core, usable three ways.
58
+
59
+ [![PyPI](https://img.shields.io/pypi/v/focus-data-toolkit)](https://pypi.org/project/focus-data-toolkit/)
60
+ [![Python](https://img.shields.io/pypi/pyversions/focus-data-toolkit)](https://pypi.org/project/focus-data-toolkit/)
61
+ [![License: MIT AND CC-BY-4.0](https://img.shields.io/badge/license-MIT%20AND%20CC--BY--4.0-blue)](LICENSE)
62
+ [![CI](https://github.com/guymano/focus-data-toolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/guymano/focus-data-toolkit/actions/workflows/ci.yml)
63
+
64
+ [FOCUS](https://focus.finops.org) is the open standard for cloud cost & usage data. FOCUS 1.4 defines
65
+ four datasets: **Cost and Usage**, **Contract Commitment**, **Billing Period** and **Invoice Detail**.
66
+ This toolkit migrates real data, synthesizes sample data, and checks structure — honestly: a
67
+ structurally valid file is not automatically FOCUS-conformant, and any value it cannot derive from
68
+ the source is either left empty (**strict** mode) or filled only as a clearly-labelled assumption
69
+ (**synthetic** mode).
70
+
71
+ ## Three ways to use it
72
+
73
+ | Interface | Best for | Get started |
74
+ |---|---|---|
75
+ | **Studio** | FinOps users who prefer a UI | `pip install "focus-data-toolkit[studio]"` → `focus-toolkit ui` |
76
+ | **Runner** | Automation & large volumes | `docker run --rm ghcr.io/guymano/focus-data-toolkit:0.12.0 version` |
77
+ | **CLI & SDK** | Engineers & scripts | `pip install focus-data-toolkit` → `focus-toolkit --help` |
78
+
79
+ All three drive the **same core**, so their outputs are byte-for-byte identical — same datasets,
80
+ manifest, diagnostics and `SHA256SUMS`. No FOCUS logic is duplicated across interfaces.
81
+
82
+ - **Studio** — a local, single-user web app: pick or upload a file (or generate one), detect it,
83
+ convert with live progress, preview a sampled page, and download the results. Binds `127.0.0.1`,
84
+ token-guarded; no data leaves your machine. → [docs/studio.md](docs/studio.md)
85
+ - **Runner** — a non-root OCI image whose entrypoint *is* the CLI, for batch jobs and CI.
86
+ → [docs/runner.md](docs/runner.md)
87
+ - **CLI & SDK** — the `focus-toolkit` command and the importable Python API.
88
+
89
+ ## Install
90
+
91
+ ```bash
92
+ pip install focus-data-toolkit
93
+ ```
94
+
95
+ The core is **standard-library only** (Python ≥ 3.11). Optional features live behind extras:
96
+
97
+ | Extra | Adds |
98
+ |---|---|
99
+ | `parquet` | Parquet I/O — columnar, decimal128, Hive partitioning (alias: `scale`) |
100
+ | `studio` | the local web UI (`focus-toolkit ui`) |
101
+ | `studio-all` | `studio` + `parquet` |
102
+ | `validator` | the official FinOps validator (`--official`; needs Python ≥ 3.12) |
103
+ | `all` | `parquet` + `validator` + `studio` |
104
+
105
+ ```bash
106
+ pipx install focus-data-toolkit # isolated CLI
107
+ uv tool install focus-data-toolkit # or with uv
108
+ docker pull ghcr.io/guymano/focus-data-toolkit:0.12.0 # container (Runner)
109
+ ```
110
+
111
+ ## Quickstart
112
+
113
+ ```bash
114
+ # 1. Generate provider-realistic sample data (aws|azure|gcp, FOCUS 1.2|1.3)
115
+ focus-toolkit generate --provider aws --focus-version 1.3 --rows 1000 --out ./out
116
+
117
+ # 2. Convert a 1.2/1.3 Cost & Usage file to FOCUS 1.4 (source version auto-detected)
118
+ focus-toolkit convert --cost-and-usage out/focus_1_3_cost_and_usage_aws.csv --out ./focus-1.4
119
+ # strict conversion of this sample exits 3 (three datasets NOT_PRODUCED, by design) —
120
+ # add --exit-policy pipeline in `set -e` / CI scripts. Use --mode synthetic to also emit
121
+ # the other three datasets (clearly labelled), or --stream --output-format parquet for large files.
122
+
123
+ # 3. Open the Studio web app (needs the studio extra: pip install "focus-data-toolkit[studio]")
124
+ focus-toolkit ui
125
+ ```
126
+
127
+ Use it as a library — the same engine the CLI, Studio and Runner run:
128
+
129
+ ```python
130
+ from focus_data_toolkit import convert_files
131
+
132
+ convert_files("cost_and_usage.csv", "focus-1.4", mode="strict")
133
+ ```
134
+
135
+ ## What it does — and what it doesn't
136
+
137
+ - **Converts** FOCUS **1.2 / 1.3 Cost & Usage → the four FOCUS 1.4 datasets.** `strict` mode emits
138
+ only what is genuinely derivable from the source; `synthetic` mode also fills the
139
+ provider-billing datasets with clearly-labelled assumed data for demos and tests.
140
+ - **Honest by design.** The source is Cost & Usage only, so Contract Commitment / Billing Period /
141
+ Invoice Detail come **only** from client [supplements](docs/supplements.md) or synthetic mode.
142
+ There is **no FOCUS 1.4 generator** and **no invalid-data generator**. Generation is in-memory
143
+ (use the CLI/Runner for very large synthetic sets), and the engine is **single-node**.
144
+ - **Built for real data.** Bounded-memory streaming conversion, atomic/journaled writes, a
145
+ deterministic manifest with per-column lineage, structured `FDT-*` diagnostics, and a
146
+ cross-dataset validation gate.
147
+
148
+ See [docs/compatibility.md](docs/compatibility.md) for the Python / OS / FOCUS support matrix.
149
+
150
+ ## Exit codes
151
+
152
+ `convert` returns meaningful codes so pipelines can branch on the outcome:
153
+
154
+ | Code | Meaning |
155
+ |---|---|
156
+ | `0` | success, no assumptions |
157
+ | `1` | lint / validation / write failure |
158
+ | `2` | invalid input or arguments |
159
+ | `3` | strict result intentionally incomplete |
160
+ | `4` | synthetic result contains assumptions |
161
+ | `5` | disk space / budget exhausted (streaming / Runner) |
162
+ | `130` | cancelled (Ctrl-C / SIGTERM) — nothing partial is published |
163
+
164
+ Code `5` is raised on the bounded-memory path (`--stream`, Parquet, partitioning, or the Runner); the
165
+ eager default CSV conversion reports a disk-full as a write failure (`1`). Add `--exit-policy pipeline`
166
+ to treat the functional-but-incomplete outcomes (`3`, `4`) as `0`.
167
+
168
+ ## Trust & provenance
169
+
170
+ - **Deterministic:** the same input always produces the same output bytes (no clock, no RNG); the
171
+ datasets and manifest are listed in `SHA256SUMS` (the non-deterministic `_run.json` run sidecar is
172
+ intentionally excluded).
173
+ - **Verifiable model:** the embedded FOCUS 1.4 model is extracted from the FinOps source workbook and
174
+ its provenance is **complete** — hash-pinned and reproduced byte-for-byte.
175
+ See [docs/model-provenance.md](docs/model-provenance.md).
176
+ - **Supply chain:** releases are built reproducibly, signed (Sigstore/cosign), and ship an SBOM and
177
+ build attestations. See [docs/releasing.md](docs/releasing.md).
178
+ - **Private:** the core does **no network I/O**, has **no telemetry**, and needs **no credentials**.
179
+ See [docs/security-model.md](docs/security-model.md).
180
+
181
+ ## Documentation
182
+
183
+ [Studio](docs/studio.md) · [Runner](docs/runner.md) · [Supplements & gaps](docs/supplements.md) ·
184
+ [Compatibility](docs/compatibility.md) · [Versioning](docs/versioning.md) ·
185
+ [Security model](docs/security-model.md) · [Model provenance](docs/model-provenance.md) ·
186
+ [Releasing](docs/releasing.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) ·
187
+ [Security policy](SECURITY.md)
188
+
189
+ ## License & credits
190
+
191
+ Code is **MIT** (see [LICENSE](LICENSE)). The embedded FOCUS 1.4 data model is a derivative of the
192
+ FinOps FOCUS data-model workbook, © the FinOps Foundation and licensed **CC-BY-4.0**, redistributed
193
+ with attribution (see [NOTICE](NOTICE)). "FOCUS" and "FinOps" are trademarks of the FinOps
194
+ Foundation; this is an independent community project, not endorsed by the FinOps Foundation. Related
195
+ sample datasets were contributed upstream to
196
+ [FOCUS-Sample-Data](https://github.com/FinOps-Open-Cost-and-Usage-Spec/FOCUS-Sample-Data).
@@ -0,0 +1,143 @@
1
+ # focus-data-toolkit
2
+
3
+ Generate realistic **FOCUS 1.2 / 1.3** cost & usage data (AWS, Azure, GCP), convert it to
4
+ **FOCUS 1.4**, and validate the result — from a single dependency-free Python core, usable three ways.
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/focus-data-toolkit)](https://pypi.org/project/focus-data-toolkit/)
7
+ [![Python](https://img.shields.io/pypi/pyversions/focus-data-toolkit)](https://pypi.org/project/focus-data-toolkit/)
8
+ [![License: MIT AND CC-BY-4.0](https://img.shields.io/badge/license-MIT%20AND%20CC--BY--4.0-blue)](LICENSE)
9
+ [![CI](https://github.com/guymano/focus-data-toolkit/actions/workflows/ci.yml/badge.svg)](https://github.com/guymano/focus-data-toolkit/actions/workflows/ci.yml)
10
+
11
+ [FOCUS](https://focus.finops.org) is the open standard for cloud cost & usage data. FOCUS 1.4 defines
12
+ four datasets: **Cost and Usage**, **Contract Commitment**, **Billing Period** and **Invoice Detail**.
13
+ This toolkit migrates real data, synthesizes sample data, and checks structure — honestly: a
14
+ structurally valid file is not automatically FOCUS-conformant, and any value it cannot derive from
15
+ the source is either left empty (**strict** mode) or filled only as a clearly-labelled assumption
16
+ (**synthetic** mode).
17
+
18
+ ## Three ways to use it
19
+
20
+ | Interface | Best for | Get started |
21
+ |---|---|---|
22
+ | **Studio** | FinOps users who prefer a UI | `pip install "focus-data-toolkit[studio]"` → `focus-toolkit ui` |
23
+ | **Runner** | Automation & large volumes | `docker run --rm ghcr.io/guymano/focus-data-toolkit:0.12.0 version` |
24
+ | **CLI & SDK** | Engineers & scripts | `pip install focus-data-toolkit` → `focus-toolkit --help` |
25
+
26
+ All three drive the **same core**, so their outputs are byte-for-byte identical — same datasets,
27
+ manifest, diagnostics and `SHA256SUMS`. No FOCUS logic is duplicated across interfaces.
28
+
29
+ - **Studio** — a local, single-user web app: pick or upload a file (or generate one), detect it,
30
+ convert with live progress, preview a sampled page, and download the results. Binds `127.0.0.1`,
31
+ token-guarded; no data leaves your machine. → [docs/studio.md](docs/studio.md)
32
+ - **Runner** — a non-root OCI image whose entrypoint *is* the CLI, for batch jobs and CI.
33
+ → [docs/runner.md](docs/runner.md)
34
+ - **CLI & SDK** — the `focus-toolkit` command and the importable Python API.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install focus-data-toolkit
40
+ ```
41
+
42
+ The core is **standard-library only** (Python ≥ 3.11). Optional features live behind extras:
43
+
44
+ | Extra | Adds |
45
+ |---|---|
46
+ | `parquet` | Parquet I/O — columnar, decimal128, Hive partitioning (alias: `scale`) |
47
+ | `studio` | the local web UI (`focus-toolkit ui`) |
48
+ | `studio-all` | `studio` + `parquet` |
49
+ | `validator` | the official FinOps validator (`--official`; needs Python ≥ 3.12) |
50
+ | `all` | `parquet` + `validator` + `studio` |
51
+
52
+ ```bash
53
+ pipx install focus-data-toolkit # isolated CLI
54
+ uv tool install focus-data-toolkit # or with uv
55
+ docker pull ghcr.io/guymano/focus-data-toolkit:0.12.0 # container (Runner)
56
+ ```
57
+
58
+ ## Quickstart
59
+
60
+ ```bash
61
+ # 1. Generate provider-realistic sample data (aws|azure|gcp, FOCUS 1.2|1.3)
62
+ focus-toolkit generate --provider aws --focus-version 1.3 --rows 1000 --out ./out
63
+
64
+ # 2. Convert a 1.2/1.3 Cost & Usage file to FOCUS 1.4 (source version auto-detected)
65
+ focus-toolkit convert --cost-and-usage out/focus_1_3_cost_and_usage_aws.csv --out ./focus-1.4
66
+ # strict conversion of this sample exits 3 (three datasets NOT_PRODUCED, by design) —
67
+ # add --exit-policy pipeline in `set -e` / CI scripts. Use --mode synthetic to also emit
68
+ # the other three datasets (clearly labelled), or --stream --output-format parquet for large files.
69
+
70
+ # 3. Open the Studio web app (needs the studio extra: pip install "focus-data-toolkit[studio]")
71
+ focus-toolkit ui
72
+ ```
73
+
74
+ Use it as a library — the same engine the CLI, Studio and Runner run:
75
+
76
+ ```python
77
+ from focus_data_toolkit import convert_files
78
+
79
+ convert_files("cost_and_usage.csv", "focus-1.4", mode="strict")
80
+ ```
81
+
82
+ ## What it does — and what it doesn't
83
+
84
+ - **Converts** FOCUS **1.2 / 1.3 Cost & Usage → the four FOCUS 1.4 datasets.** `strict` mode emits
85
+ only what is genuinely derivable from the source; `synthetic` mode also fills the
86
+ provider-billing datasets with clearly-labelled assumed data for demos and tests.
87
+ - **Honest by design.** The source is Cost & Usage only, so Contract Commitment / Billing Period /
88
+ Invoice Detail come **only** from client [supplements](docs/supplements.md) or synthetic mode.
89
+ There is **no FOCUS 1.4 generator** and **no invalid-data generator**. Generation is in-memory
90
+ (use the CLI/Runner for very large synthetic sets), and the engine is **single-node**.
91
+ - **Built for real data.** Bounded-memory streaming conversion, atomic/journaled writes, a
92
+ deterministic manifest with per-column lineage, structured `FDT-*` diagnostics, and a
93
+ cross-dataset validation gate.
94
+
95
+ See [docs/compatibility.md](docs/compatibility.md) for the Python / OS / FOCUS support matrix.
96
+
97
+ ## Exit codes
98
+
99
+ `convert` returns meaningful codes so pipelines can branch on the outcome:
100
+
101
+ | Code | Meaning |
102
+ |---|---|
103
+ | `0` | success, no assumptions |
104
+ | `1` | lint / validation / write failure |
105
+ | `2` | invalid input or arguments |
106
+ | `3` | strict result intentionally incomplete |
107
+ | `4` | synthetic result contains assumptions |
108
+ | `5` | disk space / budget exhausted (streaming / Runner) |
109
+ | `130` | cancelled (Ctrl-C / SIGTERM) — nothing partial is published |
110
+
111
+ Code `5` is raised on the bounded-memory path (`--stream`, Parquet, partitioning, or the Runner); the
112
+ eager default CSV conversion reports a disk-full as a write failure (`1`). Add `--exit-policy pipeline`
113
+ to treat the functional-but-incomplete outcomes (`3`, `4`) as `0`.
114
+
115
+ ## Trust & provenance
116
+
117
+ - **Deterministic:** the same input always produces the same output bytes (no clock, no RNG); the
118
+ datasets and manifest are listed in `SHA256SUMS` (the non-deterministic `_run.json` run sidecar is
119
+ intentionally excluded).
120
+ - **Verifiable model:** the embedded FOCUS 1.4 model is extracted from the FinOps source workbook and
121
+ its provenance is **complete** — hash-pinned and reproduced byte-for-byte.
122
+ See [docs/model-provenance.md](docs/model-provenance.md).
123
+ - **Supply chain:** releases are built reproducibly, signed (Sigstore/cosign), and ship an SBOM and
124
+ build attestations. See [docs/releasing.md](docs/releasing.md).
125
+ - **Private:** the core does **no network I/O**, has **no telemetry**, and needs **no credentials**.
126
+ See [docs/security-model.md](docs/security-model.md).
127
+
128
+ ## Documentation
129
+
130
+ [Studio](docs/studio.md) · [Runner](docs/runner.md) · [Supplements & gaps](docs/supplements.md) ·
131
+ [Compatibility](docs/compatibility.md) · [Versioning](docs/versioning.md) ·
132
+ [Security model](docs/security-model.md) · [Model provenance](docs/model-provenance.md) ·
133
+ [Releasing](docs/releasing.md) · [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) ·
134
+ [Security policy](SECURITY.md)
135
+
136
+ ## License & credits
137
+
138
+ Code is **MIT** (see [LICENSE](LICENSE)). The embedded FOCUS 1.4 data model is a derivative of the
139
+ FinOps FOCUS data-model workbook, © the FinOps Foundation and licensed **CC-BY-4.0**, redistributed
140
+ with attribution (see [NOTICE](NOTICE)). "FOCUS" and "FinOps" are trademarks of the FinOps
141
+ Foundation; this is an independent community project, not endorsed by the FinOps Foundation. Related
142
+ sample datasets were contributed upstream to
143
+ [FOCUS-Sample-Data](https://github.com/FinOps-Open-Cost-and-Usage-Spec/FOCUS-Sample-Data).
@@ -87,8 +87,8 @@ fixes; there are no long-term-support branches yet.
87
87
 
88
88
  | Version | Supported |
89
89
  | ---------- | ----------------------------------------------------------- |
90
- | `0.11.x` | ✅ Current line — security fixes land here. |
91
- | `< 0.11.0` | ❌ Superseded pre-1.0 releases / snapshots; upgrade to 0.11.x. |
90
+ | `0.12.x` | ✅ Current line — security fixes land here. |
91
+ | `< 0.12.0` | ❌ Superseded pre-1.0 releases / snapshots; upgrade to 0.12.x. |
92
92
 
93
93
  When `1.0.0` is released, this table will be updated with the then-current
94
94
  support policy.
@@ -48,6 +48,10 @@ This is tracked as a "Windows streaming / atomic-write hardening" follow-up. On
48
48
  Windows, use the eager conversion path; on Linux/macOS, `--stream` gives flat
49
49
  memory regardless of row count.
50
50
 
51
+ Windows users who need the streaming path can run it through the container
52
+ instead: the Runner image is Linux, so `--stream`, Parquet and partitioned
53
+ output work there from a Windows host. See [docs/runner.md](runner.md).
54
+
51
55
  > Note: because streaming is unsupported on Windows, the POSIX-only path-traversal
52
56
  > guard it relies on is not a Windows security control. See
53
57
  > [docs/security-model.md](security-model.md).
@@ -9,7 +9,7 @@ The release **pipeline** lives in `.github/workflows/`:
9
9
  | Workflow | Trigger | What it does |
10
10
  | --- | --- | --- |
11
11
  | `release-build.yml` | `workflow_call` (reusable) | Build wheel+sdist **once** (locked toolchain, `SOURCE_DATE_EPOCH` = commit date), test them, generate the CycloneDX SBOM + `SHA256SUMS` + a build manifest, run `verify_release.py`, upload the artifacts. No publish scopes. |
12
- | `release-dry-run.yml` | `workflow_dispatch` | Calls `release-build` and re-verifies — **no** id-token, **no** environment, publishes nothing. Rehearse on any branch. |
12
+ | `release-dry-run.yml` | `workflow_dispatch` + `pull_request` on release-relevant paths | Calls `release-build` and re-verifies — **no** id-token, **no** environment, publishes nothing. Rehearse on any branch; also runs automatically on PRs touching the release workflows, `constraints/`, the release scripts, packaging metadata or the version/CHANGELOG, so a broken release chain is caught at PR time (informational — deliberately not a required check, because a paths-filtered required check would block unrelated PRs). |
13
13
  | `release.yml` | push tag `v*` | Calls `release-build`, then **attests** wheel/sdist/SBOM/checksums (GitHub Artifact Attestations, keyless OIDC) and **publishes** to PyPI via Trusted Publishing in the `pypi` environment. The same artifacts flow by digest — nothing is rebuilt to publish. |
14
14
  | `reproducibility.yml` | `workflow_dispatch` | Double-builds and compares (see Reproducibility below). |
15
15
 
@@ -50,16 +50,27 @@ these are in place:
50
50
  and dependency-review can run (see below).
51
51
  - [ ] **CODEOWNERS** confirmed and "require review from Code Owners" turned on.
52
52
 
53
+ Pre-release manual steps (each release, owner-run):
54
+
55
+ - [ ] Run `reproducibility.yml` (workflow_dispatch) on the release commit and check the
56
+ wheel double-build gate is green — it uses the exact locked build procedure of
57
+ `release-build.yml`, so a green run vouches for the artifact the release will ship.
58
+ - [ ] Studio browser smoke (the JS frontend has no automated browser test — a deliberate
59
+ trade-off, see `tests/test_studio.py` for the API-level coverage): `focus-toolkit ui`,
60
+ open the printed tokenized URL, select a file, run a conversion, watch progress,
61
+ download the result.
62
+
53
63
  ## The security workflows
54
64
 
55
65
  `.github/workflows/codeql.yml` and `scorecard.yml` run on `push` / `pull_request`
56
66
  / `schedule` (plus `branch_protection_rule` for Scorecard). They upload results
57
67
  to GitHub **code scanning**, which must be enabled in the repository settings
58
68
  (Operationally Ready checklist above) for the uploads to land.
59
- `actions/dependency-review-action` similarly needs the **Dependency Graph**; a
60
- `dependency-review` job can be added to `security.yml` once it is enabled
61
- (dependency CVEs are already covered by `pip-audit` + Dependabot in the
62
- meantime).
69
+ `.github/workflows/security.yml` also runs a **`dependency-review`** job (pull
70
+ requests only) that gates a PR's dependency **diff** against the GitHub Advisory
71
+ database; it needs the repository **Dependency Graph** (now enabled). It
72
+ complements `pip-audit` (which scans the installed environment) and Dependabot
73
+ (post-merge alerts).
63
74
 
64
75
  ## Cutting a release
65
76