Skip to content

test: the http family checks each obligation (WS2 step 4) - #53

Merged
CaffeinatedCoder merged 12 commits into
release/2.2.0from
test/ws2-http-checks
Oct 17, 2026
Merged

CaffeinatedCoder merged 12 commits into
release/2.2.0from
test/ws2-http-checks

Conversation

@CaffeinatedCoder

@CaffeinatedCoder CaffeinatedCoder commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

WS2 step 4, the http family: every probe whose claim's first surface is http (BaseProbe, C03 to C08, C12 to C16, C21) runs a Row.Check per obligation its row reaches, named by the claim's obligation id and given its baseline, and every excuse of outcome inapplicable, expected-refusal or inconclusive is a cited c.Inapplicable(Cite…) / c.Refused(Cite…) or a c.Inconclusive(reason), except the text-matching sites this step keeps. Each http claim's obligations now earn credit on their own: prober status shows 0 uncredited for all 12 http claims (before: every obligation of C03 to C16 uncredited), and every seed of every http claim is caught by its own obligation's check.

What changed

  • HttpBaseline (BaseProbe's wire half, C21's schema half). Per side, the echo, the request read with the row's input.json, the binding of a valid value and of a value Create refuses are each a check of their own. BaseProbe's checks are the implicit claim: its rows are C07's, C07 promises nothing of the wire, and its seed names no obligation. Baselines: the twin; a closed set's own response read back (RoundTrip); the AspNetCore README's 400 for a refused binding (Docs(…#binding)). C21's Schema starts the host once and runs every obligation's check. Before, ??= stopped at the first failure; now each obligation's check runs even when another fails.
  • FaultClaim (C04, C06, C07, C14, C15). Each comparison is tagged with an Aspect: the start, a member left out, a member's status, fault, type and detail, the operation's statuses, their order, the problem schema and the problem types it names. A claim's Spec.Of names the obligations each aspect is checked for. One check per obligation runs against the claim's README or CHANGELOG section, or against Refusal for a host that must not start and for a member left out. The answers are fetched once, on one host.
  • ComposedProbes (C03, C05). These follow the same shape. C03's unmapped-set-stops-start and C05's unnamed-value-throws are refusal checks. They now run beside the rest instead of short-circuiting it.
  • SchemaClaim (C08, C12, C13, C16). Each claim gathers what its host or hosts answered and wrote once, then judges it per obligation:
    • C08 checks per host's naming and leave-out, and checks the names the warning gives.
    • C12 checks the source's own description at a property, every position, and the decimal converter's parameter against Framework.
    • C13 checks the document's required against the wire, and what an absent patch reads.
    • C16 checks the refusal, the names it gives and the patch-held type apart from the documents that build.
  • NoteKeys is untouched. The http family no longer calls an outcome key except at the three text-matching sites below. The pass keys stay as c.Note.

