engineering-process 1.2.4__tar.gz → 1.2.5__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.4/engineering_process.egg-info → engineering_process-1.2.5}/PKG-INFO +9 -1
  2. {engineering_process-1.2.4 → engineering_process-1.2.5}/README.md +8 -0
  3. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/__init__.py +1 -1
  4. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/production_engineering.py +6 -1
  5. {engineering_process-1.2.4 → engineering_process-1.2.5/engineering_process.egg-info}/PKG-INFO +9 -1
  6. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/production-engineering/SKILL.md +5 -0
  7. {engineering_process-1.2.4 → engineering_process-1.2.5}/pyproject.toml +1 -1
  8. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_lifecycle.py +37 -0
  9. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_production_engineering.py +25 -0
  10. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_release.py +43 -0
  11. {engineering_process-1.2.4 → engineering_process-1.2.5}/LICENSE +0 -0
  12. {engineering_process-1.2.4 → engineering_process-1.2.5}/MANIFEST.in +0 -0
  13. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/__main__.py +0 -0
  14. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/_supervisor_contract.py +0 -0
  15. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/_supervisor_posix.py +0 -0
  16. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/_supervisor_windows.py +0 -0
  17. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/_windows_job.py +0 -0
  18. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/adoption.py +0 -0
  19. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/cli.py +0 -0
  20. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/commands.py +0 -0
  21. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/contracts.py +0 -0
  22. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/distribution.py +0 -0
  23. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/helper_launch.py +0 -0
  24. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/lifecycle.py +0 -0
  25. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/project.py +0 -0
  26. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/publication_compat.py +0 -0
  27. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/release.py +0 -0
  28. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/repository.py +0 -0
  29. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/requirements-dev.txt +0 -0
  30. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/requirements-runtime.txt +0 -0
  31. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/skills.py +0 -0
  32. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process/supervision.py +0 -0
  33. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process.egg-info/SOURCES.txt +0 -0
  34. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process.egg-info/dependency_links.txt +0 -0
  35. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process.egg-info/entry_points.txt +0 -0
  36. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process.egg-info/requires.txt +0 -0
  37. {engineering_process-1.2.4 → engineering_process-1.2.5}/engineering_process.egg-info/top_level.txt +0 -0
  38. {engineering_process-1.2.4 → engineering_process-1.2.5}/process-graph.json +0 -0
  39. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/finish-change/SKILL.md +0 -0
  40. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/implement-change/SKILL.md +0 -0
  41. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/improve-process/SKILL.md +0 -0
  42. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/plan-change/SKILL.md +0 -0
  43. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/production-engineering/invariants.json +0 -0
  44. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/review-change/SKILL.md +0 -0
  45. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/run-change/SKILL.md +0 -0
  46. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/start-change/SKILL.md +0 -0
  47. {engineering_process-1.2.4 → engineering_process-1.2.5}/process_assets/skills/verify-change/SKILL.md +0 -0
  48. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/change.schema.json +0 -0
  49. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/plan.schema.json +0 -0
  50. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/process-graph.schema.json +0 -0
  51. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/process-lock.schema.json +0 -0
  52. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/production-engineering.schema.json +0 -0
  53. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/project-legacy.schema.json +0 -0
  54. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/project.schema.json +0 -0
  55. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/receipt.schema.json +0 -0
  56. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/release-change.schema.json +0 -0
  57. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/release.schema.json +0 -0
  58. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/review.schema.json +0 -0
  59. {engineering_process-1.2.4 → engineering_process-1.2.5}/schemas/run.schema.json +0 -0
  60. {engineering_process-1.2.4 → engineering_process-1.2.5}/setup.cfg +0 -0
  61. {engineering_process-1.2.4 → engineering_process-1.2.5}/templates/AGENTS.process.md +0 -0
  62. {engineering_process-1.2.4 → engineering_process-1.2.5}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
  63. {engineering_process-1.2.4 → engineering_process-1.2.5}/templates/adopt-process-windows-job.py +0 -0
  64. {engineering_process-1.2.4 → engineering_process-1.2.5}/templates/adopt-process.py +0 -0
  65. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_adoption.py +0 -0
  66. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_architecture.py +0 -0
  67. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_automation.py +0 -0
  68. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_cli.py +0 -0
  69. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_commands.py +0 -0
  70. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_contracts.py +0 -0
  71. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_distribution.py +0 -0
  72. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_publication_compat.py +0 -0
  73. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_pypi_cache_horizon.py +0 -0
  74. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_repository.py +0 -0
  75. {engineering_process-1.2.4 → engineering_process-1.2.5}/tests/test_skills.py +0 -0
  76. {engineering_process-1.2.4 → engineering_process-1.2.5}/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.4
