32 new tests covering D1 deliverables:
- Migration shape (3): failure_response_code column presence,
type/length/nullability, index name. MySQL information_schema
introspection.
- Exception hierarchy (11): abstract base, RuntimeException ancestor,
per-subclass constructor + reasonCode (named-args asserting
submissionId is preserved structurally), Timeout extends Infra and
inherits temporary_error, all subclasses extend base, previous-throwable
chaining works, IdentityMatchInvariantViolation is NOT in the
binding-applicator hierarchy and IS a DomainException.
- FormBindingExceptionClassifier matrix (6): each subclass maps to its
reason code; Timeout dispatches to inherited 'temporary_error';
arbitrary RuntimeException -> 'unknown_error'; IdentityMatchInvariantViolation
-> 'unknown_error' (intentional fallback per docstring).
- FormFieldBindingMergeStrategy::validForTargetType (4 tests covering
the full 4 strategies x 3 target types matrix).
- FormSubmissionIdentityMatchResolved (4): ShouldBroadcast contract,
private channel naming ('private-submission.{id}'), broadcast-as
string, payload assignment.
- FormSubmission failure_response_code cast (4): persists as plain
string, NULL by default, factory state composes with apply_status,
round-trips for all four canonical codes.
Baseline regenerated to absorb new tautological-assertion entries (48
lines) — these are class-hierarchy regression guards that Larastan
correctly flags as statically known. The pattern is established in the
codebase per existing baseline entries for similar tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Same root cause as 832375b — the new failure_response_code migration
sits at the top of the WS-5/WS-6 stack, so every test that pins --step
to walk back through that stack needs +1.
- FormFieldOptionsBackfillTest: 6 -> 7 (10 occurrences)
- ConditionalLogicBackfillTest: 10 -> 11 (4 occurrences)
- FormFieldConfigBackfillAndDropTest: 16 -> 17 (1 occurrence)
- FormFieldValidationRuleBackfillTest: 19 -> 20 (7 occurrences)
Total: 22 backfill tests now green again.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per RFC-WS-6 §Q3 v1.3 addition 2.
- Added 'failure_response_code' to FormSubmission $fillable + 'string' cast.
Plain string (not enum) — the exception subclass on
form_submission_action_failures is the canonical classification source;
this column is a denormalised mirror for response-shape rendering.
- Factory fluent state method withFailureResponseCode() with documentation
of the four valid values.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per RFC-WS-6 §Q3 v1.3 addition 2.
Centralises the Throwable -> failure_response_code mapping so the
listener (ApplyBindingsOnFormSubmit::handle catch block) and the
retry-service (FormFailureRetryService::recordFailure) produce
identical classifications. Single behaviour-change point.
Resolution order: FormBindingApplicatorException subclass dispatch via
reasonCode(); fallback 'unknown_error' for anything outside the hierarchy.
Wiring into the listener and the retry service lands in D2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per RFC-WS-6 §Q1 v1.3 addition 2.
Broadcast event class only — not yet dispatched. D2 wires the dispatch
call into TriggerPersonIdentityMatchOnFormSubmit::handle (after the
final identity_match_status write), and the channel-authorization
callback into routes/channels.php.
Frontend Echo subscription is a separate frontend follow-up (out of
WS-6 v1.3-delta scope).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per RFC-WS-6 §V1 + ARCH-BINDINGS §4.2.
Implements the strategy x target-type validity matrix. Append is the
only non-trivial case: valid only for COLLECTION targets. The
AppendStrategyRequiresCollectionTarget publish-guard uses this method
(D2 wiring confirms call sites; this commit provides the building block).
Existing methods (nullWinnerBehaviour, isValidForScalarTargets) untouched.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per RFC-WS-6 §Q3 v1.3 addition 2 (binding hierarchy) + §Q2 (invariant exception).
- Refactored FormBindingApplicatorException from concrete final to abstract
base. Constructor (submissionId, message, previous?) preserves submissionId
as a public readonly property so D2's outer-transaction handler can write
it structurally to form_submission_action_failures.context JSON without
regex-parsing the message. Replaced public-readonly reasonCode property
with abstract reasonCode(): string method.
- Added 3 reason-coded subclasses:
- FormBindingSchemaConfigException -> 'schema_config_error' (422)
- FormBindingInfraException -> 'temporary_error' (503, NOT final because
Timeout extends it)
- FormBindingDataIntegrityException -> 'data_integrity_error' (422)
- Added FormBindingApplicatorTimeoutException extending FormBindingInfraException
(timeout = temporary infra issue from user perspective; reasonCode inherited).
- Added IdentityMatchInvariantViolation as a sibling DomainException — NOT
in the FormBindingApplicatorException hierarchy because it's thrown
outside the binding-applicator pipeline.
- Migrated 3 existing throw sites in FormBindingApplicator::apply():
- 'no_transaction' -> FormBindingInfraException (developer-error wants
infra-triage workflow: GlitchTip alert + retry-after)
- 'no_schema' -> FormBindingSchemaConfigException
- 'unknown_purpose' -> FormBindingSchemaConfigException
- Updated FormBindingApplicatorIntegrationTest::test_no_transaction_guard_present
to assert against the new throw shape (FormBindingInfraException + new
message string) while preserving the test's intent (guard exists in source).
Wiring (deadline wrapper, classifier integration in listener catch +
retry-service recordFailure) lands in D2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The forward + rollback migration tests pin --step to a fixed count to
walk the WS-5/WS-6 stack back to known pre-states. The new
2026_05_08_000001_add_failure_response_code_to_form_submissions
migration sits at the top of that stack, so both rollback step counts
need +1 to reach the same destinations.
- pre-WS-5a rollback: --step 21 -> 22 (used twice)
- pre-WS-5b rollback (from fully-forward): --step 19 -> 20 (used once)
Comments updated to enumerate the v1.3-delta D1 migration in the WS-6
group.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per RFC-WS-6 §Q3 v1.3 addition 2 + ARCH-BINDINGS §7.1 v1.2.
Denormalised mirror of the FormBindingApplicatorException subclass
classification, written by ApplyBindingsOnFormSubmit's outer-transaction
catch block (D2) when apply_status='failed'. Drives response-shape copy.
NULL when apply_status is not 'failed'.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>