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.
Scanned 6/6/2026
Install via CLI
openskills install YuDefine/nuxt-supabase-starter---
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 結果。
No comments yet. Be the first to comment!