engineering-process 1.2.4__tar.gz → 1.2.6__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-1.2.4 → engineering_process-1.2.6}/MANIFEST.in +1 -1
- {engineering_process-1.2.4/engineering_process.egg-info → engineering_process-1.2.6}/PKG-INFO +42 -1
- {engineering_process-1.2.4 → engineering_process-1.2.6}/README.md +41 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/__init__.py +1 -1
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/contracts.py +8 -3
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/production_engineering.py +6 -1
- {engineering_process-1.2.4 → engineering_process-1.2.6/engineering_process.egg-info}/PKG-INFO +42 -1
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/SOURCES.txt +1 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/production-engineering/SKILL.md +5 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/pyproject.toml +2 -1
- engineering_process-1.2.6/templates/renovate.json +6 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_automation.py +45 -1
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_contracts.py +18 -1
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_lifecycle.py +37 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_production_engineering.py +25 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_release.py +43 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/LICENSE +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/__main__.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_supervisor_contract.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_windows_job.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/adoption.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/cli.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/commands.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/distribution.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/helper_launch.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/lifecycle.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/project.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/publication_compat.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/release.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/repository.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/skills.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/supervision.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process-graph.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/finish-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/implement-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/improve-process/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/plan-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/production-engineering/invariants.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/review-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/run-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/start-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/verify-change/SKILL.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/change.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/plan.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/process-graph.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/process-lock.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/production-engineering.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/project.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/receipt.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/release-change.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/release.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/review.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/run.schema.json +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/setup.cfg +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/templates/AGENTS.process.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/templates/adopt-process.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_adoption.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_architecture.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_cli.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_commands.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_distribution.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_publication_compat.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_pypi_cache_horizon.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_repository.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_skills.py +0 -0
- {engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_supervisor_posix.py +0 -0
{engineering_process-1.2.4/engineering_process.egg-info → engineering_process-1.2.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
4
4
|
Summary: A small agent-neutral engineering lifecycle with managed adoption
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/phuongnse/engineering-process
|
|
@@ -441,6 +441,14 @@ At any point:
|
|
|
441
441
|
|
|
442
442
|
## Release to consumer PR
|
|
443
443
|
|
|
444
|
+
This producer's release identity inputs and text assets declared by
|
|
445
|
+
`tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
|
|
446
|
+
Writers select that representation explicitly; JSON writers use
|
|
447
|
+
`write_json_atomic`. Existing distribution verification checks the declared text
|
|
448
|
+
bytes before building. It rejects CRLF, mixed endings, BOM and invalid UTF-8 even
|
|
449
|
+
when text-mode reads or Git conversion would hide the difference. Binary data and
|
|
450
|
+
fixtures outside that text inventory retain their own format contracts.
|
|
451
|
+
|
|
444
452
|
Every opted-in consumer uses Renovate's pip-compile manager. Its engineering-process
|
|
445
453
|
package rule keeps the adoption pull request in draft and runs exactly:
|
|
446
454
|
|
|
@@ -463,6 +471,39 @@ authorizes that merge.
|
|
|
463
471
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
464
472
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
465
473
|
|
|
474
|
+
The optional [Renovate preset](templates/renovate.json) is generated from the
|
|
475
|
+
canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
|
|
476
|
+
dependency selection, supported platforms, schedules, major-update approval,
|
|
477
|
+
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
478
|
+
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
479
|
+
|
|
480
|
+
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
481
|
+
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
482
|
+
commit of a verified release that contains the preset. Remove obsolete inline
|
|
483
|
+
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
484
|
+
The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
|
|
485
|
+
it does not claim compatibility with earlier publication contracts. A future
|
|
486
|
+
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
487
|
+
|
|
488
|
+
Renovate resolves presets from the protected base configuration before dependency
|
|
489
|
+
updates and post-upgrade tasks. Bootstrap the compatible preset in that base before
|
|
490
|
+
relying on its generated drafts; changing the candidate configuration alone cannot
|
|
491
|
+
repair the same run's body. Verify the native rendered body against both the base
|
|
492
|
+
and candidate process authority during adoption. Pending fields and unchecked
|
|
493
|
+
Completion gate items are proposals, never evidence or approval.
|
|
494
|
+
|
|
495
|
+
Before collecting lifecycle evidence for a bot PR, apply its configured
|
|
496
|
+
`stopUpdatingLabel` (Renovate's default is `stop-updating`) and confirm the head is
|
|
497
|
+
unchanged after any in-flight bot run finishes. Keep the label through verification,
|
|
498
|
+
independent review, receipt and the consumer-owned merge. Do not request a native
|
|
499
|
+
rebase or dashboard retry while reviewing: an explicit retry can resume updates.
|
|
500
|
+
If the candidate changes, open a new implementation cycle and collect fresh
|
|
501
|
+
evidence. Remove the pause label only when returning the PR to automation.
|
|
502
|
+
|
|
503
|
+
These are native [shared preset](https://docs.renovatebot.com/config-presets/) and
|
|
504
|
+
[review pause](https://docs.renovatebot.com/configuration-options/#stopupdatinglabel)
|
|
505
|
+
boundaries; the control plane does not become a second PR publisher.
|
|
506
|
+
|
|
466
507
|
## Compatibility
|
|
467
508
|
|
|
468
509
|
Version 1.x retains a few small pre-1.0 command shapes so existing consumers can
|
|
@@ -417,6 +417,14 @@ At any point:
|
|
|
417
417
|
|
|
418
418
|
## Release to consumer PR
|
|
419
419
|
|
|
420
|
+
This producer's release identity inputs and text assets declared by
|
|
421
|
+
`tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
|
|
422
|
+
Writers select that representation explicitly; JSON writers use
|
|
423
|
+
`write_json_atomic`. Existing distribution verification checks the declared text
|
|
424
|
+
bytes before building. It rejects CRLF, mixed endings, BOM and invalid UTF-8 even
|
|
425
|
+
when text-mode reads or Git conversion would hide the difference. Binary data and
|
|
426
|
+
fixtures outside that text inventory retain their own format contracts.
|
|
427
|
+
|
|
420
428
|
Every opted-in consumer uses Renovate's pip-compile manager. Its engineering-process
|
|
421
429
|
package rule keeps the adoption pull request in draft and runs exactly:
|
|
422
430
|
|
|
@@ -439,6 +447,39 @@ authorizes that merge.
|
|
|
439
447
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
440
448
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
441
449
|
|
|
450
|
+
The optional [Renovate preset](templates/renovate.json) is generated from the
|
|
451
|
+
canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
|
|
452
|
+
dependency selection, supported platforms, schedules, major-update approval,
|
|
453
|
+
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
454
|
+
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
455
|
+
|
|
456
|
+
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
457
|
+
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
458
|
+
commit of a verified release that contains the preset. Remove obsolete inline
|
|
459
|
+
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
460
|
+
The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
|
|
461
|
+
it does not claim compatibility with earlier publication contracts. A future
|
|
462
|
+
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
463
|
+
|
|
464
|
+
Renovate resolves presets from the protected base configuration before dependency
|
|
465
|
+
updates and post-upgrade tasks. Bootstrap the compatible preset in that base before
|
|
466
|
+
relying on its generated drafts; changing the candidate configuration alone cannot
|
|
467
|
+
repair the same run's body. Verify the native rendered body against both the base
|
|
468
|
+
and candidate process authority during adoption. Pending fields and unchecked
|
|
469
|
+
Completion gate items are proposals, never evidence or approval.
|
|
470
|
+
|
|
471
|
+
Before collecting lifecycle evidence for a bot PR, apply its configured
|
|
472
|
+
`stopUpdatingLabel` (Renovate's default is `stop-updating`) and confirm the head is
|
|
473
|
+
unchanged after any in-flight bot run finishes. Keep the label through verification,
|
|
474
|
+
independent review, receipt and the consumer-owned merge. Do not request a native
|
|
475
|
+
rebase or dashboard retry while reviewing: an explicit retry can resume updates.
|
|
476
|
+
If the candidate changes, open a new implementation cycle and collect fresh
|
|
477
|
+
evidence. Remove the pause label only when returning the PR to automation.
|
|
478
|
+
|
|
479
|
+
These are native [shared preset](https://docs.renovatebot.com/config-presets/) and
|
|
480
|
+
[review pause](https://docs.renovatebot.com/configuration-options/#stopupdatinglabel)
|
|
481
|
+
boundaries; the control plane does not become a second PR publisher.
|
|
482
|
+
|
|
442
483
|
## Compatibility
|
|
443
484
|
|
|
444
485
|
Version 1.x retains a few small pre-1.0 command shapes so existing consumers can
|
|
@@ -67,9 +67,9 @@ def digest_json(value: Any) -> str:
|
|
|
67
67
|
return "sha256:" + hashlib.sha256(canonical_bytes(value)).hexdigest()
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def
|
|
71
|
-
"""
|
|
72
|
-
|
|
70
|
+
def formatted_json_bytes(value: Any) -> bytes:
|
|
71
|
+
"""Serialize human-readable JSON as UTF-8 without BOM, using LF."""
|
|
72
|
+
return (
|
|
73
73
|
json.dumps(
|
|
74
74
|
value,
|
|
75
75
|
ensure_ascii=False,
|
|
@@ -79,6 +79,11 @@ def write_json_atomic(path: Path, value: Any) -> None:
|
|
|
79
79
|
)
|
|
80
80
|
+ "\n"
|
|
81
81
|
).encode("utf-8")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def write_json_atomic(path: Path, value: Any) -> None:
|
|
85
|
+
"""Write canonical human-readable JSON without exposing a partial file."""
|
|
86
|
+
data = formatted_json_bytes(value)
|
|
82
87
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
83
88
|
temporary = path.with_name(f".{path.name}.tmp")
|
|
84
89
|
try:
|
|
@@ -98,7 +98,12 @@ def validate_review_assessments(review: dict[str, Any], process_root: Path) -> N
|
|
|
98
98
|
unlinked = sorted(
|
|
99
99
|
finding_id
|
|
100
100
|
for finding_id, finding in findings.items()
|
|
101
|
-
if finding["origin"] == "production-invariant"
|
|
101
|
+
if finding["origin"] == "production-invariant"
|
|
102
|
+
and finding_id not in linked
|
|
103
|
+
and not (
|
|
104
|
+
finding["severity"] == "non-blocking"
|
|
105
|
+
and finding.get("disposition", {}).get("status") == "resolved"
|
|
106
|
+
)
|
|
102
107
|
)
|
|
103
108
|
if unlinked:
|
|
104
109
|
raise ProcessError(
|
{engineering_process-1.2.4 → engineering_process-1.2.6/engineering_process.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
4
4
|
Summary: A small agent-neutral engineering lifecycle with managed adoption
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/phuongnse/engineering-process
|
|
@@ -441,6 +441,14 @@ At any point:
|
|
|
441
441
|
|
|
442
442
|
## Release to consumer PR
|
|
443
443
|
|
|
444
|
+
This producer's release identity inputs and text assets declared by
|
|
445
|
+
`tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
|
|
446
|
+
Writers select that representation explicitly; JSON writers use
|
|
447
|
+
`write_json_atomic`. Existing distribution verification checks the declared text
|
|
448
|
+
bytes before building. It rejects CRLF, mixed endings, BOM and invalid UTF-8 even
|
|
449
|
+
when text-mode reads or Git conversion would hide the difference. Binary data and
|
|
450
|
+
fixtures outside that text inventory retain their own format contracts.
|
|
451
|
+
|
|
444
452
|
Every opted-in consumer uses Renovate's pip-compile manager. Its engineering-process
|
|
445
453
|
package rule keeps the adoption pull request in draft and runs exactly:
|
|
446
454
|
|
|
@@ -463,6 +471,39 @@ authorizes that merge.
|
|
|
463
471
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
464
472
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
465
473
|
|
|
474
|
+
The optional [Renovate preset](templates/renovate.json) is generated from the
|
|
475
|
+
canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
|
|
476
|
+
dependency selection, supported platforms, schedules, major-update approval,
|
|
477
|
+
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
478
|
+
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
479
|
+
|
|
480
|
+
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
481
|
+
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
482
|
+
commit of a verified release that contains the preset. Remove obsolete inline
|
|
483
|
+
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
484
|
+
The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
|
|
485
|
+
it does not claim compatibility with earlier publication contracts. A future
|
|
486
|
+
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
487
|
+
|
|
488
|
+
Renovate resolves presets from the protected base configuration before dependency
|
|
489
|
+
updates and post-upgrade tasks. Bootstrap the compatible preset in that base before
|
|
490
|
+
relying on its generated drafts; changing the candidate configuration alone cannot
|
|
491
|
+
repair the same run's body. Verify the native rendered body against both the base
|
|
492
|
+
and candidate process authority during adoption. Pending fields and unchecked
|
|
493
|
+
Completion gate items are proposals, never evidence or approval.
|
|
494
|
+
|
|
495
|
+
Before collecting lifecycle evidence for a bot PR, apply its configured
|
|
496
|
+
`stopUpdatingLabel` (Renovate's default is `stop-updating`) and confirm the head is
|
|
497
|
+
unchanged after any in-flight bot run finishes. Keep the label through verification,
|
|
498
|
+
independent review, receipt and the consumer-owned merge. Do not request a native
|
|
499
|
+
rebase or dashboard retry while reviewing: an explicit retry can resume updates.
|
|
500
|
+
If the candidate changes, open a new implementation cycle and collect fresh
|
|
501
|
+
evidence. Remove the pause label only when returning the PR to automation.
|
|
502
|
+
|
|
503
|
+
These are native [shared preset](https://docs.renovatebot.com/config-presets/) and
|
|
504
|
+
[review pause](https://docs.renovatebot.com/configuration-options/#stopupdatinglabel)
|
|
505
|
+
boundaries; the control plane does not become a second PR publisher.
|
|
506
|
+
|
|
466
507
|
## Compatibility
|
|
467
508
|
|
|
468
509
|
Version 1.x retains a few small pre-1.0 command shapes so existing consumers can
|
|
@@ -29,6 +29,11 @@ Verification proves only the deterministic properties exercised by its exact
|
|
|
29
29
|
commands on the unchanged candidate. It must not claim to infer architecture,
|
|
30
30
|
security, intent, or arbitrary prose semantics that those commands do not observe.
|
|
31
31
|
|
|
32
|
+
When an artifact claim depends on byte identity, make encoding and newline rules
|
|
33
|
+
explicit at its writer and compare bytes at the exported or installed boundary.
|
|
34
|
+
Text-mode newline normalization and a clean Git diff cannot supply that proof;
|
|
35
|
+
follow the artifact owner's format rather than imposing one text policy on consumers.
|
|
36
|
+
|
|
32
37
|
Independent review reassesses every invariant against the contract, plan, complete
|
|
33
38
|
diff, profile evidence, and readiness result. Mark it `satisfied`, `not-applicable`,
|
|
34
39
|
or `violated`, give a concrete rationale, and cite evidence for `satisfied`. A
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "engineering-process"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.6"
|
|
8
8
|
description = "A small agent-neutral engineering lifecycle with managed adoption"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -42,6 +42,7 @@ dev = {file = ["engineering_process/requirements-dev.txt"]}
|
|
|
42
42
|
"share/engineering-process/templates" = [
|
|
43
43
|
"templates/AGENTS.process.md",
|
|
44
44
|
"templates/PULL_REQUEST_TEMPLATE.md",
|
|
45
|
+
"templates/renovate.json",
|
|
45
46
|
"templates/adopt-process.py",
|
|
46
47
|
"templates/adopt-process-windows-job.py",
|
|
47
48
|
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
3
|
+
"description": "Pending process draft; generated from templates/PULL_REQUEST_TEMPLATE.md.",
|
|
4
|
+
"prBodyTemplate": "{{{header}}}",
|
|
5
|
+
"prHeader": "## Summary\n\n- Outcome: Update {{#each upgrades}}{{depName}} from {{currentValue}}{{#if currentDigest}} ({{currentDigest}}){{/if}} to {{newValue}}{{#if newDigest}} ({{newDigest}}){{/if}}{{#unless @last}}; {{/unless}}{{/each}}.\n- Scope: {{#each upgrades}}{{packageFile}}{{#unless @last}}; {{/unless}}{{/each}}.\n\n## Contract and risk\n\n- Source: pending\n- Risk: pending\n- Compatibility: pending\n- Stack: pending\n\n## Verification\n\n- Profiles: pending\n- Snapshot: pending\n- Completion receipt: pending\n\n## Independent review\n\n- Verdict: pending\n- Cycles: pending\n- Blocking findings: pending\n- Non-blocking dispositions: pending\n\n## Completion gate\n\n- [ ] Accepted scope is implemented without silent expansion.\n- [ ] Required profiles pass on the reviewed snapshot.\n- [ ] Independent review approved with no blocking finding.\n- [ ] Every non-blocking finding has a recorded disposition.\n"
|
|
6
|
+
}
|
|
@@ -3,9 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import re
|
|
6
|
+
import tempfile
|
|
6
7
|
import unittest
|
|
8
|
+
from unittest.mock import patch
|
|
7
9
|
|
|
8
|
-
from engineering_process.
|
|
10
|
+
from engineering_process.contracts import ProcessError
|
|
11
|
+
from engineering_process.publication_compat import _pull_request_body_issues, validate_pull_request
|
|
12
|
+
from verification.generate_renovate_preset import generate_preset
|
|
13
|
+
from verification import generate_renovate_preset
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
ROOT = Path(__file__).resolve().parent.parent
|
|
@@ -15,6 +20,45 @@ ACTIVE_PROCESS_PIN = re.compile(
|
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
class AutomationTests(unittest.TestCase):
|
|
23
|
+
def test_renovate_preset_matches_the_canonical_pending_template(self) -> None:
|
|
24
|
+
template = (ROOT / "templates" / "PULL_REQUEST_TEMPLATE.md").read_text(encoding="utf-8")
|
|
25
|
+
preset = json.loads((ROOT / "templates" / "renovate.json").read_text(encoding="utf-8"))
|
|
26
|
+
self.assertEqual(generate_preset(template), preset)
|
|
27
|
+
self.assertEqual([], _pull_request_body_issues(preset["prHeader"], "draft"))
|
|
28
|
+
self.assertTrue(_pull_request_body_issues(preset["prHeader"], "ready"))
|
|
29
|
+
|
|
30
|
+
def test_renovate_preset_rejects_an_incomplete_public_contract(self) -> None:
|
|
31
|
+
template = (ROOT / "templates" / "PULL_REQUEST_TEMPLATE.md").read_text(encoding="utf-8")
|
|
32
|
+
with self.assertRaises(ProcessError):
|
|
33
|
+
generate_preset(template.replace("## Completion gate", "## Other"))
|
|
34
|
+
|
|
35
|
+
def test_renovate_generator_rejects_changed_bytes_and_recovers(self) -> None:
|
|
36
|
+
template = (ROOT / "templates" / "PULL_REQUEST_TEMPLATE.md").read_bytes()
|
|
37
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
38
|
+
root = Path(directory)
|
|
39
|
+
(root / "templates").mkdir()
|
|
40
|
+
(root / "templates" / "PULL_REQUEST_TEMPLATE.md").write_bytes(template)
|
|
41
|
+
target = root / "templates" / "renovate.json"
|
|
42
|
+
with patch.object(generate_renovate_preset, "PROJECT_ROOT", root), patch("sys.argv", ["generate"]):
|
|
43
|
+
self.assertEqual(0, generate_renovate_preset.main())
|
|
44
|
+
expected = target.read_bytes()
|
|
45
|
+
self.assertNotIn(b"\r", expected)
|
|
46
|
+
self.assertFalse(expected.startswith(b"\xef\xbb\xbf"))
|
|
47
|
+
for changed in (
|
|
48
|
+
expected.replace(b"\n", b"\r\n"),
|
|
49
|
+
expected.replace(b"\n", b"\r\n", 1),
|
|
50
|
+
b"\xef\xbb\xbf" + expected,
|
|
51
|
+
):
|
|
52
|
+
with self.subTest(changed=changed[:20]):
|
|
53
|
+
target.write_bytes(changed)
|
|
54
|
+
with patch("sys.argv", ["generate", "--check"]):
|
|
55
|
+
with self.assertRaisesRegex(ProcessError, "is stale"):
|
|
56
|
+
generate_renovate_preset.main()
|
|
57
|
+
self.assertEqual(0, generate_renovate_preset.main())
|
|
58
|
+
self.assertEqual(expected, target.read_bytes())
|
|
59
|
+
with patch("sys.argv", ["generate", "--check"]):
|
|
60
|
+
self.assertEqual(0, generate_renovate_preset.main())
|
|
61
|
+
|
|
18
62
|
def test_pull_request_template_defines_public_evidence_hierarchy(self) -> None:
|
|
19
63
|
template = (ROOT / "templates" / "PULL_REQUEST_TEMPLATE.md").read_text(
|
|
20
64
|
encoding="utf-8"
|
|
@@ -2,11 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from copy import deepcopy
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
import tempfile
|
|
5
6
|
import unittest
|
|
6
7
|
|
|
7
8
|
from jsonschema import Draft202012Validator
|
|
8
9
|
|
|
9
|
-
from engineering_process.contracts import
|
|
10
|
+
from engineering_process.contracts import (
|
|
11
|
+
ProcessError,
|
|
12
|
+
formatted_json_bytes,
|
|
13
|
+
read_json,
|
|
14
|
+
validate_document,
|
|
15
|
+
write_json_atomic,
|
|
16
|
+
)
|
|
10
17
|
from engineering_process.distribution import schemas_root
|
|
11
18
|
from engineering_process.project import (
|
|
12
19
|
load_project,
|
|
@@ -21,6 +28,16 @@ SCHEMAS = schemas_root(ROOT)
|
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
class ContractTests(unittest.TestCase):
|
|
31
|
+
def test_json_documents_have_one_utf8_lf_representation(self) -> None:
|
|
32
|
+
document = {"z": "\u0111", "a": 1}
|
|
33
|
+
expected = b'{\n "a": 1,\n "z": "\xc4\x91"\n}\n'
|
|
34
|
+
self.assertEqual(expected, formatted_json_bytes(document))
|
|
35
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
36
|
+
target = Path(directory) / "document.json"
|
|
37
|
+
write_json_atomic(target, document)
|
|
38
|
+
self.assertEqual(expected, target.read_bytes())
|
|
39
|
+
self.assertEqual(document, read_json(target))
|
|
40
|
+
|
|
24
41
|
def test_every_schema_is_valid_and_is_used(self) -> None:
|
|
25
42
|
expected = {
|
|
26
43
|
"change",
|
|
@@ -599,6 +599,43 @@ class LifecycleTests(unittest.TestCase):
|
|
|
599
599
|
self.assertEqual("implementing", state["phase"])
|
|
600
600
|
self.assertEqual({}, state["verification"])
|
|
601
601
|
|
|
602
|
+
def test_resolved_invariant_finishes_with_its_identity_and_history_preserved(self) -> None:
|
|
603
|
+
self.begin()
|
|
604
|
+
self.verify_all()
|
|
605
|
+
start_review(self.root, PROCESS_ROOT, "sample-change", actor_id="reviewer", context_id="review-context", kind="agent")
|
|
606
|
+
first = self.review_document("changes-requested")
|
|
607
|
+
first["findings"][0]["priority"] = "P2"
|
|
608
|
+
first_path = self.root / ".process" / "runs" / "first-review.json"
|
|
609
|
+
write_json(first_path, first)
|
|
610
|
+
state = submit_review(self.root, PROCESS_ROOT, "sample-change", first_path)
|
|
611
|
+
self.assertEqual("changes-requested", state["phase"])
|
|
612
|
+
|
|
613
|
+
begin_implementation(self.root, PROCESS_ROOT, "sample-change", actor_id="implementer", context_id="implementation-context-2", kind="agent")
|
|
614
|
+
(self.root / "product.txt").write_text("corrected\n", encoding="utf-8")
|
|
615
|
+
self.verify_all()
|
|
616
|
+
start_review(self.root, PROCESS_ROOT, "sample-change", actor_id="reviewer", context_id="review-context", kind="agent")
|
|
617
|
+
approved = self.review_document("approved")
|
|
618
|
+
carried = deepcopy(first["findings"][0])
|
|
619
|
+
carried["severity"] = "non-blocking"
|
|
620
|
+
carried["disposition"] = {"status": "resolved", "rationale": "The corrected candidate satisfies the invariant with fresh verification."}
|
|
621
|
+
approved["findings"] = [carried]
|
|
622
|
+
second_path = self.root / ".process" / "runs" / "second-review.json"
|
|
623
|
+
for field, changed in (("priority", "P3"), ("origin", "contract")):
|
|
624
|
+
invalid = deepcopy(approved)
|
|
625
|
+
invalid["findings"][0][field] = changed
|
|
626
|
+
write_json(second_path, invalid)
|
|
627
|
+
with self.subTest(field=field), self.assertRaisesRegex(ProcessError, "identity fields are immutable"):
|
|
628
|
+
submit_review(self.root, PROCESS_ROOT, "sample-change", second_path)
|
|
629
|
+
write_json(second_path, approved)
|
|
630
|
+
state = submit_review(self.root, PROCESS_ROOT, "sample-change", second_path)
|
|
631
|
+
self.assertEqual("approved", state["phase"])
|
|
632
|
+
self.assertEqual(first, state["reviewHistory"][0]["document"])
|
|
633
|
+
self.assertEqual(approved, state["reviewHistory"][1]["document"])
|
|
634
|
+
state, receipt = finish_change(self.root, PROCESS_ROOT, "sample-change", actor_id="coordinator", context_id="finish-context", kind="agent")
|
|
635
|
+
self.assertEqual("completed", state["phase"])
|
|
636
|
+
self.assertEqual(2, state["cycle"])
|
|
637
|
+
self.assertEqual("approved", receipt["review"]["verdict"])
|
|
638
|
+
|
|
602
639
|
def test_correction_review_requires_the_original_reviewer_identity(self) -> None:
|
|
603
640
|
self.begin()
|
|
604
641
|
self.verify_all()
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/tests/test_production_engineering.py
RENAMED
|
@@ -190,6 +190,31 @@ class ProductionEngineeringTests(unittest.TestCase):
|
|
|
190
190
|
with self.assertRaisesRegex(ProcessError, "blocking finding"):
|
|
191
191
|
validate_review_assessments(violation, ROOT)
|
|
192
192
|
|
|
193
|
+
def test_resolved_invariant_findings_do_not_require_a_current_violation(self) -> None:
|
|
194
|
+
review = deepcopy(self.review)
|
|
195
|
+
review["findings"] = [{
|
|
196
|
+
"id": "resolved-invariant",
|
|
197
|
+
"severity": "non-blocking",
|
|
198
|
+
"priority": "P2",
|
|
199
|
+
"criterionId": "works",
|
|
200
|
+
"origin": "production-invariant",
|
|
201
|
+
"summary": "The previous invariant violation is resolved on this snapshot.",
|
|
202
|
+
"disposition": {"status": "resolved", "rationale": "The corrected structure and current evidence satisfy the invariant."},
|
|
203
|
+
}]
|
|
204
|
+
validate_document(review, "review", schema_root=SCHEMAS)
|
|
205
|
+
validate_review_assessments(review, ROOT)
|
|
206
|
+
for status in ("accepted-risk", "tracked-follow-up"):
|
|
207
|
+
with self.subTest(status=status):
|
|
208
|
+
review["findings"][0]["disposition"] = {
|
|
209
|
+
"status": status,
|
|
210
|
+
"rationale": "The invariant violation remains open.",
|
|
211
|
+
"owner": "consumer-owner",
|
|
212
|
+
"recordUrl": "https://example.invalid/issues/1",
|
|
213
|
+
}
|
|
214
|
+
validate_document(review, "review", schema_root=SCHEMAS)
|
|
215
|
+
with self.assertRaisesRegex(ProcessError, "must be linked"):
|
|
216
|
+
validate_review_assessments(review, ROOT)
|
|
217
|
+
|
|
193
218
|
def test_review_cannot_approve_a_violation_or_leave_invariant_finding_unlinked(self) -> None:
|
|
194
219
|
violation = deepcopy(self.review)
|
|
195
220
|
assessment = violation["productionEngineering"][0]
|
|
@@ -12,12 +12,54 @@ import unittest
|
|
|
12
12
|
from engineering_process.contracts import ProcessError, read_json, write_json_atomic
|
|
13
13
|
from engineering_process.release import derive_next_version, validate_release
|
|
14
14
|
from verification.normalize_sdist import normalize
|
|
15
|
+
from verification.prepare_release import _replace_once
|
|
16
|
+
from verification.verify_distribution import validate_distribution_text
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
ROOT = Path(__file__).resolve().parent.parent
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ReleaseTests(unittest.TestCase):
|
|
23
|
+
def test_release_replacement_writes_explicit_lf_on_every_host(self) -> None:
|
|
24
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
25
|
+
target = Path(directory) / "version.txt"
|
|
26
|
+
for source in (b"version=old\nnext\n", b"version=old\r\nnext\r\n", b"version=old\r\nnext\n"):
|
|
27
|
+
with self.subTest(source=source):
|
|
28
|
+
target.write_bytes(source)
|
|
29
|
+
_replace_once(target, "old", "new")
|
|
30
|
+
self.assertEqual(b"version=new\nnext\n", target.read_bytes())
|
|
31
|
+
|
|
32
|
+
def test_release_replacement_rejects_bom_before_writing(self) -> None:
|
|
33
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
34
|
+
target = Path(directory) / "version.txt"
|
|
35
|
+
original = b'\xef\xbb\xbfVERSION = "old"\r\n'
|
|
36
|
+
target.write_bytes(original)
|
|
37
|
+
with self.assertRaisesRegex(ProcessError, "UTF-8 without BOM"):
|
|
38
|
+
_replace_once(target, "old", "new")
|
|
39
|
+
self.assertEqual(original, target.read_bytes())
|
|
40
|
+
self.assertFalse(target.with_name(f".{target.name}.release.tmp").exists())
|
|
41
|
+
|
|
42
|
+
def test_distribution_text_uses_the_declared_inventory_and_rejects_byte_drift(self) -> None:
|
|
43
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
44
|
+
root = Path(directory)
|
|
45
|
+
(root / "engineering_process").mkdir()
|
|
46
|
+
(root / "assets").mkdir()
|
|
47
|
+
(root / "pyproject.toml").write_bytes(b'[tool.setuptools.data-files]\n"share/process" = ["assets/newly-declared.fragment"]\n')
|
|
48
|
+
(root / "release.json").write_bytes(b"{}\n")
|
|
49
|
+
(root / "engineering_process" / "__init__.py").write_bytes(b'VERSION = "1.0.0"\n')
|
|
50
|
+
(root / "opaque.bin").write_bytes(b"\x00\xff\r\n")
|
|
51
|
+
target = root / "assets" / "newly-declared.fragment"
|
|
52
|
+
valid = b"first\nsecond\n"
|
|
53
|
+
target.write_bytes(valid)
|
|
54
|
+
validate_distribution_text(root)
|
|
55
|
+
for invalid in (b"first\r\nsecond\r\n", b"first\r\nsecond\n", b"\xef\xbb\xbf" + valid, b"\xff\n"):
|
|
56
|
+
with self.subTest(invalid=invalid):
|
|
57
|
+
target.write_bytes(invalid)
|
|
58
|
+
with self.assertRaisesRegex(RuntimeError, "UTF-8 without BOM and use LF"):
|
|
59
|
+
validate_distribution_text(root)
|
|
60
|
+
target.write_bytes(valid)
|
|
61
|
+
validate_distribution_text(root)
|
|
62
|
+
|
|
21
63
|
def test_current_release_identity_is_consistent(self) -> None:
|
|
22
64
|
release = read_json(ROOT / "release.json")
|
|
23
65
|
result = validate_release(ROOT, ROOT, tag=f"v{release['version']}")
|
|
@@ -90,6 +132,7 @@ class ReleaseTests(unittest.TestCase):
|
|
|
90
132
|
self.assertIn(
|
|
91
133
|
f'version = "{expected}"', (target / "pyproject.toml").read_text()
|
|
92
134
|
)
|
|
135
|
+
validate_distribution_text(target)
|
|
93
136
|
|
|
94
137
|
def test_invalid_change_type_fails(self) -> None:
|
|
95
138
|
with self.assertRaises(ProcessError):
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_supervisor_contract.py
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_supervisor_posix.py
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/_supervisor_windows.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/helper_launch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/publication_compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/requirements-dev.txt
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process/requirements-runtime.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/requires.txt
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/engineering_process.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/finish-change/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/plan-change/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/review-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/run-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/start-change/SKILL.md
RENAMED
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/process_assets/skills/verify-change/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/schemas/production-engineering.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{engineering_process-1.2.4 → engineering_process-1.2.6}/templates/adopt-process-windows-job.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|