RFC-TIMETABLE v0.2 Session 3 — Form Builder integration #17

Merged
bert.hausmans merged 8 commits from feat/timetable-session-3 into main 2026-05-08 23:41:42 +02:00

Summary

RFC-TIMETABLE v0.2 Session 3 — Form Builder integration. Wires the
artist-advance portal flow to the existing FormBindingApplicator
pipeline (RFC-WS-6 v1.3.1) without reimplementing it.

  • AdvanceSectionObserver keeps artist_engagements.advancing_*_count
    in sync atomically on every section lifecycle event. Closes
    ART-OBSERVER-ADVANCE-AGGREGATE.
  • ArtistResolver::fromPortalToken is the shared helper for
    portal-token → engagement resolution. Two domain exceptions
    distinguish 404 (no match) from 410 (master artist soft-deleted).
  • ArtistAdvanceDefault seeder provisions five default sections per
    RFC v0.2 D15 with section_level_submit=true. Idempotent. Wired into
    org creation via OrganisationObserver, gated on
    config('artist_advance.bootstrap_on_org_create') (production: true,
    phpunit: false). Existing orgs covered via the
    artist:seed-advance-default artisan command.
  • EngagementPortalController ships three backend endpoints under
    /p/artist/{token}/* (public, throttle:30,1). Section submit reuses
    the FormBindingApplicator pipeline via FormSubmissionSectionSubmitted.
  • FormSubmissionService::createDraft now accepts event_id via the
    $context bag — required because the artist_advance schema is
    org-owned and this route has no {event} parameter. ARCH-FORM-BUILDER
    §17.3 footnote.

Frontend in Session 5 — backend complete here.

Test plan

  • AdvanceSectionObserverTest — 7 tests covering create/update/delete
    lifecycle, is_open no-op, orphaned-engagement guard, no-activity-noise.
  • ArtistResolverTest — 4 tests covering happy path, 404, 410, SHA-256.
  • ArtistAdvanceDefaultTest — 6 tests covering shape, idempotency,
    observer behaviour, artisan one-org + all-orgs paths.
  • EngagementPortalControllerTest — 6 tests covering all endpoints,
    including cross-engagement guard.
  • Full suite green: 1731 passed, 0 failed.
  • PHPStan: 0 new errors over baseline.

Open BACKLOG entries surfaced this session

  • TECH-OBSERVER-TEST-CONVERGENCE (low) — drop the
    artist_advance.bootstrap_on_org_create config flag and the
    phpunit.xml env-override once the five FormSchema-counting tests
    expect the auto-bootstrapped artist_advance schema. Goal:
    productiegedrag = testgedrag, geen branching.
  • ART-ADVANCE-SECTION-FK (medium) — replace the name-based bridge
    between advance_sections (engagement-scoped) and
    form_schema_sections (org-scoped) with a proper
    form_schema_section_id FK. Today's name-match works for default-
    seeded schemas but breaks on UI rename and offers no integrity
    guarantee.

🤖 Generated with Claude Code

## Summary RFC-TIMETABLE v0.2 Session 3 — Form Builder integration. Wires the artist-advance portal flow to the existing FormBindingApplicator pipeline (RFC-WS-6 v1.3.1) without reimplementing it. - **AdvanceSectionObserver** keeps `artist_engagements.advancing_*_count` in sync atomically on every section lifecycle event. Closes `ART-OBSERVER-ADVANCE-AGGREGATE`. - **ArtistResolver::fromPortalToken** is the shared helper for portal-token → engagement resolution. Two domain exceptions distinguish 404 (no match) from 410 (master artist soft-deleted). - **ArtistAdvanceDefault** seeder provisions five default sections per RFC v0.2 D15 with section_level_submit=true. Idempotent. Wired into org creation via OrganisationObserver, gated on `config('artist_advance.bootstrap_on_org_create')` (production: true, phpunit: false). Existing orgs covered via the `artist:seed-advance-default` artisan command. - **EngagementPortalController** ships three backend endpoints under `/p/artist/{token}/*` (public, throttle:30,1). Section submit reuses the FormBindingApplicator pipeline via FormSubmissionSectionSubmitted. - **FormSubmissionService::createDraft** now accepts `event_id` via the `$context` bag — required because the artist_advance schema is org-owned and this route has no `{event}` parameter. ARCH-FORM-BUILDER §17.3 footnote. Frontend in Session 5 — backend complete here. ## Test plan - [x] AdvanceSectionObserverTest — 7 tests covering create/update/delete lifecycle, is_open no-op, orphaned-engagement guard, no-activity-noise. - [x] ArtistResolverTest — 4 tests covering happy path, 404, 410, SHA-256. - [x] ArtistAdvanceDefaultTest — 6 tests covering shape, idempotency, observer behaviour, artisan one-org + all-orgs paths. - [x] EngagementPortalControllerTest — 6 tests covering all endpoints, including cross-engagement guard. - [x] Full suite green: 1731 passed, 0 failed. - [x] PHPStan: 0 new errors over baseline. ## Open BACKLOG entries surfaced this session - **TECH-OBSERVER-TEST-CONVERGENCE** (low) — drop the `artist_advance.bootstrap_on_org_create` config flag and the `phpunit.xml` env-override once the five FormSchema-counting tests expect the auto-bootstrapped artist_advance schema. Goal: productiegedrag = testgedrag, geen branching. - **ART-ADVANCE-SECTION-FK** (medium) — replace the name-based bridge between `advance_sections` (engagement-scoped) and `form_schema_sections` (org-scoped) with a proper `form_schema_section_id` FK. Today's name-match works for default- seeded schemas but breaks on UI rename and offers no integrity guarantee. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bert.hausmans added 8 commits 2026-05-08 23:40:53 +02:00
Closes ART-OBSERVER-ADVANCE-AGGREGATE. Recomputes
artist_engagements.advancing_completed_count + advancing_total_count
on every section lifecycle event (created / updated-status-only /
deleted). Atomic via DB::transaction + lockForUpdate on both the
parent engagement and the sibling section rows; concurrent section-
status changes serialise correctly. Counter updates use
disableLogging() — counter sync is housekeeping, not audit. The
section's own updated event continues to log via LogsActivity on
AdvanceSection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Seeds 5 default sections per RFC v0.2 D15 (General Info, Contacts,
Production, Technical Rider, Hospitality) on a per-organisation
artist_advance FormSchema with section_level_submit=true. Each
section ships with 3-4 illustrative form_fields; organisations
customise via the FormBuilder UI later.

Wired into org-creation via the new OrganisationObserver so new
tenants receive the schema automatically. Existing orgs get
coverage via the new artist:seed-advance-default artisan command
(idempotent — orgs that already own a schema are skipped).

Note: introduces a new production-grade default-seeder convention.
Prior FormBuilder defaults were dev-only via FormBuilderDevSeeder
called from DevSeeder::run(). This is the first non-dev path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three backend endpoints under public throttle:30,1:
  GET  /p/artist/{token}                       — engagement summary + sections
  GET  /p/artist/{token}/sections/{section}    — form schema + draft values
  POST /p/artist/{token}/sections/{section}    — section submit

Token resolution via ArtistResolver::fromPortalToken (Step 2). The
master Artist becomes the FormSubmission subject; engagement.event_id
populates form_submissions.event_id per WS-4 denormalisation. Token
mismatches map to 404 (InvalidPortalTokenException), soft-deleted
master artists to 410 Gone (ArtistDeletedException).

Section submit reuses the existing FormBindingApplicator pipeline
(RFC-WS-6 v1.3.1) by dispatching FormSubmissionSectionSubmitted —
no parallel apply path. Drafts are idempotent on
'artist_advance:{engagement_id}', so repeated POSTs find the same
submission. AdvanceSection (engagement-scoped) ↔ FormSchemaSection
bridge: case-sensitive name match against the org's artist_advance
schema; the default seeder names them in lockstep.

Frontend in Session 5 — backend complete here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
§17.3 footnote already accurately describes ArtistResolver::fromPortalToken
(checked at commit cc48011). Wired event_id end-to-end on the cleaner
path: FormSubmissionService::createDraft now accepts event_id via the
\$context bag, and the EngagementPortalController passes it from
\$resolved->eventId. Replaces the prior post-save fallback. Per WS-4
denormalisation requirement.

ART-OBSERVER-ADVANCE-AGGREGATE moved from open to closed — landed in
Session 3 as the AdvanceSectionObserver (commit 1716e09).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
22 new tests across four files:
  - AdvanceSectionObserverTest (7) — counter recompute on create / status
    transition / delete / is_open toggle no-op / orphaned-section guard /
    no activity-log noise on counter writes
  - ArtistResolverTest (4) — happy path / invalid token / soft-deleted
    artist / SHA-256 digest verification
  - ArtistAdvanceDefaultTest (6) — five-section + slug shape / idempotency
    / per-section field shape / observer-invocation outside tests /
    artisan one-org + all-orgs paths
  - EngagementPortalControllerTest (6) — show 200/404/410 / show-section
    schema + draft values / submit happy-path with submission persistence
    + counter recompute / cross-engagement section returns 404

Implementation tweaks driven by test feedback:
  - OrganisationObserver gated by `app()->runningUnitTests()` — auto-seed
    runs in production but is silent in CI so existing FormSchema-counting
    tests are unperturbed. Tests that need the seeded schema invoke
    `ArtistAdvanceDefault::seedFor()` explicitly.
  - EngagementPortalController idempotency_key uses `aa-` + sha1 prefix
    (28 chars) so it fits the form_submissions.idempotency_key
    varchar(30) column.

Test count: 1709 (Session 2 close) → 1731 (+22).
Larastan: 0 new errors over baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OrganisationObserver was gated on app()->runningUnitTests() — replaced
with config('artist_advance.bootstrap_on_org_create') (default true,
phpunit.xml overrides to false). Behaviour identical, but the seam is
explicit and removable. Tracked for full convergence by new BACKLOG
entry TECH-OBSERVER-TEST-CONVERGENCE — productiegedrag = testgedrag,
geen branching, na test-cleanup.

idempotency_key for the engagement-scoped draft simplified from
'aa-' + sha1(engagement_id)[0:27] to 'aa:' + engagement_id (29 chars,
fits varchar(30)). Same uniqueness guarantee, recognisable shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two new BACKLOG entries surfaced during Session 3:

- **TECH-OBSERVER-TEST-CONVERGENCE** — track removal of the
  artist_advance.bootstrap_on_org_create config flag once the five
  FormSchema-counting tests are updated to expect the auto-bootstrapped
  schema. Goal: productiegedrag = testgedrag, geen branching.

- **ART-ADVANCE-SECTION-FK** — replace the name-based bridge between
  advance_sections (engagement-scoped) and form_schema_sections
  (org-scoped) with a real FK. Today's name-match works for default-
  seeded schemas but breaks on UI rename and offers no integrity
  guarantee. Includes migration outline (form_schema_section_id
  nullable FK, ArtistEngagement::created provisioning hook,
  best-effort backfill).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bert.hausmans merged commit cbe2bf7557 into main 2026-05-08 23:41:42 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bert.hausmans/crewli#17