Skip to content

ADR-0022: The characters a value is made of are read where its length already is, and the regular expressions are somebody else's

Status: Accepted, amended at M9.6, at 2.1, and on 12 October 2026 Date: 2026-09-20

A specification says three kinds of thing about a piece of text. How long it may be, which RandomValueProvider has read since M1.5. Which kind of value it is — format: date-time, format: email, format: uuid. And how it must be spelled — pattern, a regular expression the value has to satisfy. Until this increment the second and the third were read by the parser, stored faithfully in StringSchema, and used by nothing that builds a value. A date-time parameter got aK3xQ9pR, and an API that looks at what it is given answered 400 before anything worth testing happened.

M2.4 closes that. Three questions come with it, and none has one obvious answer.

Every openapi.* file in the corpus, parsed, walking every place a request can carry a value — a parameter, or a step inside a request body. Places, not schemas: a shape a document declares once and twenty operations refer to is twenty places.

Documents parsed 50
Places naming a kind of value RESTest now builds 225, in 14 documents
…of them in the five APIs the tool is measured on 25 — flight-search 6, pet-clinic 7, gestao-hospital 6, kafka-rest-proxy 4, notebook-manager 2
Places naming a kind RESTest does not build 4 distinct names: Integer, string, password, binary
Places stating a spelling rule 23 — pet-clinic 10, GitHub 7, AmadeusHotel 6
Distinct spelling rules across the corpus 26

The thousand-odd format declarations a grep finds in those documents are mostly on values an API returns. Those are the schema oracle's business, not generation's, and nothing here sends one.

Two of the four kinds we do not build are somebody writing the type of a value where its kind was asked for. password is a note to whoever draws the form. binary is not text at all. An ordinary word answers all four, which is what makes the list of kinds worth building short and closed.

The spelling rules are a compiler's problem

Section titled “The spelling rules are a compiler's problem”

The 26 rules use character classes, ranges, negation, \d, \p{L}, non-capturing groups, alternation, and every quantifier there is. One of them is ^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$. Working backwards from such a rule to a string that satisfies it is parsing a language and walking the result.

And the rules do not stand alone. Thirteen places state a spelling and a length, and one of them — GitHub's ^[0-9a-fA-F]+$ with minLength and maxLength both 40 — is satisfied by nothing the spelling alone suggests. The spelling says one or more; only the length says forty.

1. Both are read where the length already is, not as new sources

Section titled “1. Both are read where the length already is, not as new sources”

RandomValueProvider.text reads format and pattern alongside minLength and maxLength. It is not a new source in the chain of ADR-0013 §1, and nothing about the chain moves.

The reason is that reading a format is reading the shape. A source in the chain answers from knowledge the document does not carry — a sample somebody wrote, a list somebody curated, a value the API handed back. There is no such knowledge here: the document said the value is a date, and a date is what gets built. Putting that behind a new name in the chain would mean arguing about where in the order it sits, and the honest answer is "last, where invention already is".

Practically it is also the only place that can be right. A shape states its kind, its spelling and its lengths at once, and they have to hold together. Inside one method all three are in hand.

The four branches, in order:

a kind and a spelling both stated -> the kind's value, kept only if the spelling accepts it
a kind we build -> a value of that kind
a spelling -> a string built to match it
neither -> an ordinary word, which is what every string used to be

Each falls through to the next when what it produces is not something the rest of the shape allows. The kind comes first because it describes the whole value and the spelling only its characters; the one place in the corpus that states both is an email beside a rule of @, and an e-mail address satisfies that rule.

2. The kinds are built, not looked up in a shipped list

Section titled “2. The kinds are built, not looked up in a shipped list”

ADR-0013 §1 lists a "format dictionary — the schema declares a format we have values for" among the eight sources, and ADR-0020 §5 names 2.4's shipped format dictionary as the competitor that makes M2.10's weighted groups worth having. No such file is shipped, and this reverses that much of both.

Two reasons:

  • Variety. The chain is exclusive until M2.10, so a shipped list would replace invention for every value of its kind. The corpus names uri 569 times; a run would send half a dozen fixed addresses for all of them, for its whole length. An invented one is different every time.
  • A fresh identifier cannot come out of a file, and a uuid is usually an identifier. Nor can a date drawn from a window somebody can widen, which is what makes a run explore rather than repeat.

