feat(form-builder): FormBindingApplicator + BindingActivityLogger (WS-6)
Orchestrates per-purpose subject resolution + binding conflict resolution + per-binding writes per RFC Q4/Q7/Q9. Per-binding failures captured in BindingPassResult, not thrown — partial failures are expected and recoverable. Catastrophic failures (no transaction, unknown purpose, missing schema) throw FormBindingApplicatorException and bubble. Per-strategy null-winner matrix implemented via a NO_OP sentinel: overwrite=write null, append=noop, replace=conditional, first_write_wins= write only into null target. Append is collection-only with set-merge semantics (deduplicated array_merge). Identity-key bindings are skipped during apply — the subject resolver already used them for lookup/provisioning; re-writing is a no-op or a clobber. Activity log hierarchical: one bindings_pass_completed parent + N binding_applied children with parent_activity_id linkage (RFC Q12). Failed bindings get error_class/error_message in their activity entry in addition to their FormSubmissionActionFailure row (deliberate dual source of truth). Refs: RFC-WS-6.md §3 (Q4, Q7, Q9, Q12) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\FormBuilder;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* RFC-WS-6 §3 (Q3) — catastrophic applicator failure that bubbles to
|
||||
* the caller. Per-binding failures are captured in BindingPassResult,
|
||||
* not thrown.
|
||||
*/
|
||||
final class FormBindingApplicatorException extends RuntimeException
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $reasonCode,
|
||||
public readonly string $submissionId,
|
||||
?string $message = null,
|
||||
) {
|
||||
parent::__construct($message ?? "FormBindingApplicator failed: {$reasonCode} (submission {$submissionId})");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user