engineering-process 0.3.0__tar.gz → 0.4.0__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 (133) hide show
  1. {engineering_process-0.3.0/engineering_process.egg-info → engineering_process-0.4.0}/PKG-INFO +4 -1
  2. {engineering_process-0.3.0 → engineering_process-0.4.0}/PRODUCTION_STANDARD.md +18 -0
  3. {engineering_process-0.3.0 → engineering_process-0.4.0}/README.md +3 -0
  4. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/__init__.py +1 -1
  5. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/_supervisor_posix.py +15 -2
  6. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/supervision.py +3 -0
  7. {engineering_process-0.3.0 → engineering_process-0.4.0/engineering_process.egg-info}/PKG-INFO +4 -1
  8. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/evolve-process/SKILL.md +13 -5
  9. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/review-change/SKILL.md +3 -0
  10. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/run-change/SKILL.md +6 -2
  11. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/run-change/references/execution.md +31 -0
  12. {engineering_process-0.3.0 → engineering_process-0.4.0}/pyproject.toml +1 -1
  13. engineering_process-0.4.0/release.json +55 -0
  14. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_environment.py +34 -0
  15. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_skills.py +21 -0
  16. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_supervision.py +62 -1
  17. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_windows_job.py +7 -0
  18. engineering_process-0.3.0/release.json +0 -54
  19. {engineering_process-0.3.0 → engineering_process-0.4.0}/LICENSE +0 -0
  20. {engineering_process-0.3.0 → engineering_process-0.4.0}/MANIFEST.in +0 -0
  21. {engineering_process-0.3.0 → engineering_process-0.4.0}/VERSIONING.md +0 -0
  22. {engineering_process-0.3.0 → engineering_process-0.4.0}/bundles.json +0 -0
  23. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/__main__.py +0 -0
  24. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/_download_worker.py +0 -0
  25. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/_supervisor_windows.py +0 -0
  26. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/_windows_job.py +0 -0
  27. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/adoption.py +0 -0
  28. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/artifact_attestation.py +0 -0
  29. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/bootstrap.py +0 -0
  30. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/bundles.py +0 -0
  31. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/cli.py +0 -0
  32. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/contracts.py +0 -0
  33. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/distribution.py +0 -0
  34. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/distribution_verify.py +0 -0
  35. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/environment.py +0 -0
  36. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/evidence.py +0 -0
  37. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/git.py +0 -0
  38. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/git_attributes.py +0 -0
  39. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/helper_launch.py +0 -0
  40. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/impact.py +0 -0
  41. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/lifecycle.py +0 -0
  42. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/managed.py +0 -0
  43. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/markdown.py +0 -0
  44. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/publication.py +0 -0
  45. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/release.py +0 -0
  46. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/release_candidate.py +0 -0
  47. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/requirements-build.txt +0 -0
  48. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/requirements-dev.txt +0 -0
  49. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/requirements-release.txt +0 -0
  50. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/requirements-runtime.txt +0 -0
  51. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/runner.py +0 -0
  52. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/runtime.py +0 -0
  53. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/skills.py +0 -0
  54. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/supplemental.py +0 -0
  55. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/syncing.py +0 -0
  56. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process/tooling.py +0 -0
  57. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process.egg-info/SOURCES.txt +0 -0
  58. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process.egg-info/dependency_links.txt +0 -0
  59. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process.egg-info/entry_points.txt +0 -0
  60. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process.egg-info/requires.txt +0 -0
  61. {engineering_process-0.3.0 → engineering_process-0.4.0}/engineering_process.egg-info/top_level.txt +0 -0
  62. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/adoption-migration.json +0 -0
  63. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/change.json +0 -0
  64. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/plan.json +0 -0
  65. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/project.json +0 -0
  66. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/release-change.json +0 -0
  67. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/release.json +0 -0
  68. {engineering_process-0.3.0 → engineering_process-0.4.0}/examples/review.json +0 -0
  69. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/assess-design/SKILL.md +0 -0
  70. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/build-frontend/SKILL.md +0 -0
  71. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/build-frontend-foundation/SKILL.md +0 -0
  72. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/change-api/SKILL.md +0 -0
  73. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/cross-repo-change/SKILL.md +0 -0
  74. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/define-change-contract/SKILL.md +0 -0
  75. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/design-module/SKILL.md +0 -0
  76. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/finish-change/SKILL.md +0 -0
  77. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/govern-ui/SKILL.md +0 -0
  78. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/implement-change/SKILL.md +0 -0
  79. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/implement-module/SKILL.md +0 -0
  80. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/implement-use-case/SKILL.md +0 -0
  81. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/integrate-mcp/SKILL.md +0 -0
  82. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/maintain-docs/SKILL.md +0 -0
  83. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/plan-change/SKILL.md +0 -0
  84. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/publish-change/SKILL.md +0 -0
  85. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/run-project-command/SKILL.md +0 -0
  86. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/specify-use-case/SKILL.md +0 -0
  87. {engineering_process-0.3.0 → engineering_process-0.4.0}/process_assets/skills/verify-change/SKILL.md +0 -0
  88. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/adoption-migration.schema.json +0 -0
  89. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/change.schema.json +0 -0
  90. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/evidence-receipt.schema.json +0 -0
  91. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/lifecycle.schema.json +0 -0
  92. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/plan.schema.json +0 -0
  93. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/process-lock.schema.json +0 -0
  94. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/project.schema.json +0 -0
  95. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/release-change.schema.json +0 -0
  96. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/release.schema.json +0 -0
  97. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/review.schema.json +0 -0
  98. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/reviewer-attestation.schema.json +0 -0
  99. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/supplemental-verification.schema.json +0 -0
  100. {engineering_process-0.3.0 → engineering_process-0.4.0}/schemas/verification.schema.json +0 -0
  101. {engineering_process-0.3.0 → engineering_process-0.4.0}/setup.cfg +0 -0
  102. {engineering_process-0.3.0 → engineering_process-0.4.0}/templates/AGENTS.process.md +0 -0
  103. {engineering_process-0.3.0 → engineering_process-0.4.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
  104. {engineering_process-0.3.0 → engineering_process-0.4.0}/templates/adopt-process-windows-job.py +0 -0
  105. {engineering_process-0.3.0 → engineering_process-0.4.0}/templates/adopt-process.py +0 -0
  106. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_adoption.py +0 -0
  107. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_adoption_runner.py +0 -0
  108. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_artifact_attestation.py +0 -0
  109. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_bootstrap.py +0 -0
  110. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_bundles.py +0 -0
  111. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_cli.py +0 -0
  112. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_contracts.py +0 -0
  113. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_distribution.py +0 -0
  114. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_distribution_verify.py +0 -0
  115. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_evals.py +0 -0
  116. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_impact.py +0 -0
  117. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_lifecycle.py +0 -0
  118. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_process_install.py +0 -0
  119. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_publication.py +0 -0
  120. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_publish_event.py +0 -0
  121. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_pypi_publication.py +0 -0
  122. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_release.py +0 -0
  123. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_release_candidate.py +0 -0
  124. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_release_preparation.py +0 -0
  125. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_release_qualification.py +0 -0
  126. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_runner.py +0 -0
  127. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_runtime.py +0 -0
  128. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_schemas.py +0 -0
  129. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_self_hosting.py +0 -0
  130. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_source_checkout.py +0 -0
  131. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_supplemental.py +0 -0
  132. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_syncing.py +0 -0
  133. {engineering_process-0.3.0 → engineering_process-0.4.0}/tests/test_tooling.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Agent-neutral end-to-end engineering lifecycle and deterministic process CLI
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/phuongnse/engineering-process
@@ -85,6 +85,9 @@ distribution: the POSIX backend owns a new process session/group and the Windows
85
85
  backend owns a kill-on-close Job Object. Consumer manifests, evidence, and exit codes
86
86
  do not branch by operating system. If an outer Windows Job applies incompatible
87
87
  nesting or UI limits, target creation fails closed instead of running uncontained.
88
+ After a command root exits, both backends allow at most 250 milliseconds for child
89
+ accounting to drain naturally. A process still present after that bound is terminated
90
+ and makes the command fail; commands with no remaining child return immediately.
88
91
 
89
92
  This task boundary intentionally separates finite commands from services and
90
93
  interactive protocols. `processctl exec`, requirement probes, setup command actions,
@@ -59,6 +59,24 @@ its schema-2 profile reports. The remote artifact id and service-computed digest
59
59
  preserved with review evidence. These reports supplement the public N-1 lifecycle
60
60
  authority; code under verification never promotes itself to lifecycle authority.
61
61
 
62
+ ## Failure to invariant
63
+
64
+ A validated command, gate, release, adoption, or external-integration failure must
65
+ preserve exact bounded evidence and classify its owner before corrective mutation as
66
+ project-local, shared-process, operations-or-external, or missing product or
67
+ authorization input. Dependent candidates stay blocked. Shared defects are corrected
68
+ in the producer rather than wrapped or duplicated by consumers; project behavior
69
+ remains project-owned unless a portable class is proven.
70
+
71
+ Every correction proves valid behavior and the corresponding fail-closed class at the
72
+ lowest reliable owner boundary. Shared corrections additionally require producer
73
+ profiles and reproduction at affected consumer boundaries before release. A transient
74
+ operations/external recovery is allowed only with unchanged source and configuration;
75
+ attempts are bounded, idempotent, diagnostic-preserving, and stop on deterministic
76
+ failure. Source, branch, version, credentials, and controls are never changed merely
77
+ to cause another attempt. Independent review treats violation of these ownership and
78
+ evidence boundaries as completion-blocking.
79
+
62
80
  ## Resource and generated-state policy
63
81
 
64
82
  Every operation over repository-controlled or remote input has explicit limits for
@@ -50,6 +50,9 @@ distribution: the POSIX backend owns a new process session/group and the Windows
50
50
  backend owns a kill-on-close Job Object. Consumer manifests, evidence, and exit codes
51
51
  do not branch by operating system. If an outer Windows Job applies incompatible
52
52
  nesting or UI limits, target creation fails closed instead of running uncontained.
53
+ After a command root exits, both backends allow at most 250 milliseconds for child
54
+ accounting to drain naturally. A process still present after that bound is terminated
55
+ and makes the command fail; commands with no remaining child return immediately.
53
56
 
54
57
  This task boundary intentionally separates finite commands from services and
55
58
  interactive protocols. `processctl exec`, requirement probes, setup command actions,
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
@@ -9,7 +9,7 @@ import subprocess
9
9
  import time
10
10
  from typing import Mapping
11
11
 
12
- from .supervision import CleanupOutcome
12
+ from .supervision import CleanupOutcome, NATURAL_DRAIN_GRACE_MILLISECONDS
13
13
 
14
14
 
15
15
  def _process_group_exists(process_group: int) -> bool:
@@ -156,7 +156,20 @@ class PosixProcessSupervisor:
156
156
  grace_seconds: float,
157
157
  ) -> CleanupOutcome:
158
158
  # The group id remains valid while any descendant from the owned session is
159
- # alive, even after the original process has exited.
159
+ # alive, even after the original process has exited. Give descendants that
160
+ # were synchronously asked to stop one short bounded interval to disappear
161
+ # naturally before classifying them as abandoned background work.
162
+ if not _process_group_exists(process.pid):
163
+ return CleanupOutcome(bounded=True)
164
+ natural_drain_seconds = min(
165
+ grace_seconds,
166
+ NATURAL_DRAIN_GRACE_MILLISECONDS / 1000,
167
+ )
168
+ if natural_drain_seconds > 0 and _wait_for_process_group(
169
+ process.pid,
170
+ natural_drain_seconds,
171
+ ):
172
+ return CleanupOutcome(bounded=True)
160
173
  return _terminate_group(process.pid, grace_seconds)
161
174
 
162
175
 
@@ -14,6 +14,9 @@ import subprocess
14
14
  from typing import Mapping, Protocol
15
15
 
16
16
 
17
+ NATURAL_DRAIN_GRACE_MILLISECONDS = 250
18
+
19
+
17
20
  @dataclass(frozen=True)
18
21
  class CleanupOutcome:
19
22
  """Result of a bounded process-tree cleanup operation."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Agent-neutral end-to-end engineering lifecycle and deterministic process CLI
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/phuongnse/engineering-process
@@ -85,6 +85,9 @@ distribution: the POSIX backend owns a new process session/group and the Windows
85
85
  backend owns a kill-on-close Job Object. Consumer manifests, evidence, and exit codes
86
86
  do not branch by operating system. If an outer Windows Job applies incompatible
87
87
  nesting or UI limits, target creation fails closed instead of running uncontained.
88
+ After a command root exits, both backends allow at most 250 milliseconds for child
89
+ accounting to drain naturally. A process still present after that bound is terminated
90
+ and makes the command fail; commands with no remaining child return immediately.
88
91
 
89
92
  This task boundary intentionally separates finite commands from services and
90
93
  interactive protocols. `processctl exec`, requirement probes, setup command actions,
@@ -14,18 +14,26 @@ incident history into permanent ceremony.
14
14
 
15
15
  1. Start from a reproducer, escaped defect, false positive, review finding, or measured
16
16
  workflow cost. Do not generalize from speculation alone.
17
- 2. Classify the evidence as a project-local defect, reusable process rule,
18
- deterministic invariant, portability gap, or obsolete rule.
19
- 3. Fix local behavior in the project owner. Change shared skills only for portable
17
+ 2. Apply the run-change failure-to-invariant protocol. Classify the owning boundary as
18
+ project-local, shared-process, operations-or-external, or missing product or
19
+ authorization input before corrective mutation; then classify the reusable class
20
+ as a process rule, deterministic invariant, portability gap, or obsolete rule.
21
+ 3. Fix local behavior in the project owner. When the defect belongs to shared process
22
+ semantics, keep the consumer candidate blocked and remove any provisional consumer
23
+ wrapper or duplicate implementation. Change shared skills only for portable
20
24
  semantics. Add CLI or schema enforcement only when deterministic.
21
- 4. Add a regression case at the lowest reliable boundary. For skill behavior, run a
22
- realistic forward test without providing the expected answer.
25
+ 4. Add regression cases at the lowest reliable owner boundary for both valid behavior
26
+ and the fail-closed class. A shared fix also requires producer profiles and a real
27
+ affected-consumer reproduction before release authorization. For skill behavior,
28
+ run a realistic forward test without providing the expected answer.
23
29
  5. Validate backward compatibility, version impact, consumer locks, and migration
24
30
  needs. Remove superseded guidance instead of preserving duplicate paths.
25
31
  Check the full `production-v1` boundary and derive every release identity surface
26
32
  from the release contract so a local fix cannot create a new cross-surface drift.
27
33
  6. Measure whether the change improves task fidelity without disproportionate
28
34
  workflow cost.
35
+ 7. For an operations or external transient, require bounded idempotent recovery with
36
+ retained per-attempt diagnostics. Never mutate source or version merely to retry.
29
37
 
30
38
  ## Hard gates
31
39
 
@@ -33,6 +33,9 @@ specific checkpoint without changing the reviewed source.
33
33
  Resolved, deferred, and false-positive findings require resolution evidence.
34
34
  Deferred remains unresolved and completion-blocking unless a future process
35
35
  contract introduces an explicit owner-approved exception.
36
+ Treat owner mismatch, consumer workaround for a shared defect, evidence-free rerun,
37
+ missing valid/fail-closed regression proof, or missing affected-consumer proof for
38
+ a shared correction as required completion-blocking findings.
36
39
  5. Request changes when any required finding remains open or deferred. Approve only
37
40
  when required outcomes and evidence are complete for the reviewed checkpoint.
38
41
  6. Validate the report with processctl contract validate --kind review, then submit
@@ -31,10 +31,14 @@ semantics shared by every project.
31
31
  3. Apply the nearest AGENTS.md and domain skill inside each phase. Project policy may
32
32
  add stronger gates but cannot remove lifecycle phases, baseline profiles,
33
33
  independent review, evidence freshness, or finding closure.
34
- 4. After a valid finding, preserve the reviewed checkpoint, begin the next
34
+ 4. When a command, gate, release, adoption, or external integration fails, apply the
35
+ failure-to-invariant protocol in the required execution reference before any
36
+ corrective mutation. Keep dependent candidates blocked until the owning boundary
37
+ and required proof are explicit.
38
+ 5. After a valid finding, preserve the reviewed checkpoint, begin the next
35
39
  implementation cycle, resolve the finding, and repeat every invalidated profile
36
40
  and independent review.
37
- 5. Report the processctl phase, cycle, current evidence, blockers, and next owner.
41
+ 6. Report the processctl phase, cycle, current evidence, blockers, and next owner.
38
42
  Never call a task complete from prose alone.
39
43
 
40
44
  ## Hard gates
@@ -54,6 +54,37 @@ When progress depends on user-controlled or external state:
54
54
  5. Report `Blocker`, `Evidence`, `Boundary`, `User action or decision needed`, and
55
55
  `Safe next step after confirmation`.
56
56
 
57
+ ## Failure-to-invariant protocol
58
+
59
+ Apply this protocol before corrective mutation whenever a command, gate, release,
60
+ adoption, or external integration produces a validated failure:
61
+
62
+ 1. Preserve the smallest reliable reproducer, exact command/event, immutable source
63
+ identity, environment, exit status, bounded output, and available service evidence.
64
+ Do not use an evidence-free rerun as diagnosis.
65
+ 2. Classify the owning boundary as `project-local`, `shared-process`,
66
+ `operations-or-external`, or `missing-product-or-authorization-input`. State the
67
+ evidence that excludes the other boundaries before selecting a fix.
68
+ 3. Keep every dependent candidate blocked. A shared-process defect must be fixed in
69
+ the shared producer; do not add a consumer-owned wrapper, duplicate algorithm,
70
+ alternate authority, relaxed control, or environment substitution to keep moving.
71
+ A project-local behavior remains in the project owner and must not be promoted to
72
+ portable core without evidence of a reusable class.
73
+ 4. Add regression evidence at the lowest reliable owner boundary for both the valid
74
+ behavior and the corresponding persistent, invalid, timeout, or interruption case
75
+ that must remain fail closed.
76
+ 5. For a shared correction, require producer profiles and a reproduction at every
77
+ affected consumer boundary before release authorization. Consumer proof supplements
78
+ producer evidence; neither substitutes for the other.
79
+ 6. Treat operations or external propagation as transient only when source and
80
+ configuration are already proven unchanged. Recovery must be bounded, idempotent,
81
+ preserve per-attempt diagnostics, and stop on a deterministic failure. Do not
82
+ change source, branch, version, credentials, or controls merely to cause another
83
+ attempt.
84
+ 7. Reopen or create the owning change lifecycle when scope moves across a boundary,
85
+ then repeat invalidated verification and independent review on one exact final
86
+ checkpoint. Record the reusable invariant, not just the incident chronology.
87
+
57
88
  ## Independent review
58
89
 
59
90
  Review begins only after all baseline and change-required profiles pass on one clean
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-process"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Agent-neutral end-to-end engineering lifecycle and deterministic process CLI"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,55 @@
1
+ {
2
+ "schemaVersion": 3,
3
+ "previousVersion": "0.3.0",
4
+ "version": "0.4.0",
5
+ "classification": "minor",
6
+ "compatibility": "backward-compatible",
7
+ "schemaImpact": "unchanged",
8
+ "migration": null,
9
+ "identity": {
10
+ "package": "engineering-process",
11
+ "distribution": "engineering_process",
12
+ "tag": "v0.4.0",
13
+ "releaseName": "v0.4.0",
14
+ "runtimeVersion": {
15
+ "path": "engineering_process/__init__.py",
16
+ "variable": "VERSION"
17
+ },
18
+ "artifacts": [
19
+ "engineering_process-0.4.0-py3-none-any.whl",
20
+ "engineering_process-0.4.0.tar.gz"
21
+ ],
22
+ "receiptAsset": "engineering-process-v0.4.0-evidence.json",
23
+ "authorizationAsset": null
24
+ },
25
+ "provenance": {
26
+ "mode": "governed",
27
+ "statement": "The public N-1 lifecycle receipt and reviewed Release PR authorize this release.",
28
+ "lifecycleReceipt": {
29
+ "asset": "engineering-process-v0.4.0-evidence.json",
30
+ "project": "engineering-process",
31
+ "changeId": "release-0-4-0",
32
+ "cycle": 1
33
+ }
34
+ },
35
+ "changes": [
36
+ {
37
+ "id": "failure-to-invariant",
38
+ "type": "capability",
39
+ "surfaces": [
40
+ "governance",
41
+ "skills"
42
+ ],
43
+ "rationale": "Require future failures to preserve evidence, classify the correct owner, repair a reusable invariant, prove valid and fail-closed behavior, and obtain affected-consumer proof before shared release."
44
+ },
45
+ {
46
+ "id": "natural-descendant-drain",
47
+ "type": "fix",
48
+ "surfaces": [
49
+ "execution",
50
+ "supervision"
51
+ ],
52
+ "rationale": "Allow short, bounded child-process shutdown to drain naturally on POSIX just as it already does under the Windows Job Object, while persistent descendants remain terminated and completion-blocking."
53
+ }
54
+ ]
55
+ }
@@ -101,6 +101,40 @@ def project_document(*, setup: bool = True, dependency: bool = False):
101
101
 
102
102
 
103
103
  class EnvironmentTests(unittest.TestCase):
104
+ @unittest.skipUnless(os.name == "posix", "POSIX process-group regression")
105
+ def test_successful_command_allows_short_natural_descendant_drain(self):
106
+ with tempfile.TemporaryDirectory() as directory:
107
+ root = Path(directory)
108
+ child_started = root / "child.started"
109
+ child_code = (
110
+ "from pathlib import Path; import sys, time; "
111
+ "Path(sys.argv[1]).write_text('started'); time.sleep(0.08)"
112
+ )
113
+ parent_code = (
114
+ "import subprocess, sys, time; from pathlib import Path; "
115
+ "subprocess.Popen([sys.executable, '-c', sys.argv[1], sys.argv[2]]); "
116
+ "marker=Path(sys.argv[2]); deadline=time.monotonic()+5; "
117
+ "\nwhile not marker.exists() and time.monotonic() < deadline: time.sleep(0.01)\n"
118
+ "raise SystemExit(0 if marker.exists() else 2)"
119
+ )
120
+
121
+ report = execute_command(
122
+ root,
123
+ identifier="natural-drain",
124
+ run=(
125
+ sys.executable,
126
+ "-c",
127
+ parent_code,
128
+ child_code,
129
+ str(child_started),
130
+ ),
131
+ timeout_seconds=10,
132
+ working_directory=".",
133
+ )
134
+
135
+ self.assertEqual("passed", report["status"])
136
+ self.assertEqual(0, report["exitCode"])
137
+
104
138
  def test_managed_command_binding_preserves_logical_evidence_without_a_shell(self):
105
139
  with tempfile.TemporaryDirectory() as directory:
106
140
  root = Path(directory)
@@ -10,6 +10,27 @@ PROCESS_ROOT = Path(__file__).resolve().parent.parent
10
10
 
11
11
 
12
12
  class SkillTests(unittest.TestCase):
13
+ def test_failure_to_invariant_protocol_is_distribution_owned(self):
14
+ skills = PROCESS_ROOT / "process_assets" / "skills"
15
+ execution = (skills / "run-change" / "references" / "execution.md").read_text(
16
+ encoding="utf-8"
17
+ )
18
+ run_change = (skills / "run-change" / "SKILL.md").read_text(encoding="utf-8")
19
+ evolve = (skills / "evolve-process" / "SKILL.md").read_text(encoding="utf-8")
20
+ review = (skills / "review-change" / "SKILL.md").read_text(encoding="utf-8")
21
+ production = (PROCESS_ROOT / "PRODUCTION_STANDARD.md").read_text(encoding="utf-8")
22
+
23
+ self.assertIn("## Failure-to-invariant protocol", execution)
24
+ self.assertIn("`project-local`, `shared-process`", execution)
25
+ self.assertIn("A shared-process defect must be fixed in", execution)
26
+ self.assertIn("valid\n behavior", execution)
27
+ self.assertIn("bounded, idempotent", execution)
28
+ self.assertIn("failure-to-invariant protocol", run_change)
29
+ self.assertIn("keep the consumer candidate blocked", evolve)
30
+ self.assertIn("real\n affected-consumer reproduction", evolve)
31
+ self.assertIn("Treat owner mismatch", review)
32
+ self.assertIn("## Failure to invariant", production)
33
+
13
34
  def test_repository_skills_are_portable(self):
14
35
  self.assertEqual(
15
36
  validate_skills(PROCESS_ROOT / "process_assets" / "skills"), []
@@ -4,11 +4,12 @@ import sys
4
4
  import tempfile
5
5
  import unittest
6
6
  import subprocess
7
+ from unittest.mock import Mock, patch
7
8
 
8
9
  from engineering_process._supervisor_posix import PosixProcessSupervisor
9
10
  from engineering_process._supervisor_windows import resolve_windows_application
10
11
  from engineering_process.helper_launch import isolated_helper_command
11
- from engineering_process.supervision import process_supervisor
12
+ from engineering_process.supervision import CleanupOutcome, process_supervisor
12
13
 
13
14
 
14
15
  class ProcessSupervisionTests(unittest.TestCase):
@@ -33,6 +34,66 @@ class ProcessSupervisionTests(unittest.TestCase):
33
34
  )
34
35
  self.assertEqual(Path(sys.executable).resolve(), application)
35
36
 
37
+ def test_posix_finalization_returns_immediately_without_descendants(self):
38
+ process = Mock(pid=123)
39
+ supervisor = PosixProcessSupervisor()
40
+ with (
41
+ patch(
42
+ "engineering_process._supervisor_posix._process_group_exists",
43
+ return_value=False,
44
+ ),
45
+ patch("engineering_process._supervisor_posix._wait_for_process_group") as wait,
46
+ patch("engineering_process._supervisor_posix._terminate_group") as terminate,
47
+ ):
48
+ outcome = supervisor.finalize(process, grace_seconds=1)
49
+
50
+ self.assertEqual(CleanupOutcome(bounded=True), outcome)
51
+ wait.assert_not_called()
52
+ terminate.assert_not_called()
53
+
54
+ def test_posix_finalization_allows_bounded_natural_drain(self):
55
+ process = Mock(pid=123)
56
+ supervisor = PosixProcessSupervisor()
57
+ with (
58
+ patch(
59
+ "engineering_process._supervisor_posix._process_group_exists",
60
+ return_value=True,
61
+ ),
62
+ patch(
63
+ "engineering_process._supervisor_posix._wait_for_process_group",
64
+ return_value=True,
65
+ ) as wait,
66
+ patch("engineering_process._supervisor_posix._terminate_group") as terminate,
67
+ ):
68
+ outcome = supervisor.finalize(process, grace_seconds=1)
69
+
70
+ self.assertEqual(CleanupOutcome(bounded=True), outcome)
71
+ wait.assert_called_once_with(123, 0.25)
72
+ terminate.assert_not_called()
73
+
74
+ def test_posix_finalization_rejects_descendant_after_natural_drain_bound(self):
75
+ process = Mock(pid=123)
76
+ supervisor = PosixProcessSupervisor()
77
+ expected = CleanupOutcome(bounded=True, descendants_found=True)
78
+ with (
79
+ patch(
80
+ "engineering_process._supervisor_posix._process_group_exists",
81
+ return_value=True,
82
+ ),
83
+ patch(
84
+ "engineering_process._supervisor_posix._wait_for_process_group",
85
+ return_value=False,
86
+ ),
87
+ patch(
88
+ "engineering_process._supervisor_posix._terminate_group",
89
+ return_value=expected,
90
+ ) as terminate,
91
+ ):
92
+ outcome = supervisor.finalize(process, grace_seconds=1)
93
+
94
+ self.assertEqual(expected, outcome)
95
+ terminate.assert_called_once_with(123, 1)
96
+
36
97
  @unittest.skipUnless(os.name == "posix", "POSIX executable permission semantics")
37
98
  def test_posix_relative_path_entries_are_resolved_from_command_working_directory(self):
38
99
  with tempfile.TemporaryDirectory() as directory:
@@ -9,6 +9,7 @@ import unittest
9
9
  from unittest.mock import patch
10
10
 
11
11
  from engineering_process import _windows_job
12
+ from engineering_process.supervision import NATURAL_DRAIN_GRACE_MILLISECONDS
12
13
 
13
14
 
14
15
  class NativeCall:
@@ -153,6 +154,12 @@ class FakeKernel32:
153
154
 
154
155
 
155
156
  class WindowsJobTests(unittest.TestCase):
157
+ def test_natural_drain_uses_the_portable_supervision_bound(self):
158
+ self.assertEqual(
159
+ NATURAL_DRAIN_GRACE_MILLISECONDS,
160
+ _windows_job.NATURAL_DRAIN_GRACE_MILLISECONDS,
161
+ )
162
+
156
163
  def test_target_is_assigned_atomically_during_process_creation(self):
157
164
  kernel32 = FakeKernel32()
158
165
 
@@ -1,54 +0,0 @@
1
- {
2
- "schemaVersion": 3,
3
- "previousVersion": "0.2.1",
4
- "version": "0.3.0",
5
- "classification": "minor",
6
- "compatibility": "backward-compatible",
7
- "schemaImpact": "unchanged",
8
- "migration": null,
9
- "identity": {
10
- "package": "engineering-process",
11
- "distribution": "engineering_process",
12
- "tag": "v0.3.0",
13
- "releaseName": "v0.3.0",
14
- "runtimeVersion": {
15
- "path": "engineering_process/__init__.py",
16
- "variable": "VERSION"
17
- },
18
- "artifacts": [
19
- "engineering_process-0.3.0-py3-none-any.whl",
20
- "engineering_process-0.3.0.tar.gz"
21
- ],
22
- "receiptAsset": "engineering-process-v0.3.0-evidence.json",
23
- "authorizationAsset": null
24
- },
25
- "provenance": {
26
- "mode": "governed",
27
- "statement": "The public N-1 lifecycle receipt and reviewed Release PR authorize this release.",
28
- "lifecycleReceipt": {
29
- "asset": "engineering-process-v0.3.0-evidence.json",
30
- "project": "engineering-process",
31
- "changeId": "release-0-3-0",
32
- "cycle": 1
33
- }
34
- },
35
- "changes": [
36
- {
37
- "id": "consumer-process-boundary-cleanup",
38
- "type": "capability",
39
- "surfaces": [
40
- "ci-bootstrap",
41
- "workflow"
42
- ],
43
- "rationale": "Provide one immutable producer-owned CI installation action so consumers can remove copied process algorithms without weakening exact hash locks or bounded registry propagation recovery."
44
- },
45
- {
46
- "id": "reopen-stale-verification",
47
- "type": "fix",
48
- "surfaces": [
49
- "lifecycle"
50
- ],
51
- "rationale": "Allow a verified change invalidated by later remote evidence or a source correction to begin a new implementation cycle while preserving prior evidence and still forbidding current verified work from bypassing independent review."
52
- }
53
- ]
54
- }