feat(timetable): ArtistResolver::fromPortalToken — engagement-scoped subject resolution

Resolves the artist subject + event_id + engagement for the
artist_advance portal flow. Per RFC v0.2 D15 + ARCH-FORM-BUILDER
§17.3 footnote: master Artist is the subject (preserves
form_submissions.subject_type='artist'), engagement provides
event_id (per WS-4 denormalisation), and engagement itself rides
along so callers can resolve advance_section context without a
second query.

Token comparison uses SHA-256 hex digest matching Session 1's
storage shape (commit eb6d396). Two domain exceptions distinguish
404 (no matching token → InvalidPortalTokenException) from 410
(master artist soft-deleted post-engagement → ArtistDeletedException
with engagementId attached).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 22:13:34 +02:00
parent 1716e090e0
commit cc48011da6
4 changed files with 132 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\Artist;
use DomainException;
/**
* Raised by ArtistResolver::fromPortalToken when the supplied portal
* token does not match any active artist_engagements row. Maps to a
* 404 at the HTTP boundary distinguishes from ArtistDeletedException
* (engagement exists but master Artist is soft-deleted, 410 Gone).
*/
final class InvalidPortalTokenException extends DomainException
{
public static function create(): self
{
return new self('Portal token does not resolve to an active engagement.');
}
}