Commits: b0195d3 (the http baseline and C21), 2dbb715 (the fault claims), ab0057c (C03, C05), d15b2b4 (the schema claims), 90918a4 (C21's inconclusive reason names the inputs), ddd146c (C16's judge renamed off the lint's .Refused(, and the Note floor), a481101 (release/2.2.0 merged in, with #50's declared applicability), db7e3aa (C21 declares its own-converter rows inapplicable), 8c92e32 (release/2.2.0 merged in again, with #51 and #52), 631e7c7 and af6e8d6 (BaseProbe's 29 findings triaged: KB-037, KB-038, a docs sentence, L01's not-promised entry), 658d173 (release/2.2.0 merged in again, with #54, the build family).

Before and after, per probe

Before: release/2.2.0 at cfc9a2a, replayed from an empty state in a worktree of its own (replay --family http --no-footprint: 13 passed). After: this branch at 658d173, replay --family http with footprints: 13 passed. Release/2.2.0 was merged in three times: at a481101 (#50's Row.cs re-staled every probe), 8c92e32 (#51 and #52 changed the OpenAPI transformers and the generators the probes run) and 658d173 (#54). Before the triage, the replays at d15b2b4, ddd146c, db7e3aa and 8c92e32 gave the same outcomes, row for row: 12 passed, and BaseProbe failed on the 29 rows triaged below. The categories are each row's and each check's from the evidence. A row that runs no Row.Check counts as one check, its row. "Rows passing" is what Rec.Run passes: no violation or harness error in the row or any of its checks, and at least one check that passed.

Probe rows before checks before rows after checks after rows passing, before → after regained (category before) lost
BaseProbe expected-refusal 39, inapplicable 211, pass 161 expected-refusal 39, inapplicable 211, pass 161 expected-refusal 18, inapplicable 127, inconclusive 55, pass 211 expected-refusal 27, inapplicable 389, inconclusive 77, pass 1123 161 → 338 177 (expected-refusal 32, inapplicable 145) 0
C03ComposedProbe pass 648 pass 648 pass 648 pass 2919 648 → 648 0 0
C04WithoutFaultsProbe expected-refusal 2, pass 394 expected-refusal 2, pass 394 expected-refusal 2, pass 394 expected-refusal 2, pass 835 394 → 396 2 (expected-refusal 2) 0
C05RuleProbe pass 645 pass 645 pass 645 pass 1666 645 → 645 0 0
C06FaultKindsProbe expected-refusal 4, pass 404 expected-refusal 4, pass 404 expected-refusal 4, pass 404 expected-refusal 4, pass 1628 404 → 408 4 (expected-refusal 4) 0
C07BlanketMappingProbe expected-refusal 1, pass 410 expected-refusal 1, pass 410 expected-refusal 1, pass 410 expected-refusal 1, pass 474 410 → 410 0 0
C08SameNameWarningProbe pass 318 pass 318 pass 318 pass 545 318 → 318 0 0
C12DescribedConverterProbe pass 332 pass 332 pass 332 pass 789 332 → 332 0 0
C13PatchRequiredProbe expected-refusal 55, pass 301 expected-refusal 55, pass 301 inconclusive 55, pass 301 inconclusive 55, pass 657 301 → 356 55 (expected-refusal 55) 0
C14DeclaredProblemProbe expected-refusal 3, pass 418 expected-refusal 3, pass 418 expected-refusal 3, pass 418 expected-refusal 12, pass 1385 418 → 418 0 0
C15MvcFaultStatusDocProbe expected-refusal 7, pass 398 expected-refusal 7, pass 398 expected-refusal 7, pass 398 expected-refusal 8, pass 543 398 → 398 0 0
C16OptionsAgreementProbe pass 310 pass 310 pass 310 pass 420 310 → 310 0 0
C21OpenapiSchemaParityProbe inapplicable 9, pass 284 inapplicable 16, pass 478 inapplicable 3, inconclusive 6, pass 284 inapplicable 6, inconclusive 10, pass 478 284 → 284 0 0
13 probes 5354 5555 (5217 pass) 5354 14053 (13462 pass) 5023 → 5261 238 0

The rows regained, 238 of them, are all rows that earned nothing before, and no row that passed before fails now.

  • BaseProbe, 177 rows. These are 145 rows that ended inapplicable and 32 that ended expected-refusal. 26 of them are the triaged rows below, which now pass by their other checks. Before, one excuse anywhere voided the row: an application's own converter on the echo, a closed set's spelling, a record class that is not bound from text, a refused value under MaxDepth=3. Now each comparison is its own check, so a row with one passing check passes beside the others.
  • C13, 55 rows (expected-refusal before). These are the MVC bodies MVC's validator refuses for the plain twin too. That part is now inconclusive, and the document's required and the minimal side are checked beside it.
  • C06, 4 rows, and C04, 2 rows (expected-refusal before). In these rows, ASP.NET alone refuses the document under MaxDepth=3, which is decision 99's case. The wire-only obligations still pass: reporting-left-out-throws, and C06's description-as-detail, no-description-no-detail and member-must-have-kind.
  • Not regained:
    • C07 1, C14 3, C15 7. These are the same MaxDepth=3 rows, but every obligation of those claims includes the document, so every check ends as record:0099.
    • C21 9. No obligation's comparison could run in these rows.

prober status at 658d173 reports 0 uncredited obligations for every http claim (C03 5/5, C04 4/4, C05 5/5, C06 4/4, C07 6/6, C08 5/5, C12 4/4, C13 3/3, C14 4/4, C15 3/3, C16 5/5, C21 5/5), and 0 open findings. On release/2.2.0 every obligation of C03 to C16 was uncredited.

prober status, the http claims, at 658d173
test/ws2-http-checks at 658d173: 181992 of 242515 pairs current (75.0%), 0 open finding(s) -> not done
  C03-composed-faults: 15583/15607  (rows: pass 648; checks: pass 2919)  obligations 5, each seeded, 0 uncredited
  C04-without-faults: 15409/15426  (rows: expected-refusal 2, pass 394; checks: expected-refusal 2, pass 835)  obligations 4, each seeded, 0 uncredited
  C05-with-rule: 15750/15783  (rows: pass 645; checks: pass 1666)  obligations 5, each seeded, 0 uncredited
  C06-fault-kinds: 15231/15248  (rows: expected-refusal 4, pass 404; checks: expected-refusal 4, pass 1628)  obligations 4, each seeded, 0 uncredited
  C07-fault-responses-blanket: 15946/15956  (rows: expected-refusal 19, inapplicable 127, inconclusive 55, pass 621; checks: expected-refusal 28, inapplicable 389, inconclusive 77, pass 1597)  obligations 6, each seeded, 0 uncredited
  C08-same-name-warning: 15609/15609  (rows: pass 318; checks: pass 545)  obligations 5, each seeded, 0 uncredited
  C12-described-converter: 14903/14904  (rows: pass 332; checks: pass 789)  obligations 4, each seeded, 0 uncredited
  C13-patch-required: 14731/14731  (rows: inconclusive 55, pass 301; checks: inconclusive 55, pass 657)  obligations 3, each seeded, 0 uncredited
  C14-declared-problem: 14886/14903  (rows: expected-refusal 3, pass 418; checks: expected-refusal 12, pass 1385)  obligations 4, each seeded, 0 uncredited
  C15-mvc-fault-status-doc: 15238/15255  (rows: expected-refusal 7, pass 398; checks: expected-refusal 8, pass 543)  obligations 3, each seeded, 0 uncredited
  C16-options-agreement: 14904/14904  (rows: pass 310; checks: pass 420)  obligations 5, each seeded, 0 uncredited
  C21-openapi-schema-parity: 13802/14218, 336 inapplicable  (rows: inapplicable 3, inconclusive 6, pass 284; checks: inapplicable 6, inconclusive 10, pass 478)  obligations 5, each seeded, 0 uncredited

Seeds

prober seeds --probe P for each of the 13 probes, at db7e3aa, after #50's merge (a first run at d15b2b4 gave the same result). After #51 changed C12's parameter-keeps-aspnet and C21's nullable-admits-null-as-the-wrapped-does patches, C12's and C21's nine seeds ran again at 8c92e32: all caught, by their own obligation's check. BaseProbe's seed ran again at 658d173, after the triage: caught on 55 rows, 6 more than before, since the KB-037 check now catches the seeded TryParse bypass under mvc.MaxValidationDepth=1, which the old excuse hid. 54 seeds, 54 caught, each by the check of its own obligation: the detail's example names it. BaseProbe's one seed names no obligation, so its claim check catches it.

seeds output
  BaseProbe (C07-fault-responses-blanket): caught, 55 of 411 rows violate; e.g. #10 claim
  C03ComposedProbe/each-error-own-set (C03-composed-faults): caught, 487 of 648 rows violate; e.g. #0 each-error-own-set
  C03ComposedProbe/detail-and-rule-intact (C03-composed-faults): caught, 163 of 648 rows violate; e.g. #2 detail-and-rule-intact
  C03ComposedProbe/statuses-ascending (C03-composed-faults): caught, 182 of 648 rows violate; e.g. #1 statuses-ascending
  C03ComposedProbe/shared-status-types (C03-composed-faults): caught, 182 of 648 rows violate; e.g. #1 shared-status-types
  C03ComposedProbe/unmapped-set-stops-start (C03-composed-faults): caught, 648 of 648 rows violate; e.g. #0 unmapped-set-stops-start
  C04WithoutFaultsProbe/doc-omits-left-out (C04-without-faults): caught, 96 of 396 rows violate; e.g. #1 doc-omits-left-out
  C04WithoutFaultsProbe/reporting-left-out-throws (C04-without-faults): caught, 312 of 396 rows violate; e.g. #0 reporting-left-out-throws
  C04WithoutFaultsProbe/narrows-wider-status (C04-without-faults): caught, 129 of 396 rows violate; e.g. #0 narrows-wider-status
  C04WithoutFaultsProbe/left-out-everywhere-stops-start (C04-without-faults): caught, 84 of 396 rows violate; e.g. #11 left-out-everywhere-stops-start
  C05RuleProbe/rule-qualified (C05-with-rule): caught, 129 of 645 rows violate; e.g. #4 rule-qualified
  C05RuleProbe/nested-chain (C05-with-rule): caught, 125 of 645 rows violate; e.g. #3 nested-chain
  C05RuleProbe/stays-the-errors (C05-with-rule): caught, 376 of 645 rows violate; e.g. #2 stays-the-errors
  C05RuleProbe/unnamed-value-throws (C05-with-rule): caught, 269 of 645 rows violate; e.g. #0 unnamed-value-throws
  C05RuleProbe/no-namespace (C05-with-rule): caught, 247 of 645 rows violate; e.g. #2 no-namespace
  C06FaultKindsProbe/kind-gives-status (C06-fault-kinds): caught, 189 of 408 rows violate; e.g. #3 kind-gives-status
  C06FaultKindsProbe/description-as-detail (C06-fault-kinds): caught, 344 of 408 rows violate; e.g. #1 description-as-detail
  C06FaultKindsProbe/no-description-no-detail (C06-fault-kinds): caught, 408 of 408 rows violate; e.g. #0 no-description-no-detail
  C06FaultKindsProbe/member-must-have-kind (C06-fault-kinds): caught, 408 of 408 rows violate; e.g. #0 member-must-have-kind
  C07BlanketMappingProbe/by-kind (C07-fault-responses-blanket): caught, 40 of 411 rows violate; e.g. #22 by-kind
  C07BlanketMappingProbe/own-mapping (C07-fault-responses-blanket): caught, 27 of 411 rows violate; e.g. #2 own-mapping
  C07BlanketMappingProbe/own-set-keeps-mapping (C07-fault-responses-blanket): caught, 64 of 411 rows violate; e.g. #1 own-set-keeps-mapping
  C07BlanketMappingProbe/enum-needs-own-mapping (C07-fault-responses-blanket): caught, 66 of 411 rows violate; e.g. #0 enum-needs-own-mapping
  C07BlanketMappingProbe/member-without-kind-stops-start (C07-fault-responses-blanket): caught, 33 of 411 rows violate; e.g. #7 member-without-kind-stops-start
  C07BlanketMappingProbe/mapping-refusal-stops-start (C07-fault-responses-blanket): caught, 70 of 411 rows violate; e.g. #4 mapping-refusal-stops-start
  C08SameNameWarningProbe/bare-names-warn (C08-same-name-warning): caught, 77 of 318 rows violate; e.g. #0 bare-names-warn
  C08SameNameWarningProbe/qualified-chain-warns (C08-same-name-warning): caught, 75 of 318 rows violate; e.g. #5 qualified-chain-warns
  C08SameNameWarningProbe/warning-names-both (C08-same-name-warning): caught, 152 of 318 rows violate; e.g. #0 warning-names-both
  C08SameNameWarningProbe/qualified-names-fix (C08-same-name-warning): caught, 75 of 318 rows violate; e.g. #5 qualified-names-fix
  C08SameNameWarningProbe/leave-one-out-fixes (C08-same-name-warning): caught, 166 of 318 rows violate; e.g. #1 leave-one-out-fixes
  C12DescribedConverterProbe/own-converter-describes-itself (C12-described-converter): caught, 119 of 332 rows violate; e.g. #1 own-converter-describes-itself
  C12DescribedConverterProbe/options-describe (C12-described-converter): caught, 28 of 332 rows violate; e.g. #19 options-describe
  C12DescribedConverterProbe/every-position (C12-described-converter): caught, 147 of 332 rows violate; e.g. #1 every-position
  C12DescribedConverterProbe/parameter-keeps-aspnet (C12-described-converter): caught, 81 of 332 rows violate; e.g. #2 parameter-keeps-aspnet
  C13PatchRequiredProbe/doc-required-where-rejected (C13-patch-required): caught, 51 of 356 rows violate; e.g. #3 doc-required-where-rejected
  C13PatchRequiredProbe/doc-optional-where-absent (C13-patch-required): caught, 119 of 356 rows violate; e.g. #0 doc-optional-where-absent
  C13PatchRequiredProbe/absent-is-default (C13-patch-required): caught, 237 of 356 rows violate; e.g. #0 absent-is-default
  C14DeclaredProblemProbe/declared-describes-fault (C14-declared-problem): caught, 30 of 421 rows violate; e.g. #15 declared-describes-fault
  C14DeclaredProblemProbe/declared-names-types (C14-declared-problem): caught, 77 of 421 rows violate; e.g. #1 declared-names-types
  C14DeclaredProblemProbe/ascending-with-declared (C14-declared-problem): caught, 105 of 421 rows violate; e.g. #1 ascending-with-declared
  C14DeclaredProblemProbe/application-json-declared (C14-declared-problem): caught, 38 of 421 rows violate; e.g. #1 application-json-declared
  C15MvcFaultStatusDocProbe/builder-reaches-doc (C15-mvc-fault-status-doc): caught, 24 of 405 rows violate; e.g. #30 builder-reaches-doc
  C15MvcFaultStatusDocProbe/without-faults-leaves-doc (C15-mvc-fault-status-doc): caught, 45 of 405 rows violate; e.g. #20 without-faults-leaves-doc
  C15MvcFaultStatusDocProbe/status-per-scope (C15-mvc-fault-status-doc): caught, 76 of 405 rows violate; e.g. #0 status-per-scope
  C16OptionsAgreementProbe/differing-refused (C16-options-agreement): caught, 37 of 310 rows violate; e.g. #7 differing-refused
  C16OptionsAgreementProbe/error-names-type-and-descriptions (C16-options-agreement): caught, 37 of 310 rows violate; e.g. #7 error-names-type-and-descriptions
  C16OptionsAgreementProbe/patch-held-refused (C16-options-agreement): caught, 37 of 310 rows violate; e.g. #7 patch-held-refused
  C16OptionsAgreementProbe/same-converters-build (C16-options-agreement): caught, 77 of 310 rows violate; e.g. #1 same-converters-build
  C16OptionsAgreementProbe/minimal-host-not-compared (C16-options-agreement): caught, 33 of 310 rows violate; e.g. #0 minimal-host-not-compared
  C21OpenapiSchemaParityProbe/the-underlying-types-schema (C21-openapi-schema-parity): caught, 10 of 293 rows violate; e.g. #15 the-underlying-types-schema
  C21OpenapiSchemaParityProbe/container-elements (C21-openapi-schema-parity): caught, 16 of 293 rows violate; e.g. #0 container-elements
  C21OpenapiSchemaParityProbe/dictionary-key (C21-openapi-schema-parity): caught, 1 of 293 rows violate; e.g. #137 dictionary-key
  C21OpenapiSchemaParityProbe/every-document-version (C21-openapi-schema-parity): caught, 53 of 293 rows violate; e.g. #5 every-document-version
  C21OpenapiSchemaParityProbe/nullable-admits-null-as-the-wrapped-does (C21-openapi-schema-parity): caught, 6 of 293 rows violate; e.g. #63 nullable-admits-null-as-the-wrapped-does

Findings of this step, triaged

The conversion left 29 BaseProbe rows failing, all from excuses that had no citation and were compared instead. Each was triaged with .claude/skills/probe-triage, in its order. None is a product defect, and no uncited excuse came back.

  • 28 rows under mvc.MaxValidationDepth=1: "validated value object bound the refused value". These span every binding: body 7, form 7, header 4, query 4, as-parameters 3, route 3.
    • Triage 1, the harness: not a harness fault, and no value object held a refused value. Every answer was the value object's default: 204 for a class (MVC writes null as no content), and 200 with the default for a struct (0, null, an empty Guid, 0001-01-01).
    • The rows that settle it: where the refused value is itself the type's default (Guid.Empty, 0UL, DateTime.MinValue, DateTimeOffset.MinValue), an answer can't tell the two apart. So I read one row per binding whose refused value is not its default: body #122, form #244, header #238, query #140, as-parameters #313, route #201. Each holds its default.
    • Triage 2, a framework fact, pinned as KB-037 (design/knowledge/KB-037-…, test test/CodoMetis.TypeKit.Facts/KB037.cs, 18 cases). Under MvcOptions.MaxValidationDepth = 1, an [ApiController] action whose simple int parameter MVC cannot bind runs anyway, with the parameter at 0, and answers 200. That holds for route, query, header, form and body. With the default depth (32), every such request is a 400. A [FromQuery] class makes the validator throw its depth refusal at the property instead, whether the property was bound or not.
    • Source: measured. The API reference of MaxValidationDepth says only that the visitor throws past the depth and that the default is 32; it says nothing of binding errors.
    • The check now requires the value object to hold its default as MVC writes it, compared as JSON (MVC's formatter escapes with its own encoder). It sends the twin a text the wrapped type cannot read, in the same host:
      • both at their default: c.Inapplicable(Cite.Fact("KB-037")), 25 rows;
      • a string-wrapped value object, where any text binds and no twin can show it: the same citation, 3 rows;
      • the twin rejecting that text with a 4xx: a violation;
      • anything else: inconclusive, which no row reached.
  • Row #83, a byte array: "bodies differ: wrapped {"P_x":"Bw=="} vs value object {"P_x":[7]}".
    • Triage 2, pinned as KB-038 (test KB038.cs, 4 cases). System.Text.Json writes a byte[] as one base64 string and reads it back only from one. A List<byte>, and an array of any other element, is a JSON array. Its source is the "Supported types in System.Text.Json" page: "byte[] is handled specially and serializes as a base64 string, not a JSON array."
    • Triage 3, intended but undocumented. docs/json.md, "The wrapped type's JSON", gains a Good-to-know item: "An array of value objects over byte is a JSON array of numbers. Each element is written as its byte, so you get [7,255], not the base64 string "B/8=" that a byte[] becomes. Keep a byte[] where a client expects base64."
    • The echo and read checks cite it with Cite.Docs("docs/json.md#the-wrapped-types-json"), 2 checks.
    • Claim L01-json-bytes lists it as not promised, a-byte-array-is-base64 (axes shape.wrapped=byte, shape.position=array-element), quoting the bold sentence, for the json family's conversion to cite.

After the triage, 26 of the 29 rows pass by their other checks. The 3 string-wrapped ones end inapplicable on every check.

Uncited excuses

Each excuse below had no sentence, record or fact to cite. Three kinds of conversion:

  • Compared, where the check can tell. What it found was triaged above, and both now cite what the triage found:
    • mvc-maxvalidationdepth1-ignores-binding-errors (BaseProbe, refused binding): 28 violations, now compared with the twin and cited by fact:KB-037.
    • byte-array-is-base64 (BaseProbe, echo and read): 1 violation, now cited by docs:docs/json.md#the-wrapped-types-json.
    • bind:plain-unbindable (BaseProbe, valid binding): compared where the value object binds and the twin does not, which happened in no row.
  • Inconclusive, where the row itself shows there is nothing to compare: no twin can be built, or the twin fails alike. The check earns nothing and excuses nothing. A fact pinned for each would let it cite one instead.
    • bind:plain-unbindable where both sides refuse the valid text, and the refused value where the wrapped type refuses it too (BaseProbe, 18 and 9 checks).
    • not-bindable-from-text: a record class, or Instant in a minimal API. The wrapped type is not bound from text either, so neither side has an endpoint (BaseProbe, 22 checks in 19 rows).
    • route-text-unsafe: a text with /, :, ? or # cannot be carried by a route segment, and escaped it binds as other text. Comparing it anyway made a refused Uri bind as https:%2F%2F… on the first try, a harness artifact, so it stays a harness limit (BaseProbe, 24 checks in 11 rows).
    • schema:nullability-not-comparable: no twin of the wrapped type at the value object's nullability. That is a struct value object over a class, or, under a source-generated context, an enum or Instant (C21, 10 checks in 6 rows).
    • mvc:plain-nested-body-throws-too: MVC's validator throws for the plain twin's body as well (C13, 55 checks in 55 rows).
    • Two new outcomes of the converted read-back: the value object's echo failed, or neither side reads its own response back (BaseProbe, 4 checks).
  • Kept as notes, being text-matching sites (next section): stj-maxdepth (HttpBaseline twice, and Positions.cs 237 in the json family's code) and mvc-400-unwritable-under-source-gen.

Text-matching sites, kept, each marked with its baseline gap

Site What the text tells Baseline gap
harness/HttpBaseline.cs:45 the value object's maximum allowed depth under MaxDepth=3 (note stj-maxdepth, uncited) no twin in the row shows System.Text.Json charging a converter a level
harness/HttpBaseline.cs:59 the same on the wrapped side the same
harness/HttpBaseline.cs:290 MVC's 500 naming ValidationProblemDetails under a source-generated context (note mvc-400-unwritable-under-source-gen, uncited) a wrapped twin refused in the same row should tell it
harness/HttpBaseline.cs:536 OpenAPI 2.0's .allOf path and [{"x-nullable":true}] (now Cite.NotPromised("openapi-2-nullable-component-loses-its-type")) no plain nullable component in the same document shows ASP.NET losing its reference too
harness/HttpBaseline.cs:544 the wrapped side's document holding "schema":{"allOf":[{"x-nullable":true}]} (now Cite.NotPromised("openapi-2-nullable-body-keeps-only-its-null")) the twin's nullable body is not compared with one ASP.NET writes alone
harness/HttpBaseline.cs:554 the $.parameters…enum path (now Cite.NotPromised("enum-component-is-its-first-use")) the enum's first use is not replayed in a document of its own
harness/Positions.cs:237 maximum allowed depth in the JSON family's parity (note stj-maxdepth, uncited) as HttpBaseline's

That is six sites in HttpBaseline. The design note counted eight at its line numbers. The two others I find there decide no outcome, so they are unmarked: the 404 that names a harness routing mistake (a failure, never an excuse), and the [F1] prefix of a failure text.

Not-promised entries added, and facts and docs

  • One not-promised entry: L01-json-bytes a-byte-array-is-base64, from the triage of row #83. It quotes docs/json.md#the-wrapped-types-json and is the place the json family's conversion will cite.
  • Two facts: KB-037 (MVC under MaxValidationDepth = 1) and KB-038 (a byte[] is a base64 string). Each has its Facts test, and each has a row in design/knowledge/README.md.
  • One docs sentence, the item in docs/json.md quoted above.

The conversion itself added no not-promised entry:

  • C21. Its schema excuses cite the claim's own entries, which already exist.
  • BaseProbe. Its wire excuses cite the sentence itself (Cite.Docs), not a not-promised entry of C07. BaseProbe runs on C07's rows as a calibration of the host, and C07 promises nothing of the wire. An entry of C07 saying "a struct value object refuses a JSON null" would put a JSON difference on the fault-responses claim. The citations are:
    • docs/json.md#null-and-missing
    • docs/json.md#when-a-value-is-rejected
    • README.md#what-you-get (a closed set travels as its name)
    • README.md#what-typekit-guarantees (StoredJsonConverterFactory)
    • src/CodoMetis.TypeKit.Generators/README.md#json (preserved references)
    • src/CodoMetis.TypeKit.AspNetCore/README.md#what-the-document-says (your own converter wins)
    • the README's #binding for the 400 as a baseline
  • The fault claims. Their one excuse, a problem document both sides refuse under MaxDepth=3, cites decision 99 (Cite.Record("0099")).

Declared applicability (#50)

Declared on the obligation (claims.json inapplicable): C21's two own-converter cases, on each of its five obligations.

  • {"json.Converters": ["own-for-value-object"]} cites not-promised:own-converter-for-the-value-object.
  • {"json.Converters": ["own-for-wrapped"]} cites not-promised:own-converter-for-the-wrapped-type.

The schema check decided both from that axis alone. They replace C21's two exclusions of 168 pairs each, and universe now counts the same 336 pairs as wanted by no obligation (C21: 13802/14218, 336 inapplicable). The two lines in HttpBaseline.SchemaParity stay, for the schema half run under another claim (ShrinkProbe's baseline). For C21 they are no longer reached: no generated row has those values, and Row.Check would record the declaration first.

Kept in code, because the shape instance or what the host did decides them:

  • C21's closed-set, OpenAPI 2.0 and enum-first-use citations, which hold only where the schemas differ at a given path. C21's closed set stays an exclusion as before.
  • Decision 99's MaxDepth=3 refusal in the fault claims, which holds only when both documents are refused by depth.
  • Every BaseProbe excuse. Each concerns one comparison, not a whole obligation, and BaseProbe's checks are the implicit claim, which no obligation entry declares.
  • The inconclusive cases, which have no citation.

No other http claim has an excuse decided by axes alone. C04's controller-scope-is-mvc and C16's only-converters-and-descriptions-compared excuse no check: the first is an exclusion, and the second's rows are checked by same-converters-build.

Verdicts

  • Facts tests: build-verdict test --project test/CodoMetis.TypeKit.Facts, TESTS OK: 49 passed, 9 skipped, at af6e8d6. The 9 skipped are the PostgreSQL facts, which need TYPEKIT_PG. KB037 has 18 cases and KB038 4.
  • docs-query check: 0 problems in 719 links, 501 ids and 38 facts. The first run flagged KB-038's source starting with a quote, fixed in af6e8d6.
  • Prober tests: build-verdict test --project test/CodoMetis.TypeKit.Prober.Tests, TESTS OK: 245 passed, 0 skipped, at db7e3aa, 8c92e32 and af6e8d6 (204 before the merge brought build: declared applicability: an obligation says where it does not apply, read by the universe, the covering generator, the gate and Row.Check #50's tests, at 90918a4 and ddd146c).
    • One run at ddd146c failed ToolchainTests.A_dotnet_without_the_pinned_sdk_falls_back_to_the_installers. It passed alone, and in the full rerun (204 passed).
    • The test starts processes, and the host was then also running the build family's seeds. I read it as flaky under load, not related to this change.
  • Conventions tests: build-verdict test --project test/CodoMetis.TypeKit.Conventions.Tests, TESTS OK: 1471 passed, 0 skipped, at ddd146c and db7e3aa, 1475 at 8c92e32 (the merge brought four), and 1477 at af6e8d6 and 658d173. The first run failed two tests, both fixed in ddd146c:
    • ProbeCiteTests read a C16 helper, AgreeRun.Refused(c, …), as a check's Refused without a Cite. The helper is now Refusal.
    • ProbeNoteKeyTests.The_corpus_notes_are_found has a floor of 130 Note calls, which keeps a walker that finds nothing from passing. The conversion leaves 127, since outcome notes became citations. The floor is 100 now. The build family's conversion lowers the count too, so that session may meet the same line.

Left as it was, or not checked

  • C14's application-json-declared checks the declared response's fault, rule and problem types. It does not check that the response keeps application/json as its media type, which no comparison held before either.
  • BaseProbe's checks are named claim, the implicit obligation, while C07 lists obligations. BaseProbe's seed names none.
  • NoteKeys is unchanged. Its outcome keys the http family no longer calls stay declared for the other families until the end of WS2.

🤖 Generated with Claude Code

CaffeinatedCoder and others added 12 commits October 17, 2026 01:24
BaseProbe's wire half (HttpBaseline.Run): per side the echo, the request read with the row's input.json and
the binding with a valid and a refused value are each a Row.Check of the implicit obligation claim (C07
promises nothing of the wire), against the twin, a closed set's own response read back, or the 400 the
AspNetCore README's Binding section documents. An intended difference cites the sentence that makes it
intended (Cite.Docs); one no document makes intended is compared (MVC under MaxValidationDepth=1, a byte
array's base64) or, where the row's twin cannot be built or fails alike, ends inconclusive.

C21's schema half (HttpBaseline.Schema) starts the host once and runs a check per obligation the row selects,
every one, not stopping at the first failure; its excuses cite C21's not-promised entries. The text-matching
sites stay, each marked with its baseline gap (HttpBaseline's depth and ValidationProblemDetails matches, the
three OpenAPI 2.0 and enum schema matches, Positions.cs's depth match).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FaultClaim.Run tags each comparison with an Aspect (the start, a member left out, a member's status,
fault, type and detail, the operation's statuses, their order, the problem schema and its problem
types), and a claim's Spec.Of names the obligations each is checked for. The row runs one check per
obligation it reaches, every check on one host with the answers fetched once, against the claim's
README or CHANGELOG section, or the refusal for a host that must not start and a member left out.
A document both refuse under MaxDepth=3 cites decision 99. C06's member-must-have-kind runs beside
the rest instead of before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ComposedClaims.WireAndDoc runs one check per obligation the claim names an aspect for (the answer's
status, fault, type, detail and rule, and the operation's statuses, problem schema and problem types),
on one host with the answers fetched once. C03's unmapped-set-stops-start and C05's
unnamed-value-throws are checks against the refusal that run beside the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each of SchemaChecks' claims gathers what its host or hosts answered and wrote once, then runs one check
per obligation: C08 per host's naming and leave-out, and the warning's names; C12 the source's own at a
property, every position, and the decimal converter's parameter against ASP.NET's own; C13 the document's
required against the wire, and what an absent patch reads; C16 the refusal, its type, its descriptions
and the patch-held type apart, and the documents that build. C13's MVC body that MVC's validator refuses
for the plain twin too ends inconclusive, uncited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A struct value object over a class has no twin at its nullability either, not only an enum or Instant
under a source-generated context; the reason now names the inputs that differ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s the citations

ProbeCiteTests read AgreeRun.Refused(c, ...) as a check's Refused without a citation; it is Refusal now.
ProbeNoteKeyTests' floor of 130 Note calls guards against a walker that finds nothing: the http family's
outcome notes became Check.Inapplicable/Refused citations, leaving 127, so the floor is 100.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ation

The schema check decided from json.Converters alone that an application's own converter, for the value
object or for the wrapped type, leaves nothing to compare (not-promised own-converter-for-the-value-object
and own-converter-for-the-wrapped-type). Each of C21's five obligations now declares both (claims.json
inapplicable, decision of PR #50), in place of the two exclusions of 168 pairs each: universe counts the
same 336 pairs as wanted by no obligation, and Row.Check records such a check before its body runs. The
code's lines stay for the schema half run under another claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Triage (.claude/skills/probe-triage) of the rows the conversion left failing:

- 28 rows under mvc.MaxValidationDepth=1, "validated value object bound the refused value": triage 1
  found no harness fault and no value object holding a refused value. Every answer was the value
  object's default (204 for a class, 200 with the default for a struct), and in one row per binding
  whose refused value is not its default (#122 body, #244 form, #238 header, #140 query, #313
  as-parameters, #201 route) that is unambiguous. Triage 2: MVC's own behaviour, pinned as KB-037
  (Facts KB037): under MaxValidationDepth = 1 an [ApiController] action whose simple parameter MVC
  cannot bind runs with the parameter at its default and answers 200, where the default depth gives a
  400; a class from the query makes the validator throw at its property. The check now requires the
  value object to hold its default and sends the twin a text it cannot read in the same host: both at
  their default cite fact:KB-037, the twin rejecting it is a violation, anything else is inconclusive.
- Row 83, a byte array: triage 2, KB-038 (Facts KB038): System.Text.Json writes byte[] as one base64
  string. Triage 3: docs/json.md "The wrapped type's JSON" now says an array of value objects over byte
  is a JSON array of numbers, cited by the echo and read checks, and claim L01-json-bytes lists it as
  not promised (a-byte-array-is-base64), quoting that sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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