framesdkpy 0.3.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.
@@ -0,0 +1,169 @@
1
+ """Character limit validator -- enforces maxLength on FRAME fields.
2
+
3
+ Core governance fields (ids, names, rules, checks, command_refs, pass_conditions)
4
+ are enforced -- exceeding their maxLength blocks the load.
5
+
6
+ Advisory/descriptive fields (code_style, git, architecture notes, environment
7
+ descriptions) are warned -- the load continues but the caller sees the warning.
8
+
9
+ Character limits are defined in the finalized schema. This validator checks them.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from framesdkpy.validators.result import ValidationResult
15
+
16
+
17
+ # ---------------------------------------------------------------------------
18
+ # Field limit registry
19
+ # ---------------------------------------------------------------------------
20
+ # Each entry: (dotted_path, max_chars, enforced_or_advisory)
21
+ # Core governance fields: enforced (error if exceeded)
22
+ # Descriptive blocks: advisory (warning if exceeded)
23
+
24
+ _FIELD_LIMITS: list[tuple[str, int, str]] = [
25
+ # Core governance fields -- enforced
26
+ ("*.id", 100, "enforced"),
27
+ ("*.name", 100, "enforced"),
28
+ ("facts.profile.name", 100, "enforced"),
29
+ ("facts.profile.summary", 300, "enforced"),
30
+ ("facts.sources[].id", 100, "enforced"),
31
+ ("facts.sources[].path", 200, "enforced"),
32
+ ("facts.quirks[].id", 100, "enforced"),
33
+ ("facts.open_questions[].id", 100, "enforced"),
34
+ ("rules.rules[].id", 100, "enforced"),
35
+ ("rules.policies[].id", 100, "enforced"),
36
+ ("rules.donts[].id", 100, "enforced"),
37
+ ("rules.ask_first[].id", 100, "enforced"),
38
+ ("rules.hints[].id", 100, "enforced"),
39
+ ("rules.commands.*.run", 500, "enforced"),
40
+ ("rules.commands.*.purpose", 300, "enforced"),
41
+ ("map.groups[].id", 100, "enforced"),
42
+ ("map.groups[].label", 150, "enforced"),
43
+ ("map.groups[].paths[]", 300, "enforced"),
44
+ ("map.entrypoints[].id", 100, "enforced"),
45
+ ("expect.must_hold[].id", 100, "enforced"),
46
+ ("expect.checks.*.name", 100, "enforced"),
47
+ ("expect.checks.*.command_ref", 200, "enforced"),
48
+ ("expect.checks.*.pass_condition", 200, "enforced"),
49
+ ("expect.proof[].id", 100, "enforced"),
50
+ ("acts.runs[].id", 100, "enforced"),
51
+ ("acts.runs[].actor", 100, "enforced"),
52
+ ("acts.blockers[].id", 100, "enforced"),
53
+
54
+ # Advisory blocks -- warned
55
+ ("facts.architecture.summary", 500, "advisory"),
56
+ ("facts.architecture.*", 500, "advisory"),
57
+ ("facts.technology.*", 100, "advisory"),
58
+ ("facts.sources[].purpose", 300, "advisory"),
59
+ ("facts.quirks[].description", 200, "advisory"),
60
+ ("facts.quirks[].why", 300, "advisory"),
61
+ ("facts.open_questions[].question", 300, "advisory"),
62
+ ("facts.open_questions[].context", 300, "advisory"),
63
+ ("rules.rules[].rule", 500, "advisory"),
64
+ ("rules.policies[].name", 150, "advisory"),
65
+ ("rules.policies[].rule", 500, "advisory"),
66
+ ("rules.donts[].rule", 300, "advisory"),
67
+ ("rules.ask_first[].trigger", 300, "advisory"),
68
+ ("rules.ask_first[].reason", 300, "advisory"),
69
+ ("rules.hints[].hint", 300, "advisory"),
70
+ ("rules.code_style", 1000, "advisory"),
71
+ ("rules.git", 1000, "advisory"),
72
+ ("map.structure", 800, "advisory"),
73
+ ("map.paths[].path", 200, "advisory"),
74
+ ("map.paths[].purpose", 300, "advisory"),
75
+ ("map.entrypoints[].path", 200, "advisory"),
76
+ ("map.managed_paths[].path", 200, "advisory"),
77
+ ("expect.outcomes.*.summary", 300, "advisory"),
78
+ ("expect.must_hold[].statement", 300, "advisory"),
79
+ ("expect.checks.*.what", 300, "advisory"),
80
+ ("expect.checks.*.how", 200, "advisory"),
81
+ ("expect.proof[].description", 300, "advisory"),
82
+ ("acts.summary", 500, "advisory"),
83
+ ("acts.runs[].goal", 300, "advisory"),
84
+ ("acts.runs[].input_summary", 300, "advisory"),
85
+ ("acts.runs[].output_summary", 300, "advisory"),
86
+ ("acts.runs[].checks[].reason", 200, "advisory"),
87
+ ("acts.blockers[].description", 300, "advisory"),
88
+ ]
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Validation
93
+ # ---------------------------------------------------------------------------
94
+
95
+
96
+ def validate_limits(data: dict, file_stem: str) -> ValidationResult:
97
+ """Check character limits on all fields in a FRAME data dict.
98
+
99
+ Walks the dict tree, matches each value against the field limit registry,
100
+ and reports any violations.
101
+ """
102
+ result = ValidationResult()
103
+ _walk_and_check(data, file_stem, result)
104
+ return result
105
+
106
+
107
+ def _walk_and_check(data, path: str, result: ValidationResult):
108
+ """Recursively walk a dict and check field limits."""
109
+ if isinstance(data, dict):
110
+ for key, value in data.items():
111
+ current_path = f"{path}.{key}"
112
+ _check_value(value, current_path, result)
113
+ _walk_and_check(value, current_path, result)
114
+ elif isinstance(data, list):
115
+ for i, item in enumerate(data):
116
+ current_path = f"{path}[{i}]"
117
+ _check_value(item, current_path, result)
118
+ _walk_and_check(item, current_path, result)
119
+
120
+
121
+ def _check_value(value, path: str, result: ValidationResult):
122
+ """Check a single value against the limit registry."""
123
+ if not isinstance(value, str):
124
+ return
125
+
126
+ # Find matching limit rules for this path
127
+ for pattern, max_chars, severity in _FIELD_LIMITS:
128
+ if not _path_matches(path, pattern):
129
+ continue
130
+
131
+ if len(value) > max_chars:
132
+ msg = f"Field exceeds maxLength of {max_chars} chars (got {len(value)})"
133
+ if severity == "enforced":
134
+ result.add_error(
135
+ path=path, message=msg, code="limit_exceeded",
136
+ expected=f"maxLength: {max_chars}", actual=f"length: {len(value)}",
137
+ )
138
+ else:
139
+ result.add_warning(path=path, message=msg, code="limit_advisory")
140
+ break # First match wins -- don't report the same field twice
141
+
142
+
143
+ def _path_matches(actual_path: str, pattern: str) -> bool:
144
+ """Check if a dot-separated path matches a pattern with * and [] wildcards.
145
+
146
+ Pattern: 'facts.profile.name' matches exactly.
147
+ Pattern: 'facts.sources[].id' matches any index: 'facts.sources[0].id'.
148
+ Pattern: 'rules.commands.*.run' matches any command name.
149
+ Pattern: 'facts.architecture.*' matches any architecture sub-field.
150
+ Pattern: '*.id' matches any top-level field ending in '.id'.
151
+ """
152
+ # Normalize array indices: replace [0], [1], etc. with []
153
+ import re
154
+ actual_normalized = re.sub(r'\[\d+\]', '[]', actual_path)
155
+
156
+ # Split both paths into segments
157
+ actual_segs = actual_normalized.split(".")
158
+ pattern_segs = pattern.split(".")
159
+
160
+ if len(actual_segs) != len(pattern_segs):
161
+ return False
162
+
163
+ for a, p in zip(actual_segs, pattern_segs):
164
+ if p == "*":
165
+ continue # Wildcard matches any segment
166
+ if a != p:
167
+ return False
168
+
169
+ return True
@@ -0,0 +1,100 @@
1
+ """Validation result objects -- returned by all validators.
2
+
3
+ Validators never raise exceptions for validation failures. They return
4
+ ValidationResult objects. The caller decides: abort, fix and retry,
5
+ or continue with warnings.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class ValidationError:
15
+ """A blocking validation failure. Must be fixed before loading."""
16
+
17
+ path: str
18
+ """Dotted path to the failing field, e.g. 'facts.profile.name'."""
19
+
20
+ message: str
21
+ """Human-readable explanation of what went wrong."""
22
+
23
+ code: str
24
+ """Machine-readable code: 'missing_required', 'type_error', 'enum_error', 'limit_exceeded'."""
25
+
26
+ expected: str | None = None
27
+ """What was expected, e.g. 'string', 'maxLength: 100'."""
28
+
29
+ actual: str | None = None
30
+ """What was found, e.g. 'int', 'length: 245'."""
31
+
32
+
33
+ @dataclass(slots=True)
34
+ class ValidationWarning:
35
+ """A non-blocking issue. Load continues but the caller should know."""
36
+
37
+ path: str
38
+ """Dotted path to the field with the warning."""
39
+
40
+ message: str
41
+ """Human-readable explanation."""
42
+
43
+ code: str
44
+ """Machine-readable code: 'missing_optional', 'limit_advisory', 'unknown_field'."""
45
+
46
+
47
+ @dataclass(slots=True)
48
+ class ValidationResult:
49
+ """Aggregate result from all validators in the pipeline.
50
+
51
+ is_valid() returns True if there are zero blocking errors.
52
+ is_clean() returns True if there are no errors AND no warnings.
53
+ merge() combines results from multiple validators into one.
54
+ """
55
+
56
+ errors: list[ValidationError] = field(default_factory=list)
57
+ """Blocking errors. The load cannot proceed until these are fixed."""
58
+
59
+ warnings: list[ValidationWarning] = field(default_factory=list)
60
+ """Non-blocking warnings. The load proceeds but the caller sees these."""
61
+
62
+ def is_valid(self) -> bool:
63
+ """True if no blocking errors. Warnings don't block."""
64
+ return len(self.errors) == 0
65
+
66
+ def is_clean(self) -> bool:
67
+ """True if no errors AND no warnings."""
68
+ return len(self.errors) == 0 and len(self.warnings) == 0
69
+
70
+ def merge(self, other: ValidationResult) -> ValidationResult:
71
+ """Combine results from multiple validators."""
72
+ return ValidationResult(
73
+ errors=self.errors + other.errors,
74
+ warnings=self.warnings + other.warnings,
75
+ )
76
+
77
+ def add_error(self, path: str, message: str, code: str,
78
+ expected: str | None = None, actual: str | None = None) -> None:
79
+ """Convenience: add a blocking error."""
80
+ self.errors.append(ValidationError(
81
+ path=path, message=message, code=code,
82
+ expected=expected, actual=actual,
83
+ ))
84
+
85
+ def add_warning(self, path: str, message: str, code: str) -> None:
86
+ """Convenience: add a non-blocking warning."""
87
+ self.warnings.append(ValidationWarning(
88
+ path=path, message=message, code=code,
89
+ ))
90
+
91
+ def summary(self) -> str:
92
+ """One-line summary for logging."""
93
+ parts = []
94
+ if self.errors:
95
+ parts.append(f"{len(self.errors)} error(s)")
96
+ if self.warnings:
97
+ parts.append(f"{len(self.warnings)} warning(s)")
98
+ if not parts:
99
+ return "valid"
100
+ return ", ".join(parts)
@@ -0,0 +1,152 @@
1
+ """Schema validator -- validates FRAME YAML data against JSON Schema definitions.
2
+
3
+ Uses jsonschema library. Fails on type errors, missing required fields, enum
4
+ violations, const violations, and unknown fields rejected by closed schema
5
+ objects.
6
+
7
+ Cross-file $ref links (./frame.schema.json) are resolved locally from the
8
+ schemas/json/ directory -- no network requests.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ from pathlib import Path
15
+
16
+ import yaml
17
+ from jsonschema import Draft202012Validator
18
+ from jsonschema.exceptions import ValidationError as JsonschemaError
19
+ from referencing import Registry, Resource
20
+
21
+ from framesdkpy.validators.result import ValidationResult
22
+
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Schema loading with local $ref resolution
26
+ # ---------------------------------------------------------------------------
27
+
28
+ def _schemas_dir() -> Path:
29
+ """Locate the schemas directory bundled with the package."""
30
+ this_file = Path(__file__).resolve()
31
+ # frame/validators/schema_validator.py → frame/validators → frame/
32
+ package_root = this_file.parent.parent
33
+ schemas = package_root / "schemas"
34
+ if not schemas.is_dir():
35
+ raise FileNotFoundError(
36
+ f"FRAME schemas directory not found at {schemas}. "
37
+ f"Expected framesdkpy/schemas/ in the FrameSDK package."
38
+ )
39
+ return schemas
40
+
41
+
42
+ # Pre-load all schemas into a local registry so cross-file $ref links resolve
43
+ # without network requests. Each schema gets registered under its $id URI.
44
+ def _build_registry() -> Registry:
45
+ """Load all 6 JSON schemas into a referencing.Registry for local $ref resolution."""
46
+ resources: list[tuple[str, Resource]] = []
47
+ schemas_dir = _schemas_dir()
48
+ for schema_file in sorted(schemas_dir.glob("*.schema.json")):
49
+ schema = json.loads(schema_file.read_text())
50
+ schema_uri = schema.get("$id", "")
51
+ if schema_uri:
52
+ resources.append((schema_uri, Resource.from_contents(schema)))
53
+ return Registry().with_resources(resources)
54
+
55
+
56
+ # Build once at module load time
57
+ _SCHEMA_REGISTRY = _build_registry()
58
+
59
+
60
+ def _load_validator(file_stem: str) -> Draft202012Validator:
61
+ """Load a FRAME JSON Schema and return a validator with local $ref resolution."""
62
+ schema_path = _schemas_dir() / f"{file_stem}.schema.json"
63
+ if not schema_path.exists():
64
+ raise FileNotFoundError(f"Schema file not found: {schema_path}")
65
+ schema = json.loads(schema_path.read_text())
66
+ return Draft202012Validator(schema, registry=_SCHEMA_REGISTRY)
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Validation
71
+ # ---------------------------------------------------------------------------
72
+
73
+
74
+ def validate_against_schema(data: dict, file_stem: str) -> ValidationResult:
75
+ """Validate FRAME data against its JSON Schema.
76
+
77
+ Args:
78
+ data: Clean dict from the translator (YAML quirks already resolved).
79
+ file_stem: One of 'facts', 'rules', 'map', 'expect', 'acts'.
80
+
81
+ Returns:
82
+ ValidationResult with errors for type/enum/required violations,
83
+ warnings for missing optional fields.
84
+ """
85
+ result = ValidationResult()
86
+ validator = _load_validator(file_stem)
87
+
88
+ # Collect errors manually so we can classify by code
89
+ for e in validator.iter_errors(data):
90
+ path = ".".join(str(p) for p in e.absolute_path) if e.absolute_path else "$"
91
+ code = _map_error_code(e)
92
+
93
+ if code in (
94
+ "missing_required",
95
+ "type_error",
96
+ "enum_error",
97
+ "const_error",
98
+ "unknown_field",
99
+ "schema_error",
100
+ ):
101
+ result.add_error(
102
+ path=path or file_stem,
103
+ message=e.message,
104
+ code=code,
105
+ expected=str(e.validator_value) if e.validator_value else None,
106
+ actual=str(e.instance)[:200] if e.instance is not None else None,
107
+ )
108
+ else:
109
+ result.add_warning(path=path or file_stem, message=e.message, code=code)
110
+
111
+ return result
112
+
113
+
114
+ def _map_error_code(error: JsonschemaError) -> str:
115
+ """Map a jsonschema error to our validation code system."""
116
+ validator = error.validator
117
+ if validator == "required":
118
+ return "missing_required"
119
+ if validator in ("type", "pattern", "format"):
120
+ return "type_error"
121
+ if validator == "enum":
122
+ return "enum_error"
123
+ if validator == "const":
124
+ return "const_error"
125
+ if validator == "additionalProperties":
126
+ return "unknown_field"
127
+ if validator == "maxLength":
128
+ return "limit_exceeded"
129
+ return "schema_error" # Catch-all for unexpected validation failures
130
+
131
+
132
+ # ---------------------------------------------------------------------------
133
+ # Convenience: validate a YAML file directly
134
+ # ---------------------------------------------------------------------------
135
+
136
+
137
+ def validate_yaml_file(file_path: str | Path) -> ValidationResult:
138
+ """Validate a single YAML FRAME file against its schema.
139
+
140
+ Parses YAML, normalizes quirks, then validates.
141
+ """
142
+ path = Path(file_path)
143
+ yaml_data = yaml.safe_load(path.read_text())
144
+ if not yaml_data or not isinstance(yaml_data, dict):
145
+ result = ValidationResult()
146
+ result.add_error(str(path), "File is empty or not a YAML object", "type_error")
147
+ return result
148
+
149
+ stem = path.stem # 'facts' from 'facts.yaml'
150
+ from framesdkpy.translators.normalizer import normalize_dict
151
+ clean = normalize_dict(yaml_data)
152
+ return validate_against_schema(clean, stem)
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: framesdkpy
3
+ Version: 0.3.0
4
+ Summary: Python SDK for FRAME -- typed project-context architecture for AI-assisted development
5
+ Project-URL: Homepage, https://github.com/haxsysgit/FrameSDK
6
+ Project-URL: Repository, https://github.com/haxsysgit/FrameSDK
7
+ Author: Arinze Elenasulu
8
+ License-Expression: MIT
9
+ Keywords: agent-governance,ai-agents,frame,project-context
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: jsonschema>=4.20
19
+ Requires-Dist: pyyaml>=6.0
20
+ Requires-Dist: referencing>=0.30
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8.0; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # FrameSDK
26
+
27
+ The Python SDK for [FRAME](https://github.com/haxsysgit/FRAME) -- a typed project-context architecture for AI-assisted development.
28
+
29
+ When you switch coding agents, the project forgets itself. Not its code -- the code is fine. But the *understanding*. The rules you agreed on. The decisions you made and why. The checks that matter. Things previous agents touched or broke.
30
+
31
+ FRAME gives the project a typed shape that agents and tools read consistently. framesdkpy is how Python tools read that shape.
32
+
33
+ ## What it does
34
+
35
+ Takes a `.haxaml/` directory with 5 YAML files and returns a typed `FRAME` object:
36
+
37
+ ```python
38
+ from framesdkpy import load_frame
39
+
40
+ frame = load_frame(".haxaml/")
41
+ frame.facts.profile.name # "Pharmax"
42
+ frame.rules.governance_level # "strict"
43
+ frame.map.entrypoints[0].path # "Backend/main.py"
44
+ frame.expect.checks["backend_tests"].pass_condition # "exit_code == 0"
45
+ ```
46
+
47
+ Every downstream tool -- Haxaml, a CLI, a CI pipeline -- gets the same shaped answer. Cross-language SDKs return the same JSON shape.
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ uv add framesdkpy
53
+ # or
54
+ pip install framesdkpy
55
+ ```
56
+
57
+ Requires Python 3.11+. Three dependencies: PyYAML, jsonschema, referencing. That's it. No Pydantic, no heavy framework.
58
+
59
+ ## What's in the box
60
+
61
+ - **loaders** -- `load_frame()` builds a typed FRAME from 5 YAML files. Strict single-directory discovery. Schema and character limit validation at load time.
62
+ - **models** -- 27 typed dataclasses across 7 files. One import: `from framesdkpy.models import FRAME`.
63
+ - **validators** -- Schema, character limits, cross-file consistency. Callable independently or through the loader.
64
+ - **translators** -- YAML to JSON with full normalization. Handles yes/True, ~/None, on/off rejection.
65
+
66
+ ## Usage patterns
67
+
68
+ ```python
69
+ from framesdkpy import load_frame, translate_directory, validate_file
70
+
71
+ # Full pipeline -- load all 5 files, validate, assemble
72
+ frame = load_frame(".haxaml/")
73
+
74
+ # Translate YAML to clean dict (normalized, but no validation)
75
+ data = translate_directory(".haxaml/")
76
+
77
+ # Validate a single file without loading the full model
78
+ result = validate_file(".haxaml/facts.yaml")
79
+ print(result.summary()) # "valid" or "2 error(s), 1 warning(s)"
80
+
81
+ # Serialize for cross-language use
82
+ json_string = frame.to_json()
83
+ ```
84
+
85
+ ## How it's built
86
+
87
+ Spec-first. Every module has a design doc (`docs/models.md`, `docs/loaders.md`, etc.) with locked decisions before any code was written. 106 tests cover construction, serialization, YAML normalization, schema enforcement, character limits, cross-file checks, and integration against a real Pharmax fixture.
88
+
89
+ No graph building, no cross-referencing, no governance. That's Haxaml's job. framesdkpy is pure ingestion -- load, validate, assemble, return.
@@ -0,0 +1,29 @@
1
+ framesdkpy/__init__.py,sha256=O6zfAF_uI344COwKg_KSQtsPWe0LhufRmbR1C3vTVAE,882
2
+ framesdkpy/loaders/__init__.py,sha256=L7lo1Z-AKuMaJ32viOE-2yfrRs9KOTUKucKsQChTAAI,2584
3
+ framesdkpy/loaders/assembler.py,sha256=UZm25BvnvJxPYjX111lwFTjUiiTrwdMZAhA0sKITn_Q,8374
4
+ framesdkpy/loaders/yaml_reader.py,sha256=ZLAaJK2XjCBT7xeu6z0lM1xWv7FJUmoza9qP5KVTiyM,2012
5
+ framesdkpy/models/__init__.py,sha256=GmRM-g-MS4Xkjc8DMMRytmg47JS8mOsLSH9fv5Jqa30,1657
6
+ framesdkpy/models/acts_model.py,sha256=ZlrYagsp1NBgR88Oa2wIWiZciyjwNTc7Wa7mGoEf5Fo,4641
7
+ framesdkpy/models/base.py,sha256=_5HIzG72Z-ojh52IGVx18Vo-3pjnlQe7pYZaOFJ2MIY,2505
8
+ framesdkpy/models/expect_model.py,sha256=JsgthOvyuvKPqXH9DdYs1l3Kizq0T3dC66FCj1Avh04,4077
9
+ framesdkpy/models/facts_model.py,sha256=rarijLCKdXOwxu1ysOFfoVTHA0XYNt6AUAhQkbayoko,5241
10
+ framesdkpy/models/frame_model.py,sha256=sT0RvMBaAmJeLycQgU__vmMCgWtpXfxbayZAPWyUMP0,1466
11
+ framesdkpy/models/map_model.py,sha256=-_NT9esHzHicsnXAwVVvLzjIPCcfV6WokcTBDHZW_Mg,4889
12
+ framesdkpy/models/rules_model.py,sha256=-OAh8nUXzDi_M_rU61kEYk3Ol8x9JgKATOn1BwuMw3o,5511
13
+ framesdkpy/schemas/acts.schema.json,sha256=SaRwbOS3FAuJg1gby54nX55QW5t2nHvoekiMkjo0bso,4228
14
+ framesdkpy/schemas/expect.schema.json,sha256=rbXJHSL7j898zafkGlDVwtxLeLixZ3DKnRl2BOCOF5o,3766
15
+ framesdkpy/schemas/facts.schema.json,sha256=As5SY7bTYxds8VoNExMkMx41yTSc4cTloMGmiuNR_xY,6011
16
+ framesdkpy/schemas/frame.schema.json,sha256=MaF3dp5TPUL-wlS3FGheWNTC7lmqJTjFS-KJXzinGpE,3211
17
+ framesdkpy/schemas/map.schema.json,sha256=jOTyE6fQ2MsB04t4vrsy3jE9QgLDsKG-EHwtL7bRRZY,4154
18
+ framesdkpy/schemas/rules.schema.json,sha256=C2sRt3__uglo9JuL6UUJ9dinI84xsxTNGisckwqnOiQ,4941
19
+ framesdkpy/translators/__init__.py,sha256=GWVGQMsR1ipn0wUmvp_MagC2pvMAx5I9ulS6fINsJG0,653
20
+ framesdkpy/translators/normalizer.py,sha256=EE5OO2974trhezVx6kt1m8aBTn7strLoLUAN8VN0QFM,3710
21
+ framesdkpy/translators/yaml_to_json.py,sha256=k_TxQ3FBjGRcRJzP7qPOgU0YzyJwQhc1i-YO-ml4NmY,2893
22
+ framesdkpy/validators/__init__.py,sha256=Ac4v6hEiJK8fnFKTrOg5aIVGOn72Gt80aJiwu8DKaaU,2406
23
+ framesdkpy/validators/cross_file_validator.py,sha256=K7IRhSBPQaEZDG6cM_gwYwKCQZuVRObw_LwJeqpTvqU,2738
24
+ framesdkpy/validators/limits_validator.py,sha256=q8FNWRGdKQ5x4iUokZCOdgCMVfiVXe4sPv11n33xXu8,6817
25
+ framesdkpy/validators/result.py,sha256=UPc8LUUnka84TqWjUAyXr9Q3IjJNrB7jc4t_1g1mC-8,3306
26
+ framesdkpy/validators/schema_validator.py,sha256=fVUw0nzt3KkFo6_CdVCLGDf8iATtEArNZ2quPOwZy34,5581
27
+ framesdkpy-0.3.0.dist-info/METADATA,sha256=BO3erz3B60pA1SPW4KlKqxQxl9EOf3UEQbhY7Q2OwcU,3707
28
+ framesdkpy-0.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
29
+ framesdkpy-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any