# ACS Diagnose — Backend Architecture

**Status:** MVP architecture and API contract baseline  
**Date:** 6 July 2026  
**Hosting constraint:** Same Namecheap/cPanel account as `ammancarservices.com`  
**API contract:** `docs/acs-diagnose-openapi.yaml`

## Decision

Deploy ACS Diagnose as a **separate Node.js 22 cPanel application** behind
`https://api.ammancarservices.com/diagnose/v1`. It can live on the same hosting account,
but it must not share a process, application directory, secrets or public file directory
with the current website or invoice assistant.

Use the host's MariaDB/MySQL service for operational data. Store uploaded photographs in
a private directory outside `public_html`, referenced by opaque database IDs and served
only through an authorized application route. Use a database-backed jobs table plus cPanel
cron for explanation, notification and deletion work; Redis is not required for the pilot.

This is the lowest-complexity architecture that fits the existing host while preserving a
clean migration path if diagnostic volume or security requirements later exceed shared
hosting.

## Logical layout

```text
iOS / Android apps
        |
        | TLS + ACS bearer token
        v
api.ammancarservices.com/diagnose/v1
        |
        +-- authentication and role checks
        +-- customer / vehicle / consent API
        +-- idempotent scan ingestion
        +-- report and staff preparation API
        +-- private attachment API
        |
        +----------> MariaDB / MySQL
        |             accounts, sessions, vehicles, scans,
        |             service requests, jobs, audit events
        |
        +----------> private file storage
        |             dashboard photos (outside public_html)
        |
        +----------> background worker via cPanel cron
                      OpenAI explanation (later credentials)
                      exports, retention and deletion
```

The marketing website remains static and independent. A later web staff console may call
the same API, but is not required to launch the native-app pilot.

## Deployment layout

Recommended cPanel arrangement:

```text
/home/<cpanel-user>/acs-diagnose-api/       Node API application
/home/<cpanel-user>/acs-diagnose-private/   private attachments and exports
/home/<cpanel-user>/amman-car-services-website/ existing website project
```

The API should have its own cPanel application entry, environment variables and startup
command. Configure the subdomain through cPanel and force HTTPS. Do not place API source,
`.env`, database backups, attachments or privacy exports under `public_html`.

Before deployment, confirm the hosting plan provides:

- a persistent Node.js 22 application;
- MariaDB/MySQL with TLS or a local Unix connection;
- cPanel cron with at least five-minute scheduling;
- sufficient private disk and backup controls;
- request-body limits that support the chosen photograph limit; and
- application logs that can be rotated without recording tokens or diagnostic payloads.

## Service boundaries

The MVP can be one modular Node application. Separate internal modules should own:

| Module | Responsibility |
|---|---|
| Identity | Verify the future OTP-provider token, issue/revoke ACS sessions, device binding |
| Accounts | Profile, language, onboarding and server-controlled roles |
| Consent | Immutable, versioned grant and withdrawal events |
| Vehicles | Customer-confirmed identity and VIN-conflict handling |
| Scan ingestion | Schema validation, hash/idempotency enforcement, immutable evidence storage |
| Assessment | Verify the client rules version/result and optionally recompute server-side |
| Explanation | Minimize evidence, call OpenAI later, validate structured output, apply fallback copy |
| Attachments | Validate, strip metadata, store privately and enforce consent/retention |
| Workshop | Sharing, service requests, prioritized preparation queue and review appendices |
| Privacy | Access export, correction, deletion, retention and backup-expiry tracking |
| Audit | Append-only security and staff-access events |

Do not split these into networked microservices for the pilot. The operational burden would
be larger than the expected benefit on cPanel.

## Authentication placeholder

The OpenAPI contract intentionally does not implement SMS delivery. The future flow is:

1. The native app asks the selected OTP provider to verify `+973` mobile ownership.
2. The provider returns a short-lived signed identity token.
3. The app sends that token to `POST /auth/exchange`.
4. The API validates issuer, audience, signature, expiry and verified phone claim.
5. The API maps the verified number to an ACS account and issues a short-lived ACS access
   token plus a rotating refresh token.

Roles and capabilities come only from the ACS database. Ahmed's or his father's number may
identify an account, but the number itself must never activate staff access. Staff mode also
requires a short-lived step-up token and every report opening requires an access purpose.

Suggested session defaults for implementation review:

- access token: 15 minutes;
- rotating refresh token: 30 days, hashed at rest;
- staff step-up token: 10 minutes;
- immediate revocation by account, device or session;
- device attestation supported as an optional field from day one.

## Offline upload and immutability

The phone creates `scan_id` and `Idempotency-Key` before upload. The API processes an upload
in one database transaction:

1. Authenticate and confirm the scan belongs to one of the account's vehicles.
2. Validate the body against `acs-diagnose-schema.json` and accepted version lists.
3. Recalculate the canonical raw-evidence hash; do not trust only the supplied hash.
4. If no scan exists, store the immutable evidence and initial assessment.
5. If the same scan ID and hash exist, return `200 reconciled`.
6. If the scan ID exists with a different hash, return `409 SCAN_HASH_CONFLICT`; preserve
   both the server record and local client record for manual review.
7. Commit the scan and explanation job together using a transactional outbox/jobs row.

The server may append explanations, sharing events and staff reviews. It must never update
the original OBD evidence in place. Corrections create a new version or appendix with actor,
reason and timestamp.

## Suggested database model

Use opaque UUIDv7 or equivalent IDs. Direct identity is separated from diagnostic evidence.

| Table | Important fields / rule |
|---|---|
| `accounts` | normalized verified mobile encrypted; mobile lookup HMAC; name; language; status |
| `account_roles` | account, role, granted/revoked by and timestamps |
| `devices` | account, platform, hashed device ID, attestation state, revoked time |
| `sessions` | hashed refresh token, device, expiry, rotation family, revoked time |
| `consent_events` | purpose, policy version, granted/withdrawn, account, time; append-only |
| `vehicles` | account, plate encrypted + lookup HMAC, customer VIN encrypted, model/year/powertrain |
| `scans` | scan ID, account ref, vehicle ref, status, urgency, schema/rules versions, evidence hash |
| `scan_payloads` | immutable normalized JSON plus optional encrypted raw command transcript |
| `explanations` | scan, language, state, validated JSON, model/prompt versions, failure class |
| `attachments` | scan, private path, hash, MIME, size, consent version, deletion deadline |
| `service_requests` | scan, contact preference, status, timestamps |
| `scan_share_events` | scan, share/withdraw event, consent/purpose and actor; append-only |
| `staff_reviews` | scan, staff account, outcome, internal note, timestamp; append-only |
| `audit_events` | actor, action, target, purpose, request ID, IP/device metadata; append-only |
| `jobs` | type, target, payload reference, state, attempts, next run, locked time |
| `idempotency_keys` | account, route, key hash, request hash, response reference, expiry |
| `privacy_requests` | type, scope, state, verification and completion timestamps |

For query performance, keep searchable status, urgency, dates and opaque references in
columns. Store the complete versioned scan document as JSON as well, but do not depend on
JSON queries for every staff-queue request.

## Background work on cPanel

A cron-triggered worker claims jobs with a database lock and a lease. Jobs must be retryable
and idempotent. Initial job types are:

- `generate_explanation`;
- `send_urgent_whatsapp` (disabled until provider and consent workflow are configured);
- `prepare_privacy_export`;
- `delete_expired_attachment`;
- `delete_expired_scan`;
- `expire_security_logs`; and
- `reconcile_stuck_job`.

The worker should use capped exponential retry, a maximum-attempt policy and a dead-letter
state visible to an administrator. A failed AI or messaging job never changes or delays the
deterministic safety result.

## OpenAI boundary placeholder

Credentials and final model configuration will be environment variables added later. The
future explanation worker must:

- receive no mobile number, name, plate, direct account ID or full VIN;
- send only the deterministic assessment and minimum supporting evidence;
- set API response storage off where supported;
- require strict structured output matching the approved explanation contract;
- reject any output that changes urgency, invents evidence or names an unverified failed part;
- store model, prompt and schema versions with the explanation; and
- fall back to reviewed deterministic English/Arabic copy on any provider failure.

Photo interpretation is a separate, explicit-consent operation. Its output is an observation
such as a visible symbol or transcribed message, never a vehicle-read DTC.

## SMS placeholder and direct WhatsApp handoff

No provider SDK should be imported into the core account module. Define the identity adapter:

```text
IdentityTokenVerifier.verify(token) -> verified mobile identity
```

This adapter will later verify the selected OTP provider. Development uses a safe fake and
must not send real SMS messages.

For service requests, the API creates a `https://wa.me/97339680866?text=...` URL and the app
opens it. The customer reviews and sends the prefilled message from their own WhatsApp. No
WhatsApp provider, access token, webhook, background message or proactive ACS message is
used. The prefilled text includes the scan reference, broad urgency, vehicle model/year and
customer request, but excludes VIN, plate, DTCs and detailed diagnostic evidence.

Urgent in-app safety copy is displayed immediately before WhatsApp is opened. Since this
design cannot proactively alert staff, an urgent scan enters the in-app staff queue, but ACS
does not assume the customer contacted the workshop until they send the WhatsApp message or
create another service request.

## API behavior conventions

- Base path: `/diagnose/v1`; breaking changes require a new major path.
- JSON timestamps: RFC 3339 UTC on the server; clients localize to Bahrain/device time.
- Errors: `application/problem+json` with stable machine-readable `code` and `request_id`.
- Pagination: opaque cursor, maximum 100 records.
- Mutating retries: `Idempotency-Key` where declared in OpenAPI.
- Unknown resource ownership returns `404`, not a revealing authorization message.
- Limits: propose 2 MiB for scan JSON and 8 MiB per photo pending real-device measurements.
- Logs must redact authorization headers, identity tokens, mobile numbers, plate, VIN, DTCs,
  notes, raw scan bodies and images.

## Security baseline before the pilot

- TLS only; HSTS after subdomain validation.
- Secrets only in cPanel environment variables, with separate development/production values.
- Database user limited to the ACS Diagnose database.
- Mobile, plate and VIN encrypted at the application layer; lookup values use a keyed HMAC,
  not a plain unsalted hash.
- Refresh tokens stored hashed; access tokens short-lived and signed with a rotatable key.
- Strict request-size, MIME and image-decompression limits.
- Image metadata stripped and downloads authorized; never expose storage paths.
- Rate limits for auth exchange, refresh, attachment upload and explanation retry.
- Append-only staff access audit, with administrator review and three-year retention.
- Dependency lockfile, automated vulnerability review and tested database/file restoration.
- Production error messages contain no stack trace or provider response body.

The shared hosting account is acceptable for a controlled MVP only after these controls and
host capabilities are verified. Move the API and database to a managed Bahrain/GCC-suitable
environment before wider rollout if cPanel cannot provide reliable process isolation,
private backups, encryption key handling or required data-transfer assurances.

## Environment variables (names only)

```text
NODE_ENV
APP_BASE_URL
DATABASE_URL
TOKEN_SIGNING_PRIVATE_KEY
TOKEN_SIGNING_KEY_ID
IDENTITY_PROVIDER
IDENTITY_PROVIDER_PROJECT_ID
IDENTITY_PROVIDER_CREDENTIALS
OPENAI_API_KEY
OPENAI_MODEL
OPENAI_PROMPT_VERSION
WHATSAPP_NUMBER
PRIVATE_UPLOAD_DIR
PRIVATE_EXPORT_DIR
FIELD_ENCRYPTION_KEY
LOOKUP_HMAC_KEY
MAX_SCAN_BYTES
MAX_PHOTO_BYTES
```

Values must never be committed. Provider variables remain unset while their features are
disabled.

## Implementation order before the adapter arrives

1. Review and freeze OpenAPI v0.1 with example requests/responses.
2. Scaffold the Node API, database migrations and development-only fake identity verifier.
3. Implement account, consent and vehicle endpoints.
4. Implement idempotent scan ingestion against the existing synthetic fixtures.
5. Implement history, explanation status and the database-backed worker with a fake explainer.
6. Implement private photo upload and deletion.
7. Implement staff step-up, purpose-limited queue and audit events.
8. Generate Swift and Kotlin API models/clients from the frozen contract.
9. Add the real OTP verifier and OpenAI adapter only when credentials and provider choices
   are ready. The WhatsApp handoff needs no provider credentials.
10. Add hardware-derived timing and scan fields without weakening the versioned contract.

## Contract acceptance checks

Before native implementation begins, automated tests should prove:

- all existing synthetic scan fixtures validate;
- an identical offline retry returns `reconciled`;
- a same-ID/different-hash retry returns `SCAN_HASH_CONFLICT` without overwriting data;
- customers cannot read another account's vehicle or scan;
- staff cannot open the queue without a valid role and step-up token;
- every staff report open records purpose and an audit event;
- an AI/provider failure leaves deterministic assessment unchanged;
- deleting a photo does not delete or alter diagnostic evidence; and
- logs and provider payloads contain none of the prohibited identity fields.
