engineering-process 2.1.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {engineering_process-2.1.0 → engineering_process-2.3.0}/MANIFEST.in +1 -0
- {engineering_process-2.1.0/engineering_process.egg-info → engineering_process-2.3.0}/PKG-INFO +57 -8
- {engineering_process-2.1.0 → engineering_process-2.3.0}/README.md +56 -7
- engineering_process-2.3.0/RELEASE_NOTES.md +23 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/__init__.py +1 -1
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_posix.py +10 -4
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_windows.py +2 -1
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/adoption.py +10 -3
- engineering_process-2.3.0/engineering_process/artifact_standards.py +155 -0
- engineering_process-2.3.0/engineering_process/automation_name.py +31 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/cli.py +77 -1
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/commands.py +6 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/contracts.py +7 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/distribution.py +5 -2
- engineering_process-2.3.0/engineering_process/issue.py +112 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/lifecycle.py +36 -0
- engineering_process-2.1.0/engineering_process/publication_compat.py → engineering_process-2.3.0/engineering_process/pr_description.py +99 -138
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/project.py +5 -0
- engineering_process-2.3.0/engineering_process/publication_compat.py +58 -0
- engineering_process-2.3.0/engineering_process/release_notes.py +76 -0
- engineering_process-2.3.0/engineering_process/source_publication.py +133 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0/engineering_process.egg-info}/PKG-INFO +57 -8
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/SOURCES.txt +18 -0
- engineering_process-2.3.0/process_assets/skills/change-complete/SKILL.md +79 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-review/SKILL.md +9 -2
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-start/SKILL.md +10 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-verify/SKILL.md +17 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/deliver-change/SKILL.md +26 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/production-engineering/SKILL.md +7 -0
- engineering_process-2.3.0/process_assets/standards/automation-name.v1.json +16 -0
- engineering_process-2.3.0/process_assets/standards/issue.v1.json +78 -0
- engineering_process-2.3.0/process_assets/standards/pull-request.v1.json +129 -0
- engineering_process-2.3.0/process_assets/standards/release-notes.v1.json +34 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/pyproject.toml +14 -1
- engineering_process-2.3.0/schemas/artifact-selection.schema.json +67 -0
- engineering_process-2.3.0/schemas/artifact-standard.schema.json +407 -0
- engineering_process-2.3.0/schemas/automation-name-data.schema.json +35 -0
- engineering_process-2.3.0/schemas/issue-data.schema.json +44 -0
- engineering_process-2.3.0/schemas/pr-description-data.schema.json +63 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/project.schema.json +8 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/receipt.schema.json +20 -2
- engineering_process-2.3.0/schemas/release-notes-data.schema.json +103 -0
- engineering_process-2.3.0/schemas/renovate-preset.schema.json +30 -0
- engineering_process-2.3.0/templates/PULL_REQUEST_TEMPLATE.md +36 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/renovate.json +1 -1
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_adoption.py +69 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_architecture.py +7 -1
- engineering_process-2.3.0/tests/test_artifact_standards.py +413 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_cli.py +1 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_commands.py +100 -2
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_contracts.py +81 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_distribution.py +13 -1
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_lifecycle.py +171 -1
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_publication_compat.py +101 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_supervisor_posix.py +35 -0
- engineering_process-2.1.0/RELEASE_NOTES.md +0 -23
- engineering_process-2.1.0/process_assets/skills/change-complete/SKILL.md +0 -39
- engineering_process-2.1.0/templates/PULL_REQUEST_TEMPLATE.md +0 -39
- {engineering_process-2.1.0 → engineering_process-2.3.0}/LICENSE +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/__main__.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_contract.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_windows_job.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/helper_launch.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/production_engineering.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/release.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/repository.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/requirements-dev.txt +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/requirements-runtime.txt +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/skills.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/supervision.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/dependency_links.txt +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/entry_points.txt +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/requires.txt +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process.egg-info/top_level.txt +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process-graph.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-implement/SKILL.md +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/change-plan/SKILL.md +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/process-improve/SKILL.md +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/process_assets/skills/production-engineering/invariants.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/change.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/plan.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/process-graph.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/process-lock.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/production-engineering.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/project-legacy.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/release-change.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/release.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/review.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/schemas/run.schema.json +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/setup.cfg +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/AGENTS.process.md +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/adopt-process-windows-job.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/templates/adopt-process.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_automation.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_production_engineering.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_publication_workflow.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_pypi_cache_horizon.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_release.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_repository.py +0 -0
- {engineering_process-2.1.0 → engineering_process-2.3.0}/tests/test_skills.py +0 -0
{engineering_process-2.1.0/engineering_process.egg-info → engineering_process-2.3.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.3.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
|
|
@@ -73,10 +73,31 @@ The coordinator hands review to an actual reviewer. A new agent review starts in
|
|
|
73
73
|
fresh context with the accepted source artifacts and no suggested verdict; the same
|
|
74
74
|
reviewer continues corrections. The reviewer inspects the change and authors its
|
|
75
75
|
own report. The runner's existing task/session interaction and returned result make
|
|
76
|
-
that work inspectable. Sharing a
|
|
76
|
+
that work inspectable. Sharing a provider or account is allowed; this is a
|
|
77
77
|
workflow for independent judgment, not authenticated identity or merge enforcement.
|
|
78
78
|
See [change-review](process_assets/skills/change-review/SKILL.md) for the handoff.
|
|
79
79
|
|
|
80
|
+
Every delegated agent and reviewer follows the active user-selected model and
|
|
81
|
+
reasoning effort under [deliver-change's settings rule](process_assets/skills/deliver-change/SKILL.md#preserve-agent-execution-settings).
|
|
82
|
+
It covers fresh agents and resumed sessions, requires native runtime confirmation,
|
|
83
|
+
and permits no autonomous upgrade or downgrade. The portable guidance does not make
|
|
84
|
+
processctl a provider runtime or a model-quality evaluator.
|
|
85
|
+
|
|
86
|
+
Consumers using the packaged publication compatibility policy can enable
|
|
87
|
+
`lifecycle.publication.required: true` in `.process/project.json` after adopting a
|
|
88
|
+
version that supports it. Start then rejects an invalid branch before creating a
|
|
89
|
+
run. Finish checks the current branch, head subject, and pinned comparison-base-to-head
|
|
90
|
+
range, and writes publication metadata in a version 2 receipt. Version 1 receipts and
|
|
91
|
+
consumers without the opt-in remain supported. Missing pinned bases and mutations
|
|
92
|
+
during the check fail completion.
|
|
93
|
+
|
|
94
|
+
PR readiness additionally requires the selected ready-state body/title and exact
|
|
95
|
+
head/range checks. Follow the [completion preflight](process_assets/skills/change-complete/SKILL.md)
|
|
96
|
+
and repeat those checks in required CI. This repository's existing
|
|
97
|
+
`verification/verify_publication.py --pull-request` uses the actual PR metadata and
|
|
98
|
+
selected consumer standard; local lifecycle completion alone does not establish
|
|
99
|
+
external PR readiness. Integration-branch pushes remain consumer-owned operations.
|
|
100
|
+
|
|
80
101
|
Runtime architecture is enforced by semantic fitness functions, not module or source-
|
|
81
102
|
line quotas. Every module has an explicit dependency layer, imports point toward lower
|
|
82
103
|
layers, the internal graph remains acyclic, and lifecycle.py alone owns state
|
|
@@ -122,6 +143,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
|
|
|
122
143
|
Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
|
|
123
144
|
stdout or stderr that could contain secrets.
|
|
124
145
|
|
|
146
|
+
For a fresh checkout of this repository, create a virtual environment with a supported
|
|
147
|
+
Python and install `engineering_process/requirements-runtime.txt`,
|
|
148
|
+
`engineering_process/requirements-dev.txt`, and
|
|
149
|
+
`engineering_process/requirements-build.txt` through that interpreter. Use
|
|
150
|
+
`.venv/Scripts/python.exe` on Windows or `.venv/bin/python` on POSIX to invoke the
|
|
151
|
+
source CLI and verification scripts. Other consumers own their runtime paths and
|
|
152
|
+
setup commands; installed consumers can use their environment's `processctl` entry
|
|
153
|
+
point without a source checkout.
|
|
154
|
+
|
|
155
|
+
The bounded runner prepends the invoking Python executable's directory to child
|
|
156
|
+
`PATH`. Bare commands therefore search that environment before the inherited path,
|
|
157
|
+
while declared argument arrays, explicit executable paths, secret filtering and
|
|
158
|
+
remaining path entries are preserved. This alignment does not install dependencies.
|
|
159
|
+
|
|
125
160
|
A failed profile also reports a safe `diagnostic` descriptor containing only its
|
|
126
161
|
validated profile identifier, check identifier, canonical one-based profile position,
|
|
127
162
|
and a fixed `processctl` argument array. The position remains unambiguous even when a
|
|
@@ -270,6 +305,13 @@ The transaction writes only managed surfaces:
|
|
|
270
305
|
|
|
271
306
|
It removes obsolete skills named by the previous process lock and preserves
|
|
272
307
|
consumer-owned skills and instructions. Applying the same version twice is a no-op.
|
|
308
|
+
Consumer-owned `.process/standards.json` selections and override definitions are also
|
|
309
|
+
preserved; the managed PR template follows the effective supported standard. See
|
|
310
|
+
[consumer document standards](ARTIFACT_STANDARDS.md).
|
|
311
|
+
[Issue records](ARTIFACT_STANDARDS.md#issues) use the same default-and-override
|
|
312
|
+
mechanism for open requests and closed evidence while tracker actions remain consumer-owned.
|
|
313
|
+
[Automation naming](ARTIFACT_STANDARDS.md#automation-names) uses the same versioned
|
|
314
|
+
selection and override mechanism; consumers apply it in their bootstrap and provider checks.
|
|
273
315
|
The legacy managed runner can enter 1.0 directly, so consumers do not need a chain of
|
|
274
316
|
per-version migration documents. The same transaction deletes the retired migration
|
|
275
317
|
directory and standing automation policy; the Windows Job Object helper remains a
|
|
@@ -482,7 +524,7 @@ supported review schemas; ordinary legacy non-blocking observations remain reada
|
|
|
482
524
|
|
|
483
525
|
### Public pull-request evidence
|
|
484
526
|
|
|
485
|
-
The
|
|
527
|
+
The default pull-request standard keeps public assurance separate from local
|
|
486
528
|
lifecycle identity. Its five sections and labeled fields are ordered and stable:
|
|
487
529
|
outcome and scope; source, risk, compatibility, and stack; profiles, snapshot, and
|
|
488
530
|
completion receipt; verdict, cycles, blocking status, and non-blocking dispositions;
|
|
@@ -492,10 +534,15 @@ handle, or local `.process/runs` path. Those values remain in lifecycle state, w
|
|
|
492
534
|
they enforce self-review rejection but do not pretend to be provider-authenticated
|
|
493
535
|
review identities.
|
|
494
536
|
|
|
495
|
-
|
|
537
|
+
The template and validator derive this contract from the same versioned definition.
|
|
538
|
+
Consumers can select a supported override through `.process/standards.json`; see
|
|
539
|
+
[generation, verification and custom-format boundaries](ARTIFACT_STANDARDS.md).
|
|
540
|
+
`processctl publication validate-pr` checks that selected contract deterministically.
|
|
496
541
|
It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
|
|
497
542
|
structure. Completion checkboxes belong only to the Completion gate section. Ready
|
|
498
|
-
pull requests must have every checkbox checked
|
|
543
|
+
pull requests must have every checkbox checked and no unresolved default placeholder
|
|
544
|
+
values; drafts may retain pending fields and unchecked work. The author/coordinator
|
|
545
|
+
replaces them with actual evidence before ready/merge; the reviewer supplies the verdict.
|
|
499
546
|
One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
|
|
500
547
|
consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
|
|
501
548
|
the complete keyword/reference syntax repeated for every issue; drafts cannot close
|
|
@@ -555,17 +602,19 @@ authorizes that merge.
|
|
|
555
602
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
556
603
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
557
604
|
|
|
558
|
-
The optional [Renovate preset](templates/renovate.json)
|
|
559
|
-
|
|
605
|
+
The optional [Renovate preset](templates/renovate.json) and public template are generated
|
|
606
|
+
from the packaged PR standard. It supplies only `prHeader` and `prBodyTemplate`;
|
|
560
607
|
dependency selection, supported platforms, schedules, major-update approval,
|
|
561
608
|
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
562
609
|
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
610
|
+
Consumers overriding that standard can generate a matching preset with
|
|
611
|
+
`processctl artifact renovate-preset`; wire it through their own Renovate configuration.
|
|
563
612
|
|
|
564
613
|
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
565
614
|
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
566
615
|
commit of a verified release that contains the preset. Remove obsolete inline
|
|
567
616
|
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
568
|
-
The preset targets the
|
|
617
|
+
The default preset targets the draft grammar used by 1.2.4 and this distribution;
|
|
569
618
|
it does not claim compatibility with earlier publication contracts. A future
|
|
570
619
|
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
571
620
|
|
|
@@ -49,10 +49,31 @@ The coordinator hands review to an actual reviewer. A new agent review starts in
|
|
|
49
49
|
fresh context with the accepted source artifacts and no suggested verdict; the same
|
|
50
50
|
reviewer continues corrections. The reviewer inspects the change and authors its
|
|
51
51
|
own report. The runner's existing task/session interaction and returned result make
|
|
52
|
-
that work inspectable. Sharing a
|
|
52
|
+
that work inspectable. Sharing a provider or account is allowed; this is a
|
|
53
53
|
workflow for independent judgment, not authenticated identity or merge enforcement.
|
|
54
54
|
See [change-review](process_assets/skills/change-review/SKILL.md) for the handoff.
|
|
55
55
|
|
|
56
|
+
Every delegated agent and reviewer follows the active user-selected model and
|
|
57
|
+
reasoning effort under [deliver-change's settings rule](process_assets/skills/deliver-change/SKILL.md#preserve-agent-execution-settings).
|
|
58
|
+
It covers fresh agents and resumed sessions, requires native runtime confirmation,
|
|
59
|
+
and permits no autonomous upgrade or downgrade. The portable guidance does not make
|
|
60
|
+
processctl a provider runtime or a model-quality evaluator.
|
|
61
|
+
|
|
62
|
+
Consumers using the packaged publication compatibility policy can enable
|
|
63
|
+
`lifecycle.publication.required: true` in `.process/project.json` after adopting a
|
|
64
|
+
version that supports it. Start then rejects an invalid branch before creating a
|
|
65
|
+
run. Finish checks the current branch, head subject, and pinned comparison-base-to-head
|
|
66
|
+
range, and writes publication metadata in a version 2 receipt. Version 1 receipts and
|
|
67
|
+
consumers without the opt-in remain supported. Missing pinned bases and mutations
|
|
68
|
+
during the check fail completion.
|
|
69
|
+
|
|
70
|
+
PR readiness additionally requires the selected ready-state body/title and exact
|
|
71
|
+
head/range checks. Follow the [completion preflight](process_assets/skills/change-complete/SKILL.md)
|
|
72
|
+
and repeat those checks in required CI. This repository's existing
|
|
73
|
+
`verification/verify_publication.py --pull-request` uses the actual PR metadata and
|
|
74
|
+
selected consumer standard; local lifecycle completion alone does not establish
|
|
75
|
+
external PR readiness. Integration-branch pushes remain consumer-owned operations.
|
|
76
|
+
|
|
56
77
|
Runtime architecture is enforced by semantic fitness functions, not module or source-
|
|
57
78
|
line quotas. Every module has an explicit dependency layer, imports point toward lower
|
|
58
79
|
layers, the internal graph remains acyclic, and lifecycle.py alone owns state
|
|
@@ -98,6 +119,20 @@ Commands are argument arrays, never shell strings. Each command has a finite tim
|
|
|
98
119
|
Output has a hard aggregate budget; evidence stores byte counts and hashes, never raw
|
|
99
120
|
stdout or stderr that could contain secrets.
|
|
100
121
|
|
|
122
|
+
For a fresh checkout of this repository, create a virtual environment with a supported
|
|
123
|
+
Python and install `engineering_process/requirements-runtime.txt`,
|
|
124
|
+
`engineering_process/requirements-dev.txt`, and
|
|
125
|
+
`engineering_process/requirements-build.txt` through that interpreter. Use
|
|
126
|
+
`.venv/Scripts/python.exe` on Windows or `.venv/bin/python` on POSIX to invoke the
|
|
127
|
+
source CLI and verification scripts. Other consumers own their runtime paths and
|
|
128
|
+
setup commands; installed consumers can use their environment's `processctl` entry
|
|
129
|
+
point without a source checkout.
|
|
130
|
+
|
|
131
|
+
The bounded runner prepends the invoking Python executable's directory to child
|
|
132
|
+
`PATH`. Bare commands therefore search that environment before the inherited path,
|
|
133
|
+
while declared argument arrays, explicit executable paths, secret filtering and
|
|
134
|
+
remaining path entries are preserved. This alignment does not install dependencies.
|
|
135
|
+
|
|
101
136
|
A failed profile also reports a safe `diagnostic` descriptor containing only its
|
|
102
137
|
validated profile identifier, check identifier, canonical one-based profile position,
|
|
103
138
|
and a fixed `processctl` argument array. The position remains unambiguous even when a
|
|
@@ -246,6 +281,13 @@ The transaction writes only managed surfaces:
|
|
|
246
281
|
|
|
247
282
|
It removes obsolete skills named by the previous process lock and preserves
|
|
248
283
|
consumer-owned skills and instructions. Applying the same version twice is a no-op.
|
|
284
|
+
Consumer-owned `.process/standards.json` selections and override definitions are also
|
|
285
|
+
preserved; the managed PR template follows the effective supported standard. See
|
|
286
|
+
[consumer document standards](ARTIFACT_STANDARDS.md).
|
|
287
|
+
[Issue records](ARTIFACT_STANDARDS.md#issues) use the same default-and-override
|
|
288
|
+
mechanism for open requests and closed evidence while tracker actions remain consumer-owned.
|
|
289
|
+
[Automation naming](ARTIFACT_STANDARDS.md#automation-names) uses the same versioned
|
|
290
|
+
selection and override mechanism; consumers apply it in their bootstrap and provider checks.
|
|
249
291
|
The legacy managed runner can enter 1.0 directly, so consumers do not need a chain of
|
|
250
292
|
per-version migration documents. The same transaction deletes the retired migration
|
|
251
293
|
directory and standing automation policy; the Windows Job Object helper remains a
|
|
@@ -458,7 +500,7 @@ supported review schemas; ordinary legacy non-blocking observations remain reada
|
|
|
458
500
|
|
|
459
501
|
### Public pull-request evidence
|
|
460
502
|
|
|
461
|
-
The
|
|
503
|
+
The default pull-request standard keeps public assurance separate from local
|
|
462
504
|
lifecycle identity. Its five sections and labeled fields are ordered and stable:
|
|
463
505
|
outcome and scope; source, risk, compatibility, and stack; profiles, snapshot, and
|
|
464
506
|
completion receipt; verdict, cycles, blocking status, and non-blocking dispositions;
|
|
@@ -468,10 +510,15 @@ handle, or local `.process/runs` path. Those values remain in lifecycle state, w
|
|
|
468
510
|
they enforce self-review rejection but do not pretend to be provider-authenticated
|
|
469
511
|
review identities.
|
|
470
512
|
|
|
471
|
-
|
|
513
|
+
The template and validator derive this contract from the same versioned definition.
|
|
514
|
+
Consumers can select a supported override through `.process/standards.json`; see
|
|
515
|
+
[generation, verification and custom-format boundaries](ARTIFACT_STANDARDS.md).
|
|
516
|
+
`processctl publication validate-pr` checks that selected contract deterministically.
|
|
472
517
|
It rejects missing, repeated, misplaced, hidden, unordered, or unsupported visible
|
|
473
518
|
structure. Completion checkboxes belong only to the Completion gate section. Ready
|
|
474
|
-
pull requests must have every checkbox checked
|
|
519
|
+
pull requests must have every checkbox checked and no unresolved default placeholder
|
|
520
|
+
values; drafts may retain pending fields and unchecked work. The author/coordinator
|
|
521
|
+
replaces them with actual evidence before ready/merge; the reviewer supplies the verdict.
|
|
475
522
|
One trailing `Refs ISSUE.` line remains optional. A ready, contract-identified final
|
|
476
523
|
consumer adoption may instead use `Closes ISSUE, closes OWNER/REPOSITORY#NUMBER.` with
|
|
477
524
|
the complete keyword/reference syntax repeated for every issue; drafts cannot close
|
|
@@ -531,17 +578,19 @@ authorizes that merge.
|
|
|
531
578
|
This repository opts in through .github/renovate.json, so it receives the same
|
|
532
579
|
adoption PR as every other consumer. See SELF_HOSTING.md and RELEASING.md.
|
|
533
580
|
|
|
534
|
-
The optional [Renovate preset](templates/renovate.json)
|
|
535
|
-
|
|
581
|
+
The optional [Renovate preset](templates/renovate.json) and public template are generated
|
|
582
|
+
from the packaged PR standard. It supplies only `prHeader` and `prBodyTemplate`;
|
|
536
583
|
dependency selection, supported platforms, schedules, major-update approval,
|
|
537
584
|
commands, draft policy, and merge authority remain consumer-owned. Regenerate it
|
|
538
585
|
with `python verification/generate_renovate_preset.py`; `--check` rejects drift.
|
|
586
|
+
Consumers overriding that standard can generate a matching preset with
|
|
587
|
+
`processctl artifact renovate-preset`; wire it through their own Renovate configuration.
|
|
539
588
|
|
|
540
589
|
Consumers add `github>phuongnse/engineering-process//templates/renovate#COMMIT_SHA`
|
|
541
590
|
to their existing `extends` array, replacing `COMMIT_SHA` with the full source
|
|
542
591
|
commit of a verified release that contains the preset. Remove obsolete inline
|
|
543
592
|
`prHeader` and `prBodyTemplate` overrides, including matching package-rule overrides.
|
|
544
|
-
The preset targets the
|
|
593
|
+
The default preset targets the draft grammar used by 1.2.4 and this distribution;
|
|
545
594
|
it does not claim compatibility with earlier publication contracts. A future
|
|
546
595
|
grammar change must preserve this adapter or ship an explicit consumer migration.
|
|
547
596
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Engineering Process v2.3.0
|
|
2
|
+
|
|
3
|
+
Changes since v2.2.0.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Generate and verify open and closed issue records from a versioned default or consumer\-owned override while keeping tracker actions and product workflow consumer\-owned\. ([#182](https://github.com/phuongnse/engineering-process/issues/182))
|
|
8
|
+
- Opt into publication checks at lifecycle start and completion\, with pinned source metadata in version 2 receipts and exact PR head\/body checks before readiness\; existing consumers and version 1 receipts remain supported\. ([#180](https://github.com/phuongnse/engineering-process/issues/180))
|
|
9
|
+
|
|
10
|
+
## Fixes
|
|
11
|
+
|
|
12
|
+
- Require delegated agents and reviewers to preserve the active user\-selected model and reasoning effort\, with native runtime confirmation on spawn and resume and no autonomous model changes\. ([#182](https://github.com/phuongnse/engineering-process/issues/182))
|
|
13
|
+
- Resolve bare child commands through the invoking Python environment in fresh sessions\, preserving installed dependencies\, declared argument arrays\, explicit executables and environment filtering\. ([#181](https://github.com/phuongnse/engineering-process/issues/181))
|
|
14
|
+
|
|
15
|
+
## Upgrade and compatibility
|
|
16
|
+
|
|
17
|
+
Merge the complete hash-locked package/adoption PR, update the local and CI environments to the selected version, and start a fresh agent session.
|
|
18
|
+
|
|
19
|
+
Consumer CI, naming conventions and branch-protection settings remain consumer-owned; adoption does not configure them automatically.
|
|
20
|
+
|
|
21
|
+
See [versioning and compatibility](https://github.com/phuongnse/engineering-process/blob/v2.3.0/VERSIONING.md) and [adoption guidance](https://github.com/phuongnse/engineering-process/blob/v2.3.0/SELF_HOSTING.md).
|
|
22
|
+
|
|
23
|
+
[Full change comparison](https://github.com/phuongnse/engineering-process/compare/v2.2.0...v2.3.0)
|
{engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_posix.py
RENAMED
|
@@ -7,6 +7,7 @@ import ctypes
|
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
import secrets
|
|
10
|
+
import shutil
|
|
10
11
|
import signal
|
|
11
12
|
import subprocess
|
|
12
13
|
import sys
|
|
@@ -35,9 +36,13 @@ def _enable_subreaper() -> None:
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def _ps_process_table() -> tuple[dict[int, int], str | None]:
|
|
39
|
+
# Supervision must not depend on the consumer's PATH.
|
|
40
|
+
application = shutil.which("ps", path=os.defpath)
|
|
41
|
+
if application is None:
|
|
42
|
+
return {}, "process table snapshot could not start"
|
|
38
43
|
try:
|
|
39
44
|
result = subprocess.run(
|
|
40
|
-
[
|
|
45
|
+
[application, "-axo", "pid=,ppid="],
|
|
41
46
|
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
|
42
47
|
timeout=PROCESS_TABLE_TIMEOUT_SECONDS, check=False, text=True,
|
|
43
48
|
)
|
|
@@ -190,7 +195,7 @@ class PosixProcessSupervisor:
|
|
|
190
195
|
if not command or "\x00" in command:
|
|
191
196
|
raise OSError("command executable is invalid")
|
|
192
197
|
supplied = Path(command)
|
|
193
|
-
if supplied.is_absolute() or
|
|
198
|
+
if supplied.is_absolute() or os.path.dirname(command):
|
|
194
199
|
candidate = supplied if supplied.is_absolute() else working_directory / supplied
|
|
195
200
|
try:
|
|
196
201
|
resolved = candidate.resolve(strict=True)
|
|
@@ -198,7 +203,8 @@ class PosixProcessSupervisor:
|
|
|
198
203
|
raise OSError(f"executable is unavailable: {command}") from error
|
|
199
204
|
if not resolved.is_file() or not os.access(resolved, os.X_OK):
|
|
200
205
|
raise OSError(f"executable is unavailable: {command}")
|
|
201
|
-
|
|
206
|
+
# The symlink path may select a virtual environment.
|
|
207
|
+
return candidate.absolute()
|
|
202
208
|
for raw_directory in environment.get("PATH", "").split(os.pathsep):
|
|
203
209
|
directory = Path(raw_directory or ".")
|
|
204
210
|
if not directory.is_absolute():
|
|
@@ -209,7 +215,7 @@ class PosixProcessSupervisor:
|
|
|
209
215
|
except OSError:
|
|
210
216
|
continue
|
|
211
217
|
if resolved.is_file() and os.access(resolved, os.X_OK):
|
|
212
|
-
return
|
|
218
|
+
return candidate.absolute()
|
|
213
219
|
raise OSError(f"executable is unavailable: {command}")
|
|
214
220
|
|
|
215
221
|
def spawn(
|
{engineering_process-2.1.0 → engineering_process-2.3.0}/engineering_process/_supervisor_windows.py
RENAMED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import ntpath
|
|
6
7
|
import os
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
import subprocess
|
|
@@ -99,7 +100,7 @@ def resolve_windows_application(
|
|
|
99
100
|
raise OSError("command executable is invalid")
|
|
100
101
|
separator = os.pathsep if path_separator is None else path_separator
|
|
101
102
|
supplied = Path(command)
|
|
102
|
-
explicit_path = supplied.is_absolute() or
|
|
103
|
+
explicit_path = supplied.is_absolute() or bool(ntpath.dirname(command))
|
|
103
104
|
suffix = supplied.suffix.casefold()
|
|
104
105
|
if suffix in _UNSUPPORTED_SHELL_SUFFIXES:
|
|
105
106
|
raise OSError(
|
|
@@ -12,6 +12,7 @@ import tempfile
|
|
|
12
12
|
from typing import Any, Callable
|
|
13
13
|
|
|
14
14
|
from . import VERSION
|
|
15
|
+
from .artifact_standards import load_standard_catalog
|
|
15
16
|
from .contracts import ProcessError, read_json, validate_document
|
|
16
17
|
from .distribution import (
|
|
17
18
|
distribution_digest,
|
|
@@ -21,6 +22,7 @@ from .distribution import (
|
|
|
21
22
|
skills_root,
|
|
22
23
|
)
|
|
23
24
|
from .project import normalize_project, project_path
|
|
25
|
+
from .pr_description import render_template
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
MAX_REQUIREMENTS_BYTES = 2_000_000
|
|
@@ -253,9 +255,8 @@ def _expected_files(
|
|
|
253
255
|
"utf-8"
|
|
254
256
|
)
|
|
255
257
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
).read_text(encoding="utf-8")
|
|
258
|
+
standards = load_standard_catalog(project_root, process_root)
|
|
259
|
+
pull_request_template = render_template(standards.resolve("pull-request"), process_root=process_root)
|
|
259
260
|
pull_request_path = project_root / ".github" / "PULL_REQUEST_TEMPLATE.md"
|
|
260
261
|
existing_pull_request = (
|
|
261
262
|
pull_request_path.read_text(encoding="utf-8")
|
|
@@ -301,6 +302,12 @@ def _expected_files(
|
|
|
301
302
|
writes[Path(".process/process.lock")] = (
|
|
302
303
|
json.dumps(lock, indent=2, sort_keys=True) + "\n"
|
|
303
304
|
).encode("utf-8")
|
|
305
|
+
collisions = standards.consumer_files.intersection(writes.keys() | deletions)
|
|
306
|
+
if collisions:
|
|
307
|
+
raise ProcessError(
|
|
308
|
+
"consumer standard files conflict with managed adoption paths; move the definitions and update the selection: "
|
|
309
|
+
+ ", ".join(sorted(path.as_posix() for path in collisions))
|
|
310
|
+
)
|
|
304
311
|
total = sum(len(value) for value in writes.values())
|
|
305
312
|
if total > MAX_MANAGED_BYTES:
|
|
306
313
|
raise ProcessError("managed adoption output exceeds its aggregate limit")
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Resolve one versioned artifact contract for both generation and verification."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .contracts import ProcessError, digest_json, load_and_validate
|
|
12
|
+
from .distribution import schemas_root
|
|
13
|
+
from .repository import STATE_PREFIXES, _git
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
IDENTIFIER = re.compile(r"[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?")
|
|
17
|
+
MAX_DOCUMENT_BYTES = 1_000_000
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class ArtifactStandard:
|
|
22
|
+
document: dict[str, Any]
|
|
23
|
+
source: str
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def rules(self) -> dict[str, Any]:
|
|
27
|
+
return self.document["rules"]
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def metadata(self) -> dict[str, Any]:
|
|
31
|
+
return {
|
|
32
|
+
**{key: self.document[key] for key in ("id", "version", "artifact", "adapter")},
|
|
33
|
+
"digest": digest_json(self.document),
|
|
34
|
+
"source": self.source,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def unresolved(self, value: str) -> bool:
|
|
38
|
+
# These are reserved values in an owned protocol, not prose classification.
|
|
39
|
+
return value.strip().casefold() in {
|
|
40
|
+
item.strip().casefold() for item in self.document.get("pendingValues", [])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _consumer_file(root: Path, relative: str) -> Path:
|
|
45
|
+
path = root / relative
|
|
46
|
+
if not path.resolve().is_relative_to(root):
|
|
47
|
+
raise ProcessError(f"standard path escapes the consumer repository: {relative}")
|
|
48
|
+
if path.resolve() != path or Path(relative).as_posix() != relative:
|
|
49
|
+
raise ProcessError(f"standard path must be canonical without links: {relative}")
|
|
50
|
+
if any(part in {"..", ".git"} for part in Path(relative).parts):
|
|
51
|
+
raise ProcessError(f"standard path is not an owned repository file: {relative}")
|
|
52
|
+
current = root
|
|
53
|
+
for part in Path(relative).parts:
|
|
54
|
+
current /= part
|
|
55
|
+
if current.is_symlink():
|
|
56
|
+
raise ProcessError(f"standard paths cannot traverse symlinks: {relative}")
|
|
57
|
+
if not path.is_file():
|
|
58
|
+
raise ProcessError(f"consumer standard file is missing: {relative}")
|
|
59
|
+
encoded = os.fsencode(Path(relative).as_posix())
|
|
60
|
+
if any(encoded.startswith(prefix) for prefix in STATE_PREFIXES):
|
|
61
|
+
raise ProcessError(f"standard file is excluded from lifecycle snapshots: {relative}")
|
|
62
|
+
inventory = _git(root, ["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", relative])
|
|
63
|
+
if encoded not in inventory.split(b"\0"):
|
|
64
|
+
raise ProcessError(f"standard file must be included in the consumer Git snapshot: {relative}")
|
|
65
|
+
return path
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _unique(values: list[str], description: str) -> None:
|
|
69
|
+
if len(values) != len(set(values)):
|
|
70
|
+
raise ProcessError(f"artifact standard requires unique {description}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _validate_relations(document: dict[str, Any]) -> None:
|
|
74
|
+
_unique([value.strip().casefold() for value in document.get("pendingValues", [])], "pending values")
|
|
75
|
+
rules = document["rules"]
|
|
76
|
+
if document["adapter"] == "pr-description":
|
|
77
|
+
sections = rules["sections"]
|
|
78
|
+
_unique([section["heading"] for section in sections], "section headings")
|
|
79
|
+
for kind in ("fields", "checks"):
|
|
80
|
+
entries = [entry for section in sections for entry in section[kind]]
|
|
81
|
+
_unique([entry["id"] for entry in entries], f"{kind} ids")
|
|
82
|
+
_unique([entry["label"] for entry in entries], f"{kind} labels")
|
|
83
|
+
elif document["adapter"] == "release-notes":
|
|
84
|
+
_unique([group["type"] for group in rules["groups"]], "change types")
|
|
85
|
+
_unique([section["id"] for section in rules["sections"]], "release section ids")
|
|
86
|
+
_unique([item["heading"] for item in rules["groups"] + rules["sections"]], "release headings")
|
|
87
|
+
elif document["adapter"] == "issue":
|
|
88
|
+
for state, definition in rules["states"].items():
|
|
89
|
+
sections = definition["sections"]
|
|
90
|
+
_unique([section["heading"] for section in sections], f"{state} issue section headings")
|
|
91
|
+
for kind in ("fields", "checks"):
|
|
92
|
+
entries = [entry for section in sections for entry in section[kind]]
|
|
93
|
+
_unique([entry["id"] for entry in entries], f"{state} issue {kind} ids")
|
|
94
|
+
_unique([entry["label"] for entry in entries], f"{state} issue {kind} labels")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def read_document(path: Path) -> bytes:
|
|
98
|
+
with path.open("rb") as stream:
|
|
99
|
+
data = stream.read(MAX_DOCUMENT_BYTES + 1)
|
|
100
|
+
if len(data) > MAX_DOCUMENT_BYTES:
|
|
101
|
+
raise ProcessError("artifact body exceeds its size limit")
|
|
102
|
+
data.decode("utf-8")
|
|
103
|
+
return data
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class StandardCatalog:
|
|
108
|
+
"""One selection snapshot for adapter resolution and adoption ownership checks."""
|
|
109
|
+
|
|
110
|
+
project_root: Path | None
|
|
111
|
+
process_root: Path
|
|
112
|
+
selections: dict[str, Any]
|
|
113
|
+
consumer_files: frozenset[Path]
|
|
114
|
+
|
|
115
|
+
def resolve(self, artifact: str) -> ArtifactStandard:
|
|
116
|
+
if len(artifact) > 128 or not IDENTIFIER.fullmatch(artifact):
|
|
117
|
+
raise ProcessError("artifact must be a canonical identifier")
|
|
118
|
+
selection = self.selections.get(artifact, {"builtin": f"{artifact}@1"})
|
|
119
|
+
if "path" in selection:
|
|
120
|
+
path = _consumer_file(self.project_root, selection["path"])
|
|
121
|
+
source = selection["path"]
|
|
122
|
+
else:
|
|
123
|
+
name, version = selection["builtin"].rsplit("@", 1)
|
|
124
|
+
path = self.process_root / "process_assets" / "standards" / f"{name}.v{version}.json"
|
|
125
|
+
source = selection["builtin"]
|
|
126
|
+
if path.is_symlink() or not path.is_file():
|
|
127
|
+
raise ProcessError(f"unsupported packaged artifact standard: {source}")
|
|
128
|
+
document = load_and_validate(path, "artifact-standard", schema_root=schemas_root(self.process_root))
|
|
129
|
+
if document["artifact"] != artifact:
|
|
130
|
+
raise ProcessError(f"selected standard is for {document['artifact']}, not {artifact}")
|
|
131
|
+
if "builtin" in selection and (name != artifact or document["version"] != int(version)):
|
|
132
|
+
raise ProcessError("packaged standard identity does not match its selection")
|
|
133
|
+
_validate_relations(document)
|
|
134
|
+
return ArtifactStandard(document, source)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def load_standard_catalog(project_root: Path | None, process_root: Path) -> StandardCatalog:
|
|
138
|
+
selections: dict[str, Any] = {}
|
|
139
|
+
consumer_files: set[Path] = set()
|
|
140
|
+
if project_root is not None:
|
|
141
|
+
project_root = project_root.resolve()
|
|
142
|
+
selector = project_root / ".process" / "standards.json"
|
|
143
|
+
if selector.exists() or selector.is_symlink():
|
|
144
|
+
document = load_and_validate(
|
|
145
|
+
_consumer_file(project_root, ".process/standards.json"),
|
|
146
|
+
"artifact-selection", schema_root=schemas_root(process_root),
|
|
147
|
+
)
|
|
148
|
+
selections = document["artifacts"]
|
|
149
|
+
consumer_files = {Path(".process/standards.json")}
|
|
150
|
+
consumer_files.update(Path(value["path"]) for value in selections.values() if "path" in value)
|
|
151
|
+
return StandardCatalog(project_root, process_root, selections, frozenset(consumer_files))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def resolve_standard(project_root: Path | None, process_root: Path, artifact: str) -> ArtifactStandard:
|
|
155
|
+
return load_standard_catalog(project_root, process_root).resolve(artifact)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Compose automation names from a selected convention and consumer-owned components."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .artifact_standards import ArtifactStandard
|
|
7
|
+
from .contracts import ProcessError, validate_document
|
|
8
|
+
from .distribution import distribution_root, schemas_root
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def render_name(
|
|
12
|
+
standard: ArtifactStandard,
|
|
13
|
+
data: dict[str, Any],
|
|
14
|
+
*,
|
|
15
|
+
state: str = "ready",
|
|
16
|
+
process_root: Path | None = None,
|
|
17
|
+
) -> str:
|
|
18
|
+
if standard.document["adapter"] != "automation-name":
|
|
19
|
+
raise ProcessError("selected standard requires a different name adapter")
|
|
20
|
+
validate_document(data, "automation-name-data", schema_root=schemas_root(distribution_root(process_root)))
|
|
21
|
+
if state not in {"draft", "ready"}:
|
|
22
|
+
raise ProcessError("artifact state must be draft or ready")
|
|
23
|
+
rules = standard.rules
|
|
24
|
+
if set(data["components"]) != set(rules["components"]):
|
|
25
|
+
raise ProcessError("name components must exactly match the selected standard")
|
|
26
|
+
name = rules["separator"].join(data["components"][key] for key in rules["components"])
|
|
27
|
+
if rules["case"] == "lower":
|
|
28
|
+
name = name.lower()
|
|
29
|
+
if len(name) > rules["maxLength"]:
|
|
30
|
+
raise ProcessError("automation name exceeds the selected length limit")
|
|
31
|
+
return name + "\n"
|