domainforge 0.13.0
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.
- package/.cargo/config.toml +6 -0
- package/.claude/settings.local.json +18 -0
- package/.coderabbit.yml +43 -0
- package/.codex/skills/release-management/SKILL.md +151 -0
- package/.codex/skills/release-management/agents/openai.yaml +4 -0
- package/.github/actions/decrypt-secrets/action.yml +121 -0
- package/.github/agents/Coder.agent.md +97 -0
- package/.github/agents/DeepResearch.agent.md +61 -0
- package/.github/chatmodes/tdd.vibepro.chatmode.md +1183 -0
- package/.github/copilot-instructions.md +13 -0
- package/.github/dependabot.yml +68 -0
- package/.github/workflows/README.md +165 -0
- package/.github/workflows/ci.yml +335 -0
- package/.github/workflows/dependabot-automerge.yml +114 -0
- package/.github/workflows/dependency-review.yml +27 -0
- package/.github/workflows/deploy.yml +87 -0
- package/.github/workflows/prepare-release.yml +168 -0
- package/.github/workflows/release-crates.yml +42 -0
- package/.github/workflows/release-npm.yml +137 -0
- package/.github/workflows/release-please.yml +29 -0
- package/.github/workflows/release-pypi.yml +96 -0
- package/.gitkeep +1 -0
- package/.release-please-manifest.json +5 -0
- package/.sea-registry.toml +10 -0
- package/.serena/project.yml +133 -0
- package/.sops.yaml +10 -0
- package/AGENTS.md +216 -0
- package/CHANGELOG.md +400 -0
- package/CLAUDE.md +62 -0
- package/CONTRIBUTING.md +323 -0
- package/Cargo.lock +3612 -0
- package/Cargo.toml +12 -0
- package/LICENSE +201 -0
- package/README.md +660 -0
- package/README_PYTHON.md +256 -0
- package/README_TYPESCRIPT.md +305 -0
- package/README_WASM.md +329 -0
- package/RELEASE_NOTES.md +41 -0
- package/bun.lock +378 -0
- package/bunfig.toml +11 -0
- package/check_output.txt +83 -0
- package/clippy_output.txt +80 -0
- package/commitlint.config.cjs +8 -0
- package/deny.toml +42 -0
- package/devbox.json +14 -0
- package/devbox.lock +76 -0
- package/docs/RELEASE_PROCESS.md +360 -0
- package/docs/diagnostics.md +161 -0
- package/docs/doc_guidelines.md +53 -0
- package/docs/explanations/README.md +21 -0
- package/docs/explanations/architecture-overview.md +109 -0
- package/docs/explanations/cross-language-binding-strategy.md +68 -0
- package/docs/explanations/graph-store-design.md +47 -0
- package/docs/explanations/performance-benchmarks.md +63 -0
- package/docs/explanations/policy-evaluation-logic.md +106 -0
- package/docs/explanations/semantic-modeling-concepts.md +109 -0
- package/docs/explanations/three-valued-logic.md +66 -0
- package/docs/explanations/versioning-strategy.md +45 -0
- package/docs/governance.md +168 -0
- package/docs/how-tos/README.md +46 -0
- package/docs/how-tos/ci-cd-validation.md +93 -0
- package/docs/how-tos/create-custom-units.md +125 -0
- package/docs/how-tos/define-policies.md +119 -0
- package/docs/how-tos/export-to-calm.md +110 -0
- package/docs/how-tos/export-to-protobuf.md +312 -0
- package/docs/how-tos/extend-grammar.md +133 -0
- package/docs/how-tos/generate-rdf-turtle.md +106 -0
- package/docs/how-tos/import-from-calm.md +114 -0
- package/docs/how-tos/import-from-sbvr.md +249 -0
- package/docs/how-tos/install-cli.md +126 -0
- package/docs/how-tos/parse-sea-files.md +132 -0
- package/docs/how-tos/policy-evaluation-modes.md +30 -0
- package/docs/how-tos/run-cross-language-tests.md +115 -0
- package/docs/how-tos/troubleshoot-napi-builds.md +55 -0
- package/docs/how-tos/use-modules-imports.md +285 -0
- package/docs/index.md +13 -0
- package/docs/plans/canonical-normalizer.md +121 -0
- package/docs/plans/cd_improvement.md +112 -0
- package/docs/plans/cli-ast.md +29 -0
- package/docs/plans/expression-bindings-and-normalizer-integration.md +174 -0
- package/docs/plans/protobuf_advanced_features_plan.md +597 -0
- package/docs/plans/protobuf_plan.yml +525 -0
- package/docs/plans/refactor_dsl_architecture.md +131 -0
- package/docs/plans/release-plan.md +163 -0
- package/docs/plans/sea_fmt_implementation_plan.md +516 -0
- package/docs/playbooks/README.md +18 -0
- package/docs/playbooks/adding-new-primitive.md +68 -0
- package/docs/playbooks/debugging-parser-failures.md +42 -0
- package/docs/playbooks/local-release-preparation.md +139 -0
- package/docs/playbooks/migrating-schema-versions.md +43 -0
- package/docs/playbooks/onboarding-contributors.md +64 -0
- package/docs/playbooks/releasing-beta.md +86 -0
- package/docs/playbooks/secret-management.md +64 -0
- package/docs/reference/README.md +199 -0
- package/docs/reference/ast-json-api.md +427 -0
- package/docs/reference/calm-mapping.md +519 -0
- package/docs/reference/cli-commands.md +588 -0
- package/docs/reference/configuration.md +202 -0
- package/docs/reference/error-codes.md +664 -0
- package/docs/reference/generated-artifacts-policy.md +53 -0
- package/docs/reference/grammar-spec.md +255 -0
- package/docs/reference/primitives-api.md +317 -0
- package/docs/reference/protobuf-api.md +426 -0
- package/docs/reference/python-api.md +485 -0
- package/docs/reference/registry.md +50 -0
- package/docs/reference/sea-dsl-ai-cheatsheet.yaml +913 -0
- package/docs/reference/security-model.md +74 -0
- package/docs/reference/typescript-api.md +508 -0
- package/docs/reference/wasm-api.md +420 -0
- package/docs/semantic-pack-review.md +144 -0
- package/docs/semantic-pack-signing.md +234 -0
- package/docs/semantic-packs.md +284 -0
- package/docs/specs/ADR-001-sea-dsl-semantic-source-of-truth.md +33 -0
- package/docs/specs/ADR-002-projection-first-class-construct.md +50 -0
- package/docs/specs/ADR-003-protobuf-projection-target.md +51 -0
- package/docs/specs/ADR-004-projection-compatibility-semantics.md +57 -0
- package/docs/specs/ADR-005-multi-language-support-strategy.md +112 -0
- package/docs/specs/ADR-006-error-handling-strategy.md +115 -0
- package/docs/specs/ADR-007-policy-evaluation-engine.md +95 -0
- package/docs/specs/ADR-008-knowledge-graph-integration.md +90 -0
- package/docs/specs/ADR-009-module-resolution-strategy.md +115 -0
- package/docs/specs/ADR-010-unit-system.md +106 -0
- package/docs/specs/PRD-001-sea-projection-framework.md +155 -0
- package/docs/specs/PRD-002-sea-cli-tooling.md +169 -0
- package/docs/specs/PRD-003-dsl-core-capabilities.md +275 -0
- package/docs/specs/README.md +62 -0
- package/docs/specs/SDS-001-protobuf-projection-engine.md +451 -0
- package/docs/specs/SDS-002-sea-core-architecture.md +268 -0
- package/docs/specs/SDS-003-parser-semantic-graph.md +377 -0
- package/docs/specs/SDS-004-policy-engine-design.md +362 -0
- package/docs/specs/SDS-005-knowledge-graph-module.md +364 -0
- package/docs/specs/SDS-006-calm-integration.md +367 -0
- package/docs/specs/SDS-007-sbvr-import.md +347 -0
- package/docs/templates/template_explanation.md +14 -0
- package/docs/templates/template_howto.md +21 -0
- package/docs/templates/template_playbook.md +21 -0
- package/docs/templates/template_reference.md +17 -0
- package/docs/templates/template_tutorial.md +24 -0
- package/docs/tutorials/README.md +12 -0
- package/docs/tutorials/first-sea-model.md +85 -0
- package/docs/tutorials/getting-started.md +98 -0
- package/docs/tutorials/python-binding-quickstart.md +107 -0
- package/docs/tutorials/typescript-binding-quickstart.md +91 -0
- package/docs/tutorials/wasm-in-browser.md +75 -0
- package/domainforge-core/CHANGELOG.md +138 -0
- package/domainforge-core/Cargo.toml +101 -0
- package/domainforge-core/MIGRATING.md +32 -0
- package/domainforge-core/README.md +197 -0
- package/domainforge-core/benchmark_results.txt +51 -0
- package/domainforge-core/build.rs +6 -0
- package/domainforge-core/deny.toml +31 -0
- package/domainforge-core/docs/specs/projections/sbvr_kg_mapping.md +43 -0
- package/domainforge-core/examples/basic.sea +7 -0
- package/domainforge-core/examples/cli/import_export_workflow.sh +38 -0
- package/domainforge-core/examples/cli/validate_example.sh +30 -0
- package/domainforge-core/examples/evolution_semantics.sea +31 -0
- package/domainforge-core/examples/parser_demo.rs +203 -0
- package/domainforge-core/grammar/sea.pest +408 -0
- package/domainforge-core/schemas/calm-v1.schema.json +170 -0
- package/domainforge-core/schemas/shacl/sea_shapes.ttl +19 -0
- package/domainforge-core/src/authority/compiler.rs +309 -0
- package/domainforge-core/src/authority/environment.rs +203 -0
- package/domainforge-core/src/authority/error.rs +164 -0
- package/domainforge-core/src/authority/fact_resolver.rs +224 -0
- package/domainforge-core/src/authority/mod.rs +25 -0
- package/domainforge-core/src/authority/pack.rs +133 -0
- package/domainforge-core/src/authority/policy.rs +224 -0
- package/domainforge-core/src/authority/resolver.rs +446 -0
- package/domainforge-core/src/authority/trace.rs +217 -0
- package/domainforge-core/src/authority/transform.rs +168 -0
- package/domainforge-core/src/authority/types.rs +617 -0
- package/domainforge-core/src/bin/domainforge.rs +25 -0
- package/domainforge-core/src/calm/export.rs +538 -0
- package/domainforge-core/src/calm/import.rs +1220 -0
- package/domainforge-core/src/calm/mod.rs +9 -0
- package/domainforge-core/src/calm/models.rs +108 -0
- package/domainforge-core/src/calm/sbvr_import.rs +9 -0
- package/domainforge-core/src/cli/authority.rs +149 -0
- package/domainforge-core/src/cli/format.rs +85 -0
- package/domainforge-core/src/cli/import.rs +133 -0
- package/domainforge-core/src/cli/mod.rs +64 -0
- package/domainforge-core/src/cli/normalize.rs +180 -0
- package/domainforge-core/src/cli/pack.rs +904 -0
- package/domainforge-core/src/cli/parse.rs +112 -0
- package/domainforge-core/src/cli/project.rs +294 -0
- package/domainforge-core/src/cli/registry.rs +41 -0
- package/domainforge-core/src/cli/test.rs +12 -0
- package/domainforge-core/src/cli/validate.rs +195 -0
- package/domainforge-core/src/cli/validate_kg.rs +80 -0
- package/domainforge-core/src/concept_id.rs +89 -0
- package/domainforge-core/src/error/diagnostics.rs +426 -0
- package/domainforge-core/src/error/fuzzy.rs +253 -0
- package/domainforge-core/src/error/mod.rs +13 -0
- package/domainforge-core/src/formatter/comments.rs +223 -0
- package/domainforge-core/src/formatter/config.rs +114 -0
- package/domainforge-core/src/formatter/mod.rs +22 -0
- package/domainforge-core/src/formatter/printer.rs +906 -0
- package/domainforge-core/src/graph/mod.rs +858 -0
- package/domainforge-core/src/graph/to_ast.rs +66 -0
- package/domainforge-core/src/kg.rs +1476 -0
- package/domainforge-core/src/kg_import.rs +251 -0
- package/domainforge-core/src/lib.rs +203 -0
- package/domainforge-core/src/module/mod.rs +1 -0
- package/domainforge-core/src/module/resolver.rs +260 -0
- package/domainforge-core/src/parser/ast.rs +2919 -0
- package/domainforge-core/src/parser/ast_convert.rs +494 -0
- package/domainforge-core/src/parser/ast_schema.rs +491 -0
- package/domainforge-core/src/parser/error.rs +291 -0
- package/domainforge-core/src/parser/lint.rs +39 -0
- package/domainforge-core/src/parser/mod.rs +193 -0
- package/domainforge-core/src/parser/printer.rs +702 -0
- package/domainforge-core/src/parser/profiles.rs +71 -0
- package/domainforge-core/src/parser/string_utils.rs +138 -0
- package/domainforge-core/src/patterns.rs +68 -0
- package/domainforge-core/src/policy/core.rs +1148 -0
- package/domainforge-core/src/policy/expression.rs +399 -0
- package/domainforge-core/src/policy/mod.rs +18 -0
- package/domainforge-core/src/policy/normalize.rs +1028 -0
- package/domainforge-core/src/policy/quantifier.rs +940 -0
- package/domainforge-core/src/policy/three_valued.rs +140 -0
- package/domainforge-core/src/policy/three_valued_microbench.rs +104 -0
- package/domainforge-core/src/policy/type_inference.rs +67 -0
- package/domainforge-core/src/policy/violation.rs +36 -0
- package/domainforge-core/src/primitives/concept_change.rs +61 -0
- package/domainforge-core/src/primitives/entity.rs +224 -0
- package/domainforge-core/src/primitives/flow.rs +111 -0
- package/domainforge-core/src/primitives/instance.rs +93 -0
- package/domainforge-core/src/primitives/mapping_contract.rs +50 -0
- package/domainforge-core/src/primitives/metric.rs +79 -0
- package/domainforge-core/src/primitives/mod.rs +25 -0
- package/domainforge-core/src/primitives/projection_contract.rs +50 -0
- package/domainforge-core/src/primitives/quantity.rs +56 -0
- package/domainforge-core/src/primitives/relation.rs +68 -0
- package/domainforge-core/src/primitives/resource.rs +237 -0
- package/domainforge-core/src/primitives/resource_instance.rs +88 -0
- package/domainforge-core/src/primitives/role.rs +49 -0
- package/domainforge-core/src/projection/buf.rs +404 -0
- package/domainforge-core/src/projection/contracts.rs +22 -0
- package/domainforge-core/src/projection/engine.rs +19 -0
- package/domainforge-core/src/projection/mod.rs +16 -0
- package/domainforge-core/src/projection/protobuf.rs +3331 -0
- package/domainforge-core/src/projection/registry.rs +43 -0
- package/domainforge-core/src/python/authority.rs +253 -0
- package/domainforge-core/src/python/error.rs +227 -0
- package/domainforge-core/src/python/formatter.rs +86 -0
- package/domainforge-core/src/python/graph.rs +366 -0
- package/domainforge-core/src/python/mod.rs +9 -0
- package/domainforge-core/src/python/policy.rs +651 -0
- package/domainforge-core/src/python/primitives.rs +796 -0
- package/domainforge-core/src/python/registry.rs +98 -0
- package/domainforge-core/src/python/semantic_pack.rs +619 -0
- package/domainforge-core/src/python/units.rs +96 -0
- package/domainforge-core/src/registry/mod.rs +432 -0
- package/domainforge-core/src/registry/tests.rs +210 -0
- package/domainforge-core/src/sbvr.rs +744 -0
- package/domainforge-core/src/semantic_pack/builder.rs +470 -0
- package/domainforge-core/src/semantic_pack/canonical_json.rs +184 -0
- package/domainforge-core/src/semantic_pack/diagnostics.rs +214 -0
- package/domainforge-core/src/semantic_pack/diff.rs +216 -0
- package/domainforge-core/src/semantic_pack/mod.rs +31 -0
- package/domainforge-core/src/semantic_pack/pack_set.rs +240 -0
- package/domainforge-core/src/semantic_pack/resolver.rs +437 -0
- package/domainforge-core/src/semantic_pack/review.rs +125 -0
- package/domainforge-core/src/semantic_pack/schema.rs +342 -0
- package/domainforge-core/src/semantic_pack/signing.rs +105 -0
- package/domainforge-core/src/semantic_pack/validator.rs +368 -0
- package/domainforge-core/src/semantic_version.rs +140 -0
- package/domainforge-core/src/test_utils.rs +12 -0
- package/domainforge-core/src/typescript/authority.rs +184 -0
- package/domainforge-core/src/typescript/error.rs +146 -0
- package/domainforge-core/src/typescript/formatter.rs +76 -0
- package/domainforge-core/src/typescript/graph.rs +391 -0
- package/domainforge-core/src/typescript/mod.rs +9 -0
- package/domainforge-core/src/typescript/policy.rs +564 -0
- package/domainforge-core/src/typescript/primitives.rs +784 -0
- package/domainforge-core/src/typescript/registry.rs +88 -0
- package/domainforge-core/src/typescript/semantic_pack.rs +470 -0
- package/domainforge-core/src/typescript/units.rs +76 -0
- package/domainforge-core/src/units/mod.rs +462 -0
- package/domainforge-core/src/uuid_module.rs +42 -0
- package/domainforge-core/src/validation_error.rs +818 -0
- package/domainforge-core/src/validation_result.rs +30 -0
- package/domainforge-core/src/wasm/authority.rs +192 -0
- package/domainforge-core/src/wasm/error.rs +145 -0
- package/domainforge-core/src/wasm/formatter.rs +69 -0
- package/domainforge-core/src/wasm/graph.rs +471 -0
- package/domainforge-core/src/wasm/mod.rs +16 -0
- package/domainforge-core/src/wasm/policy.rs +607 -0
- package/domainforge-core/src/wasm/primitives.rs +295 -0
- package/domainforge-core/src/wasm/semantic_pack.rs +471 -0
- package/domainforge-core/src/wasm/units.rs +62 -0
- package/domainforge-core/std/aws.sea +6 -0
- package/domainforge-core/std/core.sea +6 -0
- package/domainforge-core/std/http.sea +27 -0
- package/domainforge-core/tests/aggregation_enhanced_tests.rs +162 -0
- package/domainforge-core/tests/aggregation_eval_tests.rs +248 -0
- package/domainforge-core/tests/aggregation_integration_tests.rs +379 -0
- package/domainforge-core/tests/aggregation_parser_tests.rs +92 -0
- package/domainforge-core/tests/aggregation_tests.rs +102 -0
- package/domainforge-core/tests/authority_conformance_tests.rs +1173 -0
- package/domainforge-core/tests/calm_round_trip_tests.rs +283 -0
- package/domainforge-core/tests/calm_schema_validation_tests.rs +137 -0
- package/domainforge-core/tests/cast_operator_tests.rs +85 -0
- package/domainforge-core/tests/cli_binary_check.rs +37 -0
- package/domainforge-core/tests/cli_import_tests.rs +291 -0
- package/domainforge-core/tests/cli_path_traversal_tests.rs +124 -0
- package/domainforge-core/tests/cli_tests.rs +63 -0
- package/domainforge-core/tests/diagnostics_tests.rs +203 -0
- package/domainforge-core/tests/dimension_unit_tests.rs +80 -0
- package/domainforge-core/tests/entity_tests.rs +69 -0
- package/domainforge-core/tests/evolution_semantics_tests.rs +157 -0
- package/domainforge-core/tests/flow_tests.rs +78 -0
- package/domainforge-core/tests/flow_unit_validation_tests.rs +31 -0
- package/domainforge-core/tests/graph_integration_tests.rs +218 -0
- package/domainforge-core/tests/graph_tests.rs +626 -0
- package/domainforge-core/tests/import_parsing_tests.rs +23 -0
- package/domainforge-core/tests/instance_integration_tests.rs +98 -0
- package/domainforge-core/tests/instance_parsing_tests.rs +58 -0
- package/domainforge-core/tests/instance_tests.rs +61 -0
- package/domainforge-core/tests/kg_uri_encoding_tests.rs +53 -0
- package/domainforge-core/tests/lint_tests.rs +19 -0
- package/domainforge-core/tests/metric_tests.rs +143 -0
- package/domainforge-core/tests/module_resolution_tests.rs +100 -0
- package/domainforge-core/tests/namespace_registry_tests.rs +247 -0
- package/domainforge-core/tests/null_handling_tests.rs +26 -0
- package/domainforge-core/tests/parser_ast_v3.rs +53 -0
- package/domainforge-core/tests/parser_dimension_registry_tests.rs +20 -0
- package/domainforge-core/tests/parser_integration_tests.rs +294 -0
- package/domainforge-core/tests/parser_metadata_tests.rs +97 -0
- package/domainforge-core/tests/parser_resource_domain_only_graph_test.rs +21 -0
- package/domainforge-core/tests/parser_resource_limits_tests.rs +122 -0
- package/domainforge-core/tests/parser_tests.rs +512 -0
- package/domainforge-core/tests/pattern_semantics_tests.rs +87 -0
- package/domainforge-core/tests/phase_14_determinism_tests.rs +166 -0
- package/domainforge-core/tests/phase_15_validation_error_tests.rs +136 -0
- package/domainforge-core/tests/phase_16_unicode_tests.rs +248 -0
- package/domainforge-core/tests/phase_17_export_tests.rs +285 -0
- package/domainforge-core/tests/phase_17_round_trip_tests.rs +264 -0
- package/domainforge-core/tests/policy_tests.rs +635 -0
- package/domainforge-core/tests/primitives_integration_tests.rs +151 -0
- package/domainforge-core/tests/print_rdf_xml.rs +14 -0
- package/domainforge-core/tests/printer_tests.rs +204 -0
- package/domainforge-core/tests/profile_tests.rs +35 -0
- package/domainforge-core/tests/projection_contracts_tests.rs +154 -0
- package/domainforge-core/tests/protobuf_projection_tests.rs +199 -0
- package/domainforge-core/tests/quantity_tests.rs +41 -0
- package/domainforge-core/tests/rdf_xml_typed_literal_tests.rs +105 -0
- package/domainforge-core/tests/registry_schema_tests.rs +33 -0
- package/domainforge-core/tests/resource_tests.rs +50 -0
- package/domainforge-core/tests/resource_unit_tests.rs +24 -0
- package/domainforge-core/tests/roles_relations_tests.rs +61 -0
- package/domainforge-core/tests/round_trip_tests.rs +34 -0
- package/domainforge-core/tests/runtime_toggle_tests.rs +70 -0
- package/domainforge-core/tests/sbvr_fact_schema_tests.rs +60 -0
- package/domainforge-core/tests/sbvr_flow_facts_tests.rs +55 -0
- package/domainforge-core/tests/sbvr_parsing_tests.rs +53 -0
- package/domainforge-core/tests/semantic_pack_alias_resolution.rs +197 -0
- package/domainforge-core/tests/semantic_pack_build.rs +302 -0
- package/domainforge-core/tests/semantic_pack_consumer_smoke.rs +150 -0
- package/domainforge-core/tests/semantic_pack_pack_set.rs +160 -0
- package/domainforge-core/tests/semantic_pack_signing.rs +157 -0
- package/domainforge-core/tests/semantic_pack_three_valued.rs +250 -0
- package/domainforge-core/tests/semantic_pack_validate.rs +196 -0
- package/domainforge-core/tests/std_lib_tests.rs +37 -0
- package/domainforge-core/tests/temporal_evaluation_tests.rs +159 -0
- package/domainforge-core/tests/temporal_semantics_tests.rs +214 -0
- package/domainforge-core/tests/three_valued_quantifiers_tests.rs +164 -0
- package/domainforge-core/tests/turtle_entity_export_tests.rs +38 -0
- package/domainforge-core/tests/turtle_escaping_tests.rs +53 -0
- package/domainforge-core/tests/turtle_resource_export_tests.rs +34 -0
- package/domainforge-core/tests/type_inference_tests.rs +40 -0
- package/domainforge-core/tests/unicode_validation_tests.rs +169 -0
- package/domainforge-core/tests/unit_tests.rs +81 -0
- package/domainforge-core/tests/validate_tests.rs +38 -0
- package/domainforge-core/tests/validation_unit_mismatch_tests.rs +83 -0
- package/domainforge-core/tests/wasm_tests.rs +229 -0
- package/domainforge-python/CHANGELOG-python.md +12 -0
- package/domainforge-python/MIGRATING.md +24 -0
- package/domainforge-python/README.md +256 -0
- package/domainforge-python/domainforge/__init__.py +95 -0
- package/domainforge-python/domainforge/domainforge.pyi +519 -0
- package/domainforge-python/pyproject.toml +36 -0
- package/domainforge-typescript/CHANGELOG-typescript.md +12 -0
- package/domainforge-typescript/LICENSE +201 -0
- package/domainforge-typescript/MIGRATING.md +24 -0
- package/domainforge-typescript/README.md +305 -0
- package/domainforge-typescript/index.d.ts +452 -0
- package/domainforge-typescript/index.js +361 -0
- package/domainforge-typescript/package.json +60 -0
- package/example.js +61 -0
- package/examples/browser.html +366 -0
- package/examples/namespaces/finance/cashflow.sea +5 -0
- package/examples/namespaces/logistics/core.sea +7 -0
- package/examples/observability_metrics.sea +38 -0
- package/fixtures/semantic_packs/acme_procurement/domain/entities.sea +39 -0
- package/fixtures/semantic_packs/acme_procurement/domain/metrics.sea +11 -0
- package/fixtures/semantic_packs/acme_procurement/domain/relations.sea +7 -0
- package/fixtures/semantic_packs/acme_procurement/domain/resources.sea +9 -0
- package/fixtures/semantic_packs/acme_procurement/review/acme.procurement.semantic-review.jsonl +7 -0
- package/fixtures/semantic_packs/acme_procurement/tests/ambiguous_vendor_alias.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/deprecated_vendor_alias.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/invalid_relation.sea +3 -0
- package/fixtures/semantic_packs/acme_procurement/tests/proposed_concept.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/rejected_concept.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/unit_mismatch.sea +7 -0
- package/fixtures/semantic_packs/acme_procurement/tests/unknown_vendor_policy.sea +8 -0
- package/fixtures/semantic_packs/acme_procurement/tests/valid_purchase_policy.sea +8 -0
- package/index.d.ts +2 -0
- package/index.js +8 -0
- package/justfile +200 -0
- package/lefthook.yml +13 -0
- package/lib/validate_native_exports.d.ts +4 -0
- package/lib/validate_native_exports.js +12 -0
- package/package.json +22 -0
- package/pytest.ini +5 -0
- package/python/tests/test_registry.py +75 -0
- package/python/tests/test_units.py +18 -0
- package/release-please-config.json +49 -0
- package/requirements-dev.txt +3 -0
- package/requirements.txt +3 -0
- package/rust-toolchain.toml +3 -0
- package/schemas/ast-v1.schema.json +72 -0
- package/schemas/ast-v2.schema.json +1200 -0
- package/schemas/ast-v3.schema.json +1200 -0
- package/schemas/sea-registry.schema.json +45 -0
- package/scripts/build-python.sh +37 -0
- package/scripts/build-release.sh +279 -0
- package/scripts/build-typescript.sh +13 -0
- package/scripts/build-wasm.sh +113 -0
- package/scripts/bump-version.sh +245 -0
- package/scripts/check_unused_test_imports.py +85 -0
- package/scripts/ci_tasks.py +379 -0
- package/scripts/clear_debug_test.sh +10 -0
- package/scripts/create-github-release.sh +262 -0
- package/scripts/create-tag.sh +203 -0
- package/scripts/find_and_link_test_binary.sh +70 -0
- package/scripts/generate-changelog.sh +271 -0
- package/scripts/generate-release-notes.sh +205 -0
- package/scripts/lint_release_security.py +96 -0
- package/scripts/lint_release_workflows.py +82 -0
- package/scripts/lint_workflow_gates.py +113 -0
- package/scripts/optimized-wasm-build.sh +61 -0
- package/scripts/patch_napi_types.py +62 -0
- package/scripts/pre-release-check.sh +289 -0
- package/scripts/prepare_rust_debug.sh +52 -0
- package/scripts/release.sh +373 -0
- package/scripts/resolve_rust_binary.py +230 -0
- package/scripts/run_commitlint.sh +29 -0
- package/scripts/test-all.sh +77 -0
- package/scripts/update_launch_program.py +93 -0
- package/secrets/README.md +27 -0
- package/secrets/secrets.yaml +21 -0
- package/test_integration.py +67 -0
- package/tests/test_authority.py +328 -0
- package/tests/test_ci_tasks.py +143 -0
- package/tests/test_expression.py +256 -0
- package/tests/test_golden_payment_flow.py +42 -0
- package/tests/test_graph.py +127 -0
- package/tests/test_instance.py +136 -0
- package/tests/test_parser.py +82 -0
- package/tests/test_primitives.py +68 -0
- package/tests/test_role_relation_parity.py +56 -0
- package/tests/test_runtime_toggle.py +156 -0
- package/tests/test_semantic_pack.py +639 -0
- package/tests/test_three_valued_eval.py +159 -0
- package/tsconfig.json +30 -0
- package/typescript-tests/advanced.test.ts +165 -0
- package/typescript-tests/authority.test.ts +216 -0
- package/typescript-tests/expression.test.ts +228 -0
- package/typescript-tests/golden-payment-flow.test.ts +51 -0
- package/typescript-tests/graph.test.ts +142 -0
- package/typescript-tests/native-binding.test.ts +20 -0
- package/typescript-tests/primitives.test.ts +88 -0
- package/typescript-tests/registry.test.ts +122 -0
- package/typescript-tests/role_relation.test.ts +63 -0
- package/typescript-tests/runtime_toggle.test.ts +141 -0
- package/typescript-tests/semantic-pack.test.ts +556 -0
- package/typescript-tests/three_valued_eval.test.ts +135 -0
- package/typescript-tests/units.test.ts +36 -0
- package/vitest.config.ts +13 -0
- package/wasm_demo.html +225 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Update `.vscode/launch.json` program path for "Debug Rust Test (auto)" configuration
|
|
4
|
+
to point at the prepared debug binary `target/debug/deps/sea_debug_test`.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 scripts/update_launch_program.py --program <path>
|
|
8
|
+
|
|
9
|
+
If the file does not exist, writes the default program path.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_config_index(configs, name):
|
|
19
|
+
for idx, c in enumerate(configs):
|
|
20
|
+
if c.get("name") == name:
|
|
21
|
+
return idx
|
|
22
|
+
return -1
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main():
|
|
26
|
+
parser = argparse.ArgumentParser()
|
|
27
|
+
parser.add_argument("--program", help="Path to the rust test binary (symlink)")
|
|
28
|
+
parser.add_argument("--launch", default=".vscode/launch.json")
|
|
29
|
+
parser.add_argument("--config-name", default="Debug Rust Test (auto)")
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
|
|
32
|
+
# Prefer a workspace-relative program path so launch.json remains portable
|
|
33
|
+
program_path = args.program or os.path.join(
|
|
34
|
+
"${workspaceFolder}", "target", "debug", "deps", "sea_debug_test"
|
|
35
|
+
)
|
|
36
|
+
# If program_path is absolute and inside the workspace, replace the prefix with ${workspaceFolder}
|
|
37
|
+
workspace_prefix = os.path.abspath(".")
|
|
38
|
+
if "${workspaceFolder}" not in program_path:
|
|
39
|
+
abs_program_path = os.path.abspath(program_path)
|
|
40
|
+
normalized_workspace = os.path.normcase(os.path.normpath(workspace_prefix))
|
|
41
|
+
normalized_program = os.path.normcase(os.path.normpath(abs_program_path))
|
|
42
|
+
if normalized_program.startswith(normalized_workspace):
|
|
43
|
+
rel_path = os.path.relpath(abs_program_path, workspace_prefix)
|
|
44
|
+
program_path = os.path.join("${workspaceFolder}", rel_path).replace("\\", "/")
|
|
45
|
+
else:
|
|
46
|
+
program_path = abs_program_path.replace("\\", "/")
|
|
47
|
+
launch_path = args.launch
|
|
48
|
+
|
|
49
|
+
if not os.path.exists(launch_path):
|
|
50
|
+
print(
|
|
51
|
+
f"Warning: {launch_path} not found, creating a new launch.json with default config"
|
|
52
|
+
)
|
|
53
|
+
launch = {"version": "0.2.0", "configurations": []}
|
|
54
|
+
else:
|
|
55
|
+
try:
|
|
56
|
+
with open(launch_path, "r") as f:
|
|
57
|
+
launch = json.load(f)
|
|
58
|
+
except json.JSONDecodeError as err:
|
|
59
|
+
print(f"Failed to parse {launch_path}: {err}")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
configs = launch.setdefault("configurations", [])
|
|
63
|
+
idx = find_config_index(configs, args.config_name)
|
|
64
|
+
|
|
65
|
+
if idx == -1:
|
|
66
|
+
# If not found, add a new configuration stub
|
|
67
|
+
configs.append(
|
|
68
|
+
{
|
|
69
|
+
"name": args.config_name,
|
|
70
|
+
"type": "codelldb",
|
|
71
|
+
"request": "launch",
|
|
72
|
+
"program": program_path,
|
|
73
|
+
"args": ["--nocapture", "${input:rustTestName}"],
|
|
74
|
+
"cwd": "${workspaceFolder}",
|
|
75
|
+
"stopAtEntry": False,
|
|
76
|
+
"terminal": "integrated",
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
print(f"Added {args.config_name} to launch.json with program={program_path}")
|
|
80
|
+
else:
|
|
81
|
+
configs[idx]["program"] = program_path
|
|
82
|
+
print(f"Updated {args.config_name} program to {program_path}")
|
|
83
|
+
|
|
84
|
+
launch_dir = os.path.dirname(launch_path)
|
|
85
|
+
if launch_dir:
|
|
86
|
+
os.makedirs(launch_dir, exist_ok=True)
|
|
87
|
+
with open(launch_path, "w") as f:
|
|
88
|
+
json.dump(launch, f, indent=2)
|
|
89
|
+
print(f"Wrote updated launch.json: {launch_path}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Secrets directory
|
|
2
|
+
|
|
3
|
+
This folder contains templates for project secrets and instructions to encrypt them using SOPS and Age.
|
|
4
|
+
|
|
5
|
+
- `secrets.template.yaml` — placeholders only. Do not add real secrets here.
|
|
6
|
+
- `secrets.yaml` — recommended final encrypted file, committed to the repo as encrypted by sops.
|
|
7
|
+
|
|
8
|
+
Quick usage:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# copy the template to a file, populate it with real values (locally only)
|
|
12
|
+
cp secrets/secrets.template.yaml secrets/secrets.yaml
|
|
13
|
+
# encrypt in-place (replace AGE recipient)
|
|
14
|
+
sops --encrypt --age "age1PUBKEY" --in-place secrets/secrets.yaml
|
|
15
|
+
# verify, commit
|
|
16
|
+
git add secrets/secrets.yaml && git commit -m "Add encrypted secrets"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Get your age public key (example):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# generate a new key pair and display the public key
|
|
23
|
+
age-keygen -o ~/.config/age/identity.key
|
|
24
|
+
# the command prints "Public key: age1..." which you can use in .sops.yaml or sops --age
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you accidentally commit unencrypted secrets, remove them from git history, rotate any keys, and inform the team.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#ENC[AES256_GCM,data:6pK7hcKpV/T/PZUR6LoVAFhLk/QI2HS7y9743J+IQRcEzzaNCIAg8f86POhGLwC1tl7m4nIcRA==,iv:7dZQwgSghllovVRcTylOifbL8jV+WV8kJOijpGyPg6M=,tag:TlNZlriOr9yQLwmaBeGrmA==,type:comment]
|
|
2
|
+
#ENC[AES256_GCM,data:RxrXMhdDFDkWjGUZMXMUGp+esb6lcIooaEuG3wAUi0R4pmzjD6BzQktHo5pcHrqA5UmvE5+rZg==,iv:OqpXUv5NsmOAllRFC/axsR8BFHx48iQzJauo3ZrGPUE=,tag:TVY8WqkSSWQivmVTbMWSZw==,type:comment]
|
|
3
|
+
PYPI_TEST_API_TOKEN: ENC[AES256_GCM,data:pfbrKqZpTIOmvrjbPR8PwysN8PskPLSgKBmeYtL/HVhPpQVRM6zs/gAB1Kvey2sx4YaL8KfQdJd+T9JnOQuMDgH0EqYsMS7I7tuP4dJbQtOL0hFRuJBPs+nTNeSQ6LV95zW6curqNhXC9shYIg3jQNiCej2mewlYEshnb19MwSVyCLKZ3eejEEmfFadQN0KPPJWX9l6nxsepZ9ke8v9gVRMjT37Sf/sN5oGDPv18ry72bNBLPvNvxjc=,iv:fWLP0faTnWPrWfvyd2GEr3+Qn0Ls7Rk4VbKosMioKEA=,tag:YUm3PYmCQFSoUoZ3Php9+w==,type:str]
|
|
4
|
+
PYPI_API_TOKEN: ENC[AES256_GCM,data:ErgZWsXEeNvpYFjOooqzIXei8uBUpZkm7NmTS6FrOc/Cn3V53uSWs0Z96eBVh/4DbZCEOBze5EUstWYNjgGpmug/D7a4AE7Y171k/oDDPBfXVQyuQi1FZhGz2eRBxq7KjVyJbc850nyDGGoVpxpGMplXogRS1sQvg+kiV50bxgD9PzkFi5dvzrwoRxONu0keJjgDT50hEev18w4M85paJ4dXufD6gJazuI4sMOGV7U1Mn1Q=,iv:meNgGdAfDMYtPOdtZdXKngF2vOu5wFvkeMN6vvMeRwQ=,tag:tEBH4jhMbW9tFb6ukQ3oqg==,type:str]
|
|
5
|
+
NPM_TOKEN: ENC[AES256_GCM,data:cHmw67qQfoMEVyOR2a0gW6y2mTlU1E5ujbY5t0SHV9DoS2wY9MDiOg==,iv:T5iTNP0jZtf+nsa8HTC8yepqdjmUPISg9biU1p5EMC4=,tag:2yJc5aLgxvIWEUooZdPgYg==,type:str]
|
|
6
|
+
CARGO_REGISTRY_TOKEN: ENC[AES256_GCM,data:8bf8hkiv0myqffATBNYdvJm5tf3P5llxxdyo8C9/YuYeD0k=,iv:zg1qdDwClMSs4LmXR1pqSlla4jOoxaGRE1aNQnDl/s4=,tag:UOekLRYsxcxTLw/0AphXOg==,type:str]
|
|
7
|
+
sops:
|
|
8
|
+
age:
|
|
9
|
+
- recipient: age1mq5sj8gj4k5vqtgefkuvs05nghanzhgmcqkxxspk0vffq9hxm5ssnj93a5
|
|
10
|
+
enc: |
|
|
11
|
+
-----BEGIN AGE ENCRYPTED FILE-----
|
|
12
|
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhZGNoaWFnK0VUR3Y4WWlq
|
|
13
|
+
V2pvU25WZjZPam1WenRzNmtCc0dtOFBQbDBVCjBrd2pXQnlnY0JUVWsrdGZPcFVy
|
|
14
|
+
cXFoUThNWjdDNTJOTXFERWlHK2ZzRUkKLS0tIFUzblkveE5DYXRVUldQemE4T3pC
|
|
15
|
+
dGZabzFkMEVzZDZYcmFZTWFSTkNXejAKPHhdfuiXu4ZCpoM3cQi48Pc0i17yCqph
|
|
16
|
+
QD8lsKfQXdFvbzz6XxXwpsYcXH+eQpvFiMOgrIkmuz7Zegfp+Oa++g==
|
|
17
|
+
-----END AGE ENCRYPTED FILE-----
|
|
18
|
+
lastmodified: "2025-12-07T02:12:19Z"
|
|
19
|
+
mac: ENC[AES256_GCM,data:h9h0FqOkoZFutnzkJ4w4zZ/Yg2aTspObl/Hyt6PcGylZNTsWpaw2VNgvO0pNkzpLO3RoEvcws74YrWAMycP4NR5xml73LXFPEDYNiDnSHqwSaWTJla6ED9oGGEmG2Zs8GDS/UyhtqMsk+pEllmi4C+1TcAp5JhDwVNdLmhHvRVE=,iv:PbWb/uSG/hUciJRGNhPORXITCahACR5HDL6g+5dRzQs=,tag:fA/X0hgGcjlzvTzF0GlHHQ==,type:str]
|
|
20
|
+
unencrypted_suffix: _unencrypted
|
|
21
|
+
version: 3.11.0
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import domainforge
|
|
3
|
+
|
|
4
|
+
# Create a supply chain model
|
|
5
|
+
graph = domainforge.Graph()
|
|
6
|
+
|
|
7
|
+
# Create entities
|
|
8
|
+
supplier = domainforge.Entity("Supplier", "supply_chain")
|
|
9
|
+
warehouse = domainforge.Entity("Warehouse", "supply_chain")
|
|
10
|
+
store = domainforge.Entity("Store", "retail")
|
|
11
|
+
|
|
12
|
+
# Create resources
|
|
13
|
+
widgets = domainforge.Resource("Widgets", "units")
|
|
14
|
+
gadgets = domainforge.Resource("Gadgets", "units")
|
|
15
|
+
|
|
16
|
+
# Add to graph
|
|
17
|
+
graph.add_entity(supplier)
|
|
18
|
+
graph.add_entity(warehouse)
|
|
19
|
+
graph.add_entity(store)
|
|
20
|
+
graph.add_resource(widgets)
|
|
21
|
+
graph.add_resource(gadgets)
|
|
22
|
+
|
|
23
|
+
# Create flows
|
|
24
|
+
flow1 = domainforge.Flow(widgets.id, supplier.id, warehouse.id, 500.0)
|
|
25
|
+
flow2 = domainforge.Flow(widgets.id, warehouse.id, store.id, 300.0)
|
|
26
|
+
flow3 = domainforge.Flow(gadgets.id, supplier.id, warehouse.id, 200.0)
|
|
27
|
+
|
|
28
|
+
graph.add_flow(flow1)
|
|
29
|
+
graph.add_flow(flow2)
|
|
30
|
+
graph.add_flow(flow3)
|
|
31
|
+
|
|
32
|
+
# Query the graph
|
|
33
|
+
print(f"Graph Statistics:")
|
|
34
|
+
print(f" Entities: {graph.entity_count()}")
|
|
35
|
+
print(f" Resources: {graph.resource_count()}")
|
|
36
|
+
print(f" Flows: {graph.flow_count()}")
|
|
37
|
+
print()
|
|
38
|
+
|
|
39
|
+
# Find entity by name
|
|
40
|
+
warehouse_id = graph.find_entity_by_name("Warehouse")
|
|
41
|
+
if warehouse_id is None:
|
|
42
|
+
raise ValueError("Warehouse entity not found in graph")
|
|
43
|
+
print(f"Warehouse ID: {warehouse_id}")
|
|
44
|
+
print()
|
|
45
|
+
|
|
46
|
+
# Get flows from warehouse
|
|
47
|
+
flows_from_warehouse = graph.flows_from(warehouse_id)
|
|
48
|
+
print(f"Flows from Warehouse: {len(flows_from_warehouse)}")
|
|
49
|
+
for flow in flows_from_warehouse:
|
|
50
|
+
print(f" - {flow.quantity} units")
|
|
51
|
+
print()
|
|
52
|
+
|
|
53
|
+
# Parse DSL source
|
|
54
|
+
dsl_source = '''
|
|
55
|
+
Entity "Supplier" in supply_chain
|
|
56
|
+
Entity "Factory" in manufacturing
|
|
57
|
+
Resource "Steel" tons
|
|
58
|
+
Flow "Steel" from "Supplier" to "Factory" quantity 100
|
|
59
|
+
'''
|
|
60
|
+
|
|
61
|
+
parsed = domainforge.Graph.parse(dsl_source)
|
|
62
|
+
print(f"Parsed Graph:")
|
|
63
|
+
print(f" Entities: {parsed.entity_count()}")
|
|
64
|
+
print(f" Resources: {parsed.resource_count()}")
|
|
65
|
+
print(f" Flows: {parsed.flow_count()}")
|
|
66
|
+
|
|
67
|
+
print("\nAll tests passed! Python bindings are working correctly.")
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the new Policy Authority API exports added in this PR.
|
|
3
|
+
|
|
4
|
+
Covers:
|
|
5
|
+
- Enum exports: FinalDecision, PolicyModality, SourceClass, ClaimLevel
|
|
6
|
+
- Class: AuthorityEnvironment
|
|
7
|
+
- Function: evaluate_authority
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import pytest
|
|
12
|
+
import domainforge
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Helpers
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
def make_minimal_config_json(packs=None) -> str:
|
|
20
|
+
if packs is None:
|
|
21
|
+
packs = []
|
|
22
|
+
return json.dumps({
|
|
23
|
+
"resolver_semantics_version": "0.4",
|
|
24
|
+
"specificity_profile": {
|
|
25
|
+
"id": "default",
|
|
26
|
+
"dimensions": [],
|
|
27
|
+
"scoring_rules": {},
|
|
28
|
+
"hash": "",
|
|
29
|
+
},
|
|
30
|
+
"unknown_handling": {
|
|
31
|
+
"permission": {"default": "escalate"},
|
|
32
|
+
"prohibition": {"default": "deny"},
|
|
33
|
+
"obligation": {"default": "escalate"},
|
|
34
|
+
"override_": {"default": "not_applicable"},
|
|
35
|
+
},
|
|
36
|
+
"fact_sources": [],
|
|
37
|
+
"fact_transforms": [],
|
|
38
|
+
"authority_packs": packs,
|
|
39
|
+
"strict_mode": False,
|
|
40
|
+
"compatibility_lowering_version": "0.4",
|
|
41
|
+
"resolver_version": "0.1",
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def make_minimal_request_json(operation="TestAction", resource_type="Order") -> str:
|
|
46
|
+
return json.dumps({
|
|
47
|
+
"request_id": "req-001",
|
|
48
|
+
"actor": {"id": "user-1"},
|
|
49
|
+
"operation": operation,
|
|
50
|
+
"resource": {"type": resource_type},
|
|
51
|
+
"context": {},
|
|
52
|
+
"requested_at": "2026-06-07T00:00:00Z",
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Enum import tests
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
def test_final_decision_enum_importable():
|
|
61
|
+
"""FinalDecision enum is importable from domainforge."""
|
|
62
|
+
assert hasattr(domainforge, "FinalDecision")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_final_decision_enum_values():
|
|
66
|
+
"""FinalDecision has Allow, Deny, Escalate, NotApplicable, Reject members."""
|
|
67
|
+
assert hasattr(domainforge.FinalDecision, "Allow")
|
|
68
|
+
assert hasattr(domainforge.FinalDecision, "Deny")
|
|
69
|
+
assert hasattr(domainforge.FinalDecision, "Escalate")
|
|
70
|
+
assert hasattr(domainforge.FinalDecision, "NotApplicable")
|
|
71
|
+
assert hasattr(domainforge.FinalDecision, "Reject")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_final_decision_members_are_distinct():
|
|
75
|
+
"""FinalDecision enum members are not equal to each other."""
|
|
76
|
+
assert domainforge.FinalDecision.Allow != domainforge.FinalDecision.Deny
|
|
77
|
+
assert domainforge.FinalDecision.Allow != domainforge.FinalDecision.Escalate
|
|
78
|
+
assert domainforge.FinalDecision.Allow != domainforge.FinalDecision.NotApplicable
|
|
79
|
+
assert domainforge.FinalDecision.Allow != domainforge.FinalDecision.Reject
|
|
80
|
+
assert domainforge.FinalDecision.Deny != domainforge.FinalDecision.Reject
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_policy_modality_enum_importable():
|
|
84
|
+
"""PolicyModality enum is importable from domainforge."""
|
|
85
|
+
assert hasattr(domainforge, "PolicyModality")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_policy_modality_enum_values():
|
|
89
|
+
"""PolicyModality has Permission, Prohibition, Obligation, Override members."""
|
|
90
|
+
assert hasattr(domainforge.PolicyModality, "Permission")
|
|
91
|
+
assert hasattr(domainforge.PolicyModality, "Prohibition")
|
|
92
|
+
assert hasattr(domainforge.PolicyModality, "Obligation")
|
|
93
|
+
assert hasattr(domainforge.PolicyModality, "Override")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_policy_modality_members_are_distinct():
|
|
97
|
+
"""PolicyModality enum members are not equal to each other."""
|
|
98
|
+
assert domainforge.PolicyModality.Permission != domainforge.PolicyModality.Prohibition
|
|
99
|
+
assert domainforge.PolicyModality.Obligation != domainforge.PolicyModality.Override
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_source_class_enum_importable():
|
|
103
|
+
"""SourceClass enum is importable from domainforge."""
|
|
104
|
+
assert hasattr(domainforge, "SourceClass")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_source_class_enum_values():
|
|
108
|
+
"""SourceClass has all seven members."""
|
|
109
|
+
for member in [
|
|
110
|
+
"CallerSupplied", "RuntimeObserved", "SystemOfRecord",
|
|
111
|
+
"Attested", "ManualApproval", "Derived", "UnknownSource",
|
|
112
|
+
]:
|
|
113
|
+
assert hasattr(domainforge.SourceClass, member), f"Missing SourceClass.{member}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_source_class_members_are_distinct():
|
|
117
|
+
"""SourceClass enum members are not equal to each other."""
|
|
118
|
+
assert domainforge.SourceClass.CallerSupplied != domainforge.SourceClass.SystemOfRecord
|
|
119
|
+
assert domainforge.SourceClass.Derived != domainforge.SourceClass.UnknownSource
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_claim_level_enum_importable():
|
|
123
|
+
"""ClaimLevel enum is importable from domainforge."""
|
|
124
|
+
assert hasattr(domainforge, "ClaimLevel")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_claim_level_enum_values():
|
|
128
|
+
"""ClaimLevel has AuditBacked, Validated, FormallyProven members."""
|
|
129
|
+
assert hasattr(domainforge.ClaimLevel, "AuditBacked")
|
|
130
|
+
assert hasattr(domainforge.ClaimLevel, "Validated")
|
|
131
|
+
assert hasattr(domainforge.ClaimLevel, "FormallyProven")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_claim_level_members_are_distinct():
|
|
135
|
+
"""ClaimLevel enum members are not equal to each other."""
|
|
136
|
+
assert domainforge.ClaimLevel.AuditBacked != domainforge.ClaimLevel.Validated
|
|
137
|
+
assert domainforge.ClaimLevel.Validated != domainforge.ClaimLevel.FormallyProven
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# AuthorityEnvironment class
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
def test_authority_environment_class_importable():
|
|
145
|
+
"""AuthorityEnvironment class is importable from domainforge."""
|
|
146
|
+
assert hasattr(domainforge, "AuthorityEnvironment")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_authority_environment_instantiation():
|
|
150
|
+
"""AuthorityEnvironment can be instantiated from a minimal config JSON."""
|
|
151
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
152
|
+
assert env is not None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_authority_environment_raises_on_invalid_config():
|
|
156
|
+
"""AuthorityEnvironment raises ValueError on invalid config JSON."""
|
|
157
|
+
with pytest.raises((ValueError, Exception)):
|
|
158
|
+
domainforge.AuthorityEnvironment("not valid json")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def test_authority_environment_validate():
|
|
162
|
+
"""AuthorityEnvironment.validate() succeeds for a valid config."""
|
|
163
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
164
|
+
# Should not raise
|
|
165
|
+
env.validate()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_authority_environment_evaluate_returns_tuple():
|
|
169
|
+
"""AuthorityEnvironment.evaluate() returns a (trace_json, decision_json) tuple."""
|
|
170
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
171
|
+
env.validate()
|
|
172
|
+
result = env.evaluate(make_minimal_request_json())
|
|
173
|
+
assert isinstance(result, tuple)
|
|
174
|
+
assert len(result) == 2
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_authority_environment_evaluate_returns_valid_json():
|
|
178
|
+
"""AuthorityEnvironment.evaluate() returns valid JSON strings."""
|
|
179
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
180
|
+
env.validate()
|
|
181
|
+
trace_json, decision_json = env.evaluate(make_minimal_request_json())
|
|
182
|
+
json.loads(trace_json) # must parse
|
|
183
|
+
json.loads(decision_json) # must parse
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_authority_environment_evaluate_raises_on_invalid_request():
|
|
187
|
+
"""AuthorityEnvironment.evaluate() raises on invalid request JSON."""
|
|
188
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
189
|
+
env.validate()
|
|
190
|
+
with pytest.raises((ValueError, Exception)):
|
|
191
|
+
env.evaluate("not valid json")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_authority_environment_evaluate_with_empty_facts():
|
|
195
|
+
"""AuthorityEnvironment.evaluate() accepts empty facts array."""
|
|
196
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
197
|
+
env.validate()
|
|
198
|
+
trace_json, decision_json = env.evaluate(make_minimal_request_json(), "[]")
|
|
199
|
+
decision = json.loads(decision_json)
|
|
200
|
+
assert "final_decision" in decision
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_authority_environment_not_applicable_with_no_packs():
|
|
204
|
+
"""AuthorityEnvironment returns not_applicable when no packs are loaded."""
|
|
205
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json([]))
|
|
206
|
+
env.validate()
|
|
207
|
+
_, decision_json = env.evaluate(make_minimal_request_json())
|
|
208
|
+
decision = json.loads(decision_json)
|
|
209
|
+
assert decision["final_decision"] == "not_applicable"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_authority_environment_repr():
|
|
213
|
+
"""AuthorityEnvironment has a string representation."""
|
|
214
|
+
env = domainforge.AuthorityEnvironment(make_minimal_config_json())
|
|
215
|
+
assert isinstance(str(env), str)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
# evaluate_authority function
|
|
220
|
+
# ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
def test_evaluate_authority_is_callable():
|
|
223
|
+
"""evaluate_authority is a callable function in domainforge."""
|
|
224
|
+
assert callable(domainforge.evaluate_authority)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_evaluate_authority_returns_tuple():
|
|
228
|
+
"""evaluate_authority returns a (trace_json, decision_json) tuple."""
|
|
229
|
+
result = domainforge.evaluate_authority(
|
|
230
|
+
make_minimal_config_json(),
|
|
231
|
+
make_minimal_request_json(),
|
|
232
|
+
)
|
|
233
|
+
assert isinstance(result, tuple)
|
|
234
|
+
assert len(result) == 2
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_evaluate_authority_returns_valid_json():
|
|
238
|
+
"""evaluate_authority returns valid JSON strings in both elements."""
|
|
239
|
+
trace_json, decision_json = domainforge.evaluate_authority(
|
|
240
|
+
make_minimal_config_json(),
|
|
241
|
+
make_minimal_request_json(),
|
|
242
|
+
)
|
|
243
|
+
json.loads(trace_json)
|
|
244
|
+
json.loads(decision_json)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_evaluate_authority_raises_on_invalid_config():
|
|
248
|
+
"""evaluate_authority raises on invalid config JSON."""
|
|
249
|
+
with pytest.raises((ValueError, Exception)):
|
|
250
|
+
domainforge.evaluate_authority("not valid json", make_minimal_request_json())
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def test_evaluate_authority_raises_on_invalid_request():
|
|
254
|
+
"""evaluate_authority raises on invalid request JSON."""
|
|
255
|
+
with pytest.raises((ValueError, Exception)):
|
|
256
|
+
domainforge.evaluate_authority(make_minimal_config_json(), "not valid json")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_evaluate_authority_raises_on_invalid_facts():
|
|
260
|
+
"""evaluate_authority raises on invalid facts JSON."""
|
|
261
|
+
with pytest.raises((ValueError, Exception)):
|
|
262
|
+
domainforge.evaluate_authority(
|
|
263
|
+
make_minimal_config_json(),
|
|
264
|
+
make_minimal_request_json(),
|
|
265
|
+
"not valid json",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def test_evaluate_authority_not_applicable_with_no_packs():
|
|
270
|
+
"""evaluate_authority returns not_applicable when no packs are loaded."""
|
|
271
|
+
_, decision_json = domainforge.evaluate_authority(
|
|
272
|
+
make_minimal_config_json([]),
|
|
273
|
+
make_minimal_request_json(),
|
|
274
|
+
)
|
|
275
|
+
decision = json.loads(decision_json)
|
|
276
|
+
assert decision["final_decision"] == "not_applicable"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_evaluate_authority_decision_has_final_decision_field():
|
|
280
|
+
"""evaluate_authority decision contains a final_decision field."""
|
|
281
|
+
_, decision_json = domainforge.evaluate_authority(
|
|
282
|
+
make_minimal_config_json(),
|
|
283
|
+
make_minimal_request_json(),
|
|
284
|
+
)
|
|
285
|
+
decision = json.loads(decision_json)
|
|
286
|
+
assert "final_decision" in decision
|
|
287
|
+
valid_decisions = {"allow", "deny", "escalate", "not_applicable", "reject"}
|
|
288
|
+
assert decision["final_decision"] in valid_decisions
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def test_evaluate_authority_trace_references_request_id():
|
|
292
|
+
"""evaluate_authority trace references the request_id from the request."""
|
|
293
|
+
trace_json, _ = domainforge.evaluate_authority(
|
|
294
|
+
make_minimal_config_json(),
|
|
295
|
+
make_minimal_request_json(),
|
|
296
|
+
)
|
|
297
|
+
assert "req-001" in trace_json
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_evaluate_authority_with_explicit_empty_facts():
|
|
301
|
+
"""evaluate_authority accepts explicit empty facts JSON array."""
|
|
302
|
+
trace_json, decision_json = domainforge.evaluate_authority(
|
|
303
|
+
make_minimal_config_json(),
|
|
304
|
+
make_minimal_request_json(),
|
|
305
|
+
"[]",
|
|
306
|
+
)
|
|
307
|
+
assert isinstance(trace_json, str)
|
|
308
|
+
assert isinstance(decision_json, str)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def test_evaluate_authority_is_deterministic():
|
|
312
|
+
"""Two calls with identical inputs produce the same final_decision."""
|
|
313
|
+
config = make_minimal_config_json()
|
|
314
|
+
request = make_minimal_request_json()
|
|
315
|
+
_, d1 = domainforge.evaluate_authority(config, request)
|
|
316
|
+
_, d2 = domainforge.evaluate_authority(config, request)
|
|
317
|
+
assert json.loads(d1)["final_decision"] == json.loads(d2)["final_decision"]
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_evaluate_authority_handles_different_operations():
|
|
321
|
+
"""evaluate_authority processes requests with different operations."""
|
|
322
|
+
for operation in ["CreateOrder", "DeleteOrder", "UpdateOrder"]:
|
|
323
|
+
_, decision_json = domainforge.evaluate_authority(
|
|
324
|
+
make_minimal_config_json(),
|
|
325
|
+
make_minimal_request_json(operation=operation),
|
|
326
|
+
)
|
|
327
|
+
decision = json.loads(decision_json)
|
|
328
|
+
assert "final_decision" in decision
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import tarfile
|
|
4
|
+
import tempfile
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from scripts.ci_tasks import (
|
|
10
|
+
_is_safe_path,
|
|
11
|
+
_validate_tar_member,
|
|
12
|
+
_validate_zip_member,
|
|
13
|
+
unpack_and_verify,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestSafePath:
|
|
18
|
+
def test_normal_path_is_safe(self):
|
|
19
|
+
assert _is_safe_path("/tmp/dest", "file.txt")
|
|
20
|
+
|
|
21
|
+
def test_subdirectory_is_safe(self):
|
|
22
|
+
assert _is_safe_path("/tmp/dest", "sub/file.txt")
|
|
23
|
+
|
|
24
|
+
def test_traversal_is_unsafe(self):
|
|
25
|
+
assert not _is_safe_path("/tmp/dest", "../outside.txt")
|
|
26
|
+
|
|
27
|
+
def test_deep_traversal_is_unsafe(self):
|
|
28
|
+
assert not _is_safe_path("/tmp/dest", "foo/../../outside.txt")
|
|
29
|
+
|
|
30
|
+
def test_absolute_path_is_unsafe(self):
|
|
31
|
+
assert not _is_safe_path("/tmp/dest", "/etc/passwd")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestTarValidation:
|
|
35
|
+
def test_safe_member_passes(self):
|
|
36
|
+
member = tarfile.TarInfo(name="safe_file.txt")
|
|
37
|
+
_validate_tar_member("/tmp/dest", member)
|
|
38
|
+
|
|
39
|
+
def test_traversal_rejected(self):
|
|
40
|
+
member = tarfile.TarInfo(name="../outside.txt")
|
|
41
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
42
|
+
_validate_tar_member("/tmp/dest", member)
|
|
43
|
+
|
|
44
|
+
def test_absolute_path_rejected(self):
|
|
45
|
+
member = tarfile.TarInfo(name="/etc/passwd")
|
|
46
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
47
|
+
_validate_tar_member("/tmp/dest", member)
|
|
48
|
+
|
|
49
|
+
def test_symlink_rejected(self):
|
|
50
|
+
member = tarfile.TarInfo(name="link")
|
|
51
|
+
member.type = tarfile.SYMTYPE
|
|
52
|
+
member.linkname = "/etc/passwd"
|
|
53
|
+
with pytest.raises(ValueError, match="Symlink"):
|
|
54
|
+
_validate_tar_member("/tmp/dest", member)
|
|
55
|
+
|
|
56
|
+
def test_hardlink_rejected(self):
|
|
57
|
+
member = tarfile.TarInfo(name="link")
|
|
58
|
+
member.type = tarfile.LNKTYPE
|
|
59
|
+
member.linkname = "/etc/passwd"
|
|
60
|
+
with pytest.raises(ValueError, match="Hardlink|Symlink"):
|
|
61
|
+
_validate_tar_member("/tmp/dest", member)
|
|
62
|
+
|
|
63
|
+
def test_deep_traversal_rejected(self):
|
|
64
|
+
member = tarfile.TarInfo(name="foo/../../outside.txt")
|
|
65
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
66
|
+
_validate_tar_member("/tmp/dest", member)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestZipValidation:
|
|
70
|
+
def test_safe_entry_passes(self):
|
|
71
|
+
_validate_zip_member("/tmp/dest", "safe_file.txt")
|
|
72
|
+
|
|
73
|
+
def test_traversal_rejected(self):
|
|
74
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
75
|
+
_validate_zip_member("/tmp/dest", "../outside.txt")
|
|
76
|
+
|
|
77
|
+
def test_absolute_path_rejected(self):
|
|
78
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
79
|
+
_validate_zip_member("/tmp/dest", "/etc/passwd")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestMaliciousArchiveExtraction:
|
|
83
|
+
def test_malicious_tar_with_traversal_fails(self, tmp_path):
|
|
84
|
+
tar_path = tmp_path / "malicious.tar.gz"
|
|
85
|
+
with tarfile.open(str(tar_path), "w:gz") as tf:
|
|
86
|
+
data = b"malicious content"
|
|
87
|
+
member = tarfile.TarInfo(name="../outside.txt")
|
|
88
|
+
member.size = len(data)
|
|
89
|
+
tf.addfile(member, io.BytesIO(data))
|
|
90
|
+
|
|
91
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
92
|
+
unpack_and_verify(str(tar_path), "domainforge")
|
|
93
|
+
|
|
94
|
+
def test_malicious_tar_with_symlink_fails(self, tmp_path):
|
|
95
|
+
tar_path = tmp_path / "symlink.tar.gz"
|
|
96
|
+
with tarfile.open(str(tar_path), "w:gz") as tf:
|
|
97
|
+
member = tarfile.TarInfo(name="evil_link")
|
|
98
|
+
member.type = tarfile.SYMTYPE
|
|
99
|
+
member.linkname = "/etc/passwd"
|
|
100
|
+
tf.addfile(member)
|
|
101
|
+
|
|
102
|
+
with pytest.raises(ValueError, match="Symlink"):
|
|
103
|
+
unpack_and_verify(str(tar_path), "domainforge")
|
|
104
|
+
|
|
105
|
+
def test_malicious_tar_with_absolute_path_fails(self, tmp_path):
|
|
106
|
+
tar_path = tmp_path / "absolute.tar.gz"
|
|
107
|
+
with tarfile.open(str(tar_path), "w:gz") as tf:
|
|
108
|
+
data = b"absolute path content"
|
|
109
|
+
member = tarfile.TarInfo(name="/tmp/evil.txt")
|
|
110
|
+
member.size = len(data)
|
|
111
|
+
tf.addfile(member, io.BytesIO(data))
|
|
112
|
+
|
|
113
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
114
|
+
unpack_and_verify(str(tar_path), "domainforge")
|
|
115
|
+
|
|
116
|
+
def test_malicious_zip_with_traversal_fails(self, tmp_path):
|
|
117
|
+
zip_path = tmp_path / "malicious.zip"
|
|
118
|
+
with zipfile.ZipFile(str(zip_path), "w") as zf:
|
|
119
|
+
zf.writestr("../outside.txt", "malicious content")
|
|
120
|
+
|
|
121
|
+
with pytest.raises(ValueError, match="Unsafe path"):
|
|
122
|
+
unpack_and_verify(str(zip_path), "domainforge")
|
|
123
|
+
|
|
124
|
+
def test_safe_tar_extracts_successfully(self, tmp_path):
|
|
125
|
+
tar_path = tmp_path / "safe.tar.gz"
|
|
126
|
+
with tarfile.open(str(tar_path), "w:gz") as tf:
|
|
127
|
+
data = b"#!/bin/sh\necho v1.0.0"
|
|
128
|
+
member = tarfile.TarInfo(name="domainforge")
|
|
129
|
+
member.size = len(data)
|
|
130
|
+
member.mode = 0o755
|
|
131
|
+
tf.addfile(member, io.BytesIO(data))
|
|
132
|
+
|
|
133
|
+
with patch_verify_cli(tmp_path):
|
|
134
|
+
result = unpack_and_verify(str(tar_path), "domainforge")
|
|
135
|
+
assert result == 0
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def patch_verify_cli(tmp_path):
|
|
139
|
+
import unittest.mock
|
|
140
|
+
|
|
141
|
+
return unittest.mock.patch(
|
|
142
|
+
"scripts.ci_tasks.verify_cli_binary", return_value=0
|
|
143
|
+
)
|