A third reason was written here first and does not survive its own implementation, so it is recorded as withdrawn rather than quietly dropped. It read: a dictionary sees only its key, where invention sees the whole shape, so every conflict in §1 resolves itself instead of needing a guard. FormattedStrings also sees only the key — a name and a source of numbers, nothing else — and §1's fallthrough is exactly the guard the argument claimed to avoid: a shape saying format: email, maxLength: 12 does not get a shortened address, it gets an ordinary word, which is what the dictionary was accused of. The two surviving reasons are enough on their own, and the withdrawn one is left here because an argument that sounds good and is not is worth being able to find again.

What does not change: the format keying stays exactly as ADR-0020 defined it, for a user's file. Somebody holding real IBANs for format: iban still writes them that way, and that file still beats invention. The difference is only that RESTest ships none of its own.

What it costs: ADR-0020 §5's argument for weighted groups loses the competitor it was counting on, and falls back to the case it already documents — a type-keyed list somebody writes replacing invention wholesale for a whole kind of value. M2.10 is unchanged in scope; its motivating example is now the smaller one.

3. The regular expressions are read by a library, and every value it builds is checked

Section titled “3. The regular expressions are read by a library, and every value it builds is checked”

com.github.curious-odd-man:rgxgen, Apache-2.0, no dependencies of its own, confined to restest-gen behind MatchingStrings with an architecture rule that says so. It parses all 26 of the corpus's rules, \p{L} and non-capturing groups included, and — the thing that settled it — generate takes a java.util.random.RandomGenerator, which is the run's own seeded source. Nothing about repeating a run exactly had to change.

Everything it builds is held against the rule again, by java.util.regex, before it is offered. Correctness is therefore a checked property of each value rather than a claim about the library, and a disagreement between two readings of a dialect costs a value instead of producing a wrong one. A rule this platform will not compile is declined for the same reason: nothing could check it.

MatchingStrings adds the things the library cannot know about. Every one of them is there because a review of this increment demonstrated the failure it prevents, on real input, three passes running.

  • How far a repetition runs. Its own answer is a hundred, and repetitions multiply: four nested "one or more" groups is a hundred to the fourth power, and a rule of that shape produced 868,356 characters. Eight is the default here, with the shape's own lower length winning over it — a specification insisting on two hundred characters is insisting — and its upper length capping both.

  • The lengths worth having, as a preference rather than a rule. Not longer than the sixty-four characters every other invented string keeps to, and not the empty string where anything else would do. A candidate outside that is set aside while better ones are looked for, and taken in the end if none turns up. Neither end can be a hard limit, and both were, once: a rule asking for a hundred and twenty-eight hexadecimal digits is the document asking, and treating sixty-four as a limit made the tool quietly send a word the document refuses; while pattern: "$" refuses nothing at all and builds only the empty string, which "at least one character" read as a limit turned into no value at all. A rule that refuses nothing is now treated as no rule, which is a separate fix and the right one — an empty value cannot go in the path of a web address, so honouring such a rule cost the operation. The hard limits are the shape's own, capped at ten thousand characters: a shape permitting fifty thousand is held to ten thousand too, because no value the tool builds is longer than that whatever a document permits.

  • How long the rule could possibly run, worked out from the rule before a character is built. This is the defence that holds, and it took four attempts, each of the first three demonstrated inadequate by review on real input. A cap on repetitions does not bound the value, because a rule may state its own counts: [a-z]{1000000} is a megabyte. Reading counts off the rule one at a time does not bound it either, because counts multiply — nine "repeat this nine times" written inside one another is 387 million characters in a rule thirty characters long, and {900000000,} states no upper count to read at all. And reading the rule as the notation defines it does not bound it, because the builder does not: a question about what comes next matches no characters by the notation's rules and the builder builds every one of them, so (?=[a-z]{900000000})x — 21 characters — was counted as one.

    So MatchLength counts what the builder will make at its very worst: pieces in a row add up, a choice takes the widest span, a repetition multiplies, counts written one after another multiply too, arithmetic saturates rather than wrapping, and a group that asks about what surrounds it costs anything between nothing and what is inside it.

    And — the part that finally made this converge — it answers only for a closed list of constructs, and refuses everything else. Four rounds of review each found another construct the builder reads differently from the notation: a question about what comes next, a back-reference, a possessive quantifier, the marks for where a value begins and ends, a set whose first character is a bracket. Enumerating the ones that bite does not terminate; enumerating the ones that are understood does. So an escape named by a letter is answered for only if it is one of ten, a group may only say that it is uncapturing, a question, or named, a quantifier may carry no marker, and a set may not hold a set. Anything else leaves the rule unanswered, and a rule unanswered is a rule not built from.

    It also counts the shortest the rule could build, which answers a different question: whether a value short enough to be worth reading exists at all. Where one does not — a rule asking for a hundred and twenty-eight characters — the first acceptable value is taken and the search stops, instead of spending half a millisecond of the run's own thread hunting for a shorter one that cannot exist.

  • Which reader to trust first. This platform compiles the rule before the library parses it. It refuses a rule nested twenty thousand deep in microseconds, where the library goes looking for memory it cannot have. A cheap first filter and nothing more: a short rule demanding a hundred million characters compiles instantly, which is why the count above exists.

  • Whether the two readings agree at all. The library does not understand lookahead, so ^(?=.*[A-Z])[A-Za-z0-9]{8}$ — an ordinary password rule — parses without complaint and produces ten characters where the rule demands eight. Every candidate would then be refused one by one and the parameter reported as one no value could be found for, which §4 makes an expensive answer. So trial values are drawn when the rule is first read, and if none of the eight satisfies this platform's reading, the rule is treated as one nobody can read. The verdict is kept for the whole run, which is the cost: a rule the two agree about only rarely is settled by eight draws taken once. Eight rather than one because the answer is kept; eight rather than eighty because the only rules that ever get there are ones the two readers really do disagree about.

  • The length, by drawing again. There is no "of this length" in the library, so candidates are drawn until one fits. GitHub's forty hexadecimal digits come out at forty about once in thirty-nine draws, so the allowance is twenty per character demanded, between two hundred and four thousand, and stops on a budget of a million characters built. It is still a lottery, and the odds are worth writing down: at an exactly demanded length n one draw in about n hits, which makes 40 characters a certainty to nine figures and 200 the same, while 2,000 — where the character budget bites before the attempts do — is about seven in ten. Beyond that the tool reports that no value could be found, which is honest about the tool and not quite honest about the shape. Nothing in the corpus demands more than 40.

