Skip to content

Assert and document the request binder's list-binding contracts - #177

Merged
Reefact merged 4 commits into
mainfrom
claude/issue-155-binder-contract-tests
Jul 18, 2026
Merged

Assert and document the request binder's list-binding contracts#177
Reefact merged 4 commits into
mainfrom
claude/issue-155-binder-contract-tests

Conversation

@Reefact

@Reefact Reefact commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Issue #155 (request binder review, finding 19/19) found two binder behaviours that ran under 100% line/branch coverage but were never asserted. This PR documents and pins the empty-required-list contract, and adds property-based invariants for the binder's collect-all and path-stability guarantees.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Analyzer / diagnostic change
  • Tests
  • Documentation
  • Build / CI / tooling

Changes

  • Document the required-list presence contract: AsRequired constrains a list's presence (non-null), not its element count — a present-but-empty list binds to an empty list, and only an absent (null) list records REQUEST_ARGUMENT_REQUIRED. Updated the XML docs of the three list converters (simple, value-type, complex) and the binder reference docs (RequestBinder.en.md + RequestBinder.fr.md).
  • Pin that contract with unit tests across the simple, value-type, and complex list converters.
  • Add FirstClassErrors.RequestBinder.PropertyTests (a new test project referencing the binder) with FsCheck invariants: collect-all records exactly one error per failing element, and the indexed error paths are positional — stable under element reordering.
  • Record ADR-0014 for the decision (EN + FR) and index it.

Testing

  • dotnet build FirstClassErrors.sln
  • dotnet test FirstClassErrors.sln
  • Analyzer tests pass (FirstClassErrors.Analyzers.UnitTests)

Full suite green: 975 tests, 0 failures, 0 warnings (includes the 3 new unit tests and the 3 new property tests). Re-verified after rebasing onto the latest main.

Documentation

  • Public API / error documentation updated (XML docs on the three list converters)
  • README / doc/ updated (RequestBinder.en.md, ADR-0014)
  • French translation kept in sync (RequestBinder.fr.md and the French ADR; no user-facing behaviour change — the contract documents existing behaviour)
  • No documentation change required

Architecture decisions

  • No architectural decision in this pull request
  • New decision recorded — ADR-0014 (accepted by the maintainer in this PR)
  • Supersedes an existing ADR — successor proposed, status not flipped: ADR-____
  • ⚠️ Conflicts with an existing ADR — flagged for the maintainer: ADR-____

Related issues

Closes #155


Generated by Claude Code

claude added 4 commits July 18, 2026 10:49
Issue #155 (request binder review, finding 19/19) flagged that whether a
present-but-empty required list is valid was neither documented nor asserted.
Record the contract: AsRequired constrains a list's presence (non-null), not
its element count, so a present-but-empty list binds to an empty list while
only an absent (null) list records REQUEST_ARGUMENT_REQUIRED.

Document it in the API XML docs of the three list converters (simple,
value-type, complex) and in the binder reference docs (EN + FR), and draft
ADR-0014 (Proposed) recording the decision for the maintainer to accept.

Refs: #155

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HzQtszd156kmTp86rfsQR
Assert the contract documented alongside: a required list that is present but
empty binds as success (an empty list) and records nothing, across the simple,
value-type, and complex list converters. Coverage alone did not assert this
boundary (issue #155, finding 19/19), so the behaviour could have changed
silently.

Refs: #155

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HzQtszd156kmTp86rfsQR
The property-test suite exercised no binder invariant (issue #155, finding
19/19). Add FirstClassErrors.RequestBinder.PropertyTests — each library keeps
its own test projects — with FsCheck invariants for the binder's defining
guarantees: collect-all records exactly one error per failing element, and the
indexed error paths are positional, so reordering the elements reorders their
paths accordingly.

Refs: #155

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HzQtszd156kmTp86rfsQR
The maintainer accepts the decision that a required list binding constrains
presence, not cardinality — a present-but-empty list is valid. Flip ADR-0014
from Proposed to Accepted (EN + FR) and update the index.

Refs: #155

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HzQtszd156kmTp86rfsQR
@Reefact
Reefact merged commit dd535ad into main Jul 18, 2026
13 checks passed
@Reefact
Reefact deleted the claude/issue-155-binder-contract-tests branch July 18, 2026 10:59
Reefact pushed a commit that referenced this pull request Jul 18, 2026
Renumber ADR-0014 -> ADR-0015 (main took 0014 for the required-list
presence contract, #177) and resolve the ADR index.
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.

Binder: two unasserted contracts despite 100% coverage (empty required list = success; no property-based invariants)

2 participants