ssot-cli 0.1.9.dev1__tar.gz → 0.1.9.dev4__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_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/PKG-INFO +22 -6
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/README.md +18 -3
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/pyproject.toml +4 -3
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/boundary_cmd.py +21 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/common.py +25 -0
- ssot_cli-0.1.9.dev4/src/ssot_cli/conformance_cmd.py +88 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/main.py +2 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/release_cmd.py +42 -3
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/spec_cmd.py +21 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/test_cmd.py +41 -2
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli.egg-info/PKG-INFO +22 -6
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli.egg-info/SOURCES.txt +1 -0
- ssot_cli-0.1.9.dev4/src/ssot_cli.egg-info/requires.txt +6 -0
- ssot_cli-0.1.9.dev1/src/ssot_cli.egg-info/requires.txt +0 -5
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/setup.cfg +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/__init__.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/adr_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/claim_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/evidence_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/feature_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/graph_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/init_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/issue_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/profile_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/registry_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/risk_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/upgrade_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli/validate_cmd.py +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli.egg-info/dependency_links.txt +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli.egg-info/entry_points.txt +0 -0
- {ssot_cli-0.1.9.dev1 → ssot_cli-0.1.9.dev4}/src/ssot_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssot-cli
|
|
3
|
-
Version: 0.1.9.
|
|
3
|
+
Version: 0.1.9.dev4
|
|
4
4
|
Summary: Primary CLI distribution for ssot-registry.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -24,8 +24,9 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
24
24
|
Classifier: Topic :: Utilities
|
|
25
25
|
Requires-Python: <3.14,>=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
|
-
Requires-Dist: ssot-contracts<0.3.0,>=0.2.
|
|
28
|
-
Requires-Dist: ssot-core<0.3.0,>=0.2.
|
|
27
|
+
Requires-Dist: ssot-contracts<0.3.0,>=0.2.15.dev2
|
|
28
|
+
Requires-Dist: ssot-core<0.3.0,>=0.2.15.dev2
|
|
29
|
+
Requires-Dist: ssot-conformance<0.3.0,>=0.2.15.dev2
|
|
29
30
|
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
30
31
|
|
|
31
32
|
<div align="center">
|
|
@@ -42,7 +43,7 @@ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
|
42
43
|
|
|
43
44
|
`ssot-cli` is the primary command-line distribution for SSOT.
|
|
44
45
|
|
|
45
|
-
It installs `ssot`, `ssot-cli`, and `ssot-registry` as equivalent executables over the same parser and runtime. The command surface is implemented here, while domain logic lives in [ssot-core](https://pypi.org/project/ssot-core/) and shared contract metadata comes from [ssot-contracts](https://pypi.org/project/ssot-contracts/).
|
|
46
|
+
It installs `ssot`, `ssot-cli`, and `ssot-registry` as equivalent executables over the same parser and runtime. The command surface is implemented here, while domain logic lives in [ssot-core](https://pypi.org/project/ssot-core/), reusable conformance checks come from [ssot-conformance](https://pypi.org/project/ssot-conformance/), and shared contract metadata comes from [ssot-contracts](https://pypi.org/project/ssot-contracts/).
|
|
46
47
|
|
|
47
48
|
- GitHub: https://github.com/groupsum/ssot-registry
|
|
48
49
|
|
|
@@ -64,7 +65,7 @@ For local development:
|
|
|
64
65
|
python -m pip install -e pkgs/ssot-cli
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
This package depends on [ssot-core](https://pypi.org/project/ssot-core/) and [ssot-contracts](https://pypi.org/project/ssot-contracts/), so installing it gives you the full CLI runtime stack.
|
|
68
|
+
This package depends on [ssot-core](https://pypi.org/project/ssot-core/), [ssot-conformance](https://pypi.org/project/ssot-conformance/), and [ssot-contracts](https://pypi.org/project/ssot-contracts/), so installing it gives you the full CLI runtime stack.
|
|
68
69
|
|
|
69
70
|
## Executable names
|
|
70
71
|
|
|
@@ -141,6 +142,21 @@ Boundary command help:
|
|
|
141
142
|
|
|
142
143
|
- Non-zero exit code indicates an operation failure or failed checks.
|
|
143
144
|
|
|
145
|
+
## Registry-driven test execution
|
|
146
|
+
|
|
147
|
+
Tests are the executable SSOT entities. Specs and boundaries resolve to tests through registry links, and runnable tests carry an `execution` object that stores the command, cwd, environment, timeout, and success rule.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
ssot test run . --id tst:pytest.conformance.registry-contract
|
|
153
|
+
ssot spec run-tests . --id spc:0525
|
|
154
|
+
ssot boundary run-tests . --id bnd:full-cert
|
|
155
|
+
ssot conformance run . --profiles registry
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`ssot conformance run` remains a compatibility wrapper for packaged conformance families. The primary operator story is now registry-driven execution through `test`, `spec`, and `boundary`.
|
|
159
|
+
|
|
144
160
|
## Command surface
|
|
145
161
|
|
|
146
162
|
### Top-level commands
|
|
@@ -848,7 +864,7 @@ ssot release certify . --release-id rel:0.1.0 --write-report
|
|
|
848
864
|
## Package relationships
|
|
849
865
|
|
|
850
866
|
- Package type: CLI distribution
|
|
851
|
-
- Depends on: [ssot-core](https://pypi.org/project/ssot-core/), [ssot-contracts](https://pypi.org/project/ssot-contracts/)
|
|
867
|
+
- Depends on: [ssot-core](https://pypi.org/project/ssot-core/), [ssot-conformance](https://pypi.org/project/ssot-conformance/), [ssot-contracts](https://pypi.org/project/ssot-contracts/)
|
|
852
868
|
- Related packages: [ssot-registry](https://pypi.org/project/ssot-registry/), [ssot-tui](https://pypi.org/project/ssot-tui/), [ssot-views](https://pypi.org/project/ssot-views/), [ssot-codegen](https://pypi.org/project/ssot-codegen/)
|
|
853
869
|
|
|
854
870
|
If you need the command-line interface, this is the package to install.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
`ssot-cli` is the primary command-line distribution for SSOT.
|
|
14
14
|
|
|
15
|
-
It installs `ssot`, `ssot-cli`, and `ssot-registry` as equivalent executables over the same parser and runtime. The command surface is implemented here, while domain logic lives in [ssot-core](https://pypi.org/project/ssot-core/) and shared contract metadata comes from [ssot-contracts](https://pypi.org/project/ssot-contracts/).
|
|
15
|
+
It installs `ssot`, `ssot-cli`, and `ssot-registry` as equivalent executables over the same parser and runtime. The command surface is implemented here, while domain logic lives in [ssot-core](https://pypi.org/project/ssot-core/), reusable conformance checks come from [ssot-conformance](https://pypi.org/project/ssot-conformance/), and shared contract metadata comes from [ssot-contracts](https://pypi.org/project/ssot-contracts/).
|
|
16
16
|
|
|
17
17
|
- GitHub: https://github.com/groupsum/ssot-registry
|
|
18
18
|
|
|
@@ -34,7 +34,7 @@ For local development:
|
|
|
34
34
|
python -m pip install -e pkgs/ssot-cli
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
This package depends on [ssot-core](https://pypi.org/project/ssot-core/) and [ssot-contracts](https://pypi.org/project/ssot-contracts/), so installing it gives you the full CLI runtime stack.
|
|
37
|
+
This package depends on [ssot-core](https://pypi.org/project/ssot-core/), [ssot-conformance](https://pypi.org/project/ssot-conformance/), and [ssot-contracts](https://pypi.org/project/ssot-contracts/), so installing it gives you the full CLI runtime stack.
|
|
38
38
|
|
|
39
39
|
## Executable names
|
|
40
40
|
|
|
@@ -111,6 +111,21 @@ Boundary command help:
|
|
|
111
111
|
|
|
112
112
|
- Non-zero exit code indicates an operation failure or failed checks.
|
|
113
113
|
|
|
114
|
+
## Registry-driven test execution
|
|
115
|
+
|
|
116
|
+
Tests are the executable SSOT entities. Specs and boundaries resolve to tests through registry links, and runnable tests carry an `execution` object that stores the command, cwd, environment, timeout, and success rule.
|
|
117
|
+
|
|
118
|
+
Examples:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
ssot test run . --id tst:pytest.conformance.registry-contract
|
|
122
|
+
ssot spec run-tests . --id spc:0525
|
|
123
|
+
ssot boundary run-tests . --id bnd:full-cert
|
|
124
|
+
ssot conformance run . --profiles registry
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`ssot conformance run` remains a compatibility wrapper for packaged conformance families. The primary operator story is now registry-driven execution through `test`, `spec`, and `boundary`.
|
|
128
|
+
|
|
114
129
|
## Command surface
|
|
115
130
|
|
|
116
131
|
### Top-level commands
|
|
@@ -818,7 +833,7 @@ ssot release certify . --release-id rel:0.1.0 --write-report
|
|
|
818
833
|
## Package relationships
|
|
819
834
|
|
|
820
835
|
- Package type: CLI distribution
|
|
821
|
-
- Depends on: [ssot-core](https://pypi.org/project/ssot-core/), [ssot-contracts](https://pypi.org/project/ssot-contracts/)
|
|
836
|
+
- Depends on: [ssot-core](https://pypi.org/project/ssot-core/), [ssot-conformance](https://pypi.org/project/ssot-conformance/), [ssot-contracts](https://pypi.org/project/ssot-contracts/)
|
|
822
837
|
- Related packages: [ssot-registry](https://pypi.org/project/ssot-registry/), [ssot-tui](https://pypi.org/project/ssot-tui/), [ssot-views](https://pypi.org/project/ssot-views/), [ssot-codegen](https://pypi.org/project/ssot-codegen/)
|
|
823
838
|
|
|
824
839
|
If you need the command-line interface, this is the package to install.
|
|
@@ -4,15 +4,16 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ssot-cli"
|
|
7
|
-
version = "0.1.9.
|
|
7
|
+
version = "0.1.9.dev4"
|
|
8
8
|
description = "Primary CLI distribution for ssot-registry."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10,<3.14"
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"ssot-contracts>=0.2.
|
|
15
|
-
"ssot-core>=0.2.
|
|
14
|
+
"ssot-contracts>=0.2.15.dev2,<0.3.0",
|
|
15
|
+
"ssot-core>=0.2.15.dev2,<0.3.0",
|
|
16
|
+
"ssot-conformance>=0.2.15.dev2,<0.3.0",
|
|
16
17
|
"tomli>=2.0.1; python_version < '3.11'",
|
|
17
18
|
]
|
|
18
19
|
keywords = ["ssot", "cli", "registry", "governance", "release-management", "validation", "developer-tools"]
|
|
@@ -12,6 +12,7 @@ from ssot_registry.api import (
|
|
|
12
12
|
list_entities,
|
|
13
13
|
remove_boundary_features,
|
|
14
14
|
remove_boundary_profiles,
|
|
15
|
+
run_boundary_tests,
|
|
15
16
|
update_entity,
|
|
16
17
|
)
|
|
17
18
|
from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, compact_dict
|
|
@@ -87,6 +88,17 @@ def register_boundary(subparsers: argparse._SubParsersAction) -> None:
|
|
|
87
88
|
freeze.add_argument("--boundary-id", default=None, help="Boundary id to freeze. Omit to freeze the active boundary.")
|
|
88
89
|
freeze.set_defaults(func=run_freeze)
|
|
89
90
|
|
|
91
|
+
run_tests = boundary_sub.add_parser(
|
|
92
|
+
"run-tests",
|
|
93
|
+
help="Resolve and execute tests for a boundary.",
|
|
94
|
+
description="Resolve the boundary's direct and profile-expanded features, then execute their linked test rows from registry metadata.",
|
|
95
|
+
)
|
|
96
|
+
add_path_argument(run_tests)
|
|
97
|
+
run_tests.add_argument("--id", default=None, help="Boundary id to execute. Omit to use program.active_boundary_id.")
|
|
98
|
+
run_tests.add_argument("--dry-run", action="store_true", help="Resolve linked tests without executing them.")
|
|
99
|
+
run_tests.add_argument("--evidence-output", default=None, help="Optional JSON output path for machine-readable execution evidence.")
|
|
100
|
+
run_tests.set_defaults(func=run_execute_tests)
|
|
101
|
+
|
|
90
102
|
|
|
91
103
|
def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
92
104
|
row = {
|
|
@@ -138,3 +150,12 @@ def run_remove_profile(args: argparse.Namespace) -> dict[str, object]:
|
|
|
138
150
|
def run_freeze(args: argparse.Namespace) -> dict[str, object]:
|
|
139
151
|
return freeze_boundary(path=args.path, boundary_id=args.boundary_id)
|
|
140
152
|
|
|
153
|
+
|
|
154
|
+
def run_execute_tests(args: argparse.Namespace) -> dict[str, object]:
|
|
155
|
+
return run_boundary_tests(
|
|
156
|
+
args.path,
|
|
157
|
+
boundary_id=args.id,
|
|
158
|
+
evidence_output=args.evidence_output,
|
|
159
|
+
dry_run=args.dry_run,
|
|
160
|
+
)
|
|
161
|
+
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
|
|
@@ -43,3 +45,26 @@ def collect_list_fields(args: argparse.Namespace, mapping: dict[str, str]) -> di
|
|
|
43
45
|
if value:
|
|
44
46
|
links[field_name] = value
|
|
45
47
|
return links
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_json_object_argument(
|
|
51
|
+
*,
|
|
52
|
+
inline_value: str | None,
|
|
53
|
+
file_value: str | None,
|
|
54
|
+
label: str,
|
|
55
|
+
) -> dict[str, Any] | None:
|
|
56
|
+
if inline_value is None and file_value is None:
|
|
57
|
+
return None
|
|
58
|
+
if inline_value is not None and file_value is not None:
|
|
59
|
+
raise ValueError(f"{label} accepts only one of inline JSON or JSON file")
|
|
60
|
+
if file_value is not None:
|
|
61
|
+
raw_text = Path(file_value).read_text(encoding="utf-8")
|
|
62
|
+
else:
|
|
63
|
+
raw_text = inline_value or ""
|
|
64
|
+
try:
|
|
65
|
+
payload = json.loads(raw_text)
|
|
66
|
+
except json.JSONDecodeError as exc:
|
|
67
|
+
raise ValueError(f"{label} must be valid JSON: {exc.msg}") from exc
|
|
68
|
+
if not isinstance(payload, dict):
|
|
69
|
+
raise ValueError(f"{label} must decode to a JSON object")
|
|
70
|
+
return payload
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ssot_registry.api import run_resolved_test_rows
|
|
6
|
+
from ssot_cli.common import add_path_argument
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_conformance(subparsers: argparse._SubParsersAction) -> None:
|
|
10
|
+
conformance = subparsers.add_parser(
|
|
11
|
+
"conformance",
|
|
12
|
+
help="Conformance package, scaffold, and execution operations.",
|
|
13
|
+
description="Operate reusable SSOT conformance discovery, scaffold, pytest execution, and evidence-output flows.",
|
|
14
|
+
)
|
|
15
|
+
conformance_sub = conformance.add_subparsers(dest="conformance_command", required=True)
|
|
16
|
+
|
|
17
|
+
profile = conformance_sub.add_parser("profile", help="Profile discovery.", description="List available SSOT conformance profiles and their grouped case families.")
|
|
18
|
+
profile_sub = profile.add_subparsers(dest="conformance_profile_command", required=True)
|
|
19
|
+
profile_list = profile_sub.add_parser("list", help="List conformance profiles.", description="Show built-in SSOT conformance profiles and family counts.")
|
|
20
|
+
add_path_argument(profile_list)
|
|
21
|
+
profile_list.set_defaults(func=run_profile_list)
|
|
22
|
+
|
|
23
|
+
discover = conformance_sub.add_parser("discover", help="Discover conformance cases.", description="Resolve selected SSOT conformance profiles into concrete case files and linked SSOT rows.")
|
|
24
|
+
add_path_argument(discover)
|
|
25
|
+
discover.add_argument("--profiles", nargs="*", default=None, help="Conformance profiles or family names to resolve.")
|
|
26
|
+
discover.set_defaults(func=run_discover)
|
|
27
|
+
|
|
28
|
+
scaffold = conformance_sub.add_parser("scaffold", help="Plan or apply conformance rows.", description="Plan or create missing conformance feature, claim, evidence, and test rows in a target SSOT repo.")
|
|
29
|
+
add_path_argument(scaffold)
|
|
30
|
+
scaffold.add_argument("--profiles", nargs="*", default=None, help="Conformance profiles or family names to scaffold.")
|
|
31
|
+
scaffold.add_argument("--apply", action="store_true", help="Create missing rows instead of only reporting the dry-run plan.")
|
|
32
|
+
scaffold.add_argument("--include-claims", action="store_true", help="Allow scaffold apply to create missing claim rows.")
|
|
33
|
+
scaffold.add_argument("--include-evidence", action="store_true", help="Allow scaffold apply to create missing evidence rows and placeholder artifacts.")
|
|
34
|
+
scaffold.set_defaults(func=run_scaffold)
|
|
35
|
+
|
|
36
|
+
run = conformance_sub.add_parser(
|
|
37
|
+
"run",
|
|
38
|
+
help="Run conformance cases from registry metadata.",
|
|
39
|
+
description="Resolve selected SSOT conformance case families into governed test rows and execute the stored registry commands.",
|
|
40
|
+
)
|
|
41
|
+
add_path_argument(run)
|
|
42
|
+
run.add_argument("--profiles", nargs="*", default=None, help="Conformance profiles or family names to execute.")
|
|
43
|
+
run.add_argument("--dry-run", action="store_true", help="Resolve governed conformance tests without executing them.")
|
|
44
|
+
run.add_argument("--evidence-output", default=None, help="Optional JSON output path for machine-readable evidence.")
|
|
45
|
+
run.set_defaults(func=run_run)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run_profile_list(args: argparse.Namespace) -> dict[str, object]:
|
|
49
|
+
from ssot_conformance import list_profiles
|
|
50
|
+
|
|
51
|
+
return {"passed": True, "profiles": list_profiles()}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_discover(args: argparse.Namespace) -> dict[str, object]:
|
|
55
|
+
from ssot_conformance import discover_cases
|
|
56
|
+
|
|
57
|
+
return discover_cases(args.profiles)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_scaffold(args: argparse.Namespace) -> dict[str, object]:
|
|
61
|
+
from ssot_conformance import apply_scaffold, plan_scaffold
|
|
62
|
+
|
|
63
|
+
if args.apply:
|
|
64
|
+
return apply_scaffold(
|
|
65
|
+
args.path,
|
|
66
|
+
profiles=args.profiles,
|
|
67
|
+
include_claims=args.include_claims,
|
|
68
|
+
include_evidence=args.include_evidence,
|
|
69
|
+
)
|
|
70
|
+
return plan_scaffold(
|
|
71
|
+
args.path,
|
|
72
|
+
profiles=args.profiles,
|
|
73
|
+
include_claims=args.include_claims,
|
|
74
|
+
include_evidence=args.include_evidence,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def run_run(args: argparse.Namespace) -> dict[str, object]:
|
|
79
|
+
from ssot_conformance import build_catalog_slice
|
|
80
|
+
|
|
81
|
+
catalog = build_catalog_slice(args.profiles)
|
|
82
|
+
return run_resolved_test_rows(
|
|
83
|
+
args.path,
|
|
84
|
+
target={"kind": "conformance", "profiles": catalog["profiles"], "families": catalog["families"]},
|
|
85
|
+
tests=list(catalog["tests"]),
|
|
86
|
+
evidence_output=args.evidence_output,
|
|
87
|
+
dry_run=args.dry_run,
|
|
88
|
+
)
|
|
@@ -12,6 +12,7 @@ from ssot_registry.util.formatting import render_payload
|
|
|
12
12
|
from .adr_cmd import register_adr
|
|
13
13
|
from .boundary_cmd import register_boundary
|
|
14
14
|
from .claim_cmd import register_claim
|
|
15
|
+
from .conformance_cmd import register_conformance
|
|
15
16
|
from .evidence_cmd import register_evidence
|
|
16
17
|
from .feature_cmd import register_feature
|
|
17
18
|
from .graph_cmd import register_graph
|
|
@@ -68,6 +69,7 @@ def build_parser(*, prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
68
69
|
register_test(subparsers)
|
|
69
70
|
register_issue(subparsers)
|
|
70
71
|
register_claim(subparsers)
|
|
72
|
+
register_conformance(subparsers)
|
|
71
73
|
register_evidence(subparsers)
|
|
72
74
|
register_risk(subparsers)
|
|
73
75
|
register_boundary(subparsers)
|
|
@@ -4,6 +4,7 @@ import argparse
|
|
|
4
4
|
|
|
5
5
|
from ssot_registry.api import (
|
|
6
6
|
add_release_claims,
|
|
7
|
+
add_release_boundaries,
|
|
7
8
|
add_release_evidence,
|
|
8
9
|
certify_release,
|
|
9
10
|
create_entity,
|
|
@@ -13,6 +14,7 @@ from ssot_registry.api import (
|
|
|
13
14
|
promote_release,
|
|
14
15
|
publish_release,
|
|
15
16
|
remove_release_claims,
|
|
17
|
+
remove_release_boundaries,
|
|
16
18
|
remove_release_evidence,
|
|
17
19
|
revoke_release,
|
|
18
20
|
update_entity,
|
|
@@ -33,7 +35,8 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
|
|
|
33
35
|
create.add_argument("--id", required=True, help="Normalized release id to create.")
|
|
34
36
|
create.add_argument("--version", required=True, help="Semantic or operator-defined version string for the release.")
|
|
35
37
|
create.add_argument("--status", choices=["draft", "candidate", "certified", "promoted", "published", "revoked"], default="draft", help="Current publication stage of the release.")
|
|
36
|
-
create.add_argument("--boundary-id", required=True, help="
|
|
38
|
+
create.add_argument("--boundary-id", required=True, help="Primary frozen boundary id that defines the release scope.")
|
|
39
|
+
create.add_argument("--boundary-ids", nargs="*", default=None, help="Additional frozen boundary ids to include in the release scope. The primary --boundary-id is always included.")
|
|
37
40
|
create.add_argument("--claim-ids", nargs="*", default=[], help="Claim ids bundled into the release.")
|
|
38
41
|
create.add_argument("--evidence-ids", nargs="*", default=[], help="Evidence ids bundled into the release.")
|
|
39
42
|
create.set_defaults(func=run_create)
|
|
@@ -53,7 +56,8 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
|
|
|
53
56
|
update.add_argument("--id", required=True, help="Release id to update.")
|
|
54
57
|
update.add_argument("--version", default=None, help="Replacement release version string.")
|
|
55
58
|
update.add_argument("--status", choices=["draft", "candidate", "certified", "promoted", "published", "revoked"], default=None, help="Updated publication stage.")
|
|
56
|
-
update.add_argument("--boundary-id", default=None, help="Replacement boundary id that defines the release scope.")
|
|
59
|
+
update.add_argument("--boundary-id", default=None, help="Replacement primary boundary id that defines the release scope.")
|
|
60
|
+
update.add_argument("--boundary-ids", nargs="*", default=None, help="Replacement full boundary-id list for the release scope.")
|
|
57
61
|
update.set_defaults(func=run_update)
|
|
58
62
|
|
|
59
63
|
delete = release_sub.add_parser("delete", help="Delete a release.", description="Remove a release record from the registry.")
|
|
@@ -85,6 +89,18 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
|
|
|
85
89
|
remove_evidence.add_argument("--evidence-ids", nargs="+", required=True, help="Evidence ids to remove from the release.")
|
|
86
90
|
remove_evidence.set_defaults(func=run_remove_evidence)
|
|
87
91
|
|
|
92
|
+
add_boundary = release_sub.add_parser("add-boundary", help="Attach boundaries to a release.", description="Add one or more boundary ids to the release scope.")
|
|
93
|
+
add_path_argument(add_boundary)
|
|
94
|
+
add_boundary.add_argument("--id", required=True, help="Release id that should receive the boundaries.")
|
|
95
|
+
add_boundary.add_argument("--boundary-ids", nargs="+", required=True, help="Boundary ids to attach to the release.")
|
|
96
|
+
add_boundary.set_defaults(func=run_add_boundary)
|
|
97
|
+
|
|
98
|
+
remove_boundary = release_sub.add_parser("remove-boundary", help="Remove boundaries from a release.", description="Remove one or more boundary ids from the release scope.")
|
|
99
|
+
add_path_argument(remove_boundary)
|
|
100
|
+
remove_boundary.add_argument("--id", required=True, help="Release id whose boundaries should be removed.")
|
|
101
|
+
remove_boundary.add_argument("--boundary-ids", nargs="+", required=True, help="Boundary ids to remove from the release.")
|
|
102
|
+
remove_boundary.set_defaults(func=run_remove_boundary)
|
|
103
|
+
|
|
88
104
|
certify = release_sub.add_parser("certify", help="Run release certification.", description="Evaluate release guards and certify the release when all required checks pass.")
|
|
89
105
|
add_path_argument(certify)
|
|
90
106
|
certify.add_argument("--release-id", default=None, help="Release id to certify. Omit to use the active release.")
|
|
@@ -109,11 +125,14 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
|
|
|
109
125
|
|
|
110
126
|
|
|
111
127
|
def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
128
|
+
boundary_ids = [args.boundary_id, *(args.boundary_ids or [])]
|
|
129
|
+
boundary_ids = list(dict.fromkeys(boundary_ids))
|
|
112
130
|
row = {
|
|
113
131
|
"id": args.id,
|
|
114
132
|
"version": args.version,
|
|
115
133
|
"status": args.status,
|
|
116
134
|
"boundary_id": args.boundary_id,
|
|
135
|
+
"boundary_ids": boundary_ids,
|
|
117
136
|
"claim_ids": args.claim_ids,
|
|
118
137
|
"evidence_ids": args.evidence_ids,
|
|
119
138
|
}
|
|
@@ -129,7 +148,19 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
|
|
|
129
148
|
|
|
130
149
|
|
|
131
150
|
def run_update(args: argparse.Namespace) -> dict[str, object]:
|
|
132
|
-
|
|
151
|
+
boundary_ids = args.boundary_ids
|
|
152
|
+
if boundary_ids is not None:
|
|
153
|
+
boundary_ids = list(dict.fromkeys(([args.boundary_id] if args.boundary_id else []) + boundary_ids))
|
|
154
|
+
if not boundary_ids:
|
|
155
|
+
raise ValueError("At least one boundary id is required when updating boundary membership")
|
|
156
|
+
elif args.boundary_id is not None:
|
|
157
|
+
boundary_ids = [args.boundary_id]
|
|
158
|
+
changes = compact_dict({
|
|
159
|
+
"version": args.version,
|
|
160
|
+
"status": args.status,
|
|
161
|
+
"boundary_id": args.boundary_id or (boundary_ids[0] if boundary_ids else None),
|
|
162
|
+
"boundary_ids": boundary_ids,
|
|
163
|
+
})
|
|
133
164
|
if not changes:
|
|
134
165
|
raise ValueError("At least one update field is required")
|
|
135
166
|
return update_entity(args.path, "releases", args.id, changes)
|
|
@@ -155,6 +186,14 @@ def run_remove_evidence(args: argparse.Namespace) -> dict[str, object]:
|
|
|
155
186
|
return remove_release_evidence(args.path, args.id, args.evidence_ids)
|
|
156
187
|
|
|
157
188
|
|
|
189
|
+
def run_add_boundary(args: argparse.Namespace) -> dict[str, object]:
|
|
190
|
+
return add_release_boundaries(args.path, args.id, args.boundary_ids)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def run_remove_boundary(args: argparse.Namespace) -> dict[str, object]:
|
|
194
|
+
return remove_release_boundaries(args.path, args.id, args.boundary_ids)
|
|
195
|
+
|
|
196
|
+
|
|
158
197
|
def run_certify(args: argparse.Namespace) -> dict[str, object]:
|
|
159
198
|
return certify_release(path=args.path, release_id=args.release_id, write_report=args.write_report)
|
|
160
199
|
|
|
@@ -15,6 +15,7 @@ from ssot_registry.api import (
|
|
|
15
15
|
sync_documents,
|
|
16
16
|
supersede_documents,
|
|
17
17
|
update_document,
|
|
18
|
+
run_spec_tests,
|
|
18
19
|
)
|
|
19
20
|
from ssot_cli.common import add_ids_argument, add_path_argument, compact_dict
|
|
20
21
|
|
|
@@ -116,6 +117,17 @@ def register_spec(subparsers: argparse._SubParsersAction) -> None:
|
|
|
116
117
|
add_path_argument(reserve_list)
|
|
117
118
|
reserve_list.set_defaults(func=run_reserve_list)
|
|
118
119
|
|
|
120
|
+
run_tests = spec_sub.add_parser(
|
|
121
|
+
"run-tests",
|
|
122
|
+
help="Resolve and execute tests for a SPEC.",
|
|
123
|
+
description="Resolve features that declare the selected SPEC in feature.spec_ids, then execute their linked test rows from registry metadata.",
|
|
124
|
+
)
|
|
125
|
+
add_path_argument(run_tests)
|
|
126
|
+
run_tests.add_argument("--id", required=True, help="SPEC id whose linked tests should be resolved and run.")
|
|
127
|
+
run_tests.add_argument("--dry-run", action="store_true", help="Resolve linked tests without executing them.")
|
|
128
|
+
run_tests.add_argument("--evidence-output", default=None, help="Optional JSON output path for machine-readable execution evidence.")
|
|
129
|
+
run_tests.set_defaults(func=run_spec_tests_command)
|
|
130
|
+
|
|
119
131
|
|
|
120
132
|
def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
121
133
|
return create_document(
|
|
@@ -191,3 +203,12 @@ def run_reserve_create(args: argparse.Namespace) -> dict[str, object]:
|
|
|
191
203
|
def run_reserve_list(args: argparse.Namespace) -> dict[str, object]:
|
|
192
204
|
return list_document_reservations(args.path, "spec")
|
|
193
205
|
|
|
206
|
+
|
|
207
|
+
def run_spec_tests_command(args: argparse.Namespace) -> dict[str, object]:
|
|
208
|
+
return run_spec_tests(
|
|
209
|
+
args.path,
|
|
210
|
+
spec_id=args.id,
|
|
211
|
+
evidence_output=args.evidence_output,
|
|
212
|
+
dry_run=args.dry_run,
|
|
213
|
+
)
|
|
214
|
+
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
|
-
from ssot_registry.api import create_entity, delete_entity, get_entity, link_entities, list_entities, unlink_entities, update_entity
|
|
6
|
-
from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict
|
|
5
|
+
from ssot_registry.api import create_entity, delete_entity, get_entity, link_entities, list_entities, run_tests, unlink_entities, update_entity
|
|
6
|
+
from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_json_object_argument
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
_LINK_MAPPING = {
|
|
@@ -31,6 +31,8 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
|
|
|
31
31
|
create.add_argument("--feature-ids", nargs="*", default=[], help="Feature ids this test verifies.")
|
|
32
32
|
create.add_argument("--claim-ids", nargs="*", default=[], help="Claim ids this test supports.")
|
|
33
33
|
create.add_argument("--evidence-ids", nargs="*", default=[], help="Evidence ids produced by or associated with the test.")
|
|
34
|
+
create.add_argument("--execution-json", default=None, help="Inline JSON object describing registry-owned execution metadata for this test.")
|
|
35
|
+
create.add_argument("--execution-file", default=None, help="Path to a JSON file containing registry-owned execution metadata for this test.")
|
|
34
36
|
create.set_defaults(func=run_create)
|
|
35
37
|
|
|
36
38
|
get = test_sub.add_parser("get", help="Show one test.", description="Fetch a single test record by id.")
|
|
@@ -50,6 +52,8 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
|
|
|
50
52
|
update.add_argument("--status", choices=["planned", "passing", "failing", "blocked", "skipped"], default=None, help="Updated execution or readiness state.")
|
|
51
53
|
update.add_argument("--kind", default=None, help="Updated test category.")
|
|
52
54
|
update.add_argument("--test-path", dest="test_path", default=None, help="Updated repository-relative path to the test or procedure.")
|
|
55
|
+
update.add_argument("--execution-json", default=None, help="Replacement inline JSON object for test execution metadata.")
|
|
56
|
+
update.add_argument("--execution-file", default=None, help="Replacement JSON file for test execution metadata.")
|
|
53
57
|
update.set_defaults(func=run_update)
|
|
54
58
|
|
|
55
59
|
delete = test_sub.add_parser("delete", help="Delete a test.", description="Remove a test record from the registry.")
|
|
@@ -73,6 +77,19 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
|
|
|
73
77
|
unlink.add_argument("--evidence-ids", nargs="*", help="Evidence ids to detach.")
|
|
74
78
|
unlink.set_defaults(func=run_unlink)
|
|
75
79
|
|
|
80
|
+
run = test_sub.add_parser(
|
|
81
|
+
"run",
|
|
82
|
+
help="Execute runnable tests from registry metadata.",
|
|
83
|
+
description="Resolve one or more test rows by id, require execution metadata, and run the stored commands from registry truth.",
|
|
84
|
+
)
|
|
85
|
+
add_path_argument(run)
|
|
86
|
+
selector = run.add_mutually_exclusive_group(required=True)
|
|
87
|
+
selector.add_argument("--id", default=None, help="Single test id to execute.")
|
|
88
|
+
selector.add_argument("--ids", nargs="+", default=None, help="One or more test ids to execute.")
|
|
89
|
+
run.add_argument("--dry-run", action="store_true", help="Resolve tests without executing their commands.")
|
|
90
|
+
run.add_argument("--evidence-output", default=None, help="Optional JSON output path for machine-readable execution evidence.")
|
|
91
|
+
run.set_defaults(func=run_execute)
|
|
92
|
+
|
|
76
93
|
|
|
77
94
|
def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
|
|
78
95
|
links = collect_list_fields(args, _LINK_MAPPING)
|
|
@@ -82,6 +99,11 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
|
|
|
82
99
|
|
|
83
100
|
|
|
84
101
|
def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
102
|
+
execution = load_json_object_argument(
|
|
103
|
+
inline_value=args.execution_json,
|
|
104
|
+
file_value=args.execution_file,
|
|
105
|
+
label="test execution metadata",
|
|
106
|
+
)
|
|
85
107
|
row = {
|
|
86
108
|
"id": args.id,
|
|
87
109
|
"title": args.title,
|
|
@@ -91,6 +113,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
|
91
113
|
"feature_ids": args.feature_ids,
|
|
92
114
|
"claim_ids": args.claim_ids,
|
|
93
115
|
"evidence_ids": args.evidence_ids,
|
|
116
|
+
"execution": execution,
|
|
94
117
|
}
|
|
95
118
|
return create_entity(args.path, "tests", row)
|
|
96
119
|
|
|
@@ -104,12 +127,18 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
|
|
|
104
127
|
|
|
105
128
|
|
|
106
129
|
def run_update(args: argparse.Namespace) -> dict[str, object]:
|
|
130
|
+
execution = load_json_object_argument(
|
|
131
|
+
inline_value=args.execution_json,
|
|
132
|
+
file_value=args.execution_file,
|
|
133
|
+
label="test execution metadata",
|
|
134
|
+
)
|
|
107
135
|
changes = compact_dict(
|
|
108
136
|
{
|
|
109
137
|
"title": args.title,
|
|
110
138
|
"status": args.status,
|
|
111
139
|
"kind": args.kind,
|
|
112
140
|
"path": args.test_path,
|
|
141
|
+
"execution": execution,
|
|
113
142
|
}
|
|
114
143
|
)
|
|
115
144
|
if not changes:
|
|
@@ -128,3 +157,13 @@ def run_link(args: argparse.Namespace) -> dict[str, object]:
|
|
|
128
157
|
def run_unlink(args: argparse.Namespace) -> dict[str, object]:
|
|
129
158
|
return unlink_entities(args.path, "tests", args.id, _build_links(args))
|
|
130
159
|
|
|
160
|
+
|
|
161
|
+
def run_execute(args: argparse.Namespace) -> dict[str, object]:
|
|
162
|
+
test_ids = [args.id] if args.id is not None else list(args.ids or [])
|
|
163
|
+
return run_tests(
|
|
164
|
+
args.path,
|
|
165
|
+
test_ids=test_ids,
|
|
166
|
+
evidence_output=args.evidence_output,
|
|
167
|
+
dry_run=args.dry_run,
|
|
168
|
+
)
|
|
169
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssot-cli
|
|
3
|
-
Version: 0.1.9.
|
|
3
|
+
Version: 0.1.9.dev4
|
|
4
4
|
Summary: Primary CLI distribution for ssot-registry.
|
|
5
5
|
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -24,8 +24,9 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
24
24
|
Classifier: Topic :: Utilities
|
|
25
25
|
Requires-Python: <3.14,>=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
|
-
Requires-Dist: ssot-contracts<0.3.0,>=0.2.
|
|
28
|
-
Requires-Dist: ssot-core<0.3.0,>=0.2.
|
|
27
|
+
Requires-Dist: ssot-contracts<0.3.0,>=0.2.15.dev2
|
|
28
|
+
Requires-Dist: ssot-core<0.3.0,>=0.2.15.dev2
|
|
29
|
+
Requires-Dist: ssot-conformance<0.3.0,>=0.2.15.dev2
|
|
29
30
|
Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
30
31
|
|
|
31
32
|
<div align="center">
|
|
@@ -42,7 +43,7 @@ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
|
|
|
42
43
|
|
|
43
44
|
`ssot-cli` is the primary command-line distribution for SSOT.
|
|
44
45
|
|
|
45
|
-
It installs `ssot`, `ssot-cli`, and `ssot-registry` as equivalent executables over the same parser and runtime. The command surface is implemented here, while domain logic lives in [ssot-core](https://pypi.org/project/ssot-core/) and shared contract metadata comes from [ssot-contracts](https://pypi.org/project/ssot-contracts/).
|
|
46
|
+
It installs `ssot`, `ssot-cli`, and `ssot-registry` as equivalent executables over the same parser and runtime. The command surface is implemented here, while domain logic lives in [ssot-core](https://pypi.org/project/ssot-core/), reusable conformance checks come from [ssot-conformance](https://pypi.org/project/ssot-conformance/), and shared contract metadata comes from [ssot-contracts](https://pypi.org/project/ssot-contracts/).
|
|
46
47
|
|
|
47
48
|
- GitHub: https://github.com/groupsum/ssot-registry
|
|
48
49
|
|
|
@@ -64,7 +65,7 @@ For local development:
|
|
|
64
65
|
python -m pip install -e pkgs/ssot-cli
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
This package depends on [ssot-core](https://pypi.org/project/ssot-core/) and [ssot-contracts](https://pypi.org/project/ssot-contracts/), so installing it gives you the full CLI runtime stack.
|
|
68
|
+
This package depends on [ssot-core](https://pypi.org/project/ssot-core/), [ssot-conformance](https://pypi.org/project/ssot-conformance/), and [ssot-contracts](https://pypi.org/project/ssot-contracts/), so installing it gives you the full CLI runtime stack.
|
|
68
69
|
|
|
69
70
|
## Executable names
|
|
70
71
|
|
|
@@ -141,6 +142,21 @@ Boundary command help:
|
|
|
141
142
|
|
|
142
143
|
- Non-zero exit code indicates an operation failure or failed checks.
|
|
143
144
|
|
|
145
|
+
## Registry-driven test execution
|
|
146
|
+
|
|
147
|
+
Tests are the executable SSOT entities. Specs and boundaries resolve to tests through registry links, and runnable tests carry an `execution` object that stores the command, cwd, environment, timeout, and success rule.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
ssot test run . --id tst:pytest.conformance.registry-contract
|
|
153
|
+
ssot spec run-tests . --id spc:0525
|
|
154
|
+
ssot boundary run-tests . --id bnd:full-cert
|
|
155
|
+
ssot conformance run . --profiles registry
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`ssot conformance run` remains a compatibility wrapper for packaged conformance families. The primary operator story is now registry-driven execution through `test`, `spec`, and `boundary`.
|
|
159
|
+
|
|
144
160
|
## Command surface
|
|
145
161
|
|
|
146
162
|
### Top-level commands
|
|
@@ -848,7 +864,7 @@ ssot release certify . --release-id rel:0.1.0 --write-report
|
|
|
848
864
|
## Package relationships
|
|
849
865
|
|
|
850
866
|
- Package type: CLI distribution
|
|
851
|
-
- Depends on: [ssot-core](https://pypi.org/project/ssot-core/), [ssot-contracts](https://pypi.org/project/ssot-contracts/)
|
|
867
|
+
- Depends on: [ssot-core](https://pypi.org/project/ssot-core/), [ssot-conformance](https://pypi.org/project/ssot-conformance/), [ssot-contracts](https://pypi.org/project/ssot-contracts/)
|
|
852
868
|
- Related packages: [ssot-registry](https://pypi.org/project/ssot-registry/), [ssot-tui](https://pypi.org/project/ssot-tui/), [ssot-views](https://pypi.org/project/ssot-views/), [ssot-codegen](https://pypi.org/project/ssot-codegen/)
|
|
853
869
|
|
|
854
870
|
If you need the command-line interface, this is the package to install.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|