# cXML File Move — Technical Reference

## How It Works in Practice

### Inbound (SAP → capacic.com → Tekpal)

SAP creates a purchase order in SAP Ariba Network → SAP Managed Gateway pushes it as cXML via HTTPS POST to **capacic.com** endpoint (`/rest/nc/upload/cxml`). The request goes through Caddy (TLS) → nc-server → `cxml-worker.upload()` saves the XML file to `upload_file_path/`. If the request is `multipart/mixed` (XML + PDF attachments), the worker extracts the XML part and saves attachments alongside it. A synchronous cXML `<Response code="200">` is returned.

Tekpal's **download timer** (every 10 min) connects to capacic.com's `/rest/nc/download/cxml` endpoint, receives all pending files, saves them to local `download_file_path`, then confirms receipt. Confirmed files are moved to `processed/` on capacic.com.

Tekpal's **importAndSend timer** (every 5 min) reads `.xml` files from `import_path`, converts cXML to sales orders via `convert.xmlToJson()`, saves to database via `dsave.saveJson()`. Moved to `processed/` after import.

### Outbound (Tekpal → SAP)

The **importAndSend timer** also reads exported cXML Order Confirmations from `export_path` and sends each via `sendCxml()` to SAP Managed Gateway with client certificate authentication. Successfully sent files are moved to `processed/`.

### Timers

| Timer | Interval | Function | What It Does |
|-------|----------|----------|--------------|
| Download timer | `"every 10 min"` | `cxml-worker.download` | Downloads pending files from capacic.com, saves locally, confirms receipt |
| Import+Send timer | `"every 5 min"` | `cxml-worker.importAndSend` | Imports XML from `import_path` to database, sends exported confirmations from `export_path` to SAP |

### Architecture

```mermaid
sequenceDiagram
    participant SAP as SAP Ariba
    participant CAP as capacic.com
    participant TEK as Tekpal nc-server
    participant DB as Database
    participant SMG as SAP Managed Gateway

    Note over SAP,CAP: Step 1: SAP sends PO
    SAP->>CAP: HTTPS POST /rest/nc/upload/cxml
    CAP->>CAP: save to upload_file_path/
    CAP-->>SAP: cXML 200 OK

    Note over CAP,TEK: Step 2: Download timer (every 10 min)
    TEK->>CAP: POST /rest/nc/download/cxml
    CAP-->>TEK: all .xml + .pdf files
    TEK->>TEK: save to download_file_path
    TEK->>CAP: POST /rest/nc/download/cxml {file_name_array}
    CAP->>CAP: move confirmed to processed/

    Note over TEK,DB: Step 3: Import+Send timer (every 5 min)
    TEK->>TEK: importAndSend(): import XML from import_path
    TEK->>DB: convert cXML → save sales orders
    TEK->>DB: query confirmed orders → export cXML to export_path
    TEK->>SMG: sendCxml() order confirmations with client cert
```

## SAP Ariba Network Integration

SAP Ariba Network is a cloud-based B2B marketplace. Sandvik (buyer) sends purchase orders via Ariba to Tekpal (supplier). The protocol is **cXML** (Commerce eXtensible Markup Language) v1.2.x over HTTPS POST.

### Document Flow Overview

```mermaid
graph LR
    subgraph "Sandvik (Buyer)"
        S[SAP ERP]
    end
    subgraph "SAP Managed Gateway"
        MG[Buyer SMG]
        SMG[Supplier SMG]
    end
    subgraph "Tekpal (Supplier)"
        C[Caddy :443]
        N[nc-server :5959]
    end

    S -->|cXML PO| MG
    MG -->|HTTPS POST| SMG
    SMG -->|HTTPS POST| C
    C -->|forward| N
    N -->|cXML Response 200| C
    C -->|200 OK| SMG

    N -->|Order Confirmation| SMG
    SMG -->|HTTPS POST| MG
    MG -->|cXML OC| S
```

### How SAP Ariba Sends Documents (Inbound)

Ariba uses **Supplier Managed Gateway** (SMG): Ariba pushes cXML documents to a supplier-hosted HTTPS endpoint. Ariba retries on failure with exponential backoff. The supplier must return a synchronous cXML `<Response>` acknowledgment — either `200 OK` (received) or an error code. Ariba considers the delivery failed only after all retries are exhausted.

### How We Send Documents to SAP (Outbound)

