Skip to content

[ANCHOR-1413]: Enhance SEP-31 persistence and JSON mapping with error handling - #2092

Merged
ceciliaromao merged 9 commits into
developfrom
fix/anchor-1413
Nov 13, 2026
Merged

ceciliaromao merged 9 commits into
developfrom
fix/anchor-1413

Conversation

@ceciliaromao

Copy link
Copy Markdown
Collaborator

Description

  • SEP-31 transactions whose fields or required_info_updates serialize to more than 255 characters are now created, patched and read back intact. sep31_transaction.fields and .required_info_updates become native JSON columns (V37__sep31_json_columns.sql, same ALTER ... TYPE ... USING form as V6 and the SEP-6 precedent), and JdbcSep31Transaction maps both with @JdbcTypeCode(SqlTypes.JSON) instead of string accessors.
  • A database failure inside any API call no longer returns the driver message or the SQL statement to the caller. AbstractControllerExceptionHandler.handleInternalError answers HTTP 500 with {"error":"An internal error occurred."} when the exception, or anything in its cause chain, is a DataAccessException, TransactionException, PersistenceException or SQLException. The full exception is still logged at ERROR and sent to Sentry. The cause-chain walk stops on a cycle. It applies to the SEP, Platform and Observer servers because they share the handler.
  • Every other 500 keeps its current message, and exceptions with their own handler (400/403/404/429/501/502) are unchanged.

Context

  • The two columns were still VARCHAR(255) from V1__master.sql while the entity writes JSON into them, so one moderately long field value (or a handful of fields) made the INSERT fail. The unhandled failure then returned the full parameterized INSERT and the column list of sep31_transaction to any caller with a SEP-10 token. The SEP-6 columns for the same purpose were already converted to JSON in V10 and V36; SEP-31 never was.
  • HackerOne report #4113076

Testing

  • ./gradlew test
  • ./gradlew :platform:test and ./gradlew :platform:spotlessCheck :essential-tests:spotlessCheck: green.
  • New unit tests: AbstractControllerExceptionHandlerTest (each persistence exception type, wrapped and cyclic chains, nothing leaked in the body, log and Sentry capture, the three advices, every specific handler unchanged), Sep31JsonMigrationTest (the migration alters exactly the two columns), JdbcSep31TransactionTest (JSON mapping and round trip).
  • New e2e tests in Sep31Tests (full class run on the local stack, 23 tests, 0 failures): POST with a 300-character value, fields of exactly 255 and 256 serialized characters, non-ASCII values, Platform API read-back, a PATCH merge past 255 characters, and required_info_updates with descriptions, choices and optional flags read back.
  • V37 on a local PostgreSQL at V36 with 142 real SEP-31 rows: Flyway applied it, both columns are json, and all 142 rows are byte-identical. The same SQL on a seeded copy kept the text null, SQL NULL, an object with choices and non-ASCII values. On a copy with a non-JSON fields value it fails and leaves the table unchanged.

Documentation

N/A: no API contract or configuration changes; this fixes a failure for valid requests.

Known limitations

  • V37 rewrites sep31_transaction under an exclusive lock (ALTER COLUMN ... TYPE), so large deployments should run it in a quiet window. There is no down migration; a row holding non-JSON text in either column makes the migration fail and roll back.
  • Flyway recording no V37 entry after that failure was not exercised; it relies on Flyway running a PostgreSQL migration in one transaction.
  • Only persistence errors are sanitized. Other unhandled 500s still return the exception message, and a persistence cause wrapped inside an exception that has its own handler (for example a BadRequestException built from a database error) is still echoed. Nothing in the SEP-31 write path builds one today. Both are candidates for a follow-up ticket.
  • The other VARCHAR(255) columns of sep31_transaction and other tables were not audited for the same overflow; one would now return the generic 500 but is not fixed here.
  • No new size limit on fields: the existing max_request_body_size (1 MiB, 413 above it) is the bound.

@ceciliaromao ceciliaromao self-assigned this Nov 2, 2026
@ceciliaromao
ceciliaromao merged commit 8a18040 into develop Nov 13, 2026
22 checks passed
@ceciliaromao
ceciliaromao deleted the fix/anchor-1413 branch November 13, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants