Policy Acceptance Infrastructure (Work Package G)¶
Superseded by LEG-01: Policy Registry & Acceptance Ledger. The design below (a single
PolicyAcceptancetable, no separate registry) was implemented and extended into the two-tablePolicyRegistry/PolicyAcceptancemodel in that document -docs/legal/is now committed and referenced, the design is built, and enforcement checkpoints exist at registration/checkout/seller onboarding. This page is kept for historical record only.Note found during this tranche: an untracked
docs/legal/directory already exists in the working tree (not authored by this tranche, not committed, provenance unknown - possibly earlier work by another session) containing draft policy text (CUSTOMER_TERMS.md,SELLER_AGREEMENT.md,PRIVACY_POLICY.md,RETURN_POLICY.md,REFUND_POLICY.md,SHIPPING_POLICY.md,PROHIBITED_PRODUCTS_POLICY.md,GRIEVANCE_POLICY.md, and others), all correctly marked "DRAFT — NOT APPROVED FOR PRODUCTION — REQUIRES INDIA LEGAL REVIEW", plus anENFORCEMENT_GAP_MATRIX.mdthat independently proposes aPolicyAcceptance-shaped table - convergent with the design below. This tranche deliberately did not read that matrix in full or incorporate its content, and did not adddocs/legal/to this commit (it is not this tranche's own work and its provenance/intended handling is for the user to decide) - flagged here so it isn't lost. See the final tranche report.
Current state (as found)¶
Zero references to Terms/Privacy Policy/Seller Agreement acceptance anywhere in the domain,
application, or database layer - confirmed by searching the entire backend for
TermsAccepted/PrivacyPolicyAccepted/AgreementAccepted/PolicyVersion/TermsVersion: no
matches. User.Create/Seller.Create/SellerOnboardingRequest (see
backend/src/ECommerce.API/Controllers/SellersController.cs) capture no acceptance flag, version,
or timestamp for any policy. There is nothing to audit-trail today - if a dispute arose over
whether a customer or seller "agreed to" a given policy version at signup/onboarding time, there is
no application-level evidence to point to (only whatever the frontend showed, unlogged).
Not implemented in this tranche - see "Why deferred" below. This document is the design only.
What is merely documentation vs. what requires application logic/database evidence¶
| Item | Documentation only | Needs application/DB evidence |
|---|---|---|
| The legal text of a policy (Terms, Privacy, Seller Agreement, Return/Refund/Cancellation/Shipping) | Yes - lives as versioned content (CMS, markdown, or a simple versioned table of {policyType, version, effectiveDate, url/text} - either is fine) |
The content itself doesn't need app logic; a pointer to which version was shown/accepted does |
| "Customer agreed to Terms v3 at signup" | No | Yes - a row: {userId, policyType, policyVersion, acceptedAt, ipAddress?} |
| "Seller agreed to the Seller Agreement v2 during onboarding" | No | Yes - same shape, scoped to Seller |
| Prohibited/restricted products list | Content-driven (could be documentation, or a DB-backed category/attribute flag if enforcement should be automatic) | Only needs app logic if Vayzo wants to technically block listing a restricted product, not merely document the rule |
| Grievance/contact information (India's Consumer Protection (E-Commerce) Rules require a published grievance officer contact) | Documentation (a page/footer) | No app logic strictly required, but surfacing it in a legally-required place (e.g. every order confirmation, per some readings of the rules) would need application wiring |
Seller identity/compliance fields (GSTIN, PAN, business address - already partially present on Seller) |
N/A | Already partially application-enforced - Seller entity has legal name/business fields (see Seller.Create); whether GSTIN/PAN specifically exist needs a closer follow-up read, not done in this pass |
Proposed design (generic, versioned - not policy-specific)¶
Mirrors this codebase's existing entity conventions exactly (BaseEntity, private setters, a
Create factory, IEntityTypeConfiguration<T>):
public enum PolicyType { CustomerTerms, PrivacyPolicy, SellerAgreement }
public class PolicyAcceptance : BaseEntity
{
public Guid UserId { get; private set; }
public PolicyType PolicyType { get; private set; }
public string PolicyVersion { get; private set; } = default!; // e.g. "2026-01-15" or "v3" - opaque string, app doesn't interpret it
public DateTime AcceptedAt { get; private set; }
public static PolicyAcceptance Create(Guid userId, PolicyType policyType, string policyVersion) { ... }
}
- Append-only - a new acceptance (e.g. re-accepting after a policy version bump) is a new row, never an update to an existing one; the full history is the audit trail.
- Unique index on
(UserId, PolicyType, PolicyVersion)- prevents duplicate rows for the same user/policy/version (idempotent re-submission), mirroringMarketplaceNotification.IdempotencyKey's existing pattern in this codebase. - No legal text stored here -
PolicyVersionis an opaque identifier; the actual policy content lives wherever the frontend/CMS serves it from (out of this design's scope - a content, not application-logic, concern per the table above). - Enforcement points (where the app would require an acceptance row to exist before
proceeding) are a separate decision from the recording mechanism above - e.g. blocking checkout
until
CustomerTermsis accepted, or blocking seller onboarding completion untilSellerAgreementis accepted. Not designed in detail here; would need product/legal input on exactly which actions must be gated.
Why deferred (not implemented in this tranche)¶
The tranche brief explicitly permits building this "only if it is clean and isolated." Given the scope already delivered in Work Package B/C (a new entity, repository, background service, SES provider, startup validation, and 32 tests), adding a second new entity/migration/API surface in the same tranche risked diluting review depth on the SES work, which is this tranche's primary, "implementation allowed" deliverable. The design above is deliberately concrete enough to implement directly in a focused follow-up tranche without further discovery work - the entity shape, indexing strategy, and documentation-vs-application-logic boundary are already resolved.