audit query (= 監査クエリ) / IDB rule の test を 業務知識 + DataLog 挙動 の 両軸 で 設計する 専用 skill。 datalog-debug-skill の §5 (= test 駆動 lifecycle) が 基本 lifecycle (= scope 別 必須件数 / dual content / status 4 値) を カバー する 一方、 本 skill は 「何を test するか / どう サンプリング するか / どの 境界値 を 押さえるか」 に 集中。
LLM が datalog_testcase_create / datalog_testrecord_propose を 呼ぶ 直前 に 本 skill を 参照し、 test 集合 が:
ことを 確認する。
すべての TestCase は dataSource を 必ず明示する。 runtime 導出 (= testRecords から自動推測) は 廃止。 作成時に作者が下表から 1 つを選び宣言する (schema が required で強制し、 未指定は parse で reject)。
| dataSource | いつ | 追加 field |
|---|---|---|
freee_e2e |
検出対象を freee に実投入できる 監査 test (= testRecords で freee 投入 → e2e 走行) | (なし) |
datom_only |
検出対象を freee に作れない ゆえ datom 単体で完結する完成形 (= e2e 不要、 passing が terminal green) |
e2eBlockedReason 必須 + e2eBlockedDetail 推奨 |
datom_only の e2eBlockedReason (= なぜ freee に作れないか / 対象でないか):
| reason | 意味 |
|---|---|
freee_required_field_missing |
必須欄 (account_item / tax_code / issue_date 等) を欠落させて作れない (400) |
freee_readonly_derived |
read-only 派生値 (月次勤怠 等) を任意値で合成できない |
freee_demo_constraint |
デモ会社 / 権限制約で作れない (従業員 0 / HR 403) |
freee_api_unsupported |
合成 POST 経路が無い (origin 手動固定 等) |
not_freee_target |
freee 検出対象でない純論理 IDB rule test (= idb rule の導出検証。 freee 投入と無関係) |
判定: 「この検出対象を freee 上に作れるか?」→ 作れる=freee_e2e / 作れない=datom_only+理由 / そもそも freee と無関係の純論理 IDB rule=datom_only+not_freee_target。 UI では datom_only は投入/e2e ボタンが不感化される (= 純論理/作成不能テストで正しい挙動)。
TestCase.tags は array で 複数 選択。 各 tag は 意図 + few-shot で 揃えて 使う。
| tag | 意図 | few-shot 観点 |
|---|---|---|
happyPath |
正常系 (= 違反 と すべきでない 標準 case) | 通常の deal で 全 必須項目 揃い |
boundary |
条文 / 規定 の 閾値 上 で の 判定 | 30000 円 ぴったり / 45 時間 ぴったり |
edgeCase |
業務上 の レア case | 0 円 取引 / 巨大金額 / 同日 多重 計上 / 過去 年度 修正 |
nullHandling |
必須 attr が 欠落 | 取引先 null / 税コード null / 期日 null |
errorHandling |
エラー 経路 | freee API 取得 失敗 / 必須 entity 不在 |
dataIntegrity |
参照 整合性 | 借方=貸方 不一致 / 親 deal 不在 の detail |
multiEntity |
複数 entity 連結 | deal + detail + partner + account_item の 4 階層 揃い vs 1 欠け |
regression |
rule 改修時 の 既存挙動 維持 | 過去 全 query で passing を 復活 確認 |
historicalBug |
過去 incident の 再発防止 | 「2024 Q3 に partner null を 拾い損ねた 事例」 |
複数 tag 重ね OK (= 「boundary + nullHandling」 等)。
audit query が 触る ドメイン 別 に、 法令 / 規程 の 「閾値」 を 列挙。 LLM は 該当 ドメイン の boundary を 必ず test に 入れる。
| boundary | 根拠 / 文脈 | test 例 |
|---|---|---|
| インボイス T番号 有無 | 適格請求書 発行事業者 登録 | T番号 あり vs なし vs 形式不正 |
| 経過措置 控除割合 | 2023-09 〜 2026-09: 80% / 2026-10 〜 2029-09: 50% | 期間 跨ぎ の 控除割合 切替 |
| 税区分 4 値 | 課税仕入 / 課税売上 / 非課税 / 対象外 | 各 値 1 件 |
| 軽減税率 | 8% / 10% / 0% (= 輸出) | 各 税率 で の 集計 |
| boundary | 根拠 / 文脈 | test 例 |
|---|---|---|
| 交際費 損金 限度 | 中小法人 800 万円 / 上位 50% / 0% | 限度直前 / 直後 |
| 寄付金 損金 限度 | 一般 / 特定 / 国等 別 限度 | 計算式 境界 |
| 役員 報酬 定期 同額 | 月額 一定 (= 不定期 改定 は 損金 不算入) | 改定 タイミング 境界 |
| boundary | 根拠 / 文脈 | test 例 |
|---|---|---|
| 電子取引 発生源 | 銀行 明細 / カード明細 / メール添付 PDF / 経費申請 等 = 電子保存 必須 | 各 発生源 で 証憑 有 vs 無 |
| 紙 受領 | 紙 領収書 は 電帳法 対象外 (= 別途 国税関係 書類 保存) | 発生源 = 紙 で 証憑 無 → 拾わない |
| スキャナ保存 タイムスタンプ | 撮影 から 3 日 以内 | 境界 タイミング |
| boundary | 根拠 / 文脈 | test 例 |
|---|---|---|
| 法定外労働 月限度 | 45 時間 / 月 | 44h / 45h / 45h+1min / 60h |
| 法定外労働 年限度 | 360 時間 / 年 | 月 平均 30h 累積 / 後半 集中 |
| 特別条項 月限度 | 100 時間 (= 単月) / 80 時間 (= 2-6 ヶ月 平均) | 単月 99h vs 100h vs 101h |
| 特別条項 年限度 | 720 時間 / 年 | 累積 境界 |
| 健康福祉 確保措置 | 80h/月 超 で 医師 面談 等 | 80h 境界 + 措置 有無 |
| boundary | 根拠 / 文脈 | test 例 |
|---|---|---|
| 高額 取引 承認 | 30000 円 / 100000 円 / 1M 円 (= 社内 規程 次第) | 各 閾値 ± 1 |
| 役員 取引 利益相反 | 役員 + 役員 親族 / 役員 関連会社 | 取引先 属性 |
| 締日 跨ぎ 取引 | 月末 / 期末 | 計上 月 切替 |
| boundary | 根拠 / 文脈 | test 例 |
|---|---|---|
| 発行日 | 法人税法 施行規則 第54条 | 発行日 有 vs 無 |
| 取引先 | 消費税法 第30条第7項 (= 課税仕入 のみ) | partner 紐付け 有 vs 無 vs 親 deal 経由 |
| 勘定科目 | 仕訳 必須 | account_item null vs 設定済 |
| 借方 / 貸方 一致 | 複式簿記 原則 | 不一致 額 1 円 vs 0 円 |
| 税コード | 消費税申告 集計用 | tax_code null vs 設定済 |
全件 (= 数千件) を test に 入れない。 サンプリング 必須項目:
各 boundary に対して 必ず positive + negative の pair を 作る。
runStatus='passing' で 検出 する二極化 が 揃ってない と:
例: 36 協定 (boundary = 45h):
| kind | datoms | expected |
|---|---|---|
| positive | excess_statutory_mins = 2701 (= 45h + 1min) | hit |
| positive | excess_statutory_mins = 3600 (= 60h、 大幅 超) | hit |
| negative | excess_statutory_mins = 2700 (= 45h ぴったり) | not-hit |
| negative | excess_statutory_mins = 2699 (= 45h - 1min) | not-hit |
走行後 runStatus + actualHits から 4 区分 を 計算:
| 期待 拾う (= positive) | 期待 拾わない (= negative) | |
|---|---|---|
| 実 拾う | truePositive (= TP、 期待通り) | falsePositive (= FP、 過剰検出 = audit query が 厳しすぎ) |
| 実 拾わない | falseNegative (= FN、 検出漏れ = audit query が 緩すぎ) | trueNegative (= TN、 期待通り) |
datalog_testrecord_list の byKindRunStatus で 集計可能。
判断:
§5 の「FP→厳しく / FN→緩く」 は 単発 の 局所処方。 ここでは 「failing を前にした時の判断順序」 を 規定する。 順序自体が FN 優先思想を体現する。
迷ったら TP に倒す (= FN を作らない、 検出する方向)。 決定不能なら warning に降格して人へ渡す (= FN も作らず歪めもしない)。 FP は アクション 4 で後から削れる。 failing を消すこと自体を目的化しない — クエリを期待値に寄せる / 期待値をクエリに合わせて緩める のは、 決定論と説明可能性をシステム内部から腐らせる。
問い0: 単発の failing か、類型内の散らばりか?
└ datalog_testrecord_list の byKindRunStatus を 類型単位 で 見る。
特定類型で FP と FN が両立 → 散らばり → §5.6 へ
単発 → 問い1へ
※ 散らばりは個々のテスト行では気づけない。集計ビューで初めて見える。
問い1: そもそも datalog で決定的に判定できる種類の違反か?
└ 判断境界・規範解釈・事実認定が絡む (外注費か給与か、 交際費か会議費か 等)
→ 否なら即 A5a (warning 降格、 人へ)。 クエリを歪めず FN も作らない。
→ 是なら問い2へ
問い2: 食い違いは FN型 か FP型 か?
├ FN型 (期待 TP・なのに検出されず = 取りこぼし) = 最優先で潰す
│ 切り分け: 期待値が誤り→A1 / fixture が誤り→A2 / クエリが狭すぎ→A3 (主力)
│ 迷ったら TP に倒す
└ FP型 (期待 TN・なのに検出 = 過剰) = 急がない。 常に FN 対応の後
クエリが広すぎる→A4 で精緻化 / 期待値の誤解→A1
| アクション | datalog で対応? | user 協調 | FN 優先での位置づけ | |
|---|---|---|---|---|
| A1 | 期待値修正 (what) | 否 (what 層、 クエリは正しい) | 必須: _propose→user _approve。 LLM 単独で書換禁止 (= 期待値は監査基準 SOR) |
FN 局面で期待値を緩めて見逃しを消すのは思想違反。 特に慎重に |
| A2 | 入力データ修正 (fixture/data) | 否 (data 層、 特に e2e で投入シナリオが意図構造にならない時) | 必須: 業務知識者に実務上のデータ形を確認、 原票 (freee 画面) で診断 | fixture の歪みを除去して 4 区分の信頼性を回復 |
| A3 | クエリ修正 (how・正攻法) | 是 (datalog_query_update) |
低 (LLM 主導)。 広げると FP 増 → 後で A4 で削る | FN 潰しの主力。 迷ったら TP 側に倒す |
| A4 | クエリ追加 (how 拡張・精緻化) | 是 (IDB 述語 / サブ条件追加、 既存は壊さず多層化) | 中: error/warning の境界を user に諮る | A3 で FN 潰した後の増分 FP を削る (=「FP はいくらでも調整できる」の実体) |
| A5a | ケース単位の降格 | 否 (明示放棄、 信頼度 error→warning) | 最も協調的: 決定を人へ返す | FN の安全弁。 歪めず・見逃さず・人に渡す |
e2e_passing (40) の手前 (= passing だが e2e 未到達) で止まっている時は、 testRecords の freee 投入結果を先に診断する。散らばりを A5a (ケース単位の決定不能) として 一律降格すると、 本来 TP で確実に黒だったものまで warning に落ちる (= FN 優先に反し確実な違反の信頼度を下げる)。 散らばりは「1 ケースがグレー」ではなく「判別境界が母集団分布にうまく引けていない」状態で、 性質が違う。
問い: その散らばりは、 追加の特徴量 (述語) で 分離可能 か、 本質的に分離不能 か?
- 分離可能: 今は混在だが未使用属性を述語化すれば TP 群 / TN 群 を分けられる (例: 「同一取引先で科目が揺れる」FP が、 実は「継続契約先 / スポット先」で正常異常が分かれていた → 契約区分述語で解ける)。 → A3 で境界を FN ゼロ位置まで広げ、 増分 FP を A4 の追加述語で削る。 散らばりを 4 区分がきれいに分かれる状態へ追い込む。
- 本質的に分離不能: どんな属性を足しても datalog の見える事実だけでは分けきれない (実態判断・規範解釈が残る)。 → 類型ごと降格 (個別ケースでなく類型全体を warning 化)。 ただし FN 優先で、 該当候補は漏れなく warning で拾う (= 網羅検出ルールは TP 志向で広く張る)。 決定は人、 しかし人の目に乗せる対象は見逃さない。
集合知ループ接続: 散らばりをどう処理したか (述語追加で解いた / 類型降格した) を記録すれば、 「人と datalog の分業境界」 の学習データになる。 分離可能な散らばりの解法は組織の監査ノウハウ、 分離不能な類型は「ここから先は人間」 という境界そのものが資産化される。
datalog-debug-skill §5 と 整合:
datalog_testrecord_propose) では 上記 1-5 の 観点 を カバー した 集合 を 提案statuteDescription (= 法的根拠 Markdown、 #406) も 同時に 埋めるstatuteDescription (audit query 側、 #396) と TestCase 側 を 対応させて 「この test が 検証する 法 / 規程」 を 明示datalog_testrecord_request_change 経由で 提案overtime_36agreement) の test set監査クエリ: 法定外労働 > 2700 分/月 を error として 検出。
| TestCase | tags | datoms (= 月次勤怠) | expected |
|---|---|---|---|
| ぴったり 45 時間 → 拾わない | boundary, happyPath |
excess_statutory_mins = 2700 | not-hit |
| 45 時間 + 1 分 → 拾う | boundary |
excess_statutory_mins = 2701 | hit |
| 大幅超 60 時間 → 拾う | boundary |
excess_statutory_mins = 3600 | hit |
| 法定外労働 属性 不在 → 拾わない | nullHandling |
excess_statutory_mins = null | not-hit |
| 特別条項 適用 (= 別 ルール) → 拾わない | edgeCase |
excess_statutory_mins = 4800、 special_clause = true | not-hit |
→ 5 件 で 「上 / 境界 / 下 / null / 例外」 を カバー。
high_amount_no_receipt) の test set監査クエリ: amount ≥ 30000 円 で has_receipt = false を warning として 検出。
| TestCase | tags | datoms (= deal) | expected |
|---|---|---|---|
| 30000 円 + 証憑 なし → 拾う (境界) | boundary |
amount = 30000, has_receipt = false | hit |
| 30001 円 + 証憑 なし → 拾う | boundary |
amount = 30001, has_receipt = false | hit |
| 29999 円 + 証憑 なし → 拾わない | boundary, happyPath |
amount = 29999, has_receipt = false | not-hit |
| 30000 円 + 証憑 あり → 拾わない | happyPath |
amount = 30000, has_receipt = true | not-hit |
| 100000 円 + 証憑 なし → 拾う (= 大幅超) | edgeCase |
amount = 100000, has_receipt = false | hit |
→ 5 件 で 「境界 ぴったり / +1 / -1 / 証憑 切替 / 大幅超」 を カバー。
taxable_purchase_no_partner) の test set監査クエリ: 課税仕入 明細 + 取引 / 明細 どちら にも 取引先 紐付け 無し で warning。
| TestCase | tags | datoms (= deal + detail + partner) | expected |
|---|---|---|---|
| 課税仕入 + 取引先 紐付け 無し → 拾う | boundary |
detail.tax = 'taxable_purchase', deal.partner = null, detail.partner = null | hit |
| 課税仕入 + 取引 自体 に 取引先 → 拾わない | multiEntity |
detail.tax = 'taxable_purchase', deal.partner = partner_1, detail.partner = null | not-hit |
| 課税仕入 + 明細 自身 に 取引先 → 拾わない | multiEntity |
detail.tax = 'taxable_purchase', deal.partner = null, detail.partner = partner_1 | not-hit |
| 課税仕入 2 件 (1 件 紐付け / 1 件 無し) → 1 件 拾う | edgeCase, multiEntity |
detail_1.partner = partner_1, detail_2.partner = null | hit (detail_2 のみ) |
| 課税売上 で 取引先 無し → 拾わない (= ルール 範囲外) | happyPath |
detail.tax = 'taxable_sales', deal.partner = null | not-hit |
→ 5 件 で 「主条件 / 取引 経由 補完 / 明細 経由 補完 / 部分 hit / ルール 範囲外」 を カバー。
datalog_testrecord_propose 呼出し 時)datalog_testrecord_propose({
testCaseId: 'tc_xxx',
proposal: {
// datoms = 試行 input (= 必要 entity を 揃える)
datoms: [
[{ 'node/id': 'work_record_1', 'attr/excess_statutory_mins': 2701, ... }],
// ... 関連 entity も 含める
],
// expectedRows = 期待 出力 (= 該当 query を 走らせた 結果)
expectedRows: [['work_record_1']], // positive
// expectedRows = [] // negative
// kind: 'positive' | 'negative' は TestCase header 側
statuteDescription: '...', // #406 連動、 法的根拠 Markdown
},
});
複数 test (= boundary 揃い) を 提案する 場合、 1 件 ずつ propose を 呼ぶ。 タグ + statuteDescription で 「なぜ この case か」 を 明示。
test は 最終 e2e (= freee 投入 + 実走行) まで が 完成形 だが、 段階を 追って runStatus で 進捗を 表現 できる。 LLM は 1 phase 終了 ごとに status を 明示更新 して、 user / 別 LLM が 続きを 引き継げる ように する。
| Phase | 出力 | TestCase.runStatus | TestRecordV2 |
|---|---|---|---|
| 1. 概要 設計 | TestCase header (= title / userDescription / why / statuteDescription / kind / tags) | test_design_pending (7) |
0 件 |
| 2. 期待 列挙 | hit 対象 row (= nodeKey + expectedKind={TP,TN} + label + userDescription) + 関連背景 row (= nodeKey + expectedKind=outOfScope) | test_design_pending (7) or failing (20) |
N > 0 件、 datoms 任意 |
| 3. データ詰め | datoms (= 走行入力 fact)、 業務期待値 (= businessExpectation。 LLM は draft ordinal のみ書込可) | passing (30) |
datoms あり |
| 4. e2e | freee 投入 (= freeeReqBody / freeeKind) → 実走行 → resultKind 観測 | e2e_passing (40) |
freeeReqBody + lastSyncedAt あり |
runStatus = test_design_pendingquery_pending (5) で 表現statuteDescription を 埋める (= #406 / datalog-debug-skill 参照)userDescription (= 一覧 UI の「業務向け詳細」) は 検証主旨 (what) を業務語で書く。
投入手順 (「明細 を 2 行 登録 します…」) は 書かない (= 投入 データ は 子 TestRecord の label/userDescription 側)。
pyramidLevel で 書き分け: unit / integration は what のみ (例: 「勘定科目 が 紐付いて いない 仕訳明細 を 検出 できる こと」)、
scenario は why (業務リスク・背景) + what (例: 「課税仕入 の 付け間違い は 利益 と 消費税額 を 歪める ため、 収入取引 に 紛れた 課税仕入明細 を 検出 できる こと」)。datoms は この phase で 入れても 入れなくても OK。 入れない 場合は runStatus = test_design_pending 維持datoms を 詰めて 走らせる (= datalog_testrecord_run)
業務期待値 (businessExpectation) を 並走して 入れる。 #574 で 3 値文字列 (issue / no_issue / needs_review) を 廃止 し、 int ordinal の 階層 enum に 変更:
| ordinal | name | label | 誰が 入れる |
|---|---|---|---|
0 |
no_issue_draft |
正常判定 (LLMドラフト) | MCP (= LLM) |
10 |
issue_draft |
違反検知 (LLMドラフト) | MCP (= LLM) |
20 |
no_issue |
正常判定 | GUI user / seed |
30 |
issue |
違反検知 | GUI user / seed |
issue (30) / no_issue → no_issue (20))。 MCP boundary は draft 以外 を throw (assertMcpWritableBusinessExpectation) する ので、 LLM が datalog_testrecord_propose 等 で 入れる 値 は 必ず draft ordinal (0 or 10)。 user が GUI で 確定 (20 / 30) に 昇格 する。解離検知: `expectedKind` ↔ `businessExpectation` が 矛盾 (= 「datalog 拾わない / 業務 issue 系」 or 「datalog 拾う / 業務 no_issue 系」) なら audit query の 限界 を 認知。 businessExpectedReason で 根拠 を 書き、 reviewEscalation を 'llm' (= LLM 上位 review) or 'expert' (= 専門家 review) に 設定
解離 は bug でなく 正常 経路 (= 「audit は 安価 自動、 解離 case を 上位 高価 調査 に 流す」 戦略)
datalog_testrecord_run で freee 投入 を 実行resultKind 観測 → 期待 と 一致 なら runStatus = e2e_passing1 TestCase 内 で hit row + non-hit row が 混在 する 場合 (= 同 deal の 3 明細 で 2 件 hit / 1 件 non-hit 等)、 TestCase.kind = 'mixed' を 使う:
expectedKind を 個別 設定例 1: 解離 → expert review
// 同 取引、 法定外労働 = 2700 分 (= 45h ぴったり) で 「拾うべきでない」 (= datalog)
// しかし 業務上 は 「同一従業員 が 2 ヶ月 連続 45h ぴったり → 健康福祉 確保 対象」 (= 業務 issue)
{
expectedKind: 'trueNegative', // datalog 上 は 拾わない
businessExpectation: 10, // issue_draft (= 違反検知 LLMドラフト)。 LLM は draft ordinal のみ。 user が GUI で 30 (issue 確定) に 昇格
businessExpectedReason: '2 ヶ月 連続 45h ぴったり は 健康福祉 確保 措置 対象 (= 80h 検知 ルール の 範囲外)',
reviewEscalation: 'expert', // 産業医 / 人事 部 へ
}
→ audit query は 「45h+1min から 拾う」 の 1 軸 だけ、 「連続 月数」 軸 は 別 query で 補う 必要。 解離 を 明示 すれば 「次の audit 設計」 ヒント に なる。
例 2: 解離 → llm review
// 課税仕入 + 取引先 紐付け 無し → 拾う (= datalog)
// しかし 業務上 「現金取引 で 3 万円 未満 だから 業務 issue でない」 (= 経過措置)
{
expectedKind: 'truePositive', // datalog 上 は 拾う
businessExpectation: 0, // no_issue_draft (= 正常判定 LLMドラフト)。 LLM は draft ordinal のみ。 user が GUI で 20 (no_issue 確定) に 昇格
businessExpectedReason: '現金 取引 + 3 万円 未満 (= 帳簿要件 緩和)',
reviewEscalation: 'llm', // LLM が 上位 ルール で 仕分け
}
→ audit query は 「課税仕入 + 取引先 無し」 で 一律 拾う、 「現金 + 3 万円 未満」 例外 は 別 query (= 緩和ルール) で 否定。
dataSource='freee_e2e' のみ 必須 (= #436 / #535)testRecords[] (= freee に 投入 すべき record の 集合) は 親 TestCase の dataSource 次第:
dataSource='freee_e2e': testRecords[] を 必ず 並べる (= freee 実投入 → e2e 走行 が 完成形)。dataSource='datom_only': testRecords[] は 不要 (= freee に 作れ ない ゆえ datom 単体 で 完結。 §0 の e2eBlockedReason を 明示し、 passing が terminal green)。旧来 の 「常時 必須 (#436)」 は #535 で freee_e2e 限定 に 改められた (= 純論理 IDB rule test 等 は datom_only で testRecords ゼロ が 正)。
詳細 雛形 / 文体 規約 / recordKind 振り分け 規則 は freee-api-test-record-skill を 参照 (= 役割分担: 本 skill は 「何を test するか」、 freee-api-test-record-skill は 「freee_e2e の とき freee 投入 record を 全部 並べる」)。
各 record の label / userDescription は freee UI 用語 限定。 attr/* / instance / = 等 の 内部 略記 禁止。
audit / testcase / testrecord の結果を user に示すときは、 テキスト説明だけで完結させず対応する GUI URL リンクをセットで提示する。 MCP 出力 (*_list / *_get / audit 実行結果) に自動付与される links (#601) をそのまま使い、 種別→画面のマッピングと host 解決は logic-solver-mcp-ui-nav §3.1.1 に従う (= URL ハードコード禁止)。