Two things are not here, and both were considered. Nothing tells the run when a rule was refused, for any of the reasons above, so the API is sent an ordinary word and nobody learns why; that belongs with the rest of what a run says about a document it could not fully use. And whether the two readers agree is decided once and kept, so a rule they agree about only rarely is settled by eight draws taken at one arbitrary moment.

The guarantee is checked rather than argued. Five thousand rules are made up from an alphabet deliberately wider than the list above — the anchors, the quotations, the possessive quantifiers, the sets holding sets, characters written as numbers — each is handed to the count, and the builder is then held to the answer. A rule the count declines is a pass, because declining is safe; an answer smaller than what gets built is a failure. That test found a real error on its first run, and it is the reason the paragraphs above can be believed where four rounds of hand-written expectations could not.

The kinds of value are not a library. UUID, DateTimeFormatter, Base64 and URI are in java.base, and a regular expression for date-time would cheerfully produce 8336-00-95T06:87:98Z — a string of the right shape that is not a date.

4. Not being able to honour a rule means two different things

Section titled “4. Not being able to honour a rule means two different things”

They end differently, and the difference is deliberate:

  • A rule nobody could read is treated as though it had not been written: an ordinary word, as before. Refusing to test a parameter over a notation nobody here understands helps nobody, and the tool never promised every dialect. This covers three cases that look different and are not — a rule neither reader accepts, a rule only one of them accepts, and a rule they both accept and understand differently. The last is the one worth naming, because it is silent: only the trial values described in §3 tell it apart from a rule that is simply hard to satisfy, and without them a password rule would take its whole operation out of the run. None of the corpus's 26 rules is in any of the three cases today, and a test says so.
  • A rule that was read, understood the same way by both, and nothing satisfying it also fits the length the shape demands, means there is genuinely no value to send. Nothing is offered, so the parameter is left out or the operation is reported as untestable. That is what RandomValueProvider already does when it runs out of attempts, for the reason its own comment gives: better than being counted among the operations being tested while every one of its requests is thrown away. It triggers nowhere in the fifty documents of the corpus, and a test pins that too — a described place that yields nothing is a failure there, not a tolerated outcome.

5. A value built this way is still recorded as invented

Section titled “5. A value built this way is still recorded as invented”