We POST cXML documents (Order Confirmations, Invoices, ASNs) to the SAP Managed Gateway endpoint. Authentication is via **client certificate** (TLS mutual authentication). The SAP endpoint URL and certificate paths are configured in `rec.json`.

### Ariba Network IPs

Ariba source IP ranges for allowlisting:

- `155.56.128.0/17`
- `130.214.160.64/28`
- `130.214.160.80/29`

### cXML Authentication Layers (Inbound)

| Layer | Mechanism | Where |
|-------|-----------|-------|
| Network | IP allowlisting (Caddy) | `/etc/caddy/Caddyfile` |
| Transport | HTTPS/TLS | Caddy terminates TLS |
| Application | `<Sender><Credential><SharedSecret>` | cXML `<Header>` element |
| Service | nc-server skips `parseBody()` + `jsonValue()` auth | `lib/rest.lua` |

### cXML Authentication Layers (Outbound)

| Layer | Mechanism | Where |
|-------|-----------|-------|
| Transport | HTTPS/TLS with client certificate | `callRest.postUrl()` with `certificate_path` + `private_key_path` |
| Application | cXML `<Header>` with sender credentials | cXML document body |

## Architecture

```mermaid
graph TB
    subgraph "SAP Ariba Network"
        AR[Ariba SMG]
    end

    subgraph "capacic.com"
        C2[Caddy :443]
        N2[nc-server :5959]
        UF[upload_file_path]
    end

    subgraph "Tekpal nc-server"
        RL[rest.lua]
        CW[cxml-worker.lua]
        DF[download_file_path]
        IMP[import_path]
        EXP[export_path]
    end

    AR -->|HTTPS POST cXML| C2
    C2 -->|forward| N2
    N2 -->|save .xml + .pdf| UF

    subgraph "Download Timer"
        CW2[cxml-worker.download]
    end

    CW2 -->|POST /rest/nc/download/cxml| N2
    N2 -->|file_array| CW2
    CW2 -->|save files| DF
    CW2 -->|confirm| N2
    N2 -->|move to processed/| UF

    subgraph "Import+Send Timer"
        CW3[cxml-worker.importAndSend]
    end

    IMP -->|read .xml| CW3
    CW3 -->|convert + dsave| DB[(Database)]
    EXP -->|read .xml| CW3
    CW3 -->|HTTPS POST + client cert| AR

    subgraph "Send to SAP"
        CW4[cxml-worker.sendCxml]
        SAP[SAP Managed Gateway]
    end

    CW4 -->|HTTPS POST + client cert| SAP
    SAP -->|cXML Response| CW4
```

## Client Certificate

### Certificate Details

| Property | TEST | PRODUCTION |
|----------|------|------------|
| URL | `https://test-integration.eu.managedgateway.cloud.sap/http/receiveInbcXML` | `https://integration.eu.managedgateway.cloud.sap/http/receiveInbcXML` |
| Auth Type | Certificate or Basic | Certificate or Basic |
| SSL Cert Serial | `04cd610780bf7ed9d9bd9a1f982800ad` | `043a492963405a0c59fc3a47f664c4de` |
| SSL Cert Expiry | 18 December 2025 05:29:59 | 18 December 2025 05:29:59 |

### Certificate Files

| File | Path |
|------|------|
| Client certificate | `nc-preference/certificate/sap-cxml/cxml-cert.pem` |
| Private key | `nc-preference/certificate/sap-cxml/cxml-key.pem` |
| PFX bundle | `nc-preference/certificate/sap-cxml/cxml-cert.pfx` |

### Certificate Config (`nc-preference/certificate/cert.json`)

The `cxml` entry in `cert.json` uses the same field names as ksef entries:

```json
"cxml": {
    "name": "cxml",
    "mode": "demo",
    "certificate_password": "cxml-cert",
    "certificate_path": "nc-preference/certificate/sap-cxml/cxml-cert.pem",
    "private_key_path": "nc-preference/certificate/sap-cxml/cxml-key.pem",
    "pfx_path": "nc-preference/certificate/sap-cxml/cxml-cert.pfx",
    "common_name": "suomentekniikkapalvelu.fi",
    "country": "FI",
    "days_valid": 365,
    "key_size": 2048,
    "organization_name": "Suomen Tekniikkapalvelu Oy",
    "organizational_unit": "IT"
}
```

`rec.json` references this entry via `cert_name`: `"cert_name": "cxml"`.

### Auto-Generation on First Use

`sendCxml()` loads cert config from `cert.json` via `cert_name`. If `certificate_path` or `private_key_path` files are missing, it generates using existing `certificate.generatePrivateKey()` + `certificate.generateCertificate()` (FFI, no command line).

Flow:

```mermaid
sequenceDiagram
    participant W as sendCxml()
    participant CJ as cert.json
    participant C as certificate module
    participant F as File System

    W->>CJ: read cert.json[cxml]
    W->>F: exists(certPath)? exists(keyPath)?
    alt both exist
        W-->>W: skip generation
    else missing
        W->>C: generatePrivateKey(keyPath, config)
        C->>F: writeFile(keyPath)
        W->>C: generateKsefCertificate(certPath, nil, keyPath, config)
        C->>F: writeFile(certPath)
    end
```

### Manual Certificate Regeneration

Delete both files to force regeneration on next send:

```bash
rm nc-preference/certificate/sap-cxml/cxml-cert.pem nc-preference/certificate/sap-cxml/cxml-key.pem
```

The next `sendCxml()` call will auto-generate new ones.

### Certificate Expiry

Current certificate expires **18 December 2025**. Renew before expiry. SAP SI team will provide updated serial numbers and certificates during renewal.

## Endpoint: `/rest/nc/upload/cxml`

| Property | Value |
|----------|-------|
| Method | `POST` |
| Content-Type | `text/xml`, `application/xml`, or `multipart/mixed` (with attachments) |
| Body | Raw cXML document, or multipart MIME with XML part + attachments |
| Response Content-Type | `application/xml` |
| Response Format | cXML `<Response>` |
| Auth | Caddy IP + HTTPS + SharedSecret |

### Request Flow

```mermaid
sequenceDiagram
    participant A as Ariba / Client
    participant C as Caddy :443
    participant R as rest.lua
    participant W as cxml-worker
    participant F as File System

    A->>C: HTTPS POST /rest/nc/upload/cxml
    C->>C: Check source IP
    alt IP not allowed
        C-->>A: 403 Forbidden
    else IP allowed
        C->>R: Forward to :5959
        R->>R: Skip parseBody() for /rest/nc/upload/cxml
        R->>R: Skip jsonValue() auth, pass raw sock
        R->>W: upload(sock)
        W->>W: Check Content-Type from sock.header
        alt multipart/mixed
            W->>W: parseMultipart() — extract XML part + attachments
            W->>F: writeFile(payloadID-timestamp.xml)
            W->>F: writeFile(attachment for each PDF/binary part)
        else text/xml
            W->>W: Read sock.body as XML
            W->>F: writeFile(payloadID-timestamp.xml)
        end
        W->>W: Extract payloadID from cXML
        W->>F: createPath(upload_file_path)
        W->>F: writeFile(payloadID-timestamp.xml)
        W-->>R: {mime="application/xml", data=cXML Response}
        R-->>C: HTTP 200 application/xml
        C-->>A: cXML Response 200 OK
    end
```

### cXML Response Format

Success:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<cXML payloadID="uuid" timestamp="2026-04-16T15:05:38Z">
  <Response>
    <Status code="200" text="OK">Received</Status>
  </Response>
</cXML>
```

Error responses:

| Status Code | Text | When |
|-------------|------|------|
| 200 | OK | Document saved successfully |
| 400 | Bad Request | Empty body |
| 401 | Unauthorized | SharedSecret mismatch |
| 500 | Internal Server Error | File save failed |

## Outbound: Send to SAP

### `sendCxml(xmlContent)` — POST cXML to SAP with Client Certificate

Sends a single cXML document to SAP Managed Gateway using `callRest.postUrl()` with TLS client certificate authentication.

```mermaid
sequenceDiagram
    participant W as cxml-worker.sendCxml()
    participant CR as callRest.postUrl()
    participant SAP as SAP Managed Gateway

    W->>W: Load rec.json config
    W->>CR: {url=sap_url, post_data=xmlContent, certificate_path, private_key_path}
    CR->>CR: setOption() detects cert+key
    CR->>CR: Create TLS socket with client cert
    CR->>SAP: HTTPS POST /http/receiveInbcXML
    SAP->>SAP: Validate client certificate
    SAP->>SAP: Process cXML
    SAP-->>CR: cXML Response
    CR-->>W: body, err
    W->>W: return body, err
```

### Usage

```lua
local cxmlWorker = require "nc/worker/cxml-worker"
local xmlContent = fs.readFile("/path/to/order-confirmation.xml")
local body, err = cxmlWorker.sendCxml(xmlContent)
if err then
    util.printError("SAP send failed: %s", err)
end
```

## File Naming

Received cXML documents are saved with a unique filename that prevents overwrites:

```text
{payloadID}-{YYYY-MM-DD_HH.MM.SS}.{ms}.xml
```

| Part | Source | Example |
|------|--------|---------|
| `payloadID` | Extracted from cXML `<cXML payloadID="...">` using PEG | `45C2325184CD1FE0AAB37CE40F84CC52` |
| `-` | Separator | |
| timestamp | `dt.currentFileStringMs()` — server local time with ms | `2026-04-16_18.07.16.12345` |
| `.xml` | Extension | |

The `payloadID` is the Ariba-assigned unique document identifier. Ariba recommends `datetime.processid.random@hostname` format. The same `payloadID` is reused on retries — the timestamp suffix ensures each save is unique even for retries.

## Folder Structure

```text
/Volumes/nc/work/tekpal/Sandvik-SAP-edi/
├── import/sales-order/
│   ├── upload/                    ← received via /rest/nc/upload/cxml on capacic.com (upload_file_path)
│   │   ├── processed/             ← moved after confirm download
│   │   ├── 45C2325184-2026-04-16_18.07.16.xml
│   │   ├── 45C2325184-2026-04-16_18.07.16.pdf   ← attachment from multipart/mixed
│   │   └── 45C2325185-2026-04-16_18.07.17.xml
│   ├── download/                  ← received via download() from capacic.com (download_file_path)
│   │   └── 45C2325184-2026-04-16_18.07.16.xml
│   ├── sap-test/                  ← test XML files for sendExample() (send_test_path)
│   │   └── 4500036247.xml
│   ├── processed/                 ← moved after import (import_move_folder)
│   └── *.xml                      ← files ready for import (import_path)
├── export/order-confirmation/     ← exported cXML documents (export_path)
│   ├── processed/                 ← moved after send (export_move_folder)
│   └── confirmation-OC123456.xml
└── (certificates in nc-preference/certificate/sap-cxml/)
```

## Configuration

### `form/nc/erp-sync/cxml/rec.json`

| Field | Purpose | Example |
|-------|---------|---------|
| `import_path` | Main import folder for cXML-to-order processing | `.../import/sales-order/` |
| `upload_file_path` | Where `/rest/nc/upload/cxml` saves received files | `.../import/sales-order/upload/` |
| `download_file_path` | Where `download()` saves files from remote | `.../import/sales-order/download/` |
| `send_test_path` | Test XML files for `sendExample()` and `download()` | `.../import/sales-order/sap-test/` |
| `export_path` | Where exported cXML documents are saved | `.../export/order-confirmation/` |
| `upload_url` | URL for `sendExample()` curl-based send to capacic.com | `https://capacic.com/sap/import` |
| `download_url` | URL for `download()` bulk file download from capacic.com | `https://capacic.com/sap/export` |
| `cert_name` | Key in `nc-preference/certificate/cert.json` for client cert config | `cxml` |
| `remote_url` | Remote endpoint URL for outbound documents | `https://test-integration.eu.managedgateway.cloud.sap/http/receiveInbcXML` |
| `remote_url_production` | Production URL (switch `remote_url` to this for go-live) | `https://integration.eu.managedgateway.cloud.sap/http/receiveInbcXML` |
| `remote_connect_timeout` | Connection timeout for remote calls (seconds) | `5` |
| `cxml_shared_secret` | Application-level SharedSecret for inbound validation | (empty = skip check) |
| `confirm_send_delay_min` | Minutes to wait after confirmation before sending to SAP | `10` |
| `import_move_folder` | Move files to `processed/` after import | `true` |
| `export_move_folder` | Move files to `processed/` after export | `true` |

### `database/connection/rest-cxml.json`

Connection config reference for SAP Managed Gateway. Contains TEST and PRODUCTION connection definitions with certificate paths. Used as documentation and can be used by `callRest.connect()` for persistent connections if needed.

### Switching from TEST to PRODUCTION

1. Update `remote_url` in `rec.json` to the production URL
2. Update `cxml` entry in `cert.json` with production certificate paths
3. Update Caddy IP allowlist if SAP provides new IPs
4. Update `cxml_shared_secret` with production value from SAP

## Worker Functions (`nc/worker/cxml-worker.lua`)

### `upload(sock)` — Receive cXML

Triggered by `POST /rest/nc/upload/cxml`. Reads Content-Type from `sock.header`. If `multipart/mixed`, parses MIME parts to extract XML and attachments (PDF etc.). Validates SharedSecret from XML, saves XML to `upload_file_path`, saves attachments alongside. Returns cXML acknowledgment.

### `fileDownload(param)` — Serve Files + Confirm Receipt (Server-Side)

Triggered by `POST /rest/nc/download/cxml`. Dual-action endpoint:

**Download action** (no `file_name_array`): Reads all `.xml` and `.pdf` files from `upload_file_path` and returns them inline. Files are NOT moved until confirmed.

Response:

```json
{
    "file_array": [
        {"file_name": "45C2325184-2026-04-16_18.07.16.xml", "content": "<cXML>..."},
        {"file_name": "attachment.pdf", "content": "..."}
    ],
    "file_count": 2
}
```

**Confirm action** (with `file_name_array`): Moves confirmed files from `upload_file_path` to `upload_file_path/processed/`.

Request: `{file_name_array: ["file1.xml", "file2.xml"]}`
Response: `{confirmed: 2}`

### `download()` — Download from capacic.com (Tekpal Timer)

Timer function. POSTs to `download_url`, receives all pending files, saves to `download_file_path`, then confirms receipt. Returns `{downloaded: N, confirmed: N, errors: N}`.

### `importAndSend()` — Import + Send (Tekpal Timer)

Timer function. Two phases:

**Phase A: Import** — Reads `.xml` files from `import_path`, converts cXML to JSON via `convert.xmlToJson()` using `import_file_parameter` convert definition, saves to database via `dsave.saveJson()` using `import_file_save_preference`. Moves to `processed/` if `import_move_folder` is true.

**Phase B: Send** — Reads `.xml` Order Confirmations from `export_path`, sends each via `sendCxml()` with client certificate. Moves to `processed/` if `export_move_folder` is true.

Returns `{imported: N, import_error: N, sent: N, send_error: N}`.

### `sendCxml(xmlContent)` — Send cXML to SAP

Sends a single cXML document to SAP Managed Gateway with client certificate authentication. Returns `(body, err)`.

### `sendExample()` — Push via curl (Test)

Reads all `.xml` files from `send_test_path`, sends each via `curl` to `upload_url_test`. Used for testing.

## SAP Business Network Configuration

### Document Routing

In the SAP Business Network supplier account, configure document routing under **Settings > Electronic Order Routing**:

1. For POs: Route to **Managed Gateway for Spend&Network**
2. For Order Confirmations: Route to **Managed Gateway for Spend&Network**
3. Per-buyer override: **Settings > Customer Relationships > Override routing**

### Firewall IPs

Authorize these Ariba IPs on the firewall:

- `155.56.128.0/17`
- `130.214.160.64/28`
- `130.214.160.80/29`

### SAP Business Network Resources

| Resource | Where |
|----------|-------|
| Managed Gateway certificates | Supplier SMG > Resources tab |
| Cloud Integration Gateway Portal Guide | Supplier SMG > Resources tab |
| Transaction rules | SAP BN > Settings > Customer Relationships > Customer Details |
| Document Validator Tool | Supplier SMG |
| Connectivity Tool | Supplier SMG |

### SAP Contact

- Seller Integrator: Lale Agajeva (<lale.agajeva@sap.com>)
- Buyer contacts: Fionna Langan (<fionna.langan@sandvik.com>), Vipin K S (<vipin.k_s@sandvik.com>)

## Testing

### Test File

`test/convert/cxml-worker-test`

### Test Cases

| # | Test | What It Verifies |
|---|------|-----------------|
| 1 | `cxml.require` | Module loads, exports `calls`, `sendExample`, `download`, `fileDownload`, `importAndSend`, `sendCxml` |
| 2 | `cxml.uploadEmptyBody` | Empty body returns 400 cXML response |
| 3 | `cxml.uploadNilBody` | Nil body returns 400 cXML response |
| 4 | `cxml.uploadValidXml` | Valid XML with SharedSecret saves to upload path, returns 200 |
| 5 | `cxml.sendExample` | `sendExample()` runs with `upload_url_test` (curl-based send) |
| 6 | `cxml.fileDownload` | `fileDownload(nil)` returns `{file_array, file_count}` |
| 7 | `cxml.fileDownloadConfirm` | `fileDownload({file_name_array: {}})` routes to confirm logic |
| 8 | `cxml.extractSharedSecret` | PEG extracts SharedSecret from cXML `<Header>` |
| 9 | `cxml.extractSharedSecretMissing` | Returns nil when SharedSecret not present |
| 10 | `cxml.generatePrivateKey` | `certificate.generatePrivateKey()` creates EC key via FFI |
| 11 | `cxml.generateCert` | `certificate.generateCertificate()` creates self-signed cert via FFI |
| 12 | `cxml.uploadMultipartXmlWithPdf` | Multipart/mixed with XML + PDF: XML saved cleanly, PDF saved as attachment |
| 13 | `cxml.uploadMultipartMissingBoundary` | Multipart without boundary returns 400 |
| 14 | `cxml.cleanup` | Removes test cert files from `/tmp` |

Tests `sendCxml` requires a running nc-server and is commented out. `sendExample` and `uploadValidXml` run live against configured endpoints. `fileDownload` tests server-side logic locally.

## Caddy Configuration

```caddyfile
handle /rest/nc/upload/cxml {
    @allowed remote_ip 155.56.128.0/17 130.214.160.64/28 130.214.160.80/29
    handle @allowed {
        reverse_proxy localhost:5959 {
            flush_interval -1
            fail_duration 1s
            unhealthy_request_count 1000
            stream_timeout 5s
        }
    }
    respond "Forbidden" 403
}
```

This block must appear **before** the `handle /rest/nc/*` catch-all in the Caddyfile. Caddy evaluates `handle` blocks top-down, first match wins. The IP restriction ensures only Ariba Network can reach the cXML endpoint.

Restart after changes: `brew services restart caddy`

## Files Changed for This Feature

| File | Change |
|------|--------|
| `/etc/caddy/Caddyfile` | IP-restricted handler for `/rest/nc/upload/cxml` on capacic.com |
| `nc/worker/cxml-worker.lua` | `upload` (multipart support), `fileDownload` (serve files + confirm receipt via same endpoint), `download` (Tekpal timer: bulk download from capacic.com), `importAndSend` (Tekpal timer: import XML + send confirmations), `sendCxml` (client cert), `sendExample` (curl test) |
| `nc/worker/query-worker.lua` | Removed old upload stub |
| `nc/nc-server.lua` | Registered `nc/worker/cxml-worker` plugin |
| `lib/rest.lua` | Skip `parseBody()` + raw sock passthrough for `/rest/nc/upload/cxml` |
| `lib/db/call-rest.lua` | `getUrl()` passes `certificate_path`/`private_key_path` from param to host table; `setOption()` handles direct cert params without `auth_preference` |
| `lib/xml/certificate.lua` | Exported `generatePrivateKey` and `generateCertificate` for reuse |
| `nc-preference/certificate/cert.json` | Added `cxml` entry with cert paths and subject fields |
| `form/nc/erp-sync/cxml/rec.json` | Added `cert_name`, `remote_url`, `remote_url_production`, `remote_connect_timeout`, `upload_url`, `upload_url_test`, `download_url`, `download_url_test`, `upload_file_path`, `download_file_path`, `send_test_path`, `confirm_send_delay_min` |
| `form/nc/erp-sync/cxml/task/cxml-download-task.json` | Two timer tasks: `cxml-worker.download` (every 10 min) and `cxml-worker.importAndSend` (every 5 min) |
| `database/connection/rest-cxml.json` | SAP Managed Gateway connection config with cert paths for TEST and PRODUCTION |
| `test/convert/cxml-worker-test.lua` | 16+ test cases: upload empty/nil/valid/multipart, fileDownload, confirmDownload, extractSharedSecret, generatePrivateKey, generateCert, multipart-missing-boundary, cleanup |
| `test/lib/dt-test.lua` | Added `currentFileStringMs` test |

## Go-Live Checklist

- [ ] Test environment: send/receive POs via SAP test endpoint
- [ ] Verify client certificate works with SAP test endpoint
- [ ] Test download timer: capacic.com → Tekpal file download + confirm
- [ ] Test importAndSend timer: XML import + order confirmation send
- [ ] Review Sandvik Transaction Rules in SAP Business Network
- [ ] Configure document routing in SAP BN (per-buyer override for Sandvik)
- [ ] Set `cxml_shared_secret` in `rec.json` with production value
- [ ] Switch `remote_url` to production URL in `rec.json`
- [ ] Update `cxml` entry in `cert.json` with production cert paths and subject
- [ ] End-to-end testing with real POs
- [ ] Configure email notifications in SAP BN
