メインコンテンツへスキップ
見出し画像

タスク管理モジュールの自動設定構成


    Incidentを1件保存すると、incident固有のビジネスルールだけでなく、taskレベルの共通ルールも同時に動きます。「先に動くのはどちらか」と聞かれると、多くの担当者は保存された順番までは意識しません。task直下の共通ビジネスルールを実機で確認すると、状態の管理はTaskStateUtilという1つの共通処理が63の子テーブル全部を相手にしていました。一方でcloseに関わる一部の処理は、コードの中でincidentとproblemを名指しして除外しています。Australiaの PDI をデフォルト設定のまま開き、task層とincident層が保存1回でどう重なるかを確かめました。

    検証環境:Australia PDI / OOTB / 2026-09-01 確認

    たどる範囲

    • task共通のビジネスルールが、いつ・どんな順番で動くか

    • TaskStateUtilが辞書上書きの3属性をどう読み、どの処理に使われるか

    • incidentを1件保存したとき、task層とincident層のルールがどう重なるか

    • UIアクションと、通知1件のevent_name不整合

    分かったこと

    • task直接のビジネスルールは書き込み後(after)が最多で、他テーブルへの同期・連携が主な役割

    • TaskStateUtilのrunMarkClosed()は、incidentとproblemをコードで名指しして除外している。task共通のclose処理はこの2大モジュールでは動かない

    • 通知はcollection=taskで登録されているが、実際にイベントを発行するビジネスルールはvtb_taskだけに限定され、他の子テーブルでは発火しない

    task共通ビジネスルールが動くタイミング

    task直接のビジネスルールはアクティブ33件です。稼働タイミングを表すwhenという項目で分けると、書き込み後に動くafterが15件でもっとも多く、書き込み前に動くbeforeは9件でした。フォーム表示前に動くbefore_displayは4件、非推奨の実行方式であるasyncは5件です。


    画像


    画像


    画像

    Incident単体のビジネスルールではbeforeが24件と最多でした。task共通層の主役はafterです。他のテーブルへの値のコピーやイベントの発行など、書き込みが終わってから動く連携処理が中心的な役割になっています。

    主要な共通ビジネスルールの処理内容

    代表14件をorder順に見ます。


    画像


    画像

    いくつかを詳しく見ます。

    Task Active State Managementがactiveを自動更新する

    条件はcurrent.state.changes() && !current.instanceOf('dmn_demand')です。stateが変わるたびに動きます。処理はTaskStateUtilからclose_states・default_work_state・default_close_stateという3属性を読み取る仕組みです。非アクティブへ変わればactiveをfalseにし、アクティブへ戻ればtrueに書き換えます。

    このルールは63の子テーブルすべてに対して同じコードで動きます。個別テーブルごとに「stateが閉じたらactiveを落とす」処理を書く必要が無くなる、task共通層の中心的な仕組みです。3属性の一覧は本シリーズ第2巻で扱っています。

    Populate Assignment Group based on CI/SO(before / order100)

    incident・problem・change_requestの3テーブルに限定した条件です。CIまたはサービスオファリングが変わったとき、担当グループを自動で補います。task共通層に置かれていますが、実行対象はコードの条件式で絞り込まれています。

    Cancel Workflows Upon Cancellationのテーブル除外条件

    stateがClosed Incomplete(4)へ変わったとき、そのレコードに紐づくワークフローを取り消します。条件式は次の通りです。

    (current.getTableName() != 'sysapproval_group') && (current.getTableName() != 'rm_release')
      && (current.getTableName() != 'incident') && (current.getTableName() != 'problem')
      && (current.state.changesTo(4))

    incident・problem・sysapproval_group・rm_releaseの4テーブルを名指しで除外しています。これらのテーブルは自前のキャンセル処理を持つため、task共通層のこの処理からは外されています。

    Stamp Approvals(before / order1,001)/Set Closure Fields(before / order10,000)

    approvalが承認・却下に変わると、approval_setへ現在時刻を記録します。Set Closure Fieldsはactiveがfalseに変わったとき、closed_by・closed_atが空ならその場で埋めます。どちらも承認・クローズという業務の節目を、task共通層で最後に整える処理です。

    Run SLAs(after / order101)/Process SLAs(async / order101、非推奨)

    どちらもSLA処理を担いますが、使い分けの基準はプロパティcom.snc.sla.engine.versionの値です。2011ならRun SLAs、2010ならProcess SLAsが動きます。SLAの時計そのものの読み方は既存記事にまとまっています。

    TaskStateUtilが子テーブルの辞書属性を読む仕組み

    task直接のビジネスルールのうち、close処理に関わる4件はTaskStateUtilというScript Includeを呼び出しています。対象はTask Active State Management・mark closed・task closer・task reopenerの4つです。個々のビジネスルールが状態遷移のロジックを持つのではなく、1つの共通クラスが判定を引き受ける設計です。


    画像


    画像

    TaskStateUtilは初期化時に、対象レコードのstateフィールドから3つの属性を読み込みます。

    ATTR_INACTIVE_STATES : "close_states",
    ATTR_DEFAULT_WORK : "default_work_state",
    ATTR_DEFAULT_CLOSE : "default_close_state",
    SYSTEM_DEFAULT_CLOSE : 3,
    SYSTEM_DEFAULT_WORK : 2,
    SYSTEM_INACTIVE_STATES : [3, 4, 7],

    属性が子テーブルの辞書に設定されていなければ、task自身の基底値(3・2・[3,4,7])を使います。設定されていれば、その値を優先します。

    incidentのstate上書きをTaskStateUtilが読むと

    incidentのsys_dictionary_override(element=state)を開くと、attributesはclose_states=7;8,default_work_state=2,default_close_state=7,ignore_filter_on_new=trueでした。


    画像

    incidentのstate選択肢は1=New/2=In Progress/3=On Hold/6=Resolved/7=Closed/8=Canceledの6値です。close_statesが指すのは7と8だけで、6(Resolved)は含まれません。TaskStateUtil.isStateInactive()はこの属性を参照するため、incidentがResolvedへ変わってもactiveはtrueのままです。Closedへ変わって初めてfalseになります。Resolvedの案件が一覧の既定フィルタ(active=true)に残り続けるのは、この辞書1行の設定が理由です。

    mark closedがincident・problemだけ除外される理由

    task直接の「mark closed」(before / order800)を開きます。条件はnew TaskStateUtil(current).runMarkClosed()です。処理はcurrent.active = false;の1行だけでした。


    画像

    runMarkClosed()のソースコードを読むと、ignoreTablesという配列に'problem'と'incident'が直接書かれています。

    var ignoreTables = ['problem', 'incident'];
    for (var index2 = 0; index2 < ignoreTables.length; ++index2)
        if (this.task.sys_class_name == ignoreTables[index2])
            inSupportedTable = false;

    incidentの保存ではこの条件が常にfalseを返し、current.active = false;は実行されません。incidentには自分専用のmark_closed(before / order100)があり、activeに加えてclosed_by・closed_atも設定しています。task共通層の同じ代入をもう一度実行する必要が無いため、コード側で明示的に外していると読めます。

    実際の順序はもう少し重なります。order50のTask Active State Managementが先にcurrent.active = falseを書き込みます。close_states(7;8)にstate(7)が含まれるためです。order100のincident固有mark_closedが同じ代入をもう一度行い、closed_by・closed_atを追加します。1回のClose保存で、activeへの代入は2つの独立したルールから行われている計算になります。2回目の代入は冪等で実害はありませんが、責務としては重複したままです。

    task closer(order900)・task reopener(order901)はincident・problemを除外していません。ただし通常のClose操作では、この2つが呼ばれる時点で既にstateがclose_statesに含まれているため、判定条件が成立せず実行されません。stateを動かさずactiveだけを直接書き換えるような、通常の画面操作から外れた更新経路でのみ働く安全網です。

    task層とincident層のビジネスルールが重なる順序

    ここまでの内容を、incidentを1件Closeする保存1回に当てはめると、次の順序になります。


    画像

    before側は、incident固有のCopy State to Incident State(order0)から始まります。続くorder50では、task共通のTask Active State Managementが動く番です。order100では、incident固有のmark_closedと、task共通のreassignment counterなどが交互に並びます。task共通のmark closed(order800)は前述のとおり実行されません。task closer・task reopener(order900〜901)も通常は条件が成立しません。最後にincident固有のCopy Incident State to State(order1,000,000)が動き、DBへ書き込まれます。

    after側では、order50にincident固有のincident eventsが動きます。これは通知の起点となるイベント発行です。同じorder50には、task共通のmetrics events・live feed eventsも並びます。order100では、incident固有のCascade closure of Incident Tasks等と、task共通のSync CI with Affected CIs等が同居します。

    before・afterともに、order値が同じ場合の実行順序はServiceNow側で保証されません。task層とincident層のルールが同じorderに重なるのは偶然で、両者が別々に開発された結果です。自社でルールを追加するときは、既存のorder帯(0・50・90・100・200・800・900〜901・1,000,000)を避け、他と重ならない値を選びます。

    UIアクションの代表的な条件と処理

    task直接のUIアクションは26件です。ボタン名で数えると重複が目立ちます。Assign to me・Repair SLAs・Add to Visual Task Board・Follow on Live Feedは、いずれも複数レコードに分かれています。フォーム・一覧・コンテキストメニュー・Polaris UIの有無など、表示形態ごとに別レコードを持つためです。26件をボタン名でまとめ直すと15種類になります。Add to Visual Task Boardだけで5件登録されています。同じボタンをリストの単一行・複数選択・右クリックメニュー・フォーム上のそれぞれに、独立して出す必要があるためです。1つの機能に対して用意する入り口の数だけ、レコードが増える仕組みになっています。


    画像


    画像

    条件式を見ると、26件中の一部はgetRecordClassName()で対象テーブルを絞り込んでいます。Create Outageはincident・problemだけに表示され、Verify Proposed Changesはchange_request・change_taskだけに表示されます。taskに置けば63テーブル全部にボタンが出るわけではなく、汎用の枠の中に個別テーブル向けの条件分岐を埋め込む設計です。

    Assign to meのようにaction_nameが違う2つのレコード(task_assign_to_meとassign_to_me)が、同じボタン名で存在する例もあります。標準の実装とBest Practiceプラグイン由来の実装が並存しているためで、条件式もcurrent.canWrite()とSOWITSMCommonUtils().canAssignToMe(current)で異なる書き方です。Create Outageの一部にはロール条件(itil・sn_problem_write・sn_incident_write)も付いています。

    通知のevent_name不整合

    task直接の通知(sysevent_email_action、collection=task)は1件だけです。「Additional Assignee List changed」という名前で、event_nameはvtb_task.previous.additional_assigneeでした。


    画像

    vtb_taskはtaskの直接の子(Private Task)で、実在するテーブルです。ただし、このイベントを実際に発行するビジネスルール「VTB Previous Additional Assignees」は、collection=vtb_taskで定義されています。

    function onAfter(current, previous) {
        gs.eventQueue("vtb_task.previous.additional_assignee", current, sendTo, current.sys_id);
    }


    画像

    通知の定義側はcollection=taskで登録されており、63テーブルすべてが対象範囲に見えます。しかし、イベントを発行するビジネスルールはvtb_taskという1テーブルにしか存在しません。incidentやproblemを保存しても、このイベント名がキューへ積まれることはありません。vtb_taskは本PDIのデモデータで0件です。通知の対象範囲(task全体)と、実際に発火しうる範囲(vtb_taskのみ)が一致していない状態で、この通知は現状のデモデータでは1件も動いていません。

    クライアントスクリプト・UIポリシー・データポリシーの少なさ

    画面側の制御に目を移すと、task直接のクライアントスクリプトは5件で、すべてonLoadです。onChange・onSubmitは0件でした。UIポリシーは「Make assignment group mandatory」という1件だけで、条件無しに発火し、全task派生フォームでassignment_groupを必須にします。Data Policy(model_table=task)は0件です。

    インシデント管理モジュールの自動設定構成では、incident単体でクライアントスクリプト18件・UIポリシー15件・Data Policy1件が確認されています。taskが用意するのは「担当グループだけは必ず入れる」という最低限の縛りだけです。優先度に応じた必須化や状態別の表示切り替えといった細かい制御は、子テーブル側が個別に積み上げる設計になっています。

    実機で気づいたこと

    mark closedがコード上でincident・problemを名指しで除外する

    TaskStateUtil.runMarkClosed()のignoreTablesは['problem', 'incident']という2値だけの配列でした。コメントによる説明は付いていません。

    task共通の「mark closed」(order800)がこの2テーブルで動くと、order50・order100で設定済みのactiveへ、3回目のcurrent.active = falseを重ねて書くだけになります。実害は無くても無駄な代入です。汎用処理の中に、主要な利用先だけを対象外にする分岐を直接書き込む設計は、Cancel Workflows Upon Cancellationのincident・problem・sysapproval_group・rm_release除外にも共通するパターンでした。

    自社でtask継承の新しいテーブルを作るとき、close処理を自作する場面を考えます。まず見るべきはTaskStateUtilの3つの関数(runMarkClosed/runTaskCloser/runTaskReopener)です。ignoreTablesにテーブル名が入っていなければ、汎用処理に任せられる範囲が広がります。

    Resolvedでactiveを落とさない判断が辞書の1行にある

    incidentのstate辞書上書きのclose_statesは7;8だけで、6(Resolved)を含みません。default_work_stateは2です。default_close_stateは7です。

    「解決済みだが未クローズ」という状態を一覧の既定フィルタ(active=true)に残したいという設計判断は、ビジネスルールのスクリプトではなく辞書のattributes欄1行に集約されています。TaskStateUtilの中身を読まなければ、この判断がどこにあるのか気づきにくくなっています。

    自社で「この状態になったら一覧から消えるか」を確認したいときは、ビジネスルールを1つずつ追うより先に、対象テーブルのstate辞書上書きのattributes欄を見ます。close_statesに含まれるかどうかで即断できます。

    通知の対象範囲と発火元の範囲が別々に決まっている

    通知定義はcollection=taskで登録され、63テーブル全体が対象に見えます。イベントを発行するビジネスルールはcollection=vtb_taskだけで定義されています。

    通知を作る担当者とイベントを発行するビジネスルールを書く担当者が別だった場合、双方のcollectionをそろえる仕組みはServiceNow側にありません。通知一覧に「collection=task」と表示されているだけでは、実際にどのテーブルから届くのかは分かりませんでした。

    自社で似た構成の通知を作るとき、通知定義のcollectionだけを見て対象範囲と判断しません。event_nameを検索し、実際にgs.eventQueue()を呼んでいるビジネスルールのcollectionまで確認します。

    実務で順番に検討する箇所

    自社のルールをtask継承のテーブルに追加するとき、次の順序で確認します。


    画像
    1. 既存の把握:order順に並べ、自社ルールを差し込む位置を決める

    2. task共通かテーブル固有か:TaskStateUtilの呼び出し元でignoreTablesの有無を確認する

    3. before・afterどちらに置くか:値の補正はbefore、他テーブルの同期はafterに寄せる

    4. state値と辞書上書き属性:close_states・default_work_state・default_close_stateを子テーブル側で確認する

    5. 重複していないか:同じ代入を複数のビジネスルールが行っていないか、orderの並びで確かめる

    6. 通知の発火元:event_nameを発行するビジネスルールのcollectionが、通知定義のcollectionと一致するか確認する

    7. UIアクションの表示条件:getRecordClassName()等の分岐で自テーブルが対象に入るか確認する

    7つのうち、影響が大きいのは2番目です。task共通層に処理を追加すると63テーブルすべてに効きます。ignoreTablesのようなテーブル除外の仕組みを用意しておかないと、後から特定のテーブルだけ挙動を変えたくなったとき、汎用処理そのものを書き換える必要が出ます。追加したルールを本番へ運ぶ手順は、Update Setの構造を押さえておくと迷いません。

    用語

    • OOTB:Out of the Box。プラグイン追加やカスタマイズを行っていない、製品のデフォルト状態

    • PDI:Personal Developer Instance。ServiceNowが無償で提供する個人検証用インスタンス

    • TaskStateUtil:状態遷移の判定・辞書属性の読み取りをtask系全テーブル共通で担うScript Include

    • ignoreTables:TaskStateUtilの各関数が持つ、処理対象から外すテーブル名の配列

    • 辞書上書き(Dictionary Override):sys_dictionary_override。継承した項目の設定を子テーブルだけで変更する仕組み

    検証範囲とおことわり

    • 検証環境:Australia PDI / OOTB / 2026-09-01 確認

    • 件数はすべてOOTBの定義の数です。本番環境やカスタマイズ済み環境では異なります

    • ビジネスルールの実行順序はorder値とスクリプトの条件式から論理的に整理したものです。実際にレコードを保存してログで発火順序を観測する検証は行っていません。実測の数え方はSN-Platform-215(problemテーブル)を参照してください

    • 33件のうち精読したのは代表7件です。残りは名前・条件・orderのみの確認にとどまります

    • 非アクティブ2件(Set Closure Fields After Workflows/Update Prediction Results)の中身は確認していません

    • state値・辞書上書き属性の一覧は本シリーズ第2巻、権限判定は第3巻、プロパティの値は第4巻で扱っています

    付録:自分の環境で確認するときのURL

    • task直接のビジネスルール:/sys_script_list.do?sysparm_query=collection=task^active=true^ORDERBYorder

    • タイミング別の件数:/sys_script_list.do?sysparm_query=collection=task^active=true^GROUPBYwhen

    • TaskStateUtil本体:/sys_script_include_list.do?sysparm_query=name=TaskStateUtil

    • incidentのstate辞書上書き:/sys_dictionary_override_list.do?sysparm_query=name=incident^element=state

    • UIアクション:/sys_ui_action_list.do?sysparm_query=table=task^active=true

    • 通知:/sysevent_email_action_list.do?sysparm_query=collection=task^active=true

    • vtb_task直下のビジネスルール:/sys_script_list.do?sysparm_query=collection=vtb_task

    関連記事

     
     
    ServiceNowのPDI(Personal Developer Instance)を実機で観察して、Noteに記事を書いています 実装・運用・教育の現場視点で淡々と記録中です リクエストがあればその記事も書きます 個人の観察日記です

    あなたへのおすすめ