# Documents & Evidence — BAMS Legal

> Versioned, confidentiality-tiered document management. Documents are also
> readable (read-only, opt-in-shared) through the M11 Client Portal.

---

## Table of Contents

1. [Document](#document)
2. [Document Version](#document-version)
3. [Evidence Item](#evidence-item)
4. [Document Lifecycle](#document-lifecycle)
5. [Portal Routes](#portal-routes)
6. [Notes & Guidelines](#notes--guidelines)

---

## Document

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `ulid` | ULID | Auto | — | Public identifier |
| `firm_id` | FK | Yes | — | |
| `matter_id` | FK | No | `null` | |
| `client_id` | FK | No | `null` | |
| `title` | String | Yes | — | |
| `legal_type` | Enum | No | `null` | `pleading`, `motion`, `affidavit`, `written_address`, `judgment`, `ruling`, `contract`, `correspondence`, `evidence`, `court_process`, `opinion`, `other` |
| `confidentiality` | Enum | No | `standard` | `standard`, `confidential`, `privileged` — **see notes below** |
| `current_version` | Integer | No | `1` | |
| `uploaded_by` | FK (User) | Yes | — | |

### Admin Routes

**Base:** `/admin/documents` · **Auth:** session, permission `documents.view` / `documents.viewConfidential` (tier-dependent)

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/documents` | List (filters: matter, client, legal_type, uploader, date range, confidentiality) |
| `GET` | `/admin/documents/search?q=` | Full-text search — respects confidentiality at the query level |
| `GET` | `/admin/documents/:id` | Get document detail + version history |
| `POST` | `/admin/matters/:id/documents` | Upload a new document |
| `PATCH` | `/admin/documents/:id` | Update title/legal_type/confidentiality |
| `POST` | `/admin/documents/:id/privileged-access` | Grant/revoke ethical-wall access (privileged tier only) |
| `DELETE` | `/admin/documents/:id` | Soft delete |

### Request & Response Examples

**Upload** — `POST /admin/matters/:id/documents` (multipart)

```
title: "Statement of Claim"
legal_type: "pleading"
confidentiality: "standard"
file: <binary>
```

```json
{
  "success": true,
  "data": { "id": 900, "ulid": "01J...", "current_version": 1 },
  "message": "Document uploaded."
}
```

**Denied confidential access** — `GET /admin/documents/:id` (non-team user, no `documents.viewConfidential`)

```json
{ "success": false, "error": { "code": "FORBIDDEN", "message": "You don't have access to this document." } }
```

### Validation Rules

| Field | Rules |
|---|---|
| `title` | Required. Max 255. |
| `confidentiality = privileged` | Requires an explicit allow-list to be set (managed by Firm Admin only) before any non-Firm-Admin can view it. |
| File | Max size from config; scanned MIME type must match declared type. |

---

## Document Version

Append-only — versions are never overwritten or deleted.

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `document_id` | FK | Yes | — | |
| `version_number` | Integer | Yes | — | |
| `file_path` | String | Yes | — | Private disk only |
| `original_name` | String | Yes | — | |
| `size` | Integer | Yes | — | Bytes |
| `mime` | String | No | `null` | |
| `sha256` | String | Auto | — | Integrity checksum, computed on upload |
| `change_note` | Text | No | `null` | |
| `uploaded_by` | FK (User) | Yes | — | |

### Admin Routes

| Method | Endpoint | Description |
|---|---|---|
| `POST` | `/admin/documents/:id/versions` | Upload a new version |
| `GET` | `/admin/documents/:id/versions` | List version history |
| `GET` | `/admin/documents/:id/versions/:v/download` | Authorized, streamed, logged download of a specific version |

### Request & Response Examples

**Upload new version** — `POST /admin/documents/:id/versions`

```
change_note: "Corrected paragraph 4 per court's directive"
file: <binary>
```

```json
{
  "success": true,
  "data": { "version_number": 2, "sha256": "a94a8fe5..." },
  "message": "Version 2 uploaded. Version 1 is retained."
}
```

### Notes & Guidelines

- Every download is streamed through an authorized controller action and
  written to the activity log ("downloaded v2 of Statement of Claim") — there
  is no direct public file URL for any document above `standard` confidentiality.

---

## Evidence Item

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `matter_id` | FK | Yes | — | |
| `document_id` | FK | No | `null` | |
| `exhibit_number` | String | Yes | — | e.g. "Exhibit A", unique per matter |
| `title` | String | Yes | — | |
| `side` | String | No | `null` | `plaintiff`, `defence` |
| `description` | Text | No | `null` | |
| `custody_notes` | Text | No | `null` | Chain of custody |
| `status` | Enum | No | `proposed` | `proposed`, `tendered`, `admitted`, `rejected` |
| `hearing_id` | FK | No | `null` | Set when tendered |

### Admin Routes

**Base:** `/admin/matters/:matterId/evidence` · **Auth:** session, permission `documents.manageEvidence`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/matters/:id/evidence` | List a matter's exhibit register |
| `POST` | `/admin/matters/:id/evidence` | Add an exhibit |
| `PATCH` | `/admin/evidence/:id/status` | Update status (link the tendering hearing) |
| `GET` | `/admin/matters/:id/evidence/print` | Printable exhibit list (queued PDF) |

### Validation Rules

| Field | Rules |
|---|---|
| `exhibit_number` | Required. Unique per matter. |
| Status `tendered`/`admitted`/`rejected` | Requires `hearing_id`. |

---

## Document Lifecycle

```
POST /admin/matters/:id/documents
        │
        ▼
  current_version = 1, sha256 computed
  matter_events row: "document_added"
        │
        ▼ (as the document is amended over time)
POST /admin/documents/:id/versions
        │
        ▼
  current_version incremented, prior versions retained forever
        │
        ▼ (litigation documents only — optional)
POST /admin/matters/:id/evidence  (linking this document as an exhibit)
        │
        ▼
PATCH /admin/evidence/:id/status → tendered → admitted / rejected
```

---

## Portal Routes

**Base:** `/portal/documents` · **Auth:** portal session, scoped to `shared_documents` only

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/portal/documents` | List documents explicitly shared with this client |
| `GET` | `/portal/documents/:ulid/download` | Authorized, streamed, logged download |

> No portal upload/versioning routes exist. Sharing is exclusively a staff-
> initiated action from the admin side (see M11).

---

## Notes & Guidelines

- **Three confidentiality tiers, enforced by policy, not UI hiding:**
  `standard` (matter team + `documents.viewAll`), `confidential` (matter team +
  `documents.viewConfidential`), `privileged` (explicit per-user allow-list
  only, regardless of team membership — for conflict-sensitive "ethical wall"
  situations).
- Search results are filtered **at the query level** by confidentiality — a
  privileged document never appears even as a title in another user's search
  results, not just when opened.
- `confidentiality != standard` documents **cannot** be added to
  `shared_documents` — this is enforced in the sharing Action itself (M11), so
  there is no code path, UI bug, or admin mistake that leaks one to a client.
