ssot-conformance 0.2.17.dev2__tar.gz → 0.2.18.dev9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/PKG-INFO +5 -4
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/pyproject.toml +5 -4
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/__init__.py +5 -0
- ssot_conformance-0.2.18.dev9/src/ssot_conformance/origin.py +378 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/PKG-INFO +5 -4
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/SOURCES.txt +1 -0
- ssot_conformance-0.2.18.dev9/src/ssot_conformance.egg-info/requires.txt +5 -0
- ssot_conformance-0.2.17.dev2/src/ssot_conformance.egg-info/requires.txt +0 -5
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/README.md +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/setup.cfg +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/__init__.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_boundary_release_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_document_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_feature_spec_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_id_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_proof_chain_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_registry_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_spec_adr_contract.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/catalog.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/discovery.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/evidence.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/plugin.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/runner.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/scaffold.py +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/dependency_links.txt +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/entry_points.txt +0 -0
- {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssot-conformance
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.18.dev9
|
|
4
4
|
Summary: Reusable SSOT conformance harness, pytest plugin, and scaffold helpers.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
24
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
25
|
Classifier: Topic :: Software Development :: Testing
|
|
25
26
|
Classifier: Topic :: Utilities
|
|
26
|
-
Requires-Python: <3.
|
|
27
|
+
Requires-Python: <3.15,>=3.10
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
|
-
Requires-Dist: ssot-core==0.2.
|
|
29
|
-
Requires-Dist: ssot-contracts==0.2.
|
|
29
|
+
Requires-Dist: ssot-core==0.2.18.dev9
|
|
30
|
+
Requires-Dist: ssot-contracts==0.2.18.dev9
|
|
30
31
|
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
31
32
|
|
|
32
33
|
<div align="center">
|
|
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ssot-conformance"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.18.dev9"
|
|
8
8
|
description = "Reusable SSOT conformance harness, pytest plugin, and scaffold helpers."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.10,<3.
|
|
10
|
+
requires-python = ">=3.10,<3.15"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"ssot-core==0.2.
|
|
15
|
-
"ssot-contracts==0.2.
|
|
14
|
+
"ssot-core==0.2.18.dev9",
|
|
15
|
+
"ssot-contracts==0.2.18.dev9",
|
|
16
16
|
"tomli>=2.0.1; python_version < '3.11'",
|
|
17
17
|
]
|
|
18
18
|
keywords = ["ssot", "conformance", "pytest", "governance", "validation", "scaffold"]
|
|
@@ -29,6 +29,7 @@ classifiers = [
|
|
|
29
29
|
"Programming Language :: Python :: 3.11",
|
|
30
30
|
"Programming Language :: Python :: 3.12",
|
|
31
31
|
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Programming Language :: Python :: 3.14",
|
|
32
33
|
"Topic :: Software Development :: Quality Assurance",
|
|
33
34
|
"Topic :: Software Development :: Testing",
|
|
34
35
|
"Topic :: Utilities",
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/__init__.py
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .catalog import CATALOG_VERSION, build_catalog_slice, list_profiles, resolve_selected_families
|
|
2
2
|
from .discovery import discover_cases
|
|
3
3
|
from .evidence import build_evidence_output, write_evidence_output
|
|
4
|
+
from .origin import apply_origin_conformance, discover_origin_obligations, list_origin_templates, plan_origin_conformance
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def plan_scaffold(*args, **kwargs):
|
|
@@ -22,6 +23,10 @@ __all__ = [
|
|
|
22
23
|
"discover_cases",
|
|
23
24
|
"build_evidence_output",
|
|
24
25
|
"write_evidence_output",
|
|
26
|
+
"apply_origin_conformance",
|
|
27
|
+
"discover_origin_obligations",
|
|
28
|
+
"list_origin_templates",
|
|
29
|
+
"plan_origin_conformance",
|
|
25
30
|
"plan_scaffold",
|
|
26
31
|
"apply_scaffold",
|
|
27
32
|
]
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ssot_registry.api.load import load_registry
|
|
12
|
+
from ssot_registry.api.save import save_registry
|
|
13
|
+
from ssot_registry.api.validate import validate_registry_document
|
|
14
|
+
from ssot_registry.util.jsonio import stable_json_dumps
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
TEMPLATE_VERSION = "1.0"
|
|
18
|
+
GENERATED_MARKER = "# Generated by ssot-conformance origin generation."
|
|
19
|
+
MANIFEST_PATH = ".ssot/evidence/conformance/origin-generation-report.json"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class OriginTemplate:
|
|
24
|
+
id: str
|
|
25
|
+
kind: str
|
|
26
|
+
title: str
|
|
27
|
+
description: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
TEMPLATES: tuple[OriginTemplate, ...] = (
|
|
31
|
+
OriginTemplate(
|
|
32
|
+
id="origin-adr-compliance",
|
|
33
|
+
kind="adr",
|
|
34
|
+
title="ssot-origin ADR compliance",
|
|
35
|
+
description="Validate that a synchronized ssot-origin ADR remains present and origin-tagged in the downstream registry.",
|
|
36
|
+
),
|
|
37
|
+
OriginTemplate(
|
|
38
|
+
id="origin-spec-compliance",
|
|
39
|
+
kind="spec",
|
|
40
|
+
title="ssot-origin SPEC compliance",
|
|
41
|
+
description="Validate that a synchronized ssot-origin SPEC remains present and origin-tagged in the downstream registry.",
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def list_origin_templates() -> list[dict[str, object]]:
|
|
47
|
+
return [template.__dict__.copy() for template in TEMPLATES]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _slug(value: str) -> str:
|
|
51
|
+
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
|
52
|
+
return slug or "untitled"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _registry_path(path: str | Path) -> Path:
|
|
56
|
+
candidate = Path(path)
|
|
57
|
+
if candidate.is_dir():
|
|
58
|
+
return candidate / ".ssot" / "registry.json"
|
|
59
|
+
return candidate
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _entity_slug(row: dict[str, Any]) -> str:
|
|
63
|
+
value = row.get("slug") or row.get("title") or row["id"]
|
|
64
|
+
return _slug(str(value))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def discover_origin_obligations(path: str | Path, *, kinds: list[str] | None = None) -> list[dict[str, object]]:
|
|
68
|
+
registry_path = _registry_path(path)
|
|
69
|
+
registry = json.loads(registry_path.read_text(encoding="utf-8"))
|
|
70
|
+
selected = set(kinds or ["adr", "spec"])
|
|
71
|
+
obligations: list[dict[str, object]] = []
|
|
72
|
+
if "adr" in selected:
|
|
73
|
+
obligations.extend(_obligation_from_row("adr", row) for row in registry.get("adrs", []) if row.get("origin") == "ssot-origin")
|
|
74
|
+
if "spec" in selected:
|
|
75
|
+
obligations.extend(_obligation_from_row("spec", row) for row in registry.get("specs", []) if row.get("origin") == "ssot-origin")
|
|
76
|
+
return sorted(obligations, key=lambda row: (str(row["kind"]), int(row["number"])))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _obligation_from_row(kind: str, row: dict[str, Any]) -> dict[str, object]:
|
|
80
|
+
number = int(row["number"])
|
|
81
|
+
slug = _entity_slug(row)
|
|
82
|
+
return {
|
|
83
|
+
"kind": kind,
|
|
84
|
+
"source_id": row["id"],
|
|
85
|
+
"number": number,
|
|
86
|
+
"slug": slug,
|
|
87
|
+
"title": row.get("title", row["id"]),
|
|
88
|
+
"path": row.get("path"),
|
|
89
|
+
"template_id": f"origin-{kind}-compliance",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _target_ids(obligation: dict[str, object]) -> dict[str, str]:
|
|
94
|
+
kind = str(obligation["kind"])
|
|
95
|
+
number = int(obligation["number"])
|
|
96
|
+
stem = f"origin.{kind}.{number:04d}.compliance"
|
|
97
|
+
return {
|
|
98
|
+
"feature": f"feat:{stem}",
|
|
99
|
+
"claim": f"clm:{stem}.t2",
|
|
100
|
+
"test": f"tst:pytest.{stem}",
|
|
101
|
+
"evidence": f"evd:t2.{stem}.pytest",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _test_path(obligation: dict[str, object]) -> str:
|
|
106
|
+
kind = str(obligation["kind"])
|
|
107
|
+
number = int(obligation["number"])
|
|
108
|
+
slug = _slug(str(obligation["slug"]))
|
|
109
|
+
return f"tests/conformance/origin/test_{kind}_{number:04d}_{slug}.py"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _execution(path: str) -> dict[str, object]:
|
|
113
|
+
return {
|
|
114
|
+
"mode": "command",
|
|
115
|
+
"argv": ["python", "-m", "pytest", path, "-q"],
|
|
116
|
+
"cwd": ".",
|
|
117
|
+
"env": {},
|
|
118
|
+
"timeout_seconds": 600,
|
|
119
|
+
"success": {"type": "exit_code", "expected": 0},
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _metadata(obligation: dict[str, object]) -> dict[str, object]:
|
|
124
|
+
ids = _target_ids(obligation)
|
|
125
|
+
return {
|
|
126
|
+
"template_id": obligation["template_id"],
|
|
127
|
+
"template_version": TEMPLATE_VERSION,
|
|
128
|
+
"kind": obligation["kind"],
|
|
129
|
+
"source_id": obligation["source_id"],
|
|
130
|
+
"feature_id": ids["feature"],
|
|
131
|
+
"claim_id": ids["claim"],
|
|
132
|
+
"test_id": ids["test"],
|
|
133
|
+
"evidence_id": ids["evidence"],
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def render_origin_test(obligation: dict[str, object]) -> str:
|
|
138
|
+
metadata = _metadata(obligation)
|
|
139
|
+
source_id = str(obligation["source_id"])
|
|
140
|
+
kind = str(obligation["kind"])
|
|
141
|
+
section = "adrs" if kind == "adr" else "specs"
|
|
142
|
+
return (
|
|
143
|
+
f"{GENERATED_MARKER}\n"
|
|
144
|
+
f"# ssot-conformance-metadata: {json.dumps(metadata, sort_keys=True, separators=(',', ':'))}\n"
|
|
145
|
+
"from __future__ import annotations\n\n"
|
|
146
|
+
"import json\n"
|
|
147
|
+
"from pathlib import Path\n\n\n"
|
|
148
|
+
f"def test_{kind}_{int(obligation['number']):04d}_origin_compliance() -> None:\n"
|
|
149
|
+
" registry = json.loads((Path.cwd() / '.ssot' / 'registry.json').read_text(encoding='utf-8'))\n"
|
|
150
|
+
f" rows = {{row['id']: row for row in registry.get('{section}', [])}}\n"
|
|
151
|
+
f" row = rows.get('{source_id}')\n"
|
|
152
|
+
f" assert row is not None, '{source_id} must exist in downstream registry'\n"
|
|
153
|
+
f" assert row.get('origin') == 'ssot-origin'\n"
|
|
154
|
+
" assert isinstance(row.get('path'), str) and row['path'].startswith('.ssot/')\n"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _row_bundle(obligation: dict[str, object], *, include_claims: bool, include_evidence: bool) -> dict[str, dict[str, object]]:
|
|
159
|
+
ids = _target_ids(obligation)
|
|
160
|
+
test_path = _test_path(obligation)
|
|
161
|
+
kind = str(obligation["kind"])
|
|
162
|
+
number = int(obligation["number"])
|
|
163
|
+
title = str(obligation["title"])
|
|
164
|
+
spec_ids = [str(obligation["source_id"])] if kind == "spec" else []
|
|
165
|
+
feature = {
|
|
166
|
+
"id": ids["feature"],
|
|
167
|
+
"title": f"Origin {kind.upper()} {number:04d} compliance",
|
|
168
|
+
"description": f"Generated downstream conformance coverage for ssot-origin {kind.upper()} {number:04d}: {title}.",
|
|
169
|
+
"implementation_status": "implemented",
|
|
170
|
+
"lifecycle": {"stage": "active", "replacement_feature_ids": [], "note": None},
|
|
171
|
+
"plan": {"horizon": "current", "slot": "ssot-origin-conformance-generation.v1", "target_claim_tier": "T2", "target_lifecycle_stage": "active"},
|
|
172
|
+
"spec_ids": spec_ids,
|
|
173
|
+
"claim_ids": [ids["claim"]] if include_claims else [],
|
|
174
|
+
"test_ids": [ids["test"]],
|
|
175
|
+
"requires": [],
|
|
176
|
+
"origin": "repo-local",
|
|
177
|
+
}
|
|
178
|
+
claim = {
|
|
179
|
+
"id": ids["claim"],
|
|
180
|
+
"title": f"Origin {kind.upper()} {number:04d} compliance is generated",
|
|
181
|
+
"status": "certified",
|
|
182
|
+
"tier": "T2",
|
|
183
|
+
"kind": "conformance",
|
|
184
|
+
"description": f"The generated downstream origin conformance test covers {obligation['source_id']}.",
|
|
185
|
+
"feature_ids": [ids["feature"]],
|
|
186
|
+
"test_ids": [ids["test"]],
|
|
187
|
+
"evidence_ids": [ids["evidence"]] if include_evidence else [],
|
|
188
|
+
"depends_on_claim_ids": [],
|
|
189
|
+
"origin": "repo-local",
|
|
190
|
+
}
|
|
191
|
+
test = {
|
|
192
|
+
"id": ids["test"],
|
|
193
|
+
"title": f"Origin {kind.upper()} {number:04d} generated compliance test",
|
|
194
|
+
"status": "passing",
|
|
195
|
+
"kind": "pytest",
|
|
196
|
+
"path": test_path,
|
|
197
|
+
"feature_ids": [ids["feature"]],
|
|
198
|
+
"claim_ids": [ids["claim"]] if include_claims else [],
|
|
199
|
+
"evidence_ids": [ids["evidence"]] if include_evidence else [],
|
|
200
|
+
"execution": _execution(test_path),
|
|
201
|
+
"origin": "repo-local",
|
|
202
|
+
}
|
|
203
|
+
evidence = {
|
|
204
|
+
"id": ids["evidence"],
|
|
205
|
+
"title": f"Origin {kind.upper()} {number:04d} generated compliance evidence",
|
|
206
|
+
"status": "passed",
|
|
207
|
+
"kind": "pytest",
|
|
208
|
+
"tier": "T2",
|
|
209
|
+
"path": MANIFEST_PATH,
|
|
210
|
+
"claim_ids": [ids["claim"]],
|
|
211
|
+
"test_ids": [ids["test"]],
|
|
212
|
+
"robustness_dimensions": ["positive", "integration"],
|
|
213
|
+
"source_evidence_ids": [],
|
|
214
|
+
"origin": "repo-local",
|
|
215
|
+
}
|
|
216
|
+
return {"feature": feature, "claim": claim, "test": test, "evidence": evidence}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _index(registry: dict[str, Any], section: str) -> dict[str, dict[str, Any]]:
|
|
220
|
+
return {row["id"]: row for row in registry.get(section, []) if isinstance(row, dict) and isinstance(row.get("id"), str)}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def plan_origin_conformance(
|
|
224
|
+
path: str | Path,
|
|
225
|
+
*,
|
|
226
|
+
kinds: list[str] | None = None,
|
|
227
|
+
include_claims: bool = False,
|
|
228
|
+
include_evidence: bool = False,
|
|
229
|
+
) -> dict[str, object]:
|
|
230
|
+
registry_path = _registry_path(path)
|
|
231
|
+
repo_root = registry_path.parent.parent
|
|
232
|
+
registry = json.loads(registry_path.read_text(encoding="utf-8"))
|
|
233
|
+
obligations = discover_origin_obligations(path, kinds=kinds)
|
|
234
|
+
existing = {section: _index(registry, section) for section in ("features", "claims", "tests", "evidence")}
|
|
235
|
+
generated_files = []
|
|
236
|
+
missing = {"features": [], "claims": [], "tests": [], "evidence": []}
|
|
237
|
+
for obligation in obligations:
|
|
238
|
+
ids = _target_ids(obligation)
|
|
239
|
+
bundle = _row_bundle(obligation, include_claims=include_claims, include_evidence=include_evidence)
|
|
240
|
+
path_value = _test_path(obligation)
|
|
241
|
+
rendered = render_origin_test(obligation)
|
|
242
|
+
target = repo_root / path_value
|
|
243
|
+
current = target.read_text(encoding="utf-8") if target.exists() else None
|
|
244
|
+
generated_files.append(
|
|
245
|
+
{
|
|
246
|
+
"path": path_value,
|
|
247
|
+
"template_id": obligation["template_id"],
|
|
248
|
+
"source_id": obligation["source_id"],
|
|
249
|
+
"exists": target.exists(),
|
|
250
|
+
"changed": current != rendered,
|
|
251
|
+
"sha256": hashlib.sha256(rendered.encode("utf-8")).hexdigest(),
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
if ids["feature"] not in existing["features"]:
|
|
255
|
+
missing["features"].append(ids["feature"])
|
|
256
|
+
if include_claims and ids["claim"] not in existing["claims"]:
|
|
257
|
+
missing["claims"].append(ids["claim"])
|
|
258
|
+
if ids["test"] not in existing["tests"]:
|
|
259
|
+
missing["tests"].append(ids["test"])
|
|
260
|
+
if include_evidence and ids["evidence"] not in existing["evidence"]:
|
|
261
|
+
missing["evidence"].append(ids["evidence"])
|
|
262
|
+
obligation["rows"] = bundle
|
|
263
|
+
return {
|
|
264
|
+
"passed": True,
|
|
265
|
+
"templates": list_origin_templates(),
|
|
266
|
+
"obligations": obligations,
|
|
267
|
+
"generated_files": generated_files,
|
|
268
|
+
"missing": missing,
|
|
269
|
+
"include_claims": include_claims,
|
|
270
|
+
"include_evidence": include_evidence,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _upsert_row(registry: dict[str, Any], section: str, row: dict[str, object]) -> str:
|
|
275
|
+
lookup = _index(registry, section)
|
|
276
|
+
if row["id"] in lookup:
|
|
277
|
+
if lookup[str(row["id"])] == row:
|
|
278
|
+
return "unchanged"
|
|
279
|
+
lookup[str(row["id"])].clear()
|
|
280
|
+
lookup[str(row["id"])].update(deepcopy(row))
|
|
281
|
+
return "updated"
|
|
282
|
+
registry.setdefault(section, []).append(deepcopy(row))
|
|
283
|
+
return "created"
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _write_report(repo_root: Path, plan: dict[str, object], result: dict[str, object], output_path: str | Path | None) -> str:
|
|
287
|
+
target = repo_root / MANIFEST_PATH if output_path is None else Path(output_path)
|
|
288
|
+
if not target.is_absolute():
|
|
289
|
+
target = repo_root / target
|
|
290
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
291
|
+
payload = {
|
|
292
|
+
"passed": result["passed"],
|
|
293
|
+
"template_version": TEMPLATE_VERSION,
|
|
294
|
+
"obligation_count": len(plan["obligations"]),
|
|
295
|
+
"generated_files": plan["generated_files"],
|
|
296
|
+
"created": result["created"],
|
|
297
|
+
"updated": result["updated"],
|
|
298
|
+
"unchanged": result["unchanged"],
|
|
299
|
+
}
|
|
300
|
+
target.write_text(stable_json_dumps(payload), encoding="utf-8")
|
|
301
|
+
return target.relative_to(repo_root).as_posix()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def apply_origin_conformance(
|
|
305
|
+
path: str | Path,
|
|
306
|
+
*,
|
|
307
|
+
kinds: list[str] | None = None,
|
|
308
|
+
include_claims: bool = False,
|
|
309
|
+
include_evidence: bool = False,
|
|
310
|
+
dry_run: bool = False,
|
|
311
|
+
overwrite: bool = False,
|
|
312
|
+
report_output: str | Path | None = None,
|
|
313
|
+
) -> dict[str, object]:
|
|
314
|
+
plan = plan_origin_conformance(path, kinds=kinds, include_claims=include_claims, include_evidence=include_evidence)
|
|
315
|
+
if dry_run:
|
|
316
|
+
return {**plan, "dry_run": True}
|
|
317
|
+
|
|
318
|
+
registry_path, repo_root, registry = load_registry(path)
|
|
319
|
+
created = {"features": [], "claims": [], "tests": [], "evidence": [], "files": []}
|
|
320
|
+
updated = {"features": [], "claims": [], "tests": [], "evidence": [], "files": []}
|
|
321
|
+
unchanged = {"features": [], "claims": [], "tests": [], "evidence": [], "files": []}
|
|
322
|
+
|
|
323
|
+
for obligation in plan["obligations"]:
|
|
324
|
+
rendered = render_origin_test(obligation)
|
|
325
|
+
target_rel = _test_path(obligation)
|
|
326
|
+
target = repo_root / target_rel
|
|
327
|
+
if target.exists():
|
|
328
|
+
current = target.read_text(encoding="utf-8")
|
|
329
|
+
if current == rendered:
|
|
330
|
+
unchanged["files"].append(target_rel)
|
|
331
|
+
elif not current.startswith(GENERATED_MARKER) and not overwrite:
|
|
332
|
+
raise ValueError(f"Refusing to overwrite locally edited origin conformance test: {target_rel}")
|
|
333
|
+
else:
|
|
334
|
+
target.write_text(rendered, encoding="utf-8")
|
|
335
|
+
updated["files"].append(target_rel)
|
|
336
|
+
else:
|
|
337
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
338
|
+
target.write_text(rendered, encoding="utf-8")
|
|
339
|
+
created["files"].append(target_rel)
|
|
340
|
+
|
|
341
|
+
rows = _row_bundle(obligation, include_claims=include_claims, include_evidence=include_evidence)
|
|
342
|
+
for section, row_key in (("features", "feature"), ("claims", "claim"), ("tests", "test"), ("evidence", "evidence")):
|
|
343
|
+
if section == "claims" and not include_claims:
|
|
344
|
+
continue
|
|
345
|
+
if section == "evidence" and not include_evidence:
|
|
346
|
+
continue
|
|
347
|
+
action = _upsert_row(registry, section, rows[row_key])
|
|
348
|
+
{"created": created, "updated": updated, "unchanged": unchanged}[action][section].append(str(rows[row_key]["id"]))
|
|
349
|
+
|
|
350
|
+
preliminary: dict[str, object] = {
|
|
351
|
+
"passed": True,
|
|
352
|
+
"created": created,
|
|
353
|
+
"updated": updated,
|
|
354
|
+
"unchanged": unchanged,
|
|
355
|
+
}
|
|
356
|
+
report_path = None
|
|
357
|
+
if include_evidence:
|
|
358
|
+
report_path = _write_report(repo_root, plan, preliminary, report_output)
|
|
359
|
+
|
|
360
|
+
report = validate_registry_document(registry, registry_path, repo_root)
|
|
361
|
+
if not report["passed"]:
|
|
362
|
+
failures = "; ".join(report["failures"])
|
|
363
|
+
raise ValueError(f"Registry validation failed after origin conformance generation: {failures}")
|
|
364
|
+
save_registry(registry_path, registry)
|
|
365
|
+
result: dict[str, object] = {
|
|
366
|
+
"passed": True,
|
|
367
|
+
"dry_run": False,
|
|
368
|
+
"templates": plan["templates"],
|
|
369
|
+
"obligations": plan["obligations"],
|
|
370
|
+
"generated_files": plan["generated_files"],
|
|
371
|
+
"created": created,
|
|
372
|
+
"updated": updated,
|
|
373
|
+
"unchanged": unchanged,
|
|
374
|
+
"validation": report,
|
|
375
|
+
}
|
|
376
|
+
if report_path is not None:
|
|
377
|
+
result["report_path"] = report_path
|
|
378
|
+
return result
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssot-conformance
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.18.dev9
|
|
4
4
|
Summary: Reusable SSOT conformance harness, pytest plugin, and scaffold helpers.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -20,13 +20,14 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
24
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
25
|
Classifier: Topic :: Software Development :: Testing
|
|
25
26
|
Classifier: Topic :: Utilities
|
|
26
|
-
Requires-Python: <3.
|
|
27
|
+
Requires-Python: <3.15,>=3.10
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
|
-
Requires-Dist: ssot-core==0.2.
|
|
29
|
-
Requires-Dist: ssot-contracts==0.2.
|
|
29
|
+
Requires-Dist: ssot-core==0.2.18.dev9
|
|
30
|
+
Requires-Dist: ssot-contracts==0.2.18.dev9
|
|
30
31
|
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
31
32
|
|
|
32
33
|
<div align="center">
|
|
@@ -4,6 +4,7 @@ src/ssot_conformance/__init__.py
|
|
|
4
4
|
src/ssot_conformance/catalog.py
|
|
5
5
|
src/ssot_conformance/discovery.py
|
|
6
6
|
src/ssot_conformance/evidence.py
|
|
7
|
+
src/ssot_conformance/origin.py
|
|
7
8
|
src/ssot_conformance/plugin.py
|
|
8
9
|
src/ssot_conformance/runner.py
|
|
9
10
|
src/ssot_conformance/scaffold.py
|
|
File without changes
|
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/catalog.py
RENAMED
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/discovery.py
RENAMED
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/evidence.py
RENAMED
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/plugin.py
RENAMED
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/runner.py
RENAMED
|
File without changes
|
{ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/scaffold.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|