engineering-process 2.5.0__tar.gz → 2.6.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-2.5.0/engineering_process.egg-info → engineering_process-2.6.0}/PKG-INFO +76 -4
- {engineering_process-2.5.0 → engineering_process-2.6.0}/README.md +75 -3
- engineering_process-2.6.0/RELEASE_NOTES.md +49 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/__init__.py +1 -1
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/cli.py +54 -1
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/contracts.py +1 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/evidence.py +8 -1
- engineering_process-2.6.0/engineering_process/impact.py +231 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/lifecycle.py +448 -25
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/pr_description.py +3 -1
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/project.py +60 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/release_notes.py +52 -5
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/repository.py +38 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0/engineering_process.egg-info}/PKG-INFO +76 -4
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process.egg-info/SOURCES.txt +3 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/change-plan/SKILL.md +13 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/change-verify/SKILL.md +60 -7
- {engineering_process-2.5.0 → engineering_process-2.6.0}/pyproject.toml +2 -1
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/project.schema.json +90 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/receipt.schema.json +7 -5
- engineering_process-2.6.0/schemas/release-change.schema.json +79 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/release-notes-data.schema.json +44 -1
- engineering_process-2.6.0/schemas/release.schema.json +105 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/run.schema.json +2 -0
- engineering_process-2.6.0/schemas/verification-impact-selection.schema.json +143 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/verification-selection.schema.json +21 -2
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_architecture.py +1 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_cli.py +10 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_commands.py +3 -1
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_contracts.py +46 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_distribution.py +16 -0
- engineering_process-2.6.0/tests/test_impact.py +374 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_lifecycle.py +135 -2
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_release.py +168 -16
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_repository.py +27 -2
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_review_contexts.py +124 -23
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_skills.py +10 -1
- engineering_process-2.5.0/RELEASE_NOTES.md +0 -17
- engineering_process-2.5.0/schemas/release-change.schema.json +0 -20
- engineering_process-2.5.0/schemas/release.schema.json +0 -40
- {engineering_process-2.5.0 → engineering_process-2.6.0}/LICENSE +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/MANIFEST.in +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/__main__.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/_supervisor_contract.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/_supervisor_posix.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/_supervisor_windows.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/_windows_job.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/adoption.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/artifact_standards.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/automation_name.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/commands.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/distribution.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/helper_launch.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/issue.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/production_engineering.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/publication_compat.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/release.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/review_contexts.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/skills.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/source_publication.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process/supervision.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process-graph.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/change-complete/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/change-implement/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/change-review/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/change-start/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/deliver-change/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/process-improve/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/production-engineering/SKILL.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/skills/production-engineering/invariants.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/standards/automation-name.v1.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/standards/issue.v1.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/standards/pull-request.v1.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/process_assets/standards/release-notes.v1.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/artifact-selection.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/artifact-standard.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/automation-name-data.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/change.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/issue-data.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/plan.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/pr-description-data.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/process-graph.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/process-lock.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/production-engineering.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/renovate-preset.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/schemas/review.schema.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/setup.cfg +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/templates/AGENTS.process.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/templates/PULL_REQUEST_TEMPLATE.md +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/templates/adopt-process.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/templates/renovate.json +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_adoption.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_artifact_standards.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_automation.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_production_engineering.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_publication_compat.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_publication_workflow.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_pypi_cache.py +0 -0
- {engineering_process-2.5.0 → engineering_process-2.6.0}/tests/test_supervisor_posix.py +0 -0
{engineering_process-2.5.0/engineering_process.egg-info → engineering_process-2.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: engineering-process
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.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
|
|
@@ -96,6 +96,22 @@ child environment match. Legacy evidence without that identity reruns. This is
|
|
|
96
96
|
whole-profile reuse: equal check IDs and ordered side effects are never merged, and
|
|
97
97
|
the lifecycle receipt still records only actual executions.
|
|
98
98
|
|
|
99
|
+
Reuse is stage-aware rather than a blanket cache: the contract and plan travel by
|
|
100
|
+
digest; valid whole-profile verification can satisfy a continuation request; review
|
|
101
|
+
and finish consume the same reports after rechecking the snapshot; and a new
|
|
102
|
+
implementation cycle invalidates verification. Operation-local calculation reuse is
|
|
103
|
+
allowed only before a mutation/concurrency boundary. Impact feedback and generated
|
|
104
|
+
documents remain derived artifacts and never replace final required profiles or
|
|
105
|
+
independent review.
|
|
106
|
+
|
|
107
|
+
Consumers that can prove complete changed-path coverage may opt selected required
|
|
108
|
+
profiles into final impact assurance with `impactProfiles.schemaVersion: 2` and
|
|
109
|
+
`finalProfiles`. Each opted profile must declare an explicit global unit whose paths
|
|
110
|
+
include the universal `**` pattern for cross-cutting reach. The lifecycle then
|
|
111
|
+
records the selected units as final
|
|
112
|
+
verification evidence; missing or unresolved coverage blocks rather than silently
|
|
113
|
+
falling back. Existing version 1 policies remain feedback-only.
|
|
114
|
+
|
|
99
115
|
`processctl change review replace-reused` repairs only an initial pending assignment
|
|
100
116
|
proven to reuse another change's agent context, before a submitted review or normal
|
|
101
117
|
report file exists. It validates a fresh replacement and current verification,
|
|
@@ -166,6 +182,33 @@ Python 3.11 or newer and Git are required. A consumer owns .process/project.json
|
|
|
166
182
|
"timeoutSeconds": 600
|
|
167
183
|
}
|
|
168
184
|
]
|
|
185
|
+
},
|
|
186
|
+
"impactProfiles": {
|
|
187
|
+
"schemaVersion": 1,
|
|
188
|
+
"profiles": {
|
|
189
|
+
"development": [
|
|
190
|
+
{
|
|
191
|
+
"id": "unit-tests",
|
|
192
|
+
"run": ["python", "-m", "unittest", "tests.test_orders"],
|
|
193
|
+
"timeoutSeconds": 600,
|
|
194
|
+
"paths": ["src/orders/**", "tests/test_orders.py"]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "cross-cutting",
|
|
198
|
+
"run": ["python", "-m", "unittest"],
|
|
199
|
+
"timeoutSeconds": 900,
|
|
200
|
+
"paths": ["**/process-policy.json"]
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"review": [
|
|
204
|
+
{
|
|
205
|
+
"id": "package",
|
|
206
|
+
"run": ["python", "-m", "build"],
|
|
207
|
+
"timeoutSeconds": 600,
|
|
208
|
+
"paths": ["src/**", "pyproject.toml"]
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
169
212
|
}
|
|
170
213
|
}
|
|
171
214
|
|
|
@@ -520,6 +563,33 @@ When continuing an incomplete verification, inspect and execute the necessary se
|
|
|
520
563
|
The explicit `--profile` form remains a refresh and is never silently converted to
|
|
521
564
|
reuse.
|
|
522
565
|
|
|
566
|
+
For fast feedback, consumers may declare a versioned `impactProfiles` policy in
|
|
567
|
+
`.process/project.json` and run only units related to the candidate paths:
|
|
568
|
+
|
|
569
|
+
processctl change explain --change-id change-123 --impact
|
|
570
|
+
processctl change verify --change-id change-123 --affected --affected-profile development
|
|
571
|
+
|
|
572
|
+
Each impact unit owns an exact command and path-pattern coverage. Matching is
|
|
573
|
+
explicit and deterministic; overlapping units all run. A unit with
|
|
574
|
+
`scope: "global"` may subsume narrower units only when its declared paths include
|
|
575
|
+
the universal `**` pattern; a narrower pattern never covers unrelated paths. Every
|
|
576
|
+
changed path must resolve for
|
|
577
|
+
each requested profile. Missing policy, unsupported policy, or an unmapped path is
|
|
578
|
+
`unresolved`/`unavailable`: the process launches nothing and returns the action
|
|
579
|
+
`inspect-diff-and-update-impact-policy`. The agent must inspect the diff and
|
|
580
|
+
dependency reach, update the consumer-owned mapping or obtain an owner decision,
|
|
581
|
+
then repeat the explanation. It must not use a full profile as a fallback.
|
|
582
|
+
|
|
583
|
+
Affected execution under a version 1 policy is feedback evidence only and never
|
|
584
|
+
advances the lifecycle or satisfies a required profile. A consumer that can prove
|
|
585
|
+
complete coverage may use schema version 2 with `finalProfiles`; `--remaining` then
|
|
586
|
+
records the selected units as `impact-assurance` evidence. Each opted profile needs
|
|
587
|
+
an explicit global unit with the universal `**` pattern for cross-cutting reach, and
|
|
588
|
+
unresolved final coverage
|
|
589
|
+
blocks rather than falling back silently. Explicit `--profile` remains the full
|
|
590
|
+
refresh. Consumers that do not adopt the opt-in retain the existing final boundary;
|
|
591
|
+
the affected command does not guess a policy for them.
|
|
592
|
+
|
|
523
593
|
Assign an independent reviewer and submit its report:
|
|
524
594
|
|
|
525
595
|
processctl change review start \
|
|
@@ -606,9 +676,11 @@ At any point:
|
|
|
606
676
|
## Release to consumer PR
|
|
607
677
|
|
|
608
678
|
Each release includes [reviewed release contents](RELEASE_NOTES.md) generated from
|
|
609
|
-
the canonical manifest: shipped
|
|
610
|
-
|
|
611
|
-
|
|
679
|
+
the canonical manifest: one record per shipped feature/fix, its source issue, the
|
|
680
|
+
problem and affected paths, the application action, compatibility impact, important
|
|
681
|
+
notes, and upgrade guidance. The release PR reviews this file; the GitHub Release
|
|
682
|
+
publishes the same contents. See [the release procedure](RELEASING.md) for authoring
|
|
683
|
+
and checks.
|
|
612
684
|
|
|
613
685
|
This producer's release identity inputs and text assets declared by
|
|
614
686
|
`tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
|
|
@@ -72,6 +72,22 @@ child environment match. Legacy evidence without that identity reruns. This is
|
|
|
72
72
|
whole-profile reuse: equal check IDs and ordered side effects are never merged, and
|
|
73
73
|
the lifecycle receipt still records only actual executions.
|
|
74
74
|
|
|
75
|
+
Reuse is stage-aware rather than a blanket cache: the contract and plan travel by
|
|
76
|
+
digest; valid whole-profile verification can satisfy a continuation request; review
|
|
77
|
+
and finish consume the same reports after rechecking the snapshot; and a new
|
|
78
|
+
implementation cycle invalidates verification. Operation-local calculation reuse is
|
|
79
|
+
allowed only before a mutation/concurrency boundary. Impact feedback and generated
|
|
80
|
+
documents remain derived artifacts and never replace final required profiles or
|
|
81
|
+
independent review.
|
|
82
|
+
|
|
83
|
+
Consumers that can prove complete changed-path coverage may opt selected required
|
|
84
|
+
profiles into final impact assurance with `impactProfiles.schemaVersion: 2` and
|
|
85
|
+
`finalProfiles`. Each opted profile must declare an explicit global unit whose paths
|
|
86
|
+
include the universal `**` pattern for cross-cutting reach. The lifecycle then
|
|
87
|
+
records the selected units as final
|
|
88
|
+
verification evidence; missing or unresolved coverage blocks rather than silently
|
|
89
|
+
falling back. Existing version 1 policies remain feedback-only.
|
|
90
|
+
|
|
75
91
|
`processctl change review replace-reused` repairs only an initial pending assignment
|
|
76
92
|
proven to reuse another change's agent context, before a submitted review or normal
|
|
77
93
|
report file exists. It validates a fresh replacement and current verification,
|
|
@@ -142,6 +158,33 @@ Python 3.11 or newer and Git are required. A consumer owns .process/project.json
|
|
|
142
158
|
"timeoutSeconds": 600
|
|
143
159
|
}
|
|
144
160
|
]
|
|
161
|
+
},
|
|
162
|
+
"impactProfiles": {
|
|
163
|
+
"schemaVersion": 1,
|
|
164
|
+
"profiles": {
|
|
165
|
+
"development": [
|
|
166
|
+
{
|
|
167
|
+
"id": "unit-tests",
|
|
168
|
+
"run": ["python", "-m", "unittest", "tests.test_orders"],
|
|
169
|
+
"timeoutSeconds": 600,
|
|
170
|
+
"paths": ["src/orders/**", "tests/test_orders.py"]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"id": "cross-cutting",
|
|
174
|
+
"run": ["python", "-m", "unittest"],
|
|
175
|
+
"timeoutSeconds": 900,
|
|
176
|
+
"paths": ["**/process-policy.json"]
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
"review": [
|
|
180
|
+
{
|
|
181
|
+
"id": "package",
|
|
182
|
+
"run": ["python", "-m", "build"],
|
|
183
|
+
"timeoutSeconds": 600,
|
|
184
|
+
"paths": ["src/**", "pyproject.toml"]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
145
188
|
}
|
|
146
189
|
}
|
|
147
190
|
|
|
@@ -496,6 +539,33 @@ When continuing an incomplete verification, inspect and execute the necessary se
|
|
|
496
539
|
The explicit `--profile` form remains a refresh and is never silently converted to
|
|
497
540
|
reuse.
|
|
498
541
|
|
|
542
|
+
For fast feedback, consumers may declare a versioned `impactProfiles` policy in
|
|
543
|
+
`.process/project.json` and run only units related to the candidate paths:
|
|
544
|
+
|
|
545
|
+
processctl change explain --change-id change-123 --impact
|
|
546
|
+
processctl change verify --change-id change-123 --affected --affected-profile development
|
|
547
|
+
|
|
548
|
+
Each impact unit owns an exact command and path-pattern coverage. Matching is
|
|
549
|
+
explicit and deterministic; overlapping units all run. A unit with
|
|
550
|
+
`scope: "global"` may subsume narrower units only when its declared paths include
|
|
551
|
+
the universal `**` pattern; a narrower pattern never covers unrelated paths. Every
|
|
552
|
+
changed path must resolve for
|
|
553
|
+
each requested profile. Missing policy, unsupported policy, or an unmapped path is
|
|
554
|
+
`unresolved`/`unavailable`: the process launches nothing and returns the action
|
|
555
|
+
`inspect-diff-and-update-impact-policy`. The agent must inspect the diff and
|
|
556
|
+
dependency reach, update the consumer-owned mapping or obtain an owner decision,
|
|
557
|
+
then repeat the explanation. It must not use a full profile as a fallback.
|
|
558
|
+
|
|
559
|
+
Affected execution under a version 1 policy is feedback evidence only and never
|
|
560
|
+
advances the lifecycle or satisfies a required profile. A consumer that can prove
|
|
561
|
+
complete coverage may use schema version 2 with `finalProfiles`; `--remaining` then
|
|
562
|
+
records the selected units as `impact-assurance` evidence. Each opted profile needs
|
|
563
|
+
an explicit global unit with the universal `**` pattern for cross-cutting reach, and
|
|
564
|
+
unresolved final coverage
|
|
565
|
+
blocks rather than falling back silently. Explicit `--profile` remains the full
|
|
566
|
+
refresh. Consumers that do not adopt the opt-in retain the existing final boundary;
|
|
567
|
+
the affected command does not guess a policy for them.
|
|
568
|
+
|
|
499
569
|
Assign an independent reviewer and submit its report:
|
|
500
570
|
|
|
501
571
|
processctl change review start \
|
|
@@ -582,9 +652,11 @@ At any point:
|
|
|
582
652
|
## Release to consumer PR
|
|
583
653
|
|
|
584
654
|
Each release includes [reviewed release contents](RELEASE_NOTES.md) generated from
|
|
585
|
-
the canonical manifest: shipped
|
|
586
|
-
|
|
587
|
-
|
|
655
|
+
the canonical manifest: one record per shipped feature/fix, its source issue, the
|
|
656
|
+
problem and affected paths, the application action, compatibility impact, important
|
|
657
|
+
notes, and upgrade guidance. The release PR reviews this file; the GitHub Release
|
|
658
|
+
publishes the same contents. See [the release procedure](RELEASING.md) for authoring
|
|
659
|
+
and checks.
|
|
588
660
|
|
|
589
661
|
This producer's release identity inputs and text assets declared by
|
|
590
662
|
`tool.setuptools.data-files` use UTF-8 without BOM and LF, matching `.gitattributes`.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Engineering Process v2.6.0
|
|
2
|
+
|
|
3
|
+
Changes since v2.5.0.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Select and assure verification units from explicit change impact.** ([#205](https://github.com/phuongnse/engineering-process/issues/205))
|
|
8
|
+
- **Problem:** A consumer could only choose a whole profile or reuse a whole-profile result, so a small change lacked a standard way to run related verification units. When impact was unclear, an automatic full-suite fallback could hide that dependency reach had not been resolved, while explicit coverage could not yet satisfy a final required profile.
|
|
9
|
+
- **What changed:** Add versioned consumer-owned impactProfiles, deterministic changed-path resolution, change explain --impact, and change verify --affected. Every path must resolve to matching units or the command stops with an explicit re-analysis action; an explicit global pattern is the only broad selection rule. Add a schema-version 2 finalProfiles opt-in so complete explicit coverage can run selected units as final impact-assurance evidence. Policy or candidate changes, missing coverage and unresolved paths fail closed; schema-version 1 policies remain feedback-only.
|
|
10
|
+
- **Where:** `schemas/project.schema.json`, `schemas/verification-impact-selection.schema.json`, `schemas/verification-selection.schema.json`, `schemas/run.schema.json`, `schemas/receipt.schema.json`, `engineering_process/impact.py`, `engineering_process/repository.py`, `engineering_process/lifecycle.py`, `engineering_process/cli.py`, `engineering_process/project.py`, `process_assets/skills/change-verify/SKILL.md`, `process_assets/skills/change-plan/SKILL.md`, `SELF_HOSTING.md`, `README.md`, `tests/test_contracts.py`, `tests/test_impact.py`, `tests/test_lifecycle.py`, `tests/test_skills.py`
|
|
11
|
+
- **Apply:** Adopt the package release, add impactProfiles to the consumer project policy, map every changed path to one or more independently executable commands, and run change explain --impact before change verify --affected. Opt selected required profiles into schema version 2 finalProfiles only after reviewing complete path coverage and global cross-cutting units; then run change verify --remaining. Resolve every unresolved path by inspecting the diff/dependency reach and updating the consumer-owned mapping or recording an owner decision.
|
|
12
|
+
- **Compatibility:** Backward-compatible opt-in. Existing project files, explicit profile verification, whole-profile reuse, required lifecycle profiles, and final review/release evidence remain supported. Consumers without impactProfiles receive a clear unavailable result, and schema-version 1 policies remain feedback-only.
|
|
13
|
+
- **Notes:** Path patterns are a versioned consumer policy, not a process guess. Overlapping units all run; a global unit may subsume narrower units only when its declared paths include the explicit universal \*\* pattern, while narrower patterns never cover unrelated paths. Affected feedback does not advance the lifecycle or prove final assurance. Final assurance records execution mode and selection identity in the existing lifecycle evidence; it does not create a second lifecycle or infer coverage from filenames, labels, commands, or diagnostics.
|
|
14
|
+
|
|
15
|
+
## Fixes
|
|
16
|
+
|
|
17
|
+
- **Render release records as readable Markdown without gratuitous escapes.** ([#171](https://github.com/phuongnse/engineering-process/issues/171))
|
|
18
|
+
- **Problem:** Generated release notes escaped every punctuation mark and padded ordinary code references, making otherwise complete issue-level records difficult to read.
|
|
19
|
+
- **What changed:** Escape only Markdown and HTML-sensitive metadata syntax, keep ordinary punctuation readable, and render simple owned references as normal inline code while retaining safe fencing for embedded backticks.
|
|
20
|
+
- **Where:** `engineering_process/release_notes.py`, `tests/test_release.py`, `release-changes/README.md`, `RELEASING.md`, `ARTIFACT_STANDARDS.md`, `RELEASE_NOTES.md`
|
|
21
|
+
- **Apply:** Use the normal prepare-release workflow and review the generated RELEASE\_NOTES.md against release.json before publication.
|
|
22
|
+
- **Compatibility:** No breaking change. Schema-v5 manifests and legacy release-note data remain readable; the selected release-notes@1 structure and published v2.5.0 body are not rewritten.
|
|
23
|
+
- **Notes:** This correction applies to subsequently generated release bodies. Published release artifacts remain immutable and must be corrected only through the owner's release policy.
|
|
24
|
+
- **Batch reusable verification evidence across lifecycle stages.** ([#198](https://github.com/phuongnse/engineering-process/issues/198))
|
|
25
|
+
- **Problem:** Continuation verification could make repeated reuse decisions and authority calculations one profile at a time even when several reports already covered the same unchanged candidate.
|
|
26
|
+
- **What changed:** Batch valid whole-profile reuse decisions into one canonical state write and reuse one process-authority digest across stable evidence comparisons. Preserve the selected runtime path spelling and virtual-environment symlinks across POSIX and Windows while retaining fresh candidate snapshots, runtime identity checks, explicit refresh semantics, and correction-cycle invalidation.
|
|
27
|
+
- **Where:** `engineering_process/evidence.py`, `engineering_process/lifecycle.py`, `engineering_process/pr_description.py`, `process_assets/skills/change-verify/SKILL.md`, `README.md`, `tests/test_lifecycle.py`, `tests/test_skills.py`
|
|
28
|
+
- **Apply:** Use change verify --remaining for continuation work. The process reuses only exact valid reports and records reuse without relaunching their child commands.
|
|
29
|
+
- **Compatibility:** No breaking change. Explicit profile verification remains an unconditional refresh; stale, legacy, or mismatched evidence reruns; final review and finish still require exact-snapshot evidence.
|
|
30
|
+
- **Notes:** The optimization is operation-scoped and does not add a persistent success cache or reuse evidence across a changed candidate, mutation boundary, or incompatible runtime. Runtime path normalization no longer dereferences the selected executable before constructing the bounded child PATH.
|
|
31
|
+
- **Make the full verification suite deterministic and expose bounded timing diagnostics.** ([#201](https://github.com/phuongnse/engineering-process/issues/201))
|
|
32
|
+
- **Problem:** The repository-owned full verification suite took about six minutes on Windows and review-context tests incorrectly reported stale evidence when run after the complete suite, while local output did not identify the slowest tests.
|
|
33
|
+
- **What changed:** Give review-context tests an isolated fixture owner, seed their verified states once per test class, use the same bounded child environment as the verification boundary, and report aggregate, slow-test, fixture-setup, and profile timing without changing profile exit semantics or deleting behavioral coverage. Lifecycle fixtures clone an immutable Git snapshot instead of copying a live .git directory, avoiding races with Git maintenance locks.
|
|
34
|
+
- **Where:** `tests/test_review_contexts.py`, `tests/test_lifecycle.py`, `verification/run_test_suite.py`
|
|
35
|
+
- **Apply:** Run the normal development and review profiles; use the bounded suite timing and slow-test summary to diagnose future cost, and do not replace final verification with a focused subset.
|
|
36
|
+
- **Compatibility:** No breaking change. Existing profile commands, required checks, skip behavior, process/Git/filesystem boundaries, and stored evidence semantics remain supported.
|
|
37
|
+
- **Notes:** The reviewed change evidence records repeated comparable self-consumer measurements and the bounded improvement method; it does not establish a universal latency target. Shared mutable checkouts and blanket mocks remain out of scope.
|
|
38
|
+
|
|
39
|
+
## Upgrade and compatibility
|
|
40
|
+
|
|
41
|
+
Merge the complete hash-locked package/adoption PR, update the local and CI environments to the selected version, and start a fresh agent session.
|
|
42
|
+
|
|
43
|
+
Consumer CI, naming conventions and branch-protection settings remain consumer-owned; adoption does not configure them automatically.
|
|
44
|
+
|
|
45
|
+
No breaking changes are included. Read each change's Apply, Compatibility and Notes entry before adopting.
|
|
46
|
+
|
|
47
|
+
See [versioning and compatibility](https://github.com/phuongnse/engineering-process/blob/v2.6.0/VERSIONING.md) and [adoption guidance](https://github.com/phuongnse/engineering-process/blob/v2.6.0/SELF_HOSTING.md).
|
|
48
|
+
|
|
49
|
+
[Full change comparison](https://github.com/phuongnse/engineering-process/compare/v2.5.0...v2.6.0)
|
|
@@ -38,12 +38,14 @@ from .lifecycle import (
|
|
|
38
38
|
start_change,
|
|
39
39
|
start_review,
|
|
40
40
|
submit_review,
|
|
41
|
+
resolve_impact_work,
|
|
41
42
|
resolve_verification_work,
|
|
43
|
+
verify_affected,
|
|
42
44
|
verify_remaining,
|
|
43
45
|
verify_change,
|
|
44
46
|
)
|
|
45
47
|
from .issue import render_issue
|
|
46
|
-
from .project import load_project, readiness_summary
|
|
48
|
+
from .project import impact_profiles, load_project, readiness_summary
|
|
47
49
|
from .production_engineering import (
|
|
48
50
|
validate_plan_assessments,
|
|
49
51
|
validate_review_assessments,
|
|
@@ -135,6 +137,7 @@ def command_project_validate(args: argparse.Namespace) -> Result:
|
|
|
135
137
|
"project validate",
|
|
136
138
|
project=project["project"],
|
|
137
139
|
profiles=sorted(project["profiles"]),
|
|
140
|
+
impactProfiles=sorted(impact_profiles(project)),
|
|
138
141
|
requiredProfiles=project["lifecycle"]["requiredProfiles"],
|
|
139
142
|
readiness=readiness_summary(project),
|
|
140
143
|
), 0
|
|
@@ -326,6 +329,34 @@ def command_change_implement(args: argparse.Namespace) -> Result:
|
|
|
326
329
|
|
|
327
330
|
def command_change_verify(args: argparse.Namespace) -> Result:
|
|
328
331
|
process_root = _process_root(args)
|
|
332
|
+
if getattr(args, "affected", False):
|
|
333
|
+
selected_profiles = tuple(getattr(args, "affected_profile", []) or []) or None
|
|
334
|
+
state, selection, executions = verify_affected(
|
|
335
|
+
args.project_root,
|
|
336
|
+
process_root,
|
|
337
|
+
{},
|
|
338
|
+
args.change_id,
|
|
339
|
+
profiles=selected_profiles,
|
|
340
|
+
)
|
|
341
|
+
execution_status = executions[0]["status"] if executions else None
|
|
342
|
+
status = (
|
|
343
|
+
"passed"
|
|
344
|
+
if selection["status"] == "ready" and execution_status == "passed"
|
|
345
|
+
else selection["status"]
|
|
346
|
+
if selection["status"] != "ready"
|
|
347
|
+
else "failed"
|
|
348
|
+
)
|
|
349
|
+
return _state_result(
|
|
350
|
+
"change verify",
|
|
351
|
+
state,
|
|
352
|
+
status=status,
|
|
353
|
+
selection=selection,
|
|
354
|
+
execution="affected",
|
|
355
|
+
evidence="feedback-only; does not satisfy a required full profile",
|
|
356
|
+
executions=executions,
|
|
357
|
+
), (0 if status == "passed" else 1)
|
|
358
|
+
if getattr(args, "affected_profile", []):
|
|
359
|
+
raise ProcessError("--affected-profile requires --affected")
|
|
329
360
|
project = load_project(args.project_root, process_root)
|
|
330
361
|
if args.remaining:
|
|
331
362
|
state, selection = verify_remaining(
|
|
@@ -345,6 +376,7 @@ def command_change_verify(args: argparse.Namespace) -> Result:
|
|
|
345
376
|
{
|
|
346
377
|
"profile": profile,
|
|
347
378
|
"status": report["status"],
|
|
379
|
+
"executionMode": report.get("executionMode", "full"),
|
|
348
380
|
"launchCount": len(report["checks"]),
|
|
349
381
|
"durationMs": report.get("durationMs", check_duration),
|
|
350
382
|
"checkDurationMs": check_duration,
|
|
@@ -386,6 +418,18 @@ def command_change_verify(args: argparse.Namespace) -> Result:
|
|
|
386
418
|
|
|
387
419
|
def command_change_explain(args: argparse.Namespace) -> Result:
|
|
388
420
|
process_root = _process_root(args)
|
|
421
|
+
if getattr(args, "impact", False):
|
|
422
|
+
profiles = (args.profile,) if args.profile else None
|
|
423
|
+
selection = resolve_impact_work(
|
|
424
|
+
args.project_root,
|
|
425
|
+
process_root,
|
|
426
|
+
{},
|
|
427
|
+
args.change_id,
|
|
428
|
+
profiles=profiles,
|
|
429
|
+
)
|
|
430
|
+
return _result("change explain", selection=selection), 0
|
|
431
|
+
if getattr(args, "profile", None):
|
|
432
|
+
raise ProcessError("--profile for change explain requires --impact")
|
|
389
433
|
project = load_project(args.project_root, process_root)
|
|
390
434
|
selection = resolve_verification_work(
|
|
391
435
|
args.project_root, process_root, project, args.change_id
|
|
@@ -667,9 +711,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
667
711
|
verify_scope = change_verify.add_mutually_exclusive_group(required=True)
|
|
668
712
|
verify_scope.add_argument("--profile")
|
|
669
713
|
verify_scope.add_argument("--remaining", action="store_true")
|
|
714
|
+
verify_scope.add_argument("--affected", action="store_true")
|
|
715
|
+
change_verify.add_argument(
|
|
716
|
+
"--affected-profile",
|
|
717
|
+
action="append",
|
|
718
|
+
default=[],
|
|
719
|
+
help="limit --affected to one or more accepted profiles",
|
|
720
|
+
)
|
|
670
721
|
|
|
671
722
|
change_explain = _leaf(change_commands, "explain", command_change_explain)
|
|
672
723
|
change_explain.add_argument("--change-id", required=True)
|
|
724
|
+
change_explain.add_argument("--impact", action="store_true")
|
|
725
|
+
change_explain.add_argument("--profile")
|
|
673
726
|
|
|
674
727
|
review = change_commands.add_parser("review")
|
|
675
728
|
review_commands = review.add_subparsers(dest="review_command", required=True)
|
|
@@ -110,6 +110,7 @@ def verification_input_digest(
|
|
|
110
110
|
profile: str,
|
|
111
111
|
*,
|
|
112
112
|
runtime: dict[str, Any] | None = None,
|
|
113
|
+
authority_digest: str | None = None,
|
|
113
114
|
) -> str | None:
|
|
114
115
|
"""Bind reusable evidence to every input controlled by this process."""
|
|
115
116
|
runtime = runtime if runtime is not None else execution_identity()
|
|
@@ -119,7 +120,11 @@ def verification_input_digest(
|
|
|
119
120
|
return digest_json({
|
|
120
121
|
"authority": {
|
|
121
122
|
"version": VERSION,
|
|
122
|
-
"distribution":
|
|
123
|
+
"distribution": (
|
|
124
|
+
authority_digest
|
|
125
|
+
if authority_digest is not None
|
|
126
|
+
else distribution_digest(process_root)
|
|
127
|
+
),
|
|
123
128
|
},
|
|
124
129
|
"project": project,
|
|
125
130
|
"contractDigest": state["contract"]["digest"],
|
|
@@ -145,6 +150,7 @@ def verification_report_matches_inputs(
|
|
|
145
150
|
require_input: bool,
|
|
146
151
|
require_explicit_scope: bool = False,
|
|
147
152
|
runtime: dict[str, Any] | None = None,
|
|
153
|
+
authority_digest: str | None = None,
|
|
148
154
|
) -> bool:
|
|
149
155
|
"""Return whether one report is reusable for the supplied inputs."""
|
|
150
156
|
scope = report.get("scope")
|
|
@@ -169,5 +175,6 @@ def verification_report_matches_inputs(
|
|
|
169
175
|
state,
|
|
170
176
|
profile,
|
|
171
177
|
runtime=runtime,
|
|
178
|
+
authority_digest=authority_digest,
|
|
172
179
|
)
|
|
173
180
|
return current is not None and recorded == current
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Strict, consumer-owned change-impact resolution for fast feedback."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fnmatch import fnmatchcase
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Iterable
|
|
8
|
+
|
|
9
|
+
from .contracts import ProcessError, digest_json, validate_document
|
|
10
|
+
from .distribution import schemas_root
|
|
11
|
+
from .project import impact_assurance_profiles, impact_profiles
|
|
12
|
+
from .repository import changed_paths
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _matches(path: str, patterns: Iterable[str]) -> bool:
|
|
16
|
+
return any(fnmatchcase(path, pattern) for pattern in patterns)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def resolve_impact_selection(
|
|
20
|
+
project_root: Path,
|
|
21
|
+
process_root: Path,
|
|
22
|
+
project: dict[str, Any],
|
|
23
|
+
state: dict[str, Any],
|
|
24
|
+
*,
|
|
25
|
+
profiles: Iterable[str] | None = None,
|
|
26
|
+
) -> dict[str, Any]:
|
|
27
|
+
"""Resolve every changed path to explicit consumer verification units.
|
|
28
|
+
|
|
29
|
+
This function is intentionally fail-closed. It never converts an unknown
|
|
30
|
+
path into a full profile request.
|
|
31
|
+
"""
|
|
32
|
+
required = tuple(state["contract"]["document"]["requiredProfiles"])
|
|
33
|
+
requested = tuple(required if profiles is None else profiles)
|
|
34
|
+
invalid = sorted(set(requested) - set(required))
|
|
35
|
+
if invalid:
|
|
36
|
+
raise ProcessError(
|
|
37
|
+
"impact selection may request only accepted required profiles: "
|
|
38
|
+
+ ", ".join(invalid)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
base = state.get("comparisonBaseCommit")
|
|
42
|
+
if not isinstance(base, str) or not base:
|
|
43
|
+
raise ProcessError("impact selection requires a pinned comparison base")
|
|
44
|
+
paths = changed_paths(project_root, base)
|
|
45
|
+
policy = impact_profiles(project)
|
|
46
|
+
assurance_profiles = tuple(impact_assurance_profiles(project))
|
|
47
|
+
policy_digest = digest_json(project.get("impactProfiles", {}))
|
|
48
|
+
selected: list[dict[str, Any]] = []
|
|
49
|
+
requirements: list[dict[str, Any]] = []
|
|
50
|
+
unresolved: list[dict[str, str]] = []
|
|
51
|
+
|
|
52
|
+
for profile in requested:
|
|
53
|
+
units = policy.get(profile) if isinstance(policy, dict) else None
|
|
54
|
+
if not isinstance(units, list) or not units:
|
|
55
|
+
requirements.append(
|
|
56
|
+
{
|
|
57
|
+
"profile": profile,
|
|
58
|
+
"status": "unavailable",
|
|
59
|
+
"selectedUnits": [],
|
|
60
|
+
"unresolvedPaths": [],
|
|
61
|
+
"reason": "the consumer has not declared an impact policy for this profile",
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
unresolved.extend({"profile": profile, "path": path} for path in paths)
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
matched: dict[str, set[str]] = {unit["id"]: set() for unit in units}
|
|
68
|
+
missing: list[str] = []
|
|
69
|
+
for path in paths:
|
|
70
|
+
matches = [unit for unit in units if _matches(path, unit["paths"])]
|
|
71
|
+
if not matches:
|
|
72
|
+
missing.append(path)
|
|
73
|
+
unresolved.append({"profile": profile, "path": path})
|
|
74
|
+
continue
|
|
75
|
+
for unit in matches:
|
|
76
|
+
matched[unit["id"]].add(path)
|
|
77
|
+
|
|
78
|
+
# Only an explicit universal pattern can subsume narrower units. A
|
|
79
|
+
# partial global pattern remains subject to the ordinary unresolved-
|
|
80
|
+
# path boundary.
|
|
81
|
+
global_ids = {
|
|
82
|
+
unit["id"]
|
|
83
|
+
for unit in units
|
|
84
|
+
if (
|
|
85
|
+
unit.get("scope", "matched") == "global"
|
|
86
|
+
and "**" in unit["paths"]
|
|
87
|
+
and matched[unit["id"]]
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
selected_for_profile = [
|
|
92
|
+
{
|
|
93
|
+
"profile": profile,
|
|
94
|
+
"id": unit["id"],
|
|
95
|
+
"matchedPaths": sorted(matched[unit["id"]]),
|
|
96
|
+
}
|
|
97
|
+
for unit in units
|
|
98
|
+
if matched[unit["id"]]
|
|
99
|
+
and (not global_ids or unit["id"] in global_ids)
|
|
100
|
+
]
|
|
101
|
+
selected.extend(selected_for_profile)
|
|
102
|
+
requirements.append(
|
|
103
|
+
{
|
|
104
|
+
"profile": profile,
|
|
105
|
+
"status": "unresolved" if missing else "ready",
|
|
106
|
+
"selectedUnits": selected_for_profile,
|
|
107
|
+
"unresolvedPaths": sorted(missing),
|
|
108
|
+
**(
|
|
109
|
+
{
|
|
110
|
+
"reason": "one or more changed paths have no declared verification unit"
|
|
111
|
+
}
|
|
112
|
+
if missing
|
|
113
|
+
else {}
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
unresolved = sorted(
|
|
119
|
+
unresolved,
|
|
120
|
+
key=lambda item: (item["profile"], item["path"]),
|
|
121
|
+
)
|
|
122
|
+
status = "ready"
|
|
123
|
+
if unresolved or not paths:
|
|
124
|
+
status = "unresolved" if policy else "unavailable"
|
|
125
|
+
selection: dict[str, Any] = {
|
|
126
|
+
"schemaVersion": 2 if assurance_profiles else 1,
|
|
127
|
+
"changeId": state["changeId"],
|
|
128
|
+
"status": status,
|
|
129
|
+
"comparisonBaseCommit": base,
|
|
130
|
+
"policyDigest": policy_digest,
|
|
131
|
+
"assuranceProfiles": [
|
|
132
|
+
profile for profile in requested if profile in assurance_profiles
|
|
133
|
+
],
|
|
134
|
+
"changedPaths": list(paths),
|
|
135
|
+
"requirements": requirements,
|
|
136
|
+
"selectedUnits": selected,
|
|
137
|
+
"unresolvedPaths": unresolved,
|
|
138
|
+
}
|
|
139
|
+
if status != "ready":
|
|
140
|
+
if not paths:
|
|
141
|
+
reason = "the candidate has no changed path from the pinned comparison base"
|
|
142
|
+
elif not policy:
|
|
143
|
+
reason = "the consumer has not declared a versioned impact policy"
|
|
144
|
+
else:
|
|
145
|
+
reason = "every requested profile must resolve every changed path before affected execution"
|
|
146
|
+
selection["resolution"] = {
|
|
147
|
+
"action": "inspect-diff-and-update-impact-policy",
|
|
148
|
+
"reason": reason,
|
|
149
|
+
"paths": list(paths),
|
|
150
|
+
}
|
|
151
|
+
return validate_document(
|
|
152
|
+
selection,
|
|
153
|
+
"verification-impact-selection",
|
|
154
|
+
schema_root=schemas_root(process_root),
|
|
155
|
+
source="verification impact selection",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def unavailable_impact_selection(
|
|
160
|
+
project_root: Path,
|
|
161
|
+
process_root: Path,
|
|
162
|
+
state: dict[str, Any],
|
|
163
|
+
reason: str,
|
|
164
|
+
*,
|
|
165
|
+
profiles: Iterable[str] | None = None,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""Build an actionable unavailable result when policy loading fails."""
|
|
168
|
+
required = tuple(state["contract"]["document"]["requiredProfiles"])
|
|
169
|
+
requested = tuple(required if profiles is None else profiles)
|
|
170
|
+
invalid = sorted(set(requested) - set(required))
|
|
171
|
+
if invalid:
|
|
172
|
+
raise ProcessError(
|
|
173
|
+
"impact selection may request only accepted required profiles: "
|
|
174
|
+
+ ", ".join(invalid)
|
|
175
|
+
)
|
|
176
|
+
paths = changed_paths(project_root, state["comparisonBaseCommit"])
|
|
177
|
+
unresolved = [
|
|
178
|
+
{"profile": profile, "path": path}
|
|
179
|
+
for profile in requested
|
|
180
|
+
for path in paths
|
|
181
|
+
]
|
|
182
|
+
selection = {
|
|
183
|
+
"schemaVersion": 1,
|
|
184
|
+
"changeId": state["changeId"],
|
|
185
|
+
"status": "unavailable",
|
|
186
|
+
"comparisonBaseCommit": state["comparisonBaseCommit"],
|
|
187
|
+
"changedPaths": list(paths),
|
|
188
|
+
"requirements": [
|
|
189
|
+
{
|
|
190
|
+
"profile": profile,
|
|
191
|
+
"status": "unavailable",
|
|
192
|
+
"selectedUnits": [],
|
|
193
|
+
"unresolvedPaths": list(paths),
|
|
194
|
+
"reason": "the consumer impact policy could not be loaded",
|
|
195
|
+
}
|
|
196
|
+
for profile in requested
|
|
197
|
+
],
|
|
198
|
+
"selectedUnits": [],
|
|
199
|
+
"unresolvedPaths": unresolved,
|
|
200
|
+
"resolution": {
|
|
201
|
+
"action": "inspect-diff-and-update-impact-policy",
|
|
202
|
+
"reason": f"the consumer impact policy is invalid or unsupported: {reason}"[:2000],
|
|
203
|
+
"paths": list(paths),
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
return validate_document(
|
|
207
|
+
selection,
|
|
208
|
+
"verification-impact-selection",
|
|
209
|
+
schema_root=schemas_root(process_root),
|
|
210
|
+
source="verification impact selection",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def impact_unit_lookup(
|
|
215
|
+
project: dict[str, Any], selection: dict[str, Any]
|
|
216
|
+
) -> dict[tuple[str, str], dict[str, Any]]:
|
|
217
|
+
"""Resolve selected output ids back to the consumer-owned command objects."""
|
|
218
|
+
policies = impact_profiles(project)
|
|
219
|
+
lookup: dict[tuple[str, str], dict[str, Any]] = {}
|
|
220
|
+
for profile, units in policies.items():
|
|
221
|
+
for unit in units:
|
|
222
|
+
lookup[(profile, unit["id"])] = unit
|
|
223
|
+
result: dict[tuple[str, str], dict[str, Any]] = {}
|
|
224
|
+
for selected in selection["selectedUnits"]:
|
|
225
|
+
key = (selected["profile"], selected["id"])
|
|
226
|
+
if key not in lookup:
|
|
227
|
+
raise ProcessError(
|
|
228
|
+
f"impact policy changed while resolving unit {key[0]}:{key[1]}"
|
|
229
|
+
)
|
|
230
|
+
result[key] = lookup[key]
|
|
231
|
+
return result
|