Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .claude/probe-axes.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ axes from its surfaces. The prober assigns surfaces to a claim; it does not choo
declarations and records such a check inapplicable before its body runs; a pair no obligation wants is never
put in a generated row, and the gate leaves it out as it leaves out an exclusion. A condition only the shape
instance decides at run time stays in the check's body as `c.Inapplicable(cite)`.
- `refused` drops a value the framework itself refuses on its own, so no row wastes its pairs on it.
- `refused` drops a value the framework itself refuses on its own, so no row wastes its pairs on it. A reason that
rests on a verified fact names it (`fact:KB-NNN`), and that fact has a test: `docs-query check` fails
`fact-not-executable-cited` for a refused entry, a not-promised entry, an applicability condition or a harness
citation of a fact without one ([decision 107](../design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md)).
`shape.refused-kind` holds the declarations the generators refuse; only the generation surface takes
it, where the refusal is the claim. Each value is a declaration and the id the Generators README's
"Build errors" table gives it, declared as C22's probe does (`harness/BuildRefusals.cs`): an unsealed
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ jobs:
echo "PROBE_CACHE_DIR=$RUNNER_TEMP/probe-cache" >> "$GITHUB_ENV"

# Every probe of the corpus, with no model, as CI replays it on every push (design/adr/0096). A probe
# that fails fails the release, like a test. The Build above built the prober.
# that fails fails the release, like a test. The Build above built the prober. Every probe's seeds are
# shown caught before the tag, locally, by `prober seeds --all` (AGENTS.md, Releasing): a build per seed,
# which this job does not pay (design/adr/0100), and --all because a run without it skips the probes
# unchanged since their seeds were caught (design/adr/0107).
- name: Replay every probe
run: dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --all

Expand Down
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ dotnet tools/Prober/bin/Release/net10.0/prober.dll gate # the release gate,
```

Proving that each probe can fail needs no model either, but builds once per seed, so it runs locally
only ([decision 100](design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md)):
only ([decision 100](design/adr/0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md)), and only for the
probes changed since their seeds were last caught; a release runs `--all`
([decision 107](design/adr/0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md)):

```bash
dotnet tools/Prober/bin/Release/net10.0/prober.dll seeds # each probe's seeded defect, applied in a scratch worktree, must fail it
Expand Down Expand Up @@ -268,11 +270,13 @@ the whole page. A ranked miss proves nothing.

## Releasing

Before `git tag`, replay the whole probe corpus and run the gate:
Before `git tag`, replay the whole probe corpus and run the gate, and show every probe's seeds caught (locally, a
build per seed; without `--all` it runs only the probes changed since their seeds were last caught, decision 107):

```bash
dotnet tools/Prober/bin/Release/net10.0/prober.dll replay --all
dotnet tools/Prober/bin/Release/net10.0/prober.dll gate
dotnet tools/Prober/bin/Release/net10.0/prober.dll seeds --all
```

The gate never starts a model and needs nothing but the tracked corpus and a replay, so the release
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# 0107. A fact that prunes or excuses is executable, its answer is a fixture beside its test, and seeds run by footprint

Date: 2026-10-17
Status: accepted

