Electronic Signature
Connect an App-owned business record to immutable documents, signature requests, and terminal outcomes without crossing the Core boundary.
Create a signed document for an App record using the Signature SDK. Prepare a protected Resource and Handle events and jobs first.
Implement the document-to-request flow
- Publish the template binding and its subject Resource resolver, using the registration example below. Reauthorize every exact source reference; a selected Party or Resource id is not read authority.
- Follow Electronic Signature contracts to call
SignatureDocumentHost::createCurrentBound()outside the App transaction. Persist the returned identity and awaitsignature.document.outcome.v1. - Re-read the ready document and submit through
SignatureHostwith its exact public id, revision, and SHA-256 checksum, frozen participants, consent, expiry, and a durable idempotency key. Acceptance of document creation does not mean rendering is complete. - Consume terminal outcomes once, matching subject and request identity and restoring current authority before changing the App record. Keep PDF bytes, credentials, invitation addresses, and signing evidence out of App handoff logs.
Confirm the result
The document becomes ready before a request is submitted; retries retain one accepted identity and duplicate events do not repeat the business effect. Revoked authority and mismatched revisions must be refused. packages/people/tests/Feature/EmploymentContractSignatureBridgeTest.php covers this durable bridge. Use Test an App for execution.
Register App bindings and data Resources
Register the template binding
A binding does not register its business Resource. People publishes
people.employment_contract, a same-App Resource Reference resolver, and then
PeopleSignatureDescriptors under a manifest-declared contribution location.
The class implements AppDescriptorContribution; Core discovers its
SignatureTemplateBindingDescriptor values from AppDescriptorSet.
| Piece | People example | Registration provides |
|---|---|---|
| Discovery root | PeopleCoreAppManifest::contributionLocations() includes src/Contribution | Discovers the contributor |
| Business Resource | Contribution/Resources/EmploymentContractModule.php | Establishes people.employment_contract identity and permission |
| Subject resolution | PeopleEmploymentContractReferenceResolver.php | Reauthorizes the exact subject for actor and Legal Entity |
| Signature family | Contribution/PeopleSignatureDescriptors.php | Registers the binding in the Signature catalog |
| App data Resources | PeopleSignatureDocumentDataSources.php | Offers Resource-backed Add App data sources |
This focused, constructor-valid form uses the identifiers and contract fields from the real People declaration:
<?php
namespace Amuzcorp\Nexia\PeopleCore\Contribution;
use Nexia\AppDescriptors\Contracts\AppDescriptorContribution;
use Nexia\AppDescriptors\AppDescriptorSet;
use Nexia\AppDescriptors\DescriptorStatus;
use Nexia\AppDescriptors\SignatureSignatoryRoleDescriptor;
use Nexia\AppDescriptors\SignatureTemplateBindingDescriptor;
use Nexia\AppDescriptors\SignatureTemplateVariableDescriptor;
use Nexia\Signature\SignatureDataClassification;
use Nexia\Signature\SignatureVariableType;
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,
));
}
}Keep App keys in the owning namespace, match resourceKey to submitted subject
references, and freeze published versions. Add every label key to the App locale catalog; keep synthetic samples free of real personal data.
After changing a contribution, refresh the generated runtime caches and inspect both discovery and descriptor validation:
task artisan -- nexia-runtime:refresh-runtime-caches --if-route-cached --no-ansi
task artisan -- nexia-apps:doctor-package-app people --no-ansi
task artisan -- nexia-apps:validate-app-descriptors people --no-ansiAvailability also requires an operational App, Active descriptor,
createPermissionKey, and eligible published template.
Register App data Resources
Implement SignatureDocumentDataSourceContribution: appDescriptors() and
signatureDocumentDataSourceProviders() must publish matching App/source/version
pairs or Core fails closed. A model-backed source uses its canonical same-App
Resource with a non-blank labelKey; supportedSubjectResourceKeys limits
subjects but does not register them. Providers reauthorize every requested
record, field, and relationship.
People's PeopleSignatureDocumentDataSources publishes these pairs:
| Source identity | Display Resource | Lookup | Accepted document subject |
|---|---|---|---|
people.worker_employment@1 | people.employment_contract | direct_subject, one | directory.party, people.employment_contract |
people.worker@1 | people.worker | derived_ref from worker, one | people.employment_contract |
people.employment@1 | people.employment | derived_ref from employment, one | people.employment_contract |
people.worker_assignments@1 | people.worker_assignment | derived_ref from worker, many ordered by effective_from | people.employment_contract |
Once People is operational, Core exposes those pairs without importing a People model or inferring fields.