You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: append #2238's record to the SR-AUD-066 index row
The row was already 'remediated' from #2237, which documented and pinned the
deviation; #2238 is the behaviour change #2237 deliberately did not make, so the
row gains its record rather than a status change.
Copy file name to clipboardExpand all lines: audit/AUDIT_FINDINGS_INDEX.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -77,7 +77,7 @@ implementation ticket.
77
77
| [SR-AUD-063](modules/core/include/System/Tuple.hpp.audit.md#sr-aud-063--medium--tuple-components-are-publicly-mutable-despite-the-immutable-net-contract) | medium | confirmed | `Tuple.hpp` | `TupleN` uses public mutable fields despite .NET Tuple's readonly components, allowing post-construction mutation without a documented adaptation. **Reviewed 2026-08-12 (#2330); STILL CONFIRMED, nothing implemented — `needs_user`.** **Measured consumer surface:** no first-party code *writes* an `ItemN` (zero matches for the assignment form outside `ValueTuple`), but **75 first-party sites read one** — `TupleTests.cpp` 33, `TupleNewTests.cpp` 26, `SystemTypesRemainingTests.cpp` 16 — and all 75 break under the only .NET-matching repair, because `CLAUDE.md` rule 5 turns `t.Item1` into `t.getItem1Property()`. `ValueTuple` is **not** affected and must not be changed with it: .NET's `ValueTuple` fields really are public and mutable, so its 52 sites are correct parity. **Premise addition: the cheap-looking shortcut is the wider break.** Making the members `const` deletes the implicitly declared copy-assignment operator, so whole-tuple assignment, `std::vector<Tuple2<…>>::push_back`, `std::sort` over tuples and every other assignable-value use stop compiling — a larger break than the getter migration, with diagnostics pointing at the standard library. No first-party site does any of that, so the cost would land downstream and unmeasured. Every route is a public source break, which is why the SR-AUD-011 remediation note already excluded this finding as "a public source break needing approval". Options A/B priced and option C (const members) rejected on measurement in #2330. `docs/CoreOwnedFindingsReviews2317.md`. |
78
78
| [SR-AUD-064](modules/core/include/System/Lazy.hpp.audit.md#sr-aud-064--medium--lazy-constructors-silently-accept-an-invalid-thread-safety-mode) | medium | remediated | `Lazy.hpp` | Mode-taking constructors retained any `LazyThreadSafetyMode` value and `getValueProperty()`'s `default:` label then dispatched it as PublicationOnly. Measured before the fix (`build-probe/2235_probe1_before.log`): `Lazy<int>(static_cast<LazyThreadSafetyMode>(99))` constructed, `getModeProperty()` returned 99, and two accesses over a throwing factory produced **two** factory invocations — no fault caching, which is PublicationOnly's contract and neither None's nor ExecutionAndPublication's, so the invalid instance did not merely survive, it silently acquired another mode's semantics. Ticket #2236 (family #2235) adds a private `requireValidMode()` with .NET's three cases and its `default: throw ArgumentOutOfRangeException(nameof(mode), SR.Lazy_ctor_ModeInvalid)`, called from the two mode-taking constructors **after** `requireFactory()`, matching .NET's argument order (pinned by a test). The five other constructors cannot produce an invalid mode and are untouched. +8 tests, mutation-checked three ways; no signature, template-parameter, `noexcept`, layout or vtable change, and `Lazy<T>` is header-only so there is no exported symbol to move. |
79
79
| [SR-AUD-065](modules/core/include/System/Lazy.hpp.audit.md#sr-aud-065--medium--lazy-accepts-an-empty-factory-and-defers-failure-to-stdbad_function_call) | medium | remediated | `Lazy.hpp` | Empty `std::function` factories are accepted then fail at first `Value` with native `std::bad_function_call`, rather than deterministic constructor validation. |
80
-
| [SR-AUD-066](modules/core/include/System/Lazy.hpp.audit.md#sr-aud-066--medium--publicationonly-wrongly-rejects-recursive-value-access) | medium | remediated | `Lazy.hpp` | An unconditional reentrancy guard throws for PublicationOnly even though .NET reserves this recursive-`Value` exception for None and ExecutionAndPublication. **Remediated by this finding's own second stated repair (#2237, family #2235): the divergence is RETAINED BY DESIGN and is now documented and pinned, not matched.** The finding authorises either implementing the PublicationOnly rule without deadlock **or** documenting and deliberately exposing the restriction; the first is not available without a user decision, because `checkNotReentrant()` is load-bearing for memory safety — a recursive `getValueProperty()` would re-`lock()` the non-recursive `publicationOnlyMutex_`, which `[thread.mutex.requirements.mutex]` makes undefined behaviour — and both structural routes to .NET's behaviour (publish-only locking, or same-thread reentrancy with a first-publication-wins discard rule) either reverse the header's already-documented PublicationOnly serialisation deviation or let an unconditionally recursive factory recurse without bound, trading a catchable `InvalidOperationException` for stack exhaustion. So the class doc-comment now carries an explicit second deviation paragraph naming the .NET rule not implemented, the `std::mutex` reason and the reopening ticket; `getValueProperty()`'s `@throws` clause records that .NET raises this for None and ExecutionAndPublication only; and +5 tests pin the behaviour in **all three** modes plus the non-recursive, fault-retry and different-instance controls a future change must not break. No executable statement changed. The behaviour change itself is ticket **#2238 (`needs_user`)**. `docs/CoreLazyThreadSafetyModeFamilyPlan.md` §4.2. |
80
+
| [SR-AUD-066](modules/core/include/System/Lazy.hpp.audit.md#sr-aud-066--medium--publicationonly-wrongly-rejects-recursive-value-access) | medium | remediated | `Lazy.hpp` | An unconditional reentrancy guard throws for PublicationOnly even though .NET reserves this recursive-`Value` exception for None and ExecutionAndPublication. **Remediated by this finding's own second stated repair (#2237, family #2235): the divergence is RETAINED BY DESIGN and is now documented and pinned, not matched.** The finding authorises either implementing the PublicationOnly rule without deadlock **or** documenting and deliberately exposing the restriction; the first is not available without a user decision, because `checkNotReentrant()` is load-bearing for memory safety — a recursive `getValueProperty()` would re-`lock()` the non-recursive `publicationOnlyMutex_`, which `[thread.mutex.requirements.mutex]` makes undefined behaviour — and both structural routes to .NET's behaviour (publish-only locking, or same-thread reentrancy with a first-publication-wins discard rule) either reverse the header's already-documented PublicationOnly serialisation deviation or let an unconditionally recursive factory recurse without bound, trading a catchable `InvalidOperationException` for stack exhaustion. So the class doc-comment now carries an explicit second deviation paragraph naming the .NET rule not implemented, the `std::mutex` reason and the reopening ticket; `getValueProperty()`'s `@throws` clause records that .NET raises this for None and ExecutionAndPublication only; and +5 tests pin the behaviour in **all three** modes plus the non-recursive, fault-retry and different-instance controls a future change must not break. No executable statement changed. The behaviour change itself is ticket **#2238 (`needs_user`)**. `docs/CoreLazyThreadSafetyModeFamilyPlan.md` §4.2. **RESIDUAL NOW REMEDIATED TOO (#2238, 2026-08-18).** #2237 documented and pinned the deviation; this is the behaviour change it deliberately did not make. `PublicationOnly` serialized its factory behind a mutex and rejected a recursive `Value()`; .NET does neither. Both are gone, transcribed from `Lazy.cs:351-378` — the factory runs with **no lock held** and the publish is a first-writer-wins guard, so every loser discards the value it computed. **The user decided this knowing the price, which was stated first**: a `PublicationOnly` factory may now run **concurrently and more than once**, and a recursive one **exhausts the stack** instead of raising a catchable `InvalidOperationException`. The guard stays in force for `None` and `ExecutionAndPublication`, where it is not a policy choice but what prevents undefined behaviour. The class doc-comment was **rewritten, not amended**, in the past tense. The old pins were **replaced**: `RecursiveValueAccess_RejectedInAllThreeModes` asserted the old contract and **hung** against the new code, which is the correct signal; its replacement bounds the recursion and pins that the **innermost** publication wins. Four mutations: two caught, one caught as a hang (inherent — it reintroduces the deadlock), and **one not caught, with the code saying so at the site** — restoring the guard changes nothing, because `creatingThreadId_` is no longer written on that path. +6 net tests. `docs/Migration-LazyPublicationOnly.md`. |
81
81
| [SR-AUD-067](modules/core/include/System/Buffer.hpp.audit.md#sr-aud-067--high--raw-bufferblockcopy-converts-negative-metadata-into-an-unbounded-memmove) | high | remediated | `Buffer.hpp` | Raw-pointer BlockCopy accepts negative offsets/count; negative count casts to `size_t` and reaches ASan-confirmed unbounded `memmove` instead of deterministic argument validation. |
82
82
| [SR-AUD-068](modules/core/include/System/ValueType.hpp.audit.md#sr-aud-068--medium--valuetype-is-publicly-constructible-and-defaults-to-identity-rather-than-net-value-semantics) | medium | confirmed | `ValueType.hpp`, `ValueTypeTests.cpp` | Public C++ `ValueType` is constructible and uses identity/address defaults, where .NET has an abstract fieldwise-value base; direct tests lock in the divergent fallback. **Reviewed 2026-08-11 (#2322); STILL CONFIRMED, nothing implemented — `needs_user`.** Live: `Equals` is `this == &other`, `GetHashCode` is the object address narrowed to `intcs`, `ToString` is the literal `"System.ValueType"`, and the implicit public default constructor makes `System::ValueType v;` compile. **Measured consumer surface: nothing derives from it in production** — the only derived types anywhere are `SimpleValueType` and `ConcreteValueType` in `ValueTypeTests.cpp`, and `System::Void` documents that it deliberately does *not* derive from it, so the finding's harm has no in-repository instance and the exposure is entirely downstream. **Not autonomous, on two different grounds.** Field-by-field `Equals`/`GetHashCode` and a runtime-type-name `ToString` are reflection, which `CLAUDE.md` lists as a **permanent deviation, out of scope** — not a TODO. What remains is making the incompatible default unreachable, and every route is a **public source break** in a shipped header (a `protected` constructor, matching .NET's own `protected ValueType()`, or an abstract class) — the same class as SR-AUD-063. Options priced in #2322. `docs/CoreOwnedFindingsReviews2317.md`. |
83
83
| [SR-AUD-069](modules/core/include/System/SequencePosition.hpp.audit.md#sr-aud-069--medium--sequenceposition-exposes-mutable-public-representation-instead-of-an-opaque-readonly-position) | medium | confirmed | `SequencePosition.hpp`, `Batch6BuffersTests.cpp` | Public mutable `void*`/integer components let callers rewrite a returned position, unlike .NET's private readonly opaque representation; tests never protect that boundary. **Reviewed 2026-08-12 (#2331); STILL CONFIRMED — it is a CONJUNCTION, and its value-contract clause is REMEDIATED while its representation clause is `needs_user` (#2332).** **Measured consumer surface:** seven files mention the type, and **direct field access exists in exactly one place — inside `SequencePosition` itself**; every other use already goes through the constructor and `GetObject()`/`GetInteger()`, so the encapsulation repair's first-party cost is zero. The two closest siblings settle the convention: `System::Index` and `System::Range` are classes with private fields and public `Equals`/`GetHashCode`. **#2331 (`done`), purely additive:** `Equals(const SequencePosition&)` and `GetHashCode()` are added and `operator==`/`!=` delegate to `Equals`, so the named and operator forms cannot drift. The hash is `((h1 << 5) + h1) ^ h2` evaluated in `uintcs` — signed overflow here would be UB, the CCF-004 class already recorded for `detail::tupleHashCombine` — with the pointer fold guarded by `if constexpr (sizeof(std::uintptr_t) > sizeof(uintcs))` because `bits >> 32` on a 32-bit target is a shift at the operand width. **It is deliberately not .NET's hash value** and the header says so; that is *not* the unverifiable-reference-text class of #2321/#2323, because a hash value is documented unstable in .NET too and this port already ships two hashes that differ from .NET's (`Range::GetHashCode`'s `397`, and `System::HashCode`'s per-process `std::random_device` seed). **+7 tests**, closing the finding's own list — equal and unequal **non-null** segments (every pre-existing direct test used `nullptr`), default equality, hash agreement, hash stability, and the caveat that component equality is not sequence-location identity — all obeying `docs/HashAssertionContractRule.md` (R1 asserted, no R2 pair, segment dependence stated over a family of eight, one R3 pin naming its property: a null segment folds to zero so the hash reduces to the integer's own bits). Three mutations, three caught. No layout, vtable, signature or `noexcept` change. **#2332 (`needs_user`):** making `object_`/`integer_` private is a public source break (structured bindings, designated initialisers, direct assignment) with zero first-party cost and unmeasurable downstream cost; layout is unaffected because all members keep the same access. The header now documents the divergence without taking the decision. `docs/CoreOwnedFindingsReviews2317.md`. |
0 commit comments