engineering-process 1.1.1__tar.gz → 1.1.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-1.1.1/engineering_process.egg-info → engineering_process-1.1.2}/PKG-INFO +1 -1
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/__init__.py +1 -1
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/commands.py +1 -1
- {engineering_process-1.1.1 → engineering_process-1.1.2/engineering_process.egg-info}/PKG-INFO +1 -1
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/verify-change/SKILL.md +4 -3
- {engineering_process-1.1.1 → engineering_process-1.1.2}/pyproject.toml +1 -1
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_commands.py +61 -6
- {engineering_process-1.1.1 → engineering_process-1.1.2}/LICENSE +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/MANIFEST.in +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/README.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/__main__.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/_windows_job.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/adoption.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/cli.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/contracts.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/distribution.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/helper_launch.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/lifecycle.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/project.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/publication_compat.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/release.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/repository.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/skills.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/supervision.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/SOURCES.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process-graph.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/finish-change/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/implement-change/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/improve-process/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/plan-change/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/review-change/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/run-change/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/start-change/SKILL.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/change.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/plan.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/process-graph.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/process-lock.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/project.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/receipt.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/release-change.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/release.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/review.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/schemas/run.schema.json +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/setup.cfg +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/templates/AGENTS.process.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/templates/adopt-process.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_adoption.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_architecture.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_automation.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_cli.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_contracts.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_lifecycle.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_publication_compat.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_release.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_repository.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_skills.py +0 -0
- {engineering_process-1.1.1 → engineering_process-1.1.2}/tests/test_supervisor_posix.py +0 -0
|
@@ -155,6 +155,7 @@ def _run_check(project_root: Path, check: dict[str, Any]) -> dict[str, Any]:
|
|
|
155
155
|
)
|
|
156
156
|
descendants = descendants or cleanup.descendants_found
|
|
157
157
|
cleanup_error = cleanup_error or cleanup.error
|
|
158
|
+
cleanup_error = cleanup_error or (None if cleanup.bounded else "unbounded cleanup")
|
|
158
159
|
exit_code = process.returncode if process.returncode is not None else -1
|
|
159
160
|
|
|
160
161
|
for reader in readers:
|
|
@@ -173,7 +174,6 @@ def _run_check(project_root: Path, check: dict[str, Any]) -> dict[str, Any]:
|
|
|
173
174
|
exit_code == 0
|
|
174
175
|
and not timed_out
|
|
175
176
|
and not output_exceeded
|
|
176
|
-
and not descendants
|
|
177
177
|
and not stream_failed
|
|
178
178
|
)
|
|
179
179
|
return {
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/verify-change/SKILL.md
RENAMED
|
@@ -11,9 +11,10 @@ profile through the lifecycle:
|
|
|
11
11
|
processctl change verify --change-id ID --profile PROFILE
|
|
12
12
|
|
|
13
13
|
Commands are exact argument arrays with timeouts. Do not substitute a different tool
|
|
14
|
-
or narrower check when a required command fails. A command failure, timeout,
|
|
15
|
-
|
|
16
|
-
change in implementing.
|
|
14
|
+
or narrower check when a required command fails. A command failure, timeout, output
|
|
15
|
+
or stream failure, failed descendant cleanup, or tracked repository mutation is a
|
|
16
|
+
failure and leaves the change in implementing. Successfully cleaned post-exit
|
|
17
|
+
descendants remain recorded without replacing the foreground command result.
|
|
17
18
|
|
|
18
19
|
The contract must already include conditional profiles required by affected enforced
|
|
19
20
|
capabilities. Run those profiles exactly; do not run every planned production gate for
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import ctypes
|
|
4
|
+
import io
|
|
3
5
|
import os
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
import subprocess
|
|
@@ -7,8 +9,38 @@ import sys
|
|
|
7
9
|
import tempfile
|
|
8
10
|
import time
|
|
9
11
|
import unittest
|
|
12
|
+
from unittest.mock import Mock, patch
|
|
10
13
|
|
|
11
14
|
from engineering_process.commands import run_check, run_profile
|
|
15
|
+
from engineering_process.supervision import CleanupOutcome
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def windows_process_is_running(process_id: int) -> bool:
|
|
19
|
+
from ctypes import wintypes
|
|
20
|
+
|
|
21
|
+
synchronize = 0x00100000
|
|
22
|
+
wait_failed = 0xFFFFFFFF
|
|
23
|
+
wait_timeout = 0x00000102
|
|
24
|
+
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
|
25
|
+
kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
|
|
26
|
+
kernel32.OpenProcess.restype = wintypes.HANDLE
|
|
27
|
+
kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
|
|
28
|
+
kernel32.WaitForSingleObject.restype = wintypes.DWORD
|
|
29
|
+
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
|
|
30
|
+
kernel32.CloseHandle.restype = wintypes.BOOL
|
|
31
|
+
handle = kernel32.OpenProcess(synchronize, False, process_id)
|
|
32
|
+
if not handle:
|
|
33
|
+
error = ctypes.get_last_error()
|
|
34
|
+
if error == 87: # ERROR_INVALID_PARAMETER: the PID no longer exists.
|
|
35
|
+
return False
|
|
36
|
+
raise ctypes.WinError(error)
|
|
37
|
+
try:
|
|
38
|
+
result = kernel32.WaitForSingleObject(handle, 0)
|
|
39
|
+
if result == wait_failed:
|
|
40
|
+
raise ctypes.WinError(ctypes.get_last_error())
|
|
41
|
+
return result == wait_timeout
|
|
42
|
+
finally:
|
|
43
|
+
kernel32.CloseHandle(handle)
|
|
12
44
|
|
|
13
45
|
|
|
14
46
|
class CommandTests(unittest.TestCase):
|
|
@@ -64,6 +96,27 @@ class CommandTests(unittest.TestCase):
|
|
|
64
96
|
self.assertEqual("failed", report["status"])
|
|
65
97
|
self.assertTrue(report["timedOut"])
|
|
66
98
|
|
|
99
|
+
def test_unbounded_cleanup_fails_without_an_adapter_error(self) -> None:
|
|
100
|
+
process = Mock(stdout=io.BytesIO(), stderr=io.BytesIO(), returncode=0)
|
|
101
|
+
process.poll.return_value = 0
|
|
102
|
+
supervisor = Mock()
|
|
103
|
+
supervisor.spawn.return_value = process
|
|
104
|
+
supervisor.finalize.return_value = CleanupOutcome(bounded=False)
|
|
105
|
+
with tempfile.TemporaryDirectory() as directory, patch(
|
|
106
|
+
"engineering_process.commands.process_supervisor",
|
|
107
|
+
return_value=supervisor,
|
|
108
|
+
):
|
|
109
|
+
report = run_check(
|
|
110
|
+
Path(directory),
|
|
111
|
+
{
|
|
112
|
+
"id": "unbounded-cleanup",
|
|
113
|
+
"run": [sys.executable, "-c", "raise SystemExit(0)"],
|
|
114
|
+
"timeoutSeconds": 10,
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
self.assertEqual("failed", report["status"])
|
|
118
|
+
self.assertTrue(report["streamFailed"])
|
|
119
|
+
|
|
67
120
|
@unittest.skipUnless(
|
|
68
121
|
sys.platform.startswith("linux"), "Linux subreaper ownership assertion"
|
|
69
122
|
)
|
|
@@ -93,7 +146,7 @@ class CommandTests(unittest.TestCase):
|
|
|
93
146
|
unrelated.wait(timeout=3)
|
|
94
147
|
|
|
95
148
|
@unittest.skipIf(os.name == "nt", "POSIX process-group assertion")
|
|
96
|
-
def
|
|
149
|
+
def test_surviving_descendant_is_terminated_after_success(self) -> None:
|
|
97
150
|
script = (
|
|
98
151
|
"import subprocess, sys; "
|
|
99
152
|
"subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(30)']); "
|
|
@@ -108,7 +161,7 @@ class CommandTests(unittest.TestCase):
|
|
|
108
161
|
"timeoutSeconds": 10,
|
|
109
162
|
},
|
|
110
163
|
)
|
|
111
|
-
self.assertEqual("
|
|
164
|
+
self.assertEqual("passed", report["status"])
|
|
112
165
|
self.assertTrue(report["descendantsTerminated"])
|
|
113
166
|
|
|
114
167
|
@unittest.skipUnless(
|
|
@@ -142,7 +195,7 @@ class CommandTests(unittest.TestCase):
|
|
|
142
195
|
@unittest.skipUnless(
|
|
143
196
|
sys.platform.startswith("linux"), "Linux subreaper containment assertion"
|
|
144
197
|
)
|
|
145
|
-
def
|
|
198
|
+
def test_detached_descendant_is_terminated_after_success(self) -> None:
|
|
146
199
|
with tempfile.TemporaryDirectory() as directory:
|
|
147
200
|
root = Path(directory)
|
|
148
201
|
pid_path = root / "child.pid"
|
|
@@ -166,12 +219,12 @@ class CommandTests(unittest.TestCase):
|
|
|
166
219
|
deadline = time.monotonic() + 3
|
|
167
220
|
while Path(f"/proc/{pid}").exists() and time.monotonic() < deadline:
|
|
168
221
|
time.sleep(0.02)
|
|
169
|
-
self.assertEqual("
|
|
222
|
+
self.assertEqual("passed", report["status"])
|
|
170
223
|
self.assertTrue(report["descendantsTerminated"])
|
|
171
224
|
self.assertFalse(Path(f"/proc/{pid}").exists())
|
|
172
225
|
|
|
173
226
|
@unittest.skipUnless(os.name == "nt", "Windows Job Object containment assertion")
|
|
174
|
-
def
|
|
227
|
+
def test_windows_job_object_terminates_descendants_after_success(self) -> None:
|
|
175
228
|
with tempfile.TemporaryDirectory() as directory:
|
|
176
229
|
root = Path(directory)
|
|
177
230
|
pid_path = root / "child.pid"
|
|
@@ -189,8 +242,10 @@ class CommandTests(unittest.TestCase):
|
|
|
189
242
|
"timeoutSeconds": 10,
|
|
190
243
|
},
|
|
191
244
|
)
|
|
192
|
-
|
|
245
|
+
pid = int(pid_path.read_text(encoding="utf-8"))
|
|
246
|
+
self.assertEqual("passed", report["status"])
|
|
193
247
|
self.assertTrue(report["descendantsTerminated"])
|
|
248
|
+
self.assertFalse(windows_process_is_running(pid))
|
|
194
249
|
|
|
195
250
|
|
|
196
251
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/_supervisor_posix.py
RENAMED
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/_supervisor_windows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/helper_launch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/publication_compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/requirements-dev.txt
RENAMED
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process/requirements-runtime.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/requires.txt
RENAMED
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/engineering_process.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/finish-change/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/plan-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/review-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/run-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.2}/process_assets/skills/start-change/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.1.1 → engineering_process-1.1.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|