engineering-process 1.2.5__tar.gz → 2.0.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-1.2.5 → engineering_process-2.0.0}/MANIFEST.in +1 -1
- {engineering_process-1.2.5/engineering_process.egg-info → engineering_process-2.0.0}/PKG-INFO +54 -5
- {engineering_process-1.2.5 → engineering_process-2.0.0}/README.md +53 -4
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/__init__.py +1 -1
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/contracts.py +8 -3
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/skills.py +15 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0/engineering_process.egg-info}/PKG-INFO +54 -5
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process.egg-info/SOURCES.txt +9 -8
- {engineering_process-1.2.5 → engineering_process-2.0.0}/process-graph.json +23 -23
- {engineering_process-1.2.5/process_assets/skills/finish-change → engineering_process-2.0.0/process_assets/skills/change-complete}/SKILL.md +5 -4
- {engineering_process-1.2.5/process_assets/skills/implement-change → engineering_process-2.0.0/process_assets/skills/change-implement}/SKILL.md +3 -3
- {engineering_process-1.2.5/process_assets/skills/plan-change → engineering_process-2.0.0/process_assets/skills/change-plan}/SKILL.md +2 -2
- {engineering_process-1.2.5/process_assets/skills/review-change → engineering_process-2.0.0/process_assets/skills/change-review}/SKILL.md +15 -6
- {engineering_process-1.2.5/process_assets/skills/start-change → engineering_process-2.0.0/process_assets/skills/change-start}/SKILL.md +2 -2
- {engineering_process-1.2.5/process_assets/skills/verify-change → engineering_process-2.0.0/process_assets/skills/change-verify}/SKILL.md +3 -3
- {engineering_process-1.2.5/process_assets/skills/run-change → engineering_process-2.0.0/process_assets/skills/deliver-change}/SKILL.md +16 -15
- {engineering_process-1.2.5/process_assets/skills/improve-process → engineering_process-2.0.0/process_assets/skills/process-improve}/SKILL.md +2 -2
- {engineering_process-1.2.5 → engineering_process-2.0.0}/pyproject.toml +10 -9
- {engineering_process-1.2.5 → engineering_process-2.0.0}/templates/AGENTS.process.md +1 -1
- engineering_process-2.0.0/templates/renovate.json +6 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_adoption.py +83 -3
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_automation.py +45 -1
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_contracts.py +18 -1
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_distribution.py +19 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_skills.py +64 -19
- {engineering_process-1.2.5 → engineering_process-2.0.0}/LICENSE +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/__main__.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/_supervisor_contract.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/_windows_job.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/adoption.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/cli.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/commands.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/distribution.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/helper_launch.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/lifecycle.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/production_engineering.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/project.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/publication_compat.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/release.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/repository.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process/supervision.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/process_assets/skills/production-engineering/SKILL.md +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/process_assets/skills/production-engineering/invariants.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/change.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/plan.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/process-graph.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/process-lock.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/production-engineering.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/project.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/receipt.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/release-change.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/release.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/review.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/schemas/run.schema.json +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/setup.cfg +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/templates/adopt-process.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_architecture.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_cli.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_commands.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_lifecycle.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_production_engineering.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_publication_compat.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_pypi_cache_horizon.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_release.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_repository.py +0 -0
- {engineering_process-1.2.5 → engineering_process-2.0.0}/tests/test_supervisor_posix.py +0 -0
{engineering_process-1.2.5/engineering_process.egg-info → engineering_process-2.0.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
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
|
|
@@ -33,11 +33,27 @@ Portable skills explain what to do. processctl owns state transitions and curren
|
|
|
33
33
|
evidence. Each consumer owns its product rules, exact commands, merge policy, and
|
|
34
34
|
release decisions.
|
|
35
35
|
|
|
36
|
+
Start delivery work with [deliver-change](process_assets/skills/deliver-change/SKILL.md).
|
|
37
|
+
It selects the current phase from lifecycle state; callers do not select a phase skill:
|
|
38
|
+
|
|
39
|
+
deliver-change
|
|
40
|
+
change-start -> change-plan -> change-implement -> change-verify
|
|
41
|
+
-> change-review -> change-complete
|
|
42
|
+
|
|
43
|
+
[process-improve](process_assets/skills/process-improve/SKILL.md) handles a reusable
|
|
44
|
+
process problem, then returns delivery to the same lifecycle.
|
|
45
|
+
[production-engineering](process_assets/skills/production-engineering/SKILL.md) supplies
|
|
46
|
+
the invariant floor consulted from planning through independent review. Both are
|
|
47
|
+
reachable specializations; neither advances lifecycle state. `change-complete` calls
|
|
48
|
+
the existing `processctl change finish` command.
|
|
49
|
+
|
|
50
|
+
For migration from the previous skill identifiers, see [Versioning](VERSIONING.md#skill-namespace-migration).
|
|
51
|
+
|
|
36
52
|
## Architecture
|
|
37
53
|
|
|
38
54
|
The distribution has four live parts:
|
|
39
55
|
|
|
40
|
-
1. Managed skills under process_assets/skills, all reachable from
|
|
56
|
+
1. Managed skills under process_assets/skills, all reachable from deliver-change.
|
|
41
57
|
2. One processctl state machine under engineering_process/lifecycle.py.
|
|
42
58
|
3. JSON Schemas that are loaded directly by the runtime.
|
|
43
59
|
4. One adoption transaction that synchronizes managed skills and configuration from
|
|
@@ -175,7 +191,7 @@ This assessment is not a production certificate. Production still requires the
|
|
|
175
191
|
consumer's immutable readiness pack, every required capability in `enforced` state,
|
|
176
192
|
fresh consumer-owned verification on the exact candidate, and independent review.
|
|
177
193
|
|
|
178
|
-
For each ordinary change, `
|
|
194
|
+
For each ordinary change, `deliver-change` first surfaces this readiness view. The accepted
|
|
179
195
|
request and consumer rules determine which capabilities are affected. Every change
|
|
180
196
|
retains the project's baseline `requiredProfiles`; start and plan add any conditional
|
|
181
197
|
evidence profiles needed by affected capabilities and include a planned gap only when
|
|
@@ -184,7 +200,7 @@ floor. A planned-to-enforced promotion is a reviewed consumer source diff with f
|
|
|
184
200
|
evidence. Unrelated planned gaps remain visible but do not block development, and no
|
|
185
201
|
skill chooses product priorities or changes readiness automatically.
|
|
186
202
|
|
|
187
|
-
When a consumer incident exposes a reusable process gap, `improve
|
|
203
|
+
When a consumer incident exposes a reusable process gap, `process-improve` first keeps
|
|
188
204
|
the consumer safe, then prepares a sanitized GitHub issue draft from that checkout.
|
|
189
205
|
It deduplicates by consumer/process-version/invariant, requires owner authorization
|
|
190
206
|
before `gh issue create`, and uses an accepted issue as the later process change source
|
|
@@ -352,7 +368,7 @@ URL; without it, the review remains pending. Earlier plan and review documents r
|
|
|
352
368
|
readable, and their runs remain registrable or finishable with the version selected by
|
|
353
369
|
the authority that started the relevant phase.
|
|
354
370
|
|
|
355
|
-
The [finding priority definitions](process_assets/skills/review
|
|
371
|
+
The [finding priority definitions](process_assets/skills/change-review/SKILL.md#finding-priority)
|
|
356
372
|
are the canonical P0-P3 impact convention for this process, including examples and
|
|
357
373
|
their relationship to blocking decisions.
|
|
358
374
|
|
|
@@ -471,6 +487,39 @@ authorizes that merge.
|
|
|
471
487
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
472
488
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
473
489
|
|
|
490
|
+
The optional [Renovate preset](templates/renovate.json) is generated from the
|
|
491
|
+
canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
|
|
492
|
+
dependency selection, supported platforms, schedules, major-update approval,
|
|
493
|
+
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
494
|
+
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
495
|
+
|
|
496
|
+
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
497
|
+
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
498
|
+
commit of a verified release that contains the preset. Remove obsolete inline
|
|
499
|
+
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
500
|
+
The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
|
|
501
|
+
it does not claim compatibility with earlier publication contracts. A future
|
|
502
|
+
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
503
|
+
|
|
504
|
+
Renovate resolves presets from the protected base configuration before dependency
|
|
505
|
+
updates and post-upgrade tasks. Bootstrap the compatible preset in that base before
|
|
506
|
+
relying on its generated drafts; changing the candidate configuration alone cannot
|
|
507
|
+
repair the same run's body. Verify the native rendered body against both the base
|
|
508
|
+
and candidate process authority during adoption. Pending fields and unchecked
|
|
509
|
+
Completion gate items are proposals, never evidence or approval.
|
|
510
|
+
|
|
511
|
+
Before collecting lifecycle evidence for a bot PR, apply its configured
|
|
512
|
+
`stopUpdatingLabel` (Renovate's default is `stop-updating`) and confirm the head is
|
|
513
|
+
unchanged after any in-flight bot run finishes. Keep the label through verification,
|
|
514
|
+
independent review, receipt and the consumer-owned merge. Do not request a native
|
|
515
|
+
rebase or dashboard retry while reviewing: an explicit retry can resume updates.
|
|
516
|
+
If the candidate changes, open a new implementation cycle and collect fresh
|
|
517
|
+
evidence. Remove the pause label only when returning the PR to automation.
|
|
518
|
+
|
|
519
|
+
These are native [shared preset](https://docs.renovatebot.com/config-presets/) and
|
|
520
|
+
[review pause](https://docs.renovatebot.com/configuration-options/#stopupdatinglabel)
|
|
521
|
+
boundaries; the control plane does not become a second PR publisher.
|
|
522
|
+
|
|
474
523
|
## Compatibility
|
|
475
524
|
|
|
476
525
|
Version 1.x retains a few small pre-1.0 command shapes so existing consumers can
|
|
@@ -9,11 +9,27 @@ Portable skills explain what to do. processctl owns state transitions and curren
|
|
|
9
9
|
evidence. Each consumer owns its product rules, exact commands, merge policy, and
|
|
10
10
|
release decisions.
|
|
11
11
|
|
|
12
|
+
Start delivery work with [deliver-change](process_assets/skills/deliver-change/SKILL.md).
|
|
13
|
+
It selects the current phase from lifecycle state; callers do not select a phase skill:
|
|
14
|
+
|
|
15
|
+
deliver-change
|
|
16
|
+
change-start -> change-plan -> change-implement -> change-verify
|
|
17
|
+
-> change-review -> change-complete
|
|
18
|
+
|
|
19
|
+
[process-improve](process_assets/skills/process-improve/SKILL.md) handles a reusable
|
|
20
|
+
process problem, then returns delivery to the same lifecycle.
|
|
21
|
+
[production-engineering](process_assets/skills/production-engineering/SKILL.md) supplies
|
|
22
|
+
the invariant floor consulted from planning through independent review. Both are
|
|
23
|
+
reachable specializations; neither advances lifecycle state. `change-complete` calls
|
|
24
|
+
the existing `processctl change finish` command.
|
|
25
|
+
|
|
26
|
+
For migration from the previous skill identifiers, see [Versioning](VERSIONING.md#skill-namespace-migration).
|
|
27
|
+
|
|
12
28
|
## Architecture
|
|
13
29
|
|
|
14
30
|
The distribution has four live parts:
|
|
15
31
|
|
|
16
|
-
1. Managed skills under process_assets/skills, all reachable from
|
|
32
|
+
1. Managed skills under process_assets/skills, all reachable from deliver-change.
|
|
17
33
|
2. One processctl state machine under engineering_process/lifecycle.py.
|
|
18
34
|
3. JSON Schemas that are loaded directly by the runtime.
|
|
19
35
|
4. One adoption transaction that synchronizes managed skills and configuration from
|
|
@@ -151,7 +167,7 @@ This assessment is not a production certificate. Production still requires the
|
|
|
151
167
|
consumer's immutable readiness pack, every required capability in `enforced` state,
|
|
152
168
|
fresh consumer-owned verification on the exact candidate, and independent review.
|
|
153
169
|
|
|
154
|
-
For each ordinary change, `
|
|
170
|
+
For each ordinary change, `deliver-change` first surfaces this readiness view. The accepted
|
|
155
171
|
request and consumer rules determine which capabilities are affected. Every change
|
|
156
172
|
retains the project's baseline `requiredProfiles`; start and plan add any conditional
|
|
157
173
|
evidence profiles needed by affected capabilities and include a planned gap only when
|
|
@@ -160,7 +176,7 @@ floor. A planned-to-enforced promotion is a reviewed consumer source diff with f
|
|
|
160
176
|
evidence. Unrelated planned gaps remain visible but do not block development, and no
|
|
161
177
|
skill chooses product priorities or changes readiness automatically.
|
|
162
178
|
|
|
163
|
-
When a consumer incident exposes a reusable process gap, `improve
|
|
179
|
+
When a consumer incident exposes a reusable process gap, `process-improve` first keeps
|
|
164
180
|
the consumer safe, then prepares a sanitized GitHub issue draft from that checkout.
|
|
165
181
|
It deduplicates by consumer/process-version/invariant, requires owner authorization
|
|
166
182
|
before `gh issue create`, and uses an accepted issue as the later process change source
|
|
@@ -328,7 +344,7 @@ URL; without it, the review remains pending. Earlier plan and review documents r
|
|
|
328
344
|
readable, and their runs remain registrable or finishable with the version selected by
|
|
329
345
|
the authority that started the relevant phase.
|
|
330
346
|
|
|
331
|
-
The [finding priority definitions](process_assets/skills/review
|
|
347
|
+
The [finding priority definitions](process_assets/skills/change-review/SKILL.md#finding-priority)
|
|
332
348
|
are the canonical P0-P3 impact convention for this process, including examples and
|
|
333
349
|
their relationship to blocking decisions.
|
|
334
350
|
|
|
@@ -447,6 +463,39 @@ authorizes that merge.
|
|
|
447
463
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
448
464
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
449
465
|
|
|
466
|
+
The optional [Renovate preset](templates/renovate.json) is generated from the
|
|
467
|
+
canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
|
|
468
|
+
dependency selection, supported platforms, schedules, major-update approval,
|
|
469
|
+
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
470
|
+
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
471
|
+
|
|
472
|
+
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
473
|
+
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
474
|
+
commit of a verified release that contains the preset. Remove obsolete inline
|
|
475
|
+
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
476
|
+
The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
|
|
477
|
+
it does not claim compatibility with earlier publication contracts. A future
|
|
478
|
+
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
479
|
+
|
|
480
|
+
Renovate resolves presets from the protected base configuration before dependency
|
|
481
|
+
updates and post-upgrade tasks. Bootstrap the compatible preset in that base before
|
|
482
|
+
relying on its generated drafts; changing the candidate configuration alone cannot
|
|
483
|
+
repair the same run's body. Verify the native rendered body against both the base
|
|
484
|
+
and candidate process authority during adoption. Pending fields and unchecked
|
|
485
|
+
Completion gate items are proposals, never evidence or approval.
|
|
486
|
+
|
|
487
|
+
Before collecting lifecycle evidence for a bot PR, apply its configured
|
|
488
|
+
`stopUpdatingLabel` (Renovate's default is `stop-updating`) and confirm the head is
|
|
489
|
+
unchanged after any in-flight bot run finishes. Keep the label through verification,
|
|
490
|
+
independent review, receipt and the consumer-owned merge. Do not request a native
|
|
491
|
+
rebase or dashboard retry while reviewing: an explicit retry can resume updates.
|
|
492
|
+
If the candidate changes, open a new implementation cycle and collect fresh
|
|
493
|
+
evidence. Remove the pause label only when returning the PR to automation.
|
|
494
|
+
|
|
495
|
+
These are native [shared preset](https://docs.renovatebot.com/config-presets/) and
|
|
496
|
+
[review pause](https://docs.renovatebot.com/configuration-options/#stopupdatinglabel)
|
|
497
|
+
boundaries; the control plane does not become a second PR publisher.
|
|
498
|
+
|
|
450
499
|
## Compatibility
|
|
451
500
|
|
|
452
501
|
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:
|
|
@@ -90,6 +90,21 @@ def validate_skills(
|
|
|
90
90
|
graph = load_and_validate(
|
|
91
91
|
graph_path, "process-graph", schema_root=schemas_root(process_root)
|
|
92
92
|
)
|
|
93
|
+
owners = {state["id"]: state["ownerSkill"] for state in graph["states"]}
|
|
94
|
+
if len(owners) != len(graph["states"]):
|
|
95
|
+
raise ProcessError("process graph state ids must be unique")
|
|
96
|
+
for state in graph["states"]:
|
|
97
|
+
for transition in state["transitions"]:
|
|
98
|
+
destination = transition["nextState"]
|
|
99
|
+
skill = transition["nextSkill"]
|
|
100
|
+
if (destination is None) != (skill is None):
|
|
101
|
+
raise ProcessError("process graph terminal transitions must have both targets null")
|
|
102
|
+
if destination is not None and destination not in owners:
|
|
103
|
+
raise ProcessError(f"process graph references missing state: {destination}")
|
|
104
|
+
if skill is not None and skill not in names:
|
|
105
|
+
raise ProcessError(
|
|
106
|
+
f"process graph transition from {state['id']} references missing skill: {skill}"
|
|
107
|
+
)
|
|
93
108
|
routed = {graph["entrySkill"]}
|
|
94
109
|
routed.update(state["ownerSkill"] for state in graph["states"])
|
|
95
110
|
routed.update(graph.get("specializations", {}).values())
|
{engineering_process-1.2.5 → engineering_process-2.0.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:
|
|
3
|
+
Version: 2.0.0
|
|
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
|
|
@@ -33,11 +33,27 @@ Portable skills explain what to do. processctl owns state transitions and curren
|
|
|
33
33
|
evidence. Each consumer owns its product rules, exact commands, merge policy, and
|
|
34
34
|
release decisions.
|
|
35
35
|
|
|
36
|
+
Start delivery work with [deliver-change](process_assets/skills/deliver-change/SKILL.md).
|
|
37
|
+
It selects the current phase from lifecycle state; callers do not select a phase skill:
|
|
38
|
+
|
|
39
|
+
deliver-change
|
|
40
|
+
change-start -> change-plan -> change-implement -> change-verify
|
|
41
|
+
-> change-review -> change-complete
|
|
42
|
+
|
|
43
|
+
[process-improve](process_assets/skills/process-improve/SKILL.md) handles a reusable
|
|
44
|
+
process problem, then returns delivery to the same lifecycle.
|
|
45
|
+
[production-engineering](process_assets/skills/production-engineering/SKILL.md) supplies
|
|
46
|
+
the invariant floor consulted from planning through independent review. Both are
|
|
47
|
+
reachable specializations; neither advances lifecycle state. `change-complete` calls
|
|
48
|
+
the existing `processctl change finish` command.
|
|
49
|
+
|
|
50
|
+
For migration from the previous skill identifiers, see [Versioning](VERSIONING.md#skill-namespace-migration).
|
|
51
|
+
|
|
36
52
|
## Architecture
|
|
37
53
|
|
|
38
54
|
The distribution has four live parts:
|
|
39
55
|
|
|
40
|
-
1. Managed skills under process_assets/skills, all reachable from
|
|
56
|
+
1. Managed skills under process_assets/skills, all reachable from deliver-change.
|
|
41
57
|
2. One processctl state machine under engineering_process/lifecycle.py.
|
|
42
58
|
3. JSON Schemas that are loaded directly by the runtime.
|
|
43
59
|
4. One adoption transaction that synchronizes managed skills and configuration from
|
|
@@ -175,7 +191,7 @@ This assessment is not a production certificate. Production still requires the
|
|
|
175
191
|
consumer's immutable readiness pack, every required capability in `enforced` state,
|
|
176
192
|
fresh consumer-owned verification on the exact candidate, and independent review.
|
|
177
193
|
|
|
178
|
-
For each ordinary change, `
|
|
194
|
+
For each ordinary change, `deliver-change` first surfaces this readiness view. The accepted
|
|
179
195
|
request and consumer rules determine which capabilities are affected. Every change
|
|
180
196
|
retains the project's baseline `requiredProfiles`; start and plan add any conditional
|
|
181
197
|
evidence profiles needed by affected capabilities and include a planned gap only when
|
|
@@ -184,7 +200,7 @@ floor. A planned-to-enforced promotion is a reviewed consumer source diff with f
|
|
|
184
200
|
evidence. Unrelated planned gaps remain visible but do not block development, and no
|
|
185
201
|
skill chooses product priorities or changes readiness automatically.
|
|
186
202
|
|
|
187
|
-
When a consumer incident exposes a reusable process gap, `improve
|
|
203
|
+
When a consumer incident exposes a reusable process gap, `process-improve` first keeps
|
|
188
204
|
the consumer safe, then prepares a sanitized GitHub issue draft from that checkout.
|
|
189
205
|
It deduplicates by consumer/process-version/invariant, requires owner authorization
|
|
190
206
|
before `gh issue create`, and uses an accepted issue as the later process change source
|
|
@@ -352,7 +368,7 @@ URL; without it, the review remains pending. Earlier plan and review documents r
|
|
|
352
368
|
readable, and their runs remain registrable or finishable with the version selected by
|
|
353
369
|
the authority that started the relevant phase.
|
|
354
370
|
|
|
355
|
-
The [finding priority definitions](process_assets/skills/review
|
|
371
|
+
The [finding priority definitions](process_assets/skills/change-review/SKILL.md#finding-priority)
|
|
356
372
|
are the canonical P0-P3 impact convention for this process, including examples and
|
|
357
373
|
their relationship to blocking decisions.
|
|
358
374
|
|
|
@@ -471,6 +487,39 @@ authorizes that merge.
|
|
|
471
487
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
472
488
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
473
489
|
|
|
490
|
+
The optional [Renovate preset](templates/renovate.json) is generated from the
|
|
491
|
+
canonical public template. It supplies only `prHeader` and `prBodyTemplate`;
|
|
492
|
+
dependency selection, supported platforms, schedules, major-update approval,
|
|
493
|
+
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
494
|
+
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
495
|
+
|
|
496
|
+
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
497
|
+
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
498
|
+
commit of a verified release that contains the preset. Remove obsolete inline
|
|
499
|
+
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
500
|
+
The preset targets the canonical draft grammar used by 1.2.4 and this distribution;
|
|
501
|
+
it does not claim compatibility with earlier publication contracts. A future
|
|
502
|
+
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
503
|
+
|
|
504
|
+
Renovate resolves presets from the protected base configuration before dependency
|
|
505
|
+
updates and post-upgrade tasks. Bootstrap the compatible preset in that base before
|
|
506
|
+
relying on its generated drafts; changing the candidate configuration alone cannot
|
|
507
|
+
repair the same run's body. Verify the native rendered body against both the base
|
|
508
|
+
and candidate process authority during adoption. Pending fields and unchecked
|
|
509
|
+
Completion gate items are proposals, never evidence or approval.
|
|
510
|
+
|
|
511
|
+
Before collecting lifecycle evidence for a bot PR, apply its configured
|
|
512
|
+
`stopUpdatingLabel` (Renovate's default is `stop-updating`) and confirm the head is
|
|
513
|
+
unchanged after any in-flight bot run finishes. Keep the label through verification,
|
|
514
|
+
independent review, receipt and the consumer-owned merge. Do not request a native
|
|
515
|
+
rebase or dashboard retry while reviewing: an explicit retry can resume updates.
|
|
516
|
+
If the candidate changes, open a new implementation cycle and collect fresh
|
|
517
|
+
evidence. Remove the pause label only when returning the PR to automation.
|
|
518
|
+
|
|
519
|
+
These are native [shared preset](https://docs.renovatebot.com/config-presets/) and
|
|
520
|
+
[review pause](https://docs.renovatebot.com/configuration-options/#stopupdatinglabel)
|
|
521
|
+
boundaries; the control plane does not become a second PR publisher.
|
|
522
|
+
|
|
474
523
|
## Compatibility
|
|
475
524
|
|
|
476
525
|
Version 1.x retains a few small pre-1.0 command shapes so existing consumers can
|
{engineering_process-1.2.5 → engineering_process-2.0.0}/engineering_process.egg-info/SOURCES.txt
RENAMED
|
@@ -31,16 +31,16 @@ engineering_process.egg-info/dependency_links.txt
|
|
|
31
31
|
engineering_process.egg-info/entry_points.txt
|
|
32
32
|
engineering_process.egg-info/requires.txt
|
|
33
33
|
engineering_process.egg-info/top_level.txt
|
|
34
|
-
process_assets/skills/
|
|
35
|
-
process_assets/skills/implement
|
|
36
|
-
process_assets/skills/
|
|
37
|
-
process_assets/skills/
|
|
34
|
+
process_assets/skills/change-complete/SKILL.md
|
|
35
|
+
process_assets/skills/change-implement/SKILL.md
|
|
36
|
+
process_assets/skills/change-plan/SKILL.md
|
|
37
|
+
process_assets/skills/change-review/SKILL.md
|
|
38
|
+
process_assets/skills/change-start/SKILL.md
|
|
39
|
+
process_assets/skills/change-verify/SKILL.md
|
|
40
|
+
process_assets/skills/deliver-change/SKILL.md
|
|
41
|
+
process_assets/skills/process-improve/SKILL.md
|
|
38
42
|
process_assets/skills/production-engineering/SKILL.md
|
|
39
43
|
process_assets/skills/production-engineering/invariants.json
|
|
40
|
-
process_assets/skills/review-change/SKILL.md
|
|
41
|
-
process_assets/skills/run-change/SKILL.md
|
|
42
|
-
process_assets/skills/start-change/SKILL.md
|
|
43
|
-
process_assets/skills/verify-change/SKILL.md
|
|
44
44
|
schemas/change.schema.json
|
|
45
45
|
schemas/plan.schema.json
|
|
46
46
|
schemas/process-graph.schema.json
|
|
@@ -57,6 +57,7 @@ templates/AGENTS.process.md
|
|
|
57
57
|
templates/PULL_REQUEST_TEMPLATE.md
|
|
58
58
|
templates/adopt-process-windows-job.py
|
|
59
59
|
templates/adopt-process.py
|
|
60
|
+
templates/renovate.json
|
|
60
61
|
tests/test_adoption.py
|
|
61
62
|
tests/test_architecture.py
|
|
62
63
|
tests/test_automation.py
|
|
@@ -1,83 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"entrySkill": "
|
|
3
|
+
"entrySkill": "deliver-change",
|
|
4
4
|
"specializations": {
|
|
5
|
-
"processChange": "improve
|
|
5
|
+
"processChange": "process-improve",
|
|
6
6
|
"productionEngineering": "production-engineering"
|
|
7
7
|
},
|
|
8
8
|
"states": [
|
|
9
9
|
{
|
|
10
10
|
"id": "unregistered",
|
|
11
|
-
"ownerSkill": "start
|
|
11
|
+
"ownerSkill": "change-start",
|
|
12
12
|
"commands": ["change start"],
|
|
13
13
|
"transitions": [
|
|
14
|
-
{"result": "success", "nextState": "specified", "nextSkill": "plan
|
|
14
|
+
{"result": "success", "nextState": "specified", "nextSkill": "change-plan"}
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"id": "specified",
|
|
19
|
-
"ownerSkill": "plan
|
|
19
|
+
"ownerSkill": "change-plan",
|
|
20
20
|
"commands": ["change plan"],
|
|
21
21
|
"transitions": [
|
|
22
|
-
{"result": "success", "nextState": "planned", "nextSkill": "implement
|
|
22
|
+
{"result": "success", "nextState": "planned", "nextSkill": "change-implement"}
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"id": "planned",
|
|
27
|
-
"ownerSkill": "implement
|
|
27
|
+
"ownerSkill": "change-implement",
|
|
28
28
|
"commands": ["change implement"],
|
|
29
29
|
"transitions": [
|
|
30
|
-
{"result": "success", "nextState": "implementing", "nextSkill": "verify
|
|
30
|
+
{"result": "success", "nextState": "implementing", "nextSkill": "change-verify"}
|
|
31
31
|
]
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"id": "implementing",
|
|
35
|
-
"ownerSkill": "verify
|
|
35
|
+
"ownerSkill": "change-verify",
|
|
36
36
|
"commands": ["change implement", "change verify"],
|
|
37
37
|
"transitions": [
|
|
38
|
-
{"result": "profile-passed", "nextState": "implementing", "nextSkill": "verify
|
|
39
|
-
{"result": "all-passed", "nextState": "verified", "nextSkill": "review
|
|
38
|
+
{"result": "profile-passed", "nextState": "implementing", "nextSkill": "change-verify"},
|
|
39
|
+
{"result": "all-passed", "nextState": "verified", "nextSkill": "change-review"}
|
|
40
40
|
]
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"id": "verified",
|
|
44
|
-
"ownerSkill": "review
|
|
44
|
+
"ownerSkill": "change-review",
|
|
45
45
|
"commands": ["change implement", "change review start"],
|
|
46
46
|
"transitions": [
|
|
47
|
-
{"result": "assigned", "nextState": "review-pending", "nextSkill": "review
|
|
48
|
-
{"result": "source-changed", "nextState": "implementing", "nextSkill": "implement
|
|
47
|
+
{"result": "assigned", "nextState": "review-pending", "nextSkill": "change-review"},
|
|
48
|
+
{"result": "source-changed", "nextState": "implementing", "nextSkill": "change-implement"}
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"id": "review-pending",
|
|
53
|
-
"ownerSkill": "review
|
|
53
|
+
"ownerSkill": "change-review",
|
|
54
54
|
"commands": ["change implement", "change review submit"],
|
|
55
55
|
"transitions": [
|
|
56
|
-
{"result": "approved", "nextState": "approved", "nextSkill": "
|
|
57
|
-
{"result": "changes-requested", "nextState": "changes-requested", "nextSkill": "implement
|
|
58
|
-
{"result": "source-changed", "nextState": "implementing", "nextSkill": "implement
|
|
56
|
+
{"result": "approved", "nextState": "approved", "nextSkill": "change-complete"},
|
|
57
|
+
{"result": "changes-requested", "nextState": "changes-requested", "nextSkill": "change-implement"},
|
|
58
|
+
{"result": "source-changed", "nextState": "implementing", "nextSkill": "change-implement"}
|
|
59
59
|
]
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
"id": "changes-requested",
|
|
63
|
-
"ownerSkill": "implement
|
|
63
|
+
"ownerSkill": "change-implement",
|
|
64
64
|
"commands": ["change implement"],
|
|
65
65
|
"transitions": [
|
|
66
|
-
{"result": "success", "nextState": "implementing", "nextSkill": "verify
|
|
66
|
+
{"result": "success", "nextState": "implementing", "nextSkill": "change-verify"}
|
|
67
67
|
]
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"id": "approved",
|
|
71
|
-
"ownerSkill": "
|
|
71
|
+
"ownerSkill": "change-complete",
|
|
72
72
|
"commands": ["change finish", "change implement"],
|
|
73
73
|
"transitions": [
|
|
74
74
|
{"result": "success", "nextState": null, "nextSkill": null},
|
|
75
|
-
{"result": "source-changed", "nextState": "implementing", "nextSkill": "implement
|
|
75
|
+
{"result": "source-changed", "nextState": "implementing", "nextSkill": "change-implement"}
|
|
76
76
|
]
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"id": "blocked",
|
|
80
|
-
"ownerSkill": "
|
|
80
|
+
"ownerSkill": "deliver-change",
|
|
81
81
|
"commands": [],
|
|
82
82
|
"transitions": []
|
|
83
83
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: Complete an approved change only while
|
|
2
|
+
name: change-complete
|
|
3
|
+
description: Complete an approved change when routed by deliver-change, only while verification and independent review still match the repository snapshot.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Complete a change
|
|
7
7
|
|
|
8
8
|
Confirm the lifecycle is approved, every required profile passed, every blocking
|
|
9
9
|
finding is closed, every non-blocking finding has its required disposition, and the
|
|
@@ -11,7 +11,8 @@ repository still matches the reviewed snapshot. Then run:
|
|
|
11
11
|
|
|
12
12
|
processctl change finish --change-id ID --actor ACTOR --context CONTEXT
|
|
13
13
|
|
|
14
|
-
The
|
|
14
|
+
The existing `change finish` CLI operation writes one bounded completion receipt and
|
|
15
|
+
marks the run completed.
|
|
15
16
|
Completion does not itself grant merge, deployment, or release authority; those
|
|
16
17
|
remain project-owned operations. Never report completion from prose alone.
|
|
17
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: implement
|
|
3
|
-
description: Implement the accepted plan or resolve blocking review findings without changing the contract implicitly.
|
|
2
|
+
name: change-implement
|
|
3
|
+
description: Implement the accepted plan or resolve blocking review findings when routed by deliver-change, without changing the contract implicitly.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Implement a change
|
|
@@ -29,4 +29,4 @@ gaps merely because they are listed.
|
|
|
29
29
|
|
|
30
30
|
When evidence exposes a contract gap, stop and ask the project owner to supersede the
|
|
31
31
|
contract. Do not make review prose into new scope. When implementation is ready,
|
|
32
|
-
route to **verify
|
|
32
|
+
route to **change-verify**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: plan
|
|
3
|
-
description:
|
|
2
|
+
name: change-plan
|
|
3
|
+
description: Plan the registered change and its verification boundary when deliver-change routes a specified change.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plan a change
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: review
|
|
3
|
-
description: Review the exact verified snapshot from an actor and context
|
|
2
|
+
name: change-review
|
|
3
|
+
description: Review the exact verified snapshot from an independent actor and context when routed by deliver-change.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Review a change
|
|
7
7
|
|
|
8
8
|
The reviewer must not share either actor identity or execution context with an
|
|
9
|
-
implementer in the current cycle.
|
|
9
|
+
implementer in the current cycle. Read `processctl change status --change-id ID`.
|
|
10
|
+
When the phase is `verified`, start the assignment:
|
|
10
11
|
|
|
11
12
|
processctl change review start --change-id ID --actor REVIEWER --context REVIEW_CONTEXT
|
|
12
13
|
|
|
14
|
+
When the phase is `review-pending`, resume the existing assignment; do not run
|
|
15
|
+
`change review start` again. Read `.process/runs/ID/run.json` for its `cycle` and
|
|
16
|
+
`reviewAssignment`, including the assigned reviewer, checkpoint, and
|
|
17
|
+
`reportSchemaVersion`. Continue with the assigned independent actor/context and the
|
|
18
|
+
existing report path, `.process/runs/ID/review-CYCLE.json`. If that reviewer is
|
|
19
|
+
unavailable, report the pending assignment as a blocker; never impersonate its
|
|
20
|
+
identity or create a replacement assignment from another context.
|
|
21
|
+
|
|
13
22
|
Review the accepted contract, plan, complete diff, focused tests, and verification
|
|
14
23
|
evidence. The first pass is comprehensive within that frozen contract. Every finding
|
|
15
24
|
maps to one accepted criterion and records priority, origin, severity, and location.
|
|
@@ -41,7 +50,7 @@ consider consumer evidence that the lifecycle cannot observe. Every schema-versi
|
|
|
41
50
|
report classifies `processImprovement` as `none`, `consumer-specific`, or
|
|
42
51
|
`shared-process` and gives a concrete rationale. Consumer-specific behavior stays in
|
|
43
52
|
the consumer. For `shared-process`, keep the assignment `review-pending` and route the
|
|
44
|
-
candidate through **improve
|
|
53
|
+
candidate through **process-improve**. Submit only after an existing or owner-authorized
|
|
45
54
|
issue supplies the stable HTTPS `recordUrl`; the review itself remains read-only.
|
|
46
55
|
|
|
47
56
|
Read the consumer readiness result and repository rules. Check the complete diff for
|
|
@@ -56,8 +65,8 @@ Validate and submit the report:
|
|
|
56
65
|
processctl contract validate --kind review REPORT_PATH
|
|
57
66
|
processctl change review submit --change-id ID --review REPORT_PATH
|
|
58
67
|
|
|
59
|
-
Review is read-only. Requested changes route back to **implement
|
|
60
|
-
routes to **
|
|
68
|
+
Review is read-only. Requested changes route back to **change-implement**; approval
|
|
69
|
+
routes to **change-complete**. Keep the same independent reviewer for corrections.
|
|
61
70
|
Follow-up scope is only carried findings, remediation diffs, and regressions against
|
|
62
71
|
the frozen contract. A new blocker must be either remediation-caused or a P0/P1 late
|
|
63
72
|
violation with a rationale. After two correction cycles, another changes-requested
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: start
|
|
3
|
-
description: Turn an accepted request into a bounded change contract
|
|
2
|
+
name: change-start
|
|
3
|
+
description: Turn an accepted request into a bounded change contract when deliver-change routes a new change to lifecycle start.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Start a change
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: verify
|
|
3
|
-
description: Run the project-owned verification profiles
|
|
2
|
+
name: change-verify
|
|
3
|
+
description: Run the project-owned verification profiles on one unchanged repository snapshot when routed by deliver-change.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Verify a change
|
|
@@ -23,4 +23,4 @@ planned capability whose gap remains open. A readiness promotion is valid only w
|
|
|
23
23
|
all evidence named by that capability passes on this same snapshot.
|
|
24
24
|
|
|
25
25
|
When all required profiles pass on the same snapshot, the lifecycle becomes verified;
|
|
26
|
-
route to **review
|
|
26
|
+
route to **change-review**.
|