0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ServiceNowのData Management機能をMCPに追加してみた

0
Posted at

はじめに

自作のServiceNow MCPサーバー(tedorigawa001/ServiceNow-MCP、npm公開中)に、Data Management(アーカイブ・破棄・クリーンアップ)のツール群を13本追加しました。

このモジュールが扱うのは「レコードを永久に消す」操作です。
ミスが即データ消失に直結するため、実装段階から安全設計にかなり気を使いました。

Data Managementとは

ServiceNowの標準機能で、テーブル単位にルールをまとめる以下の論理構造を持ちます。
なお、sys_archivesys_auto_flushsys_dm_policy を直接参照する物理的な親子関係ではありません。
MCPでは対象テーブル名を使って、有効なPolicyの存在を照合しています。

  • Policy: どのテーブルを対象にするかを宣言する土台
  • Archive Rule: 条件に合うレコードをライブテーブルからアーカイブテーブルへ移動(可逆)
  • Destroy Rule: アーカイブ済みデータを一定期間後に完全削除(不可逆)
  • Cleanup Rule(Auto Flush): アーカイブを経由せず、ライブテーブルのレコードを直接削除(不可逆)

安全設計

設計方針としては、作成系ツールは常に非アクティブなルールしか作らない
つまり create_archive_rulecreate_destroy_rule を呼んでも、その時点では何も起きません。
実際にデータが動く/消えるのは、別途 set_*_active を呼んで明示的に有効化した時だけです。

const created = await client.createRecord('sys_archive_destroy', {
  // ...
  active: false, // 常に非アクティブで作成
});

有効化には2段階のゲートを掛けています。

  1. 確認フレーズの一致: confirmation: "I_UNDERSTAND" を渡さないと拒否
  2. 対象テーブルにアクティブなPolicyが存在すること: 土台となるPolicyがアクティブでない状態でルールだけ動かせてしまう事故を防ぐ
async function assertActivePolicyForTable(client: ServiceNowClient, table: string): Promise<void> {
  const policies = await client.queryRecords({
    table: 'sys_dm_policy', query: `tablename=${table}^active=true`, limit: 1,
  });
  if (!policies.records.length) {
    throw new ServiceNowError(`An active Data Management Policy is required before activating a rule for ${table}.`, 'CONFLICT');
  }
}

実機検証で見つけたバグの話: チェック漏れ

Archive・Destroy・Cleanupの3種類の set_*_active ツールがある中で、コードを一通り読んだ時点では気づきませんでした。
全部似た形をしているので、目視レビューでは「たぶん同じパターンだろう」と読み飛ばしてしまいがちです。

そこで実際にPDI上で、**「架空のテーブル向けに、Policyを非アクティブなまま、Destroy Ruleだけを確認フレーズ付きで有効化してみる」**というテストをしました
(アーカイブ済みデータが存在しない架空テーブルを使ったので、万一通ってしまっても実害はゼロという設計にしてあります)。

[OK] set_archive_rule_active: ポリシー非アクティブ → 正しく拒否
[!!] set_destroy_rule_active: ポリシー非アクティブでも有効化できてしまった

set_destroy_rule_active にだけ assertActivePolicyForTable の呼び出しが無かったのです。3つとも同じ見た目の関数なのに、1つだけガードが抜けている——典型的な「コピペ実装の抜け漏れ」パターンでした。

修正: 参照を辿ってPolicyまでたどり着く

Destroy RuleはArchive Ruleに archive フィールドで紐づいており、テーブル名を直接持っていません。そこで参照を1段解決するヘルパーを追加しました。

function referenceSysId(value: unknown, field: string): string {
  if (typeof value === 'string') return requireSysId(value, field);
  if (value && typeof value === 'object' && 'value' in value) {
    return requireSysId((value as { value: unknown }).value, field);
  }
  throw new ServiceNowError(`${field} must contain a 32-character sys_id`, 'INVALID_REQUEST');
}
case 'set_destroy_rule_active': {
  // ...
  if (args.active === true) {
    const destroyRule = await client.getRecord('sys_archive_destroy', sysId);
    const archiveRule = await client.getRecord('sys_archive', referenceSysId(destroyRule.archive, 'destroy rule archive'));
    await assertActivePolicyForTable(client, requireTableName(archiveRule.table, 'archive rule table'));
  }
  // ...
}

referenceSysId はREST APIの参照フィールドが「文字列」と「{value, link} オブジェクト」の2形態で返ってくる曖昧さを吸収しつつ、最終的に32桁sys_idの正規表現で厳格に検証します。ここでも自由入力をそのままクエリに埋め込まない、という一貫した方針を守っています。

修正後、同じテストを再実行すると期待通り拒否されました。

さらに、この拒否経路を再現する単体テストを追加しました。以後は、親Archive Rule経由で有効Policyを解決する処理が抜けると回帰テストで検出できます。

[OK] set_destroy_rule_active もポリシー非アクティブで拒否:
     An active Data Management Policy is required before activating a rule for x_mcp_dm_fix_669222.

もう一つの実機発見: Cleanup Ruleは架空テーブルを許さない

余談ですが、検証中に別の非対称性も見つかりました。sys_dm_policy.tablenamesys_archive.table は存在しないテーブル名(x_mcp_dm_verify_xxxxxx)でもレコード作成を受け付けてくれましたが、sys_auto_flush(Cleanup Rule)はPDIで作成を拒否されました。ServiceNow側のテーブルごとの実装差で、コード側の問題ではありません。実機を触らないと分からない類の挙動です。

まとめ

  • 「作成は無効状態で作成、有効化を明に求める」という安全設計自体はレビューで問題なく確認できた
  • しかし3つの関数のうち1つだけガードが抜けている非対称バグは、コードを読むだけでは見つけられず、実際に危険な操作を(実害ゼロの条件で)実行してみて初めて発覚した
  • 破棄・クリーンアップ系のような不可逆操作を扱うツールは、ユニットテストのモックだけでなく、実機での「意地悪テスト」を組み込む価値が高い

ツールはnpmで公開済みです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?