SAIF.Agency.Api is the only writer of the AGENCY schema, so the rules below
are the whole of SAIF's agreed behaviour for agency data: what a valid agency
number looks like, which agencies and agents are visible to a reader, and what
must be true before an agency's contact details may be changed. Contact field
validation is delegated to SAIF.Framework.Validation and keeps that library's
VAL- identifiers; every other rule is enforced by AgencyService and is
reported to the caller as a 400, 404, or 409 response.
An agency number is exactly six numeric digits with no separators or alphabetic characters, because it is the key the whole distribution channel is filed under and a malformed value cannot identify an agency.
Trigger
Evaluated in the action body, immediately after model binding and before any service call.
Enforcement
AgencyApiBase.ValidateAgencyNumber applies the ^[0-9]{6}$ pattern and returns ValidationFailure when it does not match.
On pass, processing continues. On failure the action returns 400 Bad Request with codeAGENCY_NUMBER_FORMAT; no database call is made and nothing is logged beyond the request entry.
BR-002 Agency search must be bounded by a producer code¶
Field
Value
Statement
A search must supply a producer code of one to ten characters. An unbounded search would return the entire distribution channel and is refused, because no consumer has a business need for it and the query would scan the whole AGENCY table.
Trigger
Evaluated in the action body of SearchAgencies, before the repository query is built.
Enforcement
AgenciesController.SearchAgencies rejects a null, empty, whitespace, or over-long producerCode through AgencyApiBase.ValidationFailure.
On pass the search runs. On failure the caller receives 400 Bad Request with codePRODUCER_CODE_REQUIRED; a search that is valid but matches nothing returns 200 OK with an empty array, which is not a failure.
BR-003 Terminated agencies are excluded unless explicitly requested¶
Field
Value
Statement
A terminated agency is invisible to reads by default, so that a consumer attributing new business cannot place it with an agency that no longer holds an appointment; a caller that genuinely needs historical data must ask for it.
Trigger
Evaluated in the service layer when the query filter is assembled, on every read of agency data.
Enforcement
AgencyService.BuildAgencyFilter adds STATUS <> 'Terminated' unless the caller passed includeTerminated=true on the lookup or activeOnly=false on the search.
A filtered-out agency is reported as 404 Not Found with codeAGENCY_NOT_FOUND on the lookup endpoint and is simply absent from the array on the search endpoints. The two behaviours differ deliberately: a lookup names one agency, a search does not.
Source
src/SAIF.Agency.Api.Services/AgencyService.cs:58
BR-004 An agency roster returns only agents with an active appointment¶
Field
Value
Statement
The roster of an agency is the set of agents whose appointment at that agency is in effect on the evaluation date. An agent who has left, or whose appointment has not yet started, is not part of the roster even though the appointment record is retained.
Trigger
Evaluated in the service layer when the roster query runs, against the asOfDate supplied by the caller or the current date.
Enforcement
AgencyService.GetAgentsForAgency joins AGENCY_APPOINTMENT on EFFECTIVE_DATE <= :asOf and (END_DATE IS NULL OR END_DATE > :asOf).
Matching agents are returned as 200 OK with an array; an agency with no agents in effect returns 200 OK with an empty array rather than 404, because the agency itself exists.
Source
src/SAIF.Agency.Api.Services/AgencyService.cs:131
BR-005 Contact details must be a deliverable email address and phone number¶
Field
Value
Statement
An agency's contact email must be a syntactically deliverable address of at most 255 characters, and its contact phone must reduce to exactly ten digits once punctuation is removed, because renewal notices and claim correspondence are sent to these values without further checking.†
Trigger
Evaluated in the service layer when the contact update is applied, before the agency's state is checked.
Enforcement
Delegated to SAIF.Framework.Validation rules VAL-051 (email form) and VAL-052 (ten digit phone), applied by ContactValidator.Validate and raised to the caller as AgencyValidationException.
On pass the update continues to BR-006. On failure AgencyApiExceptionFilter returns 400 Bad Request with codeCONTACT_INVALID and one details entry per failing field, naming VAL-051 or VAL-052; nothing is written.
Source
src/SAIF.Agency.Api.Services/AgencyService.cs:188
BR-006 Contact details may only be changed on an active agency¶
Field
Value
Statement
Contact details may be changed only while the agency's status is Active. A suspended or terminated agency's contact block is frozen as the record of where correspondence was sent while the appointment was in force.
Trigger
Evaluated in the service layer after field validation passes and before the agency is updated.
Enforcement
AgencyService.UpdateContact raises AgencyStateConflictException when AGENCY.STATUS is anything other than Active.
On pass the update proceeds. On failure AgencyApiExceptionFilter returns 409 Conflict with codeAGENCY_NOT_ACTIVE; the unit of work is rolled back and no audit row is written.
Source
src/SAIF.Agency.Api.Services/AgencyService.cs:197
BR-007 Every accepted contact change writes an audit row¶
Field
Value
Statement
An accepted change to an agency's contact block must record what the values were, what they became, who changed them, and when, so that a misdirected renewal notice can be traced to the change that caused it.
Trigger
Evaluated at persistence, inside the same unit of work that writes the new contact values.
Enforcement
AgencyService.UpdateContact inserts an AGENCY_CONTACT_AUDIT row carrying the previous and new values, CHANGED_BY from AgencyApiBase.Caller, and the request's correlation id, then commits both statements together.
The caller receives 204 No Content only when both writes commit. If the audit insert fails the whole unit of work rolls back and the caller receives 500 Internal Server Error; a contact change is never persisted without its audit row.
Source
src/SAIF.Agency.Api.Services/AgencyService.cs:221
BR-008 A contact update must carry the expected agency version¶
Field
Value
Statement
An update must state the agency version the caller read, and is refused if the stored version has moved on. Two portal users editing the same agency must not silently overwrite each other's address changes.
Trigger
Evaluated at persistence, when the update statement is issued against the version column.
Enforcement
AgencyRepository.UpdateContact includes VERSION = :expectedVersion in the WHERE clause and increments VERSION on success; a zero row count raises StaleAgencyVersionException.
On pass, the new version is returned in the X-Agency-Version response header with 204 No Content. On failure the action catches the exception, attaches the current version, and AgencyApiExceptionFilter returns 409 Conflict with codeAGENCY_VERSION_STALE; the caller must re-read the agency and resubmit.
Source
src/SAIF.Agency.Api.Data/AgencyRepository.cs:164
BR-009 An agent is returned as licensed only while the license is active¶
Field
Value
Statement
An agent is reported as licensed in a state only while the state license is unexpired and not revoked, because an appointment written against a lapsed license is not enforceable.
Trigger
Evaluated in the service layer on every agent read and when an appointment is created.
Enforcement
AgentService.ResolveLicense compares AGENT_LICENSE.EXPIRATION_DATE and REVOKED_FLAG against the evaluation date; AppointmentService.Create calls the same method before inserting.
Origin
api
Reached from
GET /v1/agents/{agentNumber}, GET /v1/agents, POST /v1/appointments
Outcome
On the read endpoints a lapsed license is returned with licenseStatusExpired rather than being hidden. On POST /v1/appointments it raises AgencyValidationException and the caller receives 400 Bad Request with codeAGENT_LICENSE_INACTIVE.
Source
src/SAIF.Agency.Api.Services/AgentService.cs:87
BR-010 An agent holds at most one active appointment at an agency¶
Field
Value
Statement
An agent may hold only one appointment at a given agency at a time. Overlapping appointments would make commission attribution ambiguous, so a new appointment may not start before the previous one at that agency has ended.
Trigger
Evaluated in the service layer before an appointment row is inserted, and when an agent's appointment history is read.
Enforcement
AppointmentService.Create queries AGENCY_APPOINTMENT for an existing row with the same agent and agency and no END_DATE, raising AgencyStateConflictException when one exists; the unique index AGENCY_APPOINTMENT_UK1 backs the check at the database.
Origin
api
Reached from
GET /v1/agents/{agentNumber}/appointments, POST /v1/appointments
Outcome
On pass the appointment is inserted and 201 Created is returned with the new appointment id. On failure the caller receives 409 Conflict with codeAPPOINTMENT_ALREADY_ACTIVE and the existing appointment id in details.
Note: Statements marked with † were retrieved from dependency library documentation
via the Legends CLI RAG service. They describe inherited behavior from the indicated
origin library and should be verified if the dependency version changes.