metrolith 4.0.0__py3-none-any.whl
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.
- archlens_json.py +489 -0
- config/__init__.py +1 -0
- config/exclusions.v1.json +39 -0
- examples/README.md +33 -0
- examples/__init__.py +1 -0
- examples/input_template.csv +2 -0
- examples/local_project/app.py +16 -0
- examples/local_project/web.js +12 -0
- examples/quickstart_repositories.csv +5 -0
- examples/ratchet-rules.json +11 -0
- metrolith-4.0.0.dist-info/METADATA +154 -0
- metrolith-4.0.0.dist-info/RECORD +368 -0
- metrolith-4.0.0.dist-info/WHEEL +5 -0
- metrolith-4.0.0.dist-info/entry_points.txt +4 -0
- metrolith-4.0.0.dist-info/licenses/LICENSE +202 -0
- metrolith-4.0.0.dist-info/top_level.txt +6 -0
- modules/acquisition.py +1008 -0
- modules/admission.py +162 -0
- modules/benchmark_qualification.py +895 -0
- modules/benchmark_runner.py +1866 -0
- modules/callable_analysis/__init__.py +253 -0
- modules/callable_analysis/cognitive.py +304 -0
- modules/callable_analysis/cognitive_rules.py +314 -0
- modules/callable_analysis/go.py +254 -0
- modules/callable_analysis/java.py +359 -0
- modules/callable_analysis/js_ts.py +448 -0
- modules/callable_analysis/metrics.py +203 -0
- modules/callable_analysis/model.py +181 -0
- modules/callable_analysis/python.py +569 -0
- modules/callable_ledger.py +346 -0
- modules/changed_code.py +1345 -0
- modules/classify.py +74 -0
- modules/cli/__init__.py +21 -0
- modules/cli/baseline_command.py +160 -0
- modules/cli/changed_command.py +277 -0
- modules/cli/check_command.py +875 -0
- modules/cli/check_output.py +104 -0
- modules/cli/compare_command.py +531 -0
- modules/cli/diff_command.py +591 -0
- modules/cli/dossier_command.py +169 -0
- modules/cli/duplication_command.py +195 -0
- modules/cli/explain_command.py +482 -0
- modules/cli/export_output.py +276 -0
- modules/cli/hotspots_command.py +286 -0
- modules/cli/performance_command.py +309 -0
- modules/cli/policy_command.py +417 -0
- modules/cli/report_command.py +1326 -0
- modules/cli/reproduce_command.py +791 -0
- modules/cli/schema_command.py +96 -0
- modules/cli/transport.py +103 -0
- modules/cli/trust_command.py +318 -0
- modules/cli/validate_command.py +235 -0
- modules/clone_repo.py +897 -0
- modules/complexity_distribution.py +402 -0
- modules/complexity_view.py +572 -0
- modules/config.py +494 -0
- modules/contribution_ledger.py +442 -0
- modules/core_metrics.py +1815 -0
- modules/coverage.py +80 -0
- modules/db_analysis.py +139 -0
- modules/deployability.py +109 -0
- modules/diagnostics.py +444 -0
- modules/dossier.py +884 -0
- modules/duplication/__init__.py +92 -0
- modules/duplication/candidates.py +602 -0
- modules/duplication/grouping.py +178 -0
- modules/duplication/lexical.py +366 -0
- modules/duplication/model.py +309 -0
- modules/duplication/output.py +1195 -0
- modules/duplication/structural.py +934 -0
- modules/duplication/structural_grouping.py +465 -0
- modules/endpoints.py +299 -0
- modules/export_csv.py +216 -0
- modules/fact_sheet.py +351 -0
- modules/git_change_extractor.py +312 -0
- modules/go_analysis.py +48 -0
- modules/hotspots.py +973 -0
- modules/inventory.py +1665 -0
- modules/inventory_audits.py +232 -0
- modules/js_ts_audit.py +30 -0
- modules/local_source.py +501 -0
- modules/message_brokers.py +50 -0
- modules/metadata.py +190 -0
- modules/metric_warnings.py +98 -0
- modules/normalized_input.py +399 -0
- modules/policy/__init__.py +1 -0
- modules/policy/check.py +2643 -0
- modules/policy/document.py +233 -0
- modules/policy/document_v2.py +706 -0
- modules/policy/engine.py +164 -0
- modules/policy/evidence.py +961 -0
- modules/policy/findings.py +472 -0
- modules/policy/metrics.py +1284 -0
- modules/policy/rules.py +412 -0
- modules/policy/sarif.py +1035 -0
- modules/policy/trust.py +579 -0
- modules/preflight.py +699 -0
- modules/presentation.py +200 -0
- modules/ratchet/__init__.py +178 -0
- modules/ratchet/admission.py +382 -0
- modules/ratchet/capture.py +633 -0
- modules/ratchet/check_service.py +983 -0
- modules/ratchet/cli_request.py +161 -0
- modules/ratchet/comparison.py +174 -0
- modules/ratchet/contract.py +1051 -0
- modules/ratchet/observations.py +620 -0
- modules/ratchet/orchestration.py +299 -0
- modules/ratchet/pairing.py +290 -0
- modules/ratchet/semantics.py +497 -0
- modules/report_composition.py +302 -0
- modules/report_view.py +1226 -0
- modules/report_view_validation.py +788 -0
- modules/repository_files.py +81 -0
- modules/repository_input.py +396 -0
- modules/revision_diff.py +1609 -0
- modules/revision_source.py +361 -0
- modules/run_artifacts.py +1989 -0
- modules/source_composition.py +479 -0
- modules/source_frontend.py +875 -0
- modules/standalone_contracts.py +228 -0
- modules/static_analysis.py +46 -0
- modules/subject.py +428 -0
- modules/summary.py +1117 -0
- modules/syntax_predicates.py +266 -0
- modules/vocabularies.py +324 -0
- pipeline.py +1827 -0
- validation/__init__.py +1 -0
- validation/artifact_io/__init__.py +128 -0
- validation/artifact_io/compatibility.py +419 -0
- validation/artifact_io/contracts.py +169 -0
- validation/artifact_io/errors.py +150 -0
- validation/artifact_io/known_exceptions.py +369 -0
- validation/artifact_io/legacy_cells.py +186 -0
- validation/artifact_io/paths.py +308 -0
- validation/artifact_io/reader.py +688 -0
- validation/artifact_io/schema_store.py +972 -0
- validation/artifact_io/strict_csv.py +512 -0
- validation/artifact_io/strict_json.py +161 -0
- validation/conformance/__init__.py +18 -0
- validation/conformance/__main__.py +84 -0
- validation/conformance/data/REVIEW.md +76 -0
- validation/conformance/data/cases/compatibility-cap-oversized-jsx-is-not-rewritten/huge.jsx +20003 -0
- validation/conformance/data/cases/content-ambiguous-marker-does-not-exclude/config.ts +4 -0
- validation/conformance/data/cases/content-qt-linguist-ts-is-excluded/app.ts +3 -0
- validation/conformance/data/cases/content-qt-linguist-ts-is-excluded/i18n/messages_de.ts +7 -0
- validation/conformance/data/cases/encoding-binary-nul-fails/binary.js +0 -0
- validation/conformance/data/cases/encoding-cr-only-line-endings/cr.py +1 -0
- validation/conformance/data/cases/encoding-invalid-utf8-fails-loc/invalid.py +3 -0
- validation/conformance/data/cases/encoding-no-final-newline/nofinal.py +2 -0
- validation/conformance/data/cases/encoding-python-docstring-counts-as-code/docstring.py +8 -0
- validation/conformance/data/cases/encoding-truncated-utf16-fails-loc/truncated.py +0 -0
- validation/conformance/data/cases/encoding-unicode-path-is-inventoried/caf/303/251//346/227/245/346/234/254/350/252/236.py +1 -0
- validation/conformance/data/cases/encoding-utf16be-bom-python/utf16be.py +0 -0
- validation/conformance/data/cases/encoding-utf16le-bom-python/utf16le.py +0 -0
- validation/conformance/data/cases/encoding-utf8-bom-python/bom.py +2 -0
- validation/conformance/data/cases/entity-go-interface-is-secondary/iface.go +9 -0
- validation/conformance/data/cases/entity-go-struct-and-receiver-method/shape.go +13 -0
- validation/conformance/data/cases/entity-java-constructor-is-secondary/Delta.java +5 -0
- validation/conformance/data/cases/entity-java-enum-is-secondary/Colour.java +6 -0
- validation/conformance/data/cases/entity-java-interface-is-secondary/Gamma.java +3 -0
- validation/conformance/data/cases/entity-java-nested-class-counts/Outer.java +7 -0
- validation/conformance/data/cases/entity-java-record-counts/Point.java +2 -0
- validation/conformance/data/cases/entity-js-anonymous-callback-is-secondary/callbacks.js +5 -0
- validation/conformance/data/cases/entity-js-arrow-lambda-is-secondary/arrow.js +1 -0
- validation/conformance/data/cases/entity-js-commonjs-exported-function/helper.js +3 -0
- validation/conformance/data/cases/entity-js-stable-assigned-class/alpha.js +3 -0
- validation/conformance/data/cases/entity-python-class-inside-function/factory.py +6 -0
- validation/conformance/data/cases/entity-python-init-is-secondary/widget.py +6 -0
- validation/conformance/data/cases/entity-python-nested-function-is-secondary/nested.py +5 -0
- validation/conformance/data/cases/git-dirty-working-tree-is-still-measured/app.py +1 -0
- validation/conformance/data/cases/git-mode-120000-symlink-excluded/app.py +1 -0
- validation/conformance/data/cases/git-mode-120000-symlink-excluded/link.py +1 -0
- validation/conformance/data/cases/git-mode-160000-submodule-excluded/app.py +1 -0
- validation/conformance/data/cases/git-mode-160000-submodule-excluded/vendorlib/inner.py +1 -0
- validation/conformance/data/cases/git-mode-map-available-in-a-repository/app.py +1 -0
- validation/conformance/data/cases/git-mode-map-failed-downgrades-inventory/app.py +1 -0
- validation/conformance/data/cases/git-mode-map-timed-out-downgrades-inventory/app.py +1 -0
- validation/conformance/data/cases/git-mode-map-unavailable-outside-a-repository/app.py +1 -0
- validation/conformance/data/cases/git-native-symlink-excluded/app.py +1 -0
- validation/conformance/data/cases/git-native-symlink-excluded/target.py +1 -0
- validation/conformance/data/cases/loc-block-comment-only-lines-excluded/block.java +5 -0
- validation/conformance/data/cases/loc-comment-and-blank-lines-excluded/Alpha.java +5 -0
- validation/conformance/data/cases/loc-inline-comment-counts-once/Beta.java +3 -0
- validation/conformance/data/cases/loc-string-content-counts-as-code/strings.js +4 -0
- validation/conformance/data/cases/malformed-empty-file-is-a-verified-zero/empty.go +0 -0
- validation/conformance/data/cases/malformed-java-unclosed-brace/Broken.java +3 -0
- validation/conformance/data/cases/malformed-python2-print-statement/legacy.py +4 -0
- validation/conformance/data/cases/recovery-clean-javascript-uses-no-strategy/clean.js +3 -0
- validation/conformance/data/cases/recovery-genuinely-broken-javascript-is-not-recovered/broken.js +2 -0
- validation/conformance/data/cases/recovery-javascript-import-assertion-positive/cfg.mjs +2 -0
- validation/conformance/data/cases/recovery-jsx-reserved-attribute-positive/comp.jsx +3 -0
- validation/conformance/data/cases/recovery-raw-jsx-ampersand-positive/amp.jsx +3 -0
- validation/conformance/data/cases/recovery-typed-javascript-tsx-negative/typed.js +4 -0
- validation/conformance/data/cases/recovery-typed-javascript-tsx-positive/.flowconfig +1 -0
- validation/conformance/data/cases/recovery-typed-javascript-tsx-positive/package.json +1 -0
- validation/conformance/data/cases/recovery-typed-javascript-tsx-positive/src/typed.js +4 -0
- validation/conformance/data/cases/recovery-typescript-keyword-parameter-positive/keywords.ts +3 -0
- validation/conformance/data/cases/scope-declaration-only-typescript-is-excluded/index.ts +3 -0
- validation/conformance/data/cases/scope-declaration-only-typescript-is-excluded/types/index.d.ts +3 -0
- validation/conformance/data/cases/scope-generated-header-is-excluded/generated.go +7 -0
- validation/conformance/data/cases/scope-generated-header-is-excluded/handwritten.go +5 -0
- validation/conformance/data/cases/scope-go-test-suffix-is-excluded/service.go +5 -0
- validation/conformance/data/cases/scope-go-test-suffix-is-excluded/service_test.go +3 -0
- validation/conformance/data/cases/scope-test-file-is-excluded/src/Main.java +3 -0
- validation/conformance/data/cases/scope-test-file-is-excluded/src/test/java/MainTest.java +3 -0
- validation/conformance/data/cases/scope-unsupported-extension-is-not-a-source-file/README.md +1 -0
- validation/conformance/data/cases/scope-unsupported-extension-is-not-a-source-file/app.py +2 -0
- validation/conformance/data/cases/scope-unsupported-extension-is-not-a-source-file/config.yaml +1 -0
- validation/conformance/data/cases/scope-vendor-directory-is-excluded/app.go +5 -0
- validation/conformance/data/cases/scope-vendor-directory-is-excluded/vendor/github.com/other/lib/lib.go +5 -0
- validation/conformance/data/cases/scope-verified-zero-entities/config.go +3 -0
- validation/conformance/data/cases/size-boundary-file-at-the-limit-is-readable/atlimit.py +10 -0
- validation/conformance/data/cases/size-boundary-oversized-file-stays-in-source-files/big.py +400 -0
- validation/conformance/data/cases/size-boundary-oversized-file-stays-in-source-files/small.py +1 -0
- validation/conformance/data/expected/compatibility-cap-oversized-jsx-is-not-rewritten.json +7 -0
- validation/conformance/data/expected/content-ambiguous-marker-does-not-exclude.json +11 -0
- validation/conformance/data/expected/content-qt-linguist-ts-is-excluded.json +11 -0
- validation/conformance/data/expected/encoding-binary-nul-fails.json +7 -0
- validation/conformance/data/expected/encoding-cr-only-line-endings.json +8 -0
- validation/conformance/data/expected/encoding-invalid-utf8-fails-loc.json +8 -0
- validation/conformance/data/expected/encoding-no-final-newline.json +9 -0
- validation/conformance/data/expected/encoding-python-docstring-counts-as-code.json +12 -0
- validation/conformance/data/expected/encoding-truncated-utf16-fails-loc.json +8 -0
- validation/conformance/data/expected/encoding-unicode-path-is-inventoried.json +11 -0
- validation/conformance/data/expected/encoding-utf16be-bom-python.json +11 -0
- validation/conformance/data/expected/encoding-utf16le-bom-python.json +11 -0
- validation/conformance/data/expected/encoding-utf8-bom-python.json +13 -0
- validation/conformance/data/expected/entity-go-interface-is-secondary.json +9 -0
- validation/conformance/data/expected/entity-go-struct-and-receiver-method.json +11 -0
- validation/conformance/data/expected/entity-java-constructor-is-secondary.json +11 -0
- validation/conformance/data/expected/entity-java-enum-is-secondary.json +9 -0
- validation/conformance/data/expected/entity-java-interface-is-secondary.json +11 -0
- validation/conformance/data/expected/entity-java-nested-class-counts.json +9 -0
- validation/conformance/data/expected/entity-java-record-counts.json +8 -0
- validation/conformance/data/expected/entity-js-anonymous-callback-is-secondary.json +8 -0
- validation/conformance/data/expected/entity-js-arrow-lambda-is-secondary.json +10 -0
- validation/conformance/data/expected/entity-js-commonjs-exported-function.json +8 -0
- validation/conformance/data/expected/entity-js-stable-assigned-class.json +9 -0
- validation/conformance/data/expected/entity-python-class-inside-function.json +11 -0
- validation/conformance/data/expected/entity-python-init-is-secondary.json +10 -0
- validation/conformance/data/expected/entity-python-nested-function-is-secondary.json +9 -0
- validation/conformance/data/expected/git-dirty-working-tree-is-still-measured.json +11 -0
- validation/conformance/data/expected/git-mode-120000-symlink-excluded.json +13 -0
- validation/conformance/data/expected/git-mode-160000-submodule-excluded.json +13 -0
- validation/conformance/data/expected/git-mode-map-available-in-a-repository.json +11 -0
- validation/conformance/data/expected/git-mode-map-failed-downgrades-inventory.json +12 -0
- validation/conformance/data/expected/git-mode-map-timed-out-downgrades-inventory.json +12 -0
- validation/conformance/data/expected/git-mode-map-unavailable-outside-a-repository.json +13 -0
- validation/conformance/data/expected/git-native-symlink-excluded.json +10 -0
- validation/conformance/data/expected/loc-block-comment-only-lines-excluded.json +9 -0
- validation/conformance/data/expected/loc-comment-and-blank-lines-excluded.json +13 -0
- validation/conformance/data/expected/loc-inline-comment-counts-once.json +9 -0
- validation/conformance/data/expected/loc-string-content-counts-as-code.json +10 -0
- validation/conformance/data/expected/malformed-empty-file-is-a-verified-zero.json +11 -0
- validation/conformance/data/expected/malformed-java-unclosed-brace.json +9 -0
- validation/conformance/data/expected/malformed-python2-print-statement.json +8 -0
- validation/conformance/data/expected/recovery-clean-javascript-uses-no-strategy.json +10 -0
- validation/conformance/data/expected/recovery-genuinely-broken-javascript-is-not-recovered.json +8 -0
- validation/conformance/data/expected/recovery-javascript-import-assertion-positive.json +10 -0
- validation/conformance/data/expected/recovery-jsx-reserved-attribute-positive.json +10 -0
- validation/conformance/data/expected/recovery-raw-jsx-ampersand-positive.json +10 -0
- validation/conformance/data/expected/recovery-typed-javascript-tsx-negative.json +8 -0
- validation/conformance/data/expected/recovery-typed-javascript-tsx-positive.json +10 -0
- validation/conformance/data/expected/recovery-typescript-keyword-parameter-positive.json +10 -0
- validation/conformance/data/expected/scope-declaration-only-typescript-is-excluded.json +8 -0
- validation/conformance/data/expected/scope-generated-header-is-excluded.json +8 -0
- validation/conformance/data/expected/scope-go-test-suffix-is-excluded.json +8 -0
- validation/conformance/data/expected/scope-test-file-is-excluded.json +8 -0
- validation/conformance/data/expected/scope-unsupported-extension-is-not-a-source-file.json +8 -0
- validation/conformance/data/expected/scope-vendor-directory-is-excluded.json +8 -0
- validation/conformance/data/expected/scope-verified-zero-entities.json +13 -0
- validation/conformance/data/expected/size-boundary-file-at-the-limit-is-readable.json +10 -0
- validation/conformance/data/expected/size-boundary-oversized-file-stays-in-source-files.json +13 -0
- validation/conformance/data/manifest.json +1118 -0
- validation/conformance/runner.py +425 -0
- validation/resources/__init__.py +13 -0
- validation/resources/schemas/analysis-1.10.schema.json +10 -0
- validation/resources/schemas/analysis-1.5.schema.json +10 -0
- validation/resources/schemas/analysis-1.6.schema.json +10 -0
- validation/resources/schemas/analysis-1.7.schema.json +10 -0
- validation/resources/schemas/analysis-1.9.schema.json +10 -0
- validation/resources/schemas/benchmark_qualification-1.0.schema.json +186 -0
- validation/resources/schemas/benchmark_qualification_registry-1.0.schema.json +252 -0
- validation/resources/schemas/callable_row-1.10.schema.json +242 -0
- validation/resources/schemas/callable_row-1.9.schema.json +234 -0
- validation/resources/schemas/catalog_row-1.11.schema.json +665 -0
- validation/resources/schemas/catalog_row-1.5.schema.json +472 -0
- validation/resources/schemas/catalog_row-1.7.schema.json +484 -0
- validation/resources/schemas/check_result-1.0.schema.json +742 -0
- validation/resources/schemas/check_result-1.1.schema.json +936 -0
- validation/resources/schemas/check_result-1.2.schema.json +939 -0
- validation/resources/schemas/check_result-1.3.schema.json +1127 -0
- validation/resources/schemas/check_result-1.4.schema.json +1404 -0
- validation/resources/schemas/compare_output-1.0.schema.json +93 -0
- validation/resources/schemas/compare_output-1.1.schema.json +114 -0
- validation/resources/schemas/contribution_container-1.5.schema.json +85 -0
- validation/resources/schemas/contribution_row-1.5.schema.json +353 -0
- validation/resources/schemas/contribution_row-1.7.schema.json +366 -0
- validation/resources/schemas/contribution_row-1.9.schema.json +392 -0
- validation/resources/schemas/environment-1.12.schema.json +136 -0
- validation/resources/schemas/environment-1.5.schema.json +107 -0
- validation/resources/schemas/errors_row-1.5.schema.json +796 -0
- validation/resources/schemas/errors_row-1.7.schema.json +808 -0
- validation/resources/schemas/explain_output-1.0.schema.json +130 -0
- validation/resources/schemas/explain_output-1.1.schema.json +182 -0
- validation/resources/schemas/explain_output-1.2.schema.json +208 -0
- validation/resources/schemas/explain_output-1.3.schema.json +216 -0
- validation/resources/schemas/file_inventory-1.6.schema.json +331 -0
- validation/resources/schemas/file_inventory-1.7.schema.json +335 -0
- validation/resources/schemas/frozen_input_row-1.5.schema.json +50 -0
- validation/resources/schemas/language_metrics_row-1.11.schema.json +377 -0
- validation/resources/schemas/language_metrics_row-1.5.schema.json +244 -0
- validation/resources/schemas/language_metrics_row-1.7.schema.json +256 -0
- validation/resources/schemas/log_event-1.5.schema.json +72 -0
- validation/resources/schemas/normalized_input_row-1.5.schema.json +155 -0
- validation/resources/schemas/normalized_input_row-1.7.schema.json +168 -0
- validation/resources/schemas/performance_profile-1.0.schema.json +118 -0
- validation/resources/schemas/policy_document-1.0.schema.json +114 -0
- validation/resources/schemas/policy_document-2.0.schema.json +284 -0
- validation/resources/schemas/policy_document-2.1.schema.json +296 -0
- validation/resources/schemas/policy_document-2.2.schema.json +312 -0
- validation/resources/schemas/policy_result-1.0.schema.json +338 -0
- validation/resources/schemas/ratchet_baseline-1.0.schema.json +381 -0
- validation/resources/schemas/recoveries_row-1.5.schema.json +378 -0
- validation/resources/schemas/recoveries_row-1.7.schema.json +390 -0
- validation/resources/schemas/recoveries_row-1.8.schema.json +394 -0
- validation/resources/schemas/report_metadata-1.0.schema.json +48 -0
- validation/resources/schemas/report_view-1.0.schema.json +334 -0
- validation/resources/schemas/repository_document-1.10.schema.json +728 -0
- validation/resources/schemas/repository_document-1.5.schema.json +164 -0
- validation/resources/schemas/repository_document-1.6.schema.json +418 -0
- validation/resources/schemas/repository_document-1.7.schema.json +495 -0
- validation/resources/schemas/repository_document-1.9.schema.json +717 -0
- validation/resources/schemas/reproduction_output-1.0.schema.json +131 -0
- validation/resources/schemas/reproduction_output-1.1.schema.json +164 -0
- validation/resources/schemas/retry_input_row-1.5.schema.json +53 -0
- validation/resources/schemas/revision_diff-1.0.schema.json +936 -0
- validation/resources/schemas/revision_diff-1.1.schema.json +1241 -0
- validation/resources/schemas/revision_diff-1.2.schema.json +1419 -0
- validation/resources/schemas/run_manifest-1.10.schema.json +384 -0
- validation/resources/schemas/run_manifest-1.11.schema.json +535 -0
- validation/resources/schemas/run_manifest-1.12.schema.json +564 -0
- validation/resources/schemas/run_manifest-1.5.schema.json +379 -0
- validation/resources/schemas/run_manifest-1.6.schema.json +378 -0
- validation/resources/schemas/run_manifest-1.7.schema.json +378 -0
- validation/resources/schemas/run_manifest-1.9.schema.json +384 -0
- validation/resources/schemas/run_status-1.10.schema.json +182 -0
- validation/resources/schemas/run_status-1.5.schema.json +120 -0
- validation/resources/schemas/run_status-1.6.schema.json +168 -0
- validation/resources/schemas/run_status-1.7.schema.json +168 -0
- validation/resources/schemas/run_status-1.9.schema.json +175 -0
- validation/resources/schemas/sheet_metrics_row-1.11.schema.json +476 -0
- validation/resources/schemas/sheet_metrics_row-1.5.schema.json +283 -0
- validation/resources/schemas/sheet_metrics_row-1.7.schema.json +295 -0
- validation/resources/schemas/trusted_evidence_receipt-1.0.schema.json +150 -0
- validation/scripts/__init__.py +1 -0
- validation/scripts/cache_contamination_check.py +115 -0
- validation/scripts/capture_environment.py +56 -0
- validation/scripts/compare_semantic_runs.py +65 -0
- validation/scripts/derive_tabular_schemas.py +392 -0
- validation/scripts/generate_source_manifest.py +447 -0
- validation/scripts/independent_inventory_check.py +279 -0
- validation/scripts/independent_loc_check.py +126 -0
- validation/scripts/loc_file_differences.py +79 -0
- validation/scripts/manual_entity_samples.py +344 -0
- validation/scripts/schema_closure_audit.py +351 -0
- validation/scripts/semantic_projection.py +731 -0
- validation/scripts/validate_outputs.py +860 -0
archlens_json.py
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
"""Neutral bounded JSON input and output for external ArchLens documents.
|
|
2
|
+
|
|
3
|
+
This module deliberately has no dependency on Policy, Ratchet, CLI, or the
|
|
4
|
+
validation package. Those domains translate :class:`StrictJsonError` into
|
|
5
|
+
their own public error types while sharing one byte-exact JSON boundary.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class StrictJsonLimits:
|
|
19
|
+
"""Resource limits applied before and after JSON decoding."""
|
|
20
|
+
|
|
21
|
+
max_bytes: int
|
|
22
|
+
max_depth: int
|
|
23
|
+
max_collection_items: int
|
|
24
|
+
max_total_items: int
|
|
25
|
+
max_string_bytes: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
POLICY_JSON_LIMITS = StrictJsonLimits(
|
|
29
|
+
max_bytes=4 * 1024 * 1024,
|
|
30
|
+
max_depth=64,
|
|
31
|
+
max_collection_items=20_000,
|
|
32
|
+
max_total_items=100_000,
|
|
33
|
+
max_string_bytes=1 * 1024 * 1024,
|
|
34
|
+
)
|
|
35
|
+
BASELINE_JSON_LIMITS = StrictJsonLimits(
|
|
36
|
+
max_bytes=64 * 1024 * 1024,
|
|
37
|
+
max_depth=64,
|
|
38
|
+
max_collection_items=1_000_000,
|
|
39
|
+
max_total_items=2_000_000,
|
|
40
|
+
max_string_bytes=16 * 1024 * 1024,
|
|
41
|
+
)
|
|
42
|
+
EVIDENCE_JSON_LIMITS = StrictJsonLimits(
|
|
43
|
+
max_bytes=512 * 1024 * 1024,
|
|
44
|
+
max_depth=64,
|
|
45
|
+
max_collection_items=5_000_000,
|
|
46
|
+
max_total_items=10_000_000,
|
|
47
|
+
max_string_bytes=64 * 1024 * 1024,
|
|
48
|
+
)
|
|
49
|
+
CHECK_JSON_LIMITS = StrictJsonLimits(
|
|
50
|
+
max_bytes=512 * 1024 * 1024,
|
|
51
|
+
max_depth=64,
|
|
52
|
+
max_collection_items=5_000_000,
|
|
53
|
+
max_total_items=10_000_000,
|
|
54
|
+
max_string_bytes=64 * 1024 * 1024,
|
|
55
|
+
)
|
|
56
|
+
SARIF_JSON_LIMITS = CHECK_JSON_LIMITS
|
|
57
|
+
ARTIFACT_JSON_LIMITS = EVIDENCE_JSON_LIMITS
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class StrictJsonError(ValueError):
|
|
61
|
+
"""One bounded JSON admission or serialization failure."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
code: str,
|
|
66
|
+
source: str,
|
|
67
|
+
message: str,
|
|
68
|
+
*,
|
|
69
|
+
location: str | None = None,
|
|
70
|
+
detail: dict[str, Any] | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
super().__init__(message)
|
|
73
|
+
self.code = code
|
|
74
|
+
self.source = source
|
|
75
|
+
self.message = message
|
|
76
|
+
self.location = location
|
|
77
|
+
self.detail = detail or {}
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
where = f" at {self.location}" if self.location else ""
|
|
81
|
+
return f"[{self.code}] {self.source}{where}: {self.message}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_UTF8_BOM = b"\xef\xbb\xbf"
|
|
85
|
+
_UTF16_LE_BOM = b"\xff\xfe"
|
|
86
|
+
_UTF16_BE_BOM = b"\xfe\xff"
|
|
87
|
+
_UTF32_LE_BOM = b"\xff\xfe\x00\x00"
|
|
88
|
+
_UTF32_BE_BOM = b"\x00\x00\xfe\xff"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def read_bounded_bytes(
|
|
92
|
+
path: Path | str,
|
|
93
|
+
*,
|
|
94
|
+
source: str | None = None,
|
|
95
|
+
limits: StrictJsonLimits,
|
|
96
|
+
) -> bytes:
|
|
97
|
+
"""Read a regular external JSON payload without unbounded allocation."""
|
|
98
|
+
|
|
99
|
+
candidate = Path(path)
|
|
100
|
+
label = source or str(candidate)
|
|
101
|
+
try:
|
|
102
|
+
size = candidate.stat().st_size
|
|
103
|
+
except OSError as exc:
|
|
104
|
+
raise StrictJsonError(
|
|
105
|
+
"artifact_unreadable", label, f"cannot stat JSON input: {exc}"
|
|
106
|
+
) from exc
|
|
107
|
+
if size > limits.max_bytes:
|
|
108
|
+
raise StrictJsonError(
|
|
109
|
+
"artifact_too_large",
|
|
110
|
+
label,
|
|
111
|
+
f"JSON input is {size} bytes; limit is {limits.max_bytes}",
|
|
112
|
+
detail={"actual_bytes": size, "max_bytes": limits.max_bytes},
|
|
113
|
+
)
|
|
114
|
+
try:
|
|
115
|
+
# The stat check gives an early, cheap rejection. The bounded read is
|
|
116
|
+
# still required because an external file can grow between stat/open.
|
|
117
|
+
with candidate.open("rb") as handle:
|
|
118
|
+
payload = handle.read(limits.max_bytes + 1)
|
|
119
|
+
except OSError as exc:
|
|
120
|
+
raise StrictJsonError(
|
|
121
|
+
"artifact_unreadable", label, f"cannot read JSON input: {exc}"
|
|
122
|
+
) from exc
|
|
123
|
+
_check_byte_count(payload, label, limits)
|
|
124
|
+
return payload
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _check_byte_count(
|
|
128
|
+
payload: bytes, source: str, limits: StrictJsonLimits
|
|
129
|
+
) -> None:
|
|
130
|
+
if len(payload) > limits.max_bytes:
|
|
131
|
+
raise StrictJsonError(
|
|
132
|
+
"artifact_too_large",
|
|
133
|
+
source,
|
|
134
|
+
f"JSON input is {len(payload)} bytes; limit is {limits.max_bytes}",
|
|
135
|
+
detail={"actual_bytes": len(payload), "max_bytes": limits.max_bytes},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def decode_utf8(
|
|
140
|
+
payload: bytes,
|
|
141
|
+
*,
|
|
142
|
+
source: str,
|
|
143
|
+
limits: StrictJsonLimits,
|
|
144
|
+
) -> str:
|
|
145
|
+
"""Decode strict UTF-8 bytes without universal-newline translation."""
|
|
146
|
+
|
|
147
|
+
if not isinstance(payload, bytes):
|
|
148
|
+
raise StrictJsonError("invalid_input_type", source, "JSON input must be bytes")
|
|
149
|
+
_check_byte_count(payload, source, limits)
|
|
150
|
+
if payload.startswith(_UTF32_LE_BOM) or payload.startswith(_UTF32_BE_BOM):
|
|
151
|
+
raise StrictJsonError(
|
|
152
|
+
"unsupported_encoding", source, "UTF-32 JSON is not supported; use UTF-8"
|
|
153
|
+
)
|
|
154
|
+
if payload.startswith(_UTF16_LE_BOM) or payload.startswith(_UTF16_BE_BOM):
|
|
155
|
+
raise StrictJsonError(
|
|
156
|
+
"unsupported_encoding", source, "UTF-16 JSON is not supported; use UTF-8"
|
|
157
|
+
)
|
|
158
|
+
if payload.startswith(_UTF8_BOM):
|
|
159
|
+
raise StrictJsonError(
|
|
160
|
+
"unexpected_byte_order_mark", source, "a UTF-8 BOM is not permitted"
|
|
161
|
+
)
|
|
162
|
+
try:
|
|
163
|
+
return payload.decode("utf-8", errors="strict")
|
|
164
|
+
except UnicodeDecodeError as exc:
|
|
165
|
+
raise StrictJsonError(
|
|
166
|
+
"invalid_utf8",
|
|
167
|
+
source,
|
|
168
|
+
f"JSON input is not valid UTF-8: {exc.reason}",
|
|
169
|
+
location=f"byte offset {exc.start}",
|
|
170
|
+
detail={"byte_offset": exc.start},
|
|
171
|
+
) from exc
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _text_as_utf8(
|
|
175
|
+
text: str, *, source: str, limits: StrictJsonLimits
|
|
176
|
+
) -> bytes:
|
|
177
|
+
if not isinstance(text, str):
|
|
178
|
+
raise StrictJsonError("invalid_input_type", source, "JSON input must be text")
|
|
179
|
+
if text.startswith("\ufeff"):
|
|
180
|
+
raise StrictJsonError(
|
|
181
|
+
"unexpected_byte_order_mark", source, "a Unicode BOM is not permitted"
|
|
182
|
+
)
|
|
183
|
+
try:
|
|
184
|
+
encoded = text.encode("utf-8", errors="strict")
|
|
185
|
+
except UnicodeEncodeError as exc:
|
|
186
|
+
raise StrictJsonError(
|
|
187
|
+
"invalid_utf8",
|
|
188
|
+
source,
|
|
189
|
+
"JSON text contains a value that cannot be encoded as UTF-8",
|
|
190
|
+
location=f"character offset {exc.start}",
|
|
191
|
+
) from exc
|
|
192
|
+
_check_byte_count(encoded, source, limits)
|
|
193
|
+
return encoded
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _check_text_depth(text: str, source: str, limit: int) -> None:
|
|
197
|
+
"""Bound container nesting before ``json.loads`` can recurse."""
|
|
198
|
+
|
|
199
|
+
depth = 0
|
|
200
|
+
in_string = False
|
|
201
|
+
escaped = False
|
|
202
|
+
for offset, character in enumerate(text):
|
|
203
|
+
if in_string:
|
|
204
|
+
if escaped:
|
|
205
|
+
escaped = False
|
|
206
|
+
elif character == "\\":
|
|
207
|
+
escaped = True
|
|
208
|
+
elif character == '"':
|
|
209
|
+
in_string = False
|
|
210
|
+
continue
|
|
211
|
+
if character == '"':
|
|
212
|
+
in_string = True
|
|
213
|
+
elif character in "[{":
|
|
214
|
+
depth += 1
|
|
215
|
+
if depth > limit:
|
|
216
|
+
raise StrictJsonError(
|
|
217
|
+
"json_depth_exceeded",
|
|
218
|
+
source,
|
|
219
|
+
f"JSON nesting exceeds the {limit} level limit",
|
|
220
|
+
location=f"character offset {offset}",
|
|
221
|
+
detail={"max_depth": limit},
|
|
222
|
+
)
|
|
223
|
+
elif character in "]}":
|
|
224
|
+
depth -= 1
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _duplicate_hook(source: str):
|
|
228
|
+
def hook(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
229
|
+
result: dict[str, Any] = {}
|
|
230
|
+
for key, value in pairs:
|
|
231
|
+
if key in result:
|
|
232
|
+
raise StrictJsonError(
|
|
233
|
+
"json_duplicate_key",
|
|
234
|
+
source,
|
|
235
|
+
f"duplicate JSON object key {key!r}",
|
|
236
|
+
location=f"key {key!r}",
|
|
237
|
+
detail={"key": key},
|
|
238
|
+
)
|
|
239
|
+
result[key] = value
|
|
240
|
+
return result
|
|
241
|
+
|
|
242
|
+
return hook
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _constant_hook(source: str):
|
|
246
|
+
def reject(value: str) -> Any:
|
|
247
|
+
raise StrictJsonError(
|
|
248
|
+
"json_non_standard_number",
|
|
249
|
+
source,
|
|
250
|
+
f"{value} is not valid JSON; numbers must be finite",
|
|
251
|
+
detail={"constant": value},
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return reject
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _expected_name(expect: type | tuple[type, ...]) -> str:
|
|
258
|
+
if isinstance(expect, type):
|
|
259
|
+
return expect.__name__
|
|
260
|
+
return "/".join(item.__name__ for item in expect)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def validate_json_value(
|
|
264
|
+
value: Any,
|
|
265
|
+
*,
|
|
266
|
+
source: str,
|
|
267
|
+
limits: StrictJsonLimits,
|
|
268
|
+
expect: type | tuple[type, ...] | None = None,
|
|
269
|
+
) -> Any:
|
|
270
|
+
"""Recursively enforce JSON types, finite numbers, and item bounds."""
|
|
271
|
+
|
|
272
|
+
if expect is not None and not isinstance(value, expect):
|
|
273
|
+
raise StrictJsonError(
|
|
274
|
+
"json_root_type_unexpected",
|
|
275
|
+
source,
|
|
276
|
+
f"root value is {type(value).__name__}, expected {_expected_name(expect)}",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
stack: list[tuple[Any, int, str, bool]] = [(value, 1, "$", False)]
|
|
280
|
+
active_containers: set[int] = set()
|
|
281
|
+
total = 0
|
|
282
|
+
while stack:
|
|
283
|
+
item, depth, location, leaving = stack.pop()
|
|
284
|
+
if leaving:
|
|
285
|
+
active_containers.remove(id(item))
|
|
286
|
+
continue
|
|
287
|
+
total += 1
|
|
288
|
+
if total > limits.max_total_items:
|
|
289
|
+
raise StrictJsonError(
|
|
290
|
+
"json_item_count_exceeded",
|
|
291
|
+
source,
|
|
292
|
+
f"JSON value contains more than {limits.max_total_items} items",
|
|
293
|
+
location=location,
|
|
294
|
+
detail={"max_total_items": limits.max_total_items},
|
|
295
|
+
)
|
|
296
|
+
if item is None or isinstance(item, bool) or isinstance(item, int):
|
|
297
|
+
continue
|
|
298
|
+
if isinstance(item, float):
|
|
299
|
+
if not math.isfinite(item):
|
|
300
|
+
raise StrictJsonError(
|
|
301
|
+
"json_non_standard_number",
|
|
302
|
+
source,
|
|
303
|
+
"JSON numbers must be finite",
|
|
304
|
+
location=location,
|
|
305
|
+
)
|
|
306
|
+
continue
|
|
307
|
+
if isinstance(item, str):
|
|
308
|
+
if len(item) > limits.max_string_bytes:
|
|
309
|
+
raise StrictJsonError(
|
|
310
|
+
"json_item_too_large",
|
|
311
|
+
source,
|
|
312
|
+
f"JSON string exceeds the {limits.max_string_bytes} byte limit",
|
|
313
|
+
location=location,
|
|
314
|
+
)
|
|
315
|
+
try:
|
|
316
|
+
item_size = len(item.encode("utf-8", errors="strict"))
|
|
317
|
+
except UnicodeEncodeError as exc:
|
|
318
|
+
raise StrictJsonError(
|
|
319
|
+
"invalid_utf8",
|
|
320
|
+
source,
|
|
321
|
+
"JSON string cannot be encoded as UTF-8",
|
|
322
|
+
location=location,
|
|
323
|
+
) from exc
|
|
324
|
+
if item_size > limits.max_string_bytes:
|
|
325
|
+
raise StrictJsonError(
|
|
326
|
+
"json_item_too_large",
|
|
327
|
+
source,
|
|
328
|
+
f"JSON string is {item_size} bytes; limit is "
|
|
329
|
+
f"{limits.max_string_bytes}",
|
|
330
|
+
location=location,
|
|
331
|
+
)
|
|
332
|
+
continue
|
|
333
|
+
if isinstance(item, (dict, list)):
|
|
334
|
+
identity = id(item)
|
|
335
|
+
if identity in active_containers:
|
|
336
|
+
raise StrictJsonError(
|
|
337
|
+
"json_circular_reference",
|
|
338
|
+
source,
|
|
339
|
+
"JSON value contains a circular reference",
|
|
340
|
+
location=location,
|
|
341
|
+
)
|
|
342
|
+
if depth > limits.max_depth:
|
|
343
|
+
raise StrictJsonError(
|
|
344
|
+
"json_depth_exceeded",
|
|
345
|
+
source,
|
|
346
|
+
f"JSON nesting exceeds the {limits.max_depth} level limit",
|
|
347
|
+
location=location,
|
|
348
|
+
detail={"max_depth": limits.max_depth},
|
|
349
|
+
)
|
|
350
|
+
active_containers.add(identity)
|
|
351
|
+
stack.append((item, depth, location, True))
|
|
352
|
+
if len(item) > limits.max_collection_items:
|
|
353
|
+
raise StrictJsonError(
|
|
354
|
+
"json_collection_size_exceeded",
|
|
355
|
+
source,
|
|
356
|
+
f"JSON collection contains {len(item)} items; limit is "
|
|
357
|
+
f"{limits.max_collection_items}",
|
|
358
|
+
location=location,
|
|
359
|
+
)
|
|
360
|
+
if isinstance(item, dict):
|
|
361
|
+
children: list[tuple[Any, int, str, bool]] = []
|
|
362
|
+
for key, child in item.items():
|
|
363
|
+
if not isinstance(key, str):
|
|
364
|
+
raise StrictJsonError(
|
|
365
|
+
"json_object_key_type_invalid",
|
|
366
|
+
source,
|
|
367
|
+
"JSON object keys must be strings",
|
|
368
|
+
location=location,
|
|
369
|
+
)
|
|
370
|
+
children.append((key, depth, f"{location}.<key>", False))
|
|
371
|
+
children.append((child, depth + 1, f"{location}.{key}", False))
|
|
372
|
+
stack.extend(reversed(children))
|
|
373
|
+
else:
|
|
374
|
+
for index in range(len(item) - 1, -1, -1):
|
|
375
|
+
stack.append(
|
|
376
|
+
(item[index], depth + 1, f"{location}[{index}]", False)
|
|
377
|
+
)
|
|
378
|
+
continue
|
|
379
|
+
raise StrictJsonError(
|
|
380
|
+
"json_value_type_invalid",
|
|
381
|
+
source,
|
|
382
|
+
f"{type(item).__name__} is not a JSON value type",
|
|
383
|
+
location=location,
|
|
384
|
+
)
|
|
385
|
+
return value
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def loads_text(
|
|
389
|
+
text: str,
|
|
390
|
+
*,
|
|
391
|
+
source: str,
|
|
392
|
+
limits: StrictJsonLimits,
|
|
393
|
+
expect: type | tuple[type, ...] | None = dict,
|
|
394
|
+
) -> Any:
|
|
395
|
+
"""Parse bounded JSON text with duplicate-key and finite-number rejection."""
|
|
396
|
+
|
|
397
|
+
_text_as_utf8(text, source=source, limits=limits)
|
|
398
|
+
_check_text_depth(text, source, limits.max_depth)
|
|
399
|
+
try:
|
|
400
|
+
value = json.loads(
|
|
401
|
+
text,
|
|
402
|
+
object_pairs_hook=_duplicate_hook(source),
|
|
403
|
+
parse_constant=_constant_hook(source),
|
|
404
|
+
)
|
|
405
|
+
except json.JSONDecodeError as exc:
|
|
406
|
+
raise StrictJsonError(
|
|
407
|
+
"json_malformed",
|
|
408
|
+
source,
|
|
409
|
+
f"malformed JSON: {exc.msg}",
|
|
410
|
+
location=f"line {exc.lineno}, column {exc.colno}",
|
|
411
|
+
detail={"line": exc.lineno, "column": exc.colno},
|
|
412
|
+
) from exc
|
|
413
|
+
return validate_json_value(value, source=source, limits=limits, expect=expect)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def loads_bytes(
|
|
417
|
+
payload: bytes,
|
|
418
|
+
*,
|
|
419
|
+
source: str,
|
|
420
|
+
limits: StrictJsonLimits,
|
|
421
|
+
expect: type | tuple[type, ...] | None = dict,
|
|
422
|
+
) -> Any:
|
|
423
|
+
text = decode_utf8(payload, source=source, limits=limits)
|
|
424
|
+
return loads_text(text, source=source, limits=limits, expect=expect)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def load_file(
|
|
428
|
+
path: Path | str,
|
|
429
|
+
*,
|
|
430
|
+
source: str | None = None,
|
|
431
|
+
limits: StrictJsonLimits,
|
|
432
|
+
expect: type | tuple[type, ...] | None = dict,
|
|
433
|
+
) -> Any:
|
|
434
|
+
label = source or str(path)
|
|
435
|
+
payload = read_bounded_bytes(path, source=label, limits=limits)
|
|
436
|
+
return loads_bytes(payload, source=label, limits=limits, expect=expect)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def dumps_strict(
|
|
440
|
+
value: Any,
|
|
441
|
+
*,
|
|
442
|
+
source: str,
|
|
443
|
+
limits: StrictJsonLimits,
|
|
444
|
+
ensure_ascii: bool = False,
|
|
445
|
+
sort_keys: bool = True,
|
|
446
|
+
separators: tuple[str, str] | None = None,
|
|
447
|
+
indent: int | None = None,
|
|
448
|
+
trailing_newline: bool = False,
|
|
449
|
+
) -> str:
|
|
450
|
+
"""Serialize only bounded, recursively valid JSON with ``allow_nan=False``."""
|
|
451
|
+
|
|
452
|
+
validate_json_value(value, source=source, limits=limits)
|
|
453
|
+
try:
|
|
454
|
+
rendered = json.dumps(
|
|
455
|
+
value,
|
|
456
|
+
ensure_ascii=ensure_ascii,
|
|
457
|
+
sort_keys=sort_keys,
|
|
458
|
+
separators=separators,
|
|
459
|
+
indent=indent,
|
|
460
|
+
allow_nan=False,
|
|
461
|
+
)
|
|
462
|
+
except (TypeError, ValueError) as exc:
|
|
463
|
+
raise StrictJsonError(
|
|
464
|
+
"json_serialization_failed", source, f"cannot serialize JSON: {exc}"
|
|
465
|
+
) from exc
|
|
466
|
+
if trailing_newline:
|
|
467
|
+
rendered += "\n"
|
|
468
|
+
encoded = _text_as_utf8(rendered, source=source, limits=limits)
|
|
469
|
+
_check_byte_count(encoded, source, limits)
|
|
470
|
+
return rendered
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
__all__ = [
|
|
474
|
+
"ARTIFACT_JSON_LIMITS",
|
|
475
|
+
"BASELINE_JSON_LIMITS",
|
|
476
|
+
"CHECK_JSON_LIMITS",
|
|
477
|
+
"EVIDENCE_JSON_LIMITS",
|
|
478
|
+
"POLICY_JSON_LIMITS",
|
|
479
|
+
"SARIF_JSON_LIMITS",
|
|
480
|
+
"StrictJsonError",
|
|
481
|
+
"StrictJsonLimits",
|
|
482
|
+
"decode_utf8",
|
|
483
|
+
"dumps_strict",
|
|
484
|
+
"load_file",
|
|
485
|
+
"loads_bytes",
|
|
486
|
+
"loads_text",
|
|
487
|
+
"read_bounded_bytes",
|
|
488
|
+
"validate_json_value",
|
|
489
|
+
]
|
config/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged Metrolith policy data."""
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.5.0",
|
|
3
|
+
"excluded_directories": [
|
|
4
|
+
".git", ".gradle", ".hg", ".idea", ".mypy_cache", ".next",
|
|
5
|
+
".nuxt", ".pytest_cache", ".svn", ".tox", ".venv", ".vscode",
|
|
6
|
+
"__pycache__", "bin", "bower_components", "build", "coverage",
|
|
7
|
+
"deps", "dist", "generated", "node_modules", "obj", "out",
|
|
8
|
+
"target", "third-party", "third_party", "vendor", "vendors", "venv"
|
|
9
|
+
],
|
|
10
|
+
"dependency_directories": [
|
|
11
|
+
"bower_components", "deps", "node_modules", "third-party",
|
|
12
|
+
"third_party", "vendor", "vendors"
|
|
13
|
+
],
|
|
14
|
+
"build_output_directories": [
|
|
15
|
+
".next", ".nuxt", "bin", "build", "coverage", "dist", "obj", "out", "target"
|
|
16
|
+
],
|
|
17
|
+
"cache_directories": [
|
|
18
|
+
".gradle", ".mypy_cache", ".pytest_cache", ".tox", ".venv",
|
|
19
|
+
"__pycache__", "venv"
|
|
20
|
+
],
|
|
21
|
+
"test_directories": [
|
|
22
|
+
"__tests__", "componenttest", "componenttests", "contracttest",
|
|
23
|
+
"contracttests", "e2e", "fixture", "fixtures", "functionaltest",
|
|
24
|
+
"functionaltests", "integration-test", "integration-tests",
|
|
25
|
+
"integrationtest", "integrationtests", "spec", "specs", "test",
|
|
26
|
+
"testdata", "tests", "unittest", "unittests"
|
|
27
|
+
],
|
|
28
|
+
"generated_suffixes": [
|
|
29
|
+
".gen.go", ".gen.java", ".gen.js", ".gen.jsx", ".gen.ts", ".gen.tsx",
|
|
30
|
+
".generated.go", ".generated.java", ".generated.js", ".generated.jsx",
|
|
31
|
+
".generated.ts", ".generated.tsx", ".generated.mjs", ".generated.cjs",
|
|
32
|
+
".generated.mts", ".generated.cts", ".pb.go", ".pb.gw.go", "_generated.go"
|
|
33
|
+
],
|
|
34
|
+
"minified_or_bundle_suffixes": [
|
|
35
|
+
".bundle.js", ".bundle.mjs", ".bundle.cjs", ".bundle.ts", ".bundle.tsx",
|
|
36
|
+
".bundle.min.js", ".chunk.js", ".chunk.mjs", ".min.js", ".min.mjs",
|
|
37
|
+
".min.cjs", ".min.ts", ".min.tsx"
|
|
38
|
+
]
|
|
39
|
+
}
|
examples/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
The beginner example is `local_project/app.py` and `local_project/web.js`.
|
|
4
|
+
Use CPython 3.13 (`>=3.13,<3.14`; tested 3.13.9) and Git on PATH.
|
|
5
|
+
Git is required even though these files are not a Git repository.
|
|
6
|
+
Run it without network access after installation:
|
|
7
|
+
|
|
8
|
+
```console
|
|
9
|
+
metrolith example run --local
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The command copies the packaged sources into controlled temporary storage and
|
|
13
|
+
runs the normal analysis pipeline. Read the printed Run path and `summary.md`;
|
|
14
|
+
`metrolith report RUN` creates an offline HTML report.
|
|
15
|
+
|
|
16
|
+
From the source project you can use the same files directly:
|
|
17
|
+
|
|
18
|
+
```console
|
|
19
|
+
metrolith analyze examples/local_project --workspace ../metrolith-demo
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`quickstart_repositories.csv` supports the existing optional `example run`
|
|
23
|
+
language selections. It contains exact frozen commits; network access or an
|
|
24
|
+
already populated cache is required. `input_template.csv` documents the CSV
|
|
25
|
+
columns for explicit repository-list workflows and is not an offline demo.
|
|
26
|
+
|
|
27
|
+
`ratchet-rules.json` is an illustrative rule array for `baseline capture`.
|
|
28
|
+
Choose your own tolerance. Capture requires a qualifying exact-revision Run;
|
|
29
|
+
the baseline's `.source-run/` sidecar and the current CLI ancestry limitation
|
|
30
|
+
are explained in [USAGE](../docs/USAGE.md) in the source distribution.
|
|
31
|
+
|
|
32
|
+
Start with the [source setup](../README.md); see [USAGE](../docs/USAGE.md)
|
|
33
|
+
for quoted Run paths and the example wrapper's exit 1 on preflight refusal.
|
examples/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Packaged Metrolith quickstart inputs."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Small, deterministic Python half of the network-free tutorial fixture."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def classify_order(total: float, item_count: int) -> str:
|
|
5
|
+
if item_count <= 0:
|
|
6
|
+
return "empty"
|
|
7
|
+
if total >= 100 and item_count >= 3:
|
|
8
|
+
return "large"
|
|
9
|
+
if total >= 25:
|
|
10
|
+
return "medium"
|
|
11
|
+
return "small"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def summarize_orders(orders: list[dict[str, float]]) -> dict[str, float]:
|
|
15
|
+
total = sum(float(order.get("total", 0)) for order in orders)
|
|
16
|
+
return {"count": len(orders), "total": total}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Small JavaScript half of the network-free tutorial fixture. */
|
|
2
|
+
export function visibleOrders(orders, includeArchived = false) {
|
|
3
|
+
return orders.filter((order) => {
|
|
4
|
+
if (includeArchived) return true;
|
|
5
|
+
return order.status !== "archived";
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function orderLabel(order) {
|
|
10
|
+
const customer = order.customer || "Anonymous";
|
|
11
|
+
return `${customer}: ${order.status || "unknown"}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
url,architecture_type,expected_language,commit_sha,enabled,notes
|
|
2
|
+
https://github.com/MstfTurgut/hotel-reservation-system,unknown,Java,0912d283fc54caadc361740e675271b0c69be336,true,Frozen Java tutorial
|
|
3
|
+
https://github.com/googlecodelabs/monolith-to-microservices,unknown,JavaScript,deeb3099f6a29c0ec8b92a20013fdc94f69f0d47,true,Frozen JavaScript tutorial
|
|
4
|
+
https://github.com/allegro/ralph,unknown,Python,f0d695e9b24a7d1fa475b81bbee87a897e389eb2,true,Frozen Python tutorial; Complete repository and Python metrics in candidate evidence
|
|
5
|
+
https://github.com/golang-app/ecommerce,unknown,Go,5708b2b784f83825c2550abdc35d1d330e4b1283,true,Frozen Go tutorial
|