Skip to content

fix: correctives to the contract of standing grants — one expiry form, each thing said once, what six more reviews found, and D10 to D13 decided (greenhouse decisions/0615) - #85

Merged
rodrigoteamx merged 11 commits into
mainfrom
fix/standing-grant-expiry-is-strict
Oct 12, 2026

Conversation

@rodrigoteamx

@rodrigoteamx rodrigoteamx commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

What

Correctives to the contract of standing grants published as 0.30.0 and 0.30.1 (greenhouse decisions/0615), in one release. Eleven commits, one per round that asked for them and one for a test:

  1. expires_at is read in one form and no other (second review of the host).
  2. A signed grant says each thing once — a key said twice in one object is refused (third review).
  3. What a fourth review of this branch found — two findings of medium severity and nine of low.
  4. Three decisions of Rod on what that review left open (2026-10-12): every text a grant keeps is one line of plain text; an argument is named in letters, digits and underscores; a red receipt always counts.
  5. What a fifth review found, and three more decisions of Rod (2026-10-12): one finding of medium severity and seven of low. Nothing is trimmed; what cannot be seen is refused; a red receipt counts from any run; an argument is fixed to visible ASCII; a host says its record reads whole.
  6. The rule of commit 5 said by the property (decided by Rod, 2026-10-12): no code point Unicode calls Default_Ignorable is taken. Two still were.
  7. What a sixth review found: one finding of medium severity and five of low. A stop that cannot be read stops every grant that names the call; a record that does not read whole lends to no seat. Two of the low ones became rules Rod decided on 2026-10-12 (D11 and D12, below).
  8. A control of commit 7 that holds on older Unicode tables — a test only; nothing in src/ changes. Commit 7 was red in this repository's checks for it, and the reason is a fact worth having: see "What D9 depends on".
  9. What a seventh review found: one finding of medium severity — a question Rod answered on 2026-10-12 (D10, below) — and two of low. A line that was not read stops every grant that names the call; a name is given as text, never as an identity built elsewhere.
  10. What an eighth review found: one finding of medium severity — the same question, and the rule rebuilt from the closed side — and four of low. A refusal names the way out of its own kind of stop; a name is text whatever the caller's file declares; a text is read only if its own canonical spelling is.
  11. What a ninth review found — it PASSED — and what Rod decided after it: no finding of high or medium severity, three of low. What a run answered is asked for its type by the contract, as a name is; the words say which of the two doors answers a stop beside a call; two sentences of the README say what is so. And D10 to D13, decided by Rod on 2026-10-12: with them this contract has no open question of design.

Release order — read this before releasing anything else

This release refuses documents that 0.30.0 and 0.30.1 accepted, and it changes what a host has to say. Of the texts both read, each has the digest it had — except a grant whose seats, or a host line whose keys, look like numbers, whose canonical order is now the order of their bytes.

It has to be published BEFORE any host that reads a book of standing grants, and such a host has to require it. There is one reader for a new document and for one already signed, so a document an earlier version took and this one refuses is refused like text that was never a grant; a host treats that as a fault of its record.

GrantStanding no longer answers "yes" for a host that says nothing. state has no default, and recordFault defaults to "nobody checked": a host built against 0.30.1 lends nothing until it says both. StandingJudge::among() asks a third thing, with no default: whether everything the host keeps of standing grants reads; its answer may carry no grant. And a named call, a receipt and a call are built from the name of an operation as text, never from an OperationId made elsewhere. No published host builds any of these: milpa/app-runtime 0.220.2 has no judge.

The ninth review PASSED on d5a62fa — receipt PASS, 0 high, 0 medium, 3 low

d5a62fa is the first head to pass since 1d15c66: 0 high, 0 medium, 3 low (fixed in commit 11, below). By execution, in the reviewer's words: every DECIDED rule does exactly what was decided; the eighth review's M1 and L1 to L4 hold; the reach of a stop does exactly what its words say (0 deviations in 1,390 cells asked 198,770 times, and 69,984 more of two and three arguments, against an oracle that shares no code with the judge); and the head only narrows against 0.30.1 — for the reader (five corpora, 68,784 documents, none read by the head alone) and for the judge (600,000 seeded scenarios, no call lent by the head that 0.30.1 did not lend).

Two judgments carry that verdict. Rod weighed them on 2026-10-12 and decided (D10, below):

  1. The residue of the reach of a stop is counted as a STATED LIMIT, not as a finding. The rule compares values, and no rule that compares values closes it. Of 104 pairs of the reviewer's own — in a grant's spelling, of the same kind, and different — all 104 are lent, and 84 are ONE act under an ordinary line of a handler: 37 under the three readings this description named (capitals, a loose ==, the default ?:), and 47 only under readings it did not name — a flag given as a word ("yes", "on", and to if the word "false"), (int) of a text with a tail ("7abc"), a list in one text ("beta,stable" publishes to both), a pattern, a path, percent-encoding, a bit mask, a sign. And one walk that is no reading at all: a stopped grant fixes channel AND format; the call says another format and the same channel, and is outside the stop. "The class has no end, and the README is right to list nothing." Only alternative (B) closes it.
  2. Without the rule about the reach of a stop the verdict would be FAIL: the seventh review's M1 comes back whole. Without the rule about marks, or the one about escapes, the verdict does not change (one more low each).

The reviewer also says how the stated limit is paid, and that the README is not complete there: a host can hand the judge the arguments "as the operation reads them" only for what an operation DECLARES (its schema: types and defaults) — what a handler folds, splits or casts inside is declared nowhere; and the same arguments decide LENDING, so a host that folds what the operation does not fold lends a call the grant does not name.

A, B and C on the same scenarios — the numbers Rod decided with: (A) was taken

The reviewer's 300,000 scenarios weighted towards houses in order, with (B) and (C) emulated from the head's own judge:

(A) TAKEN — under doubt, a stop stops (B) not taken — a stop reaches the whole operation for that seat (C) not taken — as published: exact by spelling, and said
calls lent 19,775 18,351 20,972
calls answered "nobody's call" 144,404 125,638 167,723
what an ordinary reading still walks around the residue above: it has no end nothing leaving the argument out, null, the empty text, another kind: 196 cells of the eighth review's table, not 23
over-reach, on the eighth review's table 75 cells 136 cells 0
  • (C) lends 1,197 calls (A) refuses. (B) refuses 1,424 calls (A) lends, and 18,766 that (A) calls nobody's. (A) never lends what (C) refuses, and (B) never lends what (A) refuses.
  • 0.30.1 lent 23,013 of those calls. Of the 3,238 the head refuses, 1,305 are this rule (stopped_beside), 1,531 a red receipt from any run, 397 an instrument that changed, 5 not measured.
  • Where 0.30.1 said "nobody's call", the head now refuses 23,319 beside a stop: what a seat's own word might have decided.
  • (B) needs one more sentence nobody has written: whether a grant in force that covers the operation with nothing fixed is frozen too. By the rule as said, yes.
  • (C) leaves the contract's own sentence untrue as it stands: "a suspension that a second grant walks around is not a suspension".

Decided by Rod, 2026-10-12 — D10 to D13

Three rules were built by the narrow side through four rounds, waiting for him. Rod decided them after the ninth review passed, and with them the contract has no open question of design.

