Repository navigation
test: the three-sided differential in the http, json and text harness, and no text-matching site left - #58
Merged
Conversation
… the serializer's own is written Measured with System.Text.Json of the pinned SDK: under MaxDepth = 3, a list element at the writer's depth 3 is written by the serializer's own converter for int and refused with a JsonException at the list's path when a converter of one's own writes it; one level less deep it is written, and reading is not charged. Its fixture is the probes' side (1) for a converter-written value under a depth limit, which the stj-maxdepth note told by the refusal's message until now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e harness The three-sided differential reads side (1) from a fact's fixture where one covers a row (FactFixture, design/adr/0107). FactFixture.cs is BCL only, so it is compiled in as it is, as the analyzer tests' variant catalogues are, rather than the Facts project referenced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ated context without ValidationProblemDetails Measured in a host without TypeKit: an [ApiController] action whose int parameter is sent abc answers 400 under the serializer's default resolver, and fails with System.Text.Json's NotSupportedException when MVC's JSON options resolve through a source-generated context that lists int alone. Its fixture is the probes' side (1) for a refused binding under such a context, which the note mvc-400-unwritable-under-source-gen told by the exception's words until now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every comparison of a value object with its wrapped twin now has three sides (harness/Sides.cs, the rules in one place): (1) the wrapped twin without TypeKit, a host without AddTypeKit() and TypeKit's JSON converters (HostCfg.WithoutTypeKit, the twin's endpoints alone) or, in process, the application's options less TypeKit's converters; (2) the twin with TypeKit, as before; (3) the value object. Where (1) and (3) refuse alike (the same operation, stage and exception type, and status; no message compared) the check is the framework's refusal, expected-refusal against Baseline.Framework, or Baseline.Fact where a fact's fixture answered for side (1) (KB-040, KB-041, KB-037); (2) differing from (1) is "TypeKit changed the wrapped type's behaviour". Side (1) runs on every row of C21, L01 and C10, whose obligations promise the underlying type's schema or JSON or that it is unaffected, and elsewhere only where (2) and (3) disagree or refuse. The text-matching sites of #53 and #57 are gone: HttpBaseline's stj-maxdepth (both sides) and MVC's 500 under a source-generated context, the three OpenAPI 2.0 path and shape checks (now side (1)'s document at the same path, and the structure of the difference), Positions.cs's and TextRow.cs's depth checks and JsonFamily.cs's Jx.Depth, TextRow.cs's Path cut by its words (now JsonException.Path, with the twin's Path beside it), OffTheWire.cs's "not a wire type" (now the refusal's TargetSite, by type), and the message comparisons that made a read refused alike a pass (json:read-refused-by-stj-alone, json-half:wrapped- alone-refuses). FaultClaim, SchemaClaim, C06 and C18 read a message only to fail a row now. The note keys those sites called stay in NoteKeys; framework-refuses is new. JsonParity.ReadMayDiffer, unused, is gone. Triage 1, harness, found by side (1): C21 row 104 compared the twin's and the value object's shapes where the row's CreateSchemaReferenceId gave both one component (an array of Nullable<T>), so the document held one schema for the two; such a pair is now inconclusive. C10 rows 24 and 190 (a sealed record reading null) and 156 (the application's converter for the value object beside a twin refused by depth) were judged against the wrong baseline once the depth note no longer hid them. The probes reading a fact's fixture watch it and the reader (registry.json). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ProbeMessageTextTests in the conventions tests reads the probe corpus's syntax: a .Message.Contains(, StartsWith(, EndsWith( or IndexOf( stands only in the condition of a failure text (an if whose branch returns one or raises a harness error, a ?: between null and one), never in an assignment, a catch filter or a branch that passes. A positive control and a floor of 30 reads (33 when written) keep it from passing on a walker that finds nothing. The EF family stays two-sided in this step: its one excuse by a message (E06's "could not be translated") is listed with its reason, and the list fails once the site is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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's last harness step: the http, json and text harness compares a value object with its wrapped twin on three sides, and every text-matching site of #53's and #57's "kept" tables is gone. (1) is the wrapped twin without TypeKit, (2) the twin with TypeKit as before, (3) the value object. The rules live in one place,
harness/Sides.cs;Row.Check's contract is unchanged. Side (1) comes from a fact's fixture where one covers the row, else it is measured, and two configurations measured here are pinned as facts with a test and a fixture: KB-040 (a converter's value is refused at the depth limit) and KB-041 (MVC's 400 under a source-generated context). Replayed: 29 probes passed in the http, json, text and build families. Every seed of every http, json and text probe is caught by its own obligation's check (91 seeds of 23 probes, 91 caught).docs-query checkis clean.The rules (
harness/Sides.cs)HostCfg.WithoutTypeKit: noAddTypeKit(), none of TypeKit's JSON converters on either set of options, and no fault responses. It maps the twin's endpoints alone (PayloadOnly = "plain", now minimal and MVC). The reason for twin-only: in a document that also describes the value object's types, ASP.NET can give both shapes one component, so the twin's schema there is not its own (C21 row 104, below). A host with both sides' endpoints is started only for the value object's position in a schema difference.Sides.WithoutTypeKit, told apart by assembly).JsonException'sPath, or, where one side writes through a nested serializer call, the other's path ending in it. No message is compared. The check endsexpected-refusalwith the new note keyframework-refuses, againstBaseline.Framework, or againstBaseline.Fact("KB-NNN")when side (1) came from a fixture.JsonParity.ReadMayDiffer's; that function was unused and is gone. Its cases were already cited inHttpBaseline.ReadRefusedAsDocumentedandJsonParity.Read.Sides.Interferes). A refusal's body, such as a problem's trace id, is not compared.inconclusive. Before, this was often a pass with nothing compared.Sides.NonInterference).parameter-keeps-aspnetand C01'scovers-every-decimalcompared with the framework without TypeKit's converters on every row before this step (SchemaClaim'sBaseline.Framework, DecimalSpelling's serializer's own decimal).FactFixture, which is compiled into the Samples as it is (<Compile Include="..\CodoMetis.TypeKit.Facts\FactFixture.cs">, as the analyzer tests' variant catalogues are). The Facts project is not referenced, since it is a test project of its own. The fixtures are read from the repository, only where the fact's entry names them on itsfixture:line.written-by,writer-depth,endpoint,binding,text.MaxValidationDepth = 1. No row's configuration is covered by KB-026's, KB-001's or KB-027's answers, so the C21 sites measure their side (1).FactFixture.csaswatchedinregistry.json, so a changed fixture stales them.Text-matching sites removed
HttpBaseline.cs:45,:59:stj-maxdepth, both sides' "maximum allowed depth"json.MaxDepth(Sides.WrittenByAConverter). The harness reads the depth the serializer hands the wrapped value to a converter at, then takes KB-040's fixture where it covers that depth (3 rows,fact:KB-040), else measures the twin through a converter of the application's.Twin refused: the twin on the host without TypeKit refusing alike (1 row), or "Your own converter wins" where the application's converter writes the value object.
HttpBaseline.cs:290: MVC's 500 namingValidationProblemDetailsjson.TypeInfoResolver=source-generated-context, an[ApiController]endpoint, a failed binding), so rows 286 and 309 endframework-refusesagainstfact:KB-041.HttpBaseline.cs:536: OpenAPI 2.0's.allOfand[{"x-nullable":true}]openapi-2-nullable-component-loses-its-type.HttpBaseline.cs:544: the wrapped side's"schema":{"allOf":[{"x-nullable":true}]}OnlyItsNull), not a search of the text. Row 257 still citesopenapi-2-nullable-body-keeps-only-its-null.HttpBaseline.cs:554: the$.parameters…enumpathDiff.Keysis["parameters", .., "enum"]), and side (1)'s twin listing the same null through the same resolved component. Row 285 still citesenum-component-is-its-first-use.Positions.cs:239,:268: L01's write and read by depthSides.Frameworkon the write, and on the read and its write back (Sides.Readnames a write back as its own operation). 4 checks now end againstfact:KB-040.JsonFamily.cs:62:Jx.Depth(C10)fact:KB-040, and the others refused alike againstframework.TextRow.cs:285,:294: L04's depthSides.Frameworkon L04's write and read back; 5 checks end againstfact:KB-040.TextRow.cs:374: L02'sPathcut off by its wordsJsonException.Pathis read as the property it is. The message must hold the input no more often than itsPathdoes, and side (1), the twin reading the same text, shows whether the serializer puts the key in itsPathtoo.OffTheWire.cs:67: "not a wire type"TargetSite) by a type nested inNotWireTypeJsonConverterFactory, compared as aType, withnameofin the failure..Messageread to excuse a row elsewherejson:read-refused-by-stj-alone, from message equality inOffTheWire,PreserveGuardandReadAlike, is nowframework-refuses, decided by type and path. So isjson-half:wrapped-alone-refusesin L03.FaultClaim's
StartRefused, SchemaClaim's C16 refusal, C06'sNoKindRefusedand C18'sNamesEachnow read a message only to fail a row.The note keys these sites called stay in
NoteKeys, each marked "not called since the three-sided differential":stj-maxdepth,mvc-400-unwritable-under-source-gen,json:read-refused-by-stj-alone,json-half:wrapped-alone-refuses. One key is new:framework-refuses(expected-refusal).A conventions test,
ProbeMessageTextTests, holds the rule. Reading syntax, it refuses a.Message.Contains(,StartsWith(,EndsWith(orIndexOf(inharness/orprobes/unless it stands in the condition of a failure text:ifwhose branch returns a failure or raises a harness error;?:betweennulland a failure.A lambda inside such a condition counts as part of it. An assignment, a
catch … whenor a branch that passes is refused. It has a positive control and a floor of 30 reads (33 today). It keeps one EF site, listed with its reason, and fails once that site is gone; that site is listed under "Not changed" below.Facts pinned
MaxDepth, System.Text.Json refuses a value a converter of one's own writes at the limit, where its own converter writes it; reading is not chargedKB040(4 cases)a-converter-at-the-limit(exception,JsonExceptionat write),the-serializer-at-the-limitanda-converter-below-the-limit(documents)fact:KB-040): BaseProbe 3, C10 11, L01 4, L03 5, L04 5; where the twin itself is refused at that depth, it refuses alike and the check ends againstframeworkValidationProblemDetails(NotSupportedException; 400 under the default resolver)KB041(2 cases)source-generated-context-refused-binding(exception),default-resolver-refused-binding(status)fact:KB-041)design/knowledge/README.mdhas both rows.docs-query check: 0 problems in 728 links, 512 ids, 11 fact citations (0 refused, 2 not-promised, 4 obligation, 5 harness) and 41 facts. Every cited fact is executable.Before and after, per probe
782026f, replayed from an empty state in a worktree of its own withreplay --all --family http --parallel 1, thenjson,textandbuildwith--parallel 4: 29 passed.abd3238, the same four replays: 29 passed (http 13, json 5, text 4, build 7).Row.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.Row.Checkof this harness and are unchanged. They passed in both replays.Nothing that passed before is lost without a reason. 27 rows no longer pass (BaseProbe 5, C21 1, L06 1, L03 20). In 26 of them, the only check that passed did so with nothing compared:
Samepassed a check where the twin and the value object both refused. Now:framework-refuses.JsonExceptionat$.P.Aby depth, against the value object'sNotSupportedExceptionfrom the Preserve guard. #339: a 400 against a 500.json:read-refused-by-stj-alone. That pass compared the two refusals' messages. It is nowframework-refuses, by type and path.json-half:wrapped-alone-refuses. That pass took any two refusals for the framework's:framework-refuses.$.p.ALPHA) and not for the value object ($.p.Alpha). In the sixth, the value object's Preserve guard throwsNotSupportedExceptionwhere the twin is refused by depth.The 27th is C21 row 104, a harness fault (triage 1) that side (1) exposed.
DateTime?[]items losttypeunder OpenAPI 2.0.own-prefixingreference ids give the twin's container and the value object's container the same component name,XCn_record_class_POfNullable1[], because ASP.NET's default id for an array of a generic type namesNullable1[]. So the document held one schema for both shapes.Pairs: C21 13802 → 13798, L03 7059 → 7057 and L06 8022 → 8021, from those rows. Every other claim holds its pairs, and every http, json and text claim still has 0 uncredited obligations. Totals: 239508 → 239501 of 242511 pairs current, 0 open findings.
What side (1) changed
framework-refuses.stj-maxdepth(38 checks): BaseProbe 4, C10 25, L01 4, L04 5. They now end against KB-040's side (1) or the twin refusing alike. Where the twin itself is refused by depth and the value object is written by the application's converter, they end in that converter's own round trip (C10 #156) or its citation (BaseProbe #186).mvc-400-unwritable-under-source-gen(2): now against KB-041.json:read-refused-by-stj-alone(C10 32, L06 22): nowframework-refuses.json-half:wrapped-alone-refuses(L03 59): nowframework-refuses, or inconclusive where the two refusals differ..claude/skills/probe-triage. None reached step 2, 3 or 4, so no step-4 finding went to the steering session. All are triage 1, harness:in-patch: the twin holds aPatch<T>, which is TypeKit's own type and which no host without TypeKit describes. Two changes settled them. The non-interference comparison skips a twin that holds one of TypeKit's types (Sides.HoldsTypeKit). And side (1)'s host maps the twin's endpoints alone: the first one mapped both sides', and ASP.NET alone described the value object's types beside the twin's.nullwas judged on the options' output, which leaves the null out. It is now judged on the plain options again.$.A, against the twin's$.P.A. The stage now accepts a path ending in the other.Cost: replay time per http claim
Each probe's two variants, replayed one probe at a time (
--parallel 1) on the same host, before and after. Side (1) costs a host start per row:The json and text families run side (1) in process. json 42.8 s → 42.5 s, text 75.8 s → 77.0 s, at
--parallel 4.Seeds
prober seeds --probe P, one probe at a time, atabd3238. It ran every http, json and text probe (23, with C18, whoseNamesEachchanged), 91 seeds in all. 91 caught, each by its own obligation's check: each line names a row, then the obligation of the check that caught it, and that is always the seed's own obligation. BaseProbe's one seed names no obligation, so itsclaimcheck catches it. None was missed and no build failed.seeds output
Verdicts
Through
build-verdict, atabd3238:test --project test/CodoMetis.TypeKit.Conventions.Tests, TESTS OK: 1491 passed, 0 skipped,ProbeMessageTextTestsamong them.release/2.2.0at782026f: it fails there with 14 sites, every.Messageread of the test: the http family checks each obligation (WS2 step 4) #53 and test: the json and text families check each obligation (WS2 step 4) #57 tables and the four other probes' reads (FaultClaim:323, SchemaClaim:671 and 672, C06:33, C18:214). Its positive control and the kept-site check pass.Wire.ThrownandBody, and OffTheWire's are gone by the diff, not by this rule.test --project test/CodoMetis.TypeKit.Prober.Tests, TESTS OK: 257 passed, 0 skipped.ToolchainTests.A_dotnet_without_the_pinned_sdk_falls_back_to_the_installersfails (NullReferenceExceptionat line 122) wheneverPROBE_STATE_DIRis set in the environment that runs the tests: the prober it starts writes its run there, not into the test's repository. It passes with the variable unset.test --project test/CodoMetis.TypeKit.Facts, TESTS OK: 87 passed, 9 skipped. The 9 skipped are the PostgreSQL facts, withoutTYPEKIT_PG.KB040has 4 cases andKB0412.docs-query check: 0 problems; 5 harness fact citations, among themBaseline.Fact("KB-040")andBaseline.Fact("KB-041").Commits:
9553eab: KB-040 (docs:)3000d79: the Samples compileFactFixture.csbf6db0b: KB-041 (docs:)9974f46: the three-sided harness, the triage fixes andwatchedabd3238:ProbeMessageTextTestsrelease/2.2.0 has not moved since
782026f.Not changed, or noticed
Hx.DocRefusedByAspNetAloneand FaultClaim's copy still compare the two documents' refusal messages and pass a refused document (doc:refused-by-aspnet-alone; C21 has 128 such rows, the fault claims more).Contains, so the conventions rule does not reach them.JsonParity.ReadMayDiffer's: a validated value object refusing whatCreaterefuses, a struct refusingnull, a closed set's unknown name) are "(1) answers and (3) refuses". So both directions accept a citation.E06OwnConverterNoValueProbe.cs:72still excuses a row by "could not be translated", the one siteProbeMessageTextTestskeeps.FaultClaim.csstill tells a refusal's type byWire.Thrown's text (StartsWith("InvalidOperationException:")), only to fail a row.Wire.Exnow carries the exception, for a later change.🤖 Generated with Claude Code