From 0897e5e317a13be612e3ae6744e5878c542017db Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Thu, 21 May 2026 10:19:52 -0700 Subject: [PATCH] fix(sync): detect untracked-only changes; reconcile timer-era memories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sync.sh: replace `git diff-index --quiet HEAD --` with `[ -n "$(git status --porcelain)" ]` in both the main-repo (Phase 1) and vault change-detection, so brand-new untracked files are no longer silently skipped (the bug Howard hit 2026-04-17). Mark project_sync_script_bug.md RESOLVED. .gitignore: exclude the datto BSOD dumps (6 MB zip + 48 MB extracted) so the detection fix doesn't sweep 54 MB of binaries into the repo. memory: finish the add_line_item reconciliation — drop legacy "time entry" / timer-billable framing from feedback_syncro_labor_type and feedback_syncro_warranty_product (and their index lines); the product-selection rules themselves are unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/memory/MEMORY.md | 6 +++--- .claude/memory/feedback_syncro_labor_type.md | 8 ++++---- .claude/memory/feedback_syncro_warranty_product.md | 2 +- .claude/memory/project_sync_script_bug.md | 8 +++++++- .claude/scripts/sync.sh | 8 +++++--- .gitignore | 5 +++++ 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index cdb52ad..4b5a242 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -36,10 +36,10 @@ - [/tmp path mismatch on Windows](feedback_tmp_path_windows.md) — Write tool and Git Bash resolve `/tmp` to DIFFERENT real dirs. Use heredoc or workspace path for JSON payloads handed to curl. Caused wrong-comment incident on Syncro #32225. - [Syncro — leave contact blank by default](feedback_syncro_blank_contact.md) — Default to blank contact ("Not Assigned") on tickets and billing for ALL customers. Blank lets Syncro use company-level email defaults; setting a contact may route to a secondary email and bypass distribution. Generalizes the prior Cascades-only rule per Winter 2026-05-04. - [Syncro — never set contact on Cascades tickets](feedback_syncro_cascades_contact.md) — Cascades-specific instance of the blank-contact rule above. Kept for the Meredith-defaulting incident detail. -- [Syncro — use a billable labor type, never "Prepaid project labor"](feedback_syncro_labor_type.md) — Time entries must use in-shop / onsite / remote / web labor. "Prepaid project labor" is exempt and won't decrement prepay blocks. Default is Remote labor for typical support tickets. Winter caught this 2026-05-04. +- [Syncro — use a billable labor type, never "Prepaid project labor"](feedback_syncro_labor_type.md) — Billable line items must use in-shop / onsite / remote / web labor. "Prepaid project labor" is exempt and won't decrement prepay blocks. Default is Remote labor for typical support tickets. Winter caught this 2026-05-04. - [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). Do NOT use Remote/Onsite + `billable: false` — Syncro silently overrides the flag. Do NOT patch `price_retail` to convert one labor product into another; pick the correct product and re-run. Hit on #32225 2026-05-06. +- [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. - [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. - [Clear-RecycleBin fails silently as SYSTEM](feedback_clear_recyclebin_system_context.md) — RMM-dispatched cleanup scripts cannot use `Clear-RecycleBin -Force`; the cmdlet uses Shell COM and silently no-ops without an interactive desktop. Enumerate `C:\$Recycle.Bin\\*` directly. Hit on ASSISTMAN-PC 2026-05-08. @@ -54,7 +54,7 @@ ## Project - [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](project_sync_script_bug.md) — Flagged for Mike. `.claude/scripts/sync.sh` line 53 misses untracked-only changes; one-line fix included. +- [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. - [MasterBooter Side Project](project_masterbooter.md) — Howard's Rust+Slint Windows deployment toolkit at C:\MasterBooter, separate from client work. Do not log to clients/. - [Audio Processor Architecture](project_audio_processor_architecture.md) - Segment-first pipeline: detect breaks before transcription for complete content capture - [Neptune Email Routing Issues](project_email_routing_neptune.md) - Multiple clients (devcon, Sorensen/rieussetcorp) have email not routing properly from Neptune diff --git a/.claude/memory/feedback_syncro_labor_type.md b/.claude/memory/feedback_syncro_labor_type.md index 6cecc71..890acbb 100644 --- a/.claude/memory/feedback_syncro_labor_type.md +++ b/.claude/memory/feedback_syncro_labor_type.md @@ -1,10 +1,10 @@ --- name: Syncro — use a billable labor type (in-shop / onsite / remote / web), never "Prepaid project labor" -description: When creating Syncro time entries, the labor type / product on the entry MUST be one of in-shop, onsite, remote, or web labor. "Prepaid project labor" is an exempt labor type and will NOT draw down a customer's prepay block — using it silently breaks block-hour accounting. +description: When billing Syncro tickets, the labor product on the line item MUST be one of in-shop, onsite, remote, or web labor. "Prepaid project labor" is an exempt labor type and will NOT draw down a customer's prepay block — using it silently breaks block-hour accounting. type: feedback --- -**Rule:** Time entries on Syncro tickets must use a billable labor product matching the work delivery channel: **in-shop**, **onsite**, **remote**, or **web labor**. Do NOT use **"Prepaid project labor"** as the labor type for normal work. +**Rule:** Line items on Syncro tickets must use a billable labor product matching the work delivery channel: **in-shop**, **onsite**, **remote**, or **web labor**. Do NOT use **"Prepaid project labor"** as the labor type for normal work. **Why:** Winter caught me on 2026-05-04 using "Prepaid project labor" by default. That product is **exempt** — it does not consume hours from a customer's prepaid block. So even if the ticket is for a prepay customer and looks billed correctly on the invoice, the block balance never decrements. Block-hour accounting silently drifts. Only the four non-exempt labor types (in-shop / onsite / remote / web) burn block time as intended. @@ -15,10 +15,10 @@ type: feedback - **Onsite labor** — work done at the client's physical location. - **In-shop labor** — hardware brought to ACG's office for repair/build. - **Web labor** — purely cloud/portal work (Microsoft 365 admin center, Entra, Cloudflare, etc.) where there's no remote-into-a-machine component. (Confirm with Winter if this distinction matters in your situation — sometimes "remote" is the right pick even for cloud work.) -- **Resolving the product_id:** Use `GET /products?search=remote+labor` (etc.) to pull the right product_id for the labor type, then pass that as `product_id` on the `timer_entry` POST. +- **Resolving the product_id:** Use `GET /products?search=remote+labor` (etc.) to pull the right product_id for the labor type, then pass that as `product_id` on the `add_line_item` POST. - **Never default to "Prepaid project labor"** unless explicitly directed. If you find an existing entry with that product on a normal billable ticket, flag it — Winter (or whoever) will need to retroactively switch the labor type so the block decrement actually posts. - **Verifying:** After billing, check that the customer's prepay block balance dropped by the expected number of hours. If it didn't, the labor type was wrong. **Real-world incident — 2026-05-04:** Tickets I created on this date used "Prepaid project labor" as the auto-selected labor type. Winter is fixing them retroactively. Going forward, default to `Remote labor` for the typical remote-support ticket, then adjust per delivery channel. -**Where this lands in skill code:** `.claude/commands/syncro.md` and the `syncro` skill workflow examples need to make labor-type selection an explicit step in the timer_entry workflow, not a silent default. +**Where this lands in skill code:** `.claude/commands/syncro.md` and the `syncro` skill workflow examples need to make labor-type selection an explicit step in the add_line_item billing workflow, not a silent default. diff --git a/.claude/memory/feedback_syncro_warranty_product.md b/.claude/memory/feedback_syncro_warranty_product.md index b1a17cb..efe49fb 100644 --- a/.claude/memory/feedback_syncro_warranty_product.md +++ b/.claude/memory/feedback_syncro_warranty_product.md @@ -15,7 +15,7 @@ type: feedback **How to apply:** - **For any warranty / no-charge work:** `product_id = 1049360`, qty = actual hours, no need to patch the line — it generates at $0 because the product's `price_retail` is $0. -- **Set `billable` based on the product, not the situation.** For the warranty product, leave `billable: true` — Syncro decides line economics from `price_retail` × `quantity`, and warranty product is $0 by design. (Anecdotally, Syncro's `timer_entry` endpoint silently overrode `billable: false` to `true` on 2026-05-06, so don't rely on it as a price gate anyway.) +- **The warranty product is $0 by design — don't fake a free line with flags.** Its `price_retail` is $0, so the line generates at $0 from `price_retail` × `quantity`. Do NOT take a regular labor product and try to neutralize it with `billable: false`; that was the original mistake (see Why — and Syncro silently overrode the flag in the timer era anyway). Pick `1049360`. - **Never reach for `update_line_item` to drop a price as a workaround.** If the dollar amount on a line is wrong, the wrong product was selected — undo, pick the correct product, redo. The only legitimate use of `update_line_item price_retail` is the Syncro auto-gen-zero recovery case (when the auto-line came in at $0 instead of the product's actual rate), and even that is a Syncro bug we're patching around, not a price-management tool. - **For the dropdown of available labor products,** see the rate table in `.claude/commands/syncro.md`. If the situation doesn't match any of those, ask before improvising. diff --git a/.claude/memory/project_sync_script_bug.md b/.claude/memory/project_sync_script_bug.md index 7aa5200..b8158b2 100644 --- a/.claude/memory/project_sync_script_bug.md +++ b/.claude/memory/project_sync_script_bug.md @@ -1,9 +1,15 @@ --- name: Sync script bug — untracked files -description: Flagged for Mike — .claude/scripts/sync.sh misses untracked-only changes +description: RESOLVED 2026-05-21 — sync.sh now uses git status --porcelain (catches untracked-only changes) in both repo and vault detection. type: project --- +> **RESOLVED 2026-05-21.** Fixed in `.claude/scripts/sync.sh`: both the main-repo (Phase 1) +> and vault change-detection now use `if [ -n "$(git status --porcelain)" ]` instead of +> `git diff-index --quiet HEAD --`, so untracked-only changes are caught. A `.gitignore` entry +> was added for the datto BSOD dumps (54 MB binary) so the fix doesn't sweep them in. Original +> report retained below. + `.claude/scripts/sync.sh` line 53 uses `git diff-index --quiet HEAD --` to detect local changes. This only flags **tracked** files with modifications. Brand-new untracked files (a new report, new session log, new memory) will NOT be detected on their own — they only get swept up when a tracked file is also dirty (because `git add -A` then runs). Symptom seen 2026-04-17 by Howard: added a single new report file, ran /sync, script said "No local changes to commit" and did nothing. Workaround was `git add ` first, then re-run. diff --git a/.claude/scripts/sync.sh b/.claude/scripts/sync.sh index 604dd88..255d42a 100755 --- a/.claude/scripts/sync.sh +++ b/.claude/scripts/sync.sh @@ -92,7 +92,9 @@ fi echo "" echo "=== Phase 1: Local changes ===" -if ! git diff-index --quiet HEAD -- 2>/dev/null; then +# Detect any change including untracked-only (git diff-index ignores untracked files, +# so a brand-new file with no tracked changes would otherwise be silently skipped). +if [ -n "$(git status --porcelain)" ]; then echo -e "${YELLOW}[INFO]${NC} Local changes detected:" git status --short echo "" @@ -270,8 +272,8 @@ else cd "$VAULT_PATH" echo -e "${GREEN}[OK]${NC} Vault: $VAULT_PATH" - # Commit any local vault changes - if ! git diff-index --quiet HEAD -- 2>/dev/null; then + # Commit any local vault changes (porcelain catches untracked-only too) + if [ -n "$(git status --porcelain)" ]; then echo -e "${YELLOW}[INFO]${NC} Local vault changes detected — committing..." git add -A git commit -m "sync: auto-sync vault from $MACHINE at $TIMESTAMP" diff --git a/.gitignore b/.gitignore index 7a6d987..9eb76dc 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,8 @@ Pictures/ projects/radio-show/audio-processor/test-data/*.mp3 projects/radio-show/audio-processor/*.egg-info/ +# Large binary diagnostic artifacts (memory dumps, extracted case archives) — +# keep raw dumps out of git; record findings in markdown instead. +clients/internal-infrastructure/datto-bsod-case-2026-05-16.zip +clients/internal-infrastructure/datto-bsod-case-2026-05-16/ +