Correctly handling errors from custom form processes

I have written (and published) grav-plugin-sqlite and grav-plugin-sequential-form.

Both work well, but not in combination when there is an error.

For example, if a sequential-form is created in an sequence.md file (a sequential-form being an extension of a standard form) with the processes:

# yaml for the start of the form with buttons and fields as standard
 process:
    - sequence:
          routes:
              - video # a route to sequence.md
              - terms # a route to sequence.md
          icons:
              - address-card
              - video-camera
              - thumbs-up
    - sql-insert:
        table: clients
    - redirect: register/final

The sequence process is defined in the sequential-form plugin, and completes without a problem.

The sql-insert process is defined in the sqlite plugin. However, the SQL INSERT stanza (not shown here) that is generated fails (in my specific case, which is not relevant here, the failure occurs because the SQL table is defined with a UNIQUE constraint that is broken by the data input into the form). The point is that the failure is due to input data, and so should be trapped for the User to know there is an error in the data.

The sqlite plugin correctly traps the error, but I cannot work out how to output the error in the form when there are multiple processes, as here.

So in the sqlite plugin, within the function ‘onFormProcessed’ in the section where sql-insert is processed, I have:

$sql ="INSERT INTO {$params['table']} ( $fields ) VALUES ( $values )";
$db = $this->grav['sqlite']['db'];
try {
  $db->exec($sql) ;
} catch ( \Exception $e ) {
  $msg = $e->getMessage();
  if ( stripos($msg, 'unique') !== false ) {
    $msg .= $this->grav['language']->translate(['PLUGIN_SQLITE.UNIQUE_FIELD_ERROR']);
  } else {
     $msg .= $this->grav['language']->translate(['PLUGIN_SQLITE.OTHER_SQL_ERROR']) . "<BR>$sql";
  }
  if ($this->grav['sqlite']['logging']) {
    $this->log_error($msg); # a function that appends the $msg string to a file
    $this->log_error(print_r($event['form'],true));
  }
  $this->grav->fireEvent('onFormValidationError', new Event([
    'form'    => $event['form'],
    'message' => $msg
  ]));
  $event->stopPropagation();
}

When sql-insert is the first process in the form, then the error message is displayed in the form, and propagation stops, as documented for the Form plugin.

In this case, however, when an error is generated by sql-insert, GRAV completely fails because the form’s sequence process gets called again. The error is generated within the template sequence.html.twig.

I cannot see why $event->stopPropagation() is not stopping the form processing.

When I trap $event['form'] with the log function, the Form process array starts with sql-insert, which is what I would expect. But why is the form being processed from the beginning?

Any pointers as how I should be handling the error trapped by sqlite?