Preserve What Was Requested and What Was Approved
When staff amend a CRM request, the product should preserve the original submission and show how the approved version differs. Overwriting one row obscures whether the customer asked for 50 units and a manager approved 60, or whether the customer had requested 60 from the start.
Model the request identity, submitted versions, approval decisions and downstream execution as distinct records. The practical specification below is a proposed design for a hypothetical order-request workflow. It is not proof that an existing CRM is immutable, legally compliant or operationally accepted.
A submitted original describes the requester's intent. It is not automatically an approval, a contract variation or authorization to execute a payment. Define the business decision that turns a reviewed version into an executable action.
Give the Request and Versions Stable Identities
Allocate a stable request ID and a separate ID for every submitted version. Version one is the original submission. Staff amendments create a new submitted version linked to its parent, with actor, server time and reason.
Working drafts can be editable under a deliberate policy. Once submitted, a version's content should be protected from ordinary updates and deletions. Make that distinction explicit so “create an amendment” does not mean editing a supposedly immutable submitted row.
| Record | Proposed fields | Purpose |
|---|---|---|
| Request | request_id, organisation_id, requester_id, current_version_id | Stable workflow identity and tenant boundary |
| Submitted version | version_id, request_id, parent_version_id, content, created_by, created_at, reason | Exact content and change provenance |
| Approval decision | decision_id, version_id, approver_id, decision, decided_at, policy_version | Attributable decision on one reviewed version |
| Dispute or withdrawal | event_id, related_decision_id, actor, time, reason | New event without erasing earlier decision |
| Execution | execution_id, approved_version_id, operation_id, status | Action actually performed from the approved content |
Keep the initial version accessible to authorized viewers through the request history, even when a newer version is current. Preserve attachment identifiers or immutable versions with the request if files form part of what was submitted. A link to a mutable attachment can change the evidence without changing the request JSON.
Bind Approval to the Exact Reviewed Version
An approver should see the version ID, original comparison, amendment reason and intended action before deciding. Store the decision against that version, rather than setting a generic approved flag on whichever version is current when the request finishes.
For multiple approval stages, record each stage's decision and the policy version. Define whether a material amendment invalidates earlier stage decisions or requires a fresh decision. Do not quietly apply an approval of version two to version three.
Preserve rejected, superseded and withdrawn decisions as history under the retention policy. The displayed current status can be derived from these records; it should not erase who approved an earlier version and when.
If the workflow requires the requester's renewed consent after a staff amendment, specify and test that requirement separately. A manager's approval cannot be assumed to replace every other business authorization.
Prevent Concurrent Edits and Stale Approvals
Suppose two staff members amend version one at the same time. The server should detect their shared base version and apply the chosen conflict policy, such as accepting one current successor and returning a review conflict for the other. Do not silently let the last request overwrite the earlier amendment.
For approval, submit the reviewed version ID and an expected workflow state. In a transaction, check current authority and the relevant version/state, then record the decision and the approved transition under the chosen concurrency mechanism. Enforce unique identifiers and retry-safe operation IDs so repeated network requests do not create duplicate approvals or execution.
PostgreSQL isolation levels define what concurrent transactions can observe; a snapshot alone does not make every application check-and-write sequence conflict-safe. Verify the selected locking or conditional-write mechanism in concurrent tests. PostgreSQL transaction isolation
The proposed execution worker uses the exact approved snapshot and an operation ID. It must not read “latest request content” later and execute an unapproved amendment. If authority is withdrawn before execution, apply the documented cancellation or reauthorization rule.
Protect Content and History at the Server
A read-only panel is an interface choice, not a data-integrity guarantee. Restrict update/delete grants for submitted versions, authorize amendment and approval endpoints, and keep maintenance operations separately controlled.
PostgreSQL RLS can constrain ordinary role access, but table owners normally bypass it and superusers and BYPASSRLS roles have broader exceptions. RLS alone cannot justify a claim that no administrator can ever alter the original. PostgreSQL row security Inspect the actual database roles, views, functions and privileged service paths.
OWASP recommends least privilege and authorization on every request. Check the organisation, request, version and allowed action for detail views, comparisons, approvals, exports and attachment downloads. OWASP authorization guidance
Requesters may see their permitted request history; approvers may see versions in their approved scope; support staff may need a specific case grant. An auditor's read-only role should also have an explicit organisation and record boundary. “All stakeholders see every version” is too broad when private notes or unrelated tenant data are present.
Retain necessary evidence without copying secrets or unnecessary sensitive fields into every version. Define correction, erasure, retention and authorized investigation-hold procedures with the product owner. A preserved version chain does not itself establish compliance with a legal requirement.
Show the Original and the Difference Clearly
A useful review screen offers the original submission, the selected amendment and a field comparison. Show the creator, time, reason, version ID and decision history. Distinguish submitted, approved, rejected, superseded, disputed and executed states.
Compare structured fields where possible. A quantity change from 50 to 60 should be visible alongside changes to price, delivery address or attachments. Do not rely only on a color highlight; include changed values or descriptive text.
Keep the original prominent within the authorized request view. Avoid exposing its private data through public metadata, exports or a comparison endpoint with weaker permission checks.
An amendment form can start from the current version, but saving a submitted amendment creates a new version. Correcting version three creates version four under this policy; it does not mutate version three's historical content.
Filled Hypothetical Approval History
Consider request REQ-104 in organisation A. These IDs, quantities and times are invented fixtures.
| Record | Content or event | Actor/time in Africa/Johannesburg | Meaning |
|---|---|---|---|
| V1 | Request 50 units of item X | Requester A, 09:00 | Preserved original submission |
| V2, parent V1 | Propose 60 units; reason: minimum pack size | Staff B, 09:20 | Submitted amendment |
| D1, version V2 | Approve V2 under policy P3 | Manager C, 09:30 | Decision on exactly 60 units |
| E1, approved V2 | Execution pending | Worker, 09:31 | No action yet established |
| DIS1, related D1 | Requester disputes quantity | Requester A, 09:35 | Separate dispute event |
| W1, related D1 | Withdraw authorization for pending execution | Authorized manager, 09:40 | History retained; pending action paused |
| V3, parent V2 | Propose 55 units with new reason | Staff B, 09:50 | Requires fresh decision under this policy |
This example does not mark V3 approved merely because V2 once was. The permitted history view can show V1 beside V2 and V3, with the relevant decisions and execution status.
If E1 had already executed, recording W1 would not undo the order. The product would need a separate authorized cancellation or compensating workflow, with its own status and evidence.
Reviewable Approval-History Specification
| Decision | Proposed rule | Evidence required |
|---|---|---|
| Original retention | Submitted V1 protected from ordinary mutation | Direct API/database-role tests |
| Amendment | New version linked to current base | Concurrency and parent-link tests |
| Decision | Separate record bound to exact version | Stale-approval test |
| Parallel stages | Each stage tied to version and policy | Changed-version invalidation fixture |
| Execution | Uses approved version and stable operation ID | Duplicate job and wrong-version tests |
| Dispute | Append event and pause eligible pending work | Pending/executed distinction |
| Visibility | Current requester/approver scope checked | Cross-organisation history tests |
| Notifications | Retryable informational events | Delivery state separate from approval |
| Retention | Approved data-class lifecycle | Purge and hold tests |
Notifications can inform stakeholders but should not be the only source of approval state. Record queued, accepted and delivered states as applicable; a notification API accepting a request does not prove recipient receipt. A failed notification should follow the documented retry/escalation policy, rather than silently rewriting the approval decision.
Acceptance and Failure Tests
| Test | Expected result |
|---|---|
| Staff creates V2 from V1 | V1 unchanged; V2 contains new content and reason |
| Ordinary caller directly edits submitted V1 | Denied and stored content unchanged |
| Two amendments use the same base concurrently | Chosen conflict policy applied without lost update |
| Approver submits decision for stale version | Rejected or explicitly processed under documented version policy |
| Approved version is amended | New version requires applicable fresh decisions |
| Approval request or execution job retries | Stable operation identity prevents duplicate effect |
| Worker reads execution content | Uses the approved snapshot, not latest unapproved version |
| Organisation B requests A history or comparison | No protected content or metadata returned |
| Pending approval is withdrawn | Eligible pending execution stops under the policy |
| Completed execution is disputed | Separate compensating workflow; no false “undone” status |
| Notification delivery fails | Decision retained, delivery failure recorded and retried |
Keep a positive control showing that the authorized requester and approver can read the original and comparison. A completely hidden history is not a successful preservation feature.
Use AI Suggestions as Drafts
If AI proposes an amendment summary or extracts request fields, keep the result as a draft for review. OpenAI Structured Outputs can enforce a supported schema, but schema conformance does not prove the extracted facts or business authority. OpenAI Structured Outputs
Validate source fields and current permissions outside the model. AI text should not directly mark a version approved or execute the request. Preserve the human or authorized-system decision that accepted the proposed change.
For further workflow ideas, see the AI CRM integration guide.
Recover Without Erasing Earlier Decisions
Identify the disputed request, version and execution state. Restrict new actions where required, then record withdrawal, rejection or correction as a new event. A later correction version should retain links to the history it repairs.
Do not “roll back approval” by deleting the old decision or changing the original submission. Inspect downstream orders, payments and notifications before describing recovery as complete. Reconcile any compensating action and read back the final state through an authorized ordinary user.
If your business needs a CRM approval workflow that preserves original and amended requests, explore our CRM development services. If you need help specifying exact-version approvals and recovery, get in touch with our team.
Frequently asked questions
How can I ensure the original request remains immutable in the CRM?
Protect submitted versions with tested endpoint authorization and database grants, then create new versions for changes. A read-only screen or RLS alone does not prevent every privileged administrator from changing data.
What if multiple staff amend the request simultaneously?
Bind each amendment to its base version and use a tested concurrency mechanism. Return an explicit conflict when required; do not silently overwrite another submitted amendment.
How does this approach differ from generic audit logging?
Audit logs record changes but often do not preserve the full request state. Versioned requests store complete snapshots per version, enabling direct comparison between original and amended requests.
Can this design support complex approval workflows with multiple approvers?
Yes. Record each stage and approver against the exact version and policy, and define which decisions must be repeated after an amendment. Test conflicts and stale approvals.
Related guides
For further background, read our AI CRM integration and Sales Marketing AI Workflows. The AI CRM Integration Glossary explains the terminology used in those guides.
Sources
- PostgreSQL row security
- OpenAI structured outputs
- OWASP authorization guidance
- PostgreSQL transaction isolation
For broader SaaS development insights, see our SaaS development overview and digital marketing analytics guide.

