engineering-process 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {engineering_process-0.2.0/engineering_process.egg-info → engineering_process-0.2.1}/PKG-INFO +1 -1
- {engineering_process-0.2.0 → engineering_process-0.2.1}/VERSIONING.md +10 -1
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/__init__.py +1 -1
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/publication.py +57 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/release.py +0 -4
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/release_candidate.py +67 -2
- {engineering_process-0.2.0 → engineering_process-0.2.1/engineering_process.egg-info}/PKG-INFO +1 -1
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process.egg-info/SOURCES.txt +4 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/pyproject.toml +1 -1
- engineering_process-0.2.1/release.json +106 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/templates/adopt-process.py +27 -2
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_adoption_runner.py +61 -1
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_publication.py +53 -0
- engineering_process-0.2.1/tests/test_publish_event.py +57 -0
- engineering_process-0.2.1/tests/test_pypi_publication.py +156 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_release.py +43 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_release_candidate.py +157 -1
- engineering_process-0.2.1/tests/test_release_preparation.py +68 -0
- engineering_process-0.2.1/tests/test_release_qualification.py +99 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_self_hosting.py +182 -12
- engineering_process-0.2.0/release.json +0 -114
- {engineering_process-0.2.0 → engineering_process-0.2.1}/LICENSE +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/MANIFEST.in +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/PRODUCTION_STANDARD.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/README.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/bundles.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/__main__.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/_download_worker.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/_windows_job.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/adoption.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/artifact_attestation.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/bootstrap.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/bundles.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/cli.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/contracts.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/distribution.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/distribution_verify.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/environment.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/evidence.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/git.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/git_attributes.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/helper_launch.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/impact.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/lifecycle.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/managed.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/markdown.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/requirements-build.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/requirements-release.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/runner.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/runtime.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/skills.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/supervision.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/supplemental.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/syncing.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/tooling.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/adoption-migration.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/change.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/plan.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/project.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/release-change.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/release.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/examples/review.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/assess-design/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/build-frontend/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/build-frontend-foundation/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/change-api/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/cross-repo-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/define-change-contract/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/design-module/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/evolve-process/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/finish-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/govern-ui/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/implement-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/implement-module/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/implement-use-case/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/integrate-mcp/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/maintain-docs/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/plan-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/publish-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/review-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/run-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/run-change/references/execution.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/run-project-command/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/specify-use-case/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/process_assets/skills/verify-change/SKILL.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/adoption-migration.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/change.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/evidence-receipt.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/lifecycle.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/plan.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/process-lock.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/project.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/release-change.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/release.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/review.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/reviewer-attestation.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/supplemental-verification.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/schemas/verification.schema.json +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/setup.cfg +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/templates/AGENTS.process.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_adoption.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_artifact_attestation.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_bootstrap.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_bundles.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_cli.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_contracts.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_distribution.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_distribution_verify.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_environment.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_evals.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_impact.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_lifecycle.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_runner.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_runtime.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_schemas.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_skills.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_source_checkout.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_supervision.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_supplemental.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_syncing.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_tooling.py +0 -0
- {engineering_process-0.2.0 → engineering_process-0.2.1}/tests/test_windows_job.py +0 -0
{engineering_process-0.2.0/engineering_process.egg-info → engineering_process-0.2.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -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.
|
|
@@ -97,7 +104,9 @@ requirement pin, omits generated hashes or managed assets, or requires a post-me
|
|
|
97
104
|
step must fail closed.
|
|
98
105
|
|
|
99
106
|
`requirements/process.in` owns the direct public pin and `requirements/process.txt`
|
|
100
|
-
is its pip-compile hash lock.
|
|
107
|
+
is its pip-compile hash lock. The lock generator is pinned, and the committed lock must
|
|
108
|
+
cover compatible binary artifacts for every supported Python/OS runner rather than
|
|
109
|
+
only the platform that compiled it. Renovate's exact allowlisted post-upgrade command runs
|
|
101
110
|
`.process/adopt-process.py`; that managed runner creates a bounded temporary
|
|
102
111
|
environment outside the checkout, rejects symlink, junction, or reparse input in
|
|
103
112
|
every supplied lock-path component, and makes one stable private snapshot. Component
|
|
@@ -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}}"],
|
{engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process/release_candidate.py
RENAMED
|
@@ -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":
|
|
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":
|
|
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.",
|
{engineering_process-0.2.0 → engineering_process-0.2.1/engineering_process.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
{engineering_process-0.2.0 → engineering_process-0.2.1}/engineering_process.egg-info/SOURCES.txt
RENAMED
|
@@ -110,8 +110,12 @@ tests/test_evals.py
|
|
|
110
110
|
tests/test_impact.py
|
|
111
111
|
tests/test_lifecycle.py
|
|
112
112
|
tests/test_publication.py
|
|
113
|
+
tests/test_publish_event.py
|
|
114
|
+
tests/test_pypi_publication.py
|
|
113
115
|
tests/test_release.py
|
|
114
116
|
tests/test_release_candidate.py
|
|
117
|
+
tests/test_release_preparation.py
|
|
118
|
+
tests/test_release_qualification.py
|
|
115
119
|
tests/test_runner.py
|
|
116
120
|
tests/test_runtime.py
|
|
117
121
|
tests/test_schemas.py
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 3,
|
|
3
|
+
"previousVersion": "0.2.0",
|
|
4
|
+
"version": "0.2.1",
|
|
5
|
+
"classification": "patch",
|
|
6
|
+
"compatibility": "backward-compatible",
|
|
7
|
+
"schemaImpact": "unchanged",
|
|
8
|
+
"migration": null,
|
|
9
|
+
"identity": {
|
|
10
|
+
"package": "engineering-process",
|
|
11
|
+
"distribution": "engineering_process",
|
|
12
|
+
"tag": "v0.2.1",
|
|
13
|
+
"releaseName": "v0.2.1",
|
|
14
|
+
"runtimeVersion": {
|
|
15
|
+
"path": "engineering_process/__init__.py",
|
|
16
|
+
"variable": "VERSION"
|
|
17
|
+
},
|
|
18
|
+
"artifacts": [
|
|
19
|
+
"engineering_process-0.2.1-py3-none-any.whl",
|
|
20
|
+
"engineering_process-0.2.1.tar.gz"
|
|
21
|
+
],
|
|
22
|
+
"receiptAsset": "engineering-process-v0.2.1-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.2.1-evidence.json",
|
|
30
|
+
"project": "engineering-process",
|
|
31
|
+
"changeId": "release-0-2-1",
|
|
32
|
+
"cycle": 1
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"changes": [
|
|
36
|
+
{
|
|
37
|
+
"id": "bootstrap-self-adoption-verifier",
|
|
38
|
+
"type": "fix",
|
|
39
|
+
"surfaces": [
|
|
40
|
+
"adoption",
|
|
41
|
+
"self-hosting",
|
|
42
|
+
"workflow"
|
|
43
|
+
],
|
|
44
|
+
"rationale": "Verify bootstrap self-adoption with the target-aware producer template so the exact installed public authority performs the check even when the managed N-1 runner predates check-only mode."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "portable-process-lock-hashes",
|
|
48
|
+
"type": "fix",
|
|
49
|
+
"surfaces": [
|
|
50
|
+
"adoption",
|
|
51
|
+
"portability",
|
|
52
|
+
"supply-chain"
|
|
53
|
+
],
|
|
54
|
+
"rationale": "Regenerate process dependency locks with the complete binary wheel hash set and pin the lock generator so one immutable authority installs on every supported runtime and platform."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "post-v0-2-release-adoption-hardening",
|
|
58
|
+
"type": "fix",
|
|
59
|
+
"surfaces": [
|
|
60
|
+
"adoption",
|
|
61
|
+
"publication",
|
|
62
|
+
"workflow"
|
|
63
|
+
],
|
|
64
|
+
"rationale": "Correct event-driven publication recovery, validate Renovate metadata through a bounded exact contract, require strict consumer outcomes, and verify self-adoption through the public hash-locked authority instead of source under development."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "release-approval-app-token",
|
|
68
|
+
"type": "fix",
|
|
69
|
+
"surfaces": [
|
|
70
|
+
"publication",
|
|
71
|
+
"workflow"
|
|
72
|
+
],
|
|
73
|
+
"rationale": "Use the short-lived Release PR owning GitHub App token for the Draft-to-Ready transition after exact lifecycle and receipt authorization, while retaining GITHUB_TOKEN for commit status writes."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "release-lifecycle-quality-contract",
|
|
77
|
+
"type": "fix",
|
|
78
|
+
"surfaces": [
|
|
79
|
+
"contracts",
|
|
80
|
+
"publication",
|
|
81
|
+
"workflow"
|
|
82
|
+
],
|
|
83
|
+
"rationale": "Generate new release lifecycle runs with schema-3 production quality assessments and a bounded schema-2 plan so public N-1 authority can validate the Release PR candidate."
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "release-pr-self-adoption-refresh",
|
|
87
|
+
"type": "fix",
|
|
88
|
+
"surfaces": [
|
|
89
|
+
"adoption",
|
|
90
|
+
"publication",
|
|
91
|
+
"workflow"
|
|
92
|
+
],
|
|
93
|
+
"rationale": "Defer a new Release PR until the latest public authority is self-adopted, refresh that adoption through the exact verified publication event, and resume release derivation automatically after the adoption merge."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "release-review-qualification-gate",
|
|
97
|
+
"type": "fix",
|
|
98
|
+
"surfaces": [
|
|
99
|
+
"contracts",
|
|
100
|
+
"publication",
|
|
101
|
+
"workflow"
|
|
102
|
+
],
|
|
103
|
+
"rationale": "Derive release review quality from its registered contract and require a disposable complete public N-1 lifecycle rehearsal before source changes can merge."
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
@@ -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
|
|
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
|
|
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(
|
|
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"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
1
3
|
import subprocess
|
|
2
4
|
import tempfile
|
|
3
5
|
import unittest
|
|
@@ -96,6 +98,57 @@ class PublicationTests(unittest.TestCase):
|
|
|
96
98
|
),
|
|
97
99
|
)
|
|
98
100
|
|
|
101
|
+
def test_draft_renovate_pr_accepts_only_bounded_debug_metadata(self):
|
|
102
|
+
payload = base64.b64encode(
|
|
103
|
+
json.dumps(
|
|
104
|
+
{
|
|
105
|
+
"createdInVer": "44.37.1",
|
|
106
|
+
"updatedInVer": "44.37.1",
|
|
107
|
+
"targetBranch": "main",
|
|
108
|
+
"labels": [],
|
|
109
|
+
},
|
|
110
|
+
separators=(",", ":"),
|
|
111
|
+
).encode("utf-8")
|
|
112
|
+
).decode("ascii")
|
|
113
|
+
body = pr_body("pending") + f"\n<!--renovate-debug:{payload}-->\n"
|
|
114
|
+
|
|
115
|
+
self.assertEqual(
|
|
116
|
+
[],
|
|
117
|
+
validate_pull_request(
|
|
118
|
+
title="chore(deps): update actions/checkout action to v7",
|
|
119
|
+
body=body,
|
|
120
|
+
branch="automation/renovate/actions-checkout-7.x",
|
|
121
|
+
state="draft",
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def test_renovate_metadata_does_not_allow_arbitrary_html_or_contracts(self):
|
|
126
|
+
valid = {
|
|
127
|
+
"createdInVer": "44.37.1",
|
|
128
|
+
"updatedInVer": "44.37.1",
|
|
129
|
+
"targetBranch": "main",
|
|
130
|
+
"labels": [],
|
|
131
|
+
}
|
|
132
|
+
unexpected = {**valid, "extra": "not-allowed"}
|
|
133
|
+
encoded = base64.b64encode(
|
|
134
|
+
json.dumps(unexpected, separators=(",", ":")).encode("utf-8")
|
|
135
|
+
).decode("ascii")
|
|
136
|
+
bodies = (
|
|
137
|
+
pr_body("pending") + "\n<div>hidden policy</div>\n",
|
|
138
|
+
pr_body("pending") + "\n<!--renovate-debug:not-base64-->\n",
|
|
139
|
+
pr_body("pending") + f"\n<!--renovate-debug:{encoded}-->\n",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
for body in bodies:
|
|
143
|
+
with self.subTest(body=body[-100:]):
|
|
144
|
+
self.assertTrue(
|
|
145
|
+
validate_pull_request(
|
|
146
|
+
title="chore(deps): update actions/checkout action to v7",
|
|
147
|
+
body=body,
|
|
148
|
+
branch="automation/renovate/actions-checkout-7.x",
|
|
149
|
+
state="draft",
|
|
150
|
+
)
|
|
151
|
+
)
|
|
99
152
|
def test_project_extensions_cannot_redefine_common_policy(self):
|
|
100
153
|
duplicate_section = pr_body() + (
|
|
101
154
|
"\n## Independent review\n\nIndependent review is not required here.\n"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from verification.validate_publish_event import (
|
|
4
|
+
PublishEventError,
|
|
5
|
+
validate_publish_event,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def event() -> dict[str, object]:
|
|
10
|
+
return {
|
|
11
|
+
"action": "engineering-process-release-ready",
|
|
12
|
+
"client_payload": {
|
|
13
|
+
"attestationDigest": f"sha256:{'a' * 64}",
|
|
14
|
+
"commit": "b" * 40,
|
|
15
|
+
"repository": "phuongnse/engineering-process",
|
|
16
|
+
"tag": "v0.2.0",
|
|
17
|
+
"version": "0.2.0",
|
|
18
|
+
},
|
|
19
|
+
"repository": {"full_name": "phuongnse/engineering-process"},
|
|
20
|
+
"sender": {"login": "phuongnse-renovate-ops[bot]"},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PublishEventTests(unittest.TestCase):
|
|
25
|
+
def test_exact_app_authored_event_is_accepted(self):
|
|
26
|
+
self.assertEqual(event()["client_payload"], validate_publish_event(event()))
|
|
27
|
+
|
|
28
|
+
def test_different_sender_is_rejected(self):
|
|
29
|
+
candidate = event()
|
|
30
|
+
candidate["sender"]["login"] = "phuongnse"
|
|
31
|
+
|
|
32
|
+
with self.assertRaisesRegex(PublishEventError, "release GitHub App"):
|
|
33
|
+
validate_publish_event(candidate)
|
|
34
|
+
|
|
35
|
+
def test_extra_payload_authority_is_rejected(self):
|
|
36
|
+
candidate = event()
|
|
37
|
+
candidate["client_payload"]["skipExisting"] = True
|
|
38
|
+
|
|
39
|
+
with self.assertRaisesRegex(PublishEventError, "unexpected fields"):
|
|
40
|
+
validate_publish_event(candidate)
|
|
41
|
+
|
|
42
|
+
def test_tag_commit_and_digest_are_bound(self):
|
|
43
|
+
cases = (
|
|
44
|
+
("tag", "v0.2.1", "tag does not match"),
|
|
45
|
+
("commit", "pending", "invalid commit"),
|
|
46
|
+
("attestationDigest", "sha256:pending", "attestation digest"),
|
|
47
|
+
)
|
|
48
|
+
for field, value, message in cases:
|
|
49
|
+
with self.subTest(field=field):
|
|
50
|
+
candidate = event()
|
|
51
|
+
candidate["client_payload"][field] = value
|
|
52
|
+
with self.assertRaisesRegex(PublishEventError, message):
|
|
53
|
+
validate_publish_event(candidate)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
unittest.main()
|