From 780277d65642d903eee09b748ce772705c50345b Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 20 Oct 2026 10:07:25 +0200 Subject: [PATCH] Add DATABASE_STRUCTURE_CONTEXT.md for DB documentation Added `DATABASE_STRUCTURE_CONTEXT.md` to provide detailed documentation of the database structure for the envelope domain. This includes entity-to-table mappings, schema snapshots, EF relationships, constraints, stored procedure behavior, and trigger wiring paths. Highlighted model-to-database drift risks, trigger mismatch alerts, and provided migration-safe operating rules. Added a refresh checklist to validate metadata consistency before and after database migrations. --- AGENTS.md | 54 ++++- DATABASE_STRUCTURE_CONTEXT.md | 384 ++++++++++++++++++++++++++++++++++ 2 files changed, 436 insertions(+), 2 deletions(-) create mode 100644 DATABASE_STRUCTURE_CONTEXT.md diff --git a/AGENTS.md b/AGENTS.md index ec939e46..43b1c08c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,16 @@ This section explains every markdown file in the repo: what it contains, why it - Implementing sender-side PDF.js overlay (signature field placement) — the receiver side is the reference implementation **Maintenance:** Update when `pdf-viewer.js` API changes, new JS interop functions are added, or the signing flow is modified. +#### `DATABASE_STRUCTURE_CONTEXT.md` +**What:** Deep DB documentation for the envelope domain. Includes table/column snapshot, entity-to-table mapping, EF relationship/constraint map, stored procedure behavior + call sites, and trigger behavior + wiring path. +**Why it exists:** DB is centralized and can evolve independently; this file preserves migration-critical data semantics when historical DB context is hard to recover. +**When you need it:** +- Planning or reviewing DB/schema changes +- Debugging status transitions/history side effects +- Tracing where `PRSIG_*` procedures or DB triggers affect app behavior +- Reconciling Domain entities with physical SQL schema +**Maintenance:** Refresh on each DB migration, trigger/procedure change, or significant Domain/EGDbContext model change. + --- ### Deployment Docs @@ -294,11 +304,51 @@ Manual testing workflow: **Key Tables:** - `TBSIG_ENVELOPE` - Envelope metadata - `TBSIG_ENVELOPE_RECEIVER` - Receiver assignments -- `TBSIG_DOC_RECEIVER_ELEMENT` - Signature fields (X, Y in INCHES) +- `TBSIG_DOCUMENT_RECEIVER_ELEMENT` - Signature fields (X, Y in INCHES) - `TBSIG_RECEIVER` - Receiver registry -- `TBSIG_DOCUMENT` - PDF binary data +- `TBSIG_ENVELOPE_DOCUMENT` - PDF binary data - `TBSIG_ENVELOPE_HISTORY` - Audit trail +### Data Structure (Domain + EGDbContext) + +Use this as the canonical quick map before touching sender/receiver workflow code. + +#### Core Aggregate Shape +- `Envelope` (`TBSIG_ENVELOPE`) is the root aggregate for sending workflow. +- `Envelope -> Documents` is `1:N` via `Document.EnvelopeId` (`TBSIG_ENVELOPE_DOCUMENT`). +- `Envelope -> Histories` is `1:N` via `History.EnvelopeId` (`TBSIG_ENVELOPE_HISTORY`). +- `Envelope <-> Receiver` is `N:N` through `EnvelopeReceiver` (`TBSIG_ENVELOPE_RECEIVER`, composite PK `{EnvelopeId, ReceiverId}`). +- `Document -> DocReceiverElement` is `1:N` via `DocReceiverElement.DocumentId` (`TBSIG_DOCUMENT_RECEIVER_ELEMENT`). +- `DocReceiverElement -> ElementAnnotation` is `1:N` via `ElementAnnotation.ElementId` (`TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION`). + +#### Status and Tracking Entities +- `DocumentStatus` (`TBSIG_DOCUMENT_STATUS`) links `EnvelopeId + ReceiverId` and stores receiver-level signing state. +- `History` stores timeline events (`EnvelopeStatus`) with polymorphic `UserReference` lookup semantics. +- `EnvelopeReceiverReadOnly` (`TBSIG_ENVELOPE_RECEIVER_READ_ONLY`) stores temporary read-only delegation with validity window (`DateValid`). + +#### Important Constraints From `EGDbContext` +- `Envelope.Uuid` has a unique index. +- `Receiver.Signature` has a unique index. +- `Receiver.EmailAddress` has a unique index. +- `EnvelopeReceiver` uses composite primary key: `{ EnvelopeId, ReceiverId }`. +- `History.UserReference` is mapped conditionally (`IsMigration == false`) to: + - `Receiver.EmailAddress` (`History.Receiver`) + - `User.Email` from UserManager (`History.Sender`) +- `EnvelopeReceiverReadOnly.AddedWho` maps to `Receiver.EmailAddress` (principal key mapping, not receiver GUID). + +#### Coordinate-Critical Fields +- Signature placement is persisted in `DocReceiverElement` as `X`, `Y`, `Width`, `Height`, `Page`. +- These coordinates are in INCHES (not PDF points, not pixels). + +#### Cross-Module Tables in Same Context +- `EGDbContext` also includes UserManager entities: `User`, `Group`, `Module`, `GroupOfUser`, `ModuleOfUser`, `UserRep`, and `ClientUser`. +- Mail dispatcher entity `EmailOut` is also registered under `#if NET` and trigger-configurable. + +#### Trigger-Aware Model Notes +- Trigger names are injected from `DbTriggerParams` in appsettings and applied via `ToTable(tb => tb.HasTrigger(...))`. +- Current trigger registration is explicit (`AddTrigger()` calls), not attribute/reflection driven. +- Do not remove trigger mappings casually; this project depends on DB-side automation/audit behavior. + ## DevExpress **License:** Commercial (v25.2.3) diff --git a/DATABASE_STRUCTURE_CONTEXT.md b/DATABASE_STRUCTURE_CONTEXT.md new file mode 100644 index 00000000..37a03886 --- /dev/null +++ b/DATABASE_STRUCTURE_CONTEXT.md @@ -0,0 +1,384 @@ +# Database Structure Context + +## Why this file exists + +This is the deep-dive DB reference for migration work when schema can change and old DB knowledge may be lost. + +- Scope: `EnvelopeGenerator.Domain` entities + `EnvelopeGenerator.Infrastructure/EGDbContext.cs` + live DB metadata snapshot from `DD_ECM`. +- Goal: make schema transitions predictable (what exists, how it is linked, where logic executes). +- Safety: all DB checks used read-only `SELECT` metadata queries; no data mutation. + +## Snapshot metadata + +- Source DB: `DD_ECM` on `SDD-VHP04-SQL19\DD_TESTING01` +- Snapshot date: `2026-10-20` +- App context: `EnvelopeGenerator.Infrastructure/EGDbContext.cs` + +## Fast navigation + +- High-level aggregate map: `AGENTS.md` -> `Database` section +- EF model config source: `EnvelopeGenerator.Infrastructure/EGDbContext.cs` +- Stored procedure wrappers: + - `EnvelopeGenerator.Application/Common/SQL/EnvelopeCreateReadSQL.cs` + - `EnvelopeGenerator.Application/Common/SQL/EnvelopeReceiverAddReadSQL.cs` + - `EnvelopeGenerator.Application/Common/SQL/DocumentCreateReadSQL.cs` +- Direct SQL execution in controller: + - `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs` + +## Core data model (ER-level) + +- `Envelope` (`TBSIG_ENVELOPE`) is root aggregate. +- `Envelope (1) -> (N) Document` via `TBSIG_ENVELOPE_DOCUMENT.ENVELOPE_ID`. +- `Envelope (1) -> (N) History` via `TBSIG_ENVELOPE_HISTORY.ENVELOPE_ID`. +- `Envelope (N) <-> (N) Receiver` through `EnvelopeReceiver` (`TBSIG_ENVELOPE_RECEIVER`, composite PK `{ENVELOPE_ID, RECEIVER_ID}`). +- `Document (1) -> (N) DocReceiverElement` via `TBSIG_DOCUMENT_RECEIVER_ELEMENT.DOCUMENT_ID`. +- `DocReceiverElement (1) -> (N) ElementAnnotation` via `TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION.ELEMENT_ID`. +- `DocumentStatus` links `Envelope + Receiver` state in `TBSIG_DOCUMENT_STATUS`. + +## Entity <-> table mapping (code-level) + +| Entity | Table | PK in code | PK in DB | Important relation notes | +|---|---|---|---|---| +| `Envelope` | `TBSIG_ENVELOPE` | `Id` | `GUID` | Has `Documents`, `Histories`, `EnvelopeReceivers`; unique index on `Uuid` configured in EF | +| `EnvelopeReceiver` | `TBSIG_ENVELOPE_RECEIVER` | `{EnvelopeId, ReceiverId}` | `{ENVELOPE_ID, RECEIVER_ID}` | DB also has `GUID` identity (not modeled as entity PK) | +| `Document` | `TBSIG_ENVELOPE_DOCUMENT` | `Id` | `GUID` | FK `ENVELOPE_ID` | +| `DocReceiverElement` | `TBSIG_DOCUMENT_RECEIVER_ELEMENT` | `Id` | `GUID` | Coordinates persisted here (`POSITION_X/Y`, `WIDTH`, `HEIGHT`, `PAGE`) | +| `ElementAnnotation` | `TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION` | `Id` | `GUID` | FK `ELEMENT_ID` | +| `DocumentStatus` | `TBSIG_DOCUMENT_STATUS` | `Id` | `GUID` | FK `ENVELOPE_ID`, `RECEIVER_ID` | +| `History` | `TBSIG_ENVELOPE_HISTORY` | `Id` | `GUID` | `USER_REFERENCE` maps to `Receiver.EmailAddress` and `User.Email` when `IsMigration == false` | +| `Receiver` | `TBSIG_RECEIVER` | `Id` | `GUID` | Unique indexes in code: `Signature`, `EmailAddress` | +| `EnvelopeReceiverReadOnly` | `TBSIG_ENVELOPE_RECEIVER_READ_ONLY` | `Id` | `GUID` | `AddedWho` is FK-like mapping to `Receiver.EmailAddress` | +| `EnvelopeType` | `TBSIG_ENVELOPE_TYPE` | `Id` | `GUID` | Used as optional envelope defaults/template | +| `EmailTemplate` | `TBSIG_EMAIL_TEMPLATE` | `Id` | `GUID` | Trigger-managed `CHANGED_WHEN` | +| `EmailOut` (legacy/bridge) | `TBEMLP_EMAIL_OUT` | `Id` | `GUID` | Internal/obsolete domain class, still registered for DB access | + +## Physical schema snapshot (table/column level) + +Notes: +- Format per column: `COLUMN_NAME TYPE NULLABLE`. +- This section reflects live DB metadata at snapshot time. + +### `TBSIG_ENVELOPE` + +- `GUID int NOT NULL` +- `USER_ID int NOT NULL` +- `STATUS int NOT NULL` +- `ENVELOPE_UUID nvarchar(36) NOT NULL` +- `MESSAGE nvarchar(max) NOT NULL` +- `EXPIRES_WHEN datetime NULL` +- `EXPIRES_WARNING_WHEN datetime NULL` +- `VERIFICATION_TYPE tinyint NOT NULL` +- `TITLE nvarchar(128) NULL` +- `CONTRACT_TYPE int NULL` +- `LANGUAGE nvarchar(5) NULL` +- `SEND_REMINDER_EMAILS bit NULL` +- `FIRST_REMINDER_DAYS int NULL` +- `REMINDER_INTERVAL_DAYS int NULL` +- `ENVELOPE_TYPE int NULL` +- `CERTIFICATION_TYPE int NULL` +- `USE_ACCESS_CODE bit NULL` +- `FINAL_EMAIL_TO_CREATOR int NULL` +- `FINAL_EMAIL_TO_RECEIVERS int NULL` +- `EXPIRES_WHEN_DAYS int NULL` +- `EXPIRES_WARNING_WHEN_DAYS int NULL` +- `TFA_ENABLED bit NOT NULL` +- `DOC_RESULT varbinary(max) NULL` +- `LANG_CODE varchar(5) NOT NULL` +- `SOURCE varchar(10) NOT NULL` +- `COMMENT varchar(128) NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` + +### `TBSIG_ENVELOPE_RECEIVER` + +- `ENVELOPE_ID int NOT NULL` +- `RECEIVER_ID int NOT NULL` +- `SEQUENCE int NOT NULL` +- `NAME nvarchar(128) NULL` +- `JOB_TITLE nvarchar(128) NULL` +- `COMPANY_NAME nvarchar(128) NULL` +- `PRIVATE_MESSAGE nvarchar(max) NULL` +- `ACCESS_CODE nvarchar(64) NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` +- `GUID bigint NOT NULL` +- `PHONE_NUMBER varchar(20) NULL` +- `PARENT_ENVELOPE_RECEIVER_ID int NOT NULL` + +### `TBSIG_ENVELOPE_DOCUMENT` + +- `GUID int NOT NULL` +- `ENVELOPE_ID int NOT NULL` +- `FILENAME nvarchar(256) NOT NULL` +- `FILEPATH nvarchar(256) NOT NULL` +- `ADDED_WHEN datetime NOT NULL` +- `FILENAME_ORIGINAL nvarchar(256) NULL` +- `BYTE_DATA varbinary(max) NULL` + +### `TBSIG_DOCUMENT_RECEIVER_ELEMENT` + +- `GUID int NOT NULL` +- `DOCUMENT_ID int NOT NULL` +- `RECEIVER_ID int NOT NULL` +- `ELEMENT_TYPE int NOT NULL` +- `POSITION_X float NOT NULL` +- `POSITION_Y float NOT NULL` +- `WIDTH float NOT NULL` +- `HEIGHT float NOT NULL` +- `PAGE int NOT NULL` +- `REQUIRED bit NOT NULL` +- `TOOLTIP nvarchar(512) NULL` +- `READ_ONLY bit NOT NULL` +- `ANNOTATION_INDEX int NOT NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` +- `CHANGED_WHO nvarchar(100) NULL` +- `FULL_NAME nvarchar(100) NULL` +- `POSITION nvarchar(100) NULL` +- `PLACE nvarchar(100) NULL` +- `INK varbinary(max) NULL` + +### `TBSIG_DOCUMENT_RECEIVER_ELEMENT_ANNOTATION` + +- `GUID bigint NOT NULL` +- `ELEMENT_ID int NOT NULL` +- `NAME nvarchar(100) NOT NULL` +- `VALUE nvarchar(max) NOT NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` +- `CHANGED_WHO nvarchar(100) NULL` +- `TYPE nvarchar(50) NOT NULL` +- `POSITION_X float NULL` +- `POSITION_Y float NULL` +- `WIDTH float NULL` +- `HEIGHT float NULL` + +### `TBSIG_DOCUMENT_STATUS` + +- `GUID int NOT NULL` +- `ENVELOPE_ID int NOT NULL` +- `RECEIVER_ID int NOT NULL` +- `STATUS int NOT NULL` +- `STATUS_CHANGED_WHEN datetime NULL` +- `VALUE nvarchar(max) NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` + +### `TBSIG_ENVELOPE_HISTORY` + +- `GUID bigint NOT NULL` +- `ENVELOPE_ID int NOT NULL` +- `USER_REFERENCE nvarchar(128) NOT NULL` +- `STATUS int NOT NULL` +- `ADDED_WHEN datetime NOT NULL` +- `ACTION_DATE datetime NULL` +- `COMMENT nvarchar(max) NULL` + +### `TBSIG_RECEIVER` + +- `GUID int NOT NULL` +- `EMAIL_ADDRESS nvarchar(128) NOT NULL` +- `SIGNATURE nvarchar(64) NOT NULL` +- `ADDED_WHEN datetime NOT NULL` +- `TOTP_SECRET_KEY nvarchar(max) NULL` +- `TFA_REG_DEADLINE datetime NULL` +- `CULTURE nvarchar(10) NULL` + +### `TBSIG_ENVELOPE_RECEIVER_READ_ONLY` + +- `GUID bigint NOT NULL` +- `ENVELOPE_ID bigint NOT NULL` +- `RECEIVER_MAIL nvarchar(250) NOT NULL` +- `DATE_VALID date NOT NULL` +- `ADDED_WHO varchar(100) NOT NULL` +- `ADDED_WHEN datetime NULL` +- `CHANGED_WHO varchar(100) NULL` +- `CHANGED_WHEN datetime NULL` + +### `TBSIG_ENVELOPE_TYPE` + +- `GUID int NOT NULL` +- `TITLE nvarchar(128) NOT NULL` +- `LANGUAGE nvarchar(5) NOT NULL` +- `EXPIRES_DAYS int NULL` +- `CERTIFICATION_TYPE int NULL` +- `USE_ACCESS_CODE bit NULL` +- `FINAL_EMAIL_TO_CREATOR int NULL` +- `FINAL_EMAIL_TO_RECEIVERS int NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` +- `EXPIRES_WARNING_DAYS int NULL` +- `SEND_REMINDER_EMAILS bit NULL` +- `FIRST_REMINDER_DAYS int NULL` +- `REMINDER_INTERVAL_DAYS int NULL` +- `CONTRACT_TYPE int NULL` + +### `TBSIG_EMAIL_TEMPLATE` + +- `GUID int NOT NULL` +- `NAME nvarchar(64) NULL` +- `BODY nvarchar(max) NULL` +- `SUBJECT nvarchar(512) NULL` +- `ADDED_WHEN datetime NOT NULL` +- `CHANGED_WHEN datetime NULL` +- `LANG_CODE varchar(5) NOT NULL` + +### `TBEMLP_EMAIL_OUT` + +- `GUID int NOT NULL` +- `REMINDER_TYPE_ID int NOT NULL` +- `SENDING_PROFILE int NOT NULL` +- `REFERENCE_ID int NOT NULL` +- `REFERENCE_STRING varchar(200) NULL` +- `ENTITY_ID int NULL` +- `WF_ID int NOT NULL` +- `WF_REFERENCE varchar(200) NULL` +- `EMAIL_ADRESS varchar(1000) NOT NULL` +- `EMAIL_SUBJ varchar(500) NOT NULL` +- `EMAIL_BODY varchar(max) NOT NULL` +- `EMAIL_ATTMT1 varchar(512) NULL` +- `EMAIL_SENT datetime NULL` +- `COMMENT varchar(500) NULL` +- `ADDED_WHO varchar(50) NOT NULL` +- `ADDED_WHEN datetime NULL` +- `CHANGED_WHO varchar(50) NULL` +- `CHANGED_WHEN datetime NULL` +- `ERROR_TIMESTAMP datetime NULL` +- `ERROR_MSG varchar(900) NULL` +- `ATTACHMENT1_BYTES varbinary(max) NULL` +- `ATT1_RELATED_ID bigint NULL` +- `ATT1_REL_TYPE varchar(20) NULL` + +## Constraints and indexes (relevant) + +From EF + DB metadata snapshot: + +- `TBSIG_ENVELOPE`: PK `PK_TBSIG_ENVELOPE (GUID)`. +- `TBSIG_ENVELOPE_RECEIVER`: composite PK `PK_TBSIG_ENVELOPE_RECEIVER (ENVELOPE_ID, RECEIVER_ID)`. +- `TBSIG_DOCUMENT_RECEIVER_ELEMENT`: PK `GUID`, plus unique key `UQTBSIG_DOCUMENT_RECEIVER_ELEMENT_DocID_PosX_PosY (DOCUMENT_ID, POSITION_X, POSITION_Y)`. +- `TBSIG_RECEIVER`: PK `GUID`, unique key `UQTBSIG_RECEIVER (EMAIL_ADDRESS)`. +- `TBEMLP_EMAIL_OUT`: PK `GUID`, FK `FK_TBEMLP_EMAIL_OUT_SENDING_PROFILE`. + +## Stored procedures: behavior and code call sites + +### Active in .NET code path + +1) `PRSIG_API_CREATE_ENVELOPE` +- Purpose: creates envelope row (`STATUS=1002`, `SOURCE='API'`, defaults) and returns `ENVELOPE_UUID` via output. +- DB writes: `TBSIG_ENVELOPE`. +- Called by: + - SQL template `EnvelopeGenerator.Application/Common/SQL/EnvelopeCreateReadSQL.cs` + - executor `EnvelopeGenerator.Infrastructure/Executor/EnvelopeExecutor.cs` + - used in `EnvelopeReceiverController.CreateAsync` and `CreateEnvelopeReceiverCommandHandler`. + +2) `PRSIG_API_CREATE_RECEIVER` +- Purpose: upserts `TBSIG_RECEIVER` by email, adds envelope assignment row with generated access code. +- DB writes: `TBSIG_RECEIVER`, `TBSIG_ENVELOPE_RECEIVER`. +- Called by: + - SQL template `EnvelopeGenerator.Application/Common/SQL/EnvelopeReceiverAddReadSQL.cs` + - executor `EnvelopeGenerator.Infrastructure/Executor/EnvelopeReceiverExecutor.cs` + - used in `EnvelopeReceiverController.CreateAsync` and `CreateEnvelopeReceiverCommandHandler`. + +3) `PRSIG_API_ADD_DOC` +- Purpose: stores one document for envelope if absent; returns doc id. +- DB writes: `TBSIG_ENVELOPE_DOCUMENT`. +- Called by: + - SQL template `EnvelopeGenerator.Application/Common/SQL/DocumentCreateReadSQL.cs` + - executor `EnvelopeGenerator.Infrastructure/Executor/DocumentExecutor.cs` + - used in `EnvelopeReceiverController.CreateAsync`. + +4) `PRSIG_API_ADD_DOC_RECEIVER_ELEM` +- Purpose: inserts signature element record if `(DOC_ID, RECEIVER_ID)` pair does not exist. +- DB writes: `TBSIG_DOCUMENT_RECEIVER_ELEMENT`. +- Called directly by: + - `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs` + +5) `PRSIG_API_ADD_HISTORY_STATE` +- Purpose: appends history row with status and user reference. +- DB writes: `TBSIG_ENVELOPE_HISTORY`. +- Called directly by: + - `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs` + +### Legacy/other path references in repo + +- `PRSIG_GET_ENVELOPES_FOR_USER` and `PRSIG_GET_CLOSED_ENVELOPES_FOR_USER` are called in legacy VB path: + - `EnvelopeGenerator.CommonServices/Models/EnvelopeModel.vb` + +- `PRSIG_API_WITHDRAW_ENVELOPE` exists in DB snapshot but no active `.cs` call site found in current server flow. + +## Trigger behavior and where they are wired + +## How trigger names are wired in app + +- Config source: `EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json` -> `DbTriggerParams`. +- DI binding: `EnvelopeGenerator.Infrastructure/DependencyInjection.cs` (`AddDbTriggerParams`). +- Model binding: `EnvelopeGenerator.Infrastructure/EGDbContext.cs` -> `AddTrigger()` -> `ToTable(tb => tb.HasTrigger(triggerName))`. + +### Trigger definitions observed in DB snapshot + +1) `TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_INS` +- Fires on insert of `TBSIG_DOCUMENT_RECEIVER_ELEMENT`. +- Normalizes/rewrites `POSITION_X`, `POSITION_Y` and sets `CHANGED_WHO='TBSIG_DOC_REC_EL_AFT_INS'`. + +2) `TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_UPD` +- Fires on update of `TBSIG_DOCUMENT_RECEIVER_ELEMENT`. +- Rewrites coordinates and `CHANGED_WHO='TBSIG_DOC_REC_EL_AFT_UPD'` except when `CHANGED_WHO='OFFSET_Y_POS'`. + +3) `TBSIG_ENVELOPE_HISTORY_AFT_INS` +- Fires on history insert. +- Central state machine side-effects: + - syncs `TBSIG_ENVELOPE.STATUS` for selected `100x` states, + - on `1003` applies Y-offset update to receiver elements, + - on `2005` calculates partial/fully signed and may insert follow-up `1006` history, + - on `2007` (type-dependent reject path) writes rejection emails into `TBEMLP_EMAIL_OUT` and updates envelope rejected state, + - on `3001` invitation-message counting can move envelope to `1004`. + +4) `TBSIG_EMAIL_TEMPLATE_AFT_UPD` +- Fires on update of `TBSIG_EMAIL_TEMPLATE`. +- Sets `CHANGED_WHEN = GETDATE()`. + +5) `TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD` +- Fires on update of `TBSIG_ENVELOPE_RECEIVER_READ_ONLY`. +- Updates config timestamp row (`TBSIG_CONFIG.GUID = 1`). + +6) `TBEMLP_EMAIL_OUT_AFT_UPD` +- Fires on update of `TBEMLP_EMAIL_OUT`. +- When `EMAIL_SENT` changes and `WF_ID in (3001..3005)`, writes corresponding history entries to `TBSIG_ENVELOPE_HISTORY`. + +### Trigger mismatch alert (important) + +- `DbTriggerParams` contains `TBEMLP_EMAIL_OUT_AFT_INS`, but this trigger was not found in the snapshot DB (`ExistsInDb = 0`). +- `TBEMLP_EMAIL_OUT_AFT_UPD` exists and is active. + +This should be treated as environment drift and validated before migrations. + +## Known model-vs-DB drift risks + +These are not necessarily bugs, but they are migration risk hotspots: + +- `Receiver.EmailAddress` is `[StringLength(250)]` in entity, DB column is `nvarchar(128)`. +- `EnvelopeReceiverReadOnly.AddedWho` entity expects `nvarchar(250)`, DB column is `varchar(100)`. +- `EnvelopeReceiver` entity does not model DB columns `GUID` and `PARENT_ENVELOPE_RECEIVER_ID`. +- `Envelope` entity does not explicitly model some DB columns (`VERIFICATION_TYPE`, `LANG_CODE`, `SOURCE`). +- `EmailOut` entity has `[NotMapped] AddedWhen`, while DB has `ADDED_WHEN` column. + +Before schema changes, reconcile these deltas intentionally (either mapping update or documented accepted divergence). + +## Migration-safe operating rules + +- Do not rely on implicit trigger behavior; document expected side effects in feature code touching history/status flows. +- Any change to `TBSIG_ENVELOPE_HISTORY` semantics requires trigger impact review first. +- Keep `DbTriggerParams` synchronized with actual DB trigger inventory per environment. +- For stored procedure changes, update both: + - SQL wrapper templates (`Application/Common/SQL/*`) + - direct SQL callers in controllers/executors. + +## Refresh checklist (read-only) + +Run these metadata checks before/after DB migration: + +1. Column inventory per table (`INFORMATION_SCHEMA.COLUMNS`). +2. Constraint inventory (`INFORMATION_SCHEMA.TABLE_CONSTRAINTS` + `KEY_COLUMN_USAGE`). +3. Trigger inventory (`sys.triggers`) and definitions (`OBJECT_DEFINITION`). +4. Stored procedure inventory (`sys.procedures`) and definitions (`OBJECT_DEFINITION`). +5. Repo call-site grep for `PRSIG_` names to detect dead or missing integration points.