ssot-cli 0.1.8__tar.gz → 0.1.9.dev3__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.8 → ssot_cli-0.1.9.dev3}/PKG-INFO +24 -6
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/README.md +20 -3
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/pyproject.toml +4 -3
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/boundary_cmd.py +21 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/common.py +25 -0
- ssot_cli-0.1.9.dev3/src/ssot_cli/conformance_cmd.py +88 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/feature_cmd.py +25 -7
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/main.py +2 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/release_cmd.py +42 -3
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/spec_cmd.py +21 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/test_cmd.py +41 -2
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli.egg-info/PKG-INFO +24 -6
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli.egg-info/SOURCES.txt +1 -0
- ssot_cli-0.1.9.dev3/src/ssot_cli.egg-info/requires.txt +6 -0
- ssot_cli-0.1.8/src/ssot_cli.egg-info/requires.txt +0 -5
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/setup.cfg +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/__init__.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/adr_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/claim_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/evidence_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/graph_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/init_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/issue_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/profile_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/registry_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/risk_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/upgrade_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli/validate_cmd.py +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli.egg-info/dependency_links.txt +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/src/ssot_cli.egg-info/entry_points.txt +0 -0
- {ssot_cli-0.1.8 → ssot_cli-0.1.9.dev3}/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.
|
|
3
|
+
Version: 0.1.9.dev3
|
|
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.dev1
|
|
28
|
+
Requires-Dist: ssot-core<0.3.0,>=0.2.15.dev1
|
|
29
|
+
Requires-Dist: ssot-conformance<0.3.0,>=0.2.15.dev1
|
|
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
|
|
@@ -313,6 +329,7 @@ ssot-registry feature create [path]
|
|
|
313
329
|
--replacement-feature-id [REPLACEMENT_FEATURE_ID ...]
|
|
314
330
|
--note NOTE
|
|
315
331
|
--horizon {backlog,current,explicit,future,next,out_of_bounds}
|
|
332
|
+
--out-of-bounds-disposition {prohibited,tolerated}
|
|
316
333
|
--claim-tier {T0,T1,T2,T3,T4}
|
|
317
334
|
--target-lifecycle-stage {active,deprecated,obsolete,removed}
|
|
318
335
|
--slot SLOT
|
|
@@ -349,6 +366,7 @@ ssot-registry feature unlink [path]
|
|
|
349
366
|
ssot-registry feature plan [path]
|
|
350
367
|
--ids IDS [IDS ...] (required)
|
|
351
368
|
--horizon {backlog,current,explicit,future,next,out_of_bounds} (required)
|
|
369
|
+
--out-of-bounds-disposition {prohibited,tolerated}
|
|
352
370
|
--claim-tier {T0,T1,T2,T3,T4}
|
|
353
371
|
--target-lifecycle-stage {active,deprecated,obsolete,removed}
|
|
354
372
|
--slot SLOT
|
|
@@ -846,7 +864,7 @@ ssot release certify . --release-id rel:0.1.0 --write-report
|
|
|
846
864
|
## Package relationships
|
|
847
865
|
|
|
848
866
|
- Package type: CLI distribution
|
|
849
|
-
- 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/)
|
|
850
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/)
|
|
851
869
|
|
|
852
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
|
|
@@ -283,6 +298,7 @@ ssot-registry feature create [path]
|
|
|
283
298
|
--replacement-feature-id [REPLACEMENT_FEATURE_ID ...]
|
|
284
299
|
--note NOTE
|
|
285
300
|
--horizon {backlog,current,explicit,future,next,out_of_bounds}
|
|
301
|
+
--out-of-bounds-disposition {prohibited,tolerated}
|
|
286
302
|
--claim-tier {T0,T1,T2,T3,T4}
|
|
287
303
|
--target-lifecycle-stage {active,deprecated,obsolete,removed}
|
|
288
304
|
--slot SLOT
|
|
@@ -319,6 +335,7 @@ ssot-registry feature unlink [path]
|
|
|
319
335
|
ssot-registry feature plan [path]
|
|
320
336
|
--ids IDS [IDS ...] (required)
|
|
321
337
|
--horizon {backlog,current,explicit,future,next,out_of_bounds} (required)
|
|
338
|
+
--out-of-bounds-disposition {prohibited,tolerated}
|
|
322
339
|
--claim-tier {T0,T1,T2,T3,T4}
|
|
323
340
|
--target-lifecycle-stage {active,deprecated,obsolete,removed}
|
|
324
341
|
--slot SLOT
|
|
@@ -816,7 +833,7 @@ ssot release certify . --release-id rel:0.1.0 --write-report
|
|
|
816
833
|
## Package relationships
|
|
817
834
|
|
|
818
835
|
- Package type: CLI distribution
|
|
819
|
-
- 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/)
|
|
820
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/)
|
|
821
838
|
|
|
822
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.
|
|
7
|
+
version = "0.1.9.dev3"
|
|
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.dev1,<0.3.0",
|
|
15
|
+
"ssot-core>=0.2.15.dev1,<0.3.0",
|
|
16
|
+
"ssot-conformance>=0.2.15.dev1,<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
|
+
)
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
|
|
5
|
-
from ssot_contracts.generated.python.enums import
|
|
5
|
+
from ssot_contracts.generated.python.enums import (
|
|
6
|
+
CLAIM_TIERS,
|
|
7
|
+
FEATURE_IMPLEMENTATION_STATUSES,
|
|
8
|
+
FEATURE_LIFECYCLE_STAGES,
|
|
9
|
+
OUT_OF_BOUNDS_DISPOSITIONS,
|
|
10
|
+
PLANNING_HORIZONS,
|
|
11
|
+
)
|
|
6
12
|
from ssot_registry.api import (
|
|
7
13
|
create_entity,
|
|
8
14
|
delete_entity,
|
|
@@ -47,6 +53,7 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
|
|
|
47
53
|
create.add_argument("--replacement-feature-id", nargs="*", default=[], help="Replacement feature ids if this feature is being deprecated or removed.")
|
|
48
54
|
create.add_argument("--note", default=None, help="Lifecycle note explaining replacement or status context.")
|
|
49
55
|
create.add_argument("--horizon", choices=sorted(PLANNING_HORIZONS), default="backlog", help="Planning horizon that determines when the feature is expected to land.")
|
|
56
|
+
create.add_argument("--out-of-bounds-disposition", choices=sorted(OUT_OF_BOUNDS_DISPOSITIONS), default=None, help="Required when a non-absent feature is out_of_bounds; tolerated means acceptable non-target support, prohibited means removal is required.")
|
|
50
57
|
create.add_argument("--claim-tier", choices=sorted(CLAIM_TIERS), default=None, help="Target assurance tier expected for claims tied to this feature.")
|
|
51
58
|
create.add_argument("--target-lifecycle-stage", choices=sorted(FEATURE_LIFECYCLE_STAGES), default="active", help="Planned future lifecycle target for this feature.")
|
|
52
59
|
create.add_argument("--slot", default=None, help="Explicit planning slot label when the horizon is schedule-driven.")
|
|
@@ -107,6 +114,12 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
|
|
|
107
114
|
help="Planning horizon to assign.",
|
|
108
115
|
)
|
|
109
116
|
plan.add_argument("--claim-tier", choices=sorted(CLAIM_TIERS), default=None, help="Target claim tier for the selected features.")
|
|
117
|
+
plan.add_argument(
|
|
118
|
+
"--out-of-bounds-disposition",
|
|
119
|
+
choices=sorted(OUT_OF_BOUNDS_DISPOSITIONS),
|
|
120
|
+
default=None,
|
|
121
|
+
help="Disposition for non-absent out_of_bounds features.",
|
|
122
|
+
)
|
|
110
123
|
plan.add_argument(
|
|
111
124
|
"--target-lifecycle-stage",
|
|
112
125
|
choices=sorted(FEATURE_LIFECYCLE_STAGES),
|
|
@@ -136,6 +149,15 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
|
|
|
136
149
|
|
|
137
150
|
|
|
138
151
|
def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
152
|
+
plan = {
|
|
153
|
+
"horizon": args.horizon,
|
|
154
|
+
"slot": args.slot,
|
|
155
|
+
"target_claim_tier": args.claim_tier,
|
|
156
|
+
"target_lifecycle_stage": args.target_lifecycle_stage,
|
|
157
|
+
}
|
|
158
|
+
if args.out_of_bounds_disposition is not None:
|
|
159
|
+
plan["out_of_bounds_disposition"] = args.out_of_bounds_disposition
|
|
160
|
+
|
|
139
161
|
row = {
|
|
140
162
|
"id": args.id,
|
|
141
163
|
"title": args.title,
|
|
@@ -146,12 +168,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
|
|
|
146
168
|
"replacement_feature_ids": args.replacement_feature_id,
|
|
147
169
|
"note": args.note,
|
|
148
170
|
},
|
|
149
|
-
"plan":
|
|
150
|
-
"horizon": args.horizon,
|
|
151
|
-
"slot": args.slot,
|
|
152
|
-
"target_claim_tier": args.claim_tier,
|
|
153
|
-
"target_lifecycle_stage": args.target_lifecycle_stage,
|
|
154
|
-
},
|
|
171
|
+
"plan": plan,
|
|
155
172
|
"spec_ids": args.spec_ids,
|
|
156
173
|
"claim_ids": args.claim_ids,
|
|
157
174
|
"test_ids": args.test_ids,
|
|
@@ -201,6 +218,7 @@ def run_plan(args: argparse.Namespace) -> dict[str, object]:
|
|
|
201
218
|
claim_tier=args.claim_tier,
|
|
202
219
|
slot=args.slot,
|
|
203
220
|
target_lifecycle_stage=args.target_lifecycle_stage,
|
|
221
|
+
out_of_bounds_disposition=args.out_of_bounds_disposition,
|
|
204
222
|
)
|
|
205
223
|
|
|
206
224
|
|
|
@@ -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.
|
|
3
|
+
Version: 0.1.9.dev3
|
|
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.dev1
|
|
28
|
+
Requires-Dist: ssot-core<0.3.0,>=0.2.15.dev1
|
|
29
|
+
Requires-Dist: ssot-conformance<0.3.0,>=0.2.15.dev1
|
|
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
|
|
@@ -313,6 +329,7 @@ ssot-registry feature create [path]
|
|
|
313
329
|
--replacement-feature-id [REPLACEMENT_FEATURE_ID ...]
|
|
314
330
|
--note NOTE
|
|
315
331
|
--horizon {backlog,current,explicit,future,next,out_of_bounds}
|
|
332
|
+
--out-of-bounds-disposition {prohibited,tolerated}
|
|
316
333
|
--claim-tier {T0,T1,T2,T3,T4}
|
|
317
334
|
--target-lifecycle-stage {active,deprecated,obsolete,removed}
|
|
318
335
|
--slot SLOT
|
|
@@ -349,6 +366,7 @@ ssot-registry feature unlink [path]
|
|
|
349
366
|
ssot-registry feature plan [path]
|
|
350
367
|
--ids IDS [IDS ...] (required)
|
|
351
368
|
--horizon {backlog,current,explicit,future,next,out_of_bounds} (required)
|
|
369
|
+
--out-of-bounds-disposition {prohibited,tolerated}
|
|
352
370
|
--claim-tier {T0,T1,T2,T3,T4}
|
|
353
371
|
--target-lifecycle-stage {active,deprecated,obsolete,removed}
|
|
354
372
|
--slot SLOT
|
|
@@ -846,7 +864,7 @@ ssot release certify . --release-id rel:0.1.0 --write-report
|
|
|
846
864
|
## Package relationships
|
|
847
865
|
|
|
848
866
|
- Package type: CLI distribution
|
|
849
|
-
- 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/)
|
|
850
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/)
|
|
851
869
|
|
|
852
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
|