Skip to content

Policy Acceptance Infrastructure (Work Package G)

Superseded by LEG-01: Policy Registry & Acceptance Ledger. The design below (a single PolicyAcceptance table, no separate registry) was implemented and extended into the two-table PolicyRegistry/PolicyAcceptance model 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 an ENFORCEMENT_GAP_MATRIX.md that independently proposes a PolicyAcceptance-shaped table - convergent with the design below. This tranche deliberately did not read that matrix in full or incorporate its content, and did not add docs/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), mirroring MarketplaceNotification.IdempotencyKey's existing pattern in this codebase.
  • No legal text stored here - PolicyVersion is 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 CustomerTerms is accepted, or blocking seller onboarding completion until SellerAgreement is 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.