ssot-cli 0.1.10.dev1__tar.gz → 0.1.11.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 (31) hide show
  1. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/PKG-INFO +4 -4
  2. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/pyproject.toml +4 -4
  3. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/boundary_cmd.py +9 -2
  4. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/claim_cmd.py +9 -2
  5. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/common.py +15 -0
  6. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/evidence_cmd.py +9 -1
  7. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/feature_cmd.py +9 -1
  8. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/issue_cmd.py +9 -1
  9. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/profile_cmd.py +9 -1
  10. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/release_cmd.py +9 -1
  11. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/risk_cmd.py +9 -1
  12. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/test_cmd.py +9 -1
  13. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli.egg-info/PKG-INFO +4 -4
  14. ssot_cli-0.1.11.dev1/src/ssot_cli.egg-info/requires.txt +6 -0
  15. ssot_cli-0.1.10.dev1/src/ssot_cli.egg-info/requires.txt +0 -6
  16. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/README.md +0 -0
  17. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/setup.cfg +0 -0
  18. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/__init__.py +0 -0
  19. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/adr_cmd.py +0 -0
  20. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/conformance_cmd.py +0 -0
  21. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/graph_cmd.py +0 -0
  22. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/init_cmd.py +0 -0
  23. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/main.py +0 -0
  24. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/registry_cmd.py +0 -0
  25. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/spec_cmd.py +0 -0
  26. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/upgrade_cmd.py +0 -0
  27. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli/validate_cmd.py +0 -0
  28. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli.egg-info/SOURCES.txt +0 -0
  29. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli.egg-info/dependency_links.txt +0 -0
  30. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/src/ssot_cli.egg-info/entry_points.txt +0 -0
  31. {ssot_cli-0.1.10.dev1 → ssot_cli-0.1.11.dev1}/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.10.dev1
3
+ Version: 0.1.11.dev1
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,9 +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.16.dev1
28
- Requires-Dist: ssot-core<0.3.0,>=0.2.16.dev1
29
- Requires-Dist: ssot-conformance<0.3.0,>=0.2.16.dev1
27
+ Requires-Dist: ssot-contracts<0.3.0,>=0.2.17.dev1
28
+ Requires-Dist: ssot-core<0.3.0,>=0.2.17.dev1
29
+ Requires-Dist: ssot-conformance<0.3.0,>=0.2.17.dev1
30
30
  Requires-Dist: tomli>=2.0.1; python_version < "3.11"
31
31
 
32
32
  <div align="center">
@@ -4,16 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ssot-cli"
7
- version = "0.1.10.dev1"
7
+ version = "0.1.11.dev1"
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.16.dev1,<0.3.0",
15
- "ssot-core>=0.2.16.dev1,<0.3.0",
16
- "ssot-conformance>=0.2.16.dev1,<0.3.0",
14
+ "ssot-contracts>=0.2.17.dev1,<0.3.0",
15
+ "ssot-core>=0.2.17.dev1,<0.3.0",
16
+ "ssot-conformance>=0.2.17.dev1,<0.3.0",
17
17
  "tomli>=2.0.1; python_version < '3.11'",
18
18
  ]
19
19
  keywords = ["ssot", "cli", "registry", "governance", "release-management", "validation", "developer-tools"]
@@ -15,7 +15,7 @@ from ssot_registry.api import (
15
15
  run_boundary_tests,
16
16
  update_entity,
17
17
  )
18
- from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, compact_dict
18
+ from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, compact_dict, load_text_argument
19
19
 
20
20
 
21
21
  def register_boundary(subparsers: argparse._SubParsersAction) -> None:
@@ -30,6 +30,8 @@ def register_boundary(subparsers: argparse._SubParsersAction) -> None:
30
30
  add_path_argument(create)
31
31
  create.add_argument("--id", required=True, help="Normalized boundary id to create.")
32
32
  create.add_argument("--title", required=True, help="Human-readable boundary title.")
33
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the boundary.")
34
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the boundary body.")
33
35
  create.add_argument("--status", choices=["draft", "active", "frozen", "retired"], default="draft", help="Current lifecycle state of the boundary.")
