Skip to content

Commit 9cf7a33

Browse files
committed
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.
1 parent 2de76a6 commit 9cf7a33

1 file changed

Lines changed: 1 addition & 1 deletion

File tree

audit/AUDIT_FINDINGS_INDEX.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ implementation ticket.
7777
| [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`. |
7878
| [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. |
7979
| [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`. |
8181
| [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. |
8282
| [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`. |
8383
| [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

Comments
 (0)