engineering-process 1.2.2__tar.gz → 1.2.4__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 (76) hide show
  1. {engineering_process-1.2.2/engineering_process.egg-info → engineering_process-1.2.4}/PKG-INFO +5 -1
  2. {engineering_process-1.2.2 → engineering_process-1.2.4}/README.md +4 -0
  3. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/__init__.py +1 -1
  4. {engineering_process-1.2.2 → engineering_process-1.2.4/engineering_process.egg-info}/PKG-INFO +5 -1
  5. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process.egg-info/SOURCES.txt +1 -0
  6. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/review-change/SKILL.md +25 -0
  7. {engineering_process-1.2.2 → engineering_process-1.2.4}/pyproject.toml +1 -1
  8. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_automation.py +27 -0
  9. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_lifecycle.py +28 -0
  10. engineering_process-1.2.4/tests/test_pypi_cache_horizon.py +81 -0
  11. {engineering_process-1.2.2 → engineering_process-1.2.4}/LICENSE +0 -0
  12. {engineering_process-1.2.2 → engineering_process-1.2.4}/MANIFEST.in +0 -0
  13. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/__main__.py +0 -0
  14. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/_supervisor_contract.py +0 -0
  15. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/_supervisor_posix.py +0 -0
  16. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/_supervisor_windows.py +0 -0
  17. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/_windows_job.py +0 -0
  18. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/adoption.py +0 -0
  19. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/cli.py +0 -0
  20. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/commands.py +0 -0
  21. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/contracts.py +0 -0
  22. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/distribution.py +0 -0
  23. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/helper_launch.py +0 -0
  24. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/lifecycle.py +0 -0
  25. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/production_engineering.py +0 -0
  26. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/project.py +0 -0
  27. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/publication_compat.py +0 -0
  28. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/release.py +0 -0
  29. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/repository.py +0 -0
  30. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/requirements-dev.txt +0 -0
  31. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/requirements-runtime.txt +0 -0
  32. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/skills.py +0 -0
  33. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process/supervision.py +0 -0
  34. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process.egg-info/dependency_links.txt +0 -0
  35. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process.egg-info/entry_points.txt +0 -0
  36. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process.egg-info/requires.txt +0 -0
  37. {engineering_process-1.2.2 → engineering_process-1.2.4}/engineering_process.egg-info/top_level.txt +0 -0
  38. {engineering_process-1.2.2 → engineering_process-1.2.4}/process-graph.json +0 -0
  39. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/finish-change/SKILL.md +0 -0
  40. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/implement-change/SKILL.md +0 -0
  41. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/improve-process/SKILL.md +0 -0
  42. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/plan-change/SKILL.md +0 -0
  43. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/production-engineering/SKILL.md +0 -0
  44. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/production-engineering/invariants.json +0 -0
  45. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/run-change/SKILL.md +0 -0
  46. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/start-change/SKILL.md +0 -0
  47. {engineering_process-1.2.2 → engineering_process-1.2.4}/process_assets/skills/verify-change/SKILL.md +0 -0
  48. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/change.schema.json +0 -0
  49. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/plan.schema.json +0 -0
  50. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/process-graph.schema.json +0 -0
  51. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/process-lock.schema.json +0 -0
  52. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/production-engineering.schema.json +0 -0
  53. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/project-legacy.schema.json +0 -0
  54. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/project.schema.json +0 -0
  55. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/receipt.schema.json +0 -0
  56. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/release-change.schema.json +0 -0
  57. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/release.schema.json +0 -0
  58. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/review.schema.json +0 -0
  59. {engineering_process-1.2.2 → engineering_process-1.2.4}/schemas/run.schema.json +0 -0
  60. {engineering_process-1.2.2 → engineering_process-1.2.4}/setup.cfg +0 -0
  61. {engineering_process-1.2.2 → engineering_process-1.2.4}/templates/AGENTS.process.md +0 -0
  62. {engineering_process-1.2.2 → engineering_process-1.2.4}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
  63. {engineering_process-1.2.2 → engineering_process-1.2.4}/templates/adopt-process-windows-job.py +0 -0
  64. {engineering_process-1.2.2 → engineering_process-1.2.4}/templates/adopt-process.py +0 -0
  65. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_adoption.py +0 -0
  66. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_architecture.py +0 -0
  67. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_cli.py +0 -0
  68. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_commands.py +0 -0
  69. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_contracts.py +0 -0
  70. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_distribution.py +0 -0
  71. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_production_engineering.py +0 -0
  72. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_publication_compat.py +0 -0
  73. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_release.py +0 -0
  74. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_repository.py +0 -0
  75. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_skills.py +0 -0
  76. {engineering_process-1.2.2 → engineering_process-1.2.4}/tests/test_supervisor_posix.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 1.2.2
3
+ Version: 1.2.4
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
@@ -352,6 +352,10 @@ URL; without it, the review remains pending. Earlier plan and review documents r
352
352
  readable, and their runs remain registrable or finishable with the version selected by
353
353
  the authority that started the relevant phase.
354
354
 
355
+ The [finding priority definitions](process_assets/skills/review-change/SKILL.md#finding-priority)
356
+ are the canonical P0-P3 impact convention for this process, including examples and
357
+ their relationship to blocking decisions.
358
+
355
359
  ## Running a change
356
360
 
357
361
  Create and validate a change contract, then register it:
@@ -328,6 +328,10 @@ URL; without it, the review remains pending. Earlier plan and review documents r
328
328
  readable, and their runs remain registrable or finishable with the version selected by
329
329
  the authority that started the relevant phase.
330
330
 
331
+ The [finding priority definitions](process_assets/skills/review-change/SKILL.md#finding-priority)
332
+ are the canonical P0-P3 impact convention for this process, including examples and
333
+ their relationship to blocking decisions.
334
+
331
335
  ## Running a change
332
336
 
333
337
  Create and validate a change contract, then register it:
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "1.2.2"
3
+ VERSION = "1.2.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 1.2.2
3
+ Version: 1.2.4
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
@@ -352,6 +352,10 @@ URL; without it, the review remains pending. Earlier plan and review documents r
352
352
  readable, and their runs remain registrable or finishable with the version selected by
353
353
  the authority that started the relevant phase.
354
354
 
355
+ The [finding priority definitions](process_assets/skills/review-change/SKILL.md#finding-priority)
356
+ are the canonical P0-P3 impact convention for this process, including examples and
357
+ their relationship to blocking decisions.
358
+
355
359
  ## Running a change
356
360
 
357
361
  Create and validate a change contract, then register it:
@@ -67,6 +67,7 @@ tests/test_distribution.py
67
67
  tests/test_lifecycle.py
68
68
  tests/test_production_engineering.py
69
69
  tests/test_publication_compat.py
70
+ tests/test_pypi_cache_horizon.py
70
71
  tests/test_release.py
71
72
  tests/test_repository.py
72
73
  tests/test_skills.py
@@ -62,3 +62,28 @@ Follow-up scope is only carried findings, remediation diffs, and regressions aga
62
62
  the frozen contract. A new blocker must be either remediation-caused or a P0/P1 late
63
63
  violation with a rationale. After two correction cycles, another changes-requested
64
64
  verdict blocks the change; it never turns into approval or permission to skip review.
65
+
66
+ ## Finding priority
67
+
68
+ P0-P3 are this process's review-impact convention. Assess the consequences if the
69
+ finding remains unresolved in a supported scenario. Consumer scheduling and incident
70
+ response deadlines remain consumer-owned.
71
+
72
+ | Priority | Impact | Illustrative example |
73
+ | --- | --- | --- |
74
+ | P0 - critical | Catastrophic loss of essential operation, authoritative data, or a critical security guarantee. | Irrecoverable loss of the only authoritative dataset. |
75
+ | P1 - high | Severe failure of a core workflow or guarantee, below P0's catastrophic consequences. | A primary workflow produces wrong results that can be regenerated from intact inputs. |
76
+ | P2 - medium | Material but contained incorrect behavior or operational or maintenance cost; core workflows remain usable. | A recoverable failure in a secondary workflow with a safe alternative. |
77
+ | P3 - low | Minor inconvenience or clarity or consistency defect with little effect on behavior or reliability. | An unclear optional explanation when all required steps remain unambiguous. |
78
+
79
+ Choose the highest level whose consequences the evidence supports. Explain affected
80
+ guarantees, reach, preconditions, recoverability, and safe alternatives. Examples are
81
+ illustrative, not an exhaustive classifier; file type, finding category, keywords,
82
+ and ease of repair do not determine priority.
83
+
84
+ Severity follows whether the reviewed snapshot violates the accepted contract or an
85
+ applicable production invariant; priority is not a waiver. A P2 or P3 finding can
86
+ therefore be blocking. The existing P0/P1 restriction on a new `critical-late` blocker
87
+ is an eligibility condition, not permission to block without the required bounded
88
+ late rationale. Preserve carried finding priorities and historical reports under the
89
+ existing correction-cycle rules.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-process"
7
- version = "1.2.2"
7
+ version = "1.2.4"
8
8
  description = "A small agent-neutral engineering lifecycle with managed adoption"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -105,6 +105,31 @@ class AutomationTests(unittest.TestCase):
105
105
  " dispatch-adoption:\n name: Dispatch consumer adoption\n", workflow
106
106
  )
107
107
  self.assertIn("needs: [metadata, publish]", workflow)
108
+ dispatch_job = workflow.split("\n dispatch-adoption:\n", maxsplit=1)[1]
109
+ self.assertIn(" timeout-minutes: 20\n", dispatch_job)
110
+ cache_wait = dispatch_job.split(
111
+ " - name: Wait for consumer Simple caches to expire\n", maxsplit=1
112
+ )[1].split(
113
+ " - name: Create short-lived Renovate event token\n", maxsplit=1
114
+ )[0]
115
+ self.assertIn(
116
+ "python verification/wait_for_pypi_cache_horizon.py",
117
+ cache_wait,
118
+ )
119
+ self.assertIn("${{ steps.release.outputs.published_at }}", cache_wait)
120
+ self.assertIn(
121
+ "ref: ${{ github.sha }}",
122
+ dispatch_job,
123
+ )
124
+ self.assertNotIn("ref: ${{ needs.metadata.outputs.source_sha }}", dispatch_job)
125
+ self.assertIn(
126
+ 'published_at=$(gh release view "$RELEASE_TAG"',
127
+ dispatch_job,
128
+ )
129
+ self.assertLess(
130
+ dispatch_job.index(" - name: Bind the published distribution\n"),
131
+ dispatch_job.index(" - name: Wait for consumer Simple caches to expire\n"),
132
+ )
108
133
  self.assertIn("Verify PyPI exposes the exact built hashes", workflow)
109
134
  visibility = workflow.split(
110
135
  " - name: Verify PyPI exposes the exact built hashes\n", maxsplit=1
@@ -142,6 +167,8 @@ class AutomationTests(unittest.TestCase):
142
167
  publish_job = workflow.split(" publish:\n", maxsplit=1)[1].split(
143
168
  "\n dispatch-adoption:\n", maxsplit=1
144
169
  )[0]
170
+ self.assertIn("ref: ${{ needs.metadata.outputs.source_sha }}", publish_job)
171
+ self.assertNotIn("ref: ${{ github.sha }}", publish_job)
145
172
  self.assertIn("id: release-token", publish_job)
146
173
  self.assertIn("repositories: engineering-process", publish_job)
147
174
  self.assertIn("permission-contents: write", publish_job)
@@ -793,6 +793,34 @@ class LifecycleTests(unittest.TestCase):
793
793
  (self.root / ".process" / "runs" / "sample-change").exists()
794
794
  )
795
795
 
796
+ def test_live_producer_policy_requires_its_accepted_issue(self) -> None:
797
+ project = json.loads((PROCESS_ROOT / ".process" / "project.json").read_text())
798
+ self.project["lifecycle"]["processChanges"] = project["lifecycle"]["processChanges"]
799
+ self.contract["consumerEvidence"] = [
800
+ {"repository": "engineering-process", "incident": "Deferred issue handoff enablement."}
801
+ ]
802
+ for source in (
803
+ "issue-110",
804
+ "https://github.com/other/process/issues/110",
805
+ "https://github.com/phuongnse/engineering-process/pull/118",
806
+ ):
807
+ with self.subTest(source=source):
808
+ self.contract["source"] = source
809
+ write_json(self.contract_path, self.contract)
810
+ with self.assertRaisesRegex(ProcessError, "numbered issue"):
811
+ start_change(
812
+ self.root, PROCESS_ROOT, self.project, self.contract_path,
813
+ actor_id="author", context_id="author-context", kind="agent",
814
+ )
815
+ self.assertFalse((self.root / ".process" / "runs").exists())
816
+ self.contract["source"] = "https://github.com/phuongnse/engineering-process/issues/110"
817
+ write_json(self.contract_path, self.contract)
818
+ state = start_change(
819
+ self.root, PROCESS_ROOT, self.project, self.contract_path,
820
+ actor_id="author", context_id="author-context", kind="agent",
821
+ )
822
+ self.assertEqual("specified", state["phase"])
823
+
796
824
  def test_evidence_only_process_change_policy_remains_compatible(self) -> None:
797
825
  self.project["lifecycle"]["processChanges"] = {
798
826
  "requireConsumerEvidence": True
@@ -0,0 +1,81 @@
1
+ from datetime import datetime, timezone
2
+ from email.message import Message
3
+ import unittest
4
+ from unittest.mock import patch
5
+
6
+ from verification.wait_for_pypi_cache_horizon import (
7
+ MAX_SIMPLE_CACHE_SECONDS,
8
+ read_simple_max_age,
9
+ parse_max_age,
10
+ remaining_seconds,
11
+ )
12
+
13
+
14
+ class _Response:
15
+ def __init__(self, headers: Message) -> None:
16
+ self.headers = headers
17
+
18
+ def __enter__(self) -> "_Response":
19
+ return self
20
+
21
+ def __exit__(self, *args: object) -> None:
22
+ return None
23
+
24
+
25
+ class PyPICacheHorizonTests(unittest.TestCase):
26
+ NOW = datetime(2026, 9, 4, 13, 30, tzinfo=timezone.utc)
27
+
28
+ def test_parses_current_bounded_header(self) -> None:
29
+ self.assertEqual(600, parse_max_age(["max-age=600, public"]))
30
+ self.assertEqual(
31
+ MAX_SIMPLE_CACHE_SECONDS,
32
+ parse_max_age([f"public, max-age={MAX_SIMPLE_CACHE_SECONDS}"]),
33
+ )
34
+
35
+ def test_rejects_missing_duplicate_malformed_and_excessive_max_age(self) -> None:
36
+ invalid = [
37
+ None,
38
+ ["public"],
39
+ ["max-age=600", "max-age=300"],
40
+ ["max-age=-1"],
41
+ ["max-age=invalid"],
42
+ [f"max-age={MAX_SIMPLE_CACHE_SECONDS + 1}"],
43
+ ]
44
+ for values in invalid:
45
+ with self.subTest(values=values), self.assertRaises(RuntimeError):
46
+ parse_max_age(values)
47
+
48
+ def test_reads_every_cache_control_field(self) -> None:
49
+ headers = Message()
50
+ headers["Content-Type"] = "application/vnd.pypi.simple.v1+json"
51
+ headers["Cache-Control"] = "public, max-age=600"
52
+ headers["Cache-Control"] = "max-age=300"
53
+ with patch(
54
+ "verification.wait_for_pypi_cache_horizon.urlopen",
55
+ return_value=_Response(headers),
56
+ ), self.assertRaises(RuntimeError):
57
+ read_simple_max_age()
58
+
59
+ def test_current_horizon_and_elapsed_resume(self) -> None:
60
+ self.assertEqual(
61
+ 305,
62
+ remaining_seconds("2026-09-04T13:20:00Z", 900, now=self.NOW),
63
+ )
64
+ self.assertEqual(
65
+ 0,
66
+ remaining_seconds("2026-09-04T13:00:00Z", 600, now=self.NOW),
67
+ )
68
+
69
+ def test_rejects_malformed_naive_and_future_timestamps(self) -> None:
70
+ invalid = [
71
+ "invalid",
72
+ "2026-09-04T13:20:00",
73
+ "2026-09-04T13:31:01Z",
74
+ ]
75
+ for published_at in invalid:
76
+ with self.subTest(published_at=published_at), self.assertRaises(RuntimeError):
77
+ remaining_seconds(published_at, 600, now=self.NOW)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ unittest.main()