An AI-powered customer support refund evaluation system that pairs deterministic business policy rules with an LLM evaluation layer to process, audit, and explain refund requests across customer-facing and administrative interfaces.
- Video Demo Walkthrough: Watch the 4-Minute Walkthrough
Worknoon Refund Engine automates the evaluation of customer return and refund requests. Rather than delegating policy compliance to an unpredictable language model, the system uses a two-tier evaluation architecture:
- Deterministic Policy Engine: Enforces strict, non-negotiable business constraints (final-sale restrictions, 30-day return windows, and high-value limits) in pure TypeScript code.
- AI Analysis Service: When an order satisfies all deterministic requirements, an LLM evaluates the customer's explanation against trusted order details to classify the issue, detect discrepancies or suspected fraud, sanitize responses, and generate customer-friendly messages.
The application includes two web interfaces:
- Customer Refund Portal (
/): A guided self-service flow for selecting orders, specifying return reasons, and receiving immediate refund decisions. - Admin Dashboard (
/admin): An operations interface displaying evaluation history, decisions, triggered rule sets, LLM reasoning, and audit logs.
- Two-Tier Decision Engine: Separates hard business policy from semantic context analysis.
- Fail-Safe Architecture: If external AI services fail, time out, or produce malformed output, the system fails safe to
ESCALATEDfor human review rather than defaulting to approval. - Prompt Injection Defense: Defense-in-depth security combining clear prompt boundaries with deterministic regex override detection to neutralize instruction-injection attacks.
- Immutable Audit Logging: Every evaluation persists the final decision (
APPROVED,DENIED, orESCALATED), triggered rule IDs, internal reasoning, and the exact customer-facing message. - Customer Portal: Interactive multi-step form with customer switcher, order selection, item breakdown, and instant resolution display.
- Admin Dashboard: Live request table with decision status badges, request timestamps, and detailed audit inspect drawer.
- One-Command Reproducibility: Complete Docker Compose setup orchestrating PostgreSQL, automatic Prisma schema migrations, seed fixtures, backend Express API, and frontend preview.
The application is structured as a decoupled full-stack monorepo:
worknoon-refund-engine/
├── client/ # Frontend Single Page Application (SPA)
│ ├── src/
│ │ ├── components/
│ │ │ ├── RefundSupport.vue # Customer refund submission portal
│ │ │ └── AdminDashboard.vue # Support admin audit history dashboard
│ │ ├── services/
│ │ │ └── api.ts # Typed fetch API client
│ │ ├── App.vue # Top navigation and layout shell
│ │ ├── main.ts # Vue Router configuration (/ and /admin)
│ │ ├── style.css # Tailwind CSS 4 styles
│ │ └── types.ts # Shared TypeScript interfaces
│ ├── Dockerfile
│ └── vite.config.ts
├── server/ # Backend REST API and Evaluation Engine
│ ├── prisma/
│ │ ├── schema.prisma # Relational schema (PostgreSQL)
│ │ ├── migrations/ # Versioned SQL migrations
│ │ └── seed.ts # Idempotent fixture database seeder
│ ├── src/
│ │ ├── services/
│ │ │ ├── policyEngine.ts # Pure deterministic policy logic
│ │ │ ├── aiService.ts # OpenAI / OpenRouter client & sanitizers
│ │ │ ├── refundOrchestrator.ts # Coordinates policy, AI, & fail-safes
│ │ │ └── refundPersistence.ts # Database persistence for requests & audit
│ │ ├── index.ts # Express application and route handlers
│ │ └── index.test.ts # Route-level integration test suite
│ ├── .env.example # Backend environment template
│ └── Dockerfile
├── docker-compose.yml # Container orchestration definition
└── .env.example # Root environment template
- Framework: Vue 3 (Composition API,
<script setup>) - Build Tool: Vite 8
- Language: TypeScript 5
- Styling: Tailwind CSS 4 (
@tailwindcss/vite) - Routing: Vue Router 4 (HTML5 history mode)
- Runtime: Node.js (ES Modules)
- Framework: Express 5
- Language: TypeScript 5
- Database & ORM: PostgreSQL 16, Prisma ORM 5
- Validation: Zod 4
- AI Integration: OpenAI SDK 7 (OpenRouter compatible)
- Testing: Node.js built-in test runner (
node --test) viatsx
- Containerization: Docker, Docker Compose
- Images:
postgres:16-alpine,node:20-alpine
flowchart TD
A[Customer submits Refund Request] --> B[Zod Input Validation]
B --> C[Fetch Order & Customer from DB]
C --> D[Run Policy Engine evaluateRefund]
D -->|Final sale or Expired >30 days| E[DENIED: Policy Short-Circuit]
D -->|Order total >$500| F[ESCALATED: High-Value Short-Circuit]
D -->|Within 30 days & <=$500 & not final sale| G[Eligible for AI Evaluation]
G --> H[OpenAI / OpenRouter Analysis]
H -->|API Error / Timeout / Malformed| I[ESCALATED: Fail-Safe]
H -->|Prompt Injection / Discrepancy / Suspicious| J[ESCALATED: Suspicious Flagged]
H -->|Valid Claim & No Discrepancies| K[APPROVED: Legitimate Claim]
E --> L[Persist RefundRequest & AuditLog]
F --> L
I --> L
J --> L
K --> L
L --> M[Return JSON Response to Client]
- Input Validation: Request payload (
orderId,reasonCategory,customerStatement) validated with Zod. - Data Retrieval: Order details, line items, and customer information loaded from PostgreSQL.
- Deterministic Evaluation:
- If an order or any line item is
isFinalSale: true-> DENIED (FINAL_SALE). - If purchase date is older than 30 days (
purchaseDate < now - 30 days) -> DENIED (REFUND_WINDOW_EXPIRED). - If order total exceeds $500 (
totalAmount > 500.00) -> ESCALATED (REFUND_AMOUNT_OVER_LIMIT). - Final-sale and expiration take precedence over high-value escalation.
- If a hard constraint is met, the system short-circuits immediately without invoking the LLM.
- If an order or any line item is
- AI Evaluation:
- Orders of exactly $500 or less that meet policy rules proceed to AI analysis.
- The LLM parses untrusted customer statements against trusted order context.
- Deterministic regex screening intercepts instruction-override patterns (
ignore all instructions,system override). - Any AI failure (network error, timeout, unparseable response) fails safe to ESCALATED (
AI_ANALYSIS_FAILED).
- Persistence: The orchestrator saves the
RefundRequestrecord and an associatedAuditLoginside a database transaction. - Response: Final status, customer-facing message, reasoning, and rule identifiers are returned to the client.
The policy engine and orchestrator enforce the following evaluation rules:
| Rule Code | Description | Threshold / Condition | Result |
|---|---|---|---|
FINAL_SALE |
Order or any item is marked final sale | order.isFinalSale = true OR item isFinalSale = true |
DENIED |
REFUND_WINDOW_EXPIRED |
Order was purchased outside return window | purchaseDate < now - 30 days |
DENIED |
REFUND_AMOUNT_OVER_LIMIT |
High-value order requiring human authorization | totalAmount > $500.00 |
ESCALATED |
Orders of exactly $500.00 (
totalAmount = 500.00) do not tripREFUND_AMOUNT_OVER_LIMITand proceed directly to AI evaluation with no hard constraint rules triggered. (Static seed fixtures include descriptive audit labels such asORDER_AT_500_DOLLARS,DELIVERED_ORDER, andWITHIN_30_DAYS).
| Rule Code | Description | Condition | Result |
|---|---|---|---|
AI_FLAGGED_SUSPICIOUS |
Statement contains conflicts, prompt injection, or fraud indicators | Model flags suspicious: true OR isInstructionOverride() matches |
ESCALATED |
AI_ANALYSIS_FAILED |
Upstream AI service unreachable, timed out, or invalid | API failure / timeout / bad format | ESCALATED (Fail-Safe) |
- Model:
liquid/lfm-2.5-2.6b:free(configurable viaOPENAI_MODEL). - Endpoint: OpenRouter API (
https://openrouter.ai/api/v1) or any OpenAI-compatible API base URL (OPENAI_BASE_URL). - Structured Outputs: Uses
response_formatJSON Schema mode (json_schemawithstrict: true) requiring four fields:classification: Category classification (DAMAGED_ITEM,INCORRECT_ITEM,BUYERS_REMORSE,LATE_DELIVERY,FRAUD_SUSPECTED,OTHER).suspicious: Boolean flag indicating instruction overrides, contradictory statements, or fraud indicators.reasoning: Technical rationale explaining how the claim compares against trusted order facts.customerMessage: Courteous, user-facing message explaining the decision.
- Message Sanitization:
- Automatically cleans internal jargon (strips references to
"AI team","AI review","eligible for AI review"). - Guarantees that suspicious or escalated claims never show an approval message to the customer.
- Automatically cleans internal jargon (strips references to
The system implements defense-in-depth against prompt injection, jailbreaking, and instruction-override attacks:
- Clear Trust Boundaries:
- Trusted database context and untrusted customer text are strictly separated in prompt formatting using boundary tags:
TRUSTED APPLICATION CONTEXT { ... } END TRUSTED APPLICATION CONTEXT UNTRUSTED CUSTOMER CONTENT ---BEGIN UNTRUSTED CUSTOMER CONTENT--- { customerStatement } ---END UNTRUSTED CUSTOMER CONTENT---
- Trusted database context and untrusted customer text are strictly separated in prompt formatting using boundary tags:
- System Prompt Hardening:
- Explicit instructions declare customer text as untrusted data that must never be followed as system instructions.
- Deterministic Regex Override Detector (
isInstructionOverride):- Scans statements for command-override patterns:
ignore (all) (previous/prior/above) instructionssystem overrideoverride (the) (refund/final-sale) policybypass (the) policy/rulesdeveloper modereveal/show system prompt
- If an instruction-override attempt is detected,
suspicious: trueis unconditionally enforced in application code, overriding any potential false-negative returned by the model.
- Scans statements for command-override patterns:
- Policy Precedence:
- Final-sale and expired orders are short-circuited before the LLM is ever called. A prompt injection cannot trigger an approval on an ineligible order because the model is never invoked.
Defined in server/prisma/schema.prisma:
OrderStatus:DELIVERED,IN_TRANSIT,CANCELLEDDecisionStatus:APPROVED,DENIED,ESCALATEDRefundReasonCategory:DAMAGED_ITEM,INCORRECT_ITEM,BUYERS_REMORSE,LATE_DELIVERY,FRAUD_SUSPECTED,OTHER
- Customer:
id(UUID, primary key)email(String, unique)name(String)createdAt(DateTime)- Relations: has many
Order, has manyRefundRequest
- Order:
id(UUID, primary key)orderNumber(String, unique)customerId(UUID, foreign key -> Customer)totalAmount(Decimal 10,2)isFinalSale(Boolean, default: false)status(OrderStatus, default: DELIVERED)purchaseDate(DateTime)deliveredDate(DateTime, optional)- Relations: belongs to
Customer, has manyOrderItem, has manyRefundRequest
- OrderItem:
id(UUID, primary key)orderId(UUID, foreign key -> Order)productName(String)sku(String)unitPrice(Decimal 10,2)quantity(Int, default: 1)isFinalSale(Boolean, default: false)
- RefundRequest:
id(UUID, primary key)orderId(UUID, foreign key -> Order)customerId(UUID, foreign key -> Customer)reasonCategory(RefundReasonCategory)customerStatement(Text)status(DecisionStatus)createdAt(DateTime)- Relations: belongs to
Order, belongs toCustomer, has oneAuditLog
- AuditLog:
id(UUID, primary key)refundRequestId(UUID, foreign key -> RefundRequest, unique)decision(DecisionStatus)rulesTriggered(String array)aiReasoning(Text)customerMessage(Text)createdAt(DateTime)
Liveness check returning service health.
- Response:
200 OK{ "status": "ok" }
Fetches all customers and their associated orders sorted by customer name.
- Response:
200 OK[ { "id": "c001...", "name": "Maya Patel", "email": "[email protected]", "orders": [ { "id": "o001...", "orderNumber": "WN-1001", "totalAmount": "129.00", "status": "DELIVERED", "purchaseDate": "2026-09-12T12:00:00.000Z", "deliveredDate": "2026-09-15T12:00:00.000Z", "isFinalSale": false } ] } ]
Fetches a single order by UUID including line items and customer details.
- Path Parameter:
id(UUID) - Response:
200 OKor404 Not Found
Submits a refund request for policy and AI evaluation, persisting the outcome and audit log.
- Request Body:
{ "orderId": "b88ff761-b566-40c9-b5ba-cbf759aa7798", "reasonCategory": "DAMAGED_ITEM", "customerStatement": "The mug handle was cracked on arrival." } - Validation:
orderId: valid UUID stringreasonCategory: validRefundReasonCategoryenumcustomerStatement: non-empty trimmed string
- Response:
200 OK(Note:{ "refundRequestId": "bacdd3be-5d0e-416e-af61-ac705c98fd03", "status": "APPROVED", "rulesTriggered": [], "aiReasoning": "Customer statement matches item in order. No discrepancies noted.", "customerMessage": "Your refund request has been approved." }rulesTriggeredis empty for clean approvals. If a constraint or flag is triggered, it contains the applicable rule codes such as["FINAL_SALE"],["REFUND_AMOUNT_OVER_LIMIT"], or["AI_FLAGGED_SUSPICIOUS"]).
Fetches all historical refund requests ordered by creation date descending.
- Response:
200 OK(Note: Pre-seeded mock fixtures in[ { "id": "bacdd3be...", "orderId": "b88ff761...", "customerId": "c001...", "reasonCategory": "DAMAGED_ITEM", "customerStatement": "The mug handle was cracked on arrival.", "status": "APPROVED", "createdAt": "2026-09-25T15:10:50.000Z", "order": { "id": "...", "orderNumber": "WN-1002" }, "customer": { "id": "...", "name": "Ethan Brooks", "email": "[email protected]" }, "auditLog": { "id": "...", "decision": "APPROVED", "rulesTriggered": ["DELIVERED_ORDER"], "aiReasoning": "...", "customerMessage": "..." } } ]seed.tsinclude descriptive baseline audit labels likeDELIVERED_ORDER,WITHIN_30_DAYS, andORDER_AT_500_DOLLARS).
Fetches a single refund request and its audit log by UUID.
- Path Parameter:
id(UUID) - Response:
200 OKor404 Not Found
Keep server secrets in ignored .env files or the Render environment settings. DATABASE_URL and OPENAI_API_KEY are server-only secrets. VITE_API_URL is public and is the only production frontend value required by this application. NODE_ENV, SEED_ON_START, and FRONTEND_ORIGIN control production startup and CORS behavior; see the deployment section below.
| Variable | Location | Description | Default / Example |
|---|---|---|---|
PORT |
Server / Root | Port for the Express backend server | 5000 |
NODE_ENV |
Server | Runtime environment; use production on Render |
development |
SEED_ON_START |
Server | Runs the destructive demo seed during startup; only local Compose sets this to true |
false |
FRONTEND_ORIGIN |
Server | Production frontend origin used by the CORS allowlist | http://localhost:5173 |
CORS_ORIGINS |
Server | Optional comma-separated additional production CORS origins | Empty |
DATABASE_URL |
Server / Root | PostgreSQL database connection string | postgresql://postgres:postgres@localhost:5432/refund_system?schema=public |
OPENAI_BASE_URL |
Server / Root | Base URL for OpenAI-compatible endpoint | https://openrouter.ai/api/v1 |
OPENAI_API_KEY |
Server / Root | API key for OpenAI / OpenRouter authentication | Required for AI evaluation |
OPENAI_MODEL |
Server / Root | Language model identifier | liquid/lfm-2.5-2.6b:free |
VITE_API_URL |
Client | Optional base URL for API calls from browser | "" (uses same-origin relative /api) |
VITE_PROXY_TARGET |
Client | Target host for Vite dev/preview /api reverse proxy |
http://localhost:5000 (local) / http://backend:5000 (Docker) |
For the reproducible local setup, prefer the Docker Compose instructions below. The backend's SEED_ON_START setting is intentionally explicit: local Compose sets it to true, while the production image defaults to false.
- Node.js 20+
- PostgreSQL 16+ running locally on port 5432 (or via Docker)
- OpenRouter or OpenAI API key
Start a PostgreSQL container if not running locally:
docker run --name worknoon-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=refund_system -p 5432:5432 -d postgres:16-alpinecd server
# Copy environment configuration
cp .env.example .env
# Edit .env with your OPENAI_API_KEY
# Install dependencies
npm install
# Run database migrations and generate Prisma client
npx prisma migrate dev
# Seed database with mock customers, orders, and refund requests
npm run db:seed
# Start backend dev server with hot reload
npm run devBackend runs at http://localhost:5000.
cd ../client
# Install dependencies
npm install
# Start Vite dev server
npm run devFrontend runs at http://localhost:5173.
- Customer Portal:
http://localhost:5173/ - Admin Dashboard:
http://localhost:5173/admin
The backend image runs Prisma migrations before the API starts. It only runs the destructive demo seed when SEED_ON_START=true, which is set explicitly by local Compose and must not be set in production.
- Copy environment variables in the project root:
cp .env.example .env
# Edit .env and supply your OPENAI_API_KEY- Start the stack:
docker compose up --buildPostgreSQL (healthy) ➔ Prisma Migrations (deploy) ➔ Local Mock Seed (explicit opt-in) ➔ Express Backend (ready) ➔ Frontend Preview
postgres: Launches PostgreSQL 16 on port 5432 with health checkpg_isready.backend: Waits for PostgreSQL to become healthy, runsnpx prisma migrate deploy, runsnpm run db:seedbecause Compose explicitly setsSEED_ON_START=true, and launches Express on port 5000 with a/readyprobe.frontend: Waits for backend to become healthy, serves production build via Vite preview on port 5173, and proxies/apirequests tohttp://backend:5000.
To stop the services and remove containers:
docker compose downTo remove volumes and start from a completely clean state:
docker compose down -vdocker compose down preserves the named PostgreSQL volume and its data. docker compose down -v permanently deletes the local database volume and all local demo data; use it only for an intentional reset. The next docker compose up --build reapplies migrations and recreates the fixtures.
GET /health is a liveness check and does not access PostgreSQL. GET /ready checks database connectivity and is used by the Compose health check.
Production uses the backend image from server/Dockerfile and a separately built frontend static site. The backend image runs prisma migrate deploy before npm start; it does not seed because its image default is SEED_ON_START=false. Never use prisma migrate dev or routine prisma db push against Aiven.
Create a Docker web service for this repository with:
- Dockerfile path:
server/Dockerfile - Docker build context:
server - Health check path:
/ready - Port:
5000(or thePORTvalue supplied by Render) NODE_ENV=productionDATABASE_URLset to Aiven's TLS connection URLFRONTEND_ORIGINset to the deployed frontend URL- The required
OPENAI_BASE_URL,OPENAI_API_KEY, andOPENAI_MODELvalues
Do not set SEED_ON_START=true in Render. If the Render UI does not expose a Docker context field for the selected service type, configure the repository root directory as server and use Dockerfile as the Dockerfile path. Verify the build logs show server as the build context.
Create a Static Site using the repository root, or set its root directory to client:
- Build command from the repository root:
cd client && npm ci && npm run build(fromclient:npm ci && npm run build) - Publish directory from the repository root:
client/dist(fromclient:dist) - Public variable:
VITE_API_URL=https://<your-backend>.onrender.com
VITE_API_URL is embedded in browser assets and must contain only the public API URL. Never put DATABASE_URL, OPENAI_API_KEY, or another secret in a VITE_ variable. Add a /* to /index.html rewrite if the Render static-site configuration requires one for Vue Router history mode.
Copy Aiven's connection string exactly, including TLS parameters, into Render's secret DATABASE_URL. Verify the database name, SSL mode, certificate requirements, connection limits, and service region in the Aiven console instead of assuming defaults.
After the first Render deployment reports /ready as healthy, run the seed once from a controlled machine with Docker. This does not depend on Render Shell availability:
docker build -t worknoon-refund-backend-seed .\server
$env:PRODUCTION_DATABASE_URL = "<paste the Aiven connection URL locally; do not commit it>"
docker run --rm `
-e DATABASE_URL="$env:PRODUCTION_DATABASE_URL" `
-e SEED_ON_START=false `
worknoon-refund-backend-seed `
sh -c "npx prisma migrate deploy && npm run db:seed"
Remove-Item Env:PRODUCTION_DATABASE_URLThe command uses the same Dockerfile and built application dependencies as the Render service, applies pending migrations before seeding, and exits after completion. Confirm PRODUCTION_DATABASE_URL points to the intended empty demo database first; the seed is intentionally destructive. Verify the synthetic data with SELECT COUNT(*) FROM "Customer";, SELECT COUNT(*) FROM "Order";, and SELECT COUNT(*) FROM "RefundRequest";. Leave SEED_ON_START unset or set to false in Render; deploys, restarts, and crashes must not rerun the seed.
Never commit or expose the URL. Smoke-test the customer flow at the frontend root, the admin dashboard at /admin, and the backend /health and /ready endpoints. Inspect Render runtime logs and Aiven metrics for migration or connection errors. All records are synthetic portfolio-demo data; this is not a production payment-processing service.
The seed script (server/prisma/seed.ts) generates realistic mock fixtures:
- 15 Customers: Diverse names and email addresses.
- 30 Orders: Ranging across standard and specialized testing scenarios:
WN-1001: Eligible order (Linen Overshirt & Wool Scarf, $129.00).WN-1002: Eligible order (Ceramic Travel Mug, $34.00).WN-1003: Final-sale order (isFinalSale: true, $78.00).WN-1004: Expired order (delivered >30 days ago).WN-1005: Recent order (delivered within 30 days).WN-1006: High-value order (Espresso Machine, $620.00 > $500).WN-1007: Mixed items order containing one final-sale item.WN-1008: Final-sale order with prompt injection text.WN-1009: Borderline high-value order (Total exactly $500.00).WN-1010: Expired order (delivered >30 days ago).WN-1011toWN-1030: Standard delivered orders ($84.00 total).
- 8 Historical Refund Requests: Pre-seeded with complete
AuditLogrecords spanningAPPROVED,DENIED, andESCALATEDoutcomes.
The backend includes an automated test suite verifying policy rules, AI integration, prompt injection handling, orchestration fail-safes, and persistence.
Run the test suite:
cd server
npm testpolicyEngine.test.ts:- Denies orders with final sale items.
- Denies orders purchased >30 days ago.
- Escalates orders over $500.
- Allows orders of exactly $500 to proceed to AI.
- Prioritizes denial over escalation when both apply.
aiService.test.ts:- Parses structured LLM responses.
- Detects instruction overrides and sets
suspicious: true. - Cleans internal AI phrasing from reasoning and messages.
- Handles missing API keys and OpenAI service errors gracefully.
refundOrchestrator.test.ts:- Short-circuits hard denials without calling the AI service.
- Short-circuits high-value escalations without calling the AI service.
- Approves eligible non-suspicious orders.
- Escalates suspicious requests and prompt injection attempts.
- Fails safe to
ESCALATEDif AI throws an error.
refundPersistence.test.ts:- Persists refund requests with nested audit logs.
- Resolves customer IDs automatically from orders.
- Integrates with orchestrator output.
index.test.ts:- Validates HTTP endpoints, input payloads, status codes, and JSON response shapes.
The refund workflow combines deterministic policy evaluation, AI-assisted request analysis, and backend-controlled final decisions. The AI does not directly modify order records or independently set the final refund status or amount.
- Deterministic refund policies take precedence over customer instructions and AI analysis.
- Final-sale and expired-window requests cannot be approved by the AI.
- Refunds exceeding the configured
$500threshold require escalation. - Known instruction-override patterns are escalated before AI analysis.
- AI analysis failures do not result in automatic approval.
- Customer-facing responses are validated against the final decision, with application-generated fallbacks for invalid or contradictory messages.
- Customer statements are treated as untrusted content and cannot modify trusted order fields such as purchase date, amount, final-sale status, or product identity.
- Known instruction-override patterns, including administrator, system-message, policy-bypass, date-manipulation, threshold-bypass, and human-review-suppression variants.
- Orchestration-boundary enforcement when an injected or alternate analyzer incorrectly returns an approval.
- Hard-policy precedence for final-sale, expired-window, and over-$500 requests, including verification that AI is not called.
- AI failures, malformed or schema-invalid responses, and invalid customer-facing messages.
- Customer-response validation, fallback behavior, and protection against returning the original customer statement.
- Full server test suite and backend build.
These checks were performed against synthetic demo data in the deployed application:
- Direct instruction-override attempt was escalated.
- Fake administrator or system-message override was escalated.
- An override embedded in a quoted delivery report was escalated.
- Mixed legitimate customer details and malicious instructions did not produce an approval.
- A normal damage report remained a false-positive control and followed the normal eligible flow.
- Final-sale and over-$500 policy precedence remained authoritative.
- Stored order fields and audit records remained consistent with trusted data.
These manual checks supplement the automated suite; they do not establish complete prompt-injection resistance. They were performed using synthetic demonstration records, not real customer or payment data.
Instruction detection uses deterministic pattern matching alongside AI analysis. Pattern matching does not cover every possible prompt-injection technique, and AI analysis remains probabilistic for novel or indirect attacks. The system is designed to preserve policy and decision integrity rather than claim complete detection of malicious language.
- Refund Window: Exactly 30 days (
purchaseDatemust be within 30 days of the evaluation date). - Refund Amount: Full order total (partial item-level refund amounts are not evaluated).
- Authentication: No user authentication or authorization is implemented, as specified by the assessment scope.
- Synthetic Data: Customer and order records are synthetic fixtures designed to cover all edge cases.
- Authority: The deterministic policy engine has final, binding authority over hard constraints (final sale, return window, amount threshold).
- LLM Role: The LLM provides semantic interpretation and discrepancy analysis rather than policy authority; it cannot approve orders that violate hard rules.
- Pre-Invocation Short-Circuiting: Skipping the LLM for final-sale and expired orders saves API token costs and latency, but skips conversational AI explanations for why an order was denied.
- Strict Structured Outputs: Using OpenAI JSON Schema mode guarantees type safety and parsing reliability, but restricts model selection to providers supporting structured JSON outputs.
- Fail-Safe to Human Review: When AI requests fail, the system escalates rather than denying. This protects customer trust at the cost of increasing manual support ticket volume.
- Monolithic Containerization: Serving the frontend build via Vite preview in Node rather than a standalone Nginx container keeps the Docker environment lightweight, unified, and free of multi-architecture binary issues.
- Partial Item Refunds: Allow customers to select specific items from multi-item orders rather than evaluating the entire order total.
- Authentication & RBAC: Implement session or JWT authentication and role-based access control separating customer accounts from support admin roles.
- Real-Time Webhooks: Integrate payment gateway webhooks (e.g. Stripe, Shopify) to trigger automatic payment disbursement upon approval.
- Admin Review Actions: Enable support agents to approve or reject escalated requests directly inside the Admin Dashboard.
- Observability & Tracing: Add OpenTelemetry spans and structured logging across policy evaluation and LLM API calls for real-time latency and token tracking.