GeneratedValue.generatedBy("random"), unchanged. Reading a format is reading the shape exactly as reading maxLength is, and RESTest did invent the value either way.

The alternative — a sub-kind, the way ADR-0019 §4 gave the three kinds of declared value one — is recorded here rather than taken. It was right there because default, enumerated and sample are three different statements by the document's author that a consumer could not otherwise tell apart. Here there is one statement and one inventor. Nothing reads a finer answer until M6's feedback, and whoever needs it then can add it without changing what is stored today.

  • A value whose kind or spelling a document states is now one the document accepts. Across the fifty documents, every value invented for such a place satisfies the whole shape it came from, and a test asserts exactly that.
  • The shared satisfaction check now covers spelling rules, which tightens every generation test in the module at once, the golden-corpus run against the five measured APIs included. It also removed the one place that had its own stricter copy of that check: DeclaredSamplesAcrossTheCorpusTest held samples to a pattern because invented values were excused from one, and nothing is excused now.
  • A new dependency, and the one here without a module descriptor. rgxgen's jar carries neither module-info.class nor an Automatic-Module-Name, so restest-gen says requires rgxgen and names an automatic module derived from the jar's file name. It compiles, it runs, and the name would change if the artefact were ever renamed. Accepted because the packaging step that automatic modules actually block is jlink, and M7.3 is a GraalVM native image; and because the alternative with a descriptor does not exist — com.github.mifmif:generex, the other candidate, is unmaintained since 2017, pulls in dk.brics.automaton, and has no descriptor either.
  • Unicode letters are letters. \p{L} is what pet-clinic's name parameters state, and a value built from it is as likely to be Cyrillic or CJK as Latin. That is what the document asked for, and an API that means "Latin letters" should have said so; it does mean those parameters now exercise the encoding path as well as the validation one.
  • Hundreds of draws for one value is a lot of draws. It happens only where a spelling and a tight length disagree, which is thirteen places in fifty documents, and the work is microseconds. If a campaign ever shows it on a profile, the answer is a repetition count aimed at the wanted length rather than drawn and filtered.
  • A rule can now cost its parameter for a reason that is not the document's fault. A rule the count above refuses — nested repetitions that multiply past what may be sent, a count too large to build — is answered with an ordinary word, which the API will very likely refuse. That is the right trade against ending the run, and it is the one place where the tool knowingly sends something the document does not accept. Nothing in the fifty-document corpus is in this case, and a test says so; if one ever is, the run has no way of mentioning it, which is a gap a later increment should close alongside the rest of what a run says about a document it could not fully use.
  • A rule is read once per run, not once per value. Reading one is parsing a small language, and the API in the corpus with the most of them states a rule for fifteen of the values in every request it takes. Measured on that one: eight microseconds to build a whole request before this increment, eleven with a rule read afresh each time, eight again once each rule is read once and kept. The store of read rules belongs to the generator rather than to the class, because everything in a generator belongs to one run and two runs in the same program have to be two runs; it holds one entry per rule the document states, so it cannot grow.
  • One more list to keep: the kinds of value RESTest builds. It is closed, it is pinned by a test against the corpus, and adding to it is adding one line to a switch.
  • A shipped dictionary keyed by format, as ADR-0013 §1 and ADR-0020 §5 both expected. Rejected for the three reasons in §2. It was the cheaper change and would have given M2.10 a better example, and neither is worth sending six fixed web addresses for every URI in an API.
  • Writing the regular-expression reader here. About 350 lines for the subset the corpus needs, no new dependency, no automatic module, and a dialect we would own. Rejected: it is a compiler, the cost of getting it subtly wrong is silent wrong values rather than a crash, and the project's own measurement of where bugs live — ten of eighteen review findings at M1.5 were in the arithmetic of building a value from a schema — is an argument against writing more of that arithmetic, not less.
  • A new source in the chain, named format or pattern. Rejected in §1: it would need a place in the order, and the only defensible place is where invention already is.
  • Honouring a stated format on a number — int32, int64, double. Rejected: those are widths, and the bounds a shape states already express them. A value of 2147483647 is a value to push at an API with, and it is in the list that does that.
  • Inferring a kind from a parameter's name — dates for bornOn, addresses for homepage. Rejected again, as ADR-0013 rejected semantic dictionaries: the hard part is deciding what a parameter means, and reading what the document says is not that.

Date: 2026-10-02