D10 · The reach of a stop: option (A), as it is built — under doubt, a stop stops

  • The finding it answers: "the grant names the call" was made for LENDING, where being exact fails closed. It also decided which calls a stop reaches, and there being exact fails open. A stopped grant fixes channel to stable; the seat calls with no channel, with an empty one, with "stable ", or with a number where the text is fixed; the stopped grant "does not name" that call. A wider grant lent it — or no grant did, and a host let the seat's own word decide. The published 0.30.1 has it.
  • The rule: a stopped grant — suspended, or its record not read whole — that lends to the seat and covers the operation refuses the call (stopped_beside). Only one thing is outside it: a call that says, beyond doubt, another value for something that entry fixes — a value of the same kind as the fixed one (text for text, a whole number for a whole number, true or false for true or false), written the way a grant can fix it (visible ASCII, single spaces inside, none around, never the empty text), and different.
  • Everything else is doubt, and stops: not saying it, null, the empty text, another kind (text for a number, a number for text, a word or a number for true or false, a number with a fraction), spaces around the value, a list, an object.
  • among() answers the stop beside the call whether or not another grant would lend it — it used to answer null, "nobody's call", where the stopped grant stood alone.
  • StandingJudge::mayBeNaming() is public, for a host to ask of a grant IN FORCE before a seat's own word decides — otherwise the seat leaves that grant's conditions behind by leaving out the argument it fixes. What a host does with a yes is its rule; the docblock says what it owes.
  • For lending nothing changes: a call is lent only under a grant that names it exactly.
  • Accepted with its residue said. An operation's own reading of a value is not reached, and no rule that compares values closes it: the ninth reviewer's figures are above. The docblock of StandingCall says what a host owes — the arguments as the operation READS them.
  • Accepted with its cost. In 300,000 scenarios (A) lends 19,775 calls where the reach as it is published — (C), with everything else of this head — lends 20,972. While a grant is stopped, a call over that operation by that seat is refused unless it says, in the grant's own kind and spelling, another value for one thing each of that grant's entries over the operation fixes.
  • The two alternatives that were NOT taken, with their numbers: (B) a stop reaches the whole operation for that seat, whatever the call says — it closes the whole residue and lends 18,351; (C) as published, exact by spelling, and said — it lends 20,972 and closes nothing.
  • The eighth reviewer's table, run as the control while it was being rebuilt (their 355 cells; a second probe of theirs confirms which source was loaded): of the cells that some ordinary reading takes for the fixed value, 146 walked around a stop before the rule was said from the closed side, and 23 after; 75 cells of over-reach appear. The ninth review confirmed those counts and says what they are worth: a property of that table, not of the rule.
  • Files: tests/Standing/AProposalOfASeventhReviewTest.php (it keeps the name it had); in StandingJudge, two ifs, mayBeNaming() and saysAnother(); NamedCall::fixable().

D11 · Stacked marks

A text that begins with a combining mark, or carries more than eight of them in a row, is refused. Controls: an accent written apart, Vietnamese, Thai, pointed Hebrew, Arabic with its vowels and Hindi are read.

D12 · Escapes

  • In a SENTENCE — an undo, a statement, a name — a \u escape that names what NAMES are made of is refused: a letter, a digit, the colon, the dot, the underscore, the dash, the space. What a writer that is safe for HTML escapes — the apostrophe, <, >, & — is read.
  • Inside arguments (names and values), inside to, and in expires_at, the escape of every printable ASCII character is refused: a value made of what a sentence is forgiven (*, /, <=) was written whole in escapes, shown as noise and read as the value.
  • What it costs: a writer that escapes the plus cannot write an expiry with a positive offset — it writes the same instant with Z —, and one that escapes <, &, the apostrophe or the quote cannot fix an argument to a value that holds one, nor name a seat that does.
  • The strict alternative was not taken: that second rule everywhere, in a sentence too. It costs the sentences of those writers.
  • Files: tests/Standing/TwoProposalsOfASixthReviewTest.php (it keeps the name it had); in Canonical, two constants, one line, one if and spellsWhatIsWrittenPlainly().

D13 · The grants that are gone, as they are

What the ninth review found (commit 11) — each red in this suite first

The reviewer's cases are in tests/Standing/WhatANinthReviewFoundInTheContractTest.php. Their file declared no strict_types, on purpose; this suite's files all declare it, so the caller in PHP's default mode is reproduced through reflection, as in commit 10.

# Finding What changed
L1 ok of a receipt is bool in the signature: from a file with no strict_types the text "false" — or "no", "failed", 2 — builds a GREEN receipt, and the call passes on it. The reviewer would fix this one before publishing the type is asked here, as it is for names: the constructor takes anything and throws TypeError for what is not a boolean. On d5a62fa, of twelve things that are not a boolean, seven built a green receipt, three a red one, and two were PHP's own TypeError
L1, the sibling the inverse a ceiling names is taken from an OperationId built elsewhere, from such a caller (EffectProfile::$rollbackContract, which a grant reads) EffectProfile takes anything for its rollback contract and throws TypeError for what is neither a text nor null
L2 for ONE stopped grant among() refuses (stopped_beside) and judge() answers "not named"; the docblock of among() said it "adds no criterion" fixed in the words, where a host reads them: the docblocks of judge() and of among(), StandingVerdict::named() and the first example of the README say that a stop beside is among()'s to answer and that a host asks among() also when it holds one grant. And a stop of the RECORD beside the call is filed under the signature check, as every fault of a record is — not under in_force. judge() itself is unchanged
L3 (a) the README said a call is refused "unless it says each fixed argument" "…unless it says, in the grant's own kind and spelling, another value for one thing each of that grant's entries over the operation fixes"
L3 (b) the README said "expired, revoked and superseded grants whose record reads stop nobody" "revoked and superseded grants whose record reads stop nobody; a grant past its own date stops nobody unless it is still suspended". The behaviour is unchanged, and Rod decided it stays (D13): a grant past its date that is still suspended keeps stopping

Built otherwise, or wider, than the line that named it — say so if it should change

  • The sibling of L1 is fixed in src/Effect, outside src/Standing. EffectProfile belongs to the whole family, and a grant can only read the text it was given: nothing inside src/Standing could ask. What it changes for the family, measured: nothing that runs — in the published family and in the host, the 13 places that give a ceiling a rollback contract hand it a text literal; the host's whole suite against this head is green, 4,636 tests, as it was against d5a62fa. What it costs: the parameter is no longer typed ?string in the signature, so a static checker no longer flags a caller that hands it a number. If only the words should change, it is one constructor.
  • The reviewer's own red test of L2 stays red by decision: it asks judge() to answer named() true for a stop beside. judge() was not to change; the test here pins what each door answers and the words that say so.
  • 41 older tests changed their pinned expectation: 40 cases that pinned in_force for every stop beside now expect the check of its kind, and one pinned the README's old sentence.

What the eighth review found (commit 10) — each red in this suite first

The reviewer's tests are in the suite: those of L1 and L3 as they were written in tests/Standing/WhatAnEighthReviewFoundInTheContractTest.php, and those of M1 (its eight cases) and L4 as they were written in the files of D10 and D12. The test of L2 is rewritten: theirs lived in a file that declares no strict_types, on purpose; this suite's files all declare it, so the caller in PHP's default mode is reproduced through reflection, whose call is made in that mode whatever a file declares (checked by execution: it builds the three things on baba41e).

