ssot-core 0.2.20.dev1__tar.gz → 0.2.21.dev1__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 (141) hide show
  1. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/PKG-INFO +4 -4
  2. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/pyproject.toml +4 -4
  3. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_core.egg-info/PKG-INFO +4 -4
  4. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_core.egg-info/SOURCES.txt +1 -0
  5. ssot_core-0.2.21.dev1/src/ssot_core.egg-info/requires.txt +7 -0
  6. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/__init__.py +10 -1
  7. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/config.py +26 -0
  8. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/entity_ops.py +265 -1
  9. ssot_core-0.2.21.dev1/src/ssot_registry/api/proof_graph.py +420 -0
  10. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/release.py +39 -0
  11. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/upgrade.py +2 -1
  12. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/service.py +89 -6
  13. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/sqlite_store.py +52 -0
  14. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/publication.py +13 -3
  15. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/registry_lock.py +26 -1
  16. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/coverage.py +1 -6
  17. ssot_core-0.2.20.dev1/src/ssot_core.egg-info/requires.txt +0 -7
  18. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/README.md +0 -0
  19. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/setup.cfg +0 -0
  20. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_core.egg-info/dependency_links.txt +0 -0
  21. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_core.egg-info/top_level.txt +0 -0
  22. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/__init__.py +0 -0
  23. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/__main__.py +0 -0
  24. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/acl/__init__.py +0 -0
  25. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/acl/policy.py +0 -0
  26. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/acl/wrapper.py +0 -0
  27. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/boundary.py +0 -0
  28. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/claims.py +0 -0
  29. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/documents.py +0 -0
  30. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/evidence.py +0 -0
  31. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/graph.py +0 -0
  32. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/init.py +0 -0
  33. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/lifecycle.py +0 -0
  34. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/load.py +0 -0
  35. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/local_assurance.py +0 -0
  36. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/origin.py +0 -0
  37. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/packs.py +0 -0
  38. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/plan.py +0 -0
  39. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/profile_eval.py +0 -0
  40. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/profile_resolution.py +0 -0
  41. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/registry.py +0 -0
  42. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/save.py +0 -0
  43. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/status_sync.py +0 -0
  44. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/test_execution.py +0 -0
  45. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/api/validate.py +0 -0
  46. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/__init__.py +0 -0
  47. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/adr_cmd.py +0 -0
  48. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/boundary_cmd.py +0 -0
  49. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/claim_cmd.py +0 -0
  50. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/common.py +0 -0
  51. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/evidence_cmd.py +0 -0
  52. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/feature_cmd.py +0 -0
  53. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/graph_cmd.py +0 -0
  54. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/init_cmd.py +0 -0
  55. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/issue_cmd.py +0 -0
  56. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/main.py +0 -0
  57. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/profile_cmd.py +0 -0
  58. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/registry_cmd.py +0 -0
  59. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/release_cmd.py +0 -0
  60. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/risk_cmd.py +0 -0
  61. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/spec_cmd.py +0 -0
  62. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/test_cmd.py +0 -0
  63. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/upgrade_cmd.py +0 -0
  64. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/cli/validate_cmd.py +0 -0
  65. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/__init__.py +0 -0
  66. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/events.py +0 -0
  67. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/models.py +0 -0
  68. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/paths.py +0 -0
  69. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/scaffold.py +0 -0
  70. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/control/sse.py +0 -0
  71. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/graph/__init__.py +0 -0
  72. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/graph/export_dot.py +0 -0
  73. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/graph/export_json.py +0 -0
  74. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/__init__.py +0 -0
  75. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/certification.py +0 -0
  76. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/claim_closure.py +0 -0
  77. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/claim_tier_gates.py +0 -0
  78. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/completion.py +0 -0
  79. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/document_lifecycle.py +0 -0
  80. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/document_supersession.py +0 -0
  81. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/feature_claims.py +0 -0
  82. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/feature_requirements.py +0 -0
  83. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/lifecycle.py +0 -0
  84. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/profile_requirements.py +0 -0
  85. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/guards/promotion.py +0 -0
  86. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/maturation/__init__.py +0 -0
  87. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/maturation/selector.py +0 -0
  88. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/__init__.py +0 -0
  89. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/boundary.py +0 -0
  90. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/claim.py +0 -0
  91. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/document.py +0 -0
  92. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/enums.py +0 -0
  93. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/evidence.py +0 -0
  94. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/feature.py +0 -0
  95. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/ids.py +0 -0
  96. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/issue.py +0 -0
  97. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/profile.py +0 -0
  98. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/registry.py +0 -0
  99. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/release.py +0 -0
  100. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/risk.py +0 -0
  101. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/schema_version.py +0 -0
  102. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/model/test.py +0 -0
  103. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/reports/__init__.py +0 -0
  104. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/reports/certification_report.py +0 -0
  105. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/reports/summary.py +0 -0
  106. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/reports/validation_report.py +0 -0
  107. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/snapshots/__init__.py +0 -0
  108. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/snapshots/boundary_snapshot.py +0 -0
  109. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/snapshots/hashing.py +0 -0
  110. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/snapshots/published_snapshot.py +0 -0
  111. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/snapshots/release_snapshot.py +0 -0
  112. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/templates/__init__.py +0 -0
  113. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/templates/registry.full.json +0 -0
  114. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/templates/registry.minimal.json +0 -0
  115. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/__init__.py +0 -0
  116. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/document_io.py +0 -0
  117. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/errors.py +0 -0
  118. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/formatting.py +0 -0
  119. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/fs.py +0 -0
  120. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/jcs.py +0 -0
  121. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/jsonio.py +0 -0
  122. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/util/time.py +0 -0
  123. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/__init__.py +0 -0
  124. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/bidirectional.py +0 -0
  125. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/bounds.py +0 -0
  126. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/claim_lineage.py +0 -0
  127. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/documents.py +0 -0
  128. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/feature_parent_links.py +0 -0
  129. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/filesystem.py +0 -0
  130. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/identity.py +0 -0
  131. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/lifecycle.py +0 -0
  132. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/origin.py +0 -0
  133. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/promotion.py +0 -0
  134. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/references.py +0 -0
  135. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/reservations.py +0 -0
  136. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/structure.py +0 -0
  137. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/validators/tiers.py +0 -0
  138. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/version.py +0 -0
  139. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/watch/__init__.py +0 -0
  140. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/watch/git_status.py +0 -0
  141. {ssot_core-0.2.20.dev1 → ssot_core-0.2.21.dev1}/src/ssot_registry/watch/observer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssot-core
3
- Version: 0.2.20.dev1
3
+ Version: 0.2.21.dev1
4
4
  Summary: Core Python runtime, registry model, validation, and release workflow APIs for SSOT.
5
5
  Author-email: Jacob Stewart <jacob@swarmauri.com>
6
6
  License-Expression: Apache-2.0
@@ -36,9 +36,9 @@ Classifier: Topic :: Utilities
36
36
  Requires-Python: <3.15,>=3.10
37
37
  Description-Content-Type: text/markdown
38
38
  Requires-Dist: orjson<4.0,>=3.10
39
- Requires-Dist: ssot-contracts==0.2.20.dev1
40
- Requires-Dist: ssot-pack-contracts<0.3.0,>=0.2.21.dev1
41
- Requires-Dist: ssot-views==0.2.20.dev1
39
+ Requires-Dist: ssot-contracts==0.2.21.dev1
40
+ Requires-Dist: ssot-pack-contracts<0.3.0,>=0.2.22.dev1
41
+ Requires-Dist: ssot-views==0.2.21.dev1
42
42
  Requires-Dist: tomli>=2.0.1; python_version < "3.11"
43
43
 
44
44
  <div align="center">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ssot-core"
7
- version = "0.2.20.dev1"
7
+ version = "0.2.21.dev1"
8
8
  description = "Core Python runtime, registry model, validation, and release workflow APIs for SSOT."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10,<3.15"
