SAIF.Framework.Common business rules¶
This page captures business rules enforced by this library.
BR-001 A date range is valid only when BeginDate is less than or equal to EndDate¶
| Field | Value |
|---|---|
| Statement | A date range is valid only when BeginDate is less than or equal to EndDate. |
| Trigger | Any range validity test |
| Enforcement | DateRangeExtensions.IsValidRange(IDateRange) — returns false for an inverted range. |
| Outcome | Callers treat an invalid range as non-overlapping / non-current. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-002 A range is "open ended" when BeginDate is DateTime.MinValue or EndDate is DateTime.MaxValue¶
| Field | Value |
|---|---|
| Statement | A range is "open ended" when BeginDate is DateTime.MinValue or EndDate is DateTime.MaxValue; these sentinels replace nulls throughout the library. |
| Trigger | Open-ended / sentinel test |
| Enforcement | DateRangeExtensions.IsOpenEnded(IDateRange), DateExtensions.IsMinOrMax(DateTime) |
| Outcome | Returns true, marking the range as unbounded on that side. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs, Common/SAIF.Common/Extensions/DateExtensions.cs |
BR-003 Two ranges with identical begin and end dates overlap only when they are not a single-day¶
| Field | Value |
|---|---|
| Statement | Two ranges with identical begin and end dates overlap only when they are not a single-day (zero-length) range. |
| Trigger | Pairwise overlap test |
| Enforcement | DateRangeExtensions.IsOverlapping |
| Outcome | A degenerate single-day range equal to another does not report as overlapping. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-004 Whether shared endpoints count as an overlap is caller-selectable¶
| Field | Value |
|---|---|
| Statement | Whether shared endpoints count as an overlap is caller-selectable: inAllowSameDateOverlap = true uses Exclusive comparison, false (default) uses Inclusive. |
| Trigger | Pairwise overlap test |
| Enforcement | DateRangeExtensions.IsOverlapping |
| Outcome | Endpoint-touching ranges report overlap by default. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-005 A set of fewer than two date ranges is always contiguous¶
| Field | Value |
|---|---|
| Statement | A set of fewer than two date ranges is always contiguous. |
| Trigger | Contiguity test |
| Enforcement | DateRangeExtensions.IsContiguous |
| Outcome | Returns true without comparison. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-006 Contiguity is evaluated on ranges sorted ascending by BeginDate, and fails on the first violating pair¶
| Field | Value |
|---|---|
| Statement | Contiguity is evaluated on ranges sorted ascending by BeginDate, and fails on the first violating pair. |
| Trigger | Contiguity test |
| Enforcement | DateRangeExtensions.IsContiguous |
| Outcome | Returns false immediately. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-007 The default contiguity mode is StartIsNextDay¶
| Field | Value |
|---|---|
| Statement | The default contiguity mode is StartIsNextDay: each range must begin exactly one day after the previous range ends. StartMatchesEnd requires exact endpoint equality; AllowGaps only forbids going backwards. |
| Trigger | Contiguity test |
| Enforcement | DateRangeExtensions.IsContiguous(IEnumerable<IDateRange>, ContiguousOptions) |
| Outcome | Mode-specific pass/fail. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-008 A set of fewer than two date ranges never self-overlaps¶
| Field | Value |
|---|---|
| Statement | A set of fewer than two date ranges never self-overlaps. |
| Trigger | Collection overlap test |
| Enforcement | DateRangeExtensions.IsOverLapping |
| Outcome | Returns false without comparison. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-009 A range whose begin and end are both sentinel values is treated as a null date and ignored during collection overlap evaluation¶
| Field | Value |
|---|---|
| Statement | A range whose begin and end are both sentinel values is treated as a null date and ignored during collection overlap evaluation. |
| Trigger | Collection overlap test |
| Enforcement | DateRangeExtensions.IsOverLapping |
| Outcome | The range is skipped. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-010 An open-ended predecessor followed by any range with a real begin date is always an overlap¶
| Field | Value |
|---|---|
| Statement | An open-ended predecessor followed by any range with a real begin date is always an overlap. |
| Trigger | Collection overlap test |
| Enforcement | DateRangeExtensions.IsOverLapping |
| Outcome | Returns true immediately. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-011 The default collection-overlap mode is DatesMayNotMatch¶
| Field | Value |
|---|---|
| Statement | The default collection-overlap mode is DatesMayNotMatch: a range beginning on or before the previous range's end date is an overlap. DatesMayMatch requires a strict <. |
| Trigger | Collection overlap test |
| Enforcement | DateRangeExtensions.IsOverLapping(IEnumerable<IDateRange>, OverlappingOptions) |
| Outcome | Mode-specific overlap verdict. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-012 The overlap of two nullable-end ranges is classified into exactly one of ten enumerated states¶
| Field | Value |
|---|---|
| Statement | The overlap of two nullable-end ranges is classified into exactly one of ten enumerated states, defaulting to NoOverlap when no condition matches. |
| Trigger | Categorical overlap classification |
| Enforcement | NullableDateRange.GetOverlapState |
| Outcome | A single DateRangeOverlapResult value. |
| Source | Common/SAIF.Common/Classes/NullableDateRange.cs, Common/SAIF.Common/Enums/DateRangeOverlapResult.cs |
BR-014 Title casing of a null or empty string is a no-op¶
| Field | Value |
|---|---|
| Statement | Title casing of a null or empty string is a no-op. |
| Trigger | Title-case request |
| Enforcement | TitleCase.ToTitleCase |
| Outcome | The input is returned unchanged. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-015 A word that already contains both uppercase and lowercase characters is assumed intentionally cased¶
| Field | Value |
|---|---|
| Statement | A word that already contains both uppercase and lowercase characters is assumed intentionally cased and is never re-cased. |
| Trigger | Per-word title casing |
| Enforcement | TitleCase.TitleCaseSingleWord |
| Outcome | The word is returned unchanged. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-016 Words in the approved acronym list¶
| Field | Value |
|---|---|
| Statement | Words in the approved acronym list (SAIF, KPD, BW, AIC, KBI, JBL, PRL, USI, ISU, AAAP, OEC, LLC, LLP, DDS, MD, DC, PPC, JV, JD, DMD, PC, LMT, LLLP, LTD, ND, OD, OT, PT, CMT, PCP, GP, VP, CEO, CFO, CST, AG, RC, MJ, PMS, II, III, IV, AC, NE, NW, SE, SW, PO, USA, CA, WA, BPOE, BHG, KOA, OHSU, AOI, TNT, MCO, GSA, AAA, IHC, ODS, OGSERP, CC) are rendered fully uppercase. |
| Trigger | Per-word title casing |
| Enforcement | TitleCase.UpperCaseWordList + TitleCaseSingleWord |
| Outcome | The word is uppercased. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-017 Minor words (is, in, the, an, of, on, and, de) are rendered fully lowercase¶
| Field | Value |
|---|---|
| Statement | Minor words (is, in, the, an, of, on, and, de) are rendered fully lowercase. |
| Trigger | Per-word title casing |
| Enforcement | TitleCase.LowerCaseWordList + TitleCaseSingleWord |
| Outcome | The word is lowercased. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-018 Known mixed-case words (PhD, LaCombe) keep their exact registered casing¶
| Field | Value |
|---|---|
| Statement | Known mixed-case words (PhD, LaCombe) keep their exact registered casing; known mixed-case prefixes (Mc, O') cause the remainder of the word to be proper-cased (McMahon, O'Brien). |
| Trigger | Per-word title casing |
| Enforcement | TitleCase.MixedCaseWordList, TitleCase.MixedCasePrefixList + TitleCaseSingleWord |
| Outcome | Canonical mixed casing is applied. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-019 A numeric word with an ordinal suffix (1st, 2nd, 3rd, 4th) keeps the suffix lowercase¶
| Field | Value |
|---|---|
| Statement | A numeric word with an ordinal suffix (1st, 2nd, 3rd, 4th) keeps the suffix lowercase. |
| Trigger | Per-word title casing |
| Enforcement | TitleCase.HasNumberSuffix / GetNumberSuffixParts / NumberSuffixes |
| Outcome | The ordinal is left lowercase. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-020 A trailing possessive 's is normalized to lowercase after casing¶
| Field | Value |
|---|---|
| Statement | A trailing possessive 's is normalized to lowercase after casing. |
| Trigger | Final title-case pass |
| Enforcement | TitleCase.ApplyExactSuffixList / MixedCaseSuffixList |
| Outcome | The suffix is re-cased. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-021 The first character of any title-cased result is always uppercase, regardless of every other rule¶
| Field | Value |
|---|---|
| Statement | The first character of any title-cased result is always uppercase, regardless of every other rule. |
| Trigger | Final title-case pass |
| Enforcement | TitleCase.ToTitleCase (final statement) |
| Outcome | First letter uppercased. |
| Source | Common/SAIF.Common/Utility/TitleCase.cs |
BR-022 A Julian date must contain at least 4 digits¶
| Field | Value |
|---|---|
| Statement | A Julian date must contain at least 4 digits. |
| Trigger | Julian validation |
| Enforcement | Julian.IsValid(int, out string) |
| Outcome | Returns false with message "The julian must contain at least 4 numbers"; ToDate escalates to ApplicationException. |
| Source | Common/SAIF.Common/Classes/Julian.cs |
BR-023 A Julian string that cannot be converted to Int32 is invalid¶
| Field | Value |
|---|---|
| Statement | A Julian string that cannot be converted to Int32 is invalid. |
| Trigger | Julian validation |
| Enforcement | Julian.IsValid(string, out string) |
| Outcome | Returns false (exception swallowed); ToDate throws ApplicationException with an empty message. |
| Source | Common/SAIF.Common/Classes/Julian.cs |
BR-024 When no millennium is supplied (or it is below 1000), the millennium is derived from the current system year's first digit x 1000, and a 1- or 2-digit Julian year is offset by it¶
| Field | Value |
|---|---|
| Statement | When no millennium is supplied (or it is below 1000), the millennium is derived from the current system year's first digit × 1000, and a 1- or 2-digit Julian year is offset by it. |
| Trigger | Julian year resolution |
| Enforcement | Julian.GetYear |
| Outcome | A 4-digit calendar year. |
| Source | Common/SAIF.Common/Classes/Julian.cs |
BR-025 Leap-year status is determined using the Julian calendar (System.Globalization.JulianCalendar), not the Gregorian calendar¶
| Field | Value |
|---|---|
| Statement | Leap-year status is determined using the Julian calendar (System.Globalization.JulianCalendar), not the Gregorian calendar. |
| Trigger | Julian year resolution |
| Enforcement | Julian.GetYear |
| Outcome | isLeapYear flag; drives the 365/366 day cap. |
| Source | Common/SAIF.Common/Classes/Julian.cs |
BR-026 The day-of-year portion of a Julian date must not exceed 365, or 366 in a leap year¶
| Field | Value |
|---|---|
| Statement | The day-of-year portion of a Julian date must not exceed 365, or 366 in a leap year. |
| Trigger | Julian decode |
| Enforcement | Julian.ToDate |
| Outcome | Throws ApplicationException("The julian day must be < {n}."). |
| Source | Common/SAIF.Common/Classes/Julian.cs |
BR-027 A Julian string is encoded as the year (2-digit by default, 4-digit on request) followed by the zero-padded 3-digit day-of-year¶
| Field | Value |
|---|---|
| Statement | A Julian string is encoded as the year (2-digit by default, 4-digit on request) followed by the zero-padded 3-digit day-of-year. |
| Trigger | Julian encode |
| Enforcement | Julian.ToJulian |
| Outcome | A Julian-format string. |
| Source | Common/SAIF.Common/Classes/Julian.cs |
BR-028 In EDI date parsing, a 6-character date is a 2-digit year and is always treated as 21st century¶
| Field | Value |
|---|---|
| Statement | In EDI date parsing, a 6-character date is a 2-digit year and is always treated as 21st century (prefixed with "20"); no 20th-century handling exists. |
| Trigger | EDI date parse |
| Enforcement | DateMaker.MakeDateTime |
| Outcome | The year resolves to 20xx. |
| Source | Common/SAIF.Common/Classes/DateMaker.cs |
BR-029 A short EDI time string is right-padded with zeros to hh24miss¶
| Field | Value |
|---|---|
| Statement | A short EDI time string is right-padded with zeros to hh24miss; the default time when none is supplied is midnight ("000000"). |
| Trigger | EDI date parse |
| Enforcement | DateMaker.MakeDateTime |
| Outcome | Missing time components default to zero. |
| Source | Common/SAIF.Common/Classes/DateMaker.cs |
BR-030 EDI file date strings use the yyyyMMdd format and the - separator between a start and an end date¶
| Field | Value |
|---|---|
| Statement | EDI file date strings use the yyyyMMdd format and the - separator between a start and an end date. |
| Trigger | EDI date format / split |
| Enforcement | DateMaker.DateFormat, DateMaker.DateSeperator, EdiFileDateString, ExtractDateRange |
| Outcome | yyyymmdd or yyyymmdd-yyyymmdd. |
| Source | Common/SAIF.Common/Classes/DateMaker.cs |
BR-031 Parsing a malformed date-range string never throws — it silently yields the unbounded default range¶
| Field | Value |
|---|---|
| Statement | Parsing a malformed date-range string never throws — it silently yields the unbounded default range. |
| Trigger | DateRange.Parse |
| Enforcement | DateRange.Parse (catch-all catch (Exception)) |
| Outcome | Returns new DateRange() = MinValue..MaxValue. |
| Source | Common/SAIF.Common/Classes/DateRange.cs |
BR-032 A DateRange constructed with no end date is unbounded forward¶
| Field | Value |
|---|---|
| Statement | A DateRange constructed with no end date is unbounded forward: EndDate defaults to DateTime.MaxValue. A DateRange with no dates at all is MinValue..MaxValue. |
| Trigger | DateRange construction |
| Enforcement | DateRange() / DateRange(DateTime) / DateRange.Empty |
| Outcome | Sentinel endpoints assigned. |
| Source | Common/SAIF.Common/Classes/DateRange.cs |
BR-033 An end date supplied as DateTime.MinValue is reinterpreted as no end date and coerced to DateTime.MaxValue¶
| Field | Value |
|---|---|
| Statement | An end date supplied as DateTime.MinValue is reinterpreted as "no end date" and coerced to DateTime.MaxValue. |
| Trigger | DateRange(DateTime, DateTime) |
| Enforcement | DateRange(DateTime beginDate, DateTime endDate) |
| Outcome | EndDate becomes MaxValue. |
| Source | Common/SAIF.Common/Classes/DateRange.cs |
BR-034 Two date ranges are equal if and only if both BeginDate and EndDate match¶
| Field | Value |
|---|---|
| Statement | Two date ranges are equal if and only if both BeginDate and EndDate match; equality against a non-IDateRange object is always false, and IsEqual treats a null counterpart as unequal. |
| Trigger | Range comparison |
| Enforcement | DateRange.operator == / != / Equals, DateRangeExtensions.IsEqual |
| Outcome | Boolean equality verdict. |
| Source | Common/SAIF.Common/Classes/DateRange.cs, Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-035 As of filtering of a range collection selects ranges containing the supplied date under the chosen inclusivity mode¶
| Field | Value |
|---|---|
| Statement | "As of" filtering of a range collection selects ranges containing the supplied date under the chosen inclusivity mode. |
| Trigger | AsOf / IsInRange |
| Enforcement | DateRangeExtensions.AsOf<T>, DateRangeExtensions.IsInRange |
| Outcome | A filtered deferred sequence. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-036 The default date-in-range inclusivity is FirstOnly — begin date inclusive, end date exclusive¶
| Field | Value |
|---|---|
| Statement | The default date-in-range inclusivity is FirstOnly — begin date inclusive, end date exclusive. |
| Trigger | IsInRange / IsBetween |
| Enforcement | DateRangeExtensions.IsInRange, IComparableExtensions.IsBetween |
| Outcome | Begin-inclusive, end-exclusive membership. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs, Common/SAIF.Common/Extensions/IComparableExtensions.cs |
BR-037 A null date is never in range¶
| Field | Value |
|---|---|
| Statement | A null date is never in range. |
| Trigger | IsInRange(DateTime?, IncludeComparisons) |
| Enforcement | DateRangeExtensions.IsInRange(IDateRange, DateTime?, IncludeComparisons) |
| Outcome | Returns false. |
| Source | Common/SAIF.Common/Extensions/DateRangeExtensions.cs |
BR-038 A guard call must itself supply a non-blank parameter name¶
| Field | Value |
|---|---|
| Statement | A guard call must itself supply a non-blank parameter name. |
| Trigger | Guard invocation |
| Enforcement | Enforce.ArgumentNotNull |
| Outcome | Throws ArgumentNullException("parameterName"). |
| Source | Common/SAIF.Common/Classes/Enforce.cs |
BR-039 A guarded argument must not be null¶
| Field | Value |
|---|---|
| Statement | A guarded argument must not be null. |
| Trigger | Guard invocation |
| Enforcement | Enforce.ArgumentNotNull (and every method that calls it) |
| Outcome | Throws ArgumentNullException(parameterName). |
| Source | Common/SAIF.Common/Classes/Enforce.cs |
BR-040 A guarded string argument must not be empty¶
| Field | Value |
|---|---|
| Statement | A guarded string argument must not be empty. |
| Trigger | Guard invocation |
| Enforcement | Enforce.ArgumentNotNullOrEmpty |
| Outcome | Throws ArgumentException("Parameter ({name}) cannot be null or empty."). |
| Source | Common/SAIF.Common/Classes/Enforce.cs |
BR-041 A guarded string argument must not be all white space¶
| Field | Value |
|---|---|
| Statement | A guarded string argument must not be all white space. |
| Trigger | Guard invocation |
| Enforcement | Enforce.ArgumentNotNullOrWhiteSpace |
| Outcome | Throws ArgumentException("Parameter ({name}) cannot be null or white space."). |
| Source | Common/SAIF.Common/Classes/Enforce.cs |
BR-042 A guarded state value (as opposed to an argument) must not be null¶
| Field | Value |
|---|---|
| Statement | A guarded state value (as opposed to an argument) must not be null; the violation is an InvalidOperationException, not an ArgumentNullException. |
| Trigger | State guard |
| Enforcement | Enforce.NotNull<T> → GenericExtensions.IfNullThrow<T,TException> |
| Outcome | Throws InvalidOperationException("The type {FullName} cannot be null."). |
| Source | Common/SAIF.Common/Classes/Enforce.cs, Common/SAIF.Common/Extensions/GenericExtensions.cs |
BR-043 A caller-supplied boolean or Func condition must evaluate true, or the caller-specified exception type/instance is thrown¶
| Field | Value |
|---|---|
| Statement | A caller-supplied boolean or Func<bool> condition must evaluate true, or the caller-specified exception type/instance is thrown. |
| Trigger | Condition guard |
| Enforcement | Enforce.True (4 overloads) |
| Outcome | Throws T or the supplied Exception. |
| Source | Common/SAIF.Common/Classes/Enforce.cs |
BR-044 A SaifValidationException always exposes a non-null IValidationResult¶
| Field | Value |
|---|---|
| Statement | A SaifValidationException always exposes a non-null IValidationResult. |
| Trigger | Validation exception construction |
| Enforcement | SaifValidationException constructors |
| Outcome | ValidationResult is never null. |
| Source | Common/SAIF.Common/Classes/SaifValidationException.cs |
BR-045 A validation exception built from a bare message carries a single failure whose PropertyName is the empty string, and the two-argument overload binds the failure to a named property¶
| Field | Value |
|---|---|
| Statement | A validation exception built from a bare message carries a single failure whose PropertyName is the empty string; the two-argument overload binds the failure to a named property. |
| Trigger | Validation exception construction |
| Enforcement | SaifValidationException.CreateValidationResult |
| Outcome | A one-failure ValidationResult. |
| Source | Common/SAIF.Common/Classes/SaifValidationException.cs |
BR-046 A ValidationResult always exposes non-null Errors and Messages collections, even when constructed from a null failure sequence¶
| Field | Value |
|---|---|
| Statement | A ValidationResult always exposes non-null Errors and Messages collections, even when constructed from a null failure sequence. |
| Trigger | ValidationResult construction |
| Enforcement | ValidationResult() / ValidationResult(IEnumerable<ValidationFailure>) |
| Outcome | Empty lists rather than nulls. |
| Source | Common/SAIF.Common/Classes/ValidationResult.cs |
BR-047 A validation result is valid if and only if it holds no errors¶
| Field | Value |
|---|---|
| Statement | A validation result is valid if and only if it holds no errors; informational Messages never affect validity. |
| Trigger | IsValid read |
| Enforcement | ValidationResult.IsValid (virtual) |
| Outcome | !Errors.Any(). |
| Source | Common/SAIF.Common/Classes/ValidationResult.cs |
BR-048 Each context failure condition maps to a fixed default target view name¶
| Field | Value |
|---|---|
| Statement | Each context failure condition maps to a fixed default target view name: NoContext, InvalidContext, InvalidContextForUser, DisabledView; a caller may override the view, and DisabledViewException may additionally carry the model for redisplay. |
| Trigger | Context exception construction |
| Enforcement | NoContextException, InvalidContextException, InvalidContextForUserException, DisabledViewException constructors; ContextException.TargetView / Model |
| Outcome | TargetView (and optionally Model) is populated for the presentation layer to route on. |
| Source | Common/SAIF.Common/Classes/ContextException.cs |
BR-049 Formatting a null exception yields an empty string rather than throwing¶
| Field | Value |
|---|---|
| Statement | Formatting a null exception yields an empty string rather than throwing. |
| Trigger | Exception formatting |
| Enforcement | ExceptionExtensions.GetFullFormattedException, GetErrorLogFormattedException |
| Outcome | Returns string.Empty. |
| Source | Common/SAIF.Common/Extensions/ExceptionExtensions.cs |
BR-050 Exception chains are reported innermost (root cause) first by default¶
| Field | Value |
|---|---|
| Statement | Exception chains are reported innermost (root cause) first by default. |
| Trigger | Exception formatting |
| Enforcement | ExceptionExtensions.GetFullFormattedException(innerToOuter = true), GetErrorLogFormattedException |
| Outcome | The exception list is reversed before rendering. |
| Source | Common/SAIF.Common/Extensions/ExceptionExtensions.cs |
BR-051 The formatted exception report renders a summary block (type, message, source) for every exception¶
| Field | Value |
|---|---|
| Statement | The formatted exception report renders a summary block (type, message, source) for every exception in the chain, followed by a separate stack-trace block per exception; empty Source and empty StackTrace values are omitted. |
| Trigger | Exception formatting |
| Enforcement | ExceptionExtensions.GetFullFormattedException |
| Outcome | A two-part formatted report. |
| Source | Common/SAIF.Common/Extensions/ExceptionExtensions.cs |
BR-052 An error-log entry begins with the innermost exception's message, followed by the sentinel @#ELOG#@, followed by the full report, so a log reader can split summary from detail¶
| Field | Value |
|---|---|
| Statement | An error-log entry begins with the innermost exception's message, followed by the sentinel @#ELOG#@, followed by the full report — so a log reader can split summary from detail. |
| Trigger | Error-log formatting |
| Enforcement | ExceptionExtensions.GetErrorLogFormattedException, ExceptionExtensions.ErrorLogMessageString |
| Outcome | Sentinel-delimited log string. |
| Source | Common/SAIF.Common/Extensions/ExceptionExtensions.cs |
BR-053 A string search pattern over a binary payload is encoded as ASCII, not Unicode¶
| Field | Value |
|---|---|
| Statement | A string search pattern over a binary payload is encoded as ASCII, not Unicode. |
| Trigger | Byte search |
| Enforcement | ByteExtensions.Search(byte[], string, int, int) |
| Outcome | Encoding.ASCII.GetBytes needle. |
| Source | Common/SAIF.Common/Extensions/ByteExtensions.cs |
BR-054 A byte search returns -1 when the haystack is empty, the needle is empty, the needle is longer than the haystack, or the start offset leaves too few bytes¶
| Field | Value |
|---|---|
| Statement | A byte search returns -1 when the haystack is empty, the needle is empty, the needle is longer than the haystack, or the start offset leaves too few bytes. |
| Trigger | Byte search |
| Enforcement | ByteExtensions.Search(byte[], byte[], int, int) |
| Outcome | Returns -1 (not found). |
| Source | Common/SAIF.Common/Extensions/ByteExtensions.cs |
BR-055 A byte search may be capped¶
| Field | Value |
|---|---|
| Statement | A byte search may be capped: when maxBytesToSearch > 0 the scan stops once the index exceeds that cap. Header stripping caps the scan at 100 bytes by default. |
| Trigger | Byte search / header strip |
| Enforcement | ByteExtensions.Search, ByteExtensions.StripHeader |
| Outcome | The scan terminates early; a signature beyond the cap is reported not-found. |
| Source | Common/SAIF.Common/Extensions/ByteExtensions.cs |
BR-056 A header is stripped only when the signature is found at a position greater than zero¶
| Field | Value |
|---|---|
| Statement | A header is stripped only when the signature is found at a position greater than zero; a payload already starting at its signature (or with no signature) is returned unchanged with found = false. |
| Trigger | Header strip |
| Enforcement | ByteExtensions.StripHeader |
| Outcome | Original or forward-skipped array, plus the found flag. |
| Source | Common/SAIF.Common/Extensions/ByteExtensions.cs |
BR-057 Disposable cryptographic and stream resources are created inside using blocks so they are released deterministically¶
| Field | Value |
|---|---|
| Statement | Disposable cryptographic and stream resources are created inside using blocks so they are released deterministically. |
| Trigger | Hashing / stream materialisation |
| Enforcement | ByteExtensions.GetMD5Hash, FileExtensions.ToByteArray |
| Outcome | Resources disposed on scope exit. |
| Source | Common/SAIF.Common/Extensions/ByteExtensions.cs, Common/SAIF.Common/Extensions/FileExtensions.cs |
BR-058 JSON serialization uses JavaScriptSerializer (System.Web.Extensions) and XML serialization uses XmlSerializer; serializable targets must be reference types (where T : class) except for SaveToJson, which is unconstrained¶
| Field | Value |
|---|---|
| Statement | JSON serialization uses JavaScriptSerializer (System.Web.Extensions) and XML serialization uses XmlSerializer; serializable targets must be reference types (where T : class) except for SaveToJson<T>, which is unconstrained. |
| Trigger | Serialization |
| Enforcement | Serializer (all members), GenericExtensions.ToJsonString / ToXmlString, DotNet40Extensions |
| Outcome | Serializer selection and type constraint. |
| Source | Common/SAIF.Common/Classes/Serializer.cs, Common/SAIF.Common/Extensions/GenericExtensions.cs, Common/SAIF.Common/Extensions/DotNet40Extensions.cs |
BR-059 XML deserialization that produces an incompatible type yields null rather than throwing, because the result is cast with as T¶
| Field | Value |
|---|---|
| Statement | XML deserialization that produces an incompatible type yields null rather than throwing, because the result is cast with as T. |
| Trigger | XML load |
| Enforcement | Serializer.LoadFromXml<T> |
| Outcome | Returns null. |
| Source | Common/SAIF.Common/Classes/Serializer.cs |
BR-060 Serialized output written to a file is explicitly flushed before the writer is disposed¶
| Field | Value |
|---|---|
| Statement | Serialized output written to a file is explicitly flushed before the writer is disposed. |
| Trigger | Save to file |
| Enforcement | Serializer.SaveToJson<T>, Serializer.SaveToXml<T> |
| Outcome | writer.Flush() is called. |
| Source | Common/SAIF.Common/Classes/Serializer.cs |
BR-061 A property-selector lambda must select a member, not invoke a method¶
| Field | Value |
|---|---|
| Statement | A property-selector lambda must select a member, not invoke a method. |
| Trigger | Property expression resolution |
| Enforcement | ExpressionExtensions.GetPropertyInfo |
| Outcome | Throws ArgumentException("Expression '{propertyLambda}' refers to a method, not a property."). |
| Source | Common/SAIF.Common/Extensions/ExpressionExtensions.cs |
BR-062 A property-selector lambda must select a property, not a field¶
| Field | Value |
|---|---|
| Statement | A property-selector lambda must select a property, not a field. |
| Trigger | Property expression resolution |
| Enforcement | ExpressionExtensions.GetPropertyInfo |
| Outcome | Throws ArgumentException("Expression '{propertyLambda}' refers to a field, not a property."). |
| Source | Common/SAIF.Common/Extensions/ExpressionExtensions.cs |
BR-063 The selected property must be declared on the target type or one of its base types¶
| Field | Value |
|---|---|
| Statement | The selected property must be declared on the target type or one of its base types. |
| Trigger | Property expression resolution |
| Enforcement | ExpressionExtensions.GetPropertyInfo |
| Outcome | Throws ArgumentException("Expresion '{propertyLambda}' refers to a property that is not from type {T}."). |
| Source | Common/SAIF.Common/Extensions/ExpressionExtensions.cs |
BR-064 A property may be set only when it has a setter, but that setter may be non-public - reflective assignment deliberately bypasses accessibility¶
| Field | Value |
|---|---|
| Statement | A property may be set only when it has a setter, but that setter may be non-public — reflective assignment deliberately bypasses accessibility. |
| Trigger | Reflective property assignment |
| Enforcement | ExpressionExtensions.SetPropertyValue |
| Outcome | Throws ArgumentException("Expresion '{propInfo.Name}' refers to a property that is not settable."), or assigns through the non-public setter. |
| Source | Common/SAIF.Common/Extensions/ExpressionExtensions.cs |
BR-065 A sequence generator starts at the caller-supplied start value, defaulting to 0, and may be repositioned at any time via SetValue¶
| Field | Value |
|---|---|
| Statement | A sequence generator starts at the caller-supplied start value, defaulting to 0, and may be repositioned at any time via SetValue. |
| Trigger | Sequence construction / reposition |
| Enforcement | SequenceGenerator<T> ctor and SetValue, Sequence ctor, LongSequence ctor |
| Outcome | CurrentValue assigned. |
| Source | Common/SAIF.Common/Classes/SequenceGenerator.cs, Common/SAIF.Common/Classes/Sequence.cs, Common/SAIF.Common/Classes/LongSequence.cs |
BR-066 NextValue is pre-increment¶
| Field | Value |
|---|---|
| Statement | NextValue is pre-increment: it advances the counter by 1 and returns the new value, so the first value issued from a zero start is 1. |
| Trigger | Sequence read |
| Enforcement | Sequence.NextValue, LongSequence.NextValue |
| Outcome | Incremented value returned. |
| Source | Common/SAIF.Common/Classes/Sequence.cs, Common/SAIF.Common/Classes/LongSequence.cs |
BR-067 Reset() returns the counter to literal zero, not to the original start value¶
| Field | Value |
|---|---|
| Statement | Reset() returns the counter to literal zero, not to the original start value. |
| Trigger | Sequence reset |
| Enforcement | Sequence.Reset, LongSequence.Reset |
| Outcome | CurrentValue = 0. |
| Source | Common/SAIF.Common/Classes/Sequence.cs, Common/SAIF.Common/Classes/LongSequence.cs |
BR-068 The application name is URL segment 1¶
| Field | Value |
|---|---|
| Statement | The application name is URL segment 1; a null URI or a URI with fewer than 2 segments yields an empty string rather than an error. |
| Trigger | URL parse |
| Enforcement | UriExtensions.ApplicationName |
| Outcome | Segment 1 without slashes, or string.Empty. |
| Source | Common/SAIF.Common/Extensions/UriExtensions.cs |
BR-069 The page name is URL segment 2, defaulting to "Index" when absent¶
| Field | Value |
|---|---|
| Statement | The page name is URL segment 2, defaulting to "Index" when absent; unlike the other URI helpers, this member throws on a null URI. |
| Trigger | URL parse |
| Enforcement | UriExtensions.PageName |
| Outcome | Segment 2 or "Index"; ArgumentNullException on null. |
| Source | Common/SAIF.Common/Extensions/UriExtensions.cs |
BR-070 The default controller/action route is Home/Index¶
| Field | Value |
|---|---|
| Statement | The default controller/action route is Home/Index; with 3 segments the action defaults to Index; with 4 or more, segments 2 and 3 are the controller and action. |
| Trigger | URL parse |
| Enforcement | UriExtensions.ControllerAndActionName |
| Outcome | A controller/action string. |
| Source | Common/SAIF.Common/Extensions/UriExtensions.cs |
BR-071 A Windows identity name is interpreted as DOMAIN\user¶
| Field | Value |
|---|---|
| Statement | A Windows identity name is interpreted as DOMAIN\user: the account id is the last backslash-delimited segment and the domain is the first. |
| Trigger | Identity parse |
| Enforcement | SecurityPrincipalExtensions.UserId, SecurityPrincipalExtensions.Domain |
| Outcome | Account or domain substring. |
| Source | Common/SAIF.Common/Extensions/SecurityPrincipalExtensions.cs |
BR-072 A string is truthy only when it uppercases to one of Y, YES, TRUE, 1, or ON¶
| Field | Value |
|---|---|
| Statement | A string is truthy only when it uppercases to one of Y, YES, TRUE, 1, or ON; null and every other value are false. |
| Trigger | String-to-boolean conversion |
| Enforcement | StringExtensions.ToBoolean |
| Outcome | true / false. |
| Source | Common/SAIF.Common/Extensions/StringExtensions.cs |
BR-073 Booleans render to flags as Y/N, YES/NO, or On/Off depending on the method chosen¶
| Field | Value |
|---|---|
| Statement | Booleans render to flags as Y/N, YES/NO, or On/Off depending on the method chosen. |
| Trigger | Boolean-to-string conversion |
| Enforcement | BooleanExtensions.ToY_N_String, ToYesNoString, ToOnOffString |
| Outcome | The corresponding flag string. |
| Source | Common/SAIF.Common/Extensions/BooleanExtensions.cs |
BR-074 Non-ASCII typographic characters are folded to ASCII equivalents before text leaves the system¶
| Field | Value |
|---|---|
| Statement | Non-ASCII typographic characters are folded to ASCII equivalents before text leaves the system: curly single quotes → ', curly/angled double quotes → ", bullet glyphs → *, all dash/hyphen variants → -, and NUL → space. A null input yields an empty string. |
| Trigger | Text normalization |
| Enforcement | StringExtensions.ReplaceUnicodeCharactersWithAscii |
| Outcome | ASCII-safe string. |
| Source | Common/SAIF.Common/Extensions/StringExtensions.cs |
BR-075 Character-class filters retain (or, for the Remove variants, exclude) exactly the characters matching the corresponding char.Is predicate, and treat a null input as an empty string¶
| Field | Value |
|---|---|
| Statement | Character-class filters retain (or, for the Remove* variants, exclude) exactly the characters matching the corresponding char.Is* predicate, and treat a null input as an empty string. |
| Trigger | Text filtering |
| Enforcement | StringUtilities.Digits / Letters / LettersOrDigits / Punctuation / Symbols / ControlCharacters / RemoveControlCharacters / RemoveWhiteSpaces |
| Outcome | A filtered string, never null. |
| Source | Common/SAIF.Common/Utility/StringUtilities.cs |
BR-076 Truncate is length-safe (clamps and tolerates null)¶
| Field | Value |
|---|---|
| Statement | Truncate is length-safe (clamps and tolerates null); Left and Right are not and will throw if the requested length exceeds the string. |
| Trigger | String slicing |
| Enforcement | StringExtensions.Truncate, Left, Right |
| Outcome | Clamped substring, or ArgumentOutOfRangeException. |
| Source | Common/SAIF.Common/Extensions/StringExtensions.cs |
BR-077 A string contains only numeric characters only when it is non-empty and contains no non-digit character at all¶
| Field | Value |
|---|---|
| Statement | A string "contains only numeric characters" only when it is non-empty and contains no non-digit character at all (regex \D must not match). |
| Trigger | Numeric-content test |
| Enforcement | StringExtensions.ContainsOnlyNumericCharacters |
| Outcome | true / false. |
| Source | Common/SAIF.Common/Extensions/StringExtensions.cs |
BR-078 A null or whitespace decimal string converts to zero rather than raising an error¶
| Field | Value |
|---|---|
| Statement | A null or whitespace decimal string converts to zero rather than raising an error. |
| Trigger | Decimal conversion |
| Enforcement | StringExtensions.ConvertToDecimalOrZero |
| Outcome | 0 for blank input. |
| Source | Common/SAIF.Common/Extensions/StringExtensions.cs |
BR-079 The IsNumeric test is 32-bit-integer-bounded¶
| Field | Value |
|---|---|
| Statement | The IsNumeric test is 32-bit-integer-bounded: decimals and values beyond Int32.MaxValue report as non-numeric. |
| Trigger | Numeric test |
| Enforcement | NumericExtensions.IsNumeric |
| Outcome | int.TryParse result. |
| Source | Common/SAIF.Common/Extensions/NumericExtensions.cs |
BR-080 A decimal's precision is its declared scale, read from the scale byte extracted via BitConverter and decimal.GetBits, with trailing zeros counted¶
| Field | Value |
|---|---|
| Statement | A decimal's precision is its declared scale, read from the scale byte of BitConverter.GetBytes(decimal.GetBits(decimalValue)[3])[2]; trailing zeros count. |
| Trigger | Precision read |
| Enforcement | NumericExtensions.Precision |
| Outcome | Decimal-place count. |
| Source | Common/SAIF.Common/Extensions/NumericExtensions.cs |
BR-081 Reducing a decimal's precision truncates¶
| Field | Value |
|---|---|
| Statement | Reducing a decimal's precision truncates; it never rounds. |
| Trigger | Precision reduction |
| Enforcement | NumericExtensions.TruncatePrecision |
| Outcome | Truncated decimal. |
| Source | Common/SAIF.Common/Extensions/NumericExtensions.cs |
BR-082 Merging dictionaries overwrites existing keys by default¶
| Field | Value |
|---|---|
| Statement | Merging dictionaries overwrites existing keys by default; passing replaceExisting = false preserves the target's existing values. |
| Trigger | Dictionary merge |
| Enforcement | DictionaryExtensions.Merge<TValue> |
| Outcome | Target dictionary mutated accordingly. |
| Source | Common/SAIF.Common/Extensions/DictionaryExtensions.cs |
BR-083 A dictionary read with a supplied default never throws on a missing key¶
| Field | Value |
|---|---|
| Statement | A dictionary read with a supplied default never throws on a missing key. |
| Trigger | Dictionary read |
| Enforcement | DictionaryExtensions.TryGetValue<TKey,TValue> |
| Outcome | Stored value or the default. |
| Source | Common/SAIF.Common/Extensions/DictionaryExtensions.cs |
BR-084 An upsert overwrites an existing key or adds a new one and always returns the supplied value¶
| Field | Value |
|---|---|
| Statement | An upsert overwrites an existing key or adds a new one and always returns the supplied value. |
| Trigger | Dictionary upsert |
| Enforcement | DictionaryExtensions.UpdateOrAdd<TKey,TValue> |
| Outcome | Dictionary updated; value returned. |
| Source | Common/SAIF.Common/Extensions/DictionaryExtensions.cs |
BR-085 Joining a sequence to a string requires a non-null separator¶
| Field | Value |
|---|---|
| Statement | Joining a sequence to a string requires a non-null separator (and, for the projection overload, a non-null projection function); elements are rendered with ToString() unless a projection is supplied. |
| Trigger | Sequence-to-string |
| Enforcement | IEnumerableExtensions.ToStringAll (2 overloads) |
| Outcome | Joined string, or ArgumentNullException. |
| Source | Common/SAIF.Common/Extensions/IEnumerableExtensions.cs |
BR-086 Taking N items from the end of a sequence preserves their original relative order¶
| Field | Value |
|---|---|
| Statement | Taking N items from the end of a sequence preserves their original relative order. |
| Trigger | Trailing take |
| Enforcement | IEnumerableExtensions.TakeFromEnd<T> |
| Outcome | The last N items in source order. |
| Source | Common/SAIF.Common/Extensions/IEnumerableExtensions.cs |
BR-087 Key-based distinct retains the first occurrence of each key¶
| Field | Value |
|---|---|
| Statement | Key-based distinct retains the first occurrence of each key. |
| Trigger | Keyed distinct |
| Enforcement | IEnumerableExtensions.Distinct<TSource,TKey> |
| Outcome | One element per distinct key. |
| Source | Common/SAIF.Common/Extensions/IEnumerableExtensions.cs |
BR-088 Outer joins compare keys with the caller's IEqualityComparer when supplied, otherwise EqualityComparer.Default, and substitute caller-supplied defaults for unmatched sides¶
| Field | Value |
|---|---|
| Statement | Outer joins compare keys with the caller's IEqualityComparer<TKey> when supplied, otherwise EqualityComparer<TKey>.Default, and substitute caller-supplied defaults for unmatched sides. |
| Trigger | Outer join |
| Enforcement | IEnumerableExtensions.FullOuterGroupJoin, FullOuterJoin, LeftOuterJoin |
| Outcome | Projected join results including unmatched rows. |
| Source | Common/SAIF.Common/Extensions/IEnumerableExtensions.cs |
BR-089 Converting a stream to a byte array copies from the stream's current position - the caller owns rewinding - and does not dispose the source stream¶
| Field | Value |
|---|---|
| Statement | Converting a stream to a byte array copies from the stream's current position — the caller owns rewinding — and does not dispose the source stream. |
| Trigger | Stream materialisation |
| Enforcement | FileExtensions.ToByteArray |
| Outcome | Byte array from the current position onward. |
| Source | Common/SAIF.Common/Extensions/FileExtensions.cs |
BR-090 Property reflection returns only the type's public properties¶
| Field | Value |
|---|---|
| Statement | Property reflection returns only the type's public properties (default Type.GetProperties() binding flags). |
| Trigger | Property enumeration |
| Enforcement | Reflector.GetPropertyInfos<TTarget> |
| Outcome | PropertyInfo[] of public properties. |
| Source | Common/SAIF.Common/Classes/Reflector.cs |
BR-091 A log file must be registered against a log id before it can be written to, and log writers are retrieved by that id¶
| Field | Value |
|---|---|
| Statement | A log file must be registered against a log id before it can be written to, and log writers are retrieved by that id. |
| Trigger | Log contract usage |
| Enforcement | IConsoleLogService.RegisterLogFile / GetLog(string) / Logs / Flush / LogException; ILogWriter.SetTimeFormat / SetTimeDelimiter / SetWriteLinePrefix |
| Outcome | Contract obligation on the implementing assembly* |
| Source | Common/SAIF.Common/Interfaces/IConsoleLogService.cs, Common/SAIF.Common/Interfaces/ILogWriter.cs |
BR-092 A console application may address at most five conventional log files (File0-File4) plus a Custom category¶
| Field | Value |
|---|---|
| Statement | A console application may address at most five conventional log files (File0–File4) plus a Custom category. |
| Trigger | Log contract usage |
| Enforcement | SAIF.Common.Enums.LogType, IConsoleLogService.GetLog(LogType) |
| Outcome | Bounded set of well-known log slots. |
| Source | Common/SAIF.Common/Enums/LogType.cs, Common/SAIF.Common/Interfaces/IConsoleLogService.cs |
BR-093 WriteToAppLog appends a line break by default when addressed by string log id, but not by default when addressed by LogType¶
| Field | Value |
|---|---|
| Statement | WriteToAppLog appends a line break by default when addressed by string log id, but not by default when addressed by LogType. |
| Trigger | Log contract usage |
| Enforcement | IConsoleLogService.WriteToAppLog (2 overloads) |
| Outcome | Differing default line-break behavior between overloads. |
| Source | Common/SAIF.Common/Interfaces/IConsoleLogService.cs |
BR-094 Log services and log writers are disposable resources and must be released by the consuming application¶
| Field | Value |
|---|---|
| Statement | Log services and log writers are disposable resources and must be released by the consuming application. |
| Trigger | Log contract usage |
| Enforcement | IConsoleLogService : IDisposable, ILogWriter : IDisposable |
| Outcome | Contract obligation on the implementing assembly* |
| Source | Common/SAIF.Common/Interfaces/IConsoleLogService.cs, Common/SAIF.Common/Interfaces/ILogWriter.cs |
Notes¶
- Mark inferred statements with
*and unknown statements with**. Do not use bold inline confidence labels.
Related documentation¶
Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.
Sources¶
Common/SAIF.Common/Utility/TitleCase.csCommon/SAIF.Common/Classes/Julian.csCommon/SAIF.Common/Classes/DateMaker.csCommon/SAIF.Common/Classes/DateRange.csCommon/SAIF.Common/Extensions/DateRangeExtensions.csCommon/SAIF.Common/Extensions/IComparableExtensions.csCommon/SAIF.Common/Classes/Enforce.csCommon/SAIF.Common/Extensions/GenericExtensions.csCommon/SAIF.Common/Classes/SaifValidationException.csCommon/SAIF.Common/Classes/ValidationResult.csCommon/SAIF.Common/Classes/ContextException.csCommon/SAIF.Common/Extensions/ExceptionExtensions.csCommon/SAIF.Common/Extensions/ByteExtensions.csCommon/SAIF.Common/Extensions/FileExtensions.csCommon/SAIF.Common/Classes/Serializer.csCommon/SAIF.Common/Extensions/DotNet40Extensions.csCommon/SAIF.Common/Extensions/ExpressionExtensions.csCommon/SAIF.Common/Classes/SequenceGenerator.csCommon/SAIF.Common/Classes/Sequence.csCommon/SAIF.Common/Classes/LongSequence.csCommon/SAIF.Common/Extensions/UriExtensions.csCommon/SAIF.Common/Extensions/SecurityPrincipalExtensions.csCommon/SAIF.Common/Extensions/StringExtensions.csCommon/SAIF.Common/Extensions/BooleanExtensions.csCommon/SAIF.Common/Utility/StringUtilities.csCommon/SAIF.Common/Extensions/NumericExtensions.csCommon/SAIF.Common/Extensions/DictionaryExtensions.csCommon/SAIF.Common/Extensions/IEnumerableExtensions.csCommon/SAIF.Common/Classes/Reflector.csCommon/SAIF.Common/Interfaces/IConsoleLogService.csCommon/SAIF.Common/Interfaces/ILogWriter.csCommon/SAIF.Common/Enums/LogType.cs