# Finding What changed
M1 the reach of a stop is narrowed, not closed; and where what lends is the seat's own word, the answer is still null the rule rebuilt from the closed side — decided by Rod as D10, above
L1 the refusal stopped_beside names a way out that does not lead out when the stop is a record that does not read; and the README says "revoked and superseded grants stop nobody" the way out is said by the KIND of stop — "until what the house keeps of X reads again" —, and the README says "whose record reads"
L2 "a name is given as text" holds only for a caller that declares strict_types: from PHP's default mode an OperationId, being Stringable, turns itself into text the three constructors and named() take mixed and throw TypeError for what is not a text
L3 a text 48 bytes under the limit is read, and its own canonical spelling (49 bytes more) is refused a text whose canonical spelling is longer than the limit is refused when it is read; the README says a grant put together in code has no limit of its own
L4 a fixed value made of what a sentence is forgiven is written whole in escapes the rule adjusted — decided by Rod as D12, above

Built otherwise, or wider, than the line that named it — say so if it should change

  • The same defect as L1, one line above it, found beside the reviewer's. A grant that NAMES the call, suspended, whose record does not read either, was told "gives a grant that supersedes it by name"; a person gave it, and the unread record refused the call again. Such a grant is now told that its record has to read again. One older test built a suspended grant without saying its record reads; its fixture says so now.
  • A stop beside the call speaks before another grant's own refusal. Where a wider grant names the call and refuses it for its own reason (not measured), the answer used to be that refusal. It is the stop now, in either order: the order said "whether or not another grant would lend".
  • The list of texts that count as a boolean is gone. Commit 9 took "1" and 1 for true, and "0", 0 and "" for false. With the rule said from the closed side nothing is listed: any value of another kind than the fixed one is doubt.
  • An identity handed in is a TypeError, not a refusal — and now for every caller, whatever its file declares. OperationId itself is untouched for the rest of the family.
  • NamedCall::fixable() is new and public: whether a value is one an argument can be fixed to. The rule needs to ask it of what a call carries.
  • In the texts that are not sentences, all 95 printable characters, not 93: the quote and the backslash written as \u0022 and \u005c are refused there too.

Said, not built

The zero — the text "0" and the number 0, which ?: reads as nothing: the ninth review counts it inside the stated residue, and it is not built.