Context: 2026-10-17, WS2b of the probing concept (facts at a pinned version shorten every loop), after the probe
triage of 2026-10-16 and 2026-10-17 pinned KB-026 to KB-038 and the harness began to cite them
(`Cite.Fact("KB-037")`, PR #53). [Decision 93](0093-verified-facts-are-pinned-dated-and-executable-where-they-can-be.md)
made a fact executable where it can be; nothing held the probe corpus to citing only those. A prose fact cited by a
`refused` entry, a `not-promised` entry or an applicability condition would prune or excuse rows on the strength of a
sentence no test re-reads when a dependency moves, which is the substring excuse in a nicer form. The facts whose
tests compare a document or a status held the measured answer inside the test's code, where the harness, which will
read a fact as the framework's side of a row (`Baseline.Fact`), cannot reach it. And `prober seeds` built once per
seed of every probe on every run, a release step's cost paid after every probe change.

## Decision

**A fact that prunes or excuses is executable.** `docs-query check` gains `fact-not-executable-cited`. It fails, at
the citing place and naming the fact, when a fact without a test is cited by a `refused` entry of
`.claude/probe-axes.md` (its reason names `KB-NNN`), a `not-promised` entry of `tools/probe/corpus/claims.json` (its
`source` is the fact's file, or a field says `fact:KB-NNN`), an obligation's `inapplicable` declaration (its `cite` is
`fact:KB-NNN`), or a `Cite.Fact("KB-NNN")` or `Baseline.Fact("KB-NNN")` literal of the harness and probe sources, read
as text with comments blanked out as `SourceLint` reads them. A fact without a test is one whose `test:` is `none, …`,
names no file, or names a file that does not exist or declares no class of the fact's id; a cited id that is no fact
fails too. `Baseline.Fact` takes a fixture as well. The check's summary counts the citations of each kind, and
`RepositoryTests` holds floors on the places read and the citations found, so a scan that finds nothing cannot pass.
A fact without a test keeps its pins and its date and may be named in a reason's prose; it never decides a row.

**A fact's measured answer is a fixture beside its test.** Where the answer is data (a document, a status, an
exception type at an operation), it is `test/CodoMetis.TypeKit.Facts/KBNNN.fixture.json`, named by the fact's
`fixture:` line: the fact's id and its answers, each with an `id`, a `kind` (`document` with `at`, a JSON pointer;
`status`; `exception`), the settings it was measured under (`when`, axis to value, as a row names them), the `value`
and a `note`. The fact's test measures every answer of the fixture, a theory over its ids, so an answer the test does
not measure fails it. `docs-query check` fails a `fixture:` whose file is missing (`fact-fixture-missing`), one that is
not `KBNNN.fixture.json` beside the test or belongs to a fact without a test (`fact-fixture`), and a fixture no fact
names (`fact-orphan-fixture`). The reader, `FactFixture`, is BCL only, so the harness can compile it as it is, and
states the rule the harness will follow: only a fixture a fact names, only while the check is clean; the one answer
whose every `when` setting is the row's, none meaning the harness measures the framework itself and several a harness
error; compared by kind; never written by the harness. KB-001, KB-026, KB-027 and KB-037 are in this form.

**Seeds run by footprint, the release runs them all.** Without `--all`, `prober seeds` runs only the probes whose seeds
identity (the probe's identity, its seeds and their patches' content) changed, or whose product footprint or seeded
file changed, since the run that caught every one of its seeds, and prints each probe it skips and why.
`seeds-last.json` records, per probe, the identity and the commit of that run; a probe with a seed missed loses its
record, so it runs next time whatever changed, and a probe without a recorded footprint runs. `--all` runs every probe
whatever the record says, and `--probe P` runs P. A release runs `seeds --all` locally before the tag, beside
`replay --all` and the gate; CI still never builds a seed ([decision 100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md)).

## Evidence

