Repository navigation
test: the json and text families check each obligation (WS2 step 4) - #57
Merged
Merged
Conversation
Every probe of the json and text families (C01, C02, C10, L01 to L04, L06) and the generation and option-result probes whose harness is theirs (C09, C20, L05) runs one Row.Check per obligation its row reaches, named by the claim's obligation id and given its baseline: the wrapped twin, the value object's own document read back, the documented refusal, or the docs sentence. Every excuse of outcome inapplicable, expected-refusal or inconclusive is a cited c.Inapplicable/c.Refused, a comparison, or a c.Inconclusive with its reason, except the text-matching sites, which stay and are marked. No NoteKeys member is removed; the pass keys stay as notes. The excuses that depend on the axes alone are declared on their obligation in claims.json (#50): L01's own converter for the value object, its byte array and its closed-set key under a key policy; L02's and L03's entries that are not generated (span parsing over a type that has none, a closed set's span parsing, parsing over a record class) and L02's own converters; L03's TryFrom of a closed set; L04's closed-set text entries, parsing over a record class, span parsing, and a date's type converter. A closed set parses by name (Parse(string), TryParse(string, out)), which TextOps now reaches, so its text entries are checked instead of skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…99's document is json-half:wrapped-alone-refuses (L03) and json:read-refused-by-stj-alone (C10, L06) are pass keys: the refusal is shown to be the framework's by the twin in the same row, as doc:refused-by-aspnet-alone is in the http family (#53). The first conversion ended those checks inconclusive, which lost 21 rows of L03 and L06 that passed on them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… outcome, and every text-matching site is marked L04's read back no longer notes plain-read-back-fails-too (expected refusal) before ending inconclusive, nor text:wrapped-round-trip-lossy-too (inapplicable) before its cited inapplicable: the outcome is the check's own call. OffTheWire's "not a wire type" and TextRow's Path cut are marked as the text-matching sites they are, with their baseline gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WS2 step 4, the json and text families: every probe whose claim's first surface is json or text (C01, C02, C10, C11, L01 to L04, L06), and the three probes whose harness is the family's (C09, C20 and L05, whose claims' first surface is generation or option-result), runs a
Row.Checkper obligation its row reaches, named by the claim's obligation id and given its baseline. Every excuse of outcome inapplicable, expected-refusal or inconclusive is a citedc.Inapplicable(Cite…)/c.Refused(Cite…), a comparison, or ac.Inconclusive(reason), except the text-matching sites this step keeps.prober statusshows 0 uncredited for all 12 claims (before: every obligation of the 11 converted claims uncredited; C11 was converted in #54), and every seed of every probe is caught by its own obligation's check.What changed
JsonParity(L01,Positions.cs). The write is one check against the wrapped twin,the-underlying-types-jsonor, where the value object is a dictionary key,as-a-dictionary-key-too. The read of the twin's document with the row'sinput.jsonisread-as-the-underlying-type-reads, against the twin. Before, a refused read voided the row's passing write; now the write passes beside it (160 rows regained). Under an application's converter for the value object, the read is that converter's own document read back (RoundTrip): "Your own converter wins" (AspNetCore README) in both directions.DecimalSpelling(C01). One write and one read of the row's shape, computed once, judged by six checks:smallest-scaleandnumber-is-a-number/string-is-a-stringagainst the docs' minimal spelling worked out here (Docs(docs/json.md#decimals-in-one-spelling)),covers-every-decimalagainst the plain decimal (keys, the value object's document, its read),reads-a-number-or-a-stringandkeeps-the-scale-it-reads.PreserveGuard(C10). A write check and a read check per row, under the obligation the row's handler and converters select: a converter of the application's (own-converters-unaffected), the guarded class under Preserve (refused-where-references-could-be-written, againstRefusal), IgnoreCycles (ignore-cycles-unaffected), otherwisevalue-converters-unaffected.OffTheWire(L06).never-writtenandnever-read, each againstRefusal, each with the application's own converter as its precedence baseline.TextClaims(L02, L03, L04,TextRow.cs). L04:invariant-whatever-the-culture(the generatedToString()against the wrapped type's invariant text, on every text row),the-type-converter-writes-the-same(the row's writing entry),text-reads-back(that text, or TryFrom's value, read back), andjson-refusal-is-a-json-exceptiontwice: the value object's document read back (RoundTrip) and the changed input refused (Refusal). L02:parsing-never-repeatsandno-inner-that-quotessplit what one comparison held, andjson-never-repeats. L03:tryfrom-runs-create,parsing-runs-create,json-runs-create(the read back, then the refused value) andrefusals-name-the-fault(FromKnownGood's, the FormatException's, the JsonException's).TextOpsnow reaches itsParse(string)andTryParse(string, out). Before,HasTextlooked only forParse(string, IFormatProvider), so every closed-set text row of L04 endedno-textand L02 and L03 skipped their parsing checks for a closed set. Those checks now run.NoteKeysis untouched. The families call no outcome key exceptstj-maxdepthat the text-matching sites below; the pass keys stay asc.Note.Commits:
f82f583(the conversion and the declarations),a4e0e7d(a JSON half the framework alone refuses is a pass, asdoc:refused-by-aspnet-aloneis: the first conversion had lost 21 rows of L03 and L06 on it),2002740(no outcome key noted beside a check's own outcome; the last two sites marked),a67ac5a(release/2.2.0 merged in, with #55).Obligation decisions
handler=none: checked asvalue-converters-unaffected, the control without a handler. No obligation's axes name it.input=word: checked as the blank obligation of its entry (required-blank-is-an-errororoptional-blank-is-no-value), as the control: a word is no blank. A null row runsnull-is-blankbeside it.text-reads-back(the value given back is the value given). L04's JSON round trip isjson-refusal-is-a-json-exceptionwithRoundTrip, the read back the refusal is measured from.tryfrom-runs-create, which L03 checks; L02 lists no obligation for it.through-every-baseruns only where the value object has a base record;object's andValueType's comparers are its own equality.covers-every-decimaland L01's read under the application's converter for the value object compare that converter's document read back (RoundTrip), which shows it wins in both directions.Before and after, per probe
Before: release/2.2.0 at
f8993e3, replayed from an empty state in a worktree of its own,replay --all --family json,textandbuild, with footprints: 16 passed. After: this branch ata67ac5a, the same three replays: 16 passed. A row that runs noRow.Checkcounts as one check, its row. "Rows passing" is whatRec.Runpasses: no violation or harness error in the row or any of its checks, and at least one check that passed. The build family's other probes (C17, C18, C19, C22) are unchanged and left out.Checks by obligation, after:
covers-every-decimalinconclusive 6, pass 111;keeps-the-scale-it-readsinconclusive 9, pass 108;number-is-a-numberinconclusive 13, pass 43;reads-a-number-or-a-stringinconclusive 9, pass 108;smallest-scaleinconclusive 13, pass 104;string-is-a-stringinconclusive 7, pass 54ignore-cycles-unaffectedexpected-refusal 4, pass 112;own-converters-unaffectedexpected-refusal 5, inapplicable 1, inconclusive 1, pass 237;refused-where-references-could-be-writteninconclusive 1, pass 7;value-converters-unaffectedexpected-refusal 16, inapplicable 2, pass 226as-a-dictionary-key-tooinapplicable 12, inconclusive 1, pass 28;read-as-the-underlying-type-readsexpected-refusal 27, inapplicable 17, inconclusive 180, pass 357;the-underlying-types-jsonexpected-refusal 6, inapplicable 107, inconclusive 11, pass 416json-never-repeatsinapplicable 238, inconclusive 69, pass 279;no-inner-that-quotesinapplicable 32, inconclusive 78, pass 249;parsing-never-repeatsinapplicable 32, inconclusive 12, pass 315never-readinapplicable 15, inconclusive 9, pass 301;never-writteninconclusive 29, pass 296hidden-is-an-error-in-bothpass 13;null-is-blankpass 6;optional-blank-is-no-valuepass 17;required-blank-is-an-errorpass 16json-runs-createexpected-refusal 9, inapplicable 40, inconclusive 29, pass 739;parsing-runs-createinapplicable 32, pass 327;refusals-name-the-faultinapplicable 10, pass 262;tryfrom-runs-createinapplicable 13, pass 61invariant-whatever-the-culturepass 512;json-refusal-is-a-json-exceptionexpected-refusal 10, inapplicable 93, inconclusive 160, pass 822;text-reads-backinapplicable 98, inconclusive 8, pass 480;the-type-converter-writes-the-sameinapplicable 31, inconclusive 15, pass 175abstract-string-constructorpass 22;implicit-resultpass 28;parsing-where-it-can-be-parsedinapplicable 4, pass 171;ref-textpass 22equal-by-the-wrapped-valuepass 101;in-a-collection-of-the-basepass 101a-failure-says-the-outcomepass 122;fails-unless-it-holds-that-reasonpass 233;hands-back-detail-and-rulepass 111;uninitialized-failspass 28;whichever-of-its-typespass 233equality-is-the-underlying-valuespass 309;ordering-follows-the-underlying-typepass 309;through-every-basepass 175Rows regained: 399. Every one ended expected-refusal or inapplicable before.
no-text, a closed set (its text is now read by name) or a record class (its JSON half now runs beside the cited text checks). The rest ended on one refusal of the JSON half; the text checks now pass beside it.read-both-threw: the read refused for the wrapped type too, which voided a passing write.Rows lost: 135 (L02 130, L01 5). All but one passed before on no comparison, by a silent
return null:json-never-repeatsunder an application's converter, the row's only check (TryFrom and the formatting entries have no parsing check), or beside parsing checks that are themselves cited or refuse nothing. The old JSON half returned null there, its comment namingnot-promised the-applications-own-converter. It is now that citation, declared in claims.json.stringorUritakes the secret as a value (aUrireads any text as a relative one). In 12, the document has no member to put the input in: the options leave it out ({}, a field withoutIncludeFields). Where the row has a parsing entry, it reads every text too. The old checks returned null with no rejection seen; they now end inconclusive, since there was nothing to quote.Apart from #363, no row that passed on a comparison fails or skips now. A pass key (
json:member-left-out-by-options) whose own comment says a row with both directions left out earns nothing is now an inconclusive check per direction, which loses no row: such a row was inapplicable before.prober status, the 12 claims, ata67ac5a:prober status, the 12 claims, at a67ac5a
Seeds
prober seeds --probe Pfor each of the 12 probes, one at a time: C01 and C10 ata4e0e7d, the others at2002740. The merge of #55 changed no product file and no corpus file, so no seed ran again. 44 seeds, 44 caught, each by the check of its own obligation: the detail's example names it. C11's four are the control: its checks are #54's, and onlyTextOpschanged under it.seeds output
Findings of this step, triaged
The conversion left 17 rows failing in
prober try, each triaged with.claude/skills/probe-triage. None is a product defect. No fact, docs sentence or not-promised entry came of them.json.MaxDepth=3): "value object threw JsonException: A possible object cycle was detected … maximum allowed depth of 3". The read now runs where the write was refused by depth, and it is refused by depth too. Triage 1: the write's text-matching site (the stj-maxdepth note) was not applied at the read. It is now, marked as the same site.json.MaxDepth=3), harness errors: "UNEXPECTED JsonException … maximum allowed depth of 3" in the read check. Triage 1: the read check re-serialized a document the write had already seen refused by depth. The read now ends on the same site's note.refusals-name-the-faultandjson-runs-create:NumberHandling=WriteAsString, Preserve or a renaming modifier): "JsonException does not name the fault: … could not read the JSON value as Int32". Triage 1: under these options the value object cannot read back its own document, and a converter of the application's for the wrapped type cannot either. The old check stopped there (json-half:wrapped-alone-refuses); the converted one went on to the refused value, whose refusal is then the document's, not Create's. The refused-value checks are now inconclusive where the read back had nothing to compare.Cite.Docs("src/CodoMetis.TypeKit.Generators/README.md#json").refusals-name-the-faultran although the twin refused too. It now runs only where the twin reads the text.Nothing reached step 4, so nothing went to the steering session.
Uncited excuses, and how each is converted
Every excuse below had no sentence, record or fact to cite, or was a silent
return nullwith the citation in a comment.own-converter-refused-for-wrapped-tooandown-vo:read-back-refused(L01): against a converter of the application's for the wrapped type at the same position. Both refuse: inconclusive; otherwise a violation.read-back-refusedunderUnmappedMemberHandling=Disallowor Preserve (L04): against the twin reading back its own document. Both refuse: inconclusive (wasplain-read-back-fails-too); otherwise a violation.NotSupportedExceptionor anInvalidOperationException: against the twin's read of the same text.e.Kind != "closed-set") and every missing entry (if (!back.NotApplicable)).JsonExceptionbefore they are cited.JsonException's message for the input; any exception's message is read now. Where the value object cannot be written, the twin's document places the input, as L01 holds the two the same.both-threw,read-both-threw,ser-both-threw,both-threw-differently(C01, C10, L01, L04, L06): both sides refuse;json:both-directions-left-out-by-options(C01, L06), now each direction the options leave out, thejson:member-left-out-by-optionsnote kept;plain-refused,refused-input-not-placeable,ser-refused(L03): the wrapped type is not written, the refused value has no place in the document;ISpanFormattable(L04,entry-not-applicablebefore): no TryFormat is generated, the wrapped type has none either.byte-array-is-base64,closed-set-key-unpoliced,preserve-documented,own-vo-converter-threw,no-textfor a record class,entry-not-applicable(span parsing, a closed set's surface),text:wrapped-round-trip-lossy-too, and the silent excuses whose comment named an entry. Each cites the not-promised entry or docs section in its comment, now as aCite.entry-not-applicablewas unreachable: the operation is substituted where the carrier has none. It is now the harness error it would be.stj-maxdepth, at the text-matching sites below.Declared applicability (#50)
Declared on the obligation (claims.json
inapplicable), the excuses the axes alone decide:the-underlying-types-json,as-a-dictionary-key-toojson.Converters=own-for-value-objectdocs:src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says("Your own converter wins")the-underlying-types-json,read-as-the-underlying-type-readsbyteatarray-elementnot-promised:a-byte-array-is-base64as-a-dictionary-key-too,read-as-the-underlying-type-readsdictionary-keyunder a key policy (json.DictionaryKeyPolicyother than the default)not-promised:a-closed-set-key-is-its-nameparsing-never-repeats,no-inner-that-quotesISpanParsableoverUri, an enum, a flags enum orNodaTime.Instantdocs:src/CodoMetis.TypeKit.Generators/README.md#what-is-generated("AUri, an enum and a NodaTime type have neither.")parsing-never-repeats,no-inner-that-quotesISpanParsableof a closed setdocs:src/CodoMetis.TypeKit.Generators/README.md#closed-setsparsing-never-repeats,no-inner-that-quotesown-record-classdocs:…#what-is-generated(IParsable"when the underlying type can be parsed")json-never-repeatsjson.Converters=own-for-wrappedorown-for-value-objectnot-promised:the-applications-own-convertertryfrom-runs-createdocs:…#closed-setsparsing-runs-createthe-type-converter-writes-the-sameToString-format,TryFormat-short-spanof a closed set; the type converter overown-record-classdocs:…#closed-sets;docs:…#what-is-generatedtext-reads-backISpanParsable,ToString-format,TryFormat-short-spanof a closed set; any text entry overown-record-class;ISpanParsableoverUri, enums,Instantdocs:…#closed-sets;docs:…#what-is-generatedtext-reads-backTimeOnly,DateTime,DateTimeOffsetnot-promised:dates-and-times-lose-detail-as-textC01, C02, C09, C10, C20, L05 and L06 declare nothing: no excuse of theirs depends on the axes alone.
The code keeps the same citations, for a row in hand without the claims (the prober's tests), as
HttpBaselinedoes. The declarations leave each claim's pair count as it was (universe): every declared pair is still wanted by another obligation of its claim.Kept in code, because the shape instance, what the serializer did, or a sibling check of the same obligation decides them:
NotSupportedException(L01, L03, L04);refusals-name-the-faultis not: it also judges the JSON refusal in the same rows.Text-matching sites, kept, each marked with its baseline gap
harness/Positions.cs:239(the old 237)maximum allowed depthunderjson.MaxDepth=3(notestj-maxdepth, uncited)harness/Positions.cs:268harness/JsonFamily.cs:62(the old 60)Jx.Depth, the same words, for C10 (PreserveGuard.cs:115,151,184,218)harness/TextRow.cs:285and294(the old 202 and 214)stj-maxdepthnote)harness/TextRow.cs:374(the old 266, inQuotes)JsonExceptionis read up to" Path:", since System.Text.Json's Path holds a rejected dictionary key (not-promised:a-rejected-dictionary-key-in-the-path)harness/OffTheWire.cs:67(the old 63)NotSupportedExceptionis TypeKit's by its words, "not a wire type"NotSupportedExceptionis the same type, and no twin refuses as the serializer alone doesThe other message reads of these files are comparisons, not excuses: a refusal held to its documented name (
Baseline.Refusal: L03's faultRefused, L04's value object named at the root, C10's wrapped type andReferenceHandler.Preserve,ReadAlike.Named), and two refusals compared message for message with the twin (json:read-refused-by-stj-alone).Not-promised entries, facts and docs
None added. Every citation is to an entry, a docs section or a fact that exists: KB-038 (C10's base64 read), L01's
a-byte-array-is-base64from #53, and the Generators, AspNetCore and docs sections named above.Verdicts
Through
build-verdict, ata67ac5a:test --project test/CodoMetis.TypeKit.Prober.Tests: TESTS OK: 257 passed, 0 skipped. At2002740, before the merge brought WS2b: a cited fact is executable, fixtures beside fact tests, seeds by footprint #55's tests, one run failedToolchainTests.A_dotnet_without_the_pinned_sdk_falls_back_to_the_installerswith aNullReferenceException(244 of 245 passed). That test starts processes, and it failed so in test: the http family checks each obligation (WS2 step 4) #53 too, under load; at the merge it passed.test --project test/CodoMetis.TypeKit.Conventions.Tests: TESTS OK: 1484 passed, 0 skipped,ProbeCiteTestsandProbeNoteKeyTestsamong them (1477 at2002740, before the merge).docs-query check: 0 problems in 725 links, 512 ids, 5 fact citations (2 not-promised, 3 harness: KB-037 twice, KB-038) and 38 facts. Every cited fact is executable (decision 107).prober universeaccepts every declaration and quote.Noticed, not changed
IFormattable,ISpanFormattableandIUtf8SpanFormattablefor "every value object".ValueObjectFormattableAspectemitsISpanFormattableonly where the wrapped type has it, so a value object overbool,stringorNodaTime.Instanthas none. L04's TryFormat rows over those end inconclusive, since L04's own sentence ("every generated … TryFormat") promises only what is generated. The table or the aspect should change; that is not this step's to decide.🤖 Generated with Claude Code