feat(form-builder): add apply_status=COMPLETED gate to SyncTagPickerSelectionsOnSubmit
Per ARCH-BINDINGS §5.6 v1.2. The queued tag-sync listener now skips unless apply_status === COMPLETED. PARTIAL and FAILED both fall through to the early-return — rebuilding user_organisation_tags against a Person whose tag-binding may have been the binding that failed would propagate partial state into derived data. Logs at info level when skipped (form-builder.queued-listener.skipped_apply_failed) for triage visibility. The fresh() reload is required because the inner-txn commit happens between dispatch and worker pickup. ApplyBindingsOnFormSectionSubmitted (the other queued listener under app/Listeners/FormBuilder/) listens to FormSubmissionSectionSubmitted, a different event — the §5.6 gate is specifically about FormSubmissionSubmitted's post-apply-status state, so the section-level listener is intentionally left without this gate. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Listeners\FormBuilder;
|
namespace App\Listeners\FormBuilder;
|
||||||
|
|
||||||
|
use App\Enums\FormBuilder\ApplyStatus;
|
||||||
use App\Enums\FormBuilder\FormFieldType;
|
use App\Enums\FormBuilder\FormFieldType;
|
||||||
use App\Enums\FormBuilder\FormPurpose;
|
use App\Enums\FormBuilder\FormPurpose;
|
||||||
use App\Events\FormBuilder\FormSubmissionSubmitted;
|
use App\Events\FormBuilder\FormSubmissionSubmitted;
|
||||||
@@ -24,6 +25,11 @@ use Illuminate\Support\Facades\Log;
|
|||||||
* person's linked user. No-ops when person.user_id is null (deferred
|
* person's linked user. No-ops when person.user_id is null (deferred
|
||||||
* sync runs on PersonIdentityService::confirmMatch).
|
* sync runs on PersonIdentityService::confirmMatch).
|
||||||
*
|
*
|
||||||
|
* Gating-invariant first statement per ARCH-BINDINGS §5.6: skip unless
|
||||||
|
* apply_status is COMPLETED. PARTIAL and FAILED both fall through —
|
||||||
|
* rebuilding tags against a Person whose tag-binding may have been the
|
||||||
|
* binding that failed would propagate partial state into derived data.
|
||||||
|
*
|
||||||
* Failure mode: log at error level; never throw. Event propagation must
|
* Failure mode: log at error level; never throw. Event propagation must
|
||||||
* reach sibling listeners (§31.1 identity, §31.3 shifts, §31.8 crowd lists).
|
* reach sibling listeners (§31.1 identity, §31.3 shifts, §31.8 crowd lists).
|
||||||
*/
|
*/
|
||||||
@@ -43,11 +49,25 @@ final class SyncTagPickerSelectionsOnSubmit implements ShouldQueue
|
|||||||
public function handle(FormSubmissionSubmitted $event): void
|
public function handle(FormSubmissionSubmitted $event): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// Gating-invariant first statement per ARCH-BINDINGS §5.6.
|
||||||
|
// The fresh() reload is required because the inner-txn commit
|
||||||
|
// happens between dispatch and worker pickup; the in-memory
|
||||||
|
// event submission may carry pre-commit state.
|
||||||
$submission = $event->submission->fresh(['schema']);
|
$submission = $event->submission->fresh(['schema']);
|
||||||
if ($submission === null) {
|
if ($submission === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($submission->apply_status !== ApplyStatus::COMPLETED) {
|
||||||
|
Log::info('form-builder.queued-listener.skipped_apply_failed', [
|
||||||
|
'listener' => self::class,
|
||||||
|
'submission_id' => (string) $submission->id,
|
||||||
|
'apply_status' => $submission->apply_status?->value,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$schema = $submission->schema;
|
$schema = $submission->schema;
|
||||||
if ($schema === null) {
|
if ($schema === null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user