@@ -12,9 +12,9 @@ license = "Apache-2.0"
12
12
  authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
13
13
  dependencies = [
14
14
  "orjson>=3.10,<4.0",
15
- "ssot-contracts==0.2.20.dev1",
16
- "ssot-pack-contracts>=0.2.21.dev1,<0.3.0",
17
- "ssot-views==0.2.20.dev1",
15
+ "ssot-contracts==0.2.21.dev1",
16
+ "ssot-pack-contracts>=0.2.22.dev1,<0.3.0",
17
+ "ssot-views==0.2.21.dev1",
18
18
  "tomli>=2.0.1; python_version < '3.11'",
19
19
  ]
20
20
  keywords = ["ssot", "core", "registry", "validation", "release-management", "governance", "compliance"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssot-core
3
- Version: 0.2.20.dev1
3
+ Version: 0.2.21.dev1
4
4
  Summary: Core Python runtime, registry model, validation, and release workflow APIs for SSOT.
5
5
  Author-email: Jacob Stewart <jacob@swarmauri.com>
6
6
  License-Expression: Apache-2.0
@@ -36,9 +36,9 @@ Classifier: Topic :: Utilities
36
36
  Requires-Python: <3.15,>=3.10
37
37
  Description-Content-Type: text/markdown
38
38
  Requires-Dist: orjson<4.0,>=3.10
39
- Requires-Dist: ssot-contracts==0.2.20.dev1
40
- Requires-Dist: ssot-pack-contracts<0.3.0,>=0.2.21.dev1
41
- Requires-Dist: ssot-views==0.2.20.dev1
39
+ Requires-Dist: ssot-contracts==0.2.21.dev1
40
+ Requires-Dist: ssot-pack-contracts<0.3.0,>=0.2.22.dev1
41
+ Requires-Dist: ssot-views==0.2.21.dev1
42
42
  Requires-Dist: tomli>=2.0.1; python_version < "3.11"
43
43
 
44
44
  <div align="center">
@@ -28,6 +28,7 @@ src/ssot_registry/api/packs.py
28
28
  src/ssot_registry/api/plan.py
29
29
  src/ssot_registry/api/profile_eval.py
30
30
  src/ssot_registry/api/profile_resolution.py
31
+ src/ssot_registry/api/proof_graph.py
31
32
  src/ssot_registry/api/registry.py
32
33
  src/ssot_registry/api/release.py
33
34
  src/ssot_registry/api/save.py
@@ -0,0 +1,7 @@
1
+ orjson<4.0,>=3.10
2
+ ssot-contracts==0.2.21.dev1
3
+ ssot-pack-contracts<0.3.0,>=0.2.22.dev1
4
+ ssot-views==0.2.21.dev1
5
+
6
+ [:python_version < "3.11"]
7
+ tomli>=2.0.1
@@ -1,6 +1,6 @@
1
1
  from .boundary import freeze_boundary
2
2
  from .claims import evaluate_claims
3
- from .config import ensure_repo_config, load_repo_config, run_repo_automation, validate_repo_config
3
+ from .config import ensure_repo_config, load_repo_config, resolve_feature_create_auto_scaffold, run_repo_automation, validate_repo_config
4
4
  from .documents import (
5
5
  create_document,
6
6
  create_document_reservation,
@@ -24,7 +24,9 @@ from .entity_ops import (
24
24
  add_release_boundaries,
25
25
  add_release_claims,
26
26
  add_release_evidence,
27
+ audit_feature_parent_links,
27
28
  create_entity,
29
+ create_feature_with_scaffolded_proof_graph,
28
30
  delete_entity,
29
31
  get_entity,
30
32
  link_entities,
@@ -36,6 +38,7 @@ from .entity_ops import (
36
38
  remove_release_boundaries,
37
39
  remove_release_claims,
38
40
  remove_release_evidence,
41
+ migrate_feature_parent_audit_edge,
39
42
  set_claim_status,
40
43
  set_claim_tier,
41
44
  set_feature_parents,
@@ -60,6 +63,7 @@ from .load import load_registry
60
63
  from .plan import plan_features, plan_issues
61
64
  from .packs import inspect_pack, preflight_pack, sync_pack
62
65
  from .origin import sync_origin_assurance_rows
66
+ from .proof_graph import certify_feature_proof_graphs
63
67
  from .registry import export_registry
64
68
  from .release import certify_release, promote_release, publish_release, revoke_release
65
69
  from .save import save_registry, save_registry_unchecked
@@ -77,6 +81,7 @@ __all__ = [
77
81
  "ensure_repo_config",
78
82
  "load_repo_config",
79
83
  "validate_repo_config",
84
+ "resolve_feature_create_auto_scaffold",
80
85
  "run_repo_automation",
81
86
  "create_document",
82
87
  "get_document",
@@ -93,6 +98,7 @@ __all__ = [
93
98
  "create_document_reservation",
94
99
  "list_document_reservations",
95
100
  "create_entity",
101
+ "create_feature_with_scaffolded_proof_graph",
96
102
  "get_entity",
97
103
  "list_entities",
98
104
  "update_entity",
@@ -111,6 +117,8 @@ __all__ = [
111
117
  "add_feature_children",
112
118
  "remove_feature_children",
113
119
  "list_feature_children",
120
+ "audit_feature_parent_links",
121
+ "migrate_feature_parent_audit_edge",
114
122
  "add_release_boundaries",
115
123
  "remove_release_boundaries",
116
124
  "add_release_claims",
@@ -123,6 +131,7 @@ __all__ = [
123
131
  "preflight_pack",
124
132
  "sync_pack",
125
133
  "sync_origin_assurance_rows",
134
+ "certify_feature_proof_graphs",
126
135
  "set_feature_lifecycle",
127
136
  "build_artifact_manifest",
128
137
  "build_local_evidence_bundle",
@@ -24,6 +24,13 @@ _DEFAULT_CONFIG: dict[str, Any] = {
24
24
  "interactive": False,
25
25
  "fail_closed": True,
26
26
  },
27
+ "commands": {
28
+ "feature": {
29
+ "create": {
30
+ "auto_scaffold_proof_graph": True,
31
+ }
32
+ }
33
+ },
27
34
  "sync": {
28
35
  "docs": "manual",
29
36
  "templates": "manual",
@@ -51,6 +58,9 @@ _DEFAULT_TEMPLATE = """[policy]
51
58
  interactive = false
52
59
  fail_closed = true
53
60
 
61
+ [commands.feature.create]
62
+ auto_scaffold_proof_graph = true
63
+
54
64
  [sync]
55
65
  docs = "manual"
56
66
  templates = "manual"
@@ -148,6 +158,11 @@ def validate_repo_config_payload(config: dict[str, Any]) -> dict[str, Any]:
148
158
  _expect_bool(policy, "interactive", prefix="policy")
149
159
  _expect_bool(policy, "fail_closed", prefix="policy")
150
160
 
161
+ commands = _expect_table(normalized, "commands")
162
+ feature_commands = _expect_table(commands, "feature")
163
+ feature_create = _expect_table(feature_commands, "create")
164
+ _expect_bool(feature_create, "auto_scaffold_proof_graph", prefix="commands.feature.create")
165
+
151
166
  sync = _expect_table(normalized, "sync")
152
167
  for field_name in ("docs", "templates", "upstream_packages"):
153
168
  _expect_choice(sync, field_name, _AUTOMATION_MODE_CHOICES, prefix="sync")
@@ -226,6 +241,17 @@ def validate_repo_config(path: str | Path) -> dict[str, Any]:
226
241
  }
227
242
 
228
243
 
244
+ def resolve_feature_create_auto_scaffold(path: str | Path, explicit: bool | None) -> bool:
245
+ if explicit is not None:
246
+ return explicit
247
+ try:
248
+ payload = load_repo_config(path)
249
+ except FileNotFoundError:
250
+ return bool(_DEFAULT_CONFIG["commands"]["feature"]["create"]["auto_scaffold_proof_graph"])
251
+ feature_create = payload["config"]["commands"]["feature"]["create"]
252
+ return bool(feature_create["auto_scaffold_proof_graph"])
253
+
254
+
229
255
  def run_repo_automation(path: str | Path) -> dict[str, Any]:
230
256
  global _AUTOMATION_DEPTH
231
257
 
@@ -4,9 +4,10 @@ from copy import deepcopy
4
4
  from pathlib import Path
5
5
  from typing import Any
6
6
 
7
- from ssot_registry.model.enums import ASSURANCE_ENTITY_SECTIONS, ASSURANCE_ORIGINS, REF_FIELD_TARGETS
7
+ from ssot_registry.model.enums import ASSURANCE_ENTITY_SECTIONS, ASSURANCE_ORIGINS, CLAIM_TIER_RANK, REF_FIELD_TARGETS
8
8
  from ssot_registry.model.registry import normalize_repo_kind
9
9
  from ssot_registry.util.errors import ValidationError
10
+ from ssot_registry.util.jsonio import stable_json_dumps
10
11
 
11
12
  from .load import load_registry
12
13
  from .save import save_registry
@@ -52,6 +53,9 @@ LINKABLE_FIELDS = {
52
53
  }
53
54
 
54
55
  SECTIONS = tuple(SECTION_LABELS)
56
+ PARENT_AUDIT_TERMS = ("requires", "require", "required", "depends", "dependency", "prerequisite", "blocks", "blocked", "before")
57
+ PARENT_AUDIT_TARGET_HORIZONS = {"current", "explicit"}
58
+ PARENT_AUDIT_INCOMPLETE_STATUSES = {"absent", "partial"}
55
59
 
56
60
 
57
61
  def _row_lookup(registry: dict[str, Any], section: str) -> dict[str, dict[str, Any]]:
@@ -192,6 +196,160 @@ def _validate_and_save(registry_path: Path, repo_root: Path, registry: dict[str,
192
196
  }
193
197
 
194
198
 
199
+ def _safe_slug(entity_id: str) -> str:
200
+ return entity_id.split(":", 1)[-1].replace("/", ".").replace(" ", "-").lower()
201
+
202
+
203
+ def _tier_sequence(target_tier: str) -> list[str]:
204
+ if target_tier not in CLAIM_TIER_RANK:
205
+ raise ValueError(f"Unsupported claim tier: {target_tier}")
206
+ return [tier for tier, _rank in sorted(CLAIM_TIER_RANK.items(), key=lambda item: item[1]) if CLAIM_TIER_RANK[tier] <= CLAIM_TIER_RANK[target_tier]]
207
+
208
+
209
+ def _ensure_scaffold_file(path: Path, content: str) -> None:
210
+ path.parent.mkdir(parents=True, exist_ok=True)
211
+ if not path.exists():
212
+ path.write_text(content, encoding="utf-8")
213
+
214
+
215
+ def create_feature_with_scaffolded_proof_graph(path: str | Path, row: dict[str, Any]) -> dict[str, Any]:
216
+ registry_path, repo_root, registry = load_registry(path)
217
+ entity_id = row.get("id")
218
+ if not isinstance(entity_id, str):
219
+ raise ValueError("feature row must include a string id")
220
+ if entity_id in _row_lookup(registry, "features"):
221
+ raise ValueError(f"Feature already exists: {entity_id}")
222
+
223
+ candidate = deepcopy(row)
224
+ _ensure_assurance_origin(registry, "features", candidate)
225
+ candidate.setdefault("parent_feature_ids", [])
226
+ _normalize_feature_parent_ids(candidate)
227
+ for field_name in LINKABLE_FIELDS["features"]:
228
+ if field_name in candidate and isinstance(candidate[field_name], list):
229
+ candidate[field_name] = _dedupe_preserve(candidate[field_name])
230
+ _validate_assurance_origin_mutation(registry, "features", candidate)
231
+
232
+ plan = candidate.get("plan") if isinstance(candidate.get("plan"), dict) else {}
233
+ target_tier = str(plan.get("target_claim_tier") or "T1")
234
+ tiers = _tier_sequence(target_tier)
235
+ slug = _safe_slug(entity_id)
236
+ evidence_id = f"evd:{target_tier.lower()}.{slug}.proof-graph"
237
+ evidence_path = f".ssot/evidence/{slug}/proof-graph-{target_tier.lower()}.json"
238
+ claim_ids = [f"clm:{slug}.{tier.lower()}" for tier in tiers]
239
+ test_ids = [f"tst:pytest.{slug}.{tier.lower()}.proof-graph" for tier in tiers]
240
+ test_path_by_tier = {
241
+ tier: f"tests/ssot_scaffold/test_{slug.replace('.', '_').replace('-', '_')}_{tier.lower()}_proof_graph.py"
242
+ for tier in tiers
243
+ }
244
+
245
+ claims: list[dict[str, Any]] = []
246
+ for index, tier in enumerate(tiers):
247
+ claim_ids_in_chain = claim_ids[: index + 1]
248
+ claim = {
249
+ "id": claim_ids[index],
250
+ "title": f"{candidate['title']} {tier} claim",
251
+ "status": "declared" if tier == "T0" else "proposed",
252
+ "tier": tier,
253
+ "kind": "runtime",
254
+ "description": f"{tier} scaffold claim for {entity_id}.",
255
+ "origin": candidate["origin"],
256
+ "feature_ids": [entity_id],
257
+ "test_ids": [test_ids[index]],
258
+ "evidence_ids": [evidence_id],
259
+ "depends_on_claim_ids": claim_ids_in_chain[:-1],
260
+ }
261
+ claims.append(claim)
262
+
263
+ test_rows: list[dict[str, Any]] = []
264
+ for index, tier in enumerate(tiers):
265
+ test_path = test_path_by_tier[tier]
266
+ test_rows.append(
267
+ {
268
+ "id": test_ids[index],
269
+ "title": f"{candidate['title']} {tier} proof-graph scaffold test",
270
+ "body": f"Planned scaffold test for {entity_id} {tier} support.",
271
+ "origin": candidate["origin"],
272
+ "status": "planned",
273
+ "kind": "pytest",
274
+ "path": test_path,
275
+ "feature_ids": [entity_id],
276
+ "claim_ids": [claim_ids[index]],
277
+ "evidence_ids": [evidence_id],
278
+ "execution": {
279
+ "argv": ["python", "-m", "pytest", test_path, "-q"],
280
+ "cwd": ".",
281
+ "env": {},
282
+ "mode": "command",
283
+ "success": {"expected": 0, "type": "exit_code"},
284
+ "timeout_seconds": 600,
285
+ },
286
+ }
287
+ )
288
+ evidence_row = {
289
+ "id": evidence_id,
290
+ "title": f"{candidate['title']} proof-graph scaffold evidence",
291
+ "status": "planned",
292
+ "kind": "scaffold",
293
+ "tier": target_tier,
294
+ "body": f"Planned scaffold evidence for {entity_id}.",
295
+ "origin": candidate["origin"],
296
+ "path": evidence_path,
297
+ "claim_ids": claim_ids,
298
+ "test_ids": test_ids,
299
+ }
300
+
301
+ candidate["claim_ids"] = _dedupe_preserve([*claim_ids, *_ensure_list_field(candidate, "claim_ids")])
302
+ candidate["test_ids"] = _dedupe_preserve([*test_ids, *_ensure_list_field(candidate, "test_ids")])
303
+ registry["features"].append(candidate)
304
+ registry.setdefault("claims", []).extend(claims)
305
+ registry.setdefault("tests", []).extend(test_rows)
306
+ registry.setdefault("evidence", []).append(evidence_row)
307
+
308
+ for created in claims:
309
+ _sync_reciprocals_for_row(registry, "claims", created)
310
+ for created in test_rows:
311
+ _sync_reciprocals_for_row(registry, "tests", created)
312
+ _sync_reciprocals_for_row(registry, "evidence", evidence_row)
313
+ _sync_reciprocals_for_row(registry, "features", candidate)
314
+
315
+ for test_path in test_path_by_tier.values():
316
+ _ensure_scaffold_file(
317
+ repo_root / test_path,
318
+ "def test_ssot_scaffold_placeholder():\n assert True\n",
319
+ )
320
+ _ensure_scaffold_file(
321
+ repo_root / evidence_path,
322
+ stable_json_dumps(
323
+ {
324
+ "schema_version": "ssot.evidence.scaffold.v1",
325
+ "feature_id": entity_id,
326
+ "claim_ids": claim_ids,
327
+ "test_ids": test_ids,
328
+ "target_tier": target_tier,
329
+ "status": "planned",
330
+ }
331
+ ),
332
+ )
333
+
334
+ mutation = _validate_and_save(registry_path, repo_root, registry, f"creating feature {entity_id} with scaffolded proof graph")
335
+ return {
336
+ "passed": True,
337
+ "registry_path": registry_path.as_posix(),
338
+ "section": "features",
339
+ "entity": candidate,
340
+ "scaffolded": {
341
+ "claim_ids": claim_ids,
342
+ "test_id": test_ids[-1],
343
+ "test_ids": test_ids,
344
+ "evidence_id": evidence_id,
345
+ "test_path": test_path_by_tier[tiers[-1]],
346
+ "test_paths": [test_path_by_tier[tier] for tier in tiers],
347
+ "evidence_path": evidence_path,
348
+ },
349
+ **mutation,
350
+ }
351
+
352
+
195
353
  def _rewrite_references_for_renamed_id(
196
354
  registry: dict[str, Any],
197
355
  *,
@@ -486,6 +644,112 @@ def list_feature_children(path: str | Path, parent_id: str) -> list[dict[str, An
486
644
  return sorted(rows, key=lambda row: row["id"])
487
645
 
488
646
 
647
+ def _audit_text(row: dict[str, Any]) -> str:
648
+ return " ".join(str(row.get(field_name, "")) for field_name in ("id", "title", "description", "body")).lower()
649
+
650
+
651
+ def audit_feature_parent_links(path: str | Path) -> dict[str, Any]:
652
+ registry_path, _repo_root, registry = load_registry(path)
653
+ feature_lookup = _row_lookup(registry, "features")
654
+ findings: list[dict[str, Any]] = []
655
+
656
+ for feature in sorted(feature_lookup.values(), key=lambda row: row["id"]):
657
+ feature_id = str(feature["id"])
658
+ parent_ids = feature.get("parent_feature_ids", [])
659
+ if not isinstance(parent_ids, list):
660
+ continue
661
+ for parent_id in sorted(dict.fromkeys(parent_ids)):
662
+ parent = feature_lookup.get(parent_id)
663
+ if parent is None:
664
+ continue
665
+
666
+ reasons: list[str] = []
667
+ feature_horizon = feature.get("plan", {}).get("horizon") if isinstance(feature.get("plan"), dict) else None
668
+ parent_status = parent.get("implementation_status")
669
+ if (feature_horizon in PARENT_AUDIT_TARGET_HORIZONS or feature.get("implementation_status") == "implemented") and (
670
+ parent_status in PARENT_AUDIT_INCOMPLETE_STATUSES
671
+ ):
672
+ reasons.append("targeted_or_implemented_child_has_incomplete_parent")
673
+
674
+ combined_text = f"{_audit_text(feature)} {_audit_text(parent)}"
675
+ matched_terms = [term for term in PARENT_AUDIT_TERMS if term in combined_text]
676
+ if matched_terms:
677
+ reasons.append(f"dependency_language:{','.join(matched_terms)}")
678
+
679
+ if not reasons:
680
+ continue
681
+
682
+ confidence = "high" if len(reasons) > 1 else "medium"
683
+ if reasons == [reason for reason in reasons if reason.startswith("dependency_language:")]:
684
+ confidence = "low"
685
+ findings.append(
686
+ {
687
+ "feature_id": feature_id,
688
+ "parent_feature_id": parent_id,
689
+ "reason": reasons,
690
+ "suggested_requires_edge": {"feature_id": feature_id, "requires": parent_id},
691
+ "confidence": confidence,
692
+ }
693
+ )
694
+
695
+ return {
696
+ "passed": True,
697
+ "registry_path": registry_path.as_posix(),
698
+ "findings": findings,
699
+ "summary": {
700
+ "finding_count": len(findings),
701
+ "high_confidence_count": sum(1 for finding in findings if finding["confidence"] == "high"),
702
+ "medium_confidence_count": sum(1 for finding in findings if finding["confidence"] == "medium"),
703
+ "low_confidence_count": sum(1 for finding in findings if finding["confidence"] == "low"),
704
+ },
705
+ }
706
+
707
+
708
+ def migrate_feature_parent_audit_edge(
709
+ path: str | Path,
710
+ feature_id: str,
711
+ parent_id: str,
712
+ *,
713
+ remove_parent_link: bool = False,
714
+ ) -> dict[str, Any]:
715
+ registry_path, repo_root, registry = load_registry(path)
716
+ feature_lookup = _row_lookup(registry, "features")
717
+ if feature_id not in feature_lookup:
718
+ raise ValueError(f"Unknown feature id: {feature_id}")
719
+ if parent_id not in feature_lookup:
720
+ raise ValueError(f"Unknown parent feature id: {parent_id}")
721
+
722
+ feature = feature_lookup[feature_id]
723
+ parent_ids = _ensure_list_field(feature, "parent_feature_ids")
724
+ if parent_id not in parent_ids:
725
+ raise ValueError(f"Feature {feature_id} does not have parent link {parent_id}")
726
+
727
+ requires = _ensure_list_field(feature, "requires")
728
+ added_requires = parent_id not in requires
729
+ if added_requires:
730
+ requires.append(parent_id)
731
+ feature["requires"] = _dedupe_preserve(requires)
732
+
733
+ removed_parent_link = False
734
+ if remove_parent_link:
735
+ feature["parent_feature_ids"] = _dedupe_sorted([value for value in parent_ids if value != parent_id])
736
+ removed_parent_link = True
737
+ else:
738
+ _normalize_feature_parent_ids(feature)
739
+
740
+ mutation = _validate_and_save(registry_path, repo_root, registry, "migrating feature parent audit edge")
741
+ return {
742
+ "passed": True,
743
+ "registry_path": registry_path.as_posix(),
744
+ "feature_id": feature_id,
745
+ "parent_feature_id": parent_id,
746
+ "added_requires": added_requires,
747
+ "removed_parent_link": removed_parent_link,
748
+ "entity": deepcopy(feature),
749
+ **mutation,
750
+ }
751
+
752
+
489
753
  def set_claim_tier(path: str | Path, claim_id: str, tier: str) -> dict[str, Any]:
490
754
  registry_path, repo_root, registry = load_registry(path)
491
755
  row = _entity_row(registry, "claims", claim_id)