3
+ Version: 1.2.5
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
@@ -441,6 +441,14 @@ At any point:
441
441
 
442
442
  ## Release to consumer PR
443
443
 
444
+ This producer's release identity inputs and text assets declared by
445
+ `tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
446
+ Writers select that representation explicitly; JSON writers use
447
+ `write_json_atomic`. Existing distribution verification checks the declared text
448
+ bytes before building. It rejects CRLF, mixed endings, BOM and invalid UTF-8 even
449
+ when text-mode reads or Git conversion would hide the difference. Binary data and
450
+ fixtures outside that text inventory retain their own format contracts.
451
+
444
452
  Every opted-in consumer uses Renovate's pip-compile manager. Its engineering-process
445
453
  package rule keeps the adoption pull request in draft and runs exactly:
446
454
 
@@ -417,6 +417,14 @@ At any point:
417
417
 
418
418
  ## Release to consumer PR
419
419
 
420
+ This producer's release identity inputs and text assets declared by
421
+ `tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
422
+ Writers select that representation explicitly; JSON writers use
423
+ `write_json_atomic`. Existing distribution verification checks the declared text
424
+ bytes before building. It rejects CRLF, mixed endings, BOM and invalid UTF-8 even
425
+ when text-mode reads or Git conversion would hide the difference. Binary data and
426
+ fixtures outside that text inventory retain their own format contracts.
427
+
420
428
  Every opted-in consumer uses Renovate's pip-compile manager. Its engineering-process
421
429
  package rule keeps the adoption pull request in draft and runs exactly:
422
430
 
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "1.2.4"
3
+ VERSION = "1.2.5"
@@ -98,7 +98,12 @@ def validate_review_assessments(review: dict[str, Any], process_root: Path) -> N
98
98
  unlinked = sorted(
99
99
  finding_id
100
100
  for finding_id, finding in findings.items()
101
- if finding["origin"] == "production-invariant" and finding_id not in linked
101
+ if finding["origin"] == "production-invariant"
102
+ and finding_id not in linked
103
+ and not (
104
+ finding["severity"] == "non-blocking"
105
+ and finding.get("disposition", {}).get("status") == "resolved"
106
+ )
102
107
  )
103
108
  if unlinked:
104
109
  raise ProcessError(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 1.2.4
3
+ Version: 1.2.5
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
@@ -441,6 +441,14 @@ At any point:
441
441
 
442
442
  ## Release to consumer PR
443
443
 
444
+ This producer's release identity inputs and text assets declared by
445
+ `tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
446
+ Writers select that representation explicitly; JSON writers use
447
+ `write_json_atomic`. Existing distribution verification checks the declared text
448
+ bytes before building. It rejects CRLF, mixed endings, BOM and invalid UTF-8 even
449
+ when text-mode reads or Git conversion would hide the difference. Binary data and
450
+ fixtures outside that text inventory retain their own format contracts.
451
+
444
452
  Every opted-in consumer uses Renovate's pip-compile manager. Its engineering-process
445
453
  package rule keeps the adoption pull request in draft and runs exactly:
446
454
 
@@ -29,6 +29,11 @@ Verification proves only the deterministic properties exercised by its exact
29
29
  commands on the unchanged candidate. It must not claim to infer architecture,
30
30
  security, intent, or arbitrary prose semantics that those commands do not observe.
31
31
 
32
+ When an artifact claim depends on byte identity, make encoding and newline rules
33
+ explicit at its writer and compare bytes at the exported or installed boundary.
34
+ Text-mode newline normalization and a clean Git diff cannot supply that proof;
35
+ follow the artifact owner's format rather than imposing one text policy on consumers.
36
+
32
37
  Independent review reassesses every invariant against the contract, plan, complete
33
38
  diff, profile evidence, and readiness result. Mark it `satisfied`, `not-applicable`,
34
39
  or `violated`, give a concrete rationale, and cite evidence for `satisfied`. A
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-process"
7
- version = "1.2.4"
7
+ version = "1.2.5"
8
8
  description = "A small agent-neutral engineering lifecycle with managed adoption"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -599,6 +599,43 @@ class LifecycleTests(unittest.TestCase):
599
599
  self.assertEqual("implementing", state["phase"])
600
600
  self.assertEqual({}, state["verification"])
601
601
 
602
+ def test_resolved_invariant_finishes_with_its_identity_and_history_preserved(self) -> None:
603
+ self.begin()
604
+ self.verify_all()
605
+ start_review(self.root, PROCESS_ROOT, "sample-change", actor_id="reviewer", context_id="review-context", kind="agent")
606
+ first = self.review_document("changes-requested")
607
+ first["findings"][0]["priority"] = "P2"
608
+ first_path = self.root / ".process" / "runs" / "first-review.json"
609
+ write_json(first_path, first)
610
+ state = submit_review(self.root, PROCESS_ROOT, "sample-change", first_path)
611
+ self.assertEqual("changes-requested", state["phase"])
612
+
613
+ begin_implementation(self.root, PROCESS_ROOT, "sample-change", actor_id="implementer", context_id="implementation-context-2", kind="agent")
614
+ (self.root / "product.txt").write_text("corrected\n", encoding="utf-8")
615
+ self.verify_all()
616
+ start_review(self.root, PROCESS_ROOT, "sample-change", actor_id="reviewer", context_id="review-context", kind="agent")
617
+ approved = self.review_document("approved")
618
+ carried = deepcopy(first["findings"][0])
619
+ carried["severity"] = "non-blocking"
620
+ carried["disposition"] = {"status": "resolved", "rationale": "The corrected candidate satisfies the invariant with fresh verification."}
621
+ approved["findings"] = [carried]
622
+ second_path = self.root / ".process" / "runs" / "second-review.json"
623
+ for field, changed in (("priority", "P3"), ("origin", "contract")):
624
+ invalid = deepcopy(approved)
625
+ invalid["findings"][0][field] = changed
626
+ write_json(second_path, invalid)
627
+ with self.subTest(field=field), self.assertRaisesRegex(ProcessError, "identity fields are immutable"):
628
+ submit_review(self.root, PROCESS_ROOT, "sample-change", second_path)
629
+ write_json(second_path, approved)
630
+ state = submit_review(self.root, PROCESS_ROOT, "sample-change", second_path)
631
+ self.assertEqual("approved", state["phase"])
632
+ self.assertEqual(first, state["reviewHistory"][0]["document"])
633
+ self.assertEqual(approved, state["reviewHistory"][1]["document"])
634
+ state, receipt = finish_change(self.root, PROCESS_ROOT, "sample-change", actor_id="coordinator", context_id="finish-context", kind="agent")
635
+ self.assertEqual("completed", state["phase"])
636
+ self.assertEqual(2, state["cycle"])
637
+ self.assertEqual("approved", receipt["review"]["verdict"])
638
+
602
639
  def test_correction_review_requires_the_original_reviewer_identity(self) -> None:
603
640
  self.begin()
604
641
  self.verify_all()
@@ -190,6 +190,31 @@ class ProductionEngineeringTests(unittest.TestCase):
190
190
  with self.assertRaisesRegex(ProcessError, "blocking finding"):
191
191
  validate_review_assessments(violation, ROOT)
192
192
 
193
+ def test_resolved_invariant_findings_do_not_require_a_current_violation(self) -> None:
194
+ review = deepcopy(self.review)
195
+ review["findings"] = [{
196
+ "id": "resolved-invariant",
197
+ "severity": "non-blocking",
198
+ "priority": "P2",
199
+ "criterionId": "works",
200
+ "origin": "production-invariant",
201
+ "summary": "The previous invariant violation is resolved on this snapshot.",
202
+ "disposition": {"status": "resolved", "rationale": "The corrected structure and current evidence satisfy the invariant."},
203
+ }]
204
+ validate_document(review, "review", schema_root=SCHEMAS)
205
+ validate_review_assessments(review, ROOT)
206
+ for status in ("accepted-risk", "tracked-follow-up"):
207
+ with self.subTest(status=status):
208
+ review["findings"][0]["disposition"] = {
209
+ "status": status,
210
+ "rationale": "The invariant violation remains open.",
211
+ "owner": "consumer-owner",
212
+ "recordUrl": "https://example.invalid/issues/1",
213
+ }
214
+ validate_document(review, "review", schema_root=SCHEMAS)
215
+ with self.assertRaisesRegex(ProcessError, "must be linked"):
216
+ validate_review_assessments(review, ROOT)
217
+
193
218
  def test_review_cannot_approve_a_violation_or_leave_invariant_finding_unlinked(self) -> None:
194
219
  violation = deepcopy(self.review)
195
220
  assessment = violation["productionEngineering"][0]
@@ -12,12 +12,54 @@ import unittest
12
12
  from engineering_process.contracts import ProcessError, read_json, write_json_atomic
13
13
  from engineering_process.release import derive_next_version, validate_release
14
14
  from verification.normalize_sdist import normalize
15
+ from verification.prepare_release import _replace_once
16
+ from verification.verify_distribution import validate_distribution_text
15
17
 
16
18
 
17
19
  ROOT = Path(__file__).resolve().parent.parent
18
20
 
19
21
 
20
22
  class ReleaseTests(unittest.TestCase):
23
+ def test_release_replacement_writes_explicit_lf_on_every_host(self) -> None:
24
+ with tempfile.TemporaryDirectory() as directory:
25
+ target = Path(directory) / "version.txt"
26
+ for source in (b"version=old\nnext\n", b"version=old\r\nnext\r\n", b"version=old\r\nnext\n"):
27
+ with self.subTest(source=source):
28
+ target.write_bytes(source)
29
+ _replace_once(target, "old", "new")
30
+ self.assertEqual(b"version=new\nnext\n", target.read_bytes())
31
+
32
+ def test_release_replacement_rejects_bom_before_writing(self) -> None:
33
+ with tempfile.TemporaryDirectory() as directory:
34
+ target = Path(directory) / "version.txt"
35
+ original = b'\xef\xbb\xbfVERSION = "old"\r\n'
36
+ target.write_bytes(original)
37
+ with self.assertRaisesRegex(ProcessError, "UTF-8 without BOM"):
38
+ _replace_once(target, "old", "new")
39
+ self.assertEqual(original, target.read_bytes())
40
+ self.assertFalse(target.with_name(f".{target.name}.release.tmp").exists())
41
+
42
+ def test_distribution_text_uses_the_declared_inventory_and_rejects_byte_drift(self) -> None:
43
+ with tempfile.TemporaryDirectory() as directory:
44
+ root = Path(directory)
45
+ (root / "engineering_process").mkdir()
46
+ (root / "assets").mkdir()
47
+ (root / "pyproject.toml").write_bytes(b'[tool.setuptools.data-files]\n"share/process" = ["assets/newly-declared.fragment"]\n')
48
+ (root / "release.json").write_bytes(b"{}\n")
49
+ (root / "engineering_process" / "__init__.py").write_bytes(b'VERSION = "1.0.0"\n')
50
+ (root / "opaque.bin").write_bytes(b"\x00\xff\r\n")
51
+ target = root / "assets" / "newly-declared.fragment"
52
+ valid = b"first\nsecond\n"
53
+ target.write_bytes(valid)
54
+ validate_distribution_text(root)
55
+ for invalid in (b"first\r\nsecond\r\n", b"first\r\nsecond\n", b"\xef\xbb\xbf" + valid, b"\xff\n"):
56
+ with self.subTest(invalid=invalid):
57
+ target.write_bytes(invalid)
58
+ with self.assertRaisesRegex(RuntimeError, "UTF-8 without BOM and use LF"):
59
+ validate_distribution_text(root)
60
+ target.write_bytes(valid)
61
+ validate_distribution_text(root)
62
+
21
63
  def test_current_release_identity_is_consistent(self) -> None:
22
64
  release = read_json(ROOT / "release.json")
23
65
  result = validate_release(ROOT, ROOT, tag=f"v{release['version']}")
@@ -90,6 +132,7 @@ class ReleaseTests(unittest.TestCase):
90
132
  self.assertIn(
91
133
  f'version = "{expected}"', (target / "pyproject.toml").read_text()
92
134
  )
135
+ validate_distribution_text(target)
93
136
 
94
137
  def test_invalid_change_type_fails(self) -> None:
95
138
  with self.assertRaises(ProcessError):