engineering-process 2.0.0__tar.gz → 2.0.2__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.
- {engineering_process-2.0.0/engineering_process.egg-info → engineering_process-2.0.2}/PKG-INFO +15 -1
- {engineering_process-2.0.0 → engineering_process-2.0.2}/README.md +14 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/__init__.py +1 -1
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/cli.py +17 -1
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/commands.py +45 -4
- {engineering_process-2.0.0 → engineering_process-2.0.2/engineering_process.egg-info}/PKG-INFO +15 -1
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-verify/SKILL.md +5 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/pyproject.toml +1 -1
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/run.schema.json +47 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_automation.py +10 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_cli.py +7 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_commands.py +122 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_contracts.py +56 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_lifecycle.py +43 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/LICENSE +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/MANIFEST.in +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/__main__.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_supervisor_contract.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_windows_job.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/adoption.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/contracts.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/distribution.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/helper_launch.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/lifecycle.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/production_engineering.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/project.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/publication_compat.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/release.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/repository.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/skills.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/supervision.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/SOURCES.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process-graph.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-complete/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-implement/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-plan/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-review/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-start/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/deliver-change/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/process-improve/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/production-engineering/SKILL.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/production-engineering/invariants.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/change.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/plan.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/process-graph.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/process-lock.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/production-engineering.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/project.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/receipt.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/release-change.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/release.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/review.schema.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/setup.cfg +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/templates/AGENTS.process.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/templates/adopt-process.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/templates/renovate.json +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_adoption.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_architecture.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_distribution.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_production_engineering.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_publication_compat.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_pypi_cache_horizon.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_release.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_repository.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_skills.py +0 -0
- {engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_supervisor_posix.py +0 -0
{engineering_process-2.0.0/engineering_process.egg-info → engineering_process-2.0.2}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: A small agent-neutral engineering lifecycle with managed adoption
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/phuongnse/engineering-process
|
|
@@ -114,6 +114,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
|
|
|
114
114
|
Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
|
|
115
115
|
stdout or stderr that could contain secrets.
|
|
116
116
|
|
|
117
|
+
A failed profile also reports a safe `diagnostic` descriptor containing only its
|
|
118
|
+
validated profile identifier, check identifier, canonical one-based profile position,
|
|
119
|
+
and a fixed `processctl` argument array. The position remains unambiguous even when a
|
|
120
|
+
compatible project has duplicate check identifiers. Run that array from the project
|
|
121
|
+
root to reproduce only the failed configured check through the same bounded runner,
|
|
122
|
+
for example:
|
|
123
|
+
|
|
124
|
+
processctl verify --profile rust --check-position 3
|
|
125
|
+
|
|
126
|
+
This selective run is diagnostic only: it does not expose captured output, replace a
|
|
127
|
+
required full-profile run, or count as lifecycle verification evidence. Maintainers
|
|
128
|
+
who need semantic tool output must use a tool-owned structured report or run the
|
|
129
|
+
consumer command in an appropriately trusted environment.
|
|
130
|
+
|
|
117
131
|
### Production readiness
|
|
118
132
|
|
|
119
133
|
A consumer declares `.process/readiness.json` with production as its direction, its
|
|
@@ -90,6 +90,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
|
|
|
90
90
|
Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
|
|
91
91
|
stdout or stderr that could contain secrets.
|
|
92
92
|
|
|
93
|
+
A failed profile also reports a safe `diagnostic` descriptor containing only its
|
|
94
|
+
validated profile identifier, check identifier, canonical one-based profile position,
|
|
95
|
+
and a fixed `processctl` argument array. The position remains unambiguous even when a
|
|
96
|
+
compatible project has duplicate check identifiers. Run that array from the project
|
|
97
|
+
root to reproduce only the failed configured check through the same bounded runner,
|
|
98
|
+
for example:
|
|
99
|
+
|
|
100
|
+
processctl verify --profile rust --check-position 3
|
|
101
|
+
|
|
102
|
+
This selective run is diagnostic only: it does not expose captured output, replace a
|
|
103
|
+
required full-profile run, or count as lifecycle verification evidence. Maintainers
|
|
104
|
+
who need semantic tool output must use a tool-owned structured report or run the
|
|
105
|
+
consumer command in an appropriately trusted environment.
|
|
106
|
+
|
|
93
107
|
### Production readiness
|
|
94
108
|
|
|
95
109
|
A consumer declares `.process/readiness.json` with production as its direction, its
|
|
@@ -61,6 +61,16 @@ def _root(value: str) -> Path:
|
|
|
61
61
|
return path
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def _check_position(value: str) -> int:
|
|
65
|
+
try:
|
|
66
|
+
position = int(value)
|
|
67
|
+
except ValueError as error:
|
|
68
|
+
raise argparse.ArgumentTypeError("check position must be an integer") from error
|
|
69
|
+
if position < 1 or position > 32:
|
|
70
|
+
raise argparse.ArgumentTypeError("check position must be between 1 and 32")
|
|
71
|
+
return position
|
|
72
|
+
|
|
73
|
+
|
|
64
74
|
def _process_root(args: argparse.Namespace) -> Path:
|
|
65
75
|
return distribution_root(args.process_root)
|
|
66
76
|
|
|
@@ -209,7 +219,12 @@ def command_verify(args: argparse.Namespace) -> Result:
|
|
|
209
219
|
process_root = _process_root(args)
|
|
210
220
|
project = load_project(args.project_root, process_root)
|
|
211
221
|
before = repository_snapshot(args.project_root)
|
|
212
|
-
report = run_profile(
|
|
222
|
+
report = run_profile(
|
|
223
|
+
args.project_root,
|
|
224
|
+
project,
|
|
225
|
+
args.profile,
|
|
226
|
+
check_position=args.check_position,
|
|
227
|
+
)
|
|
213
228
|
after = repository_snapshot(args.project_root)
|
|
214
229
|
if not same_checkpoint(before, after):
|
|
215
230
|
report["status"] = "failed"
|
|
@@ -433,6 +448,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
433
448
|
setup.add_argument("--allow", action="append", default=[])
|
|
434
449
|
verify = _leaf(commands, "verify", command_verify, help="Run a project verification profile")
|
|
435
450
|
verify.add_argument("--profile", required=True)
|
|
451
|
+
verify.add_argument("--check-position", type=_check_position)
|
|
436
452
|
adoption = commands.add_parser("adoption", help="Apply or check managed adoption")
|
|
437
453
|
adoption_commands = adoption.add_subparsers(dest="adoption_command", required=True)
|
|
438
454
|
for name, handler in (("apply", command_adoption_apply), ("check", command_adoption_check)):
|
|
@@ -199,20 +199,61 @@ def run_profile(
|
|
|
199
199
|
project_root: Path,
|
|
200
200
|
project: dict[str, Any],
|
|
201
201
|
profile: str,
|
|
202
|
+
*,
|
|
203
|
+
check_position: int | None = None,
|
|
202
204
|
) -> dict[str, Any]:
|
|
203
|
-
|
|
204
|
-
if
|
|
205
|
+
configured_checks = project["profiles"].get(profile)
|
|
206
|
+
if configured_checks is None:
|
|
205
207
|
raise ProcessError(f"unknown verification profile: {profile}")
|
|
208
|
+
if check_position is None:
|
|
209
|
+
checks = configured_checks
|
|
210
|
+
positions = range(1, len(checks) + 1)
|
|
211
|
+
else:
|
|
212
|
+
if check_position < 1 or check_position > len(configured_checks):
|
|
213
|
+
raise ProcessError(
|
|
214
|
+
f"profile {profile} has no check at position: {check_position}"
|
|
215
|
+
)
|
|
216
|
+
checks = [configured_checks[check_position - 1]]
|
|
217
|
+
positions = [check_position]
|
|
206
218
|
reports: list[dict[str, Any]] = []
|
|
207
|
-
|
|
219
|
+
failed_position: int | None = None
|
|
220
|
+
for position, check in zip(positions, checks, strict=True):
|
|
208
221
|
report = run_check(project_root, check)
|
|
209
222
|
reports.append(report)
|
|
210
223
|
if report["status"] != "passed":
|
|
224
|
+
failed_position = position
|
|
211
225
|
break
|
|
212
|
-
|
|
226
|
+
result = {
|
|
213
227
|
"profile": profile,
|
|
214
228
|
"status": "passed" if len(reports) == len(checks) and all(
|
|
215
229
|
report["status"] == "passed" for report in reports
|
|
216
230
|
) else "failed",
|
|
231
|
+
"scope": (
|
|
232
|
+
{"kind": "profile"}
|
|
233
|
+
if check_position is None
|
|
234
|
+
else {
|
|
235
|
+
"kind": "check",
|
|
236
|
+
"check": checks[0]["id"],
|
|
237
|
+
"position": check_position,
|
|
238
|
+
}
|
|
239
|
+
),
|
|
217
240
|
"checks": reports,
|
|
218
241
|
}
|
|
242
|
+
failed = next((report for report in reports if report["status"] == "failed"), None)
|
|
243
|
+
if failed is not None:
|
|
244
|
+
assert failed_position is not None
|
|
245
|
+
result["diagnostic"] = {
|
|
246
|
+
"kind": "selective-check-reproduction",
|
|
247
|
+
"profile": profile,
|
|
248
|
+
"check": failed["id"],
|
|
249
|
+
"position": failed_position,
|
|
250
|
+
"command": [
|
|
251
|
+
"processctl",
|
|
252
|
+
"verify",
|
|
253
|
+
"--profile",
|
|
254
|
+
profile,
|
|
255
|
+
"--check-position",
|
|
256
|
+
str(failed_position),
|
|
257
|
+
],
|
|
258
|
+
}
|
|
259
|
+
return result
|
{engineering_process-2.0.0 → engineering_process-2.0.2/engineering_process.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: A small agent-neutral engineering lifecycle with managed adoption
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/phuongnse/engineering-process
|
|
@@ -114,6 +114,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
|
|
|
114
114
|
Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
|
|
115
115
|
stdout or stderr that could contain secrets.
|
|
116
116
|
|
|
117
|
+
A failed profile also reports a safe `diagnostic` descriptor containing only its
|
|
118
|
+
validated profile identifier, check identifier, canonical one-based profile position,
|
|
119
|
+
and a fixed `processctl` argument array. The position remains unambiguous even when a
|
|
120
|
+
compatible project has duplicate check identifiers. Run that array from the project
|
|
121
|
+
root to reproduce only the failed configured check through the same bounded runner,
|
|
122
|
+
for example:
|
|
123
|
+
|
|
124
|
+
processctl verify --profile rust --check-position 3
|
|
125
|
+
|
|
126
|
+
This selective run is diagnostic only: it does not expose captured output, replace a
|
|
127
|
+
required full-profile run, or count as lifecycle verification evidence. Maintainers
|
|
128
|
+
who need semantic tool output must use a tool-owned structured report or run the
|
|
129
|
+
consumer command in an appropriately trusted environment.
|
|
130
|
+
|
|
117
131
|
### Production readiness
|
|
118
132
|
|
|
119
133
|
A consumer declares `.process/readiness.json` with production as its direction, its
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-verify/SKILL.md
RENAMED
|
@@ -16,6 +16,11 @@ or stream failure, failed descendant cleanup, or tracked repository mutation is
|
|
|
16
16
|
failure and leaves the change in implementing. Successfully cleaned post-exit
|
|
17
17
|
descendants remain recorded without replacing the foreground command result.
|
|
18
18
|
|
|
19
|
+
A failed report may include a safe selective-reproduction argument array. It can
|
|
20
|
+
confirm the isolated failure through the same bounded runner, but it remains a
|
|
21
|
+
diagnostic action: never submit it in place of the required full profile, and do not
|
|
22
|
+
expect it to reveal captured stdout or stderr.
|
|
23
|
+
|
|
19
24
|
The contract must already include conditional profiles required by affected enforced
|
|
20
25
|
capabilities. Run those profiles exactly; do not run every planned production gate for
|
|
21
26
|
an unrelated change, and do not treat a passing baseline profile as evidence for a
|
|
@@ -147,6 +147,51 @@
|
|
|
147
147
|
"stderr": {"$ref": "#/$defs/stream"}
|
|
148
148
|
}
|
|
149
149
|
},
|
|
150
|
+
"verificationScope": {
|
|
151
|
+
"oneOf": [
|
|
152
|
+
{
|
|
153
|
+
"type": "object",
|
|
154
|
+
"additionalProperties": false,
|
|
155
|
+
"required": ["kind"],
|
|
156
|
+
"properties": {"kind": {"const": "profile"}}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"type": "object",
|
|
160
|
+
"additionalProperties": false,
|
|
161
|
+
"required": ["kind", "check", "position"],
|
|
162
|
+
"properties": {
|
|
163
|
+
"kind": {"const": "check"},
|
|
164
|
+
"check": {"$ref": "#/$defs/id"},
|
|
165
|
+
"position": {"type": "integer", "minimum": 1, "maximum": 32}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
"diagnostic": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"additionalProperties": false,
|
|
173
|
+
"required": ["kind", "profile", "check", "position", "command"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"kind": {"const": "selective-check-reproduction"},
|
|
176
|
+
"profile": {"$ref": "#/$defs/id"},
|
|
177
|
+
"check": {"$ref": "#/$defs/id"},
|
|
178
|
+
"position": {"type": "integer", "minimum": 1, "maximum": 32},
|
|
179
|
+
"command": {
|
|
180
|
+
"type": "array",
|
|
181
|
+
"minItems": 6,
|
|
182
|
+
"maxItems": 6,
|
|
183
|
+
"prefixItems": [
|
|
184
|
+
{"const": "processctl"},
|
|
185
|
+
{"const": "verify"},
|
|
186
|
+
{"const": "--profile"},
|
|
187
|
+
{"$ref": "#/$defs/id"},
|
|
188
|
+
{"const": "--check-position"},
|
|
189
|
+
{"type": "string", "pattern": "^(?:[1-9]|[12][0-9]|3[0-2])$"}
|
|
190
|
+
],
|
|
191
|
+
"items": false
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
150
195
|
"verificationReport": {
|
|
151
196
|
"type": "object",
|
|
152
197
|
"additionalProperties": false,
|
|
@@ -154,6 +199,8 @@
|
|
|
154
199
|
"properties": {
|
|
155
200
|
"profile": {"$ref": "#/$defs/id"},
|
|
156
201
|
"status": {"enum": ["passed", "failed"]},
|
|
202
|
+
"scope": {"$ref": "#/$defs/verificationScope"},
|
|
203
|
+
"diagnostic": {"$ref": "#/$defs/diagnostic"},
|
|
157
204
|
"checks": {
|
|
158
205
|
"type": "array",
|
|
159
206
|
"maxItems": 32,
|
|
@@ -269,6 +269,16 @@ class AutomationTests(unittest.TestCase):
|
|
|
269
269
|
encoding="utf-8"
|
|
270
270
|
)
|
|
271
271
|
self.assertTrue(workflow.startswith("name: CI\n"))
|
|
272
|
+
events = workflow.split("on:\n", maxsplit=1)[1].split(
|
|
273
|
+
"\npermissions:\n", maxsplit=1
|
|
274
|
+
)[0]
|
|
275
|
+
self.assertEqual(
|
|
276
|
+
" pull_request:\n"
|
|
277
|
+
" types: [opened, synchronize, reopened, ready_for_review]\n"
|
|
278
|
+
" push:\n"
|
|
279
|
+
" branches: [main]\n",
|
|
280
|
+
events,
|
|
281
|
+
)
|
|
272
282
|
policy_job = " policy-verification:\n" + workflow.split(
|
|
273
283
|
" policy-verification:\n", maxsplit=1
|
|
274
284
|
)[1].split("\n adopted-process:\n", maxsplit=1)[0]
|
|
@@ -38,6 +38,13 @@ class CliTests(unittest.TestCase):
|
|
|
38
38
|
set(subparsers.choices),
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
def test_verify_accepts_an_explicit_diagnostic_check(self) -> None:
|
|
42
|
+
args = build_parser().parse_args(
|
|
43
|
+
["verify", "--profile", "rust", "--check-position", "2"]
|
|
44
|
+
)
|
|
45
|
+
self.assertEqual("rust", args.profile)
|
|
46
|
+
self.assertEqual(2, args.check_position)
|
|
47
|
+
|
|
41
48
|
def test_skills_validate_emits_machine_readable_result(self) -> None:
|
|
42
49
|
output = io.StringIO()
|
|
43
50
|
with contextlib.redirect_stdout(output):
|
|
@@ -12,6 +12,7 @@ import unittest
|
|
|
12
12
|
from unittest.mock import Mock, patch
|
|
13
13
|
|
|
14
14
|
from engineering_process.commands import run_check, run_profile
|
|
15
|
+
from engineering_process.contracts import ProcessError
|
|
15
16
|
from engineering_process.supervision import CleanupOutcome
|
|
16
17
|
|
|
17
18
|
|
|
@@ -82,6 +83,127 @@ class CommandTests(unittest.TestCase):
|
|
|
82
83
|
report = run_profile(Path(directory), project, "development")
|
|
83
84
|
self.assertEqual("failed", report["status"])
|
|
84
85
|
self.assertEqual(["fail"], [item["id"] for item in report["checks"]])
|
|
86
|
+
self.assertEqual({"kind": "profile"}, report["scope"])
|
|
87
|
+
self.assertEqual(
|
|
88
|
+
{
|
|
89
|
+
"kind": "selective-check-reproduction",
|
|
90
|
+
"profile": "development",
|
|
91
|
+
"check": "fail",
|
|
92
|
+
"position": 1,
|
|
93
|
+
"command": [
|
|
94
|
+
"processctl",
|
|
95
|
+
"verify",
|
|
96
|
+
"--profile",
|
|
97
|
+
"development",
|
|
98
|
+
"--check-position",
|
|
99
|
+
"1",
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
report["diagnostic"],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def test_profile_failure_descriptor_excludes_child_details(self) -> None:
|
|
106
|
+
secret = "TOPSECRET-DIAGNOSTIC-VALUE"
|
|
107
|
+
project = {
|
|
108
|
+
"profiles": {
|
|
109
|
+
"rust": [
|
|
110
|
+
{
|
|
111
|
+
"id": "format",
|
|
112
|
+
"run": [sys.executable, "-c", "raise SystemExit(0)"],
|
|
113
|
+
"timeoutSeconds": 10,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "rust-tests",
|
|
117
|
+
"run": [
|
|
118
|
+
sys.executable,
|
|
119
|
+
"-c",
|
|
120
|
+
f"import sys; print({secret!r}); print({secret!r}, file=sys.stderr); raise SystemExit(101)",
|
|
121
|
+
],
|
|
122
|
+
"timeoutSeconds": 10,
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
with tempfile.TemporaryDirectory(prefix="secret-working-directory-") as directory:
|
|
128
|
+
report = run_profile(Path(directory), project, "rust")
|
|
129
|
+
self.assertEqual(["format", "rust-tests"], [item["id"] for item in report["checks"]])
|
|
130
|
+
self.assertEqual(101, report["checks"][-1]["exitCode"])
|
|
131
|
+
self.assertEqual("rust-tests", report["diagnostic"]["check"])
|
|
132
|
+
self.assertEqual(2, report["diagnostic"]["position"])
|
|
133
|
+
rendered = repr(report)
|
|
134
|
+
self.assertNotIn(secret, rendered)
|
|
135
|
+
self.assertNotIn(sys.executable, rendered)
|
|
136
|
+
self.assertNotIn("secret-working-directory", rendered)
|
|
137
|
+
|
|
138
|
+
def test_selective_reproduction_runs_only_the_named_check(self) -> None:
|
|
139
|
+
project = {
|
|
140
|
+
"profiles": {
|
|
141
|
+
"rust": [
|
|
142
|
+
{
|
|
143
|
+
"id": "would-fail",
|
|
144
|
+
"run": [sys.executable, "-c", "raise SystemExit(7)"],
|
|
145
|
+
"timeoutSeconds": 10,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"id": "rust-tests",
|
|
149
|
+
"run": [sys.executable, "-c", "raise SystemExit(0)"],
|
|
150
|
+
"timeoutSeconds": 10,
|
|
151
|
+
},
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
156
|
+
report = run_profile(Path(directory), project, "rust", check_position=2)
|
|
157
|
+
self.assertEqual("passed", report["status"])
|
|
158
|
+
self.assertEqual(
|
|
159
|
+
{"kind": "check", "check": "rust-tests", "position": 2},
|
|
160
|
+
report["scope"],
|
|
161
|
+
)
|
|
162
|
+
self.assertEqual(["rust-tests"], [item["id"] for item in report["checks"]])
|
|
163
|
+
self.assertNotIn("diagnostic", report)
|
|
164
|
+
|
|
165
|
+
def test_duplicate_ids_reproduce_one_authoritative_position(self) -> None:
|
|
166
|
+
project = {
|
|
167
|
+
"profiles": {
|
|
168
|
+
"rust": [
|
|
169
|
+
{
|
|
170
|
+
"id": "rust-tests",
|
|
171
|
+
"run": [sys.executable, "-c", "raise SystemExit(0)"],
|
|
172
|
+
"timeoutSeconds": 10,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "rust-tests",
|
|
176
|
+
"run": [sys.executable, "-c", "raise SystemExit(101)"],
|
|
177
|
+
"timeoutSeconds": 10,
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
183
|
+
report = run_profile(Path(directory), project, "rust")
|
|
184
|
+
reproduced = run_profile(
|
|
185
|
+
Path(directory), project, "rust", check_position=2
|
|
186
|
+
)
|
|
187
|
+
self.assertEqual(2, report["diagnostic"]["position"])
|
|
188
|
+
self.assertEqual(["rust-tests"], [item["id"] for item in reproduced["checks"]])
|
|
189
|
+
self.assertEqual(101, reproduced["checks"][0]["exitCode"])
|
|
190
|
+
|
|
191
|
+
def test_selective_reproduction_rejects_unknown_position(self) -> None:
|
|
192
|
+
project = {
|
|
193
|
+
"profiles": {
|
|
194
|
+
"rust": [
|
|
195
|
+
{
|
|
196
|
+
"id": "rust-tests",
|
|
197
|
+
"run": [sys.executable, "-c", "raise SystemExit(0)"],
|
|
198
|
+
"timeoutSeconds": 10,
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
with tempfile.TemporaryDirectory() as directory, self.assertRaisesRegex(
|
|
204
|
+
ProcessError, "profile rust has no check at position: 2"
|
|
205
|
+
):
|
|
206
|
+
run_profile(Path(directory), project, "rust", check_position=2)
|
|
85
207
|
|
|
86
208
|
def test_timeout_is_a_failure(self) -> None:
|
|
87
209
|
with tempfile.TemporaryDirectory() as directory:
|
|
@@ -81,6 +81,62 @@ class ContractTests(unittest.TestCase):
|
|
|
81
81
|
with self.assertRaisesRegex(ProcessError, "Additional properties"):
|
|
82
82
|
validate_document(project, "project", schema_root=SCHEMAS)
|
|
83
83
|
|
|
84
|
+
def test_run_schema_accepts_legacy_and_safe_diagnostic_reports(self) -> None:
|
|
85
|
+
schema = read_json(SCHEMAS / "run.schema.json")
|
|
86
|
+
validator = Draft202012Validator(schema).evolve(
|
|
87
|
+
schema=schema["$defs"]["verificationReport"]
|
|
88
|
+
)
|
|
89
|
+
report = {
|
|
90
|
+
"profile": "rust",
|
|
91
|
+
"status": "failed",
|
|
92
|
+
"checks": [
|
|
93
|
+
{
|
|
94
|
+
"id": "rust-tests",
|
|
95
|
+
"status": "failed",
|
|
96
|
+
"exitCode": 101,
|
|
97
|
+
"timedOut": False,
|
|
98
|
+
"outputExceeded": False,
|
|
99
|
+
"descendantsTerminated": False,
|
|
100
|
+
"streamFailed": False,
|
|
101
|
+
"durationMs": 1,
|
|
102
|
+
"stdout": {
|
|
103
|
+
"bytes": 0,
|
|
104
|
+
"sha256": f"sha256:{'0' * 64}",
|
|
105
|
+
"truncated": False,
|
|
106
|
+
},
|
|
107
|
+
"stderr": {
|
|
108
|
+
"bytes": 0,
|
|
109
|
+
"sha256": f"sha256:{'0' * 64}",
|
|
110
|
+
"truncated": False,
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
],
|
|
114
|
+
"checkpoint": {
|
|
115
|
+
"head": "0" * 40,
|
|
116
|
+
"fingerprint": f"sha256:{'0' * 64}",
|
|
117
|
+
"fileCount": 1,
|
|
118
|
+
"byteCount": 1,
|
|
119
|
+
},
|
|
120
|
+
"recordedAt": "2026-09-07T00:00:00+00:00",
|
|
121
|
+
}
|
|
122
|
+
validator.validate(report)
|
|
123
|
+
report["scope"] = {"kind": "profile"}
|
|
124
|
+
report["diagnostic"] = {
|
|
125
|
+
"kind": "selective-check-reproduction",
|
|
126
|
+
"profile": "rust",
|
|
127
|
+
"check": "rust-tests",
|
|
128
|
+
"position": 1,
|
|
129
|
+
"command": [
|
|
130
|
+
"processctl",
|
|
131
|
+
"verify",
|
|
132
|
+
"--profile",
|
|
133
|
+
"rust",
|
|
134
|
+
"--check-position",
|
|
135
|
+
"1",
|
|
136
|
+
],
|
|
137
|
+
}
|
|
138
|
+
validator.validate(report)
|
|
139
|
+
|
|
84
140
|
def test_review_v6_requires_durable_non_blocking_dispositions(self) -> None:
|
|
85
141
|
review = {
|
|
86
142
|
"schemaVersion": 6,
|
|
@@ -256,6 +256,49 @@ class LifecycleTests(unittest.TestCase):
|
|
|
256
256
|
)
|
|
257
257
|
self.assertIsNone(lifecycle_status(self.root, PROCESS_ROOT, "sample-change")["nextCommand"])
|
|
258
258
|
|
|
259
|
+
def test_failed_profile_persists_only_safe_diagnostic_metadata(self) -> None:
|
|
260
|
+
self.begin()
|
|
261
|
+
secret = "TOPSECRET-LIFECYCLE-VALUE"
|
|
262
|
+
self.project["profiles"]["development"] = [
|
|
263
|
+
{
|
|
264
|
+
"id": "format",
|
|
265
|
+
"run": [sys.executable, "-c", "raise SystemExit(0)"],
|
|
266
|
+
"timeoutSeconds": 10,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
"id": "rust-tests",
|
|
270
|
+
"run": [
|
|
271
|
+
sys.executable,
|
|
272
|
+
"-c",
|
|
273
|
+
f"import sys; print({secret!r}, file=sys.stderr); raise SystemExit(101)",
|
|
274
|
+
],
|
|
275
|
+
"timeoutSeconds": 10,
|
|
276
|
+
},
|
|
277
|
+
]
|
|
278
|
+
state, report = verify_change(
|
|
279
|
+
self.root, PROCESS_ROOT, self.project, "sample-change", "development"
|
|
280
|
+
)
|
|
281
|
+
self.assertEqual("implementing", state["phase"])
|
|
282
|
+
self.assertEqual({"kind": "profile"}, report["scope"])
|
|
283
|
+
self.assertEqual("rust-tests", report["diagnostic"]["check"])
|
|
284
|
+
persisted = (
|
|
285
|
+
self.root / ".process" / "runs" / "sample-change" / "run.json"
|
|
286
|
+
).read_text(encoding="utf-8")
|
|
287
|
+
self.assertNotIn(secret, persisted)
|
|
288
|
+
self.assertNotIn(sys.executable, persisted)
|
|
289
|
+
|
|
290
|
+
def test_existing_run_without_diagnostic_fields_remains_readable(self) -> None:
|
|
291
|
+
self.begin()
|
|
292
|
+
verify_change(
|
|
293
|
+
self.root, PROCESS_ROOT, self.project, "sample-change", "development"
|
|
294
|
+
)
|
|
295
|
+
run_path = self.root / ".process" / "runs" / "sample-change" / "run.json"
|
|
296
|
+
run = json.loads(run_path.read_text(encoding="utf-8"))
|
|
297
|
+
run["verification"]["development"].pop("scope")
|
|
298
|
+
write_json(run_path, run)
|
|
299
|
+
state = lifecycle_status(self.root, PROCESS_ROOT, "sample-change")
|
|
300
|
+
self.assertEqual("implementing", state["phase"])
|
|
301
|
+
|
|
259
302
|
def test_plan_registration_requires_every_canonical_invariant(self) -> None:
|
|
260
303
|
start_change(
|
|
261
304
|
self.root,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_supervisor_contract.py
RENAMED
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_supervisor_posix.py
RENAMED
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/_supervisor_windows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/helper_launch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/publication_compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/requirements-dev.txt
RENAMED
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process/requirements-runtime.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/requires.txt
RENAMED
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/engineering_process.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-plan/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-review/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/process_assets/skills/change-start/SKILL.md
RENAMED
|
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
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/schemas/production-engineering.schema.json
RENAMED
|
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
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/templates/adopt-process-windows-job.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-2.0.0 → engineering_process-2.0.2}/tests/test_production_engineering.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|