Where a document declares no kind, the kind its name or its description implies is sent half the time, by a table of rules measured against the fifty documents of the corpus. This reverses the alternative this record rejected, "inferring a kind from a parameter's name", on two grounds the rejection did not have: the campaigns of 1-2 October showed what it costs, and each rule is now measured against what the corpus's documents declare rather than taken on trust, with a switch to turn the whole of it off.

A document that writes format: email gets an e-mail address; one that names a property billing_email and says nothing else gets a word of random letters, which every API that checks an address refuses. In the campaigns of 1-2 October that was the commonest kind of knowledge the hand-written dictionaries supplied and a rule could: market's POST /customer/cart/pay answered 406 on ccNumber to every invented card and accepted 113 payments made with test card numbers; flight-search refused registrations for "a valid e-mail address"; restcountries' currencies and languagetool's languages wanted codes.

Two clues, the description first, because it is the more precise: a date template or a standard it names leaves nothing to guess, where a name can mean several things. The order follows the matching rules of ARTE (Alonso et al., IEEE TSE 2023), which reads the description before the name for the same reason; nothing of ARTE's machinery is taken - no knowledge base, no language processing, no learning from replies - only the order and its rule for "K code" phrases.

  • The description, by what it contains: a date or time template written in letters (YYYY-MM-DD, yyyy-MM-dd'T'HH:mm:ss, YYYYMMDD, dd/MM/yyyy, hh:mm), or a sample date written out (1996-08-01T00:00:00), which gives a value written exactly that way; a standard it names - ISO 639, BCP 47 or RFC 5646 for a language, ISO 3166 for a country (not 3166-2, which is provinces), ISO 4217 for a currency, RFC 3339, an ISO 8601 duration, an HTTP date, Unix time, E.164; and "K code" for a country, a currency or a language, only when K is tied to the place's own name.
  • The description, by how it begins: "The UUID of…", "The email address", "URL of…", "Phone number…". A mention anywhere else is not a statement: "Title of the job associated with the UUID" is not a UUID.
  • The name, by the words it ends in: …At and timestamp a date and time, email, url, uuid, phone, a card number, a currency, a language.

The table is kept as data in one class, ImpliedFormats, which answers one question - which kind does this name and description imply, and by which rule - and nothing else in RESTest knows how a name or a description is matched. A rule is kept only if, where a document of the corpus does declare what it wants, it agrees nine times in ten over three places or more; ImpliedFormatsAcrossTheCorpusTest pins every rule's count and fails if one falls below. That is what left out date and country as names on their own - corpus documents declare a date and time for names ending in Date as often as a date, and want country names as often as codes - cc, which is a country code wherever the corpus uses it, and "ISO 8601" on its own. The one rule kept below the bar, the language names, is kept by the maintainer's choice and said so in the test.

Inside invention, not as a source of its own: a source of its own in the plan would win every value inside a body, because invention asks the rest of the strategy first for what is nested, and a random word would then never be sent there. So invention, at a place no declared kind, closed list or sample settles, sends the implied kind with probability generation.impliedFormatChance, a half by default, and an ordinary word otherwise, at any depth. A spelling rule the document states still holds, and so do its lengths; a value they refuse is replaced by what would have been sent anyway. Nothing is drawn where nothing is implied, so a place no rule recognises is invented exactly as before, and with generation.impliedFormats off a run draws what it drew before, number for number.

The kinds that are new are built from lists the standards publish - currency, country and language codes - or that are set aside for testing: the card numbers card companies publish for tests, the telephone ranges kept for fiction. That is a narrowing of §2's "built, not looked up in a shipped list" for these five only, since a currency code cannot be built any other way; the values are still recorded as invented, as §5 says.

On market and flight-search, each restarted before every run, five seeds, three minutes each, the shipped plan, generation.impliedFormats on against off:

market, off market, on flight-search, off flight-search, on
Operations answered 2XX, mean 10.6 12.0 19.4 19.8
Area under that curve 1,838 2,029 3,372 3,432
Branch coverage 3.6% 5.6% 26.3% 26.3%
Distinct faults, counted as the benchmark counts them 48.0 60.0 0 0
Payments answered 2XX, per seed 0 everywhere 6, 4, 4, 7, 11
Registrations answered 2XX, per seed 1 everywhere 14, 14, 24, 19, 15

