docs(session)+rules: 2026-05-27 — Quantum M365 onboarding, IX autodiscover fix, Syncro emergency/labor/attribution rules

Session logs: root (Michael #32329 hosting offer + IX simplehost.email autodiscover DNS fix + Cascades #32332 emergency correction) + Quantum client log (M365 tenant 2fd0092b onboarding, break-glass GA, CA report-only).

Syncro rule overhaul:
- Emergency billing: prepaid -> 26184 @ hours x1.5 (was 26118); non-prepaid -> 26184 with channel rate (onsite $262.50 / remote+inshop $225)
- Never make up labor items (existing product + real name; QuickBooks sync)
- Corrections preserve original tech's user_id (commission); adding notes/labor never changes ticket owner

/remediation-tool: Conditional Access may be managed programmatically (report-only first + exclude break-glass + confirm before enforce); fabb3421 deprecated for customer tenants; Quantum tenant onboarded (gotchas table).

Memory: 4 new (no-madeup-labor, corrections-preserve-tech, ca-programmatic, quantum-godaddy-tenant) + updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 14:57:53 -07:00
parent ed7715424d
commit a42d657c55
12 changed files with 249 additions and 54 deletions

View File

@@ -43,7 +43,7 @@ Create, update, close, comment on, and bill tickets in Syncro PSA.
**Billing:** Always ask for minutes and labor type before adding any line item. Never assume a default.
**Emergency/after-hours billing — check prepaid first:** Before adding a `26184` (Emergency) line item, `GET /customers/<id>` and read `prepay_hours`. If `prepay_hours > 0`, the customer has a prepaid block — bill `26118` (Onsite) at `quantity × 1.5` instead (prepaid debits by quantity, not by dollars). Never stack `26118` + `26184` for the same hours — the Emergency product rate already has the 1.5× multiplier baked in. Verified 2026-04-23 on ticket #32203 (Desert Auto Tech) after Winter caught the bug.
**Emergency/after-hours billing — check prepaid first:** Before adding a `26184` (Emergency) line item, `GET /customers/<id>` and read `prepay_hours`. Emergency = time-and-a-half (×1.5), applied ONCE — never bill a separate regular + emergency line for the same hours. **No prepaid (`prepay_hours == 0`):** `26184` at qty = actual hours; set `price_retail` by delivery channel — **Onsite $262.50** (175×1.5, 26184's default), **Remote / In-Shop $225** (150×1.5, override price_retail). The rate carries the 1.5×; do NOT also ×1.5 the qty. **Prepaid (`prepay_hours > 0`):** still use `26184`, at qty = actual hours **× 1.5** (premium goes in the quantity since prepaid debits by quantity; invoice nets $0, block debits hours×1.5). e.g. 1.5 emergency hrs prepaid → `26184` @ 2.25. (Rule updated 2026-05-27 by Mike: prepaid emergency uses `26184`, NOT the old `26118`×1.5 — keeps the line labeled emergency + mapping right in QuickBooks. Original ×1.5-not-additive lesson: #32203 Desert Auto Tech 2026-04-23, Winter.)
**Prepaid customers — ALL billing (not just emergency):** `GET /customers/<id>``prepay_hours` before creating ANY invoice for a prepaid customer. When you bill a prepaid customer using a billable labor product (remote / onsite / in-shop / web), Syncro automatically deducts from their prepay block and the invoice total shows $0.00. The line item name is annotated "- Applied X Prepay Hours". This is correct behavior — do NOT treat a $0.00 invoice as an error. Verify the deduction by re-fetching `customer.prepay_hours` after invoicing and confirming it dropped by `quantity`.
@@ -84,6 +84,10 @@ When invoked, use the Syncro REST API via `curl`. All requests include `?api_key
Every Syncro API call is attributed to the **owner of the API key**. Comments, line items, timer entries, and invoices created by the API are logged as the API user — regardless of who is running the command. So the skill MUST use a per-user API key that matches the actual tech running it, or comments will be misattributed.
**Preserve attribution on edits — do NOT reassign (Mike 2026-05-27):**
- **Correcting mis-billed labor** (a debug action) must keep the ORIGINAL tech's `user_id` (their commission). `update_line_item` preserves the existing `user_id`; a remove+add defaults the new line to the API-key owner — so set `user_id` to the original tech on `add_line_item`, or PUT it afterward. Determine the original tech from `.ticket.user_id` and the line's `.user_id`. Don't take a tech's commission just because the math was fixed by someone else.
- **Ticket ownership:** adding notes/labor or changing status does NOT change the ticket owner. Multiple techs routinely work one ticket. Only PUT a ticket's `user_id` (reassign owner) when explicitly asked; status PUTs send only `status`.
| identity.json user | Syncro user | user_id |
|---|---|---|
| `mike` | Michael Swanson | 1735 |
@@ -270,7 +274,7 @@ Whether the draft came from Ollama or Claude wrote it directly:
1. `price_retail` was fetched live from `GET /products/<product_id>``.product.price_retail` and matches what will be shown to the user
2. `quantity` = minutes ÷ 60 — verify the arithmetic (e.g. 45 min = 0.75, not 0.77)
3. Computed total = `price_retail × quantity` — matches what was communicated to user
4. If labor_type is `emergency` and `prepay_hours > 0`: product must be `26118`, qty must be actual_hours × 1.5
4. If labor_type is `emergency` and `prepay_hours > 0`: product must be `26184` (emergency item), qty must be actual_hours × 1.5 (premium in the quantity)
5. `comment_body` uses `<br>`, not `<ul>/<li>`
6. No internal notes or credential data in a customer-visible comment body
@@ -659,7 +663,7 @@ RATE=$(curl -s "${BASE}/products/${PRODUCT_ID}?api_key=${API_KEY}" | jq -r '.pro
| `1190473` | Labor - Remote Business | Remote work |
| `26118` | Labor - Onsite Business | Onsite work |
| `573881` | Labor - In Shop Business | Device brought to ACG shop |
| `26184` | Labor - Emergency or After Hours | Non-prepaid emergency only; 1.5× rate baked in |
| `26184` | Labor - Emergency or After Hours | ALL emergency. Non-prepaid: qty=actual hrs, set price_retail by channel (Onsite $262.50, Remote/In-Shop $225). Prepaid: qty=actual hrs×1.5 (rate moot) |
| `1049360` | Labor - Warranty work | Any warranty / no-charge work |
| `9269124` | Labor - Internal Labor | Internal ACG time, not customer-facing |
| `26117` | Fee - Travel Time | Per travel event |
@@ -671,9 +675,9 @@ RATE=$(curl -s "${BASE}/products/${PRODUCT_ID}?api_key=${API_KEY}" | jq -r '.pro
| prepay_hours | Regular | Emergency |
|---|---|---|
| `0` / null | delivery-channel product, qty = actual_hours | `26184`, qty = actual_hours |
| `> 0` | delivery-channel product, qty = actual_hours | delivery-channel product, qty = actual_hours × **1.5** |
| `> 0` | delivery-channel product, qty = actual_hours | `26184`, qty = actual_hours × **1.5** |
Prepaid blocks debit by quantity not dollars — for emergency prepaid, use the normal delivery-channel product at 1.5× qty, not `26184` (which has 1.5× already in the dollar rate).
Emergency = time-and-a-half, applied once. Non-prepaid: the `26184` rate ($262.50) carries the 1.5× in dollars, so qty = actual hours. Prepaid: invoice is $0 (debits by quantity), so the 1.5× goes in the **quantity** — bill `26184` at actual hours × 1.5 (e.g. 1.5 hrs → 2.25). (Updated 2026-05-27, Mike — previously "delivery-channel product ×1.5"; now `26184` so the line stays labeled emergency.)
#### Invoices

View File

@@ -28,10 +28,11 @@
- [D2TESTNAS SSH Access](feedback_d2testnas_ssh.md) - Use root@192.168.0.9 with Paper123!@#, not sysadmin
- [Bypass Permissions Setting](feedback_bypass_permissions_setting.md) - Set permissions.defaultMode to bypassPermissions in settings.json on all machines
- [No indented code blocks](feedback_no_indented_code_blocks.md) — Never indent code inside fences; Howard copy-pastes directly and leading spaces break PowerShell
- [365 Remediation Tool](feedback_365_remediation_tool.md) - Always means Graph API app fabb3421, not CIPP
- [365 Remediation Tool](feedback_365_remediation_tool.md) — "remediation tool" = tiered ComputerGuru app suite via /remediation-tool; NOT CIPP, NOT the deprecated fabb3421
- [CA managed programmatically (with discipline)](feedback_ca_programmatic_management.md) — Conditional Access CAN be written via Tenant Admin app; ALWAYS report-only first + exclude break-glass + confirm before enforcing. Overrides old "CA manual" rule.
- [Ollama Tier-0 Routing](feedback_ollama_tier0_routing.md) - Route drafts/summaries/classifications through Ollama (qwen3:14b). Mike designed ClaudeTools this way — not optional.
- [/save writes narrative directly](feedback_save_no_ollama.md) — No Ollama for /save; write all sections inline — too slow
- [Syncro Emergency Billing](feedback_syncro_emergency_billing.md) — Emergency = 1.5× multiplier, not additive. Branch by `customer.prepay_hours`: no-prepaid → `26184` at actual hrs; prepaid → `26118` at hrs×1.5. Never stack. Always set `price_retail`.
- [Syncro Emergency Billing](feedback_syncro_emergency_billing.md) — Emergency = time-and-a-half (×1.5), applied once, never additive. Branch by `customer.prepay_hours`: no-prepaid → `26184` at actual hrs; prepaid → `26184` at hrs×1.5 (premium in the QUANTITY). One line. Always set `price_retail`. (Updated 2026-05-27: prepaid now uses 26184, not 26118.)
- [Identity precedence](feedback_identity_precedence.md) — Trust `.claude/identity.json` over the system-reminder `userEmail` hint when they disagree (shared-login machines).
- [1Password — always use service token](feedback_1password_service_token.md) — Source OP_SERVICE_ACCOUNT_TOKEN from SOPS for every `op` call. Desktop-app integration prompts are unacceptable in agent flows.
- [Point vault-access teammates at SOPS path](feedback_vault_pointer_for_teammates.md) — When relaying infra/credential info to Howard or other vault-access teammates, hand over the SOPS path + key anchors; don't transcribe the entry's fields into the message.
@@ -42,6 +43,8 @@
- [Syncro — bill with add_line_item, not timers](feedback_syncro_timer_first.md) — Bill tickets with `POST /tickets/{id}/add_line_item` directly; the timer workflow (`timer_entry → charge_timer_entry`) is NOT used. Set product_id, quantity (decimal hours), price_retail, name, description, taxable:false. Supersedes the old "timers required" rule (Mike confirmed 2026-05-21).
- [Syncro — timer_entry response is FLAT (HISTORICAL)](feedback_syncro_timer_response_shape.md) — Reference only: timers are NO LONGER part of the billing workflow (superseded by add_line_item — see feedback_syncro_timer_first.md). Retained for the rare manual-timer case: response is flat (`{"id": N, ...}`), parse `.id` not `.timer.id`. Originally hit on #32253 2026-05-05.
- [Syncro — warranty has its own product, never patch dollar amounts](feedback_syncro_warranty_product.md) — Warranty/no-charge work uses product `1049360` (Labor- Warranty work, $0). Don't fake a free line by patching `price_retail` or neutralizing a regular product — pick the correct product and re-run. Hit on #32225 2026-05-06.
- [Syncro — never make up labor items](feedback_syncro_no_madeup_labor_items.md) — Labor lines MUST be an existing Syncro product used with its REAL name; never invent/rename a line. Description field is free text. Made-up items break the QuickBooks sync. Incident #32332.
- [Syncro — preserve attribution (labor + ticket owner)](feedback_syncro_corrections_preserve_tech.md) — Corrections keep the original tech's labor user_id (commission); update_line_item preserves it, remove+add defaults to the API-key owner. Adding notes/labor never changes the ticket owner. Only reassign labor or ticket ownership when explicitly asked. (#32332)
- [SQL instance role — verify by connections, not name](feedback_sql_instance_role_by_connection.md) — Standard installed under default `SQLEXPRESS` instance name is real. Prove role with `sys.dm_exec_sessions` + `Get-NetTCPConnection -OwningProcess` before recommending stop/uninstall. IMC1 2026-05-05/06 near-miss.
- [Syncro — confirm appointment owner explicitly](feedback_syncro_appointment_owner.md) — When creating tickets with appointments, always ask "who is the appointment owner?" in the preview. Don't auto-default to ticket's assigned tech. Don't add additional attendees without explicit confirmation. Howard caught on Kittle ticket #32263 2026-05-08.
- [Syncro — verify appointment date day-of-week](feedback_syncro_appointment_date_check.md) — Always compute and display the day name (e.g. "Saturday 2026-05-23") in the ticket preview — never just the numeric date. Verify with `py -c "import datetime; ..."` before posting. Wrong-day incident on #32312 2026-05-21 (Sunday booked instead of Saturday). Reported by Winter.
@@ -65,6 +68,7 @@
- [Mac gururmm setup pending](project_mac_gururmm_setup_pending.md) — ACTION REQUIRED: run `bash scripts/install-hooks.sh` in gururmm repo on Mikes-MacBook-Air before any RMM work
## Project
- [Quantum GoDaddy M365 tenant](project_quantum_godaddy_m365_tenant.md) — quantumwms.com parked in a GoDaddy-provisioned M365 tenant (id ddf3d2c9-b76c-40d9-a216-9f11a1a26f97, netorg18235235.onmicrosoft.com); blocks Pax8 migration until GoDaddy removed. Managed = no DNS takeover; need GoDaddy/GA access.
- [Cascades Migration Plan](project-cascades-migration-plan.md) — Active multi-day migration. Plan file: `C:\Users\Howard\.claude\plans\wise-discovering-panda.md`. Syncro ticket: #110680053. Resume: "resume the Cascades migration plan".
- [GuruRMM Development Principles](gururmm-development-principles.md) - MANDATORY: every feature needs full stack (backend, API, UI, docs, scalability). Product must work without AI agents (AI features are enhancements). Documented in guru-rmm/docs/DESIGN.md.
- [Sync script bug — untracked files (RESOLVED)](project_sync_script_bug.md) — FIXED 2026-05-21: sync.sh now uses `git status --porcelain` for change detection (repo + vault), so untracked-only changes are caught. Added .gitignore for the datto BSOD dumps so the fix doesn't sweep 54MB of binaries.

View File

@@ -1,32 +1,32 @@
---
name: 365 Remediation Tool Reference
description: "365 remediation tool" always means the Claude-MSP-Access Graph API app (fabb3421-8b34-484b-bc17-e46de9703418), not CIPP
type: feedback
---
When user says "365 remediation tool" or "remediation tool", they ALWAYS mean the Claude-MSP-Access Graph API application (App ID: fabb3421-8b34-484b-bc17-e46de9703418). This is NOT CIPP.
**Why:** User explicitly clarified this after I incorrectly navigated to CIPP. The remediation tool is direct Graph API access using client credentials flow against customer tenants.
**How to apply:** Authenticate directly via Graph API using the app's client secret from SOPS vault (`msp-tools/claude-msp-access-graph-api.sops.yaml`), get tenant ID from OpenID discovery for the target domain, and query Graph API endpoints directly. No browser/UI needed.
**Preferred invocation: use the `/remediation-tool` skill** (`.claude/skills/remediation-tool/`, also surfaces as a `/remediation-tool` command). It wraps tenant resolution, token caching, the 10-point user breach check, and tenant-wide sweep. Remediation actions are gated behind explicit `YES` confirmation. Reference docs at `references/gotchas.md`, `references/graph-endpoints.md`, `references/checklist.md`.
### Directory Role Requirements (discovered 2026-04-01)
Graph API permissions alone are NOT sufficient for privileged operations. The service principal also needs Entra directory roles assigned per-tenant:
| Operation | Required Directory Role |
|-----------|----------------------|
| Password reset | User Administrator |
| Exchange transport rules, mailbox permissions | Exchange Administrator |
**Roles assigned so far:**
- Valleywide Plastering (5c53ae9f...): User Administrator
- Dataforth (7dfa3ce8...): User Administrator, Exchange Administrator
**For new tenants:** After admin consent, manually assign roles via Entra portal > Roles and administrators. The app cannot self-assign directory roles.
### Exchange Online REST API
For Exchange cmdlets (Get-TransportRule, Add-MailboxPermission, etc.), use scope `https://outlook.office365.com/.default` and POST to `https://outlook.office365.com/adminapi/beta/$TENANT_ID/InvokeCommand` with `{"CmdletInput":{"CmdletName":"...", "Parameters":{...}}}`.
---
name: 365 Remediation Tool Reference
description: "365 remediation tool" = ACG's tiered ComputerGuru Graph/EXO app suite via the /remediation-tool skill; NOT CIPP, and NOT the deprecated fabb3421 single-app
type: feedback
---
When the user says "365 remediation tool" or "remediation tool", they mean ACG's direct Graph/Exchange tooling against customer tenants via the **`/remediation-tool` skill** (`.claude/skills/remediation-tool/`). This is NOT CIPP.
**App suite (current — tiered):** Security Investigator `bfbc12a4` (Graph read + EXO read), Exchange Operator `b43e7342` (EXO write), User Manager `64fac46b` (user/license/MFA/pw write), Tenant Admin `709e6eed` (high-priv directory), Defender Add-on `dbf8ad1a` (MDE-licensed tenants ONLY). Secrets in `msp-tools/computerguru-*.sops.yaml`. Client-credentials auth; tenant ID via OpenID discovery (or the `*.onmicrosoft.com` domain when the primary domain isn't verified). Use the lowest tier needed. Each app is consented per-tenant (URLs in `references/gotchas.md`); privileged ops also need directory roles assigned to the SP in that tenant (`onboard-tenant.sh`).
**DEPRECATED — do NOT consent to customer tenants:** `fabb3421` ("AI Remediation" / "Claude-MSP-Access", secret `msp-tools/claude-msp-access-graph-api.sops.yaml`). ~159 perms incl. Defender ATP, so admin consent **breaks with AADSTS650052 on any tenant lacking an MDE license**. It still works where already consented (e.g. ACG's own tenant — the `/mailbox` skill reads our own mailboxes with it), but new onboarding MUST use the tiered suite. (Corrected 2026-05-27 during Quantum onboarding — nearly consented the deprecated app to a no-MDE tenant.)
**Why (original):** user clarified "remediation tool" != CIPP after a wrong CIPP navigation. **How to apply:** prefer the `/remediation-tool` skill — it wraps tenant resolution, token caching, breach check, sweep, gated remediation, and consent/onboarding URLs (`references/gotchas.md`, `graph-endpoints.md`, `checklist.md`).
### Directory Role Requirements (discovered 2026-04-01)
Graph API permissions alone are NOT sufficient for privileged operations. The service principal also needs Entra directory roles assigned per-tenant:
| Operation | Required Directory Role |
|-----------|----------------------|
| Password reset | User Administrator |
| Exchange transport rules, mailbox permissions | Exchange Administrator |
**Roles assigned so far:**
- Valleywide Plastering (5c53ae9f...): User Administrator
- Dataforth (7dfa3ce8...): User Administrator, Exchange Administrator
**For new tenants:** After admin consent, manually assign roles via Entra portal > Roles and administrators. The app cannot self-assign directory roles.
### Exchange Online REST API
For Exchange cmdlets (Get-TransportRule, Add-MailboxPermission, etc.), use scope `https://outlook.office365.com/.default` and POST to `https://outlook.office365.com/adminapi/beta/$TENANT_ID/InvokeCommand` with `{"CmdletInput":{"CmdletName":"...", "Parameters":{...}}}`.

View File

@@ -0,0 +1,19 @@
---
name: feedback-ca-programmatic-management
description: Conditional Access MAY be managed programmatically via the remediation-tool Tenant Admin app (overrides the old "CA stays manual in portal" boundary); strict report-only-first + break-glass-exclude discipline required
metadata:
type: feedback
---
Conditional Access policies **may be created/modified programmatically** via the `/remediation-tool` **Tenant Admin tier** (`709e6eed` — carries `Policy.ReadWrite.ConditionalAccess` + the Conditional Access Administrator directory role). This **overrides** the prior scope boundary ("CA stays manual in the portal").
**Why:** Mike explicitly directed it 2026-05-27 (Quantum onboarding). His rationale: with a **break-glass account excluded** and policies in **report-only**, the blast radius is near zero, and he wants the capability for scale (templated CA baselines across tenants).
**How to apply — mandatory discipline every time:**
1. Create/modify in **report-only first**`state: "enabledForReportingButNotEnforced"`. Never create a policy directly `enabled`.
2. Always **exclude the tenant's break-glass account** in `conditions.users.excludeUsers` (create the break-glass GA first if none exists).
3. **Verify impact** in Entra sign-in logs (report-only logs what *would* happen) before enforcing.
4. Get **explicit user confirmation before flipping any policy to `enabled`** on a tenant with real users.
5. Entra **app registrations** still stay manual — only CA is in scope for programmatic management.
Endpoint: `POST/PATCH https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies` with the tenant-admin token. Verified working on Quantum tenant 2fd0092b (CA001 MFA-all + CA002 block-legacy, report-only). See [[365-remediation-tool-reference]].

View File

@@ -0,0 +1,18 @@
---
name: feedback-syncro-corrections-preserve-tech
description: Preserve Syncro attribution — corrections keep the original tech's labor user_id (commission); and adding notes/labor never changes the ticket owner. Only reassign labor or ticket ownership when explicitly asked.
metadata:
type: feedback
---
When fixing labor line items that were billed incorrectly (wrong product, quantity, name, or bad math — a **debug/correction action**), do NOT let the labor get reassigned to the correcting tech. **Preserve the ORIGINAL tech's attribution (`user_id`)** on each line so their commission isn't lost.
- **Prefer `update_line_item` in place** — it preserves the line's existing `user_id`. (Verified on #32332: updating Howard's line kept `user_id=1750`; the dollar/product changed but the commission stayed with Howard.)
- **If you must REMOVE + re-ADD a line**, the new line defaults to the **API-key owner's** `user_id` (e.g. Mike `1735`) — so explicitly set `user_id` to the original tech on `add_line_item`, or PUT `update_line_item` to fix the new line's `user_id` afterward.
- Determine the original tech from the **ticket's `.ticket.user_id`** and the line's `.user_id` before correcting; verify it still matches after.
**Tech user_ids:** Mike `1735`, Howard `1750`, Winter `1737`, Rob `1760`.
**Ticket ownership (related rule, Mike 2026-05-27):** Simply adding notes or labor to a ticket does **NOT** change the ticket owner (`.ticket.user_id` / assigned tech). Multiple techs routinely work the same ticket. **Only change ticket ownership when explicitly asked** — never PUT a ticket's `user_id` as a side effect of commenting, billing, or status changes. (Status PUTs should send only `status`; line edits use `update_line_item`; neither should touch `user_id`.)
**Why:** Mike — a billing correction is a debug action (e.g. Claude or someone billed it wrong); the **original tech still did the work and keeps the commission**. Don't take Howard's commission just because the math was fixed by Mike/Winter. Hit on #32332 (Cascades) 2026-05-27 — Howard's mis-billed labor was corrected via Mike's API key; update-in-place preserved `user_id=1750`, but a remove+add would have stolen the commission. Related: per-user-key attribution in [[365-remediation-tool-reference]] / `/syncro` Attribution rule.

View File

@@ -1,23 +1,22 @@
---
name: Syncro emergency/after-hours billing — check prepay_hours first
description: Emergency labor is 1.5× multiplier, not additive. Branch by customer.prepay_hours — wrong branch doubles or undercharges. Applies to every /syncro bill for emergency work.
type: feedback
description: Emergency labor is time-and-a-half (×1.5), applied once, never additive. Branch by customer.prepay_hours. Prepaid → emergency item 26184 at hours×1.5 (premium in quantity); non-prepaid → 26184 at actual hours (rate has 1.5×).
metadata:
type: feedback
---
**Rule:** Before adding any Emergency/after-hours labor line item on a Syncro ticket, `GET /customers/<id>` and read `prepay_hours`.
**Rule:** Before adding any Emergency/after-hours labor line on a Syncro ticket, `GET /customers/<id>` and read `prepay_hours`. Emergency = **time-and-a-half (×1.5), applied ONCE** — never bill a separate regular line + emergency line for the same hours.
- If `prepay_hours == 0` (no prepaid block): use product `26184` (Labor - Emergency/After Hours) at quantity = actual hours. The $262.50/hr rate already has the 1.5× multiplier baked in.
- If `prepay_hours > 0` (customer has a prepaid block): use product `26118` (Labor - Onsite) at quantity = actual hours × 1.5. Prepaid blocks debit by QUANTITY, not dollars, so we bump qty instead of swapping to the Emergency product.
- **No prepaid block (`prepay_hours == 0`):** product `26184` (Labor - Emergency or After Hours) at quantity = **actual hours**, and set `price_retail` by the work's **delivery channel** (the 1.5× lives in the dollars — do NOT also ×1.5 the quantity): **Onsite emergency = $262.50** (175 × 1.5; this is 26184's default rate); **Remote / In-Shop emergency = $225** (150 × 1.5) → override `price_retail` to `225`. Fetch the base rate live and ×1.5 if unsure.
- **Prepaid block (`prepay_hours > 0`):** product `26184` at quantity = **actual hours × 1.5** (hours + 50%). Prepaid blocks debit by QUANTITY not dollars, so the 1.5× premium goes in the **quantity**; the invoice nets to $0 and the block debits hours×1.5. e.g. 1.5 emergency hrs → `26184` @ **2.25**. (Delivery channel / dollar rate is **irrelevant** for prepaid — only the quantity hrs×1.5 matters.)
Never stack `26118` + `26184` for the same hour of work. Pick one path based on the prepaid state.
**(Updated 2026-05-27 — Mike):** prepaid emergency now uses the **emergency item `26184`** at ×1.5 quantity — this REPLACES the old "prepaid → onsite `26118` at ×1.5." Using 26184 labels the line correctly as emergency and maps right in QuickBooks; the dollar double-1.5 worry doesn't apply to prepaid since the invoice is $0. Reaffirmed on #32332 (Cascades, prepaid 27h): total 1.5 emergency hrs → `26184` @ 2.25 (Howard had split it into made-up onsite/emergency lines).
**Why:** Learned on ticket #32203 (Desert Auto Tech) 2026-04-23. Howard asked to bill "1 hour onsite + 1 hour emergency onsite." I posted both as separate additive line items and the invoice came out at $437.50 when the correct bill for 1 actual hour of emergency work was $262.50. Winter caught it and explained the rule: "the goal is to have it bill at time and a half." The Emergency product = time-and-a-half by rate; prepaid accounts = time-and-a-half by quantity. Swapping products AND multiplying quantity double-counts.
**Why ×1.5-not-additive:** Learned on #32203 (Desert Auto Tech) 2026-04-23 — billing "1h onsite + 1h emergency" as two additive lines came out $437.50 when 1 actual hour of emergency should bill at time-and-a-half. Emergency IS time-and-a-half; one line.
**How to apply:**
- Every `/syncro bill` for emergency/after-hours work: check `prepay_hours` BEFORE choosing the product. Do not shortcut this.
- For a 2-hour emergency job:
- Non-prepaid customer → one line, 2.0 hrs × `26184` → $525.00
- Prepaid customer → one line, 3.0 hrs × `26118` → 3 hours debit from block
- Always set `price_retail` explicitly on `add_line_item`. The old "omit and let Syncro auto-calc" guidance was wrong — the rate does not populate from the product config, and the invoice will post at $0 if `price_retail` is missing. Fetch the current rate with `GET /products/<id>`.
- Never let a customer-facing invoice post without verifying `.invoice.total` matches the expected `qty × price_retail`.
- Full rules and examples live in `.claude/commands/syncro.md` under the "Labor product IDs" section.
- Every emergency/after-hours bill: check `prepay_hours` BEFORE choosing the quantity. One emergency line on `26184`.
- Always set `price_retail` explicitly (fetch live via `GET /products/26184`); the rate doesn't auto-populate and the line posts $0 if omitted.
- Use the product's REAL name on the line (work detail goes in the description) — see [[feedback-syncro-no-madeup-labor-items]].
- Verify after invoicing: `.invoice.total` (non-prepaid) or the prepay-block decrement (prepaid).
- Full rules: `.claude/commands/syncro.md`.

View File

@@ -0,0 +1,12 @@
---
name: feedback-syncro-no-madeup-labor-items
description: NEVER invent or rename Syncro labor line items — every labor line must use an existing product with its REAL name (from GET /products/<id>); work detail goes in the description field, not the name
metadata:
type: feedback
---
Every labor line item on a Syncro ticket/invoice MUST be an **existing Syncro product, billed under its REAL name** (fetched from `GET /products/<id>``.product.name`) with the live `price_retail`. **NEVER make up a custom line-item name** — even when the `product_id` is a real product. The line's `name` field = the product's actual name, verbatim. Put any work-specific narrative in the `description` field, never by renaming the line.
**Why:** Mike flagged ticket #32332 (Cascades — Chris Knight new-user setup), where product `26118` (real name **"Labor - Onsite Business"**) was billed on two lines as **"Emergency Call Setup"** and **"Onsite Computer Setup"** — fabricated names. Invented/renamed labor items break the **Syncro -> QuickBooks sync** — QB maps each labor line to an existing item, so a fabricated name has no QB match and messes up the accounting (Mike's stated reason). The **`description` field is free text and can be whatever the work needs** — only the `name`/product must be an existing Syncro item. Mike: "You CANNOT make up labor items. You MUST use existing items only for all labor items... the labor item must use the ones that already exist in syncro (otherwise it messes things up in Quickbooks)."
**How to apply:** When adding ANY labor line — `GET /products/<id>`, copy `.product.name` verbatim into `name`, use `.product.price_retail` for `price_retail`, `taxable:false` for labor. Pick the correct EXISTING labor product (remote `1190473` "Labor - Remote Business" $150, onsite `26118` "Labor - Onsite Business" $175, emergency/after-hours `26184` "Labor - Emergency or After Hours Business" $262.50, in-shop `573881`, warranty `1049360`, etc. — full table in `/syncro`). Differentiate the work in `description`, not `name`. If no existing product fits the need, STOP and ask Mike — do not invent one. Related: [[feedback-syncro-live-rates]], [[feedback-syncro-warranty-product]].

View File

@@ -0,0 +1,18 @@
---
name: quantum-godaddy-m365-tenant
description: Quantum Wealth Management M365 migration uses a NEW fully-onboarded tenant 2fd0092b (quantumwms.com verified+primary); the old GoDaddy tenant ddf3d2c9 was bypassed, not taken over
metadata:
type: project
---
During the Quantum Wealth Management email migration (Intermedia HEX -> Microsoft 365, Syncro #32323), Pax8 reported `quantumwms.com` was "already attached to a tenant." Unauthenticated discovery (2026-05-27) confirmed:
- **Existing tenant:** `ddf3d2c9-b76c-40d9-a216-9f11a1a26f97`, default domain `netorg18235235.onmicrosoft.com`, brand name "quantumwms.com", **Managed**, NA region. The `NETORG#######.onmicrosoft.com` pattern = **GoDaddy-provisioned M365 tenant**.
- `quantumwms.com` itself returns realm **Unknown** (added but not verified-as-primary in that tenant) — this pending/parked claim is what blocks Pax8.
- Mail is on **Intermedia** (`*.exch090.serverdata.net`; SPF `spf.intermedia.net`), with GoDaddy (`secureserver.net`) + Proofpoint Essentials (`ppe-hosted.com`) still in SPF. So the GoDaddy M365 tenant is almost certainly **dormant** (no mailboxes there).
**Why this matters:** It's a *Managed* tenant, so there is **NO DNS-based admin takeover** (that only works on unmanaged/viral tenants). Access requires the customer's **GoDaddy account login / the tenant Global Admin**, or GoDaddy releasing it.
**RESOLUTION (2026-05-27):** Mike chose to **spin up a fresh tenant** rather than take over GoDaddy's (only 2 users — cleaner). The operative tenant is now **`2fd0092b-e9b7-474c-ad73-301f34dd6b64`** ("Quantum Wealth Management", `quantumwms.onmicrosoft.com`): `quantumwms.com` is **verified + primary** there, `john@`/`sheila@` are licensed (Pax8 M365), `sysadmin@quantumwms.com` is the ACG admin. Pax8 GDAP approved + the **full ComputerGuru app suite consented & directory-roles assigned via `onboard-tenant.sh`** (Tenant Admin, Security Investigator, Exchange Operator, User Manager, Defender; Exch/User/Auth/CA roles). The GoDaddy tenant `ddf3d2c9` (netorg18235235.onmicrosoft.com) was **bypassed** — it never verified the domain, so the new tenant claimed it; no GoDaddy takeover/defederate needed. Remaining migration steps: PST backups of both mailboxes (Intermedia has no server-side export), DNS cutover (MX/autodiscover/archival+sent-mail-encryption — Jen Curry/IFG meeting Thu 2026-05-28 1PM), then move mail Intermedia -> M365. Tracked on Syncro #32323; tenant row added to remediation-tool gotchas.md table.
**(Historical) original blocker:** Pax8 reported the domain "attached to a tenant" = the GoDaddy-provisioned `ddf3d2c9` (NETORG onmicrosoft). Managed tenant, so no DNS takeover; would have needed GoDaddy GA access. Superseded by the fresh-tenant decision above.

View File

@@ -59,6 +59,7 @@ When triggered automatically (vs. via `/remediation-tool`), follow the same work
## Scope boundaries
- **Not a replacement for CIPP.** Use CIPP for bulk baseline configuration, templates, standards alerting. Use this tool for focused investigation and point-in-time remediation.
- **Not for creating/modifying Entra apps or Conditional Access policies.** Those are sensitive enough to stay manual in the portal.
- **Entra app registrations stay manual in the portal** — don't create/modify the multi-tenant apps themselves via the tool.
- **Conditional Access policies CAN be managed programmatically** (Tenant Admin tier holds `Policy.ReadWrite.ConditionalAccess` + the Conditional Access Administrator role). MANDATORY discipline: (1) always create/modify in **report-only** (`state: enabledForReportingButNotEnforced`) first; (2) always **exclude the tenant's break-glass account** (`conditions.users.excludeUsers`); (3) verify impact in Entra sign-in logs before enforcing; (4) get explicit user confirmation before flipping any policy to `enabled` on a tenant with real users. (CA-manual boundary relaxed 2026-05-27 at Mike's direction — break-glass + report-only keep blast radius near zero.)
- **Not for Graph permissions the apps don't have.** If a call 403s and the scope isn't in the relevant app's manifest, stop and tell the user — don't try to work around it.
- **Defender tier requires MDE license.** If the tenant doesn't have MDE, the token request succeeds but API calls return AADSTS650052. Check before using.

View File

@@ -121,6 +121,7 @@ If token request or API call returns AADSTS650052 referencing `WindowsDefenderAT
| Grabblaw | 032b383e-96e4-491b-880d-3fd3295672c3 | YES (2026-04-20) | — | YES (2026-04-20) | YES (2026-04-20) | — | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | Fully onboarded |
| martylryan.com | (resolve via script) | YES (2026-04-20) | — | YES (old app) | YES (2026-04-20) | — | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | ASSIGNED (2026-04-20) | Fully onboarded |
| mvaninc.com | 5affaf1e-de89-416b-a655-1b2cf615d5b1 | YES (2026-04-21) | — | YES (2026-04-21) | YES (2026-04-21) | — | — | — | — | Fully onboarded. Incident 2026-04-21: sysadmin GA account unauthorized sign-in from OKC via device PRT (MITCH-LAPTOP/JUNE). Remediated: pw reset, sessions revoked. CA policy (MFA all users) still pending — Mike to create. |
| Quantum Wealth Management | 2fd0092b-e9b7-474c-ad73-301f34dd6b64 | YES (2026-05-27) | YES (2026-05-27) | YES (2026-05-27) | YES (2026-05-27) | YES (2026-05-27) | ASSIGNED (2026-05-27) | ASSIGNED (2026-05-27) | ASSIGNED (2026-05-27) | Fully onboarded via onboard-tenant.sh. NEW tenant (not the dormant GoDaddy one ddf3d2c9); quantumwms.com verified+primary; john@/sheila@ licensed. Intermedia->M365 migration in progress (Syncro #32323). |
**Migration note:** Valleywide, Dataforth, and Cascades still use the old deprecated app. Next visit: consent Security Investigator + assign Exchange Administrator role to new SP, then retire old app consent.

View File

@@ -48,3 +48,60 @@ Posted a **customer-visible, emailed** update to #32323 acknowledging the forwar
- Ticket: #32323 (id 111056440), customer_id 7088747 — https://computerguru.syncromsp.com/tickets/111056440 — comment id 413437310.
- Source email: Sheila Peress (sheila@quantumwms.com), "FW: Intermedia Concern [#SR-150626]", 2026-05-27 13:55, forwarding IFG Software Support (softwaresupport@ifgsd.com).
- Wiki: wiki/clients/quantumwms.md.
---
## Update: 14:49 PT — M365 migration: tenant onboarded, security baseline started
### Session Summary
Major progress on the Intermedia -> M365 migration (#32323). Jen Curry (IFG) called back and **approved + strongly encouraged** the move; emailed Sheila the update, set up appointments (Wed 5/27 2:00 PM with Sheila for licensing + PST backup kickoff; Thu 5/28 1:00 PM with Jen to finalize DNS for archival + sent-mail encryption), created a PST-backup TODO, and created an empty **"365 Services" recurring invoice template** (schedule 509862, Monthly, next run 2026-06-01) for Pax8 to populate.
Resolved the tenant question. Pax8 reported `quantumwms.com` "attached to a tenant" — discovery found a dormant **GoDaddy-provisioned tenant** (`ddf3d2c9...`, `netorg18235235.onmicrosoft.com`, brand "quantumwms.com") that had the domain parked but unverified. Mike chose to **spin up a fresh tenant** (only 2 users; cleaner than a GoDaddy takeover). Pax8 provisioned **new tenant `2fd0092b-e9b7-474c-ad73-301f34dd6b64`** ("Quantum Wealth Management", `quantumwms.onmicrosoft.com`); `quantumwms.com` verified + primary there; `john@`/`sheila@` licensed (Business Premium); `sysadmin@` is the ACG admin (GA). The GoDaddy tenant was bypassed.
Onboarded ACG management access: Pax8 **GDAP approved** (relationship "Default_Ariz_Quantum Weal_704149625747913", 180 days), then ran `onboard-tenant.sh` against `2fd0092b` — only the **Tenant Admin** app needed a manual consent click; the script programmatically consented the rest (Security Investigator, Exchange Operator, User Manager, Defender) and assigned directory roles. Verified with a live Graph read. (Hit a wrong-tenant snag first: I'd pointed consent at the GoDaddy `ddf3d2c9` and `sysadmin@` bounced — re-discovery showed the domain had since verified into the new `2fd0092b`.)
Started the **security baseline** (Mike chose Conditional Access over Security Defaults — Business Premium includes Entra P1). Set John's initial password. Created a **break-glass GA** (`breakglass@quantumwms.onmicrosoft.com`, excluded from CA). Created **CA001 (MFA all) + CA002 (block legacy) in report-only** programmatically (Mike relaxed the "CA stays manual" rule given break-glass + report-only = near-zero blast radius). Emailed Sheila for the office Comcast **static IP** (for a trusted-location CA policy). Enforcement deferred until after tomorrow's mail cutover (Security Defaults covers MFA in the interim).
### Key Decisions
- **Fresh tenant, not GoDaddy takeover** — only 2 users; the GoDaddy tenant (`ddf3d2c9`) is a Managed tenant (no DNS takeover possible) and dormant, so a clean new tenant (`2fd0092b`) was simpler. The domain wasn't verified in GoDaddy's, so the new tenant claimed it.
- **Conditional Access over Security Defaults** — they pay for Business Premium (P1); CA is granular + break-glass-excludable + audit-friendly for a compliance-sensitive financial firm.
- **CA created in report-only, programmatically** — Mike opted to enable programmatic CA writes; safe here (break-glass excluded + report-only enforces nothing). Enforce after the mail cutover so block-legacy is observed against real mail traffic.
- **Single GA + break-glass** — `sysadmin@` (daily) + `breakglass@` (emergency, CA-excluded, password-never-expires) to prevent lockout before enforcing CA.
### Configuration Changes
- Syncro #32323: appointments `5598140927` (Wed 2PM Sheila) + `5598140928` (Thu 1PM Jen); recurring schedule **509862** ("365 Services", empty); comments for migration updates.
- M365 tenant `2fd0092b`: full ComputerGuru app suite consented + directory roles; CA001 `22cd5d4b` + CA002 `52db2b88` (report-only); break-glass GA created; John password set.
### Credentials & Secrets
- **M365 tenant:** `2fd0092b-e9b7-474c-ad73-301f34dd6b64` ("Quantum Wealth Management", `quantumwms.onmicrosoft.com`, `quantumwms.com` primary). Old GoDaddy tenant `ddf3d2c9-b76c-40d9-a216-9f11a1a26f97` (`netorg18235235.onmicrosoft.com`) — dormant, bypassed.
- **john@quantumwms.com** — initial password set 2026-05-27 by Mike: `SheilaDeena1952#` (forceChange=false; John MFA-enrolls at first sign-in). Licensed Business Premium.
- **sysadmin@quantumwms.com** — ACG admin, Global Admin (id `003cacd2-dc29-4fb6-9da4-756927c91e16`).
- **breakglass@quantumwms.onmicrosoft.com** — emergency GA (id `ad4a7a5c-a030-4e6f-bcd6-a0e7c7630f99`), cloud-only, password-never-expires, excluded from all CA. Password VAULTED at `clients/quantumwms/m365-breakglass.sops.yaml` (vault commit f08f339).
- **GDAP:** Pax8 US, relationship "Default_Ariz_Quantum Weal_704149625747913", Approved, 180 days.
### Infrastructure & Servers
- Email today: Intermedia HEX (`*.exch090.serverdata.net`), migrating to M365 tenant `2fd0092b`. License SKU: **SPB** (Business Premium) ×2.
- CA policies (report-only): CA001 Require MFA all users (`22cd5d4b-5e6a-4fbe-ad50-e57555b12d8d`), CA002 Block legacy auth (`52db2b88-55bf-4e7d-b060-ea4b14a253e2`), both exclude break-glass. Security Defaults still ON (interim).
### Commands & Outputs
- Onboard: `bash .claude/skills/remediation-tool/scripts/onboard-tenant.sh 2fd0092b-...` → [SUCCESS] (re-ran once to clear Graph replication-lag perm errors).
- Tenant discovery: `getuserrealm`/`openid-config` for quantumwms.com → first "Unknown"/not-found (GoDaddy parked), later Managed → `2fd0092b`.
- CA create: `POST /identity/conditionalAccess/policies` (tenant-admin token, `state: enabledForReportingButNotEnforced`).
### Pending / Incomplete Tasks
- **Thu 5/28 1:00 PM:** Jen Curry (IFG) — finalize DNS (archival + sent-mail encryption), then mail cutover ~1 PM.
- **PST backups** of John + Sheila mailboxes before cutover (todo `d3623023`) — Intermedia has no server-side export.
- **CA enforcement** (todo `6be618e1`): after mail cutover, disable Security Defaults + flip CA001/CA002 to enabled; add office static-IP named-location policy once Sheila sends the Comcast IP (requested).
- **Defender for Business** onboarding (BP-included, app consented).
- John Velez consent / Sheila's static IP reply.
### Reference Information
- Tenant `2fd0092b`; GoDaddy `ddf3d2c9`. GDAP "Default_Ariz_Quantum Weal_704149625747913" (Pax8). CA001 `22cd5d4b`, CA002 `52db2b88`. Schedule `509862`. Appts `5598140927`/`5598140928`. Todos `d3623023` (PST), `6be618e1` (CA baseline), `06c16144` is RMM (unrelated). Break-glass id `ad4a7a5c`. Memory: `project_quantum_godaddy_m365_tenant.md`.

View File

@@ -254,3 +254,65 @@ Built a new `/mailbox` command (`.claude/commands/mailbox.md`) for reading and s
### Reference Information
- Commits: `b22de6c` (gitignore autotask), `f8c00d3` (add /mailbox). Skill: `.claude/commands/mailbox.md`. Graph app `fabb3421` (see also `feedback_365_remediation_tool.md`).
---
## Update: 14:55 PT — Quantum M365 onboarding; IX autodiscover fix; Syncro emergency/labor rule overhaul
### Session Summary
Multi-client afternoon. **Michael Johnson #32329** (residential, prepaid=none): pulled the calendar-emergency ticket; emailed a hosting offer (his neptune-hosted mailbox has never been billed — product `45869` "Email - Exchange Hosted Email" $5/mo, or $50/yr) and **waived today's emergency fee** as a courtesy (noting declared emergencies normally carry a half-hour min). Noticed he was getting **Outlook cPanel redirect popups** and traced it to the `simplehost.email` DNS zone on **IX** (`172.16.3.10`, WHM/cPanel): `autodiscover`/`autoconfig` + a set of SRV records pointed at the cPanel box instead of the real mail host. Fixed `autodiscover` → CNAME `mail.acghosting.com` and removed all 6 SRV records (autodiscover/caldav/carddav); left `autoconfig` per Mike. Backed up the zone first. Emailed Michael that it's resolved.
**Quantum Wealth Management** M365 migration advanced substantially — full detail in `clients/quantumwms/session-logs/2026-05-27-session.md`. Summary: Jen Curry (IFG) approved the move; appointments + PST-backup TODO + an empty "365 Services" recurring template created; the GoDaddy-parked tenant was bypassed for a **fresh tenant `2fd0092b`**, onboarded with the full ComputerGuru app suite (Pax8 GDAP + `onboard-tenant.sh`); started the security baseline — break-glass GA, Conditional Access in report-only (programmatic), John's password set, office static-IP requested for a trusted-location policy.
**Cascades #32332** (prepaid) drove a Syncro rule overhaul. Howard had billed an emergency new-user setup with **made-up labor line names** ("Emergency Call Setup", "Onsite Computer Setup") on the wrong product. Corrected to a single line — `26184` "Labor - Emergency or After Hours Business" @ **2.25** (1.5 hrs × 1.5) — **via `update_line_item` (preserving Howard's `user_id=1750`** so his commission stayed intact). Posted an internal note for Winter; Winter resolved it / handled the invoice+QB re-sync.
That cascade produced several **rule changes** (all encoded in memory + the relevant skills): emergency billing (prepaid → `26184` @ hours×1.5 quantity, replacing the old `26118`×1.5; non-prepaid → `26184` with channel rate: Onsite $262.50, Remote/In-Shop $225); **never make up labor items** (existing product + real name; made-up items break the QuickBooks sync; description is free text); **corrections preserve the original tech's `user_id`** (commission); **Conditional Access may now be managed programmatically** (report-only first + exclude break-glass + confirm before enforce); and the **`fabb3421` app is deprecated** for customer-tenant onboarding (breaks AADSTS650052 on no-MDE tenants — use the tiered suite).
### Key Decisions
- **IX autodiscover fix via `whmapi1`, backup-first** — removed the cPanel proxy-subdomain hijack (autodiscover A→cPanel + SRVs) that caused Outlook redirect alerts; pointed autodiscover at the real Exchange (`mail.acghosting.com` = 67.206.163.124). Affects all `simplehost.email` hosted-mail clients, not just Michael.
- **#32332 corrected in place (`update_line_item`), not remove+add** — preserved Howard's `user_id`/commission. Codified as a rule: corrections are a debug action, don't reassign labor to the correcting tech.
- **Emergency rule: prepaid now uses `26184`** (was `26118`) at hours×1.5 quantity — keeps the line labeled emergency for QuickBooks; the dollar double-1.5 worry is moot for prepaid ($0 invoice).
- **Quantum: fresh tenant + CA over Security Defaults + programmatic CA** (see Quantum log).
### Problems Encountered
- **Wrong-tenant consent** for Quantum (pointed at GoDaddy `ddf3d2c9`; `sysadmin@` bounced) — re-discovery showed the domain had verified into the new `2fd0092b`; corrected. (Quantum log.)
- **`onboard-tenant.sh` replication-lag perm errors** — re-ran (idempotent) → clean.
- **#32332 prepaid gotcha** — Mike's "use the emergency item `26184`" would've been wrong for a prepaid customer under the OLD rule; the prepay check (27 hrs) caught it, then Mike clarified the rule (prepaid emergency = `26184` ×1.5 quantity).
### Configuration Changes
- IX `172.16.3.10`: `/var/named/simplehost.email.db``autodiscover` A→CNAME `mail.acghosting.com`, 6 SRV records removed, `autoconfig` left. Backup `simplehost.email.db.bak-claude-20260527`.
- Memory (new): `feedback_syncro_no_madeup_labor_items.md`, `feedback_syncro_corrections_preserve_tech.md`, `feedback_ca_programmatic_management.md`, `project_quantum_godaddy_m365_tenant.md`. (modified): `feedback_syncro_emergency_billing.md`, `feedback_365_remediation_tool.md`, `MEMORY.md`. (committed earlier this session): `feedback_psa_default_syncro.md`, `reference_coord_messages_api_shape.md`.
- Skills: `.claude/commands/syncro.md` (emergency-billing rules, 4 spots), `.claude/skills/remediation-tool/SKILL.md` (CA-manual boundary relaxed), `.claude/skills/remediation-tool/references/gotchas.md` (Quantum tenant row).
- Syncro: #32329 (Michael) hosting offer + waiver + DNS-fix notes, status Waiting on Customer; #32332 (Cascades) single corrected emergency line + internal note.
### Credentials & Secrets
- IX `simplehost.email` autodiscover now → `mail.acghosting.com` (neptune Exchange, `67.206.163.124`). IX = `172.16.3.10` (vault `infrastructure/ix-server.sops.yaml`).
- Michael Johnson hosted-email billing product: `45869` ("Email - Exchange Hosted Email", $5). Customer 152567.
- Quantum creds (tenant `2fd0092b`, break-glass, John's initial pw) — in the Quantum client log.
### Infrastructure & Servers
- IX (`172.16.3.10`, ix.azcomputerguru.com, ext 72.194.62.5): Rocky Linux WHM/cPanel, 80+ accounts. Hosts `simplehost.email` DNS zone (ACG hosted-email domain). `mail.acghosting.com` = neptune Exchange (`67.206.163.124`).
### Commands & Outputs
- IX: `whmapi1 removezonerecord/addzonerecord zone=simplehost.email ...` (autodiscover→CNAME, SRVs removed); verified via `dig +short autodiscover.simplehost.email`.
- #32332: `PUT /tickets/111233015/update_line_item``26184` @ 2.25, `user_id` preserved 1750.
### Pending / Incomplete Tasks
- **Michael #32329:** awaiting hosting choice ($5/mo vs $50/yr); ticket Waiting on Customer.
- **Cascades #32332:** Resolved; Winter verifying invoice/QB re-sync.
- **Quantum:** see Quantum log — Thu 5/28 1PM Jen DNS + mail cutover, PST backups, CA enforce, Defender, static IP.
- IX autodiscover may be recreated by cPanel proxy-subdomain feature — if Michael's popups return, disable that feature in WHM.
### Reference Information
- Tickets: #32329 (id 111214431, Michael Johnson), #32332 (id 111233015, Cascades), #32323 (id 111056440, Quantum).
- IX `172.16.3.10`; mail.acghosting.com `67.206.163.124`. Products: hosting `45869`, emergency `26184`, onsite `26118`, remote `1190473`. Tech user_ids: Mike 1735, Howard 1750, Winter 1737.
- Quantum tenant `2fd0092b`; detail in `clients/quantumwms/session-logs/2026-05-27-session.md`.