34
36
  add_optional_bool_argument(create, "--frozen", default=False, help_text="Record whether the boundary contents are locked against further scope edits.")
35
37
  create.add_argument("--feature-ids", nargs="*", default=[], help="Direct feature ids included in the scoped delivery unit.")
@@ -50,6 +52,8 @@ def register_boundary(subparsers: argparse._SubParsersAction) -> None:
50
52
  add_path_argument(update)
51
53
  update.add_argument("--id", required=True, help="Boundary id to update.")
52
54
  update.add_argument("--title", default=None, help="Replacement boundary title.")
55
+ update.add_argument("--body", default=None, help="Replacement longer-form boundary narrative.")
56
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement boundary body.")
53
57
  update.add_argument("--status", choices=["draft", "active", "frozen", "retired"], default=None, help="Updated lifecycle state.")
54
58
  add_optional_bool_argument(update, "--frozen", default=None, help_text="Change whether the boundary is locked against scope edits.")
55
59
  update.set_defaults(func=run_update)
@@ -101,9 +105,11 @@ def register_boundary(subparsers: argparse._SubParsersAction) -> None:
101
105
 
102
106
 
103
107
  def run_create(args: argparse.Namespace) -> dict[str, object]:
108
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="boundary")
104
109
  row = {
105
110
  "id": args.id,
106
111
  "title": args.title,
112
+ "body": body,
107
113
  "status": args.status,
108
114
  "frozen": args.frozen,
109
115
  "feature_ids": args.feature_ids,
@@ -121,7 +127,8 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
121
127
 
122
128
 
123
129
  def run_update(args: argparse.Namespace) -> dict[str, object]:
124
- changes = compact_dict({"title": args.title, "status": args.status, "frozen": args.frozen})
130
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="boundary")
131
+ changes = compact_dict({"title": args.title, "body": body, "status": args.status, "frozen": args.frozen})
125
132
  if not changes:
126
133
  raise ValueError("At least one update field is required")
127
134
  return update_entity(args.path, "boundaries", args.id, changes)
@@ -14,7 +14,7 @@ from ssot_registry.api import (
14
14
  unlink_entities,
15
15
  update_entity,
16
16
  )
17
- from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict
17
+ from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
18
18
 
19
19
 
20
20
  _LINK_MAPPING = {
@@ -40,6 +40,8 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
40
40
  create.add_argument("--tier", choices=["T0", "T1", "T2", "T3", "T4"], default="T0", help="Assurance tier required for the claim.")
41
41
  create.add_argument("--kind", required=True, help="Operator-defined claim category.")
42
42
  create.add_argument("--description", default="", help="What the claim asserts and why it matters.")
43
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the claim.")
44
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the claim body.")
43
45
  create.add_argument("--feature-ids", nargs="*", default=[], help="Feature ids the claim is about.")
44
46
  create.add_argument("--test-ids", nargs="*", default=[], help="Test ids that support the claim.")
45
47
  create.add_argument("--evidence-ids", nargs="*", default=[], help="Evidence ids that substantiate the claim.")
@@ -61,6 +63,8 @@ def register_claim(subparsers: argparse._SubParsersAction) -> None:
61
63
  update.add_argument("--title", default=None, help="Replacement claim title.")
62
64
  update.add_argument("--kind", default=None, help="Updated claim category.")
63
65
  update.add_argument("--description", default=None, help="Replacement claim description.")
66
+ update.add_argument("--body", default=None, help="Replacement longer-form claim narrative.")
67
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement claim body.")
64
68
  update.set_defaults(func=run_update)
65
69
 
66
70
  delete = claim_sub.add_parser("delete", help="Delete a claim.", description="Remove a claim record from the registry.")
@@ -110,6 +114,7 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
110
114
 
111
115
 
112
116
  def run_create(args: argparse.Namespace) -> dict[str, object]:
117
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="claim")
113
118
  row = {
114
119
  "id": args.id,
115
120
  "title": args.title,
@@ -117,6 +122,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
117
122
  "tier": args.tier,
118
123
  "kind": args.kind,
119
124
  "description": args.description,
125
+ "body": body,
120
126
  "feature_ids": args.feature_ids,
121
127
  "test_ids": args.test_ids,
122
128
  "evidence_ids": args.evidence_ids,
@@ -133,7 +139,8 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
133
139
 
134
140
 
135
141
  def run_update(args: argparse.Namespace) -> dict[str, object]:
136
- changes = compact_dict({"title": args.title, "kind": args.kind, "description": args.description})
142
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="claim")
143
+ changes = compact_dict({"title": args.title, "kind": args.kind, "description": args.description, "body": body})
137
144
  if not changes:
138
145
  raise ValueError("At least one update field is required")
139
146
  return update_entity(args.path, "claims", args.id, changes)
@@ -68,3 +68,18 @@ def load_json_object_argument(
68
68
  if not isinstance(payload, dict):
69
69
  raise ValueError(f"{label} must decode to a JSON object")
70
70
  return payload
71
+
72
+
73
+ def load_text_argument(
74
+ *,
75
+ inline_value: str | None,
76
+ file_value: str | None,
77
+ label: str,
78
+ ) -> str | None:
79
+ if inline_value is None and file_value is None:
80
+ return None
81
+ if inline_value is not None and file_value is not None:
82
+ raise ValueError(f"{label} accepts only one of body or body_file")
83
+ if file_value is not None:
84
+ return Path(file_value).read_text(encoding="utf-8")
85
+ return inline_value
@@ -12,7 +12,7 @@ from ssot_registry.api import (
12
12
  update_entity,
13
13
  verify_evidence_rows,
14
14
  )
15
- from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict
15
+ from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
16
16
 
17
17
 
18
18
  _LINK_MAPPING = {
@@ -36,6 +36,8 @@ def register_evidence(subparsers: argparse._SubParsersAction) -> None:
36
36
  create.add_argument("--status", choices=["planned", "collected", "passed", "failed", "stale"], default="planned", help="Current freshness or outcome state of the evidence artifact.")
37
37
  create.add_argument("--kind", required=True, help="Operator-defined evidence category such as report, bundle, or log.")
38
38
  create.add_argument("--tier", choices=["T0", "T1", "T2", "T3", "T4"], default="T0", help="Assurance tier the evidence contributes toward.")
39
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the evidence row.")
40
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the evidence body.")
39
41
  create.add_argument("--evidence-path", dest="evidence_path", required=True, help="Repository-relative location of the evidence artifact.")
40
42
  create.add_argument("--claim-ids", nargs="*", default=[], help="Claim ids supported by the evidence.")
41
43
  create.add_argument("--test-ids", nargs="*", default=[], help="Test ids associated with the evidence.")
@@ -58,6 +60,8 @@ def register_evidence(subparsers: argparse._SubParsersAction) -> None:
58
60
  update.add_argument("--status", choices=["planned", "collected", "passed", "failed", "stale"], default=None, help="Updated freshness or outcome state.")
59
61
  update.add_argument("--kind", default=None, help="Updated evidence category.")
60
62
  update.add_argument("--tier", choices=["T0", "T1", "T2", "T3", "T4"], default=None, help="Updated assurance tier contribution.")
63
+ update.add_argument("--body", default=None, help="Replacement longer-form evidence narrative.")
64
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement evidence body.")
61
65
  update.add_argument("--evidence-path", dest="evidence_path", default=None, help="Updated repository-relative path to the artifact.")
62
66
  update.set_defaults(func=run_update)
63
67
 
@@ -94,12 +98,14 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
94
98
 
95
99
 
96
100
  def run_create(args: argparse.Namespace) -> dict[str, object]:
101
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="evidence")
97
102
  row = {
98
103
  "id": args.id,
99
104
  "title": args.title,
100
105
  "status": args.status,
101
106
  "kind": args.kind,
102
107
  "tier": args.tier,
108
+ "body": body,
103
109
  "path": args.evidence_path,
104
110
  "claim_ids": args.claim_ids,
105
111
  "test_ids": args.test_ids,
@@ -116,12 +122,14 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
116
122
 
117
123
 
118
124
  def run_update(args: argparse.Namespace) -> dict[str, object]:
125
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="evidence")
119
126
  changes = compact_dict(
120
127
  {
121
128
  "title": args.title,
122
129
  "status": args.status,
123
130
  "kind": args.kind,
124
131
  "tier": args.tier,
132
+ "body": body,
125
133
  "path": args.evidence_path,
126
134
  }
127
135
  )
@@ -20,7 +20,7 @@ from ssot_registry.api import (
20
20
  unlink_entities,
21
21
  update_entity,
22
22
  )
23
- from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict
23
+ from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
24
24
 
25
25
 
26
26
  _LINK_MAPPING = {
@@ -48,6 +48,8 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
48
48
  create.add_argument("--id", required=True, help="Normalized feature id to create.")
49
49
  create.add_argument("--title", required=True, help="Human-readable feature title.")
50
50
  create.add_argument("--description", default="", help="Operator-facing summary of the feature's purpose.")
51
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the feature.")
52
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the feature body.")
51
53
  create.add_argument("--implementation-status", choices=sorted(FEATURE_IMPLEMENTATION_STATUSES), default="absent", help="Current implementation state in the codebase.")
52
54
  create.add_argument("--lifecycle-stage", choices=sorted(FEATURE_LIFECYCLE_STAGES), default="active", help="Actual lifecycle state of the feature today.")
53
55
  create.add_argument("--replacement-feature-id", nargs="*", default=[], help="Replacement feature ids if this feature is being deprecated or removed.")
@@ -78,6 +80,8 @@ def register_feature(subparsers: argparse._SubParsersAction) -> None:
78
80
  update.add_argument("--id", required=True, help="Feature id to update.")
79
81
  update.add_argument("--title", default=None, help="Replacement feature title.")
80
82
  update.add_argument("--description", default=None, help="Replacement feature description.")
83
+ update.add_argument("--body", default=None, help="Replacement longer-form feature narrative.")
84
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement feature body.")
81
85
  update.add_argument("--implementation-status", choices=sorted(FEATURE_IMPLEMENTATION_STATUSES), default=None, help="Updated implementation state in the codebase.")
82
86
  update.set_defaults(func=run_update)
83
87
 
@@ -149,6 +153,7 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
149
153
 
150
154
 
151
155
  def run_create(args: argparse.Namespace) -> dict[str, object]:
156
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="feature")
152
157
  plan = {
153
158
  "horizon": args.horizon,
154
159
  "slot": args.slot,
@@ -162,6 +167,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
162
167
  "id": args.id,
163
168
  "title": args.title,
164
169
  "description": args.description,
170
+ "body": body,
165
171
  "implementation_status": args.implementation_status,
166
172
  "lifecycle": {
167
173
  "stage": args.lifecycle_stage,
@@ -186,10 +192,12 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
186
192
 
187
193
 
188
194
  def run_update(args: argparse.Namespace) -> dict[str, object]:
195
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="feature")
189
196
  changes = compact_dict(
190
197
  {
191
198
  "title": args.title,
192
199
  "description": args.description,
200
+ "body": body,
193
201
  "implementation_status": args.implementation_status,
194
202
  }
195
203
  )
@@ -13,7 +13,7 @@ from ssot_registry.api import (
13
13
  unlink_entities,
14
14
  update_entity,
15
15
  )
16
- from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict
16
+ from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
17
17
 
18
18
 
19
19
  _LINK_MAPPING = {
@@ -40,6 +40,8 @@ def register_issue(subparsers: argparse._SubParsersAction) -> None:
40
40
  create.add_argument("--status", choices=["open", "in_progress", "blocked", "resolved", "closed"], default="open", help="Current workflow state of the issue.")
41
41
  create.add_argument("--severity", choices=["low", "medium", "high", "critical"], default="medium", help="Operational severity of the issue.")
42
42
  create.add_argument("--description", default="", help="Operator-facing problem summary and context.")
43
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the issue.")
44
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the issue body.")
43
45
  create.add_argument("--horizon", choices=["current", "next", "future", "explicit", "backlog", "out_of_bounds"], default="backlog", help="Planning horizon used to schedule the issue.")
44
46
  create.add_argument("--slot", default=None, help="Explicit release train, sprint, or slot label when planning explicitly.")
45
47
  create.add_argument("--feature-ids", nargs="*", default=[], help="Feature ids affected by the issue.")
@@ -66,6 +68,8 @@ def register_issue(subparsers: argparse._SubParsersAction) -> None:
66
68
  update.add_argument("--title", default=None, help="Replacement issue title.")
67
69
  update.add_argument("--severity", choices=["low", "medium", "high", "critical"], default=None, help="Updated operational severity.")
68
70
  update.add_argument("--description", default=None, help="Replacement issue description.")
71
+ update.add_argument("--body", default=None, help="Replacement longer-form issue narrative.")
72
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement issue body.")
69
73
  add_optional_bool_argument(update, "--release-blocking", default=None, help_text="Change whether the issue is treated as a release blocker.")
70
74
  update.set_defaults(func=run_update)
71
75
 
@@ -125,12 +129,14 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
125
129
 
126
130
 
127
131
  def run_create(args: argparse.Namespace) -> dict[str, object]:
132
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="issue")
128
133
  row = {
129
134
  "id": args.id,
130
135
  "title": args.title,
131
136
  "status": args.status,
132
137
  "severity": args.severity,
133
138
  "description": args.description,
139
+ "body": body,
134
140
  "plan": {"horizon": args.horizon, "slot": args.slot},
135
141
  "feature_ids": args.feature_ids,
136
142
  "claim_ids": args.claim_ids,
@@ -151,11 +157,13 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
151
157
 
152
158
 
153
159
  def run_update(args: argparse.Namespace) -> dict[str, object]:
160
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="issue")
154
161
  changes = compact_dict(
155
162
  {
156
163
  "title": args.title,
157
164
  "severity": args.severity,
158
165
  "description": args.description,
166
+ "body": body,
159
167
  "release_blocking": args.release_blocking,
160
168
  }
161
169
  )
@@ -12,7 +12,7 @@ from ssot_registry.api import (
12
12
  unlink_entities,
13
13
  update_entity,
14
14
  )
15
- from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict
15
+ from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
16
16
 
17
17
  _LINK_MAPPING = {
18
18
  "feature_ids": "feature_ids",
@@ -33,6 +33,8 @@ def register_profile(subparsers: argparse._SubParsersAction) -> None:
33
33
  create.add_argument("--id", required=True, help="Normalized profile id to create.")
34
34
  create.add_argument("--title", required=True, help="Human-readable profile title.")
35
35
  create.add_argument("--description", default="", help="Operator-facing summary of the profile's scope.")
36
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the profile.")
37
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the profile body.")
36
38
  create.add_argument("--status", choices=["draft", "active", "retired"], default="draft", help="Current lifecycle state of the profile.")
37
39
  create.add_argument("--kind", choices=["capability", "certification", "deployment", "interoperability"], default="capability", help="Why the profile exists operationally.")
38
40
  create.add_argument("--feature-ids", nargs="*", default=[], help="Feature ids directly included in the profile.")
@@ -61,6 +63,8 @@ def register_profile(subparsers: argparse._SubParsersAction) -> None:
61
63
  update.add_argument("--id", required=True, help="Profile id to update.")
62
64
  update.add_argument("--title", default=None, help="Replacement profile title.")
63
65
  update.add_argument("--description", default=None, help="Replacement profile description.")
66
+ update.add_argument("--body", default=None, help="Replacement longer-form profile narrative.")
67
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement profile body.")
64
68
  update.add_argument("--status", choices=["draft", "active", "retired"], default=None, help="New lifecycle state.")
65
69
  update.add_argument("--kind", choices=["capability", "certification", "deployment", "interoperability"], default=None, help="New operational role for the profile.")
66
70
  update.add_argument("--claim-tier", choices=["T0", "T1", "T2", "T3", "T4"], default=None, help="Updated default claim tier for evaluation.")
@@ -104,6 +108,7 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
104
108
 
105
109
 
106
110
  def run_create(args: argparse.Namespace) -> dict[str, object]:
111
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="profile")
107
112
  return create_entity(
108
113
  args.path,
109
114
  "profiles",
@@ -111,6 +116,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
111
116
  "id": args.id,
112
117
  "title": args.title,
113
118
  "description": args.description,
119
+ "body": body,
114
120
  "status": args.status,
115
121
  "kind": args.kind,
116
122
  "feature_ids": args.feature_ids,
@@ -133,10 +139,12 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
133
139
 
134
140
 
135
141
  def run_update(args: argparse.Namespace) -> dict[str, object]:
142
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="profile")
136
143
  changes = compact_dict(
137
144
  {
138
145
  "title": args.title,
139
146
  "description": args.description,
147
+ "body": body,
140
148
  "status": args.status,
141
149
  "kind": args.kind,
142
150
  "claim_tier": args.claim_tier,
@@ -19,7 +19,7 @@ from ssot_registry.api import (
19
19
  revoke_release,
20
20
  update_entity,
21
21
  )
22
- from ssot_cli.common import add_ids_argument, add_path_argument, compact_dict
22
+ from ssot_cli.common import add_ids_argument, add_path_argument, compact_dict, load_text_argument
23
23
 
24
24
 
25
25
  def register_release(subparsers: argparse._SubParsersAction) -> None:
@@ -33,6 +33,8 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
33
33
  create = release_sub.add_parser("create", help="Create a release candidate.", description="Create a release record tied to a frozen boundary and optional supporting claims and evidence.")
34
34
  add_path_argument(create)
35
35
  create.add_argument("--id", required=True, help="Normalized release id to create.")
36
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the release.")
37
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the release body.")
36
38
  create.add_argument("--version", required=True, help="Semantic or operator-defined version string for the release.")
37
39
  create.add_argument("--status", choices=["draft", "candidate", "certified", "promoted", "published", "revoked"], default="draft", help="Current publication stage of the release.")
38
40
  create.add_argument("--boundary-id", required=True, help="Primary frozen boundary id that defines the release scope.")
@@ -54,6 +56,8 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
54
56
  update = release_sub.add_parser("update", help="Edit release metadata.", description="Update mutable release fields without changing its claim or evidence membership lists.")
55
57
  add_path_argument(update)
56
58
  update.add_argument("--id", required=True, help="Release id to update.")
59
+ update.add_argument("--body", default=None, help="Replacement longer-form release narrative.")
60
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement release body.")
57
61
  update.add_argument("--version", default=None, help="Replacement release version string.")
58
62
  update.add_argument("--status", choices=["draft", "candidate", "certified", "promoted", "published", "revoked"], default=None, help="Updated publication stage.")
59
63
  update.add_argument("--boundary-id", default=None, help="Replacement primary boundary id that defines the release scope.")
@@ -125,10 +129,12 @@ def register_release(subparsers: argparse._SubParsersAction) -> None:
125
129
 
126
130
 
127
131
  def run_create(args: argparse.Namespace) -> dict[str, object]:
132
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="release")
128
133
  boundary_ids = [args.boundary_id, *(args.boundary_ids or [])]
129
134
  boundary_ids = list(dict.fromkeys(boundary_ids))
130
135
  row = {
131
136
  "id": args.id,
137
+ "body": body,
132
138
  "version": args.version,
133
139
  "status": args.status,
134
140
  "boundary_id": args.boundary_id,
@@ -148,6 +154,7 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
148
154
 
149
155
 
150
156
  def run_update(args: argparse.Namespace) -> dict[str, object]:
157
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="release")
151
158
  boundary_ids = args.boundary_ids
152
159
  if boundary_ids is not None:
153
160
  boundary_ids = list(dict.fromkeys(([args.boundary_id] if args.boundary_id else []) + boundary_ids))
@@ -156,6 +163,7 @@ def run_update(args: argparse.Namespace) -> dict[str, object]:
156
163
  elif args.boundary_id is not None:
157
164
  boundary_ids = [args.boundary_id]
158
165
  changes = compact_dict({
166
+ "body": body,
159
167
  "version": args.version,
160
168
  "status": args.status,
161
169
  "boundary_id": args.boundary_id or (boundary_ids[0] if boundary_ids else None),
@@ -12,7 +12,7 @@ from ssot_registry.api import (
12
12
  unlink_entities,
13
13
  update_entity,
14
14
  )
15
- from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict
15
+ from ssot_cli.common import add_ids_argument, add_optional_bool_argument, add_path_argument, collect_list_fields, compact_dict, load_text_argument
16
16
 
17
17
 
18
18
  _LINK_MAPPING = {
@@ -39,6 +39,8 @@ def register_risk(subparsers: argparse._SubParsersAction) -> None:
39
39
  create.add_argument("--status", choices=["active", "mitigated", "accepted", "retired"], default="active", help="Current treatment state of the risk.")
40
40
  create.add_argument("--severity", choices=["low", "medium", "high", "critical"], default="medium", help="Operational severity of the exposure.")
41
41
  create.add_argument("--description", default="", help="Operator-facing description of the exposure and context.")
42
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the risk.")
43
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the risk body.")
42
44
  create.add_argument("--feature-ids", nargs="*", default=[], help="Feature ids exposed by the risk.")
43
45
  create.add_argument("--claim-ids", nargs="*", default=[], help="Claim ids affected by the risk.")
44
46
  create.add_argument("--test-ids", nargs="*", default=[], help="Test ids related to the risk.")
@@ -63,6 +65,8 @@ def register_risk(subparsers: argparse._SubParsersAction) -> None:
63
65
  update.add_argument("--title", default=None, help="Replacement risk title.")
64
66
  update.add_argument("--severity", choices=["low", "medium", "high", "critical"], default=None, help="Updated operational severity.")
65
67
  update.add_argument("--description", default=None, help="Replacement risk description.")
68
+ update.add_argument("--body", default=None, help="Replacement longer-form risk narrative.")
69
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement risk body.")
66
70
  add_optional_bool_argument(update, "--release-blocking", default=None, help_text="Change whether the risk is treated as a release blocker.")
67
71
  update.set_defaults(func=run_update)
68
72
 
@@ -115,12 +119,14 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
115
119
 
116
120
 
117
121
  def run_create(args: argparse.Namespace) -> dict[str, object]:
122
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="risk")
118
123
  row = {
119
124
  "id": args.id,
120
125
  "title": args.title,
121
126
  "status": args.status,
122
127
  "severity": args.severity,
123
128
  "description": args.description,
129
+ "body": body,
124
130
  "feature_ids": args.feature_ids,
125
131
  "claim_ids": args.claim_ids,
126
132
  "test_ids": args.test_ids,
@@ -140,11 +146,13 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
140
146
 
141
147
 
142
148
  def run_update(args: argparse.Namespace) -> dict[str, object]:
149
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="risk")
143
150
  changes = compact_dict(
144
151
  {
145
152
  "title": args.title,
146
153
  "severity": args.severity,
147
154
  "description": args.description,
155
+ "body": body,
148
156
  "release_blocking": args.release_blocking,
149
157
  }
150
158
  )
@@ -3,7 +3,7 @@
3
3
  import argparse
4
4
 
5
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
6
+ from ssot_cli.common import add_ids_argument, add_path_argument, collect_list_fields, compact_dict, load_json_object_argument, load_text_argument
7
7
 
8
8
 
9
9
  _LINK_MAPPING = {
@@ -25,6 +25,8 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
25
25
  add_path_argument(create)
26
26
  create.add_argument("--id", required=True, help="Normalized test id to create.")
27
27
  create.add_argument("--title", required=True, help="Human-readable test title.")
28
+ create.add_argument("--body", default=None, help="Optional longer-form narrative for the test.")
29
+ create.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the test body.")
28
30
  create.add_argument("--status", choices=["planned", "passing", "failing", "blocked", "skipped"], default="planned", help="Current execution or readiness state of the test.")
29
31
  create.add_argument("--kind", required=True, help="Operator-defined test category such as unit, integration, or manual.")
30
32
  create.add_argument("--test-path", dest="test_path", required=True, help="Repository-relative location of the executable test or test specification.")
@@ -49,6 +51,8 @@ def register_test(subparsers: argparse._SubParsersAction) -> None:
49
51
  add_path_argument(update)
50
52
  update.add_argument("--id", required=True, help="Test id to update.")
51
53
  update.add_argument("--title", default=None, help="Replacement test title.")
54
+ update.add_argument("--body", default=None, help="Replacement longer-form test narrative.")
55
+ update.add_argument("--body-file", default=None, help="Path to a UTF-8 text file containing the replacement test body.")
52
56
  update.add_argument("--status", choices=["planned", "passing", "failing", "blocked", "skipped"], default=None, help="Updated execution or readiness state.")
53
57
  update.add_argument("--kind", default=None, help="Updated test category.")
54
58
  update.add_argument("--test-path", dest="test_path", default=None, help="Updated repository-relative path to the test or procedure.")
@@ -99,6 +103,7 @@ def _build_links(args: argparse.Namespace) -> dict[str, list[str]]:
99
103
 
100
104
 
101
105
  def run_create(args: argparse.Namespace) -> dict[str, object]:
106
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="test")
102
107
  execution = load_json_object_argument(
103
108
  inline_value=args.execution_json,
104
109
  file_value=args.execution_file,
@@ -107,6 +112,7 @@ def run_create(args: argparse.Namespace) -> dict[str, object]:
107
112
  row = {
108
113
  "id": args.id,
109
114
  "title": args.title,
115
+ "body": body,
110
116
  "status": args.status,
111
117
  "kind": args.kind,
112
118
  "path": args.test_path,
@@ -127,6 +133,7 @@ def run_list(args: argparse.Namespace) -> dict[str, object]:
127
133
 
128
134
 
129
135
  def run_update(args: argparse.Namespace) -> dict[str, object]:
136
+ body = load_text_argument(inline_value=args.body, file_value=args.body_file, label="test")
130
137
  execution = load_json_object_argument(
131
138
  inline_value=args.execution_json,
132
139
  file_value=args.execution_file,
@@ -135,6 +142,7 @@ def run_update(args: argparse.Namespace) -> dict[str, object]:
135
142
  changes = compact_dict(
136
143
  {
137
144
  "title": args.title,
145
+ "body": body,
138
146
  "status": args.status,
139
147
  "kind": args.kind,
140
148
  "path": args.test_path,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssot-cli
3
- Version: 0.1.10.dev1
3
+ Version: 0.1.11.dev1
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,9 +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.16.dev1
28
- Requires-Dist: ssot-core<0.3.0,>=0.2.16.dev1
29
- Requires-Dist: ssot-conformance<0.3.0,>=0.2.16.dev1
27
+ Requires-Dist: ssot-contracts<0.3.0,>=0.2.17.dev1
28
+ Requires-Dist: ssot-core<0.3.0,>=0.2.17.dev1
29
+ Requires-Dist: ssot-conformance<0.3.0,>=0.2.17.dev1
30
30
  Requires-Dist: tomli>=2.0.1; python_version < "3.11"
31
31
 
32
32
  <div align="center">
@@ -0,0 +1,6 @@
1
+ ssot-contracts<0.3.0,>=0.2.17.dev1
2
+ ssot-core<0.3.0,>=0.2.17.dev1
3
+ ssot-conformance<0.3.0,>=0.2.17.dev1
4
+
5
+ [:python_version < "3.11"]
6
+ tomli>=2.0.1
@@ -1,6 +0,0 @@
1
- ssot-contracts<0.3.0,>=0.2.16.dev1
2
- ssot-core<0.3.0,>=0.2.16.dev1
3
- ssot-conformance<0.3.0,>=0.2.16.dev1
4
-
5
- [:python_version < "3.11"]
6
- tomli>=2.0.1
File without changes
File without changes