engineering-process 0.2.1__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.
- {engineering_process-0.2.1/engineering_process.egg-info → engineering_process-0.3.0}/PKG-INFO +33 -1
- {engineering_process-0.2.1 → engineering_process-0.3.0}/README.md +32 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/VERSIONING.md +14 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/__init__.py +1 -1
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/lifecycle.py +36 -1
- {engineering_process-0.2.1 → engineering_process-0.3.0/engineering_process.egg-info}/PKG-INFO +33 -1
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process.egg-info/SOURCES.txt +1 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/pyproject.toml +1 -1
- engineering_process-0.3.0/release.json +54 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_lifecycle.py +62 -0
- engineering_process-0.3.0/tests/test_process_install.py +235 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_self_hosting.py +36 -1
- engineering_process-0.2.1/release.json +0 -106
- {engineering_process-0.2.1 → engineering_process-0.3.0}/LICENSE +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/MANIFEST.in +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/PRODUCTION_STANDARD.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/bundles.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/__main__.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/_download_worker.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/_windows_job.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/adoption.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/artifact_attestation.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/bootstrap.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/bundles.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/cli.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/contracts.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/distribution.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/distribution_verify.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/environment.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/evidence.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/git.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/git_attributes.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/helper_launch.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/impact.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/managed.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/markdown.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/publication.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/release.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/release_candidate.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/requirements-build.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/requirements-release.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/runner.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/runtime.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/skills.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/supervision.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/supplemental.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/syncing.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process/tooling.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/adoption-migration.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/change.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/plan.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/project.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/release-change.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/release.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/examples/review.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/assess-design/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/build-frontend/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/build-frontend-foundation/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/change-api/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/cross-repo-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/define-change-contract/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/design-module/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/evolve-process/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/finish-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/govern-ui/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/implement-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/implement-module/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/implement-use-case/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/integrate-mcp/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/maintain-docs/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/plan-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/publish-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/review-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/run-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/run-change/references/execution.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/run-project-command/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/specify-use-case/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/process_assets/skills/verify-change/SKILL.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/adoption-migration.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/change.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/evidence-receipt.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/lifecycle.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/plan.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/process-lock.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/project.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/release-change.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/release.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/review.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/reviewer-attestation.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/supplemental-verification.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/schemas/verification.schema.json +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/setup.cfg +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/templates/AGENTS.process.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/templates/adopt-process.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_adoption.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_adoption_runner.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_artifact_attestation.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_bootstrap.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_bundles.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_cli.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_contracts.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_distribution.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_distribution_verify.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_environment.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_evals.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_impact.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_publication.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_publish_event.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_pypi_publication.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_release.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_release_candidate.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_release_preparation.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_release_qualification.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_runner.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_runtime.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_schemas.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_skills.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_source_checkout.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_supervision.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_supplemental.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_syncing.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_tooling.py +0 -0
- {engineering_process-0.2.1 → engineering_process-0.3.0}/tests/test_windows_job.py +0 -0
{engineering_process-0.2.1/engineering_process.egg-info → engineering_process-0.3.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 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
|
|
@@ -98,6 +98,20 @@ Release, self-adoption, and consumer adoption are separate changes:
|
|
|
98
98
|
checkpoint. Merge completes adoption; there is no post-merge synchronization.
|
|
99
99
|
5. N+1 governs only changes that begin after the adoption checkpoint.
|
|
100
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
|
+
|
|
101
115
|
Renovate PRs are generated adoption candidates, not trusted adoption evidence.
|
|
102
116
|
Automerge is forbidden for process-authority updates. A PR that changes only a
|
|
103
117
|
requirement pin, omits generated hashes or managed assets, or requires a post-merge
|
|
@@ -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"] = []
|
{engineering_process-0.2.1 → engineering_process-0.3.0/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.
|
|
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
|
|
@@ -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
|
+
}
|
|
@@ -300,6 +300,68 @@ class LifecycleTests(unittest.TestCase):
|
|
|
300
300
|
self.assertEqual(report["impact"]["baseRef"], "HEAD")
|
|
301
301
|
self.assertEqual(report["impact"]["changedPaths"], [])
|
|
302
302
|
|
|
303
|
+
def test_stale_verified_source_can_begin_a_new_implementation_cycle(self):
|
|
304
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
305
|
+
base = Path(directory)
|
|
306
|
+
root = base / "project"
|
|
307
|
+
inputs = base / "inputs"
|
|
308
|
+
root.mkdir()
|
|
309
|
+
inputs.mkdir()
|
|
310
|
+
self.initialize_repository(root)
|
|
311
|
+
self.prepare_verified_change(root, inputs)
|
|
312
|
+
|
|
313
|
+
(root / "tracked.txt").write_text("remote CI correction\n", encoding="utf-8")
|
|
314
|
+
subprocess.run(["git", "add", "tracked.txt"], cwd=root, check=True)
|
|
315
|
+
subprocess.run(
|
|
316
|
+
["git", "commit", "-qm", "fix: address remote verification"],
|
|
317
|
+
cwd=root,
|
|
318
|
+
check=True,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
state = begin_implementation(
|
|
322
|
+
root,
|
|
323
|
+
"change-1",
|
|
324
|
+
actor_id="worker",
|
|
325
|
+
context_id="worker-context",
|
|
326
|
+
kind="agent",
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
self.assertEqual("implementing", state["phase"])
|
|
330
|
+
self.assertEqual(2, state["cycle"])
|
|
331
|
+
self.assertEqual([], state["verification"])
|
|
332
|
+
invalidated = [
|
|
333
|
+
event
|
|
334
|
+
for event in state["history"]
|
|
335
|
+
if event["event"] == "verification-invalidated"
|
|
336
|
+
]
|
|
337
|
+
self.assertEqual(1, len(invalidated))
|
|
338
|
+
self.assertEqual(1, invalidated[0]["previousCycle"])
|
|
339
|
+
self.assertEqual(
|
|
340
|
+
"source-changed-after-verification", invalidated[0]["reason"]
|
|
341
|
+
)
|
|
342
|
+
self.assertEqual(2, len(invalidated[0]["previousVerification"]))
|
|
343
|
+
|
|
344
|
+
def test_current_verified_source_cannot_bypass_independent_review(self):
|
|
345
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
346
|
+
base = Path(directory)
|
|
347
|
+
root = base / "project"
|
|
348
|
+
inputs = base / "inputs"
|
|
349
|
+
root.mkdir()
|
|
350
|
+
inputs.mkdir()
|
|
351
|
+
self.initialize_repository(root)
|
|
352
|
+
self.prepare_verified_change(root, inputs)
|
|
353
|
+
|
|
354
|
+
with self.assertRaisesRegex(
|
|
355
|
+
ContractError, "must enter independent review"
|
|
356
|
+
):
|
|
357
|
+
begin_implementation(
|
|
358
|
+
root,
|
|
359
|
+
"change-1",
|
|
360
|
+
actor_id="worker",
|
|
361
|
+
context_id="worker-context",
|
|
362
|
+
kind="agent",
|
|
363
|
+
)
|
|
364
|
+
|
|
303
365
|
def test_full_lifecycle_requires_independent_review(self):
|
|
304
366
|
with tempfile.TemporaryDirectory() as directory:
|
|
305
367
|
base = Path(directory)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
from verification import install_process_runtime as installer
|
|
9
|
+
from verification.install_process_runtime import (
|
|
10
|
+
Attempt,
|
|
11
|
+
BACKOFF_SECONDS,
|
|
12
|
+
InstallError,
|
|
13
|
+
PUBLIC_INDEX,
|
|
14
|
+
install_process_runtime,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ProcessRuntimeInstallTests(unittest.TestCase):
|
|
19
|
+
def fixture(self, root: Path) -> Path:
|
|
20
|
+
lock = root / "requirements" / "process.txt"
|
|
21
|
+
lock.parent.mkdir()
|
|
22
|
+
lock.write_text(
|
|
23
|
+
"--only-binary :all:\n\nengineering-process==0.2.1 \\\n"
|
|
24
|
+
" --hash=sha256:cbabb56b367f5d48e5f2d0e6dd5837eca7ea46c40b47ef232d7d04e014609153\n",
|
|
25
|
+
encoding="utf-8",
|
|
26
|
+
)
|
|
27
|
+
return lock
|
|
28
|
+
|
|
29
|
+
def test_installs_exact_hash_lock_from_public_index_with_sanitized_environment(self):
|
|
30
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
31
|
+
root = Path(directory)
|
|
32
|
+
lock = self.fixture(root)
|
|
33
|
+
calls = []
|
|
34
|
+
|
|
35
|
+
def runner(command, cwd, environment):
|
|
36
|
+
calls.append((tuple(command), cwd, environment))
|
|
37
|
+
return Attempt(0, b"installed\n", b"")
|
|
38
|
+
|
|
39
|
+
index_name = "_".join(("PIP", "INDEX", "URL"))
|
|
40
|
+
extra_index_name = "_".join(("PIP", "EXTRA", "INDEX", "URL"))
|
|
41
|
+
with patch.dict(
|
|
42
|
+
os.environ,
|
|
43
|
+
{
|
|
44
|
+
index_name: "untrusted-index-sentinel",
|
|
45
|
+
extra_index_name: "untrusted-extra-sentinel",
|
|
46
|
+
"PYTHONPATH": "/untrusted",
|
|
47
|
+
},
|
|
48
|
+
clear=False,
|
|
49
|
+
):
|
|
50
|
+
install_process_runtime(root, lock, runner=runner)
|
|
51
|
+
|
|
52
|
+
self.assertEqual(1, len(calls))
|
|
53
|
+
command, cwd, environment = calls[0]
|
|
54
|
+
self.assertEqual(root.resolve(), cwd)
|
|
55
|
+
self.assertIn("--require-hashes", command)
|
|
56
|
+
self.assertIn("--no-cache-dir", command)
|
|
57
|
+
self.assertEqual(PUBLIC_INDEX, command[command.index("--index-url") + 1])
|
|
58
|
+
self.assertEqual(str(lock.resolve()), command[-1])
|
|
59
|
+
self.assertNotIn(index_name, environment)
|
|
60
|
+
self.assertNotIn(extra_index_name, environment)
|
|
61
|
+
self.assertNotIn("PYTHONPATH", environment)
|
|
62
|
+
self.assertEqual(os.devnull, environment["PIP_CONFIG_FILE"])
|
|
63
|
+
|
|
64
|
+
def test_retries_only_exact_pinned_version_absence(self):
|
|
65
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
66
|
+
root = Path(directory)
|
|
67
|
+
lock = self.fixture(root)
|
|
68
|
+
attempts = iter(
|
|
69
|
+
(
|
|
70
|
+
Attempt(
|
|
71
|
+
1,
|
|
72
|
+
b"",
|
|
73
|
+
(
|
|
74
|
+
b"Could not find a version that satisfies the requirement "
|
|
75
|
+
b"engineering-process==0.2.1\n"
|
|
76
|
+
b"No matching distribution found for "
|
|
77
|
+
b"engineering-process==0.2.1\n"
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
Attempt(0, b"installed\n", b""),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
sleeps = []
|
|
84
|
+
|
|
85
|
+
install_process_runtime(
|
|
86
|
+
root,
|
|
87
|
+
lock,
|
|
88
|
+
runner=lambda *_arguments: next(attempts),
|
|
89
|
+
sleeper=sleeps.append,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.assertEqual([BACKOFF_SECONDS[0]], sleeps)
|
|
93
|
+
|
|
94
|
+
def test_fails_immediately_for_hash_or_other_deterministic_errors(self):
|
|
95
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
96
|
+
root = Path(directory)
|
|
97
|
+
lock = self.fixture(root)
|
|
98
|
+
sleeps = []
|
|
99
|
+
|
|
100
|
+
with self.assertRaisesRegex(InstallError, "non-retryable"):
|
|
101
|
+
install_process_runtime(
|
|
102
|
+
root,
|
|
103
|
+
lock,
|
|
104
|
+
runner=lambda *_arguments: Attempt(
|
|
105
|
+
1, b"", b"THESE PACKAGES DO NOT MATCH THE HASHES\n"
|
|
106
|
+
),
|
|
107
|
+
sleeper=sleeps.append,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
self.assertEqual([], sleeps)
|
|
111
|
+
|
|
112
|
+
def test_bounds_timeout_output_and_eventual_absence(self):
|
|
113
|
+
scenarios = (
|
|
114
|
+
(Attempt(-1, b"", b"", timed_out=True), "exceeded 300 seconds"),
|
|
115
|
+
(Attempt(1, b"", b"", output_exceeded=True), "output exceeded"),
|
|
116
|
+
)
|
|
117
|
+
for attempt, expected in scenarios:
|
|
118
|
+
with self.subTest(expected), tempfile.TemporaryDirectory() as directory:
|
|
119
|
+
root = Path(directory)
|
|
120
|
+
lock = self.fixture(root)
|
|
121
|
+
with self.assertRaisesRegex(InstallError, expected):
|
|
122
|
+
install_process_runtime(
|
|
123
|
+
root,
|
|
124
|
+
lock,
|
|
125
|
+
runner=lambda *_arguments, value=attempt: value,
|
|
126
|
+
sleeper=lambda _delay: None,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
130
|
+
root = Path(directory)
|
|
131
|
+
lock = self.fixture(root)
|
|
132
|
+
transient = Attempt(
|
|
133
|
+
1,
|
|
134
|
+
b"",
|
|
135
|
+
(
|
|
136
|
+
b"Could not find a version that satisfies the requirement "
|
|
137
|
+
b"engineering-process==0.2.1\n"
|
|
138
|
+
b"No matching distribution found for engineering-process==0.2.1\n"
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
sleeps = []
|
|
142
|
+
with self.assertRaisesRegex(InstallError, "did not become visible"):
|
|
143
|
+
install_process_runtime(
|
|
144
|
+
root,
|
|
145
|
+
lock,
|
|
146
|
+
runner=lambda *_arguments: transient,
|
|
147
|
+
sleeper=sleeps.append,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
self.assertEqual(list(BACKOFF_SECONDS), sleeps)
|
|
151
|
+
|
|
152
|
+
def test_rejects_ambiguous_non_binary_or_symlinked_locks(self):
|
|
153
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
154
|
+
root = Path(directory)
|
|
155
|
+
lock = self.fixture(root)
|
|
156
|
+
lock.write_text(
|
|
157
|
+
"engineering-process==0.2.1\nengineering-process==0.2.1\n",
|
|
158
|
+
encoding="utf-8",
|
|
159
|
+
)
|
|
160
|
+
with self.assertRaisesRegex(InstallError, "exactly one"):
|
|
161
|
+
install_process_runtime(
|
|
162
|
+
root,
|
|
163
|
+
lock,
|
|
164
|
+
runner=lambda *_arguments: Attempt(0, b"", b""),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if os.name != "nt":
|
|
168
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
169
|
+
root = Path(directory)
|
|
170
|
+
target = root / "target.txt"
|
|
171
|
+
target.write_text(
|
|
172
|
+
"--only-binary :all:\nengineering-process==0.2.1\n",
|
|
173
|
+
encoding="utf-8",
|
|
174
|
+
)
|
|
175
|
+
link = root / "requirements" / "process.txt"
|
|
176
|
+
link.parent.mkdir()
|
|
177
|
+
link.symlink_to(target)
|
|
178
|
+
with self.assertRaisesRegex(InstallError, "symlinks"):
|
|
179
|
+
install_process_runtime(
|
|
180
|
+
root,
|
|
181
|
+
link,
|
|
182
|
+
runner=lambda *_arguments: Attempt(0, b"", b""),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def test_real_runner_bounds_output_and_terminates_on_timeout(self):
|
|
186
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
187
|
+
root = Path(directory)
|
|
188
|
+
with patch.object(installer, "MAX_OUTPUT_BYTES", 1024):
|
|
189
|
+
attempt = installer._run_attempt(
|
|
190
|
+
[
|
|
191
|
+
sys.executable,
|
|
192
|
+
"-c",
|
|
193
|
+
"import sys; sys.stdout.buffer.write(b'x' * 100000); sys.stdout.flush()",
|
|
194
|
+
],
|
|
195
|
+
root,
|
|
196
|
+
os.environ.copy(),
|
|
197
|
+
)
|
|
198
|
+
self.assertTrue(attempt.output_exceeded)
|
|
199
|
+
self.assertLessEqual(len(attempt.stdout) + len(attempt.stderr), 1024)
|
|
200
|
+
|
|
201
|
+
with (
|
|
202
|
+
patch.object(installer, "ATTEMPT_TIMEOUT_SECONDS", 0.1),
|
|
203
|
+
patch.object(installer, "TERMINATION_TIMEOUT_SECONDS", 1.0),
|
|
204
|
+
):
|
|
205
|
+
started = installer.time.monotonic()
|
|
206
|
+
attempt = installer._run_attempt(
|
|
207
|
+
[sys.executable, "-c", "import time; time.sleep(30)"],
|
|
208
|
+
root,
|
|
209
|
+
os.environ.copy(),
|
|
210
|
+
)
|
|
211
|
+
elapsed = installer.time.monotonic() - started
|
|
212
|
+
self.assertTrue(attempt.timed_out)
|
|
213
|
+
self.assertLess(elapsed, 3.0)
|
|
214
|
+
|
|
215
|
+
@unittest.skipIf(os.name == "nt", "Windows descendants are covered by Job Object tests")
|
|
216
|
+
def test_real_runner_terminates_descendants_left_by_successful_root(self):
|
|
217
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
218
|
+
attempt = installer._run_attempt(
|
|
219
|
+
[
|
|
220
|
+
sys.executable,
|
|
221
|
+
"-c",
|
|
222
|
+
(
|
|
223
|
+
"import subprocess, sys; "
|
|
224
|
+
"subprocess.Popen([sys.executable, '-c', "
|
|
225
|
+
"'import time; time.sleep(30)'])"
|
|
226
|
+
),
|
|
227
|
+
],
|
|
228
|
+
Path(directory),
|
|
229
|
+
os.environ.copy(),
|
|
230
|
+
)
|
|
231
|
+
self.assertTrue(attempt.descendants_terminated)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
unittest.main()
|