The eighth reviewer's notes that are not counted: the reason of a suspension is a seat's own sentence in the host as it was (for the host's round); one operation covered twice under two spellings, where the wider entry makes the narrower one a decoy — the note the reviewer would act on; matches(), covering() and namesOperation() still take any text and trim it (lenient is the safe side for asking whether a grant governs); ok: "false" builds a green receipt from a caller in PHP's default mode; Operation::actsOnDigest() trims; a receipt's who, arguments and at are kept as they came.

What the seventh review found (commit 9) — each red in this suite first

The reviewer's test for L1 is in tests/Standing/WhatASeventhReviewFoundInTheContractTest.php as written; its test for M1 is in the file of D10.

# Finding What changed
M1 a stop is as exact as a spelling: the same act, said another way, is lent by a wider grant decided by Rod as D10, above
L1 a line that was not read, in the list of receipts, refuses its own grant in judge() and stops nobody else in among() it stops the others, as a fault the host says does. Asked of the LIST, not of the reason the grant was refused for: a signature that does not hold is said first and would hide it
L2 the rule about escapes said "no writer makes one"; ordinary writers do the rule is narrowed and its sentence corrected (D12, above)
— an OperationId handed in ready-made has already lost what stood at its ends: new NamedCall(new OperationId(" lab:scan\n")) is built a named call, a receipt and a call are built from the name as text; an identity made elsewhere is not taken (TypeError). OperationId itself is untouched for the rest of the family
— StandingCall takes any text as its operation it names one operation, as it reads, or it is not built

Said, not built — as the coordinator asked

Operation::actsOnDigest() trims the digest an operation's own actsOn answers; a receipt's who, arguments and at are kept as they came; eight marks on every letter still pass; one operation may be covered twice under two spellings. They go in the acta as not covered.

What these three constructors no longer take

NamedCall::__construct(), ConditionReceipt::__construct() and StandingCall::__construct() took an OperationId (the first and the third, or a text). They take the text only. A host that built a receipt with new OperationId($name) hands in $name. Measured: the 105 operation names three houses list at their terminal are each "one operation, as it reads".

What the sixth review found (commit 7) — each red in this suite first

Four of the reviewer's tests are in tests/Standing/WhatASixthReviewFoundInTheContractTest.php as written, but for the third argument among() now asks for; its two for the marks and the escapes are in the file of D11 and D12.

# Finding What changed
M1 among several grants, one whose record does not read whole is refused alone, and the call passes under another it refuses the call whatever another would lend, as a suspension does; the sentence names it and says the other lends nothing meanwhile. A suspension that reads still speaks first
M1, the other half a document that no longer reads is no grant, so there was nowhere to say it among() takes a third argument with no default — why the host's whole record does not read, or null — and with a reason nothing is lent to any seat, whatever grants were handed in: the answer carries no grant and is never null
L1 four fillers that are blank by definition are taken (U+13441, U+13442, U+16FE4, U+1D159) refused, and in the README's table. Where the PCRE knows \p{DI}, the pattern names the property beside the list
L3 D8: the empty text is a value an argument may be fixed to it is not: at least one character
L4 the judge skips what is not a receipt, with a warning of PHP, and passes on the green before it it refuses, record_unread, whatever the thing is and wherever it is in the list
— a receipt's subject with a line break after it is another text nothing is trimmed from the two digests of a receipt: such a line is not a receipt, and one cannot be built

Said, not built — as the coordinator asked

One operation covered twice under two spellings; user_data:delete and user:data:delete being one operation (that is OperationId, older than this branch); the watcher, an instrument or the inverse being the covered operation; within_seconds at its maximum. They go in the acta as not covered.

Built otherwise than the smallest fix named — say so if it should change

  • L4 is asked beside the record fault, before the state is read, not inside the loop over the conditions: a list that holds what is not a receipt refuses even a call no condition would have read it for. GrantStanding's declared type for the list says what is true now: anything, held to receipts by the judge.
  • The property and the list. The pattern names \p{DI} when the PCRE knows it, and the list is kept whole. On these tables the property changes no answer — the list alone refuses the same code points — so the one thing a test cannot show here is the property refusing a character the list does not have. A test pins that the pattern names it, and that the list alone still leaves out no code point of the set.
  • M1's other half changes what among() returns: grant may be null. A host that read ['grant']->id has to look first.

Decided by Rod, 2026-10-12 — commits 5 and 6

  • D8 · An argument is fixed to a value a person can read, and no other. Text that is visible ASCII — ! to ~, single spaces inside, none around — or a whole number, or true or false. A fixed value is compared byte by byte with what a caller sends: strict with a zero-width space after it was another run, and a green receipt from the look-alike outvoted the red one from the run a person read. Cost accepted: an argument cannot be fixed to a value with an accent or a letter of another alphabet. The class says what a host owes: it shows a fixed value to whoever signs through Canonical::shown().
  • D5, complete · A red receipt counts from any run. A "no" of that instrument on that content stops the act whatever arguments the run carried — one more, the fixed one missing or at another value, none at all — until the content is measured again by the run the condition describes. Green stays closed. Another content, or another operation, says nothing of the condition. The README has the table.
  • D9 · A text a grant keeps carries nothing that is not seen. Kept: the letters of every language, accents, digits, punctuation, symbols, and the ordinary space inside a text. Refused when read: controls; every format character; the line and paragraph separators; every space that is not U+0020; the variation selectors and U+034F; the fillers that print as nothing; private use and unassigned places; bytes that are not text. The README lists exactly which characters, as measured.
    • The rule is said by the property, not by a list (commit 6): no code point of Unicode's Default_Ignorable_Code_Point set is taken. Commit 5 left two of that set as text — the Khmer inherent vowels, U+17B4 and U+17B5, by its own measurement the only two. They are refused, and a test asks every code point there is, with the Unicode tables of the PCRE the suite runs on as a second instrument: a character added to that set turns it red the day the tables know it.
    • The control the decision asked for: a grant whose texts are in Spanish with accents, ñ, ¿ ¡ « » and both kinds of quotes is read, and reads back with the digest it had. So are French, Hebrew, Arabic, Japanese, an accent written apart from its letter, and an emoji without a selector.
    • Measured on what the lab really holds, by execution: the sixteen standing books the cattle arms left — 28 signed grants, 67 receipts, 58 reasons and undo texts, 17 fixed text values. Read with this head: 27 grants, 67 receipts, 58 texts. The one grant refused is the fixture of an earlier finding (an expiry in a form 0.30.1 took), and it is refused the same way on 755c3ef; so is one line that is no line at all, left by the arm that nests an argument 509 levels deep. Nothing the lab uses is refused by D8, D9 or the end of trimming. Controls: the same reader refuses a lab grant once a zero-width space, a no-break space, a trailing space or a fixed value with an accent is put in it — and on 755c3ef it takes all four.

Three tests this suite pinned change, by decision

  • testTextThatIsOnlyTextIsStillTaken — a no-break space, a soft hyphen and a zero-width joiner were taken; they are refused (D9). What was blank around a text was trimmed off; the text is refused (L1).
  • testAnOperationNameEndsWhereItEnds — " lab:scan\n" was that operation; it names none now (L1).
  • testARedReceiptSpeaksOnlyForTheConditionItsRunFitsAndTheContentItMeasured, renamed …SpeaksForItsInstrumentAndTheContentItMeasured — a red receipt from a run in another mode than the one a condition fixes did not count; it does (D5).

Four more older tests changed only the words they expect: a name is said "builds" where it was «builds», and a fixed value is refused for D8's reason.

What the fifth review found (commit 5) — each red in this suite first

The reviewer's nine tests are in tests/Standing/WhatAFifthReviewFoundInTheContractTest.php as they were written, but for one name that ended in a space, which L1 now refuses.

# Finding What changed
M1 an argument fixed to strict + U+200B is accepted, and its run outvotes the run a person read D8, above
L1 trim() took a line break, a carriage return, a tab, a vertical tab or a null byte off both ends of nine fields nothing is taken off the ends of a text: the reader returns it as written and refuses one that is not as it reads, a space at either end included. One rule in the seventeen places a grant holds a text
L2 the judge's refusals repeat the name of a condition, of the falsifier, because, signatureFault and the content, raw and at any length see "built otherwise", below
L3 a line whose by names no operation is a receipt; and null for an unreadable line is the easy thing to skip by is held to the name of one operation. GrantStanding::$recordFault: a host says its record of the grant reads whole, or the judge refuses (record_unread); the default is that nobody checked
L4 a red receipt that lacks the fixed argument, or carries it at another value, is ignored D5 complete, above
L5 no limit to the text fromJson() reads; the text is decoded twice a text longer than 256 KB is refused before it is read; one scan of the text says both a key said twice and a list written as an object, and listsAreLists() is gone
L6 the default sort is still in Operation::instrumentDigest() and three classes of Effect/ SORT_STRING in the seven places a digest depends on an order
L7 the constructor takes what the reader refuses a grant built in code is held to the same: its inverse, its texts, its expiry to the second and in a year of four digits; a receipt built by hand names an operation
— state is the one default of GrantStanding that still says yes it has no default
— a rollback_contract under a compensatable ceiling is any sentence the name of one operation under any ceiling
— shown() cuts inside an escape it ends where a character ends

Built otherwise, or wider, than the line that named it — say so if it should be narrower

  • L2, the host's own sentences. The order was Canonical::shown() in the ten places. Eight of them — names and the content — are. because, signatureFault and the new recordFault go through Canonical::said() instead: repeated as they came when they are one line of plain text, up to 480 characters, and spelled out by shown() otherwise. A reason names what a person has to cite to lift a stop — the digest of the suspension — and shown() cuts at 96 bytes and writes « » as escapes: the digest would be gone.
  • L2, what shown() costs a name. A condition named with an accent is said back with its escape ("sin nombres de m\u00e1quina"). That is shown() as it was ordered: nothing a name carries can pass for this contract's own «…».
  • L2, two places the review did not list. The name of an operation is plain by its own pattern and has no length of its own: with a name of 100,004 bytes the judge's sentence was 200,213 bytes. A sentence says at most a line of it now (Canonical::short()), in the judge and in five refusals. And the effect profile had four raw echoes, not three.
  • L1, one more end. The identity of an operation took a separator off either end too: :lab:publish: was kept as lab.publish. Refused now — a spelling names an operation when its identity is that same spelling. How a surface spells the inside (lab:publish, lab.publish, lab_publish, capitals) is still one operation, as it is everywhere in this family.
  • L3 is asked under the Signature check, with its own reason record_unread: the seven checks stay seven.
  • D9, the variation selectors. The decision names two ranges; Unicode's own property has four more code points, the Mongolian free variation selectors (U+180B–U+180D, U+180F). They are refused with the rest.
  • D9, "every format character" is the whole category Cf, which includes a few that draw something: the Arabic number signs and their kin (U+0600–U+0605, U+06DD, U+070F, U+0890–U+0891, U+08E2) and the format controls of four historic and musical notations. Refused, as the decision says "every".

What D9 depends on — measured; nothing is decided by it

  • "Unassigned" is read from the Unicode tables of the PCRE the host runs. Here they are those of Unicode 16. A host with older tables refuses a letter added since; the README says so. A document that uses none of those is read the same everywhere.
  • This repository's own checks run on a PCRE with tables older than Unicode 15 — measured, by a red. The first head of commit 7 (8d4e307) failed there, in one test: its control said a hieroglyph next to the two blank ones «draws something, and is text», and that hieroglyph is as new as they are. For that PCRE the place is not assigned yet, and the text was refused — exactly what the paragraph above says a host with older tables does. Locally (PCRE2 10.47, Unicode 16) it was green. Commit 8 uses characters every table has had for many versions.
  • The pattern names code points; the test names the property. \p{DI} is not written in the pattern: a PCRE older than 10.40 does not know that name, PHP 8.3 builds against 10.30 and later, and a pattern that does not compile refuses every text there is. The test fails, saying so, on a PCRE that cannot be asked.

Decided by Rod, 2026-10-12 — commit 4, and two things commit 3 had built ahead of the decision

  • D7 · Every text a grant keeps is one line of plain text, or the grant is refused when it is read. D9 now extends it, and L1 ends the trimming it left.
  • D6 · An argument is named in letters, digits and underscores, starting with a letter, at most 64.
  • D5 · A red receipt always counts — complete in commit 5.

What the fourth review found (commit 3) — each red in this suite first

Nine of the tests are the reviewer's own, moved into tests/Standing/WhatAFourthReviewFoundInTheContractTest.php as they were written.

# Finding What changed
M1 an id followed by a newline is taken ($ without D), in three patterns the id of a grant, the name of an operation and the operation a ceiling names as its inverse end where they end
M2 the scan for a repeated key is quadratic: 458 KB → 4.3 s, 927 KB → 28 s a frame is asked for its name, never kept by a second hand: 458 KB → 0.02 s, 5 MB → 0.2 s. On the reviewer's 15,000 fuzz texts it answers as before: 15,000 the same, 0 differ
L1 the canonical order is PHP's default comparison: names that look like numbers give 2–3 digests for one grant sorted by the bytes of the keys (SORT_STRING), in the four places; the class says the order and that a float is written with the precision of its process
L2 a rollback_contract that is not text is dropped in silence: two documents, one digest a grant's ceiling is read as it is written, or refused — see below
L3 four expiries the reader takes have a canonical form it refuses (year 10000, year -0001) refused when read
L4 Canonical::saidOnce() is public and never returns on a text that does not parse it refuses what json_validate() refuses, before a key is read
L5 a refusal says back what the drafter wrote, raw and at any length said back as JSON spells it, in plain characters, at most a line (Canonical::shown()), in the 19 places a refusal quotes a document; the id is looked at before a sentence is built around it. Commit 4 closes one byte commit 3 left: DEL, which JSON does not escape
L6 a list may be written as an object with the keys "0", "1" refused: the shape is decided from the text
L7 the names of arguments are any text that is not blank D6, above
L8 the reader of a receipt answers null for a line it cannot read, and its docblock said «measures nothing» the docblock says what a host owes such a line. Nothing else changes here; the host's half is the host's
L9 a red receipt from a run with one argument more is ignored D5, above
— new GrantStanding() says the signature holds the default fails closed: a host says it verified it, or it is read as not having

Built wider than the line that named it — say so if it should be narrower

  • L2. The fix named was «refuse an inverse that is not text». The reviewer's own table has a third row — a ceiling that changes nothing, said «guaranteed», with and without an inverse: one digest — which that fix does not close. So a ceiling is refused whenever what the lenient reader of history would keep is not what was written: an inverse that is not the name of one operation in words, and an axis read as another (none + guaranteed reads not_applicable; guaranteed by a sentence reads manual_recovery).
  • L5. Refusals now spell what they quote as "budget" where they said «budget». A host that matches on the old spelling changes with it.
  • D7 reaches every text the reader keeps, not only name, statement and undo: also each seat, lifts, the text an argument is fixed to and the inverse a ceiling names. The reason is the same — a listing prints them — and none of the 432 texts measured is refused.

Reproduced before it was changed

  • On 0b71e04 (0.30.1): 18 of 20 forms of an expiry that are not ISO 8601 with an offset were accepted.
  • On 1d15c66: a document that says expires_at twice was accepted and read with no expiry.
  • On d7d438a, in one run with the sources as they were pushed: 66 of the 73 tests the third commit added were red.
  • On 07e302d, in one run with the sources as they were pushed: 42 of the 43 tests the fourth commit adds or changes are red.
  • On d5a62fa, in one run with the final tests as written: 61 of the 1003 tests commit 11 ends with are red — 20 of the 22 of the ninth review's file (the other 2 are its controls), and the 41 older ones whose pinned expectation changed.
  • On baba41e, in one run with the final tests as written: 95 of the 981 tests commit 10 ends with are red.
    • 64 are red for what that head does: 28 of the 40 spellings the rule is asked walk around the stop, the reviewer's 8, the 15 of the eighth review's file, 10 for the adjusted escapes, and 3 for the second half, the order of two stops and what the docblocks owe.
    • 12 are red only because the sentence of the refusal changed: those calls were stopped on that head already.
    • 19 are errors for a method that head lacks: NamedCall::fixable() (17) and mayBeNaming(), which was private (2).
  • On 4177ab8, in one run: 36 of the 901 tests commit 9 ends with are red — the 8 of the seventh review's file, 15 of the 27 of the file of D10 (the other 12 are its controls), and 13 for the narrowed escapes. That run hands a receipt an identity in five places of the older tests, as that head asks, so that each red is red for its own assertion; as written 124 are red, most of them errors for a receipt built from a text.
  • On c7ad91f, in one run with the sources as they were pushed: 26 of the 843 tests commit 7 ends with are red — 17 of the 19 the seventh commit adds for the review, 8 of the 21 for the marks and the escapes (the other 13 are their controls), and the one older test whose expectation changes.
  • On e0bf655, in one run: the three tests commit 6 adds or widens are red — U+17B4 and U+17B5 each accepted in a text, and the test over the property naming exactly those two.
  • On 755c3ef, in one run with the sources as they were pushed: 221 of the 800 tests commit 5 ends with are red — 214 of the 311 the fifth commit adds, and the 7 older ones whose expectation changes. The other 97 of the new file are controls, 90 of them ends of a text that a pattern already held.
    • That run has the two things the head does not have yet taken out of the test helper (recordFault, LONGEST), so that each red is red for its own assertion. As written, 278 are red, 83 of them errors for a parameter, a constant or a method that head lacks.

Measured on this head

  • 1003 tests, 5017 assertions; line coverage 100% (1492/1492). PHPStan, code style, docblock and attribution gates pass.
  • Mutants (greenhouse scripts/lab/standing-grant-1194/mutants-command.json): 327, each caught, with the instrument's two controls — the 319 from before and 8 new for commit 11
    • The first run of commit 10 left one survivor, and it has its test now: that a stop which NAMES the call speaks before a stop beside it was said in a docblock, and asked by a test in which nothing stood beside the call. A test with one stop of each kind, in both orders, tells them apart
    • The first run of commit 9 left one survivor, and it is gone with a reason: the anchor of the pattern for an operation can no longer be reached by any input, now that a name is text and is compared with its identity before the pattern is asked. Five mutants that aimed at the old bounds of the rule about escapes were replaced by seventeen for the narrowed rule.
    • The first run of commit 7 left fourteen survivors, and twelve were one test that asked its question where another rule answered first. The test that asks the list alone for every Default_Ignorable code point asked each one by itself — and a mark by itself is refused for beginning the text (the rule that is now D11). Twelve mutants of the list hid behind that. It asks each between two letters now. The other two were conditions that could not be told from their absence; one is gone, and the other's mutant asks what can be told.
    • The first run of commit 5 left ten survivors, and six of them were a test that had stopped testing. The fourth round's test of what a refusal says back used a 300 KB payload; the new 256 KB limit refused it first, and the test went on passing for that reason alone. It uses 100 KB now and asserts the refusal is not the one for length.
    • Three more asked for an assertion on which refusal speaks, and have it. Four pieces could not be told from their absence and are gone: a clause for a blank inverse and its mutant, a category in the pattern (a surrogate has no spelling in UTF-8), a condition on which bracket closed, and a check a later one made redundant.
  • Exactly what D9 refuses, asked of all 1,112,064 code points through the public reader (not by reading the pattern): 957,526 are refused — 819,533 unassigned, 137,468 private use, 170 format, 65 controls, 16 spaces, 2 separators, and 272 by code point. A second instrument, the Unicode properties of the same PCRE, agrees where they overlap: no variation selector is taken, and no Default_Ignorable code point is.
  • The explicit list of format characters the README prints was checked against the category: put in the pattern in its place, the suite was green and the same code points were refused (asked on commit 5).
  • Against d5a62fa, on five reviewers' corpora (68,784 documents): commit 11 changes no line of the reading. It does not touch how a document is read. The ninth review read the same 68,784 under 0.30.1 and under d5a62fa: none read by the head alone.
  • Against the published 0.30.1, on the four reviewers' corpora (40,529 documents, read by a reviewer's own reader): the head only narrows. None is accepted only by the head; 11,093 that 0.30.1 took are refused; the 33 digests that moved are the order of seats that look like numbers.
  • Against baba41e, on the same 40,529: commit 10 refuses 16 documents that head took, accepts none it refused, and moves no digest. 15 write, as an escape, a character of a text that is not a sentence (D12); 1 is the document whose own canonical spelling is longer than a grant is (L3).
  • The seven spellings of one act (greenhouse scripts/lab/standing-grant-1194/a-stop-and-a-spelling.php, two controls): a wider grant lent seven on 4177ab8, five on baba41e, and lends one on this head — capitals.
  • Against 4177ab8, on the three corpora there were then (24,740 documents): commit 9 changes three documents and moves no digest. The three are a sentence with an apostrophe, with < and with & written as escapes — the reviewer's own examples — which the narrowed rule reads again. Nothing else is accepted or refused otherwise. (The rule about a stop changes verdicts, not documents.)
  • Against c7ad91f, on the fifth and sixth reviews' corpora (10,741 documents): commit 7 only narrows. None is accepted only now; no digest moves; 178 that c7ad91f took are refused — 127 for one of the four fillers, 14 for marks (D11), 26 for an escape (D12), 11 for an argument fixed to the empty text.
  • The sixteen lab books read as before: nothing the lab uses is refused by this commit, D11 and D12 included.
  • The family's form gate (greenhouse decisions/0598) on a house of the published family with this head laid over as its archive: green.
  • The host under review (feat: the house judges a standing grant before a seat's act (greenhouse decisions/0615) app-runtime#797, local, not pushed) adopted commit 10 and added the rule the contract leaves to a house: a grant in force that may be naming a call governs a seat that holds the word on its own — decided by Rod, narrow (D10 and D13). Measured against d5a62fa: its whole suite, its 240 mutants, both cattle arms (54 and 154 expectations). It already hands a receipt a strict boolean; commit 11 asks nothing new of it.

Not measured

  • Other PHP versions than 8.3 locally; CI runs 8.3 and 8.4.
  • A PCRE with older Unicode tables than this one (10.47): what it refuses of the newest letters is described, not run.
  • The names gate of the family on a house with this candidate (C10 runs both before a release).
  • The host's whole suite and its cattle arms against this head: they run in the host's round.

Not to be released from here.

… (greenhouse decisions/0615)

`expires_at` was read by whatever reads as a date. So a grant could carry
a date relative to the moment of reading: another instant each time, and
so another grant than the one a person signed. It was written, and then
read as not honoured, for a digest that no longer matched. A day that does
not exist was read as the days after it. A date with no offset was read in
the zone of whichever process asked.

It is now a date and time with its offset, in ISO 8601, and nothing else:
the date, a `T`, the time to the second, then `Z` or the offset as hours
and minutes with its sign. Anything else is refused with the sentence this
contract already had. What a grant writes back is unchanged.

No house holds a grant yet, because no published runtime writes one: no
stored document changes its reading.

Found by a second independent review of getmilpa/app-runtime#797, and
reproduced in this suite before the change: 18 of 20 forms that are not
that one were accepted.
…host owes it (greenhouse decisions/0615)

A JSON text may say a key twice, and a reader takes the last one and says
nothing of the first. For a document a person signs, that is a way to show
one thing and mean another: an expiry and then none, one seat and then
another. Measured on this branch: such a document was accepted, and read
with no expiry.

A standing grant whose text says a key twice in one object is now refused,
naming the key and the object it is in — at every depth, and however the
key is spelled.

Two things are said in the classes and changed in nothing else. The
canonical spelling does not give every value back: a host that writes a
line with it reads the line back before it trusts it. And there is one
reader for a new document and for one already signed: what an earlier
version of this contract accepted and this one refuses is refused like
text that was never a grant, so a host that keeps signed documents treats
one it can no longer read as a fault of its record, never as a grant that
is not there.

Found by a third independent review, with execution, of this branch at
1d15c66. Decided by Rod on 2026-10-12: one release of the contract.
@rodrigoteamx rodrigoteamx changed the title fix: a standing grant's expiry is one form, the same at every reading (greenhouse decisions/0615) fix: a standing grant's expiry is one form, and a signed grant says each thing once (greenhouse decisions/0615) Oct 12, 2026
…is long, and a refusal says back only what is safe to read (greenhouse decisions/0615)

A fourth independent review, with execution, of this branch at d7d438a:
two findings of medium severity and nine of low. Each was red in this
suite before it was changed; nine of the tests are the reviewer's own.

A name ends where it ends. The pattern for a grant's id, for the name of
an operation and for the operation a ceiling names as its inverse ended
in `$`, which also matches before one last newline: a grant that said it
superseded "release-line" and a newline was taken, and named no grant.

The scan for a key said twice costs what the text is long. It kept each
open object by a second hand, so every key copied every key said before
it: 458 KB held the reader for 4.3 s, 927 KB for 28 s. It now answers the
same on the reviewer's 15,000 texts, and 5 MB in 0.2 s. It also answers
for any text: one that does not parse is refused before a key is read,
where the scan used to walk it and never come back.

The canonical order is the order of the bytes. Sorted the way PHP compares
what looks like a number, seats "7" and "007" gave one grant two digests.

A grant's ceiling is read as it is written, or refused: an inverse that
is not the name of one operation, and an axis the lenient reader of
history would change, used to be dropped or changed in silence. An expiry
whose own spelling in UTC is not the one form is refused when it is read.
A list written as an object with the keys "0", "1" is refused.

A refusal says back what a drafter wrote as JSON spells it, in plain
characters and at most a line of it: said back raw, a key could clear the
line it was printed on and leave "OK: ... is in force" in its place. The
id is looked at before a sentence is built around it.

A standing that says nothing of the signature fails closed. The reader of
a receipt says what a host owes a line it can no longer read.

Two things are built as the narrowest option and wait for a person's
decision: a red receipt is read open (a run that carries one argument
more than the condition describes still says no), and an argument a
grant fixes or leaves free is named in letters and underscores — the
narrowest alphabet that refuses none of the 76 names measured on the
published catalogue.
@rodrigoteamx rodrigoteamx changed the title fix: a standing grant's expiry is one form, and a signed grant says each thing once (greenhouse decisions/0615) fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what a fourth review found (greenhouse decisions/0615) Oct 12, 2026
…ent is named in letters, digits and underscores (greenhouse decisions/0615)

Three decisions of Rod on 2026-10-12, on what the fourth review left open.

Every text a grant keeps is one line of plain text, and one that is not is
refused when it is read: no line break, no tab, no other control
character, none of the characters that turn or isolate the direction text
is read in, no line or paragraph separator. It was the refusal that read
as "OK", taken to a grant that is accepted: a condition named so that a
judge's own sentence cleared the line a terminal printed it on. It holds
for how an act is taken back, for the name and the statement of a
condition and of the falsifier, for each seat, for the suspension a grant
lifts, for the text an argument is fixed to and for the inverse a ceiling
names — in a document and in a value built by hand. Measured on what the
labs and the suites write in a grant: 432 texts, none refused.

An argument is named in letters, digits and underscores, starting with a
letter, at most 64 of them: the alphabet built before had no digits, and
an argument named sha256 could not be fixed.

A red receipt always counts: it is read open, as it was built. Nothing
changes there but the words that called it a proposal.

Saying text back in a refusal left one control character as it came —
DEL, which JSON does not escape. It is written as its escape now.
…t counts from any run, and a host says its record reads whole (greenhouse decisions/0615)

What a fifth review found in this branch, and three decisions of Rod on
2026-10-12 about what it left open.

Decided by Rod:

An argument is fixed to a value a person can read, and no other: text
that is visible ASCII, single spaces inside, none around — or a whole
number, or true or false. A fixed value is compared byte by byte with what
a caller sends, so "strict" with a zero-width space after it was another
run: the run a person read said no, and the look-alike said yes. It costs
that an argument cannot be fixed to a value with an accent.

A red receipt counts from any run. A "no" of that instrument on that
content stops the act whatever arguments the run carried — one more, the
fixed one missing or at another value, none at all — until the content is
measured again by the run the condition describes. Green stays closed.
Another content, or another operation, says nothing of the condition.

A text a grant keeps carries nothing that is not seen. Besides controls
and the direction controls: every format character, every space that is
not U+0020, the variation selectors, the fillers that print as nothing,
private use and unassigned places. Letters of every language, accents and
punctuation are kept. The README lists exactly what is refused, as
measured over every code point; nothing the lab's sixteen books hold is
refused by it.

Found by the review:

Nothing is taken off the ends of a text. A reader that trims repairs: nine
fields took a line break, a tab or a null byte at either end. A text that
is not as it reads is refused, in all seventeen places a grant holds one —
and so is the name of an operation with a separator at either end, which
its identity took off the same way.

What the judge says is bounded and plain. A condition, a falsifier and
the content are named as JSON spells them, at most a line; a sentence the
host hands in is repeated as it came when it is one line of plain text,
up to 480 characters, and spelled out otherwise. The effect profile's
refusals say back what they quote the same way. One place the review did
not list: the name of an operation is plain by its own pattern and has no
length of its own — a hundred thousand letters are one name — so a
sentence says at most a line of it too.

A line whose "by" names no operation is not a receipt. And a host now
says its record of a grant reads whole (GrantStanding::$recordFault), or
the judge refuses: skipping a line that cannot be read was the easy thing
to do, and a red receipt skipped leaves the green before it speaking.
Where the grant stands has no default any more.

A text longer than 256 KB is refused before it is read, and one scan of
the text says both a key said twice and a list written as an object.

Scopes, rules and the parts of a certificate are sorted by their bytes
wherever a digest depends on the order.

A grant built in code is held to what a grant read from its text is held
to: its inverse, its texts and its expiry. An inverse is the name of one
operation under any ceiling.

Three expectations this suite pinned change, by decision: a no-break
space, a soft hyphen and a zero-width joiner were taken; what is blank
around a text or a name was trimmed off; and a red receipt from a run in
another mode than the one a condition fixes did not count.
@rodrigoteamx rodrigoteamx changed the title fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what a fourth review found (greenhouse decisions/0615) fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what a fourth and a fifth review found (greenhouse decisions/0615) Oct 12, 2026
…t a grant keeps (greenhouse decisions/0615)

Decided by Rod on 2026-10-12, as part of "what is not seen is refused":
the rule is said by the property, not by a list. No code point of
Unicode's Default_Ignorable_Code_Point set is taken.

Two of that set were still taken — the Khmer inherent vowels, U+17B4 and
U+17B5, the only two of it this reader read as text. They are refused now.

A test asks every code point there is, through the reader, with the
Unicode tables of the PCRE the suite runs on as a second instrument: a
character added to that set turns it red the day the tables know it. Of
the 1,112,064 code points, 957,522 are refused.

The pattern names the two by code point and not the property by its
name: a PCRE older than 10.40 does not know that name, and a pattern
that does not compile refuses every text there is.
…, and a record that does not read whole lends to no seat (greenhouse decisions/0615)

What a sixth review found in this branch: one finding of medium severity
and five of low. Two of the low ones are built as proposals, not decided.

A stop that can no longer be read was not walked around by one grant —
and was by two. Among several grants that name a call, the one whose
record does not read whole was refused alone, and the call passed under
another: the line that cannot be read may be the one that stopped it. It
refuses the call now whatever another grant would lend, as a suspension
does.

And the other half of it: a document a host kept and can no longer read
is no grant it can hand in, so there was nowhere to say it. among() asks
a third thing, with no default: whether everything the host keeps of
standing grants reads. When it does not, nothing is lent to any seat,
whatever grants were handed in.

The judge no longer skips what is not a receipt. A list built by mapping
the reader over the lines holds a null for a line that cannot be read;
the judge went on, with a warning of PHP, on the green before it. It
refuses.

Nothing is trimmed from the two digests of a receipt either: a subject
with a line break after it was another text, and a red receipt spelled
that way said nothing of the content it measured.

An argument is not fixed to the empty text, which is no character at all.

Four more fillers that are blank by their own definition are refused:
U+13441, U+13442, U+16FE4, U+1D159. And where the PCRE knows Unicode's
property for "shown as nothing" by name, the pattern names it beside the
list, so that a character added to that set is refused the day a house
runs on tables that know it.

PROPOSED, NOT DECIDED — each a small change that can be taken out:

A text does not begin with a combining mark, and carries no more than
eight in a row. Three hundred marks on one letter are drawn over the
lines around it; ordinary writing stacks at most four or five.

What a person signs spells a plain character plainly: a \u escape that
names a printable ASCII character other than the quote and the backslash
is refused. An operation written entirely in escapes was shown as escapes
and read as its name, with the digest of the plain text.

Two expectations this suite pinned change: the empty text was a value an
argument could be fixed to; and a key was "spelled two ways" with an
escape of a plain letter, which the second proposal refuses first — it
is spelled with a letter a writer does escape now, and holds either way.
@rodrigoteamx rodrigoteamx changed the title fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what a fourth and a fifth review found (greenhouse decisions/0615) fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what a fourth, a fifth and a sixth review found (greenhouse decisions/0615) Oct 12, 2026
…ables (greenhouse decisions/0615)

The test that four fillers are refused had a control beside it: their
neighbours draw something, and are text. Two of those neighbours are
hieroglyphs as new as the fillers themselves (Unicode 15). The checks of
this repository run on a PCRE whose tables are older: for it those two
places are not assigned yet, and a text that carries one is refused — as
the contract says a host with older tables does.

The control uses characters of the same scripts that every table has had
for many versions. Nothing in src/ changes.
…and a name is given as text, never as an identity built elsewhere (greenhouse decisions/0615)

What a seventh review found in this branch: one finding of medium
severity, which is a question for whoever decides, and two of low.

A line that was not read, found in the list of receipts, refused its own
grant and nobody else's. The judge said of a grant that what the house
keeps of it does not read whole, and among several grants the call passed
under another. It stops the others now, as a fault the host says does —
asked of the list, not of the reason the grant was refused for: a
signature that does not hold is said first, and would hide it.

A name is given as text. An identity built elsewhere has already had its
ends taken off, and nothing can ask for them back: a named call and a
receipt could be built from one where the same text was refused. They are
built from the text now, and so is a call, which took any text as its
operation.

PROPOSED, NOT DECIDED — each a small change that can be taken out:

Under doubt, a stop stops. "The grant names the call" was made for
lending, where being exact fails closed; it also decided which calls a
stop reaches, and there being exact fails open. A stopped grant fixes an
argument, a wider grant is in force, and the seat calls without that
argument: the stopped grant "does not name" the call and the wider one
lends it. With the proposal a stopped grant that lends to the seat and
covers the operation refuses a call another grant would lend, unless the
call says another value for something that entry fixes. Not another: the
argument is not there, or is null; a number and the text of its digits; a
boolean and the texts PHP and JSON write it as. Capitals, a space around
a value and words for yes and no are not set aside. This is older than
this branch: the published version has it.

The proposal about escapes is narrowed, and its sentence corrected. It
said no writer makes such an escape; writers that are safe for HTML do —
the apostrophe, the angle brackets, the ampersand — and their documents
were refused. It refuses now only the escape of what names are made of:
letters, digits, the colon, the dot, the underscore, the dash and the
space. The strict alternative is written beside it for whoever decides.

The proposal about combining marks is as it was.

One mutant is gone: the anchor of the pattern for an operation can no
longer be reached by any input, now that a name is compared with its
identity before the pattern is asked.
@rodrigoteamx rodrigoteamx changed the title fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what a fourth, a fifth and a sixth review found (greenhouse decisions/0615) fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what four more reviews found (greenhouse decisions/0615) Oct 12, 2026
…ext for every caller, and a text is read only if its own spelling is — and the reach of a stop is proposed from the closed side (greenhouse decisions/0615)

What an eighth review found in this branch: one finding of medium
severity, which is the same question for whoever decides, and four of
low.

FIXED

A stop beside a call named a way out that did not lead out. When the stop
was a record that does not read, the refusal asked for a grant that
supersedes the stopped one by name; a person gave it, and the same
refusal came back. The way out is said by the kind of stop now: a record
is out of the way when it reads again. The same held one line above, for
a grant that names the call, suspended, whose record does not read
either: it is told the same. And the README says that expired, revoked
and superseded grants stop nobody when their record reads.

A name is given as text for every caller. `strict_types` belongs to the
caller's file: from PHP's default mode an OperationId, being Stringable,
turned itself into text before the constructor looked. A named call, a
receipt, a call and `named()` take anything and throw a TypeError for
what is not a text.

A text is read only if its own canonical spelling is. That spelling says
`supersedes`, `lifts` and `expires_at` also when the text left them out,
so a text 48 bytes under the limit was read and its spelling refused.

PROPOSED, NOT DECIDED — each a change that can be taken out

Under doubt, a stop stops — rebuilt. It listed what does not say another
value, and the review found 146 of 355 spellings still walking around a
stop: listing what PHP reads as one value never ends. The rule is said
from the closed side now. A stopped grant that lends to the seat and
covers the operation refuses the call, unless the call says beyond doubt
another value for something that entry fixes: a value of the same kind as
the fixed one, written the way a grant can fix it, and different.
Everything else is doubt. Of the same 355, 23 stay open, each an
operation's own reading of a value: capitals, a text PHP compares as the
same number, and a zero that `?:` reads as nothing; 75 are refused that
no reading takes for the fixed value.

Its second half: the stop beside a call is answered whether or not
another grant would lend it, where the answer used to be `null` and a
host let the seat's own word decide; and `mayBeNaming()` is public, for a
host to ask of a grant in force before that word decides. For lending
nothing changes. `StandingCall` says what a host owes: the arguments as
the operation reads them.

Plain letters as escapes — adjusted. In a sentence it is as it was. Where
a text is not a sentence — a seat, the expiry, the name and the value of
an argument — the escape of every printable ASCII character is refused: a
value made of what a sentence is forgiven was written whole in escapes.
It costs a writer that escapes the plus an expiry with a positive offset;
it writes the same instant with Z.

Stacked marks: as it was.
@rodrigoteamx rodrigoteamx changed the title fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what four more reviews found (greenhouse decisions/0615) fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what five more reviews found (greenhouse decisions/0615) Oct 12, 2026
…ich door answers a stop beside a call — and what were three proposals are decided (greenhouse decisions/0615)

What a ninth review found in this branch. It passed: no finding of high
or medium severity, three of low.

What a run answered is asked for its type by the contract, as a name is.
`ok` was typed `bool` in the signature, and `strict_types` belongs to the
caller's file: from PHP's default mode the text "false" — or "no",
"failed", the number 2 — built a GREEN receipt, and the call passed on
it. A no that was said was kept as a yes. The constructor takes anything
now and throws a TypeError for what is not a boolean. Its sibling: the
inverse a ceiling names was taken from an identity built elsewhere, from
such a caller. An effect profile asks whether its rollback contract is a
text; it is the one change outside src/Standing, and every caller in the
family hands it a text already.

The words say which of the two doors answers a stop beside a call. For
one stopped grant whose fixed argument the call leaves out, `among()`
refuses and `judge()` says «not named», while the docblock of `among()`
said it adds no criterion. `judge()` is unchanged. Its docblock, the one
of `among()`, the verdict's and the first example of the README now say
that a stop beside is `among()`'s to answer, and that a host asks
`among()` also when it holds one grant, with every grant that lends to
the seat and covers the operation. And a stop of the record beside the
call is filed under the signature check, as every fault of a record is.

Two sentences of the README say what is so. The cost of a stop: a call is
refused unless it says another value for one thing each entry of the
stopped grant over the operation fixes — not «each fixed argument». And
the grants that are gone: revoked and superseded ones whose record reads
stop nobody; one past its own date stops nobody unless it is still
suspended. That behaviour is not changed here.

DECIDED BY ROD, 2026-10-12 — D10 to D13. What were three proposals are
rules, and no behaviour changes with it: the source, the README and the
tests say «decided» where they said «proposed».

D10. The reach of a stop: under doubt, a stop stops. Only a call that
says, beyond doubt, another value for something the stopped grant fixes
is outside it — of the same kind, in a grant's own spelling, and
different. `among()` answers the stop whether or not another grant would
lend, and `mayBeNaming()` is the host's to ask. Decided with its limit
said — an operation's own reading of a value is not reached, and no rule
that compares values closes it — and with its cost. A stop over the whole
operation, and a stop exact by spelling as published, were not taken.

D11. A text that begins with a combining mark, or carries more than
eight in a row, is refused.

D12. In a sentence the escape of what names are made of is refused;
inside `arguments`, inside `to` and in `expires_at`, the escape of every
printable ASCII character.

D13. The grants that are gone, as they are: one past its own date that
is still suspended keeps stopping until a new grant names it.
@rodrigoteamx rodrigoteamx changed the title fix: correctives to the contract of standing grants — one expiry form, each thing said once, and what five more reviews found (greenhouse decisions/0615) fix: correctives to the contract of standing grants — one expiry form, each thing said once, what six more reviews found, and D10 to D13 decided (greenhouse decisions/0615) Oct 12, 2026
@rodrigoteamx
rodrigoteamx merged commit 31bc32f into main Oct 12, 2026
4 checks passed
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.

1 participant