--- name: investigator description: > A scientific debugging methodology for diagnosing persistence failures, integration mismatches, and layered system failures in full-stack applications. Invoke this skill whenever a user reports: a value that saves but reverts on reload; a controller that returns success but nothing persists; a component and its backend disagreeing on a value; a fix that was applied but the symptom persists; or any bug where the failure point is unclear across multiple layers. Also trigger for phrases like "it's not saving", "keeps reverting", "the API says success but nothing changes", "not sure where it's breaking", "worked before but now it doesn't", or "already tried X but still broken". Do not skip this skill for debugging tasks just because they seem simple — layered systems hide failures at unexpected layers. --- # Investigator A scientific debugging protocol for persistence failures and integration mismatches in layered full-stack systems. Evidence before hypothesis. Wire over code. Systemic over isolated. --- ## When to invoke - A value saves but reverts to a default on reload - A controller reports success but nothing actually persists - A component and its data source disagree on a value - Multiple layers are involved and the failure point is unclear - A fix was applied but the symptom persists The protocol is **intentionally slow at the start and fast at the end**. Evidence gathering precedes hypothesis formation — reversing this is the most common cause of wasted debugging effort. --- ## The Seven Steps ### Step 1 — Trace the code path end-to-end Do not speculate. Ask the user (or inspect the codebase) to trace the exact path the value travels, in actual source files: - What **writes** the value? (user input → state → API call → storage) - What **reads** it back? (storage → query → component → render) - What has changed recently in that path? **Deliverable**: a written statement of the expected data flow with file paths and line numbers. If this cannot be written, do not proceed to Step 2. --- ### Step 2 — Verify the write/read contract at the component boundary Zoom in on the exact interface between the component and its data source: - What format does the component **send**? (camelCase, JSON, form data) - What format does storage **expect**? (snake_case, serialised PHP, DB columns) - Is there a transformation layer? Does it handle edge cases: `0`, `false`, `""`? - Does the **read path** return the same shape the write path sent? **Deliverable**: confirm or deny that the component and its backend agree on the data contract. --- ### Step 3 — Map the full lifecycle including caches and middleware Widen scope beyond the obvious failure point: - What happens **before** the write? (auth, middleware, request parsing) - What happens **after** the write? (cache invalidation, serialisation, revalidation) - What does the **network actually send and receive** — not what the code intends, what the wire sees - Are there caching layers between the write and the read? **Deliverable**: a map of the full request/response lifecycle with any silent failure points identified. --- ### Step 4 — Generate ≥3 hypotheses before testing any This is the most important step. Do not investigate any hypothesis before generating a minimum of three: - The **obvious** cause — what the symptom points to directly - The **systemic/infrastructure** cause — something that would affect all values, not just this one - The **type or serialisation** cause — something specific to the failing value's type or range Rank by **testability**. Investigate the most testable hypothesis first, not the most intuitive one. Discard each only when disproven by evidence, never by reasoning alone. **Deliverable**: a written list of hypotheses in testing order. --- ### Step 5 — Check every adjacent component for the same failure mode Once a failure mode is identified in one component, immediately audit adjacent components: - Do other controllers share the same persistence mechanism? - Do any have fields with the same value characteristics (zero, false, empty string)? - Would the same root cause affect them **silently**, in ways not yet visible? **Deliverable**: a determination of whether the bug is isolated or systemic. Systemic bugs require systemic fixes — patching only the one that surfaced is incomplete work. --- ### Step 6 — Verify against the running system, not the source code At every stage, verify against the live system: - Make **direct requests** to endpoints; inspect the raw response body - Check what is **actually stored** in the database, not what the ORM claims - Confirm HTTP status codes **and** response `Content-Type` — a 200 with HTML body is not a success - Test the **specific failing value** (`0`, `false`, `""`) in addition to the happy path **Deliverable**: every hypothesis is confirmed or eliminated by observed evidence, not inference. --- ### Step 7 — Patch every occurrence, not just the one that surfaced After the fix lands: - Confirm the original failing case now works - Confirm adjacent components are not broken - Ask: does this fix apply everywhere the same pattern exists? - Apply the fix at every occurrence before closing the investigation **Deliverable**: the investigation is closed only when the root cause is patched at every instance, not just the one that surfaced. --- ## Core Principles **Evidence before hypothesis.** Steps 1–3 gather evidence. Step 4 forms hypotheses. Inverting this produces confirmation bias and missed root causes. **Three root causes, minimum.** The true root cause is often not the obvious one. The "at least three" constraint forces lateral thinking. **Test the wire, not the code.** What the code intends and what the network sends are different things. Always verify at the transport layer. **Systemic suspicion.** When one instance of a pattern fails, treat every other instance of the same pattern as suspect until proven otherwise. **A 200 is not a success.** HTTP status alone does not confirm correctness. Always validate Content-Type and response body structure. --- ## How to run this protocol with a user When invoked, structure the conversation as follows: 1. **Orient**: restate the symptom in one sentence to confirm you understand it. 2. **Step 1 prompt**: ask the user to describe or show the full data path — what writes it, what reads it, file paths if available. 3. **Steps 2–3**: work through the contract and lifecycle together, asking targeted questions. Surface anything that could be a silent failure. 4. **Step 4**: present your three hypotheses explicitly, ranked by testability. State why each is on the list. 5. **Steps 5–6**: guide the user through empirical tests. Provide the exact commands or requests to run (curl, fetch, direct DB query). Interpret results together. 6. **Step 7**: once root cause is confirmed, enumerate every place the fix must be applied. Do not close until all are patched. Do not jump to solutions. Do not accept "it should work" as evidence. Do not close the investigation on a single passing test. --- ## Quick Reference ``` 1. Trace the code path end-to-end (file paths + line numbers) 2. Verify the write/read contract at the component boundary 3. Map the full lifecycle including caches and middleware 4. Generate ≥3 hypotheses before testing any 5. Check every adjacent component for the same failure mode 6. Verify against the running system, not the source code 7. Patch every occurrence, not just the one that surfaced ``` --- ## Reference: iD7 Case Study (June 2026) A real instance of this protocol in action, useful for calibrating what "rigorous" looks like. **Symptom**: `subheadMtRem: 0` reverted to `1` on every page reload. **Step 1** — Path: HeroController → `POST /api/save-hero-config` → WordPress REST API → `wp_options` → WPGraphQL → `fetchHeroSettings()` → render. **Step 2** — `class-hero-graphql.php` declared spacing fields as nullable `Float`. PHP `empty(0.0) === true`, causing WPGraphQL to coerce `0` → `null`. **Step 3** — `NEXT_PUBLIC_WORDPRESS_API_URL` pointed to `http://wordpress/graphql`. Tested this URL directly from the Next.js container. **Step 4 hypotheses**: 1. WPGraphQL `empty()` coercion of nullable Float *(obvious)* 2. Plain permalink structure breaking all path-based WordPress routing *(systemic/infrastructure)* 3. ISR cache serving stale data *(cache layer)* **Step 6** — Empirical test: `fetch('http://wordpress/graphql')` returned `` with HTTP 200. GraphQL was silently failing on every page load. Every render used hardcoded TypeScript defaults. The save route was returning `{"saved": true}` while POSTing to a URL that served homepage HTML. **True root cause**: WordPress plain permalink structure. Both `/graphql` and `/wp-json/` require mod_rewrite, which requires pretty permalinks. The `empty()` fix was real but moot. **Step 5** — Same `empty()` vulnerability found in Disqualifier (`delay: 0`) and Ribbon (`isActive: false`, `pauseOnHover: false`). All three patched. **Step 7** — Confirmed `subheadMtRem: 0`, `delay: 0`, `pixelOpacity: 0`, `isActive: false`, and `pauseOnHover: false` all persist through the full GraphQL round-trip.