Market is better on every seed by every measure: its payment takes a card number only when it passes the check digit, which a test card does and a number built from its spelling rule does not, and its registration and contacts reach code they never reached. On flight-search registration answers because the e-mail address is one; no login answers yet, since the password registered is not the one a login is sent, which is what 9.7 is for. The changes made after review read descriptions more strictly and touch no place of either document, so the table stands for them.

  • A source of its own in the plan, which needs invention to stop asking the strategy first for what is nested; that is the 2.1 design.
  • A knowledge base, language processing or learning from what the API accepted, which are ARTE's and stay with 2.1's external data provider.
  • Passwords, postal codes, people's names, and a date where a date and time is declared.

Date: 2026-10-04

A name now also implies a password, a user name, a person's name, a name of letters and a gender. The amendment above left passwords and people's names out; the campaigns of 1-4 October showed what that costs, on APIs the competition does not disclose in advance and no dictionary covers.

Without a dictionary, four of user-management's operations - creating, registering, updating and logging in a user - never answered 2XX in four runs, and market's registration never did either. The invented password was thirty letters and digits, and the API wants a capital, a small letter, a digit and a symbol; blog refused user names longer than fifteen characters, and market refused a name with a digit in it. Every one of those is a rule an API holds a person's details to, written nowhere in its document, and met by any value a person would have typed.

Five name rules, after the ten already there:

  • N11, a password: a name ending in password, passwd, pwd or passphrase. Sent as twelve to sixteen characters with at least one capital, one small letter, one digit and one of @$!%*?& - the characters of the rule most often used to check a password, which accepts those symbols and no others.
  • N12, a user name: a name ending in username, loginname or nickname, or that is login and nothing more (lastLogin is when somebody last logged in); in either case not where the description says the value may be an e-mail address. Sent as a small letter and five to ten more small letters and digits, fresh each time, so it is neither too long nor already taken.
  • N13, a person's name: a name ending in firstName, givenName, forename, middleName, lastName, surname, familyName or fullName. Sent as a real given or family name, of letters alone. Two people may share a name, so a short list is enough.
  • N14, a gender: a name ending in gender or sex, sent as one of the usual ways of writing one (male, FEMALE, M...).
  • N15, a bare name: a product's, a pet's or a notebook's as often as a person's. What they share is that a name of letters is one, so that is what is sent - built rather than taken from a list, a capital and small letters taking turns between consonants and vowels, fresh each time, since many APIs refuse to make two things of the same name. A longer name ending in name - companyName, fileName, displayName - is left alone. Kept below the bar by the maintainer's choice, and said so in the test: its misses in the corpus are names written with digits, slashes or underscores - a secret, a branch, a release - and it is the only rule that reaches a registration that refuses a name with a digit in it under that one word.

Same switch, same chance: generation.impliedFormats, and generation.impliedFormatChance of the time, a half by default. The corpus, held against what its documents declare or show:

Rule Right Wrong Places in requests it newly reaches
N11 password 7 0 7
N12 user name 14 0 15
N13 person's name 9 1 12
N14 gender 2 0 1
N15 bare name 53 7 40

What counts as right for these, where no pattern above recognises a sample: a password sample is any word without a space, since a sample as weak as foo is still a password and a strong one is accepted where it is; a user name's samples are letters, digits, dots, underscores and hyphens; a name's - a person's or any - are letters, spaces, hyphens, apostrophes and full stops; a gender's are one of the usual words. For N15 that is a statement about the shape of a name, not about whose name it is: most of its fifty-three are the names of things.

  • format: password on its own. The standard uses it to say a value is to be hidden from view, not what kind of text it is, so it no longer stops a name from being read: a property called password that is marked that way gets a strong password by its name. The mark alone implies nothing, since the only places in the corpus that carry it are API keys, which are not passwords.
  • Postal codes, days and months, file names, and values a description lists in prose ("like en-US, de-DE, fr, or auto"): each would reach something the 1-4 October campaigns showed missing, and each is left until it is measured on its own.
  • Lengths a place states below a kind's own - a password allowed ten characters at most, a user name allowed eight - are not built to: the value is refused by the length, as every implied value too long for its place is, and what would have been sent anyway is sent.

Amendment (12 October 2026): no format keying for a file

Section titled “Amendment (12 October 2026): no format keying for a file”

Date: 2026-10-12

The format keying this record keeps for a user's own file is gone: since ADR-0033 a dictionary file is keyed by operation and place, and a value for a format the document declares is written under each place that declares it.