ALDER WORKS CUSTOM CRM TUTORIAL FIXTURE

This is a running fictional CRM for the Quickchat AI create-enquiry, save-reference, update-timeline tutorial. Use synthetic records only. It runs with Python 3.11 or newer and has no third-party Python dependencies. It is not a production CRM or a customer-authentication implementation.

FILES TO DISTRIBUTE

crm_server.py       The actual HTTP server used for the tutorial receipts.
inspect_crm.py      Read-only inspection of its persisted SQLite records.
demo-config.json   The tested prompt, Action recipes, request shapes and test messages.
README.txt          These instructions.

Do not distribute the local .service-token, CRM state, seed scripts, simulation runner or machine-specific logs as part of the public starter kit.

START THE FIXTURE

1. In a new directory, place the four files listed above. Create your own random token:

   python -c 'from pathlib import Path; import secrets; p=Path(".crm-demo-token"); p.write_text(secrets.token_urlsafe(32)+"\n"); p.chmod(0o600)'

2. Start the server in one terminal and leave it running:

   python crm_server.py --token-file .crm-demo-token --state crm-state

3. Install cloudflared using Cloudflare's current installation instructions. In another terminal, start a temporary HTTPS tunnel to the fixture:

   cloudflared tunnel --url http://127.0.0.1:8766

   Copy the https://....trycloudflare.com URL printed by cloudflared. Keep this terminal running too. A new tunnel may have a new URL. A temporary tunnel is for this synthetic test, not for a production integration.

4. Connect your Quickchat AI account MCP to a supported AI client. Ask it to create the two inactive API Actions from demo-config.json using your HTTPS tunnel URL. For each Authorization header it must send this placeholder through save_http_request_action:

   {"name":"Authorization","value":"Bearer REPLACE_IN_DASHBOARD","is_secret":true}

   Tell the AI client not to ask for your real service token and not to test or activate the Actions yet. This initializes the encrypted, write-only header. The current manual editor preserves that setting on an existing header but does not offer a switch to initialize it.

5. In the Quickchat AI dashboard, open each created Action. In the existing Authorization row, replace [redacted] with Bearer followed by a space and the token from your local .crm-demo-token file. Save, then reopen the Action and verify the value is displayed as [redacted]. Do not delete or rename that header row. Do not paste the token into an AI client, AI Main Prompt or visitor conversation. The Content-Type header is application/json. On create_enquiry also verify Idempotency-Key contains the built-in conversation_id chip from Add AI Data. The key is supplied by Quickchat AI, not requested from the visitor or generated by the model.

6. In the Action editor, verify the named values with Add AI Data. The {{email}}, {{requirements}} and {{timeline}} values in this file describe the named chips; the editor creates their internal references. Insert {{metadata_crm_enquiry_id}} from conversation memory in the PATCH URL. Do not invent or ask the visitor to type an enquiry ID.

7. Save the AI Main Prompt, parameters, response filters, Save to memory and Run only when exactly as in demo-config.json, then activate the two Actions. Both successful responses are filtered through $.nothing_to_return, a path the fixture deliberately does not return. Save to memory still reads the full create response and stores $.id under crm_enquiry_id. Successful writes return only id and status at the API boundary; the model receives an empty object and the HTTP status. Error bodies remain visible to the model.

8. Send the same_record messages from demo-config.json in one live test conversation. Inspect the persisted CRM record after each turn:

   python inspect_crm.py --state crm-state

   The first request creates one enquiry. The second request changes only its timeline to early December. The ID, email and requirements remain the same. To focus on one actual record:

   python inspect_crm.py --state crm-state --id enq_001

The authenticated /admin endpoint is a read-only HTML view of these same records. The /state endpoint returns the same data as JSON. The /health endpoint needs no token. The server also writes requests to crm-state/http-requests.jsonl without storing the Authorization header.

API CONTRACT

POST /enquiries accepts email and requirements as required nonempty strings, plus an optional timeline string. Omitted timeline becomes an empty string. Unknown fields or invalid input return 422. A successful request returns HTTP 201 and {"id":"enq_001","status":"created"}.

POST also requires Idempotency-Key. The fixture commits its validated payload hash and enquiry reference in the same SQLite transaction, with a unique constraint on integration scope and key. An identical request with the same key returns HTTP 200 and the original ID with status already_created, without another write. A changed payload with the same key returns HTTP 409 idempotency_conflict and changes nothing. Key order in the JSON does not matter; changes to string values do. The caller must reconcile a conflict instead of claiming the changed fields were saved.

PATCH /enquiries/enq_001 accepts only a nonempty timeline string. It preserves email and requirements and returns HTTP 200 and {"id":"enq_001","status":"updated"}. An unknown reference returns 404. Missing or incorrect Bearer credentials return 401. Other routes return 404.

The service token authenticates the integration. It does not prove that a visitor owns an existing record. The fixture accepts new public enquiries and provides no read Action to the assistant. The create gate prevents this Action from creating again after its reference was captured in that conversation. The server's idempotency constraint also prevents repeated creations under the same conversation key when the first response was lost. This deliberately allows one enquiry per conversation. A new conversation has a new key and can create another enquiry for the same email. This is not visitor identity or cross-session deduplication. Add customer authentication and record authorization to a real integration as its requirements demand.

FAILURE TESTS

The ordinary server has no deliberate faults enabled. Its optional --fault-file is an author-test mechanism. That local JSON file maps synthetic email addresses to reject, missing_id, commit_then_delay or commit_then_close. Do not enable it for normal use. missing_id stores a record but omits the returned reference. commit_then_delay stores a record and delays its first response 55 seconds. Identical replays then return the existing ID immediately; changed replays return 409. These deliberately distinguish an unsuccessful response from an unsuccessful write. The tutorial evidence identifies exactly which cases used these controlled faults.

STOP

Stop cloudflared and the server with Ctrl+C. The SQLite database and request log remain under crm-state. Keep the token private. Remove the test state only when you no longer need its evidence.
