---
name: data-sanity
description: "Layer C of spectra pre-handoff quality gates. Static audit for the data-shape failure class where a client query param literal violates the server zod schema bound (e.g. perPage: 200 vs max(100)) → silent 4xx → lookup map empties → admin list column collapses to a uniform fallback. Use during /spectra-apply Design Review and before /spectra-archive, or ad-hoc on any change that touches paginated queries + lookup-resolved columns."
effort: medium
metadata:
  author: clade
  version: "1.0"
---
<!-- 🔒 LOCKED — managed by clade · auto-generated by sync-to-agents; edit source in .claude/ then re-run sync -->

`/data-sanity` — Layer C static data-shape audit.

Born from perno `app-status-badge-extraction`（2026-05-24）: `attendance/amendments.vue` 用 `useEmployeeListQuery({ perPage: 200 })`，但 server schema 是 `perPage: z....max(100)` → API 400 → `employeeNameMap` empty → 員工 column 整列「-」。typecheck / lint / design review / verify:ui 全沒抓到，因為它是**資料形狀**問題，不是型別或視覺問題。

這是 clade 自有 skill（**不是** `/impeccable` 的 sub-command — impeccable 是第三方 npx-managed skill，clade 不擁有）。

## 何時跑

- **`/spectra-apply` Section 7 Design Review 期間**：對本 change 觸及的 paginated query + lookup-resolved column 跑一次。
- **`/spectra-archive` Step 0 之前**：archive gate 的 data-shape 防線。
- **Ad-hoc**：任何改到 `useXxxQuery({ <param>: <literal> })` 或 lookup map 的工作。

## 怎麼跑

從 clade central 呼叫（`<clade-vendor>` = `~/offline/clade/vendor`，與其他 vendor script 同慣例）：

```bash
node <clade-vendor>/scripts/audit-data-sanity.mjs \
  --consumer-path . \
  [--files <comma-separated-touched-files>] \
  --json
```

- 不給 `--files` → walk 整個 consumer（skip node_modules/.nuxt/dist）。
- 給 `--files` → 只掃指定檔（spectra-apply 時傳本 phase touched files 較快）。

## 偵測項

1. **PARAM_BOUNDARY（Critical — blocks archive）**：client 端 pagination-ish param literal（`perPage` / `per_page` / `pageSize` / `limit` / `take` / `first` / `count` / `top` / `size`）超過（max）或低於（min）同名 server zod bound（`.max(N)` / `.min(N)` / `.length(N)`）。對應 UI-INV-2（lookup-resolved column 解析率 100%）。
2. **LOOKUP_MAP_RISK（advisory / warn）**：偵測 `xxxMap` / `xxxLookup` / `xxxById` 從 query data（`.reduce(` / `Object.fromEntries(` / `new Map(`）建的 lookup map。提醒：來源 query 若失敗，此 map empty → 對應 column uniform fallback。確認來源 query param 在 schema bound 內。

> **限制（heuristic，非真 AST）**：clade home 無 oxc-parser / @vue/compiler-sfc，本 audit 是 dependency-free regex 掃描（與 `d-pattern-audit.mjs` / `manual-review-audit.mjs` 同慣例），靠 **param 名跨檔對應** 連結 client literal ↔ server bound。同名 param 出現在不相關 schema 時可能 false-link；PARAM_BOUNDARY 命中後請人工確認該 client query 真打到該 server endpoint。

## 結果處置

- **exit 0 `status: "pass"`** → 通過。advisory lookupRisks 印到 stderr 供參考，不 block。
- **exit 1 `status: "fail"`**（含 PARAM_BOUNDARY）→ **Critical，MUST block**：root-cause 修 client literal 到 bound 內（典型：`perPage: 200` → `100`），或若新 bound 是有意則調 server schema。**NEVER** 標 phase / archive done、**NEVER** 留給 user 在 manual review 抓。
- JSON schema：`{ status: "pass"|"fail", violations: [{param, clientValue, kind, bound, clientFile, clientLine, serverFile, serverLine}], lookupRisks: [{name, file, line}], scanned: N }`。

## 與其他 Layer 的關係

- **Layer B**（`refactor-invariant-check.mjs`）：runtime 偵測「column 整欄 fallback」（症狀）。Layer C 是 static 偵測「param 超界」（root cause）。兩者互補。
- **Layer D**（`UI-INVARIANTS.md`）：UI-INV-2 是本 skill 的契約來源。
- **Layer E.1**（pre-handoff self-analysis）：D4 維度（API contract boundary）可直接引用本 audit 結果。
