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.
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/CHANGELOG.md +144 -1
- focus_data_toolkit-0.12.0/PKG-INFO +196 -0
- focus_data_toolkit-0.12.0/README.md +143 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/SECURITY.md +2 -2
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/compatibility.md +4 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/releasing.md +16 -5
- focus_data_toolkit-0.12.0/docs/runner.md +554 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/security-model.md +10 -8
- focus_data_toolkit-0.12.0/docs/studio.md +196 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/pyproject.toml +4 -2
- focus_data_toolkit-0.12.0/scripts/regenerate_golden_fixtures.py +89 -0
- focus_data_toolkit-0.12.0/scripts/validate_official_samples.py +353 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/_version.py +1 -1
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/__init__.py +8 -1
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/contract_applied.py +64 -21
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/cost_and_usage.py +40 -7
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/streaming.py +6 -1
- focus_data_toolkit-0.12.0/src/focus_data_toolkit/generators/engine/allocation_math.py +54 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/determinism.py +43 -1
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/json_focus.py +34 -20
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/ladder.py +1 -1
- focus_data_toolkit-0.12.0/src/focus_data_toolkit/generators/engine/scenarios_core.py +502 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/serialize.py +80 -8
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/aws.py +4 -3
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/azure.py +4 -3
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/gcp.py +1 -1
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/scenarios.py +5 -17
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/adapter.py +8 -4
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/v1_2.py +20 -12
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/v1_3.py +58 -18
- {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
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_json_keys.py +8 -2
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/codes.py +4 -0
- focus_data_toolkit-0.12.0/src/focus_data_toolkit.egg-info/PKG-INFO +196 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/SOURCES.txt +5 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/requires.txt +1 -1
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/README.md +6 -2
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows100_seed42.csv +101 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7_credits.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_contract_commitment_rows100_seed42.csv +11 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows100_seed42.csv +101 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7_credits.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows100_seed42.csv +101 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7_credits.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_contract_commitment_rows100_seed42.csv +11 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows100_seed42.csv +101 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7_credits.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows100_seed42.csv +101 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7_credits.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_contract_commitment_rows100_seed42.csv +12 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows100_seed42.csv +101 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7.csv +26 -0
- focus_data_toolkit-0.12.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7_credits.csv +26 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_contract_applied.py +62 -3
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_cross_provider.py +22 -3
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_fake_provider.py +8 -1
- focus_data_toolkit-0.12.0/tests/test_generated_conformance.py +614 -0
- focus_data_toolkit-0.12.0/tests/test_official_validator.py +81 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_parquet.py +4 -3
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_streaming.py +4 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_synthetic_conversion.py +37 -0
- focus_data_toolkit-0.11.0/PKG-INFO +0 -519
- focus_data_toolkit-0.11.0/README.md +0 -466
- focus_data_toolkit-0.11.0/docs/runner.md +0 -129
- focus_data_toolkit-0.11.0/docs/studio.md +0 -70
- focus_data_toolkit-0.11.0/src/focus_data_toolkit/generators/engine/scenarios_core.py +0 -380
- focus_data_toolkit-0.11.0/src/focus_data_toolkit.egg-info/PKG-INFO +0 -519
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows100_seed42.csv +0 -101
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_contract_commitment_rows100_seed42.csv +0 -7
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows100_seed42.csv +0 -101
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/aws_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows100_seed42.csv +0 -101
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_contract_commitment_rows100_seed42.csv +0 -9
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows100_seed42.csv +0 -101
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/azure_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows100_seed42.csv +0 -101
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_2_cost_and_usage_rows25_seed7_credits.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_contract_commitment_rows100_seed42.csv +0 -9
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows100_seed42.csv +0 -101
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7.csv +0 -26
- focus_data_toolkit-0.11.0/tests/fixtures/golden/compatibility_golden/gcp_1_3_cost_and_usage_rows25_seed7_credits.csv +0 -26
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/CONTRIBUTING.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/LICENSE +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/MANIFEST.in +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/NOTICE +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/audit/2026-07-improvement-plan.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/audit/2026-07-repo-audit.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/model-provenance.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/supplements.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/docs/versioning.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/schema/model_provenance.schema.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/check_pinned_actions.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/generate_resolved_sbom.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/generate_sbom.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/verify_model_provenance.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/scripts/verify_release.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/setup.cfg +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/__main__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/cli.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/context/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/context/billing.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/context/provider.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/billing_period.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/contract_commitment.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/detect.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/convert/invoice_detail.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/errors.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/focus_json.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/_shim.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/engine/context.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/providers/profile.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/generators/versions/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/atomic_writer.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/csv_io.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/parquet_io.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/records.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/io/row_source.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/lifecycle.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/manifest.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/capabilities.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_1_4_model.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/focus_1_4_servicesubcategory.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/iso_4217_currencies.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schema_check.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/allocatedmethoddetailsobjectschema.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/commitmentprogrameligibilitydetailsobjectschema.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/contractappliedobjectschema.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/json_schemas/contractcommitmentapplicabilityobjectschema.json +0 -0
- {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
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/model_provenance.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/model/validator.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/modes.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/official_validator.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/progress.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/provenance.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/py.typed +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/runtime.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/schema/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/schema/detection.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/schema/registry.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/storage/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/storage/external_index.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/storage/spill.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/app.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/config.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/frontend/app.js +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/frontend/index.html +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/frontend/style.css +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/jobs.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/preview.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/security.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/studio/server.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/adapters_provenance.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/aws_invoice_summary.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/aws_savings_plans.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/azure_invoice.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/gcp_compute_commitments.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/adapters/registry.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/apply.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/gaps.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/kinds.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/loader.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/spec.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/supplement/validate.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/__init__.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/allocation.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/bundle.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/corrections.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/reconciliation.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit/validate/referential.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/dependency_links.txt +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/entry_points.txt +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/src/focus_data_toolkit.egg-info/top_level.txt +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/conftest.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/client_like/SOURCES.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/client_like/consolidated_multi_provider_1_3.csv +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_correction_set.csv +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_equal.csv +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_negative.csv +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/golden/compatibility_golden/scenarios_sca_weighted.csv +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/official/SOURCES.md +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/official/invoice_detail_grain_example.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/fixtures/official/numeric_format_examples.json +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_adapters_aws.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_adapters_azure_gcp.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_atomic_write.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_billing_lifecycle.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_bundle_gate.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_bundle_validation.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_capabilities.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_cli.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_client_like.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_container.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_contract_commitment_semantics.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_convert_roundtrip.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_corrections.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_detect.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_fixtures_are_synthetic.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_focus_json.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_gaps.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_generator_golden.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_generators.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_grouping_keys.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_lifecycle_chains.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_lineage_counters.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_lint_focus.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_manifest.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_model_provenance.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_multi_provider.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_official_json_schemas.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_packaging.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_parquet_input.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_participant_entities.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_partitioning.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_progress_cancel.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_release_tooling.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_replace_recovery.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_runtime.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_schema_detection.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_single_source_focus_rules.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_spill.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_split_allocation.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_strict_conversion.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_studio.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_supplement_apply.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_supplement_loader.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_supplement_streaming.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_validator.py +0 -0
- {focus_data_toolkit-0.11.0 → focus_data_toolkit-0.12.0}/tests/test_workflow_pins.py +0 -0
- {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.
|
|
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
|
+
[](https://pypi.org/project/focus-data-toolkit/)
|
|
60
|
+
[](https://pypi.org/project/focus-data-toolkit/)
|
|
61
|
+
[](LICENSE)
|
|
62
|
+
[](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
|
+
[](https://pypi.org/project/focus-data-toolkit/)
|
|
7
|
+
[](https://pypi.org/project/focus-data-toolkit/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](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.
|
|
91
|
-
| `< 0.
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|