PersonProvisioner reads bindings from schema_snapshot (RFC Q6) and provisions Persons via lockForUpdate + firstOrCreate (RFC Q8). Person is event-scoped (Person::$organisationScopeColumn = 'event_id'), so the lookup matches by (email, event_id) — cross-event submissions never collide. Throws PersonProvisioningException on misconfiguration (failsafe — publish guards should prevent these at config time): no_transaction, no_event, no_identity_key, identity_key_missing_value, no_crowd_type. Snapshot enrichment: FormFieldBindingService::toApplicatorShape + FormSubmissionService snapshot now adds a 'bindings' (plural) key with binding id, merge_strategy, trust_level, is_identity_key. Singular 'binding' key kept for legacy webhook / GDPR readers. Includes RFC V4 state-injection concurrency test asserting recovery semantics under lockForUpdate windows. Refs: RFC-WS-6.md §3 (Q6, Q8), §4 (V4) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
28 lines
874 B
PHP
28 lines
874 B
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Exceptions\FormBuilder;
|
|
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* RFC-WS-6 §3 (Q8) — failure during PersonProvisioner operation.
|
|
*
|
|
* Reason codes (informal contract):
|
|
* - 'no_transaction' called outside DB::transaction
|
|
* - 'no_identity_key' schema has no is_identity_key=true binding for person
|
|
* - 'no_event' submission missing event_id (Person scope is event_id)
|
|
* - 'identity_key_missing_value' identity-key form_value is absent or null
|
|
*/
|
|
final class PersonProvisioningException extends RuntimeException
|
|
{
|
|
public function __construct(
|
|
public readonly string $reasonCode,
|
|
public readonly string $submissionId,
|
|
?string $message = null,
|
|
) {
|
|
parent::__construct($message ?? "Person provisioning failed: {$reasonCode} (submission {$submissionId})");
|
|
}
|
|
}
|