Does the Whole Name Get Saved? A Proposed Axis for Length and Truncation Correctness (September 2026)
Every length limit is checked with a value the developer typed themselves, and on that value all four units agree. A pre-registered seven-signal axis for whether the value submitted is the value stored.
Updated on September 16, 2026
On this page
Quick answer. Every length limit a developer checks is checked with a value they typed themselves, in their own alphabet. On that value, all four of the quantities an application might mean by "length" are the same integer, so the number in the schema looks like one limit. It is four. This axis proposes a way to score whether the value a person submits is the value a generated application stores and returns. It is a pre-registration, not a result: no builder is scored, named or assigned a posture on this page.
A length limit looks like the least interesting decision in an application. You put maxlength="255" on the input, you declare the column varchar(255), the two numbers match, and you move on. It is one number in two places and there is nothing in it that looks like a judgement.
The two numbers do not match. They are not measured in the same unit, neither unit is the one the person typing is counting in, and a third unit decides whether the value fits on disk. The three documents that define those units each say so, clearly, in their own words, and none of them is the resolution.
Why this is not already covered
Three axes in this series come close enough that the boundary has to be drawn explicitly, in both directions, or the axis is not worth proposing. One more is near enough to name.
Against input validation and data integrity
That axis asks whether an oversize string is rejected. Its payload battery includes an oversize string alongside a missing field and a wrong-typed field, and its rejection-semantics signal scores whether malformed input produces a deliberate 400 or 422. This axis asks what happens to a string that is accepted.
Stated both ways. An application can reject every oversize payload with a clean 422 and a structured error, scoring full marks there, and still mangle a name that it accepted, because the bound it enforced was counted in one unit and the column that stored the value counts in another. And an application can preserve every accepted value byte for byte, including the difficult ones, while accepting a two hundred thousand character paste into a field meant for a surname, because nothing anywhere bounds it at all.
The boundary is the moment of acceptance. Before it, that axis. After it, this one. Full detail is in our input validation and data integrity proposal.
Against text-comparison and collation correctness
That axis asks whether two strings are one value. This one asks whether one string survives. They share a vocabulary, which is why the boundary needs stating: both care about normalisation forms, both care that a name can be written more than one way.
They come apart at the remedy. That axis rewards normalising at the boundary, and normalising is the correct answer to its question. Normalising changes the length. Composing an accented name into NFC makes it shorter in code points than its NFD form, and decomposing makes it longer, so an application that normalises at the boundary to satisfy that axis has changed the quantity this axis measures, in a direction its own rubric does not name.
Stated both ways. An application can compare text under a flawless accent-insensitive collation, merge every duplicate account correctly, and still truncate the same name on the way into storage. And an application can store every value it is given with perfect fidelity in all four units and still register one person twice, because it compares bytes.
That axis's scope note draws its own boundaries against concurrent writes, internationalisation and per-route titles, and closes by naming the numeric question. It does not mention length, in either direction. Its rubric rewards normalisation at weight 20 and names the act without specifying the form, which is exactly the gap this axis sits in. Full detail is in our text-comparison and collation proposal.
Against data-export correctness
That axis has a signal for whether a short export is distinguishable from a complete one, weighted 11. The unit there is the row: a file that stops after forty of two hundred records is the failure. The unit here is the field: a file with every row present, one of which holds a name that lost its last four letters before it was ever written down.
Stated both ways. An export can carry a manifest, a row count and a completion marker, so that no consumer can mistake a partial file for a whole one, and every row in it can still carry a silently shortened value. And an application can preserve every field it stores exactly, then ship it in a file that stops halfway with nothing to say so.
They also compose, which is worth more than the carve-out. A value truncated at the storage boundary is not recoverable at export time, because by then the missing characters do not exist anywhere. The export axis can only ever be faithful to what the database holds. Full detail is in our data-export correctness proposal.
Named without a link, to stay inside the internal-link budget
Our internationalisation axis scores whether an application can present itself in the reader's language. This one asks whether it can store the reader's name. An application localised into twelve languages can still refuse four letters of a Devanagari word, and a monolingual English application can handle every script correctly in storage while offering no translation at all.
Four documented facts that make this measurable
1. One field, four different answers to the question how long is this
We recomputed all of this rather than quoting it, because the whole axis turns on the arithmetic and the arithmetic is easy to state and easy to get wrong.
There are four candidate units. The person typing counts what Unicode calls extended grapheme clusters. A browser attribute counts UTF-16 code units. A PostgreSQL column counts characters, meaning code points. Storage, indexes and byte budgets count bytes, and for text arriving over the web that generally means UTF-8.
Scroll to see more
| Value | Grapheme clusters | Code points | UTF-16 code units | UTF-8 bytes |
|---|---|---|---|---|
The ASCII name Ana Smith | 9 | 9 | 9 | 9 |
| An accented four-letter first name, NFC | 4 | 4 | 4 | 5 |
| The same name, NFD | 4 | 5 | 5 | 6 |
| Three Han characters | 3 | 3 | 3 | 9 |
| A six-code-point Devanagari word | 4 | 6 | 6 | 18 |
| A national flag, two regional indicators | 1 | 2 | 4 | 8 |
| A four-person family emoji, one ZWJ sequence | 1 | 7 | 11 | 25 |
Read the first row and the last row together. That is the axis.
On Ana Smith, all four counts are the identical integer, 9. On a single family emoji, the same four questions return 1, 7, 11 and 25. The four units do not merely differ on hard values; they coincide exactly on every unaccented Latin value, which is every value a developer types into their own form while checking that the field works.
Now put a limit on it. The same declared number, 255, admits a different quantity of the same value at each layer.
Scroll to see more
| Repeated value | maxlength="255", code units | varchar(255), characters | A 255-byte budget |
|---|---|---|---|
| An ASCII letter | 255 | 255 | 255 |
| A Han character | 255 | 255 | 85 |
| An accented letter stored as NFD | 127 | 127 | 85 |
| A family emoji | 23 | 36 | 10 |
Two consequences fall straight out, and they run in opposite directions.
The browser is the stricter of the two declared limits for anything outside the Basic Multilingual Plane, because a code point there costs two code units and only one character. So maxlength="255" refuses a value that varchar(255) would have stored. That direction produces a false rejection, not data loss, and because characters can never exceed code units, a column declared in characters will never refuse a value the matching maxlength allowed.
The dangerous direction is the byte budget, and it is invisible to both. A name of 255 Han characters is 255 code points and 255 UTF-16 code units, so it satisfies maxlength="255" and satisfies varchar(255), and it is 765 UTF-8 bytes. Anything downstream that budgets 255 bytes, another system's byte-counted column, an index key limit, a header, a fixed-width file, receives a value three times the size it planned for, having been told twice that it was within limits.
2. The database counts characters, and says so in a parenthesis
PostgreSQL's documentation on character types flags the unit ambiguity itself, in four words inside brackets.
PostgreSQL 17 documentation, section 8.3, verbatim: "Both of these types can store strings up to n characters (not bytes) in length."
That parenthesis is doing real work. It is the database pre-empting the most common wrong assumption about its own type declaration, and it settles one of the four units: varchar(n) and char(n) are counted in characters. The same page then settles the byte question in the other direction, noting that the maximum permitted n is capped below the roughly one gigabyte ceiling on a stored string because, in its words, with multibyte character encodings the number of characters and bytes can be quite different.
One more fact from the same section is worth carrying, because it is a limit no length budget expresses: "Regardless of the specific character set, the character with code zero (sometimes called NUL) cannot be stored." A value can be the right length in all four units and still be unstorable.
3. Two documented paths truncate without raising an error, and the standard requires both
This is the part that turns a units problem into a data-loss problem, and PostgreSQL documents both paths in consecutive sentences.
PostgreSQL 17 documentation, section 8.3, verbatim: "An attempt to store a longer string into a column of these types will result in an error, unless the excess characters are all spaces, in which case the string will be truncated to the maximum length. (This somewhat bizarre exception is required by the SQL standard.)"
And immediately after, verbatim: "However, if one explicitly casts a value to character varying(n) or character(n), then an over-length value will be truncated to n characters without raising an error. (This too is required by the SQL standard.)"
Read those in the direction an application experiences them. The default behaviour on overflow is an error, which is the behaviour you want, because an error is observable. There are then two documented exceptions in which the value is shortened and nothing is raised. One is trailing whitespace, which is exactly what a paste from a spreadsheet or a PDF carries. The other is an explicit cast, which is a single piece of ordinary-looking code.
The documentation's own editorial on the first exception, "somewhat bizarre", is a fair description, and the attribution matters for the rubric: both behaviours are required by the SQL standard, so they are not a defect in one engine that a different engine would avoid. Any rubric that scores overflow behaviour has to score what the application does about them rather than which database it chose.
4. Unbounded is the default at two layers, in each layer's own words
Neither of the two layers that can declare a limit declares one unless it is asked to.
PostgreSQL 17 documentation, section 8.3, verbatim: "If character varying (or varchar) is used without length specifier, the type accepts strings of any length."
MDN's reference for the maxlength attribute, verbatim: "If no maxlength is specified, or an invalid value is specified, the input has no maximum length."
The same MDN page settles the browser's unit and hedges its own enforcement in one paragraph: "The length is measured in UTF-16 code units, which is often but not always equal to the number of characters." And on enforcement: "While the browser will generally prevent user from entering more text than the maxlength attribute allows, should the length be longer than the maxlength allows, the read-only tooLong property of a ValidityState object will be true."
The sentence that matters most for a generated application is the scope of that enforcement. Verbatim: "Constraint validation is only applied when the value is changed by the user." A value placed into the field by script, by an autofill, by a restored draft or by a test harness is not constrained by the attribute at all. The attribute is an instruction to a browser about typing, not a bound on the value that will be submitted.
MDN's reference for the JavaScript string length property closes the loop, and in doing so names this axis's trap in its own words. Verbatim: "The length data property of a String value contains the length of the string in UTF-16 code units." And then, on when that matters: "For common scripts like Latin, Cyrillic, wellknown CJK characters, etc., this should not be an issue, but if you are working with certain scripts, such as emojis, mathematical symbols, or obscure Chinese characters, you may need to account for the difference between code units and characters."
That is an accurate and well-intentioned sentence, and it describes precisely the condition under which a developer's own testing cannot see the problem. For the scripts a developer is most likely to type, this is not an issue. The application is not going to be used only by people who type those scripts.
What the person typing is counting, and why no layer counts it
The fourth unit is the only one with a human behind it, and the Unicode Consortium has written a note about it that reads as though it were written for this axis.
Unicode Standard Annex 29 defines the unit first. Verbatim: "A single Unicode code point is often, but not always the same as a basic unit of a writing system for a language, or what a typical user might think of as a "character"." It then names length limits explicitly as one of the contexts in which the basic unit is defined differently, alongside drop-caps, initialisms and text selection.
Then it gives the prescription, and this is the single most useful paragraph we found for this axis. UAX 29, section 3, verbatim: "Note: A text size limit based on a count of grapheme clusters is a user-perceived limit, just like a word count; it is not a data size limit. There is no limit on the size of a grapheme cluster as defined in this section. When limits on data size are desirable, they should be used together with word or character count limits. For instance, a form might simultaneously restrict submissions to 300 "characters" defined by a count of grapheme clusters and to 3000 UTF-8 bytes to ensure a bound on storage size."
Three things follow, and we checked the third rather than assuming it.
First, the correct number of limits is two, not one. Unicode's position is that a user-perceived limit and a data-size limit do different jobs and you need both. Neither the browser attribute nor the column declaration implements that, and neither claims to.
Second, a grapheme limit cannot bound storage at all, because "There is no limit on the size of a grapheme cluster." One thing the user counts as one character has no byte ceiling.
Third, the illustrative pairing in that note does not survive contact with a value Unicode itself defines as a single cluster. We recomputed it: 300 four-person family emoji is exactly 300 grapheme clusters, satisfying the first limit, and 7500 UTF-8 bytes, which exceeds the illustrative 3000-byte budget by 4500 bytes. The note says "For instance", so this is an example rather than a guarantee, and the same note supplies the reason it cannot be a guarantee two sentences earlier. The lesson for a rubric is that the byte budget has to be derived from the worst case the application will accept, not from a ratio that holds for prose.
The proposed rubric
Seven signals, weighted to 100. This is a proposal. The weights are the part most worth arguing about before anything is scored.
Scroll to see more
| Signal | Weight | What a failing case looks like |
|---|---|---|
| The unit of the limit is stated, and the same unit is used at every layer that counts | 22 | The form says 255, the column says 255, and nobody has written down that one of those is code units and the other is characters |
| Overflow is a deliberate, observable outcome rather than a silent shortening | 20 | The write path casts explicitly, or the value arrives with trailing whitespace, and the stored value is shorter than the submitted one with nothing raised and nothing logged |
| A user-perceived limit and a data-size limit are both present | 16 | A single limit is declared, so either the user is refused a name that would have fitted or storage is handed a value multiples larger than budgeted |
| An accepted value round-trips byte-identically, including multi-code-point clusters | 14 | Submit, store, read back, and a flag or a family emoji returns as a fragment, a replacement glyph, or a different normalisation form than was sent |
| The bound is chosen for the field rather than inherited from a template default | 9 | Every text column in the schema is the same round number, or every one is unbounded, which are the same absence of a decision |
| Where shortening is intentional, it cuts on a cluster boundary and says so | 10 | A summary field is cut mid-cluster, splitting a grapheme into an invalid fragment, and the user is not told the value was shortened |
| The limit is disclosed to the person typing, in a unit they can verify | 7 | The field accepts input silently until submission fails, or reports a count that disagrees with what the user can see they typed |
Three weighting decisions worth arguing about now
Why the unit signal carries the most. Every other failure on the list is downstream of not having decided what the number means. An application that has written down that its limit is 120 grapheme clusters and 600 UTF-8 bytes can get the enforcement wrong and then fix it. An application whose limit is the bare integer 255 in two places cannot even state what it intends, so there is nothing to test against. We are open to the argument that silent shortening deserves the top weight instead, on the grounds that it is the only failure on the list that destroys data rather than refusing it.
Why intentional shortening scores at all. Cutting a value is often correct, for a preview, a summary, an index key or a fixed-width interchange format. The signal is not whether an application shortens but whether it shortens on a boundary the standard defines and tells anyone it did. A contributor could reasonably argue this belongs in a presentation axis rather than a correctness one.
Why disclosure is only 7. Telling the user the limit is genuinely useful and is the weakest signal here, because a field that discloses the wrong unit is worse than one that discloses nothing. We have deliberately weighted the underlying correctness above its presentation, and that ordering is a judgement rather than a measurement.
Four postures
Level 0, Unbounded. No limit is declared at any layer. Text columns with no length specifier, no maxlength, no server-side bound.
Level 1, Declared. A number exists at one layer, usually inherited from a scaffold, with the unit unexamined and overflow behaviour untested.
Level 2, Agreed. The application layer and the storage layer enforce the same quantity in a stated unit, and overflow is a deliberate rejection rather than a shortening.
Level 3, Bounded in both units. A user-perceived limit and a data-size limit are enforced together as UAX 29 prescribes, overflow is observable, accepted values round-trip byte-identically, and any intentional shortening cuts on a cluster boundary and is disclosed.
One thing to say plainly before anyone reads the ladder as a progress bar. Moving from Level 0 to Level 1 is not unambiguous progress. A Level 0 application never shortens a value, because it has no limit to shorten it to; whatever it is given, it stores. Level 1 is the first posture on this ladder that is capable of silently changing data, because it is the first that has a number to enforce and has not checked what the number means or what happens at the edge. A generated application that ships unbounded text columns is worse on five of the seven signals and better on the one that destroys data. The ladder measures whether a decision was made, and Level 1 is the posture of having half-made one.
The measurement protocol
Ten steps, pre-registered so that a contributor can run them before we do and disagree with the result.
- Generate the application from a fixed reference prompt describing a product with a person's name, a short display name and a free-text notes field. The prompt describes the product, not its validation, so that the measurement is of default posture rather than instruction-following.
- Record what is declared and where: every
maxlengthattribute in the emitted forms, every length bound in the server handlers, and every column type in the emitted schema. Note whether any of them states a unit. - Establish the looks-correct control first. Submit
Ana Smith. Confirm it stores, reads back byte-identically, and passes every declared check. This is the observation that every developer already has, and its purpose here is to prove the harness works before anything is expected to fail. - Submit the same value with forty trailing spaces appended, at a total length one character over the declared bound. Record whether the write errors, stores the full value, or stores a shortened one. This exercises the documented whitespace exception.
- Submit a value at exactly the declared bound counted in grapheme clusters, composed entirely of four-person family emoji. Record which layer refuses it, and in which unit its error message is expressed.
- Submit a value of the declared bound in Han characters. Record the stored byte length. This is the case that passes both declared limits and exceeds a byte budget of the same number.
- Submit an accented name in NFD. Read it back. Record whether the returned value is byte-identical, has been normalised to a different form, or has been shortened.
- Set the field's value programmatically rather than by typing, then submit, to test whether anything enforces the bound once the browser's user-change constraint does not apply.
- Repeat step 4 through step 8 against the API directly, bypassing the form entirely, and record any difference between the two paths.
- For every case in which the stored value differs from the submitted value, record whether the application told anyone: a raised error, a validation message, a log line, or nothing at all.
The control in step 3 is not ceremony. A harness that reports failures on steps 4 through 9 while step 3 also fails has measured its own plumbing.
The named trap: the round-number illusion
Every axis in this series names the illusion that hides its defect. This one is the round-number illusion.
The illusion is that 255 is a limit. It is four limits that happen to be the same integer on the values used to check it. A developer types a name into the form, watches it save, opens the row, sees the name, and has genuinely verified that the field works. They have verified it for values on which grapheme clusters, code points, UTF-16 code units and UTF-8 bytes are numerically identical, which is every unaccented Latin string, which is the entire set of test data a person produces by typing on their own keyboard in their own language.
This is the eighth trap in the series with the same structure, and the structure is why these defects survive review. Every observation available at test time is generated by a single actor. The developer types the value, so the value is short and ASCII. There is no second party in the loop whose alphabet would disagree, and no layer that reports a disagreement, because on that value there is no disagreement to report. The failure does not arrive with a new deployment. It arrives with a user.
Two properties make it worse than the average member of that family. First, Level 0 and Level 3 are indistinguishable under the developer's own test, because an application with no limits and an application with two correctly-united limits both store Ana Smith perfectly. Second, the defect is silent in the specific direction that matters: the documented shortening paths raise nothing, so the row looks fine unless someone compares it to what was submitted, and by then the submitted value is gone.
Two documented tensions worth stating
The database recommends the posture with no enforcement. PostgreSQL's own guidance on the three character types is unambiguous. Verbatim: "There is no performance difference among these three types, apart from increased storage space when using the blank-padded type, and a few extra CPU cycles to check the length when storing into a length-constrained column." And, in the same section: "In most situations text or character varying should be used instead."
So the length check is nearly free, and the recommendation is nonetheless the unbounded type. Both halves are correct, and together they say that the column is not where the database thinks a domain rule belongs. We resolve this by scope rather than by choosing. The storage layer should be unbounded or generously bounded, the domain rule belongs in the layer that knows the domain, and the rubric therefore scores whether a unit-stated bound exists somewhere and is enforced before the write, not whether the column carries a number. The cost of that resolution should be stated too: it gives up the storage layer as a backstop, so an application that satisfies this axis by validating in the handler has nothing catching a write path that forgets to.
The two-limit prescription cannot be checked with one ratio. UAX 29 asks for a user-perceived limit and a data-size limit together, and its illustrative pairing of 300 clusters to 3000 UTF-8 bytes is exceeded two and a half times over by 300 clusters of a value it defines as one cluster each. The tension is between wanting a simple rule of thumb and the standard's own statement that cluster size is unbounded. We resolve it by derivation: the byte budget is computed from the worst-case value the application intends to accept, and where that worst case is genuinely unbounded, the byte limit becomes the binding one and the user-facing count is advisory.
The seam between the three documents
Each of the three sources is correct, complete for its own layer, and silent on the composition.
MDN says what the browser and the language count, UTF-16 code units, and when that will not match characters. PostgreSQL says what the column counts, characters and not bytes, and what happens at the edge. UAX 29 says why neither of those is the unit the person typing has in mind, and that a correct design needs two limits rather than one.
The resolution appears in none of them, and none of them is at fault for that. The browser is not the place to document a database, and a Unicode annex is not the place to document a form attribute. The composition is the application's to make, and it is the thing nothing in the emitted code is obliged to state. That is what makes it worth an axis rather than a bug report.
What this axis is not, and what we are not claiming
No scores today. This page proposes the axis, the rubric, the postures and the protocol. It does not rank anyone, and it assigns no builder a posture. Publishing the rubric before any result is deliberate, so that the weights and the battery can be challenged before they are used.
It is not a character-encoding audit. Whether an application serves UTF-8 correctly is a different and largely solved question. This axis assumes the bytes arrive intact and asks what the application's own limits then do to them.
It is not a validation axis. Rejecting bad input is scored elsewhere, and an application can score full marks there and fail every signal here.
Limitations, stated rather than discovered later.
- One generation is one sample. A builder that is inconsistent between generations needs a multi-run design before any score is fair.
- The grapheme count is an approximation and says so. UAX 29 describes extended grapheme clusters as a best-effort approximation that may not cover all potential types of user-perceived character. A rubric built on it inherits that, and tailored cases exist where a different notion is preferred.
- The byte unit depends on the encoding in force. We have assumed UTF-8 throughout because that is what text arriving over the web generally is. An application storing UTF-16 has different arithmetic and the same structural problem.
- Signal 6 is partly aesthetic. Cutting on a cluster boundary is measurable. Whether a given field should be cut at all is an editorial judgement we are not claiming to settle.
- The protocol cannot see a bound that is never reached. A limit generous enough that no test value approaches it is unfalsifiable by this battery, and scoring it as a pass would be as wrong as scoring it as a failure.
Counter-rubrics and reproduction attempts are welcome. If your run of this battery disagrees with ours when we publish results, that is the most useful thing you can send us.
References
- PostgreSQL Global Development Group, PostgreSQL 17 Documentation, section 8.3, Character Types. The character count and the parenthetical note that it is not bytes; the error on overflow and the two documented exceptions; the behaviour of a missing length specifier; the NUL restriction; the comparison of the three types and the recommendation. Read 16 September 2026. https://www.postgresql.org/docs/17/datatype-character.html
- MDN Web Docs, String: length. The UTF-16 code-unit definition and the note on which scripts make the difference visible. Read 16 September 2026. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length
- MDN Web Docs, maxlength, HTML attribute reference. The code-unit measurement, the absent-attribute default, the tooLong validity state, and the user-change scope of constraint validation. Read 16 September 2026. https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/maxlength
- Unicode Consortium, Unicode Standard Annex 29, Unicode Text Segmentation, section 3, Grapheme Cluster Boundaries. The user-perceived character definition, text size limits as a context in which the basic unit differs, and the note on user-perceived limits versus data size limits. Read 16 September 2026. https://www.unicode.org/reports/tr29/
All counts in the two tables and the 300-cluster calculation were recomputed from the definitions rather than quoted from a secondary source.
Written by
BuilderProof editorial teamCite this benchmark
BuilderProof editorial team. "Does the Whole Name Get Saved? A Proposed Axis for Length and Truncation Correctness (September 2026)". BuilderProof, September 2026. https://www.builderproof.org/benchmarks/does-the-whole-name-get-saved-length-truncation-axis-september-2026.
@misc{builderproof-does-the-whole-name-get-saved-length-truncation-axis-september-2026,
title = {{Does the Whole Name Get Saved? A Proposed Axis for Length and Truncation Correctness (September 2026)}},
author = {{BuilderProof editorial team}},
year = {2026},
month = {sep},
howpublished = {\url{https://www.builderproof.org/benchmarks/does-the-whole-name-get-saved-length-truncation-axis-september-2026}},
note = {BuilderProof, builderproof.org}
}Frequently asked questions
Is a length limit not just input validation?
They are adjacent and they come apart at the moment of acceptance. Input validation asks whether an oversize value is rejected. This axis asks what happens to a value that was accepted: whether the string the person submitted is the string that gets stored and returned. An application can reject every oversize payload with a clean 422 and still shorten a name it accepted, because the bound it checked and the column that stored the value count in different units.
Why would one declared limit mean four different things?
Because four different layers count differently, and each documents itself correctly. Unicode Standard Annex 29 describes what the person typing counts, extended grapheme clusters. MDN records that the HTML maxlength attribute and the JavaScript string length property both count UTF-16 code units. PostgreSQL documents that varchar(n) stores up to n characters and adds the words not bytes in brackets. Storage, indexes and byte budgets count bytes. A single four-person family emoji is 1 grapheme cluster, 7 code points, 11 UTF-16 code units and 25 UTF-8 bytes.
Does a value ever get shortened without an error?
Yes, and PostgreSQL documents two paths in consecutive sentences. Storing an over-length value normally raises an error, except when the excess characters are all spaces, in which case the documentation states the string will be truncated to the maximum length and calls the exception somewhat bizarre. Separately, an explicit cast to varchar(n) truncates without raising an error. The documentation notes that both behaviours are required by the SQL standard, so they are not specific to one engine.
What is the round-number illusion?
It is the assumption that the number 255 in a schema is one limit. It is four limits that happen to be the same integer on the values used to check it. On an unaccented Latin string such as Ana Smith, grapheme clusters, code points, UTF-16 code units and UTF-8 bytes are all 9, so no value a developer types on their own keyboard in their own language can distinguish the four units. The failure does not arrive with a deployment. It arrives with a user.
Related benchmarks
Does the Export Open Anywhere Else? A Proposed Axis for Data-Export Correctness (September 2026)
Every export is checked by the one reader whose settings match the writer's. A pre-registered seven-signal axis for whether a generated application's export survives arriving anywhere else.
Can One Person Sign Up Twice With the Same Email? A Text-Comparison and Collation Axis (September 2026)
Two strings a human reads as one name can be two different byte sequences, and the default comparison in a generated app calls them different. We pre-register a seven-signal rubric for text-comparison and collation correctness, and name the trap that hides it.
Input-Validation and Data-Integrity Posture: A Proposed Axis for Whether AI App Builders Validate Untrusted Input at the Boundary (August 2026)
A candidate BuilderProof benchmark axis that scores whether the code AI app builders emit validates untrusted input at the server boundary, or trusts whatever the client sends. Rubric, four posture levels, an adversarial-payload reproduction protocol, and an open call for comment.