전자 서명 계약
NEXIA 전자 서명의 App contribution, 문서, 서명자, 요청, 결과, 오류 계약을 찾습니다.
전자 서명 계약
요청을 만들기 전에 문서 경로를 정하세요. App 문서는 양식 바인딩과 데이터 소스를 게시하고, 업로드한 PDF는 요청 계약으로 전달합니다. 컨테이너에서 SignatureDocumentHost 또는 SignatureHost를 조회하세요. 반환 상태와 권한이 확인된 링크를 결과로 사용하며 문서 생성만으로 서명이 완료되었다고 판단하지 않습니다.
기존 요청 조회
복원된 행위자·Legal Entity와 App 소유 대상의 ResourceRef를 사용하세요.
$requestPublicId는 요청 식별자이며 접근 권한을 부여하지 않습니다.
use Nexia\Signature\Contracts\SignatureHost;
use Nexia\Signature\SignatureRequestQueryInput;
$query = new SignatureRequestQueryInput(
legalEntity: $legalEntity,
actor: $actor,
subject: $subject,
requestPublicId: $requestPublicId,
);
$summary = app(SignatureHost::class)->summary($query);호스트는 SignatureRequestSummary 또는 null을 반환합니다. 결과가 없을 때 다른
대상의 요청을 노출하지 않도록 처리하세요. 문서 생성과 요청 제출에는 아래 게시·제출 계약을 사용합니다.
시그니처
App은 아래 공개 SDK interface만 import합니다. 구현체는 Core가 host에 binding합니다.
interface SignatureDocumentHost
{
public function createCurrentBound(CurrentBoundSignableDocumentSubmission $submission): CurrentBoundSignableDocumentResult;
public function replace(SignableDocumentReference $predecessor, CurrentBoundSignableDocumentSubmission $submission): CurrentBoundSignableDocumentResult;
public function rebuildPreReady(PreReadySignableDocumentReference $predecessor, CurrentBoundSignableDocumentSubmission $submission): CurrentBoundSignableDocumentResult;
public function retryPreReady(PreReadySignableDocumentReference $document, Actor $actor, string $reasonCode): CurrentBoundSignableDocumentResult;
public function summary(string $publicId, Actor $actor): ?SignableDocumentSummary;
public function previewHrefIfAuthorized(string $publicId, Actor $actor): ?string;
public function sourceDownloadHrefIfAuthorized(string $publicId, Actor $actor): ?string;
public function cancel(string $publicId, Actor $actor): SignableDocumentSummary;
}
interface SignatureHost
{
public function submit(SignatureRequestSubmission $submission): SignatureRequestResult;
public function summary(SignatureRequestQueryInput $query): ?SignatureRequestSummary;
public function requestDetailHrefIfAuthorized(SignatureRequestQueryInput $query): ?string;
public function completedDocumentDetailHrefIfAuthorized(SignatureRequestQueryInput $query): ?string;
public function cancel(SignatureRequestCancelInput $input): SignatureRequestActionResult;
public function resend(SignatureRequestResendInput $input): SignatureRequestActionResult;
public function reissue(SignatureRequestReissueInput $input): SignatureRequestResult;
}소스 경로는 packages/app-sdk/packages/laravel/src/Signature/Contracts/SignatureDocumentHost.php와 packages/app-sdk/packages/laravel/src/Signature/Contracts/SignatureHost.php입니다.
rebuildPreReady()는 queued/rendering revision을 취소하고 현재 양식 winner에서
successor 하나를 만듭니다. 반면 retryPreReady()는 소유 workflow가 다시 인가한
retryable 또는 attempts-exhausted revision 그 자체를 재등록합니다.
completedDocumentDetailHrefIfAuthorized()는 요청이 complete이고 현재 actor가 최종
artifact를 읽을 수 있을 때만 보호된 UI Route를 반환합니다.
추가 source 중립 interface는 source plan, credential, 양식 선택, 참여자 정책, 요청 로컬 preparation을 다룹니다.
interface SignatureDocumentPlanHost
{
public function submit(SignatureDocumentPlanSubmission $submission): CurrentBoundSignableDocumentResult|PreparedSignableDocumentResult;
}
interface SignatureRequestCredentialHost
{
public function deriveRequestPasswordVerifier(string $password): SignatureRequestPasswordVerifier;
}
interface SignatureTemplateCatalogHost
{
public function eligible(SignatureTemplateCatalogQuery $query): SignatureTemplateCatalogResult;
}
interface SignatureTemplateParticipantAssignmentHost
{
public function resolve(SignatureTemplateParticipantAssignmentQuery $query): SignatureTemplateParticipantAssignmentResult;
}
interface SignatureRequestPreparationHost
{
public function begin(SignatureRequestPreparationSubmission $submission): SignatureRequestPreparationReference;
public function submit(SignatureRequestPreparationReference $preparation, SignatureRequestSubmission $submission): SignatureRequestResult;
}PreparedSignableDocumentHost는 plan host가 내부적으로 쓰는 하위 수준 업로드 PDF bridge입니다. App 코드가 게시 양식과 업로드 PDF를 의도적으로 모두 지원할 때는 SignatureDocumentPlanHost를 우선 사용하세요. SignatureRequestPreparationHost는 App이 인가한 canonical snapshot에서 요청 로컬 편집 draft를 시작하고 일반 요청 idempotency 계약으로 정확한 ready 문서를 접수합니다. 업무 값의 소유권은 App에 남습니다. 편집한 업무 값을 받아야 한다면 소유 App command로 반영한 뒤 새 preparation을 시작하세요. Catalog와 참여자 할당 query는 항상 actor, Legal Entity, 정확한 subject, binding version, locale, effective date를 전달합니다.
양식 바인딩 게시
App은 재사용 가능한 Signature 양식 family마다 SignatureTemplateBindingDescriptor를
만들어 AppDescriptorContribution::appDescriptors()가 반환하는 불변 set에 넣습니다.
Contributor 클래스는 App Manifest의 contributionLocations()가 선언한 namespace와
디렉터리 아래에 있어야 합니다. Core는 그 위치에서 interface를 발견한 뒤 descriptor
class로 set을 거릅니다. SignatureTemplateBindingContribution이라는 별도 interface도,
container나 service provider에 직접 등록하는 단계도 없습니다.
Descriptor의 resourceKey는 family를 이미 존재하는 canonical Resource에 연결할 뿐 그
Resource를 게시하지 않습니다. App 소유 subject에는 일반 Resource contribution과 생성
권한, ResourceReferenceResolutionContribution도 필요합니다. Core가 문서를 만들거나
읽을 때 권한과 정확한 ResourceRef를 모두 다시 검사하기 때문입니다.
소스 경로는
packages/app-sdk/packages/laravel/src/AppDescriptors/AppDescriptorContribution.php,
packages/app-sdk/packages/laravel/src/AppDescriptors/AppDescriptorSet.php,
packages/app-sdk/packages/laravel/src/AppDescriptors/SignatureTemplateBindingDescriptor.php입니다.
데이터 소스 게시
양식 작성자가 보호 값을 불러올 수 있게 하는 App은 같은 App contribution 발견 위치에 descriptor와 provider 한 쌍을 게시합니다.
interface SignatureDocumentDataSourceContribution extends AppDescriptorContribution
{
public function signatureDocumentDataSourceProviders(): array;
}
interface SignatureDocumentDataSourceProvider
{
public function appKey(): string;
public function sourceKey(): string;
public function sourceVersion(): int;
public function resolve(SignatureDocumentDataQuery $query): SignatureDocumentDataResult;
}appDescriptors()는 정적인 SignatureDocumentDataSourceDescriptor를, signatureDocumentDataSourceProviders()는 runtime provider를 반환합니다. Core는 App, source key, version, contribution class, App owner가 모두 일치할 때만 둘을 연결합니다.
새로 작성하는 모델 기반 source에는 선택 필드인 resourceKey를 지정하세요. 그러면
registry는 정확히 같은 key와 발견된 App owner를 가진, label이 있는
ResourceDescriptor를 요구하고 catalog는 작성 UI에 그 Resource label을 직렬화합니다.
이는 source와 호환되는 문서 subject를 제한하는 supportedSubjectResourceKeys와 별개입니다.
Descriptor와 provider는 존재하지 않는 Resource나 subject Resource를 만들어 주지 않습니다.
완전한 저장소 내 예시는
packages/people/src/Descriptors/PeopleSignatureDocumentDataSources.php입니다.
PeopleCoreAppManifest::contributionLocations()가 Descriptors 디렉터리를 포함하고, 이
클래스가 근로계약·근로자·고용·근로자 배치 Resource용 data-source descriptor 네 개와
일치하는 provider 네 개를 반환합니다.
Bulk binding 게시
정확한 Group Bulk binding을 지원하는 App은 SignatureBulkBindingContribution을
구현합니다. appDescriptors()는 SignatureBulkBindingDescriptor를 게시하고
signatureBulkBindingProviders()는 짝이 되는 runtime provider를 게시합니다.
Core는 App key, binding key, binding version, capability contract version이 모두
정확히 일치할 때만 연결하며 provider가 없는 descriptor는 unavailable입니다.
Provider는 제한된 preview를 인가하고, preview 결과를 신뢰하지 않은 채 최종 실행
context를 reauthorize()해야 합니다.
SignatureBulkBindingDescriptor 필드 | 계약 |
|---|---|
appKey, bindingKey, bindingVersion, capabilityContractVersion | 정확한 provider identity. Binding key는 App이 소유 |
supportedSubjectResourceKeys | 고유한 canonical Resource key 1개에서 20개 |
roleAssignmentPolicies | 고유 role policy 1개에서 8개와 각 policy가 허용하는 assignment source |
supportedInvitationChannels, supportedAuthenticationMethods | Core가 강제하는 비어 있지 않은 capability 상한 |
groupSelectorResourceKey, groupSelectorLabelKey, groupSelectorRequired | 함께 선언하는 선택적 selector metadata. 필수 selector는 지원 subject Resource 중 하나를 지칭 |
status | Descriptor lifecycle. 비활성 contribution은 사용할 수 없음 |
Preview에는 Core가 복원한 tenant, Legal Entity, actor, template, group selection,
정확한 participant slot이 전달됩니다. 반환되는 execution plan은 document variable,
classification, signatory-role 값을 일반 직렬화에서 제외하며 protectedPayload()만
암호화된 Core 저장소로 전달할 수 있습니다. 최종 실행은 이전 preview snapshot이
아니라 provider가 새로 반환한 reauthorization 결과를 사용합니다.
최소 예제
People App은 packages/people/src/Contribution/PeopleSignatureDescriptors.php에서 발견 가능한 양식 family를 선언합니다.
namespace Amuzcorp\Nexia\PeopleCore\Contribution;
use Nexia\AppDescriptors\Contracts\AppDescriptorContribution;
use Nexia\AppDescriptors\AppDescriptorSet;
use Nexia\AppDescriptors\DescriptorStatus;
use Nexia\AppDescriptors\SignatureTemplateBindingDescriptor;
use Nexia\AppDescriptors\SignatureTemplateVariableDescriptor;
use Nexia\AppDescriptors\SignatureSignatoryRoleDescriptor;
use Nexia\Signature\SignatureVariableType;
use Nexia\Signature\SignatureDataClassification;
final class PeopleSignatureDescriptors implements AppDescriptorContribution
{
public static function appDescriptors(): AppDescriptorSet
{
return AppDescriptorSet::of(new SignatureTemplateBindingDescriptor(
appKey: 'people',
bindingKey: 'people.employment_contract',
resourceKey: 'people.employment_contract',
labelKey: 'people.signature_bindings.employment_contract.label',
descriptionKey: 'people.signature_bindings.employment_contract.description',
createPermissionKey: 'people.employment_contract.create_document',
variables: [
new SignatureTemplateVariableDescriptor('worker.full_name', 'people.signature_bindings.employment_contract.variables.worker_full_name', SignatureVariableType::String, true, SignatureDataClassification::Restricted),
new SignatureTemplateVariableDescriptor('contract.starts_on', 'people.signature_bindings.employment_contract.variables.contract_starts_on', SignatureVariableType::Date, true, SignatureDataClassification::Internal),
],
signatoryRoles: [
new SignatureSignatoryRoleDescriptor('worker', 'people.signature_bindings.employment_contract.roles.worker', 1, 1),
],
syntheticSample: [
'worker.full_name' => 'Alex Kim',
'contract.starts_on' => '2026-09-01',
],
templateKeys: ['people.employment_contract.new_hire'],
version: '1.0',
status: DescriptorStatus::Active,
));
}
}이 형태는 constructor 검증을 통과하면서 최소 관계만 보여 줍니다. 실제 People binding에는
선택적인 사용자 대표 역할과 나머지 근로계약 변수가 더 들어갑니다. 이 클래스는
PeopleCoreAppManifest::contributionLocations()가 해당 namespace와 디렉터리를 포함하기
때문에 발견됩니다. Service provider에 따로 등록하지 않습니다.
이 descriptor가 등록하는 것은 Signature 양식 family이지 업무 Resource 자체가 아닙니다.
resourceKey는 이미 존재하는 canonical Resource를 지칭해야 하며, 문서 생성을 인가하기
전에 Core가 ResourceReferenceResolutionContribution으로 정확한 subject를 해석할 수
있어야 합니다. People에서는
Contribution/Resources/EmploymentContractModule.php와
Contribution/ReferenceResolution/PeopleEmploymentContractReferenceResolver.php가 그
별도 contribution입니다.
Contribution 발견과 테넌트 양식 게시가 끝났다면 현재 App 사실로 문서를 생성합니다.
Handler 발췌입니다. $legalEntity, $actor, $subject는 복원·인가된 문맥이고 $workerPartyPublicId, $contractPublicId, $correlationId는 App 레코드와 해당 연산에서 가져옵니다. $workerPartyPublicId에는 Worker Resource ID가 아니라 근로자와 연결된 person Party의 public ID를 넣습니다.
use Nexia\Signature\Contracts\SignatureDocumentHost;
use Nexia\Signature\CurrentBoundSignableDocumentSubmission;
$documents = app(SignatureDocumentHost::class);
$result = $documents->createCurrentBound(new CurrentBoundSignableDocumentSubmission(
legalEntity: $legalEntity,
actor: $actor,
subject: $subject,
bindingKey: 'people.employment_contract',
bindingVersion: '1.0',
templateKey: 'people.employment_contract.new_hire',
locale: 'ko',
effectiveOn: '2026-09-01',
variables: [
'worker.full_name' => 'Alex Kim',
'contract.starts_on' => '2026-09-01',
],
signatoryRoles: [
'worker' => [['party_public_id' => $workerPartyPublicId]],
],
idempotencyKey: 'people.employment-contract.document:'.$contractPublicId,
correlationId: $correlationId,
));CurrentBoundSignableDocumentResult는 접수 결과이지 ready 참조가 아닙니다. signature.document.outcome.v1을 소비하고 summary()를 호출한 뒤, 상태가 ready이며 저장한 subject, revision, checksum이 모두 맞을 때만 SignableDocumentReference를 만드세요.
게시 양식의 parallel 또는 sequential 진행 방식과 서명 순서도 ready 문서에 함께 동결됩니다. App은 이 값을 SignatureRequestSubmission에 넣지 않습니다. Core가 expectedDocument에서 읽으며 요청 수준 override는 거부합니다.
먼저 dry run으로 fail-closed 시작 파일을 확인합니다.
task artisan -- nexia-apps:make-package-signature-data-source assets AssignedAssetLabelsV2 \
--subject-resource-key=people.employment_contract \
--source-resource-key=assets.asset_assignment --cardinality=many \
--min-items=0 --max-items=50 --field-key=asset_display_name \
--field-type=string --field-classification=internal --field-formatter=plain두 target 경로를 검토한 다음에만 --write를 붙이세요. 이 명령은 descriptor/contribution과 provider를 만들지만, provider는 모든 인가와 provenance TODO를 구현하기 전까지 unavailable만 반환합니다. model, fillable, cast, column, relation에서 field를 자동 추론하지 않습니다. 기존 target은 명시적인 --write --force 조합 없이는 덮어쓰지 않습니다.
파라미터
SignatureTemplateBindingDescriptor
| 파라미터 | 타입 | 필수 | 기본값 | 동작 |
|---|---|---|---|---|
appKey | string | 예 | — | 소유 App key |
bindingKey | string | 예 | — | App 소유 family key이며 key로도 노출 |
resourceKey | string | 예 | — | family가 결합되는 canonical Resource 종류 |
labelKey, descriptionKey | string | 예 | — | catalog 현지화 문자열 key |
createPermissionKey | string | 예 | — | 문서 생성 때 Core가 재검사하는 App 소유 권한 |
variables | SignatureTemplateVariableDescriptor 목록 | 예 | — | 고유한 타입 변수와 데이터 분류 |
signatoryRoles | 비어 있지 않은 SignatureSignatoryRoleDescriptor 목록 | 예 | — | 고유 lower-snake-case 역할, 최소/최대 인원, 선택적 할당 정책 |
syntheticSample | key가 있는 array | 예 | — | 실제 데이터가 아닌 preview 값. 모든 필수 변수 포함 |
templateKeys | 비어 있지 않은 string 목록 | 예 | — | binding이 허용하는 고유 App 소유 양식 family |
version | string | 아니요 | 1.0 | 문서에 동결하는 정확한 contribution 버전 |
status | DescriptorStatus | 아니요 | Active | Contribution lifecycle |
requestNavigationId | string 또는 null | 아니요 | null | App 소유 요청 진입점을 위한 선택적 Shell navigation identity |
requestNavigationRoute | 로컬 absolute route 또는 null | 아니요 | null | 선택적 로컬 Route. requestNavigationId가 필요하고 //, 공백, 제어 문자를 거부 |
변수 타입은 string, integer, decimal, boolean, date, datetime, money입니다. 분류는 public, internal, confidential, restricted이며 로그에는 public만 나타날 수 있습니다. Money 값은 숫자 amount와 영문 대문자 세 글자 currency를 가집니다.
각 역할의 선택적 SignatureParticipantAssignmentPolicy는 허용 authority를 하나 이상
선언합니다. 값은 binding_resolved, request_supplied, template_fixed입니다. 이
정책은 참여자를 제공할 수 있는 경로를 제한할 뿐 참여자를 직접 해석하거나 인가하지
않습니다.
SignatureDocumentDataSourceDescriptor
| 파라미터 | 필수 동작 |
|---|---|
appKey, sourceKey, sourceVersion | App 소유 key와 양수 정수 source version. provider도 같은 tuple을 반환 |
resourceKey | 선택적인 같은 App의 canonical Resource key. 제공하면 작성 화면에서 사용하는 label이 있는 ResourceDescriptor를 식별합니다. |
supportedSubjectResourceKeys, lookupMode | 명시적 subject와 direct_subject, derived_ref, explicit_source_ref, owner_scoped_query 중 하나 |
defaultSourceRefAnchor | derived_ref, explicit_source_ref에서 필수인 정규 owner projection anchor. 다른 mode에서는 금지되며 Core는 작성 초기값으로만 사용하고 record를 추론하지 않음 |
cardinality, stableSortKey | one 또는 제한된 many. many는 선언된 필수 deterministic scalar sort field가 필요 |
providedDataContracts | 선택적인 versioned semantic contract. 여러 provider가 제공하면 선언 field schema가 같아야 함 |
fields | 명시적인 type, required/optional, classification, 호환 formatter allowlist |
syntheticSample, label key | 작성 화면에서만 쓰는 완전한 sample과 App locale key. 실제 값 사용 금지 |
SignatureDocumentDataQuery
Core는 tenant, Legal Entity, actor, purpose, 정확한 subject ResourceRef, derived anchor, 선택적 explicit source ref, asOf, 요청 field, 선택 ref를 복원합니다. Provider는 보호 field와 record를 각각 다시 인가하고 subject 관계와 App 소유 as-of/state 규칙을 확인하며, 조회 상한을 적용하고 selected ref를 정확히 다시 해석해야 합니다. ResourceRef나 party_id는 후보를 식별할 뿐 접근 권한을 주지 않습니다.
CurrentBoundSignableDocumentSubmission
| 파라미터 | 타입 | 필수 | 기본값 | 동작 |
|---|---|---|---|---|
legalEntity, actor | SDK 계약 | 예 | — | 복원한 현재 실행 context |
subject | ResourceRef | 예 | — | Core가 인가할 App 소유 업무 레코드 |
bindingKey, bindingVersion | string | 예 | — | 설치된 descriptor의 정확한 identity |
templateKey | string | 예 | — | 현재 게시된 revision을 찾을 양식 family |
locale | string | 예 | — | ko, en-US 같은 BCP-47 형식 locale |
effectiveOn | Y-m-d string | 예 | — | 유효한 게시 양식 revision 선택 |
variables | key가 있는 array | 예 | — | descriptor와 대조할 현재 App 값 |
signatoryRoles | key별 목록 | 예 | — | 역할별 scalar-only 업무 identity 스냅샷 |
idempotencyKey | string | 예 | — | 같은 key와 payload는 기존 command 결과 반환 |
correlationId | UUID string | 예 | — | 추적 identity |
causationId | UUID 또는 null | 아니요 | null | 원인 event identity |
trustedAssets | 목록 | 아니요 | [] | 관인 placement 같은 명시적 host-governed asset |
SignatureRequestSubmission
| 파라미터 | 타입 | 필수 | 기본값 | 동작 |
|---|---|---|---|---|
legalEntity, actor, subject | SDK 계약 | 예 | — | 현재 tenant, scope, App resource 권한과 일치해야 함 |
expectedDocument | SignableDocumentReference | 예 | — | 동결된 public id, 양수 revision, 소문자 64자 checksum |
participants | 비어 있지 않은 목록 | 예 | — | 고유한 양수 sequence가 ready 문서의 동결된 서명 순서와 일치하는 불변 SignatureParticipantSnapshot |
consentPolicy | SignatureConsentPolicyReference | 예 | — | 정확한 host policy key/version. baseline은 standard-consent@1.0 |
expiryPolicy | SignatureExpiryPolicy | 예 | — | timezone이 명시된 ISO-8601 기한. 현재 action은 expire |
idempotencyKey | string | 예 | — | 최대 191자 |
correlationId | UUID string | 예 | — | 추적 identity |
causationId | UUID 또는 null | 아니요 | null | 원인 event identity |
서명자에는 roleKey, 양수 sequence, 고유한 assignedFieldKeys, displayName, locale, 초대 channel/address, 인증 profile, 필수 method를 선언합니다. 내부 person Party 서명자는 선택적 partyPublicId도 전달합니다. 이 값은 권한이 아니라 identity assertion이며 Core는 현재 tenant의 active person Party와 초대 이메일이 일치하는지 다시 검증한 뒤에만 개인 수신함 binding을 만듭니다. 외부 서명자에는 값을 넣지 않으며 이메일 주소로 Party를 추정하지 않습니다. email_otp를 고르면 이메일 OTP 주소도 필요합니다. 불투명한 requestPasswordVerifier는 SignatureRequestCredentialHost로 만드세요. App이 요청 암호를 직접 hash하면 안 됩니다.
partyPublicId는 transport snapshot에는 포함되지만 toLogSafeArray()에서는 제외됩니다. Party binding으로 로그인 상태에서 진입해도 동결된 email_otp나 request_password 인증은 그대로 완료해야 합니다.
요청 입력에는 의도적으로 routingMode나 signingOrder가 없습니다.
옵션
문서 source
| Source | 계약 | 선택 시점 |
|---|---|---|
| 게시 양식 | SignatureDocumentHost::createCurrentBound() | App이 안정적인 template family를 기여할 때. 우선 경로 |
| 조합 가능한 plan | PublishedTemplateSignatureDocumentSource를 담은 SignatureDocumentPlanHost::submit() | 한 action이 source 종류를 의도적으로 선택할 때 |
| 업로드 PDF | UploadedPdfSignatureDocumentSource를 담은 plan host | 일회성 PDF와 명시적 정규화 field rectangle이 필요할 때 |
업로드 PDF는 private uploadIntentId, 서명자 스냅샷, SignatureFieldDefinition을 사용합니다. Rectangle은 1부터 시작하는 page와 0..1 안의 정규화된 x, y, width, height를 사용합니다. 가능한 field kind는 text, checkbox, date, select, signer_name, signature, signed_at입니다. 선택적 datePart는 표시 전용입니다. date는 year, month, day를 받고 signed_at은 여기에 date, time도 받으며, 다른 field kind는 거부합니다.
인증과 전송
| Profile | 필수 method | 현재 가용성 |
|---|---|---|
standard_password@1.0 | request_password | 로컬 password derivation이 준비되면 operational |
standard_email_otp@1.0 | email_otp | mail과 queue가 준비되면 operational |
standard_password_email_otp@1.0 | request_password와 email_otp | 두 dependency가 모두 준비되면 operational |
standard_sms_otp@1.0 | sms_otp | Unavailable |
지원되는 초대 channel은 이메일입니다. enum에 있다는 이유로 sms나 sms_otp를 받아들이지 마세요. Core는 unavailable capability를 거부합니다.
데이터 소스 버전 관리
Field, 의미, cardinality, lookup, provenance의 호환되지 않는 변경에는 새 양수 source version을 사용합니다. 게시 양식 revision이 이전 version을 가리키는 동안에는 정확한 기존 descriptor/provider를 계속 운영하거나, successor 양식을 게시하고 새 preparation만 명시적으로 전환하세요. 조립된 계약과 App locale key는 php artisan nexia-apps:validate-app-descriptors <app-key>로 검증합니다.
진행 방식과 상태
| 계약 | 값 또는 필드 | 의미 |
|---|---|---|
SignatureRoutingMode | parallel, sequential | 게시 양식에서 문서를 거쳐 요청까지 복사되는 불변 실행 정책 |
SignatureRequestResult::routingMode | SignatureRoutingMode | 접수된 요청의 동결 mode. 이전 직렬화 결과는 parallel로 읽음 |
SignatureRequestSummary::routingMode | SignatureRoutingMode | 인가된 log-safe 요청 mode |
SignatureParticipantProgress | roleKey, roleSlot, sequence, status, routingState, presentedRevision, completedAt | Log-safe 서명자 identity, 실행 순서, 현재 eligibility, 제시된 요청 로컬 PDF revision ordinal |
SignatureParticipantRoutingState | waiting, revision_pending, active, completed, closed, failed | 이전 서명자, 검증된 누적 PDF, 현재 실행 가능 상태 또는 종료 상태 구분 |
parallel에서는 모든 서명자가 요청 로컬 revision 0을 대상으로 active입니다. sequential에서는 서명자 N이 검증된 revision N - 1이 존재한 뒤에만 active가 됩니다. presentedRevision은 실제로 본 ordinal이며 artifact 접근 권한, PDF 내용, checksum을 노출하지 않습니다.
출력 또는 반환값
| 호출 | 반환 | 의미 |
|---|---|---|
createCurrentBound, replace, rebuildPreReady | CurrentBoundSignableDocumentResult | Public id, accepted|existing, 문서 상태, 동결된 binding/template 버전, revision |
SignatureDocumentHost::summary | ?SignableDocumentSummary | 인가된 비콘텐츠 상태, resolved field, page count, checksum/failure, 시도 횟수, trusted asset |
SignatureHost::submit, reissue | SignatureRequestResult | Public id, accepted|existing, lifecycle 상태, subject, 정확한 문서, 선택적 predecessor, 동결된 진행 방식 |
SignatureHost::summary | ?SignatureRequestSummary | 동결된 진행 방식, log-safe 서명자 routing/progress와 제시 revision, 기한, 실패 코드, 완료 artifact 참조 |
cancel, resend | SignatureRequestActionResult | Action, accepted|existing, 변경 후 요청 상태 |
| 인가형 href method | ?string | 보호된 Core route. 현재 권한이 없으면 null |
문서 상태는 queued, rendering, ready, retryable_failed, terminal_failed, cancelled, superseded입니다. 요청 종료 상태는 completed, declined, expired, cancelled, failed이고, 그 전 상태는 draft, preparing, ready, dispatching, in_progress, finalizing입니다.
SignatureRequestSummary::toLogSafeArray()에는 서명자 이름, 초대/OTP 주소, credential material, 문서 내용이 없습니다. 진단 코드에서 이를 SignatureParticipantSnapshot::toArray()로 바꾸지 마세요.
오류
Host 실패는 SignatureException을 씁니다. message 문자열이 아니라 errorCode로 분기하세요.
SignatureErrorCode | 원인 | 해결 |
|---|---|---|
unauthorized, request_scope_mismatch | Actor, Legal Entity, subject, App 권한이 오래됨 | 현재 context를 복원하고 업무 레코드를 재인가 |
binding_unavailable, template_unavailable | Descriptor/version이 없거나 적격 게시 양식이 없음 | Contribution 발견, 정확한 버전, locale, 유효일 확인 |
document_not_ready, document_reference_mismatch | 미완료 또는 다른 revision/checksum으로 요청 | 문서 결과를 소비하고 인가된 summary에서 참조 재구성 |
payload_drift, request_conflict | 한 idempotency identity를 서로 다른 동결 입력에 재사용 | key마다 canonical payload 하나를 유지하고 변경은 명시적 successor로 생성 |
participant_invalid, participant_assignment_invalid | 역할 인원, sequence, field 소유가 맞지 않음 | Binding과 ready 문서 assignment로 서명자 재구성 |
authentication_profile_unavailable, authentication_methods_invalid | Profile 버전과 필수 method가 다름 | 정확한 profile method set 제출 |
unsupported_capability | SMS 또는 runtime dependency를 쓸 수 없음 | Operational capability 선택. host gate 우회 금지 |
consent_policy_unavailable, expiry_policy_invalid | Policy 버전이 없거나 deadline/action이 잘못됨 | 게시 policy와 timezone을 명시한 미래 timestamp 사용 |
request_terminal | 종료 요청을 변경하려 함 | 유효한 successor가 필요하면 reissue() 시작 |
execution_unavailable, rate_limited | 제한된 runtime 또는 전송 실패 | Handoff를 보존하고 인가된 멱등 경로로 재시도 |
artifact_verification_failed | 완료 PDF나 manifest identity 검증 실패 | 업무 적용을 중단하고 무결성 incident로 escalation |
DTO constructor는 host 호출 전에 잘못된 형식에 InvalidArgumentException을 던집니다. UUID가 아닌 correlation id, list와 keyed array 혼동, 중복 sequence, 잘못된 locale/date/timestamp, 페이지 밖 rectangle이 여기에 해당합니다.
실제 사용
packages/people/src/Actions/SubmitEmploymentContractDocumentHandoff.php는 위임된 actor를 복원하고 people.employment_contract.create_document를 재검사합니다. Inbox reservation이 commit된 뒤에만 createCurrentBound()를 호출하고, 결과를 받으면 binding, version, template을 비교한 다음 handoff를 접수합니다.
이어서 packages/people/src/Actions/SubmitEmploymentContractSignatureHandoff.php가 영속 App handoff로 SignatureRequestSubmission을 다시 만듭니다. 현재 SignableDocumentReference를 확인하고 submit() 또는 reissue()를 호출하며, subject, 문서, checksum, predecessor가 다른 결과는 거부합니다.
worker를 sequence 1, employer representative를 sequence 2로 제출하지만 진행 방식은 선택하지 않습니다. 실행 정책은 게시 양식이 계속 소유합니다.
마지막으로 packages/people/src/Actions/ApplyEmploymentContractSignatureOutcome.php가 signature.request.accepted.v1과 signature.request.outcome.v1을 소비합니다. App Inbox transaction 전에 인가된 summary 두 개를 호출하고, 현재 actor, permission, subject, expiry, 문서, artifact 사실이 모두 맞을 때만 projection을 한 번 적용합니다.
데이터 소스 구현은 packages/people/src/Descriptors/PeopleSignatureDocumentDataSources.php와 packages/people/src/Signature/WorkerAssignmentsSignatureDocumentDataSourceProvider.php를 함께 비교하세요. Descriptor는 exact employment-contract subject, derived Worker anchor, bounded many-cardinality field와 stable sort key를 선언합니다. Provider는 tenant, Legal Entity, actor, subject, selected reference를 다시 검사하고 as-of 기간을 적용하며, 50개로 제한한 뒤 effective date와 public ID 순으로 정렬합니다.
다음 명령으로 이 연동을 검증합니다.
php artisan test packages/people/tests/Feature/EmploymentContractSignatureBridgeTest.php관련 문서
- 전자 서명 — Core, SDK, App의 전체 소유권 흐름 추적
- Contribution 계약 — 발견 위치와
AppDescriptorContribution게시 경계 - 이벤트와 비동기 작업 처리하기 — transaction 경계를 넘어 호출과 결과를 영속화
- Resource Reference — 모든 문서와 요청이 운반하는 canonical subject 구성