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.
Files changed (27) hide show
  1. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/PKG-INFO +5 -4
  2. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/pyproject.toml +5 -4
  3. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/__init__.py +5 -0
  4. ssot_conformance-0.2.18.dev9/src/ssot_conformance/origin.py +378 -0
  5. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/PKG-INFO +5 -4
  6. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/SOURCES.txt +1 -0
  7. ssot_conformance-0.2.18.dev9/src/ssot_conformance.egg-info/requires.txt +5 -0
  8. ssot_conformance-0.2.17.dev2/src/ssot_conformance.egg-info/requires.txt +0 -5
  9. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/README.md +0 -0
  10. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/setup.cfg +0 -0
  11. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/__init__.py +0 -0
  12. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_boundary_release_contract.py +0 -0
  13. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_document_contract.py +0 -0
  14. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_feature_spec_contract.py +0 -0
  15. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_id_contract.py +0 -0
  16. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_proof_chain_contract.py +0 -0
  17. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_registry_contract.py +0 -0
  18. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/cases/test_spec_adr_contract.py +0 -0
  19. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/catalog.py +0 -0
  20. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/discovery.py +0 -0
  21. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/evidence.py +0 -0
  22. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/plugin.py +0 -0
  23. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/runner.py +0 -0
  24. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance/scaffold.py +0 -0
  25. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/dependency_links.txt +0 -0
  26. {ssot_conformance-0.2.17.dev2 → ssot_conformance-0.2.18.dev9}/src/ssot_conformance.egg-info/entry_points.txt +0 -0
  27. {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.17.dev2
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.14,>=3.10
27
+ Requires-Python: <3.15,>=3.10
27
28
  Description-Content-Type: text/markdown
28
- Requires-Dist: ssot-core==0.2.17.dev2
29
- Requires-Dist: ssot-contracts==0.2.17.dev2
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.17.dev2"
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.14"
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.17.dev2",
15
- "ssot-contracts==0.2.17.dev2",
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",
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssot-conformance
3
- Version: 0.2.17.dev2
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.14,>=3.10
27
+ Requires-Python: <3.15,>=3.10
27
28
  Description-Content-Type: text/markdown
28
- Requires-Dist: ssot-core==0.2.17.dev2
29
- Requires-Dist: ssot-contracts==0.2.17.dev2
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
@@ -0,0 +1,5 @@
1
+ ssot-core==0.2.18.dev9
2
+ ssot-contracts==0.2.18.dev9
3
+
4
+ [:python_version < "3.11"]
5
+ tomli>=2.0.1
@@ -1,5 +0,0 @@
1
- ssot-core==0.2.17.dev2
2
- ssot-contracts==0.2.17.dev2
3
-
4
- [:python_version < "3.11"]
5
- tomli>=2.0.1