업무 프로세스·전자 결재 문제 해결
누락되거나 렌더링되지 않거나 거부되는 Business Process descriptor와 전자 결재 업무 폼을, 라이프사이클·권한 검사를 약화하지 않고 진단합니다.
업무 프로세스와 전자 결재
Descriptor가 보이지 않으면 발견·테넌트 설치·라이프사이클·권한 순서로 확인합니다. 렌더링이나 실행 실패는 아래 증상별 절차를 따릅니다.
첫 번째를 정리해주는 doctor부터 시작하세요.
task artisan -- nexia-apps:doctor-package-app qualitycomposer 항목이 있다는 것이 레코드 동작이 승인됐다는 증거는 아닙니다. Descriptor는 동작을 후보로 만들 뿐입니다. 여러분의 App은 여전히 페이로드를 검증하고 최종 쓰기에서 Policy를 강제해야 합니다.
증상 색인
| 증상 | 절 |
|---|---|
부팅 시 descriptor 이름이 담긴 InvalidArgumentException | Descriptor가 부팅 시 검증에 실패한다 |
kind [X] must be one of: serviceTask, sendTask, receiveTask | work action kind가 틀렸다 |
| BPMN 선택기에 동작이 없음 | Descriptor가 아예 나타나지 않는다 |
| Approval composer에 바인딩이 없음 | Descriptor가 아예 나타나지 않는다 |
| 바인딩은 제시되는데 composer가 아무것도 렌더링하지 않음 | 바인딩이 아무것도 렌더링하지 않는다 |
| Approval 문서가 비었거나 라벨이 잘못됨 | 문서가 비었거나 라벨이 잘못됐다 |
| 라벨이 i18n 키 원문으로 보임 | 라벨이 키 원문으로 보인다 |
| 게시된 프로세스가 어떤 태스크에서 실패 | 게시된 프로세스가 깨진다 |
| 승인된 건이 레코드를 바꾸지 않음 | Approval은 성공했는데 아무 일도 없다 |
| 고정 Approval 제출이 stale 또는 불일치로 거부됨 | 고정 제출이 거부된다 |
| 기존 작업이 옛 동작을 씀 | 실행 중인 정의 밑에서 동작이 바뀌었다 |
Descriptor가 부팅 시 검증에 실패한다
ApprovalFormBindingDescriptor [quality.inspection_decision.submit] must declare at least one entry mode.
ApprovalFormBindingDescriptor [quality.inspection_decision.submit] entry mode [attach] must be one of: create, link
ApprovalFormBindingDescriptor [quality] resource_key must be a non-empty string.
SlotWidgetDescriptor [quality.x] family_key must be a non-empty string.
ProcessTemplateDescriptor key must be a non-empty string.
원인. Descriptor 생성자가 즉시 검증하므로, 잘못된 descriptor는 요청 중간이 아니라 부팅 때 실패합니다. 메시지가 파생된 descriptor id를 알려줍니다.
해결. 메시지가 지목한 필드를 읽으세요. 흔한 원인입니다.
| 메시지가 언급한 것 | 해결 |
|---|---|
entry mode | create나 link 또는 둘 다만 쓰기 |
must be a non-empty string | 값 공급 |
family_key | 생략은 ''이 아니라 null 전달 |
공백만 있는 값은 실수로 들어간 빈 문자열을 잡기 위해 일부러 거부합니다.
확인. 애플리케이션이 부팅되고 descriptor가 자기 레지스트리에 나타납니다.
예방. packages/assets/src/Descriptors/AssetApprovalDescriptors.php처럼 바인딩과 스키마 식별자를 함께 관리합니다. 전자 결재에서 연결 순서를 확인하세요.
work action kind가 틀렸다
ProcessWorkActionDescriptor [quality.inspection.finalize] kind [userTask] must be one of: serviceTask, sendTask, receiveTask
원인. userTask는 의도적으로 빠져 있습니다. 사람 태스크는 다른 계열입니다.
해결. 상호작용 모양으로 고르세요.
| Kind | 의미 | topic이 가리키는 것 |
|---|---|---|
serviceTask | 프로세스가 여러분 App을 호출하고 기다림 | App이 소비하는 워커 topic |
sendTask | 프로세스가 메시지를 내보냄 | 메시지 topic |
receiveTask | 프로세스가 메시지를 기다림 | 메시지 topic |
사람 태스크는 같은 AppDescriptorSet에 ProcessUserTaskFormDescriptor를 게시하세요.
확인. 동작이 BPMN 요소 선택기에 나타납니다.
예방. 사람을 위한 폼은 결코 work action이 아닙니다.
Descriptor가 아예 나타나지 않는다
Action absent from the BPMN picker, or binding absent from the Approval composer.
원인. 아래 관문 중 하나입니다. 순서대로 확인하면 첫 실패가 이유를 설명합니다. 발견과 설치는 항상 적용되고 나머지 둘은 조건부입니다.
| 관문 | 확인 | 충족되지 않았을 때 증상 |
|---|---|---|
| 발견 | nexia-apps:doctor-package-app → contribution locations | 클래스가 스캔된 적 없음 |
| 설치 | 이 테넌트의 설치 상태 | App이 operational이 아니라 아무것도 해석되지 않음 |
| 라이프사이클 | descriptor의 status | Deprecated는 새 선택을 막고, Removed는 해석을 멈춤 |
| 권한 | 행위자의 권한, descriptor가 선언한 경우에만 | 권한 없는 행위자에게 올바르게 숨겨짐. ProcessTemplateDescriptor와 ProcessWorkActionDescriptor에는 permission 필드가 없어 이 관문이 적용되지 않음 |
해결. 발견은 클래스가 contributionLocations()가 선언한 디렉터리 아래 있고 contribution 인터페이스를 선언하는지 확인하세요. 트레이트만으로는 발견되지 않습니다. 설치는 nexia-apps:repair-tenant-app, 라이프사이클은 Active로 설정, 권한은 그 권한을 담은 Role 배정입니다.
확인. App이 operational인 테넌트에서 그 권한을 가진 행위자에게 descriptor가 나타납니다.
예방. 발견을 의심하기 전에 권한과 설치를 확인하세요. doctor가 발견 문제는 몇 초로 정리합니다.
바인딩이 아무것도 렌더링하지 않는다
Binding offered, composer renders nothing.
원인. Approval 바인딩은 자기완결적이지 않습니다. 산출물 셋이 키로 이어지고, 하나만 어긋나도 composer가 제시할 수는 있지만 렌더링하지 못하는 바인딩이 됩니다.
| 산출물 | 키 | 일치해야 할 대상 |
|---|---|---|
ApprovalFormBindingDescriptor | formWidgetKey | 어떤 SlotWidgetDescriptor.key |
SlotWidgetDescriptor | slot | approval.composer.business_form |
SlotWidgetDescriptor | component | 등록된 프런트엔드 컴포넌트 |
해결. 키를 맞춘 뒤 컴포넌트 등록을 확인하세요.
task artisan -- nexia-apps:doctor-package-app qualitydoctor는 이 짝짓기를 확인할 수 없습니다. shell component pairing 검사는 pageElements()만 순회하고 Slot Widget의 component는 그 맵에 없습니다. 이름이 resources/js/index.ts에 바인딩됐는지 직접 확인하세요. 파생 가능한 경로에 화면을 두거나 overrides 항목을 추가한 뒤 다시 빌드하세요.
task artisan -- nexia-apps:activate-package-app quality --build-assets확인. 업무 폼을 고르면 composer에 여러분 컴포넌트가 렌더링됩니다.
예방. packages/assets/src/Descriptors/AssetApprovalDescriptors.php처럼 바인딩과 스키마 식별자를 함께 관리합니다. 전자 결재에서 연결 순서를 확인하세요.
문서가 비었거나 라벨이 잘못됐다
Approval document is empty or mislabeled.
원인. 바인딩의 documentSchemaKey가 기여된 ApprovalDocumentSchema와 맞지 않습니다. 스키마 키는 {appKey}.{resourceKey}입니다.
해결. 바인딩의 documentSchemaKey가 스키마의 {appKey}.{resourceKey}와 같은지, 그리고 클래스가 AppDescriptorContribution::appDescriptors()에서 ApprovalDocumentSchema를 반환하는지 확인하세요. 바인딩과 스키마를 함께 배포하세요. packages/assets/src/Descriptors/AssetApprovalDescriptors.php가 두 선언을 함께 제공합니다.
확인. 승인자가 선언된 섹션과 필드를 봅니다.
예방. 스키마는 App이 저장하는 것이 아니라 승인자가 읽는 것을 선언합니다. 판단에 필요한 필드를 넣고 내부 장부는 빼세요.
라벨이 키 원문으로 보인다
quality.approval.inspection_decision.submit.label
원인. labelKey, descriptionKey, approvedLabelKey, rejectedLabelKey 중 하나가 패키지 로케일 카탈로그에 항목이 없습니다.
해결. 지원 로케일 전부 — ko, en, zh — 에 문자열 값을 가진 평면 JSON으로 키를 넣은 뒤 캐시를 지우세요.
task artisan -- nexia-runtime:validate-translations
task artisan -- nexia-runtime:clear-translation-catalog-cache확인. OK translation catalogs가 나오고 각 로케일에서 라벨이 렌더링됩니다.
예방. descriptor와 같은 커밋에서 모든 로케일을 추가하세요.
게시된 프로세스가 깨진다
A published process fails at a task whose descriptor was removed.
원인. Process 정의는 Work Action을 {app, action_key}로 해석하고 생성된
외부 태스크마다 해석된 App, action, version, topic을 고정합니다. 이 tuple이
활성 descriptor와 더 이상 일치하지 않거나 descriptor가 Removed·삭제됐거나
App이 비활성화되면 fail closed합니다. Process Template는 생성 시점 출처일
뿐이며 이미 게시된 정의의 런타임 의존이 아닙니다.
해결. descriptor를 Active로 되돌리거나 App을 재활성화하세요. 기능을 정말 퇴역시키는 것이라면 참조하는 정의들을 대체물 위로 다시 작성해야 합니다.
확인. 실패했던 태스크에서 프로세스가 재개됩니다.
예방. 순서대로 퇴역시키세요.
Deprecated— 새 작성이 그것을 선택하지 않게 되고, 실행 중 작업은 계속됩니다.- 기존 참조가 빠지기를 기다리거나 마이그레이션합니다.
- 그다음
Removed또는 삭제입니다.
바로 삭제하면 Removed와 같게 동작하는데, 이유의 기록이 남지 않습니다.
Approval은 성공했는데 아무 일도 없다
Approved case did not change the record.
원인. App 결과 핸들러가 누락되었거나 실패·거절되면 레코드가 바뀌지 않을 수 있습니다. 바인딩은 요청을 라우팅하고 쓰기를 승인하지 않습니다. 플랫폼이 결과를 기록하고 여러분 App에 알립니다. 그다음 여러분 App이 자기 Policy를 강제하며 변경을 적용합니다.
해결. 승인된 결과에 대해 App이 수행하는 레코드 동작을 구현하고 테스트하세요. submitPermissionKey는 제출을 통제하고 이후의 쓰기는 통제하지 않습니다.
확인. 승인 뒤 레코드에 변경이 반영되고 Policy가 평가됐습니다.
예방. 승인된 건은 App 판단의 입력이지 판단의 대체물이 아닙니다. Approval이 예라고 했다고 Policy를 건너뛰지 마세요.
고정 제출이 거부된다
Bound approval submission is refused as stale or mismatched.
원인. 선택된 template이 제출한 binding 키·버전과 더 이상 맞지 않거나, 현재 descriptor 버전이 다르거나, ResourceRef가 그 binding에 대한 정규 reference가 아니거나, 해석된 결재선이 낡았습니다. 호스트는 case를 만들기 전에 전부 확인합니다.
해결. Template, binding, 정규 Resource reference, Resource 버전, 결재선을 다시 해석해 새 BoundApprovalSubmission을 만드세요. 옛 snapshot 일부만 편집해 재시도하지 마세요.
확인. 제출 뒤 ApprovalHost::caseSummary()가 고정된 binding·Resource 버전을 반환합니다. Case가 없거나 비정규 legacy reference라면 부분 summary가 아니라 null입니다.
예방. Binding, template, Resource, 결재선 버전을 하나의 고정된 판단으로 취급하세요. 연산이 재생될 수 있다면 idempotency key와 fingerprint를 운반하세요.
실행 중인 정의 밑에서 동작이 바뀌었다
Existing work uses behavior different from when the definition was published.
원인. 핸들러의 동작은 바뀌었는데 descriptor 버전은 유지되어, 외부 태스크가 고정한 버전으로 기존 동작을 식별할 수 없는 상태입니다.
해결. 기존 action의 동작을 복원합니다. 호환되지 않는 동작은 새 action key로 공개하고 정의를 명시적으로 전환하세요. Work Action registry는 버전이 아니라 App과 action key로 조회합니다. 같은 키에 두 번째 버전을 공개해도 옛 구현을 선택할 수 있는 것은 아닙니다. 기존 키의 버전을 교체하면 이미 생성된 외부 태스크는 버전 검사에서 거절됩니다.
확인. 기존 태스크는 원래 업무 효과를 유지하고 새 정의는 대체 키를 사용해야 합니다. 실패 태스크를 복구하기 전 이미 커밋된 업무 효과가 있는지도 확인하세요.
예방. 게시한 action을 변경하기 전에 기존 태스크를 유지할 출시 방식을 정하세요.
| 변경 | 기존 외부 태스크에 미치는 영향 |
|---|---|
| 같은 버전에서 동작 변경 | 버전 검사만으로 의미 변화를 감지할 수 없음 |
| 같은 action key의 버전 교체 | 고정된 태스크가 descriptor 버전 검사에서 실패 |
| 대체 action key 추가, 기존 핸들러 유지 | 기존 태스크가 원래 키로 계속 실행 가능 |
Deprecated | 새 선택에서는 숨기고 기존 런타임 해석은 유지 |
Removed 또는 삭제 | 해석 실패 |
관련 문서
- 업무 프로세스 — 협력하는 세 조각
- 전자 결재 계약 — 바인딩과 스키마 시그니처
- 업무 프로세스 계약 — Process descriptor 시그니처
- 확장 모델 — 라이프사이클과 버전 고정
- Slot Widget 추가 — 바인딩이 맞춰야 하는 위젯