engineering-process 1.0.0__tar.gz → 1.0.1__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.0.0/engineering_process.egg-info → engineering_process-1.0.1}/PKG-INFO +1 -1
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/__init__.py +1 -1
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/_supervisor_posix.py +24 -25
- {engineering_process-1.0.0 → engineering_process-1.0.1/engineering_process.egg-info}/PKG-INFO +1 -1
- {engineering_process-1.0.0 → engineering_process-1.0.1}/pyproject.toml +1 -1
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_architecture.py +1 -1
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_automation.py +39 -1
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_commands.py +28 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/LICENSE +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/MANIFEST.in +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/README.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/__main__.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/_windows_job.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/adoption.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/cli.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/commands.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/contracts.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/distribution.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/helper_launch.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/lifecycle.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/project.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/publication_compat.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/release.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/repository.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/skills.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/supervision.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/SOURCES.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process-graph.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/finish-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/implement-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/improve-process/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/plan-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/review-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/run-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/start-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/verify-change/SKILL.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/change.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/plan.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/process-graph.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/process-lock.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/project.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/receipt.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/release-change.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/release.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/review.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/schemas/run.schema.json +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/setup.cfg +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/templates/AGENTS.process.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/templates/adopt-process.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_adoption.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_cli.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_contracts.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_lifecycle.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_publication_compat.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_release.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_repository.py +0 -0
- {engineering_process-1.0.0 → engineering_process-1.0.1}/tests/test_skills.py +0 -0
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/_supervisor_posix.py
RENAMED
|
@@ -98,13 +98,24 @@ def _pid_alive(pid: int) -> bool:
|
|
|
98
98
|
status = Path(f"/proc/{pid}/stat")
|
|
99
99
|
if status.is_file():
|
|
100
100
|
try:
|
|
101
|
-
|
|
101
|
+
state = status.read_text(encoding="utf-8").rsplit(")", 1)[1].split()[0]
|
|
102
|
+
if state == "Z":
|
|
103
|
+
try:
|
|
104
|
+
os.waitpid(pid, os.WNOHANG)
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
return False
|
|
102
108
|
except (OSError, IndexError):
|
|
103
109
|
pass
|
|
104
110
|
return True
|
|
105
111
|
|
|
106
112
|
|
|
107
113
|
def _process_group_exists(process_group: int) -> bool:
|
|
114
|
+
try:
|
|
115
|
+
while os.waitpid(-process_group, os.WNOHANG)[0]:
|
|
116
|
+
pass
|
|
117
|
+
except OSError:
|
|
118
|
+
pass
|
|
108
119
|
try:
|
|
109
120
|
os.killpg(process_group, 0)
|
|
110
121
|
except ProcessLookupError:
|
|
@@ -114,6 +125,14 @@ def _process_group_exists(process_group: int) -> bool:
|
|
|
114
125
|
return True
|
|
115
126
|
|
|
116
127
|
|
|
128
|
+
def _signal_processes(processes: set[int], signal_number: int) -> None:
|
|
129
|
+
for pid in processes:
|
|
130
|
+
try:
|
|
131
|
+
os.kill(pid, signal_number)
|
|
132
|
+
except OSError:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
117
136
|
def _wait_for_process_group(process_group: int, timeout: float) -> bool:
|
|
118
137
|
deadline = time.monotonic() + timeout
|
|
119
138
|
while time.monotonic() < deadline:
|
|
@@ -144,11 +163,7 @@ def _terminate_group(process_group: int, grace_seconds: float) -> CleanupOutcome
|
|
|
144
163
|
return CleanupOutcome(
|
|
145
164
|
bounded=bounded,
|
|
146
165
|
descendants_found=True,
|
|
147
|
-
error=
|
|
148
|
-
None
|
|
149
|
-
if bounded
|
|
150
|
-
else "command process group could not be terminated within the bounded grace period"
|
|
151
|
-
),
|
|
166
|
+
error=None if bounded else "command process group exceeded its termination grace period",
|
|
152
167
|
)
|
|
153
168
|
|
|
154
169
|
|
|
@@ -250,24 +265,12 @@ class PosixProcessSupervisor:
|
|
|
250
265
|
live = {pid for pid in candidates if _pid_alive(pid)}
|
|
251
266
|
if not live:
|
|
252
267
|
return CleanupOutcome(bounded=True)
|
|
253
|
-
|
|
254
|
-
try:
|
|
255
|
-
os.kill(pid, signal.SIGTERM)
|
|
256
|
-
except ProcessLookupError:
|
|
257
|
-
pass
|
|
258
|
-
except OSError:
|
|
259
|
-
pass
|
|
268
|
+
_signal_processes(live, signal.SIGTERM)
|
|
260
269
|
deadline = time.monotonic() + grace_seconds
|
|
261
270
|
while time.monotonic() < deadline and any(_pid_alive(pid) for pid in live):
|
|
262
271
|
time.sleep(0.02)
|
|
263
272
|
survivors = {pid for pid in live if _pid_alive(pid)}
|
|
264
|
-
|
|
265
|
-
try:
|
|
266
|
-
os.kill(pid, signal.SIGKILL)
|
|
267
|
-
except ProcessLookupError:
|
|
268
|
-
pass
|
|
269
|
-
except OSError:
|
|
270
|
-
pass
|
|
273
|
+
_signal_processes(survivors, signal.SIGKILL)
|
|
271
274
|
deadline = time.monotonic() + grace_seconds
|
|
272
275
|
while time.monotonic() < deadline and any(
|
|
273
276
|
_pid_alive(pid) for pid in survivors
|
|
@@ -282,11 +285,7 @@ class PosixProcessSupervisor:
|
|
|
282
285
|
return CleanupOutcome(
|
|
283
286
|
bounded=bounded,
|
|
284
287
|
descendants_found=True,
|
|
285
|
-
error=
|
|
286
|
-
None
|
|
287
|
-
if bounded
|
|
288
|
-
else "detached descendants survived bounded termination"
|
|
289
|
-
),
|
|
288
|
+
error=None if bounded else "detached descendants survived bounded termination",
|
|
290
289
|
)
|
|
291
290
|
|
|
292
291
|
def terminate(
|
|
@@ -28,7 +28,7 @@ class ArchitectureTests(unittest.TestCase):
|
|
|
28
28
|
actual = {path.name for path in (ROOT / "engineering_process").glob("*.py")}
|
|
29
29
|
self.assertTrue(removed_modules.isdisjoint(actual))
|
|
30
30
|
workflows = {path.name for path in (ROOT / ".github" / "workflows").glob("*.yml")}
|
|
31
|
-
self.assertEqual({"ci.yml", "
|
|
31
|
+
self.assertEqual({"ci.yml", "publish.yml", "release-pr.yml"}, workflows)
|
|
32
32
|
|
|
33
33
|
def test_source_skills_have_no_orphaned_legacy_directories(self) -> None:
|
|
34
34
|
skills = {
|
|
@@ -43,7 +43,7 @@ class AutomationTests(unittest.TestCase):
|
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
def test_release_dispatches_the_published_version_to_the_control_plane(self) -> None:
|
|
46
|
-
workflow = (ROOT / ".github" / "workflows" / "
|
|
46
|
+
workflow = (ROOT / ".github" / "workflows" / "publish.yml").read_text(
|
|
47
47
|
encoding="utf-8"
|
|
48
48
|
)
|
|
49
49
|
self.assertIn("engineering-process-published", workflow)
|
|
@@ -54,6 +54,44 @@ class AutomationTests(unittest.TestCase):
|
|
|
54
54
|
self.assertIn("SOURCE_DATE_EPOCH", workflow)
|
|
55
55
|
self.assertIn("verification/normalize_sdist.py", workflow)
|
|
56
56
|
self.assertIn("points to a different commit", workflow)
|
|
57
|
+
self.assertIn("workflow_dispatch:", workflow)
|
|
58
|
+
self.assertIn("release_sha:", workflow)
|
|
59
|
+
self.assertIn('git merge-base --is-ancestor "$RELEASE_SOURCE_SHA" origin/main', workflow)
|
|
60
|
+
self.assertIn("needs.metadata.outputs.source_sha", workflow)
|
|
61
|
+
self.assertNotIn('--target "$GITHUB_SHA"', workflow)
|
|
62
|
+
self.assertEqual(
|
|
63
|
+
3,
|
|
64
|
+
workflow.count(
|
|
65
|
+
'path.suffix == ".whl" or path.name.endswith(".tar.gz")'
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
self.assertIn("for file in dist/*.whl dist/*.tar.gz; do", workflow)
|
|
69
|
+
self.assertNotIn("for file in dist/*; do", workflow)
|
|
70
|
+
publish_job = workflow.split(" publish:\n", maxsplit=1)[1].split(
|
|
71
|
+
"\n dispatch-adoption:\n", maxsplit=1
|
|
72
|
+
)[0]
|
|
73
|
+
self.assertIn("id: release-token", publish_job)
|
|
74
|
+
self.assertIn("repositories: engineering-process", publish_job)
|
|
75
|
+
self.assertIn("permission-contents: write", publish_job)
|
|
76
|
+
self.assertIn(
|
|
77
|
+
"GH_TOKEN: ${{ steps.release-token.outputs.token }}", publish_job
|
|
78
|
+
)
|
|
79
|
+
self.assertNotIn("GH_TOKEN: ${{ github.token }}", publish_job)
|
|
80
|
+
self.assertIn('"repos/$GITHUB_REPOSITORY/git/refs"', publish_job)
|
|
81
|
+
self.assertIn('-f ref="refs/tags/$RELEASE_TAG"', publish_job)
|
|
82
|
+
self.assertIn('-f sha="${{ needs.metadata.outputs.source_sha }}"', publish_job)
|
|
83
|
+
self.assertIn("--verify-tag", publish_job)
|
|
84
|
+
self.assertNotIn("--target", publish_job)
|
|
85
|
+
trusted_checkout = workflow.index(" ref: main")
|
|
86
|
+
preflight = workflow.index("name: Authorize release source from trusted main")
|
|
87
|
+
source_checkout = workflow.index("ref: ${{ steps.release.outputs.source_sha }}")
|
|
88
|
+
editable_install = workflow.index(
|
|
89
|
+
"python -m pip install -r engineering_process/requirements-runtime.txt "
|
|
90
|
+
"--editable ."
|
|
91
|
+
)
|
|
92
|
+
self.assertLess(trusted_checkout, preflight)
|
|
93
|
+
self.assertLess(preflight, source_checkout)
|
|
94
|
+
self.assertLess(source_checkout, editable_install)
|
|
57
95
|
|
|
58
96
|
def test_ci_checks_the_adopted_hash_locked_distribution_separately(self) -> None:
|
|
59
97
|
workflow = (ROOT / ".github" / "workflows" / "ci.yml").read_text(
|
|
@@ -111,6 +111,34 @@ class CommandTests(unittest.TestCase):
|
|
|
111
111
|
self.assertEqual("failed", report["status"])
|
|
112
112
|
self.assertTrue(report["descendantsTerminated"])
|
|
113
113
|
|
|
114
|
+
@unittest.skipUnless(
|
|
115
|
+
sys.platform.startswith("linux"), "Linux subreaper reaping assertion"
|
|
116
|
+
)
|
|
117
|
+
def test_exited_descendant_is_reaped_and_passes(self) -> None:
|
|
118
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
119
|
+
root = Path(directory)
|
|
120
|
+
pid_path = root / "child.pid"
|
|
121
|
+
child = "import time; time.sleep(0.01)"
|
|
122
|
+
script = (
|
|
123
|
+
"import pathlib, subprocess, sys, time; "
|
|
124
|
+
f"p=subprocess.Popen([sys.executable, '-c', {child!r}]); "
|
|
125
|
+
"pathlib.Path(sys.argv[1]).write_text(str(p.pid), encoding='utf-8'); "
|
|
126
|
+
"time.sleep(0.05)"
|
|
127
|
+
)
|
|
128
|
+
report = run_check(
|
|
129
|
+
root,
|
|
130
|
+
{
|
|
131
|
+
"id": "naturally-drained-descendant",
|
|
132
|
+
"run": [sys.executable, "-c", script, str(pid_path)],
|
|
133
|
+
"timeoutSeconds": 10,
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
pid = int(pid_path.read_text(encoding="utf-8"))
|
|
137
|
+
self.assertEqual("passed", report["status"])
|
|
138
|
+
self.assertFalse(report["descendantsTerminated"])
|
|
139
|
+
self.assertFalse(report["streamFailed"])
|
|
140
|
+
self.assertFalse(Path(f"/proc/{pid}").exists())
|
|
141
|
+
|
|
114
142
|
@unittest.skipUnless(
|
|
115
143
|
sys.platform.startswith("linux"), "Linux subreaper containment assertion"
|
|
116
144
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/_supervisor_windows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/helper_launch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/publication_compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/requirements-dev.txt
RENAMED
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process/requirements-runtime.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/requires.txt
RENAMED
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/engineering_process.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/finish-change/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/plan-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/review-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/run-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/start-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.0.0 → engineering_process-1.0.1}/process_assets/skills/verify-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.0.0 → engineering_process-1.0.1}/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
|