Problem
(1) The RequestBinderOptions.Default getter executes lock (Gate) { _frozen = true; return _default; } unconditionally (RequestBinderOptions.cs:29-41), so every request bound through the bare Bind.Request entry acquires a process-global monitor and performs a shared cache-line write — on precisely the hot path issue #151/ADR-0023 optimized to sub-microsecond precision. Once frozen, _default can never change, so the steady state needs neither. (2) Nothing prevents calling New/Create twice on the same binder, or binding more properties after a terminal ran: errors keep accumulating and BuildFailureEnvelope silently rebuilds — inconsistent with the binder's loud bug-channel doctrine (cross-binder reads, mis-declared properties, and invalid selectors all throw).
Impact
(1) is a needless scalability tax on every request; (2) is the one silent-misuse hole in an API otherwise defined by loud programming-error reporting — and it becomes a behavioral change after v1.
Direction
- Double-checked fast path preserving ADR-0017's semantics exactly:
if (Volatile.Read(ref _frozen)) return _default; before the lock; setter unchanged.
- Add a terminal-ran flag to
RequestBinding; a second terminal — and any converter-stage call after a terminal — throws InvalidOperationException naming the contract ("a binder binds one request; create a new binder per request").
- While in the file: give the default-constructed
BindingScope failure its own message (today it misdiagnoses as a cross-binder read).
Acceptance criteria
- Steady-state
Default reads take no lock and perform no write (concurrency tests for the freeze contract still pass).
- Terminal reuse and post-terminal binding throw with the documented message; tests pin both.
Context
Surfaced by the 2026-07-20 architecture, design & ecosystem audit (doc/handwritten/for-maintainers/audit/2026-07-20-firstclasserrors-architecture-and-design-audit.md, §8.2-Binder/§14-HV11; branch claude/firstclasserrors-audit-83gap7).
Problem
(1) The
RequestBinderOptions.Defaultgetter executeslock (Gate) { _frozen = true; return _default; }unconditionally (RequestBinderOptions.cs:29-41), so every request bound through the bareBind.Requestentry acquires a process-global monitor and performs a shared cache-line write — on precisely the hot path issue #151/ADR-0023 optimized to sub-microsecond precision. Once frozen,_defaultcan never change, so the steady state needs neither. (2) Nothing prevents callingNew/Createtwice on the same binder, or binding more properties after a terminal ran: errors keep accumulating andBuildFailureEnvelopesilently rebuilds — inconsistent with the binder's loud bug-channel doctrine (cross-binder reads, mis-declared properties, and invalid selectors all throw).Impact
(1) is a needless scalability tax on every request; (2) is the one silent-misuse hole in an API otherwise defined by loud programming-error reporting — and it becomes a behavioral change after v1.
Direction
if (Volatile.Read(ref _frozen)) return _default;before the lock; setter unchanged.RequestBinding; a second terminal — and any converter-stage call after a terminal — throwsInvalidOperationExceptionnaming the contract ("a binder binds one request; create a new binder per request").BindingScopefailure its own message (today it misdiagnoses as a cross-binder read).Acceptance criteria
Defaultreads take no lock and perform no write (concurrency tests for the freeze contract still pass).Context
Surfaced by the 2026-07-20 architecture, design & ecosystem audit (
doc/handwritten/for-maintainers/audit/2026-07-20-firstclasserrors-architecture-and-design-audit.md, §8.2-Binder/§14-HV11; branchclaude/firstclasserrors-audit-83gap7).