On 2026-10-17 the citation rule read 3 refused entries, 41 not-promised entries, 11 obligation declarations and 87
harness and probe sources, and found 4 citations: KB-001 and KB-027 by not-promised entries, KB-037 twice by the
harness; every one is executable. Each kind was seeded in a scratch copy of the repository and failed the check: a
cited fact's `test:` line removed, one pointed at a missing file, and a prose fact (KB-005) cited from a refused entry,
an obligation declaration and the harness. A wrong answer seeded into KB001's and KB037's fixtures failed
`KB001` and `KB037`. Each rule of the seeds selection was seeded in the prober (identity, footprint, seeded file,
`--all`, a missed seed's record, a missing footprint, never skipping) and failed its `SeedsTests`.

Rejected: a fixture kept in `design/knowledge/` beside the entry, which no build copies and the facts project would
reach by path.

## Consequences

- A triage that settles a row on a framework fact writes the test first, then cites it; a prose fact that would
explain a row is research to do, not a citation.
- `prober seeds` after a probe or product change runs seconds to minutes instead of one build per seed of every probe.
- Wiring `Baseline.Fact` to the fixtures is the harness's next step; this record only fixes the rule it follows. A
probe's identity does not yet include the facts it cites, so a changed fixture does not stale the probes reading it
until that step adds it.
5 changes: 3 additions & 2 deletions design/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,17 +99,18 @@ per area ([design](../plan.md)) link to the records they rest on.
| [0090](0090-faults-alike-are-a-warning-until-3-0.md) | Two fault types writing one name are a startup warning until 3.0.0 | 2026-10-14 | accepted |
| [0091](0091-an-exclusion-narrows-a-wider-status.md) | An endpoint's exclusion narrows a wider scope's status; a status every endpoint leaves out is refused | 2026-10-14 | accepted |
| [0092](0092-repository-tooling-is-dotnet-and-ci-needs-no-ai.md) | Repository tooling is .NET, a file-based app or a tool project by how it is used; git and the pinned SDK are the only prerequisites; the tests run on Linux, Windows and macOS; CI never depends on an AI | 2026-10-15 | accepted; session-start builds by [0105](0105-every-tool-is-built-in-the-session-s-checkout.md) |
| [0093](0093-verified-facts-are-pinned-dated-and-executable-where-they-can-be.md) | Verified facts are pinned, dated and executable where they can be: one fact per file in `design/knowledge/`, a characterization test per observable fact, and `docs-query check` holding the pins to what is installed | 2026-10-15 | accepted |
| [0093](0093-verified-facts-are-pinned-dated-and-executable-where-they-can-be.md) | Verified facts are pinned, dated and executable where they can be: one fact per file in `design/knowledge/`, a characterization test per observable fact, and `docs-query check` holding the pins to what is installed | 2026-10-15 | accepted; a cited fact is executable and an answer is a fixture beside the test by [0107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md) |
| [0094](0094-topic-branches-into-a-release-branch-and-fix-trailers.md) | From 2.2.0, topic branches merge into a release branch with merge commits only, and every `fix:` commit carries `Defect-Class:` and `Found-By:` trailers, held by a test | 2026-10-15 | accepted |
| [0095](0095-a-fix-is-tested-across-its-input-class.md) | A fix is tested across its input class: the fixer names the class, runs the fix's test over that class's variant catalogue (`TypeArgumentSites` for a type argument, the `shape.*` axes, the settings axes) and fixes the siblings it finds; `Found-By: VA` | 2026-10-15 | accepted |
| [0096](0096-ci-replays-every-probe-and-the-release-runs-the-gate.md) | CI replays every probe, from an empty state, on every push and pull request, and a failing probe fails the run; the release replays them and runs the gate before it packs, the gate advisory; held by `ReleaseWiringTests` | 2026-10-15 | accepted; the gate binds from 2.2.0 by [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md); the build workflow's replay runs by family and footprint by [0101](0101-ci-replays-by-family-and-footprint.md) |
| [0097](0097-2-2-0-is-a-consolidation-release-and-the-gate-binds.md) | 2.2.0 is a consolidation release: no public API, its additive items move to 2.3.0, and the probe gate binds from it | 2026-10-15 | accepted |
| [0098](0098-a-null-passes-no-comparison-in-a-query.md) | A nullable record-class value object compared with `<`, `<=`, `>` or `>=` in a query answers as SQL does, leaving out its null rows; documented, not refused | 2026-10-15 | accepted |
| [0099](0099-a-probe-row-says-how-it-ended.md) | A probe row says how it ended: notes are a closed catalogue of keys and outcomes, a row that earned nothing is a skipped test labelled with its settings, a skip for a finding fails the replay once the finding is fixed and the gate while it stands, and a refused document is judged against ASP.NET alone | 2026-10-15 | accepted; note keys are a typed catalogue the compiler checks by [0103](0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md) |
| [0100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md) | Every registered probe names a seeded product defect, and `prober seeds` (local, a build per seed) requires a violation it causes; the prober tests hold that every seed exists and applies; pairwise coverage is a floor, three-way combinations belong to the variant catalogue | 2026-10-15 | accepted; a seed per obligation of a claim by [0104](0104-a-claim-states-its-obligations-and-each-has-a-seed.md) |
| [0100](0100-every-probe-is-shown-to-fail-against-a-seeded-defect.md) | Every registered probe names a seeded product defect, and `prober seeds` (local, a build per seed) requires a violation it causes; the prober tests hold that every seed exists and applies; pairwise coverage is a floor, three-way combinations belong to the variant catalogue | 2026-10-15 | accepted; a seed per obligation of a claim by [0104](0104-a-claim-states-its-obligations-and-each-has-a-seed.md); seeds run by footprint, a release `--all`, by [0107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md) |
| [0101](0101-ci-replays-by-family-and-footprint.md) | The build workflow replays the probes in legs by family, each running its probes in parallel, under a time limit: a push every probe with coverage and the merged state saved, a pull request only the probes it stales against its base branch's saved state and without a footprint; `prober merge` joins the legs and refuses a probe no leg saw | 2026-10-16 | accepted |
| [0102](0102-an-exclusion-carries-its-reason.md) | Each `exclude` entry of claims.json is a required one-sentence reason and its pairs; the prober refuses a claims file its schema does not match, naming the claim and the field, so an invented reason field is refused; `prober universe` prints each exclusion's reason beside the claim's narrowings; the prober writes JSON with only JSON's own escapes, so an unchanged publish leaves a tracked file as it is | 2026-10-16 | accepted |
| [0103](0103-a-harness-error-is-never-a-catch-and-note-keys-are-constants.md) | A harness error is never a catch: `Rec.Run` ends a row whose check throws as a harness error, not a violation, and `prober seeds` credits only a violation no harness error raised; note keys are members of a typed `NoteKeys` the compiler checks, and `register`, `publish` and a conventions test refuse a note or a skip whose key or finding is not written out | 2026-10-16 | accepted |
| [0104](0104-a-claim-states-its-obligations-and-each-has-a-seed.md) | A claim lists its `obligations`, each a sentence quoted verbatim from its source, and what it does not promise in `not-promised`, each quoted from the document or record that makes it intended; a claim without the field has one obligation, `claim`; a probe carries a seed per obligation it checks, and the check without a build refuses a seed naming an unlisted obligation and a listed obligation no seed names; `status` shows each claim's obligations, and `prober gaps` reports what the corpus leaves out, deciding nothing | 2026-10-16 | accepted |
| [0105](0105-every-tool-is-built-in-the-session-s-checkout.md) | SessionStart builds the guard in `$CLAUDE_PROJECT_DIR` and every other tool, through `tools/AgentTools.slnf`, in the session's own checkout; a hook, the prober, the smoke test and the verdict fall back to the SDK installer's `dotnet` when the one on PATH lacks the pinned SDK, and a session start that builds nothing tells the agent; the guard lets `dotnet msbuild -getProperty/-getItem` without a target through | 2026-10-17 | accepted |
| [0106](0106-a-parameter-of-a-value-object-over-a-described-type-is-inlined.md) | A value object over a type the JSON options describe is written inline as a route, query, header or form parameter and as an element of a query or header array, where value objects are components: the component is its JSON positions'; every other value object keeps its reference; the schema below a parameter is asked for with the parameter's source and type alone | 2026-10-17 | accepted |
| [0107](0107-a-cited-fact-is-executable-and-seeds-run-by-footprint.md) | A fact that prunes or excuses a row is executable: `docs-query check` fails `fact-not-executable-cited` where a refused entry, a not-promised entry, an obligation's declaration or the harness cites a fact without its test; a fact's measured answer is `KBNNN.fixture.json` beside its test, named by `fixture:`, read by a BCL-only reader the harness will use; `prober seeds` runs only the probes whose seeds identity or product footprint changed since every seed was caught, and a release runs `--all` | 2026-10-17 | accepted |
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pins:
source: the document Microsoft.AspNetCore.OpenApi writes for an MVC [FromBody] int and int?, and a minimal-API [FromBody] enum?, under OpenApiSpecVersion.OpenApi2_0 and OpenApi3_1, measured in a host without TypeKit
turned-on: the probe finding G1, dismissed as no TypeKit bug (tools/probe/corpus/dismissed.jsonl), and the probe harness comparing a nullable value object with the nullable wrapped type
test: test/CodoMetis.TypeKit.Facts/KB001.cs
fixture: test/CodoMetis.TypeKit.Facts/KB001.fixture.json
---

# KB-001. Under OpenAPI 2.0, ASP.NET writes a nullable request body as `allOf: [{x-nullable: true}]` and drops its `pattern`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pins:
source: the document Microsoft.AspNetCore.OpenApi writes for an MVC [FromBody] record with a property of a record struct and one of its Nullable<T>, under OpenApiSpecVersion.OpenApi2_0 and OpenApi3_1, measured in a host without TypeKit
turned-on: row 38 of C21OpenapiSchemaParityProbe (claim C21-openapi-schema-parity), where a nullable value-object property that is a component had no type under OpenAPI 2.0
test: test/CodoMetis.TypeKit.Facts/KB026.cs
fixture: test/CodoMetis.TypeKit.Facts/KB026.fixture.json
---

# KB-026. Under OpenAPI 2.0, ASP.NET writes a nullable property of a component as `allOf: [{x-nullable: true}]`, without the reference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pins:
source: the document Microsoft.AspNetCore.OpenApi writes for minimal API header parameters of two enums written by name (JsonStringEnumConverter), each used once nullable and once not, in opposite orders, under OpenApiSpecVersion.OpenApi3_1 and OpenApi3_0, measured in a host without TypeKit
turned-on: row 285 of C21OpenapiSchemaParityProbe (claim C21-openapi-schema-parity, finding F3), where the wrapped E? header's enum listed null through E's component and the value object's did not
test: test/CodoMetis.TypeKit.Facts/KB027.cs
fixture: test/CodoMetis.TypeKit.Facts/KB027.fixture.json
---

# KB-027. The component ASP.NET makes for an enum written by name is the schema of the first use it meets
Expand Down
Loading
Loading