engineering-process 0.2.0__tar.gz → 0.3.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.2.0/engineering_process.egg-info → engineering_process-0.3.0}/PKG-INFO +33 -1
  2. {engineering_process-0.2.0 → engineering_process-0.3.0}/README.md +32 -0
  3. {engineering_process-0.2.0 → engineering_process-0.3.0}/VERSIONING.md +24 -1
  4. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/__init__.py +1 -1
  5. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/lifecycle.py +36 -1
  6. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/publication.py +57 -0
  7. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/release.py +0 -4
  8. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/release_candidate.py +67 -2
  9. {engineering_process-0.2.0 → engineering_process-0.3.0/engineering_process.egg-info}/PKG-INFO +33 -1
  10. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process.egg-info/SOURCES.txt +5 -0
  11. {engineering_process-0.2.0 → engineering_process-0.3.0}/pyproject.toml +1 -1
  12. engineering_process-0.3.0/release.json +54 -0
  13. {engineering_process-0.2.0 → engineering_process-0.3.0}/templates/adopt-process.py +27 -2
  14. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_adoption_runner.py +61 -1
  15. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_lifecycle.py +62 -0
  16. engineering_process-0.3.0/tests/test_process_install.py +235 -0
  17. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_publication.py +53 -0
  18. engineering_process-0.3.0/tests/test_publish_event.py +57 -0
  19. engineering_process-0.3.0/tests/test_pypi_publication.py +156 -0
  20. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_release.py +43 -0
  21. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_release_candidate.py +157 -1
  22. engineering_process-0.3.0/tests/test_release_preparation.py +68 -0
  23. engineering_process-0.3.0/tests/test_release_qualification.py +99 -0
  24. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_self_hosting.py +217 -12
  25. engineering_process-0.2.0/release.json +0 -114
  26. {engineering_process-0.2.0 → engineering_process-0.3.0}/LICENSE +0 -0
  27. {engineering_process-0.2.0 → engineering_process-0.3.0}/MANIFEST.in +0 -0
  28. {engineering_process-0.2.0 → engineering_process-0.3.0}/PRODUCTION_STANDARD.md +0 -0
  29. {engineering_process-0.2.0 → engineering_process-0.3.0}/bundles.json +0 -0
  30. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/__main__.py +0 -0
  31. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/_download_worker.py +0 -0
  32. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/_supervisor_posix.py +0 -0
  33. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/_supervisor_windows.py +0 -0
  34. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/_windows_job.py +0 -0
  35. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/adoption.py +0 -0
  36. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/artifact_attestation.py +0 -0
  37. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/bootstrap.py +0 -0
  38. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/bundles.py +0 -0
  39. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/cli.py +0 -0
  40. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/contracts.py +0 -0
  41. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/distribution.py +0 -0
  42. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/distribution_verify.py +0 -0
  43. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/environment.py +0 -0
  44. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/evidence.py +0 -0
  45. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/git.py +0 -0
  46. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/git_attributes.py +0 -0
  47. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/helper_launch.py +0 -0
  48. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/impact.py +0 -0
  49. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/managed.py +0 -0
  50. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/markdown.py +0 -0
  51. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/requirements-build.txt +0 -0
  52. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/requirements-dev.txt +0 -0
  53. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/requirements-release.txt +0 -0
  54. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/requirements-runtime.txt +0 -0
  55. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/runner.py +0 -0
  56. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/runtime.py +0 -0
  57. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/skills.py +0 -0
  58. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/supervision.py +0 -0
  59. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/supplemental.py +0 -0
  60. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/syncing.py +0 -0
  61. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process/tooling.py +0 -0
  62. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process.egg-info/dependency_links.txt +0 -0
  63. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process.egg-info/entry_points.txt +0 -0
  64. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process.egg-info/requires.txt +0 -0
  65. {engineering_process-0.2.0 → engineering_process-0.3.0}/engineering_process.egg-info/top_level.txt +0 -0
  66. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/adoption-migration.json +0 -0
  67. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/change.json +0 -0
  68. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/plan.json +0 -0
  69. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/project.json +0 -0
  70. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/release-change.json +0 -0
  71. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/release.json +0 -0
  72. {engineering_process-0.2.0 → engineering_process-0.3.0}/examples/review.json +0 -0
  73. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/assess-design/SKILL.md +0 -0
  74. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/build-frontend/SKILL.md +0 -0
  75. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/build-frontend-foundation/SKILL.md +0 -0
  76. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/change-api/SKILL.md +0 -0
  77. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/cross-repo-change/SKILL.md +0 -0
  78. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/define-change-contract/SKILL.md +0 -0
  79. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/design-module/SKILL.md +0 -0
  80. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/evolve-process/SKILL.md +0 -0
  81. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/finish-change/SKILL.md +0 -0
  82. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/govern-ui/SKILL.md +0 -0
  83. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/implement-change/SKILL.md +0 -0
  84. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/implement-module/SKILL.md +0 -0
  85. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/implement-use-case/SKILL.md +0 -0
  86. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/integrate-mcp/SKILL.md +0 -0
  87. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/maintain-docs/SKILL.md +0 -0
  88. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/plan-change/SKILL.md +0 -0
  89. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/publish-change/SKILL.md +0 -0
  90. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/review-change/SKILL.md +0 -0
  91. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/run-change/SKILL.md +0 -0
  92. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/run-change/references/execution.md +0 -0
  93. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/run-project-command/SKILL.md +0 -0
  94. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/specify-use-case/SKILL.md +0 -0
  95. {engineering_process-0.2.0 → engineering_process-0.3.0}/process_assets/skills/verify-change/SKILL.md +0 -0
  96. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/adoption-migration.schema.json +0 -0
  97. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/change.schema.json +0 -0
  98. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/evidence-receipt.schema.json +0 -0
  99. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/lifecycle.schema.json +0 -0
  100. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/plan.schema.json +0 -0
  101. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/process-lock.schema.json +0 -0
  102. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/project.schema.json +0 -0
  103. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/release-change.schema.json +0 -0
  104. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/release.schema.json +0 -0
  105. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/review.schema.json +0 -0
  106. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/reviewer-attestation.schema.json +0 -0
  107. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/supplemental-verification.schema.json +0 -0
  108. {engineering_process-0.2.0 → engineering_process-0.3.0}/schemas/verification.schema.json +0 -0
  109. {engineering_process-0.2.0 → engineering_process-0.3.0}/setup.cfg +0 -0
  110. {engineering_process-0.2.0 → engineering_process-0.3.0}/templates/AGENTS.process.md +0 -0
  111. {engineering_process-0.2.0 → engineering_process-0.3.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
  112. {engineering_process-0.2.0 → engineering_process-0.3.0}/templates/adopt-process-windows-job.py +0 -0
  113. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_adoption.py +0 -0
  114. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_artifact_attestation.py +0 -0
  115. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_bootstrap.py +0 -0
  116. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_bundles.py +0 -0
  117. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_cli.py +0 -0
  118. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_contracts.py +0 -0
  119. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_distribution.py +0 -0
  120. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_distribution_verify.py +0 -0
  121. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_environment.py +0 -0
  122. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_evals.py +0 -0
  123. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_impact.py +0 -0
  124. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_runner.py +0 -0
  125. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_runtime.py +0 -0
  126. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_schemas.py +0 -0
  127. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_skills.py +0 -0
  128. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_source_checkout.py +0 -0
  129. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_supervision.py +0 -0
  130. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_supplemental.py +0 -0
  131. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_syncing.py +0 -0
  132. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_tooling.py +0 -0
  133. {engineering_process-0.2.0 → engineering_process-0.3.0}/tests/test_windows_job.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 0.2.0
3
+ Version: 0.3.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
@@ -136,6 +136,26 @@ attribute files are rejected by existing managed-tree ownership and content chec
136
136
  External Git overrides that alter a checkout still fail byte-exact distribution
137
137
  attestation. A consumer never authors or maintains process skills locally.
138
138
 
139
+ CI installs the pinned authority through the repository-root
140
+ `phuongnse/engineering-process` action from the same governed release. Consumers pin
141
+ the action with the release commit's full object id and retain the human-readable
142
+ `v<SemVer>` annotation; floating tags and copied installer implementations are not
143
+ supported. The action reads the consumer-owned `requirements/process.txt` and changes
144
+ no version or source decision: it preserves the complete hash lock, public PyPI,
145
+ binary-only policy, sanitized pip environment, exact-version-only propagation retry,
146
+ bounded output and time, and cross-platform descendant cleanup. The action source is
147
+ resolved only from `github.action_path`, so an untrusted consumer checkout cannot
148
+ replace the installer.
149
+
150
+ Project-owned CI workflows remain local because they select the project's commands
151
+ and evidence. Reusable installation and publication grammar belong to this
152
+ distribution; product, architecture, dependency, documentation, and acceptance
153
+ checks remain with the consumer. The managed `.process/adopt-process.py` and Windows
154
+ Job Object sidecar are intentional bootstrap snapshots, not consumer implementations:
155
+ Renovate must install and verify a target authority before that target exists in the
156
+ checkout, and `processctl sync --check` compares those bytes with the pinned
157
+ distribution.
158
+
139
159
  For an existing consumer, a published version is adopted through one Renovate draft.
140
160
  The managed runner installs the target authority from the complete hash lock outside
141
161
  the checkout and atomically updates the process lock and managed assets. If the
@@ -386,6 +406,13 @@ processctl change verify --change-id issue-123 --profile review \
386
406
  --actor worker --context worker-session --actor-kind agent
387
407
  ~~~
388
408
 
409
+ If later remote evidence or a source correction invalidates that verified checkpoint
410
+ before review, commit the correction and run `change implement` again. The CLI
411
+ preserves the earlier evidence, records `verification-invalidated`, increments the
412
+ cycle, and requires every profile again. It accepts this transition only when the
413
+ recorded verification is actually stale; a current verified checkpoint cannot use it
414
+ to bypass independent review.
415
+
389
416
  After the phase becomes `verified`, a separate reviewer context registers its
390
417
  assignment:
391
418
 
@@ -504,6 +531,11 @@ authenticates who produced it.
504
531
  draft contains the new lock, managed contracts, skill snapshots, and any
505
532
  target-version consumer-owned project migration; after CI and fresh-context
506
533
  independent review, merge is the end of adoption.
534
+ - The repository-root GitHub Action is the shared CI bootstrap surface. Consumers pin
535
+ its full governed release commit, while the exact Python authority remains selected
536
+ exclusively by their hash-locked `requirements/process.txt`. The action invokes the
537
+ producer-owned installer from its immutable action checkout and never downloads or
538
+ executes helper source from the consumer branch.
507
539
  - Versioned JSON schemas define change, plan, verification, review, lifecycle,
508
540
  completion-related artifacts, release-change fragments, and the release
509
541
  classification contract. The generated Release PR gate binds that contract to the
@@ -101,6 +101,26 @@ attribute files are rejected by existing managed-tree ownership and content chec
101
101
  External Git overrides that alter a checkout still fail byte-exact distribution
102
102
  attestation. A consumer never authors or maintains process skills locally.
103
103
 
104
+ CI installs the pinned authority through the repository-root
105
+ `phuongnse/engineering-process` action from the same governed release. Consumers pin
106
+ the action with the release commit's full object id and retain the human-readable
107
+ `v<SemVer>` annotation; floating tags and copied installer implementations are not
108
+ supported. The action reads the consumer-owned `requirements/process.txt` and changes
109
+ no version or source decision: it preserves the complete hash lock, public PyPI,
110
+ binary-only policy, sanitized pip environment, exact-version-only propagation retry,
111
+ bounded output and time, and cross-platform descendant cleanup. The action source is
112
+ resolved only from `github.action_path`, so an untrusted consumer checkout cannot
113
+ replace the installer.
114
+
115
+ Project-owned CI workflows remain local because they select the project's commands
116
+ and evidence. Reusable installation and publication grammar belong to this
117
+ distribution; product, architecture, dependency, documentation, and acceptance
118
+ checks remain with the consumer. The managed `.process/adopt-process.py` and Windows
119
+ Job Object sidecar are intentional bootstrap snapshots, not consumer implementations:
120
+ Renovate must install and verify a target authority before that target exists in the
121
+ checkout, and `processctl sync --check` compares those bytes with the pinned
122
+ distribution.
123
+
104
124
  For an existing consumer, a published version is adopted through one Renovate draft.
105
125
  The managed runner installs the target authority from the complete hash lock outside
106
126
  the checkout and atomically updates the process lock and managed assets. If the
@@ -351,6 +371,13 @@ processctl change verify --change-id issue-123 --profile review \
351
371
  --actor worker --context worker-session --actor-kind agent
352
372
  ~~~
353
373
 
374
+ If later remote evidence or a source correction invalidates that verified checkpoint
375
+ before review, commit the correction and run `change implement` again. The CLI
376
+ preserves the earlier evidence, records `verification-invalidated`, increments the
377
+ cycle, and requires every profile again. It accepts this transition only when the
378
+ recorded verification is actually stale; a current verified checkpoint cannot use it
379
+ to bypass independent review.
380
+
354
381
  After the phase becomes `verified`, a separate reviewer context registers its
355
382
  assignment:
356
383
 
@@ -469,6 +496,11 @@ authenticates who produced it.
469
496
  draft contains the new lock, managed contracts, skill snapshots, and any
470
497
  target-version consumer-owned project migration; after CI and fresh-context
471
498
  independent review, merge is the end of adoption.
499
+ - The repository-root GitHub Action is the shared CI bootstrap surface. Consumers pin
500
+ its full governed release commit, while the exact Python authority remains selected
501
+ exclusively by their hash-locked `requirements/process.txt`. The action invokes the
502
+ producer-owned installer from its immutable action checkout and never downloads or
503
+ executes helper source from the consumer branch.
472
504
  - Versioned JSON schemas define change, plan, verification, review, lifecycle,
473
505
  completion-related artifacts, release-change fragments, and the release
474
506
  classification contract. The generated Release PR gate binds that contract to the
@@ -71,6 +71,13 @@ verification manifest and project-adoption migration each follow that rule. Addi
71
71
  optional `timeoutSeconds` evidence to verification schema 2 preserves existing
72
72
  schema-2 documents and therefore does not advance that artifact's schema version.
73
73
 
74
+ Every generator must be qualified against the exact released reader that consumes its
75
+ output. For a lifecycle artifact, validation at creation is insufficient: CI runs the
76
+ generated change, plan, review, completion, and exported evidence through public N-1
77
+ up to the final consuming transition. Review schema and quality mappings derive from
78
+ the registered change contract instead of being maintained as an independent version
79
+ constant.
80
+
74
81
  Package `schemaImpact` is `unchanged`, `additive`, or `breaking` for the combined
75
82
  release. Additive schema capability requires at least a capability release; a
76
83
  breaking schema requires an incompatible release.
@@ -91,13 +98,29 @@ Release, self-adoption, and consumer adoption are separate changes:
91
98
  checkpoint. Merge completes adoption; there is no post-merge synchronization.
92
99
  5. N+1 governs only changes that begin after the adoption checkpoint.
93
100
 
101
+ The repository-root GitHub Action and Python package are two surfaces of the same
102
+ governed release checkpoint. A consumer invocation pins the action by the release
103
+ commit's full object id and keeps the `v<SemVer>` annotation; Renovate groups that
104
+ GitHub Action identity with the direct Python authority update. The action does not
105
+ select the package version. It installs only the consumer's complete hash lock, so
106
+ the process lock, Python artifacts, managed assets, action source, and reviewed tag
107
+ remain independently checkable parts of one release identity.
108
+
109
+ Consumer repositories do not own copies of reusable installation or publication
110
+ algorithms. They retain declarative process/project locks, exact project commands,
111
+ and managed bootstrap snapshots required before a target version is installed. A
112
+ clean-cutover PR may delete an obsolete local helper only after it pins an immutable
113
+ public action checkpoint; no compatibility shim or dual execution path is retained.
114
+
94
115
  Renovate PRs are generated adoption candidates, not trusted adoption evidence.
95
116
  Automerge is forbidden for process-authority updates. A PR that changes only a
96
117
  requirement pin, omits generated hashes or managed assets, or requires a post-merge
97
118
  step must fail closed.
98
119
 
99
120
  `requirements/process.in` owns the direct public pin and `requirements/process.txt`
100
- is its pip-compile hash lock. Renovate's exact allowlisted post-upgrade command runs
121
+ is its pip-compile hash lock. The lock generator is pinned, and the committed lock must
122
+ cover compatible binary artifacts for every supported Python/OS runner rather than
123
+ only the platform that compiled it. Renovate's exact allowlisted post-upgrade command runs
101
124
  `.process/adopt-process.py`; that managed runner creates a bounded temporary
102
125
  environment outside the checkout, rejects symlink, junction, or reparse input in
103
126
  every supplied lock-path component, and makes one stable private snapshot. Component
@@ -1,3 +1,3 @@
1
1
  """Agent-neutral engineering process."""
2
2
 
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
@@ -624,10 +624,45 @@ def _begin_implementation_unlocked(
624
624
  kind: str,
625
625
  ) -> dict[str, Any]:
626
626
  state = load_state(project_root, change_id)
627
- _require_phase(state, "planned", "implementing", "changes-requested")
628
627
  _contract(project_root, state)
629
628
  _plan(project_root, state)
630
629
  actor = _actor(actor_id, context_id, kind)
630
+ if state["phase"] == "verified":
631
+ source = source_state(project_root)
632
+ checkpoints = {item["checkpoint"] for item in state["verification"]}
633
+ fingerprints = {
634
+ item["workspaceFingerprint"] for item in state["verification"]
635
+ }
636
+ current = (
637
+ source["dirty"] is False
638
+ and source["checkpoint"] is not None
639
+ and source["fingerprint"] is not None
640
+ and checkpoints == {source["checkpoint"]}
641
+ and fingerprints == {source["fingerprint"]}
642
+ )
643
+ if current:
644
+ raise ContractError(
645
+ "current verified change must enter independent review before "
646
+ "another implementation cycle"
647
+ )
648
+ previous_cycle = state["cycle"]
649
+ previous_verification = list(state["verification"])
650
+ state["cycle"] += 1
651
+ state["implementationActors"] = []
652
+ state["verification"] = []
653
+ state["reviewAssignment"] = None
654
+ state["review"] = None
655
+ _event(
656
+ state,
657
+ "verification-invalidated",
658
+ actor,
659
+ cycle=state["cycle"],
660
+ previousCycle=previous_cycle,
661
+ previousVerification=previous_verification,
662
+ reason="source-changed-after-verification",
663
+ )
664
+ else:
665
+ _require_phase(state, "planned", "implementing", "changes-requested")
631
666
  if state["phase"] == "changes-requested":
632
667
  state["cycle"] += 1
633
668
  state["implementationActors"] = []
@@ -1,5 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import base64
4
+ import binascii
5
+ import json
3
6
  import re
4
7
  from pathlib import Path
5
8
 
@@ -243,12 +246,66 @@ def managed_pull_request_visibility_issues(body: str) -> list[str]:
243
246
 
244
247
 
245
248
  PROJECT_EXTENSION_HEADING = "## Project-specific requirements"
249
+ RENOVATE_DEBUG_COMMENT_RE = re.compile(
250
+ r"^<!--renovate-debug:(?P<payload>[A-Za-z0-9+/]{1,4096}={0,2})-->$"
251
+ )
252
+ RENOVATE_DEBUG_KEYS = {
253
+ "createdInVer",
254
+ "updatedInVer",
255
+ "targetBranch",
256
+ "labels",
257
+ }
258
+
259
+
260
+ def _strip_renovate_debug_comment(extension: str) -> tuple[str, list[str]]:
261
+ lines = extension.strip().splitlines()
262
+ matches = [
263
+ (index, match)
264
+ for index, line in enumerate(lines)
265
+ if (match := RENOVATE_DEBUG_COMMENT_RE.fullmatch(line)) is not None
266
+ ]
267
+ if not matches:
268
+ return extension, []
269
+ if len(matches) != 1 or matches[0][0] != len(lines) - 1:
270
+ return extension, ["Renovate metadata comment must appear exactly once at the end"]
271
+ _, match = matches[0]
272
+ try:
273
+ decoded = base64.b64decode(match.group("payload"), validate=True)
274
+ if len(decoded) > 3072:
275
+ raise ValueError("payload exceeds 3072 decoded bytes")
276
+ metadata = json.loads(decoded.decode("utf-8"))
277
+ except (binascii.Error, UnicodeDecodeError, json.JSONDecodeError, ValueError):
278
+ return extension, ["Renovate metadata comment must contain bounded base64 JSON"]
279
+ if not isinstance(metadata, dict) or set(metadata) != RENOVATE_DEBUG_KEYS:
280
+ return extension, ["Renovate metadata comment has an unexpected JSON contract"]
281
+ if any(
282
+ not isinstance(metadata[key], str)
283
+ or not metadata[key]
284
+ or len(metadata[key]) > 64
285
+ for key in ("createdInVer", "updatedInVer")
286
+ ):
287
+ return extension, ["Renovate metadata versions must be bounded strings"]
288
+ if metadata["targetBranch"] != "main":
289
+ return extension, ["Renovate metadata must target protected main"]
290
+ labels = metadata["labels"]
291
+ if (
292
+ not isinstance(labels, list)
293
+ or len(labels) > 50
294
+ or any(not isinstance(label, str) or len(label) > 100 for label in labels)
295
+ ):
296
+ return extension, ["Renovate metadata labels must be a bounded string list"]
297
+ return "\n".join(lines[:-1]).strip(), []
246
298
 
247
299
 
248
300
  def validate_project_extensions(body: str, *, allow_pending: bool) -> list[str]:
249
301
  body = _normalized_markdown(body)
250
302
  _, end = _managed_span(body)
251
303
  extension = body[end:]
304
+ if not extension.strip():
305
+ return []
306
+ extension, metadata_issues = _strip_renovate_debug_comment(extension)
307
+ if metadata_issues:
308
+ return metadata_issues
252
309
  if not extension.strip():
253
310
  return []
254
311
  if contains_raw_html(extension):
@@ -118,10 +118,6 @@ def validate_reviewed_checkpoint(
118
118
  ],
119
119
  )
120
120
  if reviewed_checkpoint != release_checkpoint:
121
- _git(
122
- project_root,
123
- ["merge-base", "--is-ancestor", reviewed_checkpoint, release_checkpoint],
124
- )
125
121
  reviewed_tree = _git(
126
122
  project_root,
127
123
  ["rev-parse", "--verify", f"{reviewed_checkpoint}^{{tree}}"],
@@ -232,7 +232,7 @@ def _release_lifecycle_documents(
232
232
  ) -> tuple[bytes, bytes]:
233
233
  change_id = f"release-{version.replace('.', '-')}"
234
234
  contract = {
235
- "schemaVersion": 2,
235
+ "schemaVersion": 3,
236
236
  "id": change_id,
237
237
  "summary": f"Authorize and publish {project} {version}",
238
238
  "source": "Automatically aggregated reviewed release-change fragments",
@@ -259,6 +259,71 @@ def _release_lifecycle_documents(
259
259
  },
260
260
  ],
261
261
  "requiredProfiles": ["development", "review"],
262
+ "quality": {
263
+ "standard": "production-v1",
264
+ "assessments": [
265
+ {
266
+ "dimension": "compatibility",
267
+ "status": "applicable",
268
+ "rationale": "The release must preserve its declared compatibility and exact identity surfaces.",
269
+ "criteria": ["ac-identity"],
270
+ },
271
+ {
272
+ "dimension": "correctness",
273
+ "status": "applicable",
274
+ "rationale": "The derived identity and published bytes must match the reviewed release contract.",
275
+ "criteria": ["ac-identity", "ac-publication"],
276
+ },
277
+ {
278
+ "dimension": "maintainability",
279
+ "status": "applicable",
280
+ "rationale": "Independent review verifies the generated contract and deterministic release owner.",
281
+ "criteria": ["ac-review"],
282
+ },
283
+ {
284
+ "dimension": "observability",
285
+ "status": "applicable",
286
+ "rationale": "Release evidence and attestations expose the exact publication outcome.",
287
+ "criteria": ["ac-publication"],
288
+ },
289
+ {
290
+ "dimension": "operability",
291
+ "status": "applicable",
292
+ "rationale": "The reviewed release must publish and recover through the declared automated workflow.",
293
+ "criteria": ["ac-publication"],
294
+ },
295
+ {
296
+ "dimension": "performance",
297
+ "status": "not-applicable",
298
+ "rationale": "Release authorization adds no product runtime or scaling behavior.",
299
+ "criteria": [],
300
+ },
301
+ {
302
+ "dimension": "privacy",
303
+ "status": "not-applicable",
304
+ "rationale": "Release metadata contains no personal or sensitive data processing.",
305
+ "criteria": [],
306
+ },
307
+ {
308
+ "dimension": "reliability",
309
+ "status": "applicable",
310
+ "rationale": "Immutable assets and exact external-state checks must fail closed and recover deterministically.",
311
+ "criteria": ["ac-publication"],
312
+ },
313
+ {
314
+ "dimension": "security",
315
+ "status": "applicable",
316
+ "rationale": "Independent review and merge remain the only publication authorization boundary.",
317
+ "criteria": ["ac-review"],
318
+ },
319
+ {
320
+ "dimension": "supply-chain",
321
+ "status": "applicable",
322
+ "rationale": "The release binds source, version, artifacts, attestations, and registry bytes.",
323
+ "criteria": ["ac-identity", "ac-publication"],
324
+ },
325
+ ],
326
+ },
262
327
  "signOff": {
263
328
  "required": False,
264
329
  "status": "not-required",
@@ -268,7 +333,7 @@ def _release_lifecycle_documents(
268
333
  contract_bytes = _canonical_json_bytes(contract)
269
334
  contract_digest = f"sha256:{hashlib.sha256(contract_bytes).hexdigest()}"
270
335
  plan = {
271
- "schemaVersion": 1,
336
+ "schemaVersion": 2,
272
337
  "changeId": change_id,
273
338
  "contractDigest": contract_digest,
274
339
  "approach": "Validate the generated release contract and every declared surface, build from the exact reviewed tree, and permit publication only after independent approval and merge.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: engineering-process
3
- Version: 0.2.0
3
+ Version: 0.3.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
@@ -136,6 +136,26 @@ attribute files are rejected by existing managed-tree ownership and content chec
136
136
  External Git overrides that alter a checkout still fail byte-exact distribution
137
137
  attestation. A consumer never authors or maintains process skills locally.
138
138
 
139
+ CI installs the pinned authority through the repository-root
140
+ `phuongnse/engineering-process` action from the same governed release. Consumers pin
141
+ the action with the release commit's full object id and retain the human-readable
142
+ `v<SemVer>` annotation; floating tags and copied installer implementations are not
143
+ supported. The action reads the consumer-owned `requirements/process.txt` and changes
144
+ no version or source decision: it preserves the complete hash lock, public PyPI,
145
+ binary-only policy, sanitized pip environment, exact-version-only propagation retry,
146
+ bounded output and time, and cross-platform descendant cleanup. The action source is
147
+ resolved only from `github.action_path`, so an untrusted consumer checkout cannot
148
+ replace the installer.
149
+
150
+ Project-owned CI workflows remain local because they select the project's commands
151
+ and evidence. Reusable installation and publication grammar belong to this
152
+ distribution; product, architecture, dependency, documentation, and acceptance
153
+ checks remain with the consumer. The managed `.process/adopt-process.py` and Windows
154
+ Job Object sidecar are intentional bootstrap snapshots, not consumer implementations:
155
+ Renovate must install and verify a target authority before that target exists in the
156
+ checkout, and `processctl sync --check` compares those bytes with the pinned
157
+ distribution.
158
+
139
159
  For an existing consumer, a published version is adopted through one Renovate draft.
140
160
  The managed runner installs the target authority from the complete hash lock outside
141
161
  the checkout and atomically updates the process lock and managed assets. If the
@@ -386,6 +406,13 @@ processctl change verify --change-id issue-123 --profile review \
386
406
  --actor worker --context worker-session --actor-kind agent
387
407
  ~~~
388
408
 
409
+ If later remote evidence or a source correction invalidates that verified checkpoint
410
+ before review, commit the correction and run `change implement` again. The CLI
411
+ preserves the earlier evidence, records `verification-invalidated`, increments the
412
+ cycle, and requires every profile again. It accepts this transition only when the
413
+ recorded verification is actually stale; a current verified checkpoint cannot use it
414
+ to bypass independent review.
415
+
389
416
  After the phase becomes `verified`, a separate reviewer context registers its
390
417
  assignment:
391
418
 
@@ -504,6 +531,11 @@ authenticates who produced it.
504
531
  draft contains the new lock, managed contracts, skill snapshots, and any
505
532
  target-version consumer-owned project migration; after CI and fresh-context
506
533
  independent review, merge is the end of adoption.
534
+ - The repository-root GitHub Action is the shared CI bootstrap surface. Consumers pin
535
+ its full governed release commit, while the exact Python authority remains selected
536
+ exclusively by their hash-locked `requirements/process.txt`. The action invokes the
537
+ producer-owned installer from its immutable action checkout and never downloads or
538
+ executes helper source from the consumer branch.
507
539
  - Versioned JSON schemas define change, plan, verification, review, lifecycle,
508
540
  completion-related artifacts, release-change fragments, and the release
509
541
  classification contract. The generated Release PR gate binds that contract to the
@@ -109,9 +109,14 @@ tests/test_environment.py
109
109
  tests/test_evals.py
110
110
  tests/test_impact.py
111
111
  tests/test_lifecycle.py
112
+ tests/test_process_install.py
112
113
  tests/test_publication.py
114
+ tests/test_publish_event.py
115
+ tests/test_pypi_publication.py
113
116
  tests/test_release.py
114
117
  tests/test_release_candidate.py
118
+ tests/test_release_preparation.py
119
+ tests/test_release_qualification.py
115
120
  tests/test_runner.py
116
121
  tests/test_runtime.py
117
122
  tests/test_schemas.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "engineering-process"
7
- version = "0.2.0"
7
+ version = "0.3.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,54 @@
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
+ }
@@ -500,6 +500,11 @@ def main(argv: list[str] | None = None) -> int:
500
500
  type=Path,
501
501
  default=Path("requirements/process.txt"),
502
502
  )
503
+ parser.add_argument(
504
+ "--check",
505
+ action="store_true",
506
+ help="Validate through the installed hash-locked authority without applying",
507
+ )
503
508
  args = parser.parse_args(argv)
504
509
  project_root = Path(os.path.abspath(os.fspath(args.project_root)))
505
510
  requirements_source, requirements_root = _requirements_binding(
@@ -566,7 +571,25 @@ def main(argv: list[str] | None = None) -> int:
566
571
  target_version = _installed_process_version(
567
572
  python, cwd=environment_root
568
573
  )
569
- if target_version == current_version:
574
+ if args.check:
575
+ output = _run(
576
+ [
577
+ str(python),
578
+ "-I",
579
+ "-c",
580
+ "from engineering_process.cli import main; "
581
+ "raise SystemExit(main())",
582
+ "adoption",
583
+ "check",
584
+ "--project-root",
585
+ str(project_root),
586
+ "--requirements-lock",
587
+ str(requirements_source),
588
+ "--json",
589
+ ],
590
+ cwd=environment_root,
591
+ )
592
+ elif target_version == current_version:
570
593
  output = json.dumps(
571
594
  {
572
595
  "requirementsDigest": requirements_digest,
@@ -615,9 +638,11 @@ def main(argv: list[str] | None = None) -> int:
615
638
  if (
616
639
  not isinstance(result, dict)
617
640
  or result.get("requirementsDigest") != requirements_digest
641
+ or result.get("version") != target_version
618
642
  ):
619
643
  raise RuntimeError(
620
- "adoption authority did not attest the private requirements snapshot"
644
+ "adoption authority did not attest the installed version and "
645
+ "private requirements snapshot"
621
646
  )
622
647
  sys.stdout.write(output)
623
648
  return 0
@@ -262,7 +262,12 @@ class AdoptionRunnerTests(unittest.TestCase):
262
262
  self.assertEqual(observed_snapshot, [snapshot])
263
263
  self.assertTrue(source.samefile(checkout))
264
264
  self.assertEqual(digest, expected)
265
- return json.dumps({"requirementsDigest": digest})
265
+ return json.dumps(
266
+ {
267
+ "requirementsDigest": digest,
268
+ "version": "0.1.1",
269
+ }
270
+ )
266
271
  return ""
267
272
 
268
273
  with (
@@ -281,6 +286,61 @@ class AdoptionRunnerTests(unittest.TestCase):
281
286
  ),
282
287
  )
283
288
 
289
+ def test_check_uses_installed_authority_even_when_target_is_adopted(self):
290
+ runner = load_runner()
291
+ content = b"--only-binary :all:\nengineering-process==0.2.0\n"
292
+ digest = "sha256:" + hashlib.sha256(content).hexdigest()
293
+ with tempfile.TemporaryDirectory() as directory:
294
+ root = Path(directory)
295
+ self.write_process_lock(root, "0.2.0")
296
+ source = root / "requirements" / "process.txt"
297
+ source.parent.mkdir()
298
+ source.write_bytes(content)
299
+ adoption_commands: list[list[str]] = []
300
+
301
+ def fake_run(argv, *, cwd):
302
+ del cwd
303
+ if any("engineering_process.VERSION" in part for part in argv):
304
+ return "0.2.0\n"
305
+ if "adoption" in argv:
306
+ adoption_commands.append(argv)
307
+ self.assertIn("check", argv)
308
+ self.assertNotIn("apply", argv)
309
+ supplied = Path(argv[argv.index("--requirements-lock") + 1])
310
+ self.assertTrue(source.samefile(supplied))
311
+ return json.dumps(
312
+ {
313
+ "requirementsDigest": digest,
314
+ "status": "passed",
315
+ "version": "0.2.0",
316
+ }
317
+ )
318
+ return ""
319
+
320
+ with (
321
+ mock.patch.object(runner, "_run", side_effect=fake_run),
322
+ mock.patch.object(runner.sys.stdout, "write"),
323
+ ):
324
+ self.assertEqual(
325
+ 0,
326
+ runner.main(
327
+ [
328
+ "--project-root",
329
+ str(root),
330
+ "--requirements-lock",
331
+ "requirements/process.txt",
332
+ "--check",
333
+ ]
334
+ ),
335
+ )
336
+
337
+ self.assertEqual(1, len(adoption_commands))
338
+ self.assertIn("-I", adoption_commands[0])
339
+ self.assertIn(
340
+ "from engineering_process.cli import main; raise SystemExit(main())",
341
+ adoption_commands[0],
342
+ )
343
+
284
344
  def test_main_noops_when_the_hash_locked_target_is_already_adopted(self):
285
345
  runner = load_runner()
286
346
  content = b"--only-binary :all:\nengineering-process==0.1.1\n"