From 912022f5da293456d1d2b64b5c3d571c76b9ef0a Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Fri, 8 May 2026 02:57:22 +0200 Subject: [PATCH] feat(form-builder): broadcast channel auth + listener layout comment update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per RFC-WS-6 §Q1 v1.3 addition 2. - routes/channels.php (NEW): authorization callback for the submission.{id} private channel. v1 authz scope is submitter-only (matches submitted_by_user_id); org-admin access is deferred per BACKLOG TECH-CHANNEL-AUTH-ORG-ADMIN. Frontend Echo subscription lands as a separate frontend follow-up. - bootstrap/app.php: registers routes/channels.php via withRouting() channels: parameter. This is NEW broadcasting wiring — Laravel's broadcasting auth middleware was not previously connected to the framework. Without this registration the channels file is dead code. - AppServiceProvider::boot: comment block updated to v1.3 listener layout (1 sync ApplyBindings + N queued, all gated on apply_status=COMPLETED per ARCH-BINDINGS §5.6). Comment on TriggerPersonIdentityMatch flipped from "(sync)" to "(queued post-v1.3)". Co-Authored-By: Claude Opus 4.7 --- api/app/Providers/AppServiceProvider.php | 29 +++++++----- api/bootstrap/app.php | 5 +++ api/routes/channels.php | 57 ++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 api/routes/channels.php diff --git a/api/app/Providers/AppServiceProvider.php b/api/app/Providers/AppServiceProvider.php index f16031bd..a0718ada 100644 --- a/api/app/Providers/AppServiceProvider.php +++ b/api/app/Providers/AppServiceProvider.php @@ -163,16 +163,24 @@ class AppServiceProvider extends ServiceProvider FormField::observe(FormFieldChildTablesCascadeObserver::class); FormFieldLibrary::observe(FormFieldChildTablesCascadeObserver::class); - // RFC-WS-6 §3 (Q1) — sync chain on FormSubmissionSubmitted, in - // this exact order: - // 1. ApplyBindingsOnFormSubmit (sync) - // 2. TriggerPersonIdentityMatchOnFormSubmit (sync) - // Queued listeners on the same event (SyncTagPickerSelectionsOnSubmit, - // future webhook dispatcher, mailables) run in parallel after the - // sync chain via the queue. Their relative registration position - // is irrelevant. + // RFC-WS-6 v1.3 §Q1 — FormSubmissionSubmitted listener layout. + // + // SYNC chain (single listener): + // 1. ApplyBindingsOnFormSubmit + // + // QUEUED listeners (parallel, all gated on apply_status=COMPLETED + // per ARCH-BINDINGS §5.6 — PARTIAL and FAILED both fall through to + // the early-return): + // - TriggerPersonIdentityMatchOnFormSubmit (gates + invariant + broadcast) + // - SyncTagPickerSelectionsOnSubmit + // + // The listener-ordering structural test + // (FormSubmissionSubmittedListenerOrderTest) asserts ApplyBindings + // is the only sync listener and that every queued listener has + // the apply_status=COMPLETED gate as its first executable + // statement. - // RFC Q1 — applies bindings sync before identity match runs. + // RFC Q1 — applies bindings sync before queued siblings fire. \Illuminate\Support\Facades\Event::listen( FormSubmissionSubmitted::class, ApplyBindingsOnFormSubmit::class, @@ -184,7 +192,8 @@ class AppServiceProvider extends ServiceProvider SyncTagPickerSelectionsOnSubmit::class, ); - // ARCH §31.1 — identity-match trigger on event_registration (sync). + // ARCH §31.1 — identity-match trigger on event_registration (queued + // post-v1.3; was sync in v1.0/v1.2 layout). \Illuminate\Support\Facades\Event::listen( FormSubmissionSubmitted::class, TriggerPersonIdentityMatchOnFormSubmit::class, diff --git a/api/bootstrap/app.php b/api/bootstrap/app.php index 5d702be5..d8978393 100644 --- a/api/bootstrap/app.php +++ b/api/bootstrap/app.php @@ -19,6 +19,11 @@ return Application::configure(basePath: dirname(__DIR__)) web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', + // Per RFC-WS-6 §Q1 v1.3 addition 2 — broadcast channel auth callbacks + // live in routes/channels.php. Registers Laravel's broadcasting auth + // middleware so private/presence channel subscriptions reach the + // closures defined there. + channels: __DIR__.'/../routes/channels.php', health: '/up', apiPrefix: 'api/v1', ) diff --git a/api/routes/channels.php b/api/routes/channels.php new file mode 100644 index 00000000..b6ec7bc2 --- /dev/null +++ b/api/routes/channels.php @@ -0,0 +1,57 @@ +withoutGlobalScopes() + ->find($submissionId); + + if ($submission === null) { + return false; + } + + // TODO TECH-CHANNEL-AUTH-ORG-ADMIN — extend to organisation admins + // once we audit the Spatie Permission helper for an + // organisation-scoped role check (hasRoleInOrganisation or + // similar). Until that audit lands, only the submitter has + // channel access. + return $submission->submitted_by_user_id === $user->id; + }, +);