Files
claudetools/.claude/standards/syncro/test-findings.md
Howard Enos 12bb9f1544 syncro: capture Phase-1 live-test findings (email-delivery trap + response shapes)
Test campaign on Howard Test sandbox (customer 36118743) surfaced traps the skill must not
re-learn:
- invoice/estimate email endpoints return 200 "Email sent" even with NO recipient
  (customer.email null) -> never report delivery from the 200; verify customer.email first.
- customer.email is UNIQUE tenant-wide; PUT /customers failure returns
  {success:false,message:[...]} not {customer:{...}} -> check .success.
- POST/PUT /contacts return a FLAT object (.id), not {contact:{...}}; contact recipient
  flags (primary/receives_invoices) are ignored via API.
- POST /contracts write succeeds but body doesn't echo the object; /contracts?customer_id
  doesn't filter server-side -> GET-verify + client-filter.

New living log .claude/standards/syncro/test-findings.md; critical items folded into
syncro.md Hard Rules + Verified Response Shapes. Correction logged to errorlog.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:46:46 -07:00

3.3 KiB

Syncro Skill — Live Test Findings (running log)

Verified behaviors/gotchas discovered while testing /syncro against the live tenant on the Howard Test sandbox customer (customer_id 36118743) + RMM-TEST-MACHINE. Newest on top. Each entry is a rule the skill must not re-learn. Critical ones are also folded into syncro.md (operating manual) and api-reference.md (breadth reference).

2026-07-10 — Phase 1 (PSA CRUD)

Emailing an invoice/estimate returns 200 "Email sent" even when it delivered to NOBODY. POST /invoices/{id}/email and POST /estimates/{id}/email take no recipient param — Syncro picks the recipient from the record server-side. If customer.email is null (and no contact is a flagged recipient), the API still returns {"message":"Email sent"} HTTP 200 but nothing is sent. NEVER report an email as delivered based on the 200 alone. Ensure the customer has a valid email (or a recipient contact) BEFORE emailing, and confirm real delivery out-of-band when it matters.

customer.email is UNIQUE across the tenant. PUT /customers/{id} with an email already used elsewhere returns {"success":false,"message":["Email has already been taken"]} and does NOT set it. For a test customer that must reach a real person whose address is already in Syncro, use plus-addressing (user+tag@domain) — unique in Syncro, still routes to the inbox IF the mail tenant has plus-addressing enabled (M365: off by default in some tenants — verify).

PUT /customers/{id} uses two different response shapes — always check for the error shape. Success → {"customer":{...}}. Validation failure → {"success":false,"message":[...]} with the field unchanged. Parsing only .customer.email hides the failure (looks like a silent no-op). Read the raw body / check .success == false on customer writes.

Contacts return a FLAT object, not wrapped. POST /contacts and PUT /contacts/{id} return {"id":N,"name":...,"email":...} directly — NOT {"contact":{...}}. Parse .id, not .contact.id. (The wrapped-parse returns null and looks like a failure when the write succeeded — a blind retry then DUPLICATES the contact. GET-verify, never retry.)

Contact recipient flags (primary, receives_invoices, receives_reports) are NOT settable via the APIPUT /contacts/{id} accepts them in the body but silently ignores them (stay null). To designate an invoice-recipient contact, set it in the Syncro GUI.

POST /contracts write succeeds but the response does not echo the created object (fields parse null). GET /contracts and match to confirm creation — never retry on the null.

GET /contracts?customer_id=N does NOT filter server-side — it returns ALL contracts. Filter client-side by .customer_id.

Reinforces the standing rule: null/blank fields in a Syncro write response are NOT proof of failure. Syncro has no idempotency, so a blind retry duplicates. Always GET the resource to confirm true state before any follow-up action.

Verified-working this phase (no gotcha, for coverage record)

POST/GET customer; POST contact; POST ticket (+ number); POST ticket comment (hidden); POST add_line_item; POST invoice (from ticket); POST estimate + line_items + PUT recalc; POST appointment; POST schedule (paused). All parse under the shapes documented in syncro.md.