付録E. SoS Contract DSL拡張(sos-dsl 0.1)
本付録は,CADLのSoS Contract DSL拡張を規定する。この拡張は,
付録A.4の生成規則
contract_def に2つのキー lifecycle: と monitors: を追加し,
契約の実行状態と実行時の観測を,言語の第一級の構成要素として記述できるようにする。
コア言語(第5章,付録A)は, これらのブロックを必須としない。 本拡張を実装していないCADL準拠の処理系は,新しいキーを理由にファイルを 拒否してはならない。新しいキーを構文上は受理し,情報レベルの診断を出すことが望ましい。
本拡張のバージョンは,コア言語とは独立に管理する。拡張の名前は sos-dsl であり,
本付録はバージョン 0.1 を記述する(リファレンス実装のソースでは,同じ版に
v0.1-sos-ext というラベルが付いている)。本拡張に依存するファイルは,
付録A.2の extensions: キーで
そのことを宣言することが望ましい。
sos:
...
extensions:
- sos-dsl: 0.1
E.1 動機
現在のCADLの契約は,規範的な合意のクラスを記述する。すなわち,当事者, A/G節,権限,情報の流れ,インセンティブのパラメータ,および違反検出のための 1つにまとめた述語を宣言する。SoS全体で何が成り立たなければならないかを 規定するには,これで十分である。しかし,次の点については意図的に何も述べていない。
- インスタンスごとの契約ライフサイクル。 「配送契約」は
DeliveryRequestごとに1つ生成され,いくつかの状態(Proposed → Assigned → Accepted → Delivering → Completed/Violated)を順に進む。クラスレベルの契約仕様では, このインスタンスごとの状態機械を表現できない。 - 宣言的な観測。
violation.detectは,観測の対象,サンプリングの方法, 検出の論理を,文字列で書いた1つの述語にまとめてしまう。何を, どの頻度で観測するかを,第一級の要素として宣言する手段がない。 - 規範に結び付いた期限。
protocol.timing.max_responseは,プロトコルの ステップの時間制約を表す。個々の契約インスタンスのあるステップに, 規範的な制約として期限を課すものではない。 - 可視化のための実行時イベントの発行。 ライフサイクルが明示されていないと, 下流のツール(CADL Explorerやランタイム)は,運用者が考える粒度で 契約のイベントを購読できない。
SoS-DSL拡張は,この4つの不足を,既存の定義に追加する形で補う。 既存の契約定義との後方互換性は保たれる。
E.2 概念上の階層
| 階層 | 既存のCADL | SoS-DSL拡張 |
|---|---|---|
| クラスレベルの規範仕様 | parties / assume / guarantee / authority / information / responsibilities / incentives / violation | 変更なし |
| インスタンスごとのライフサイクル | — | lifecycle: |
| 宣言的な観測 | 単一の violation.detect | monitors: |
| 規範に結び付いた期限 | timing による暗黙の指定 | ライフサイクル遷移の deadline: |
本拡張は,契約のための別の名前空間を導入しない。lifecycle: と monitors: は,
assume:,guarantee:,その他の付録A.4のキーをすでに受理している,
同じ contract_def のキーである。
E.3 構文(A.4へのEBNFの追加)
文法には付録Aの記法を用いる。コロンで終わる終端記号はYAMLマッピングのキーであり,
{ "-" , x } はYAMLシーケンスである。(* string *) と注記した生成規則は,
1つのYAML文字列スカラーの内容を記述する。lifecycle_block と monitor_def は,
A.4が参照している2つの非終端記号である。
lifecycle_block = "states:" , state_list ,
"initial:" , identifier ,
"terminal:" , state_list ,
[ "transitions:" , { "-" , lifecycle_trans } ] ;
state_list = { "-" , identifier } ;
lifecycle_trans = "id:" , identifier ,
"from:" , ( identifier | state_list ) ,
"to:" , identifier ,
"on:" , event ,
[ "when:" , rule ] ,
[ "deadline:" , duration_lit ] ,
[ "on_violation:" , on_violation_body ] ,
[ "emit:" , { "-" , text } ] ;
on_violation_body = "transition:" , identifier ,
[ "severity:" , severity ] ;
severity = "Minor" | "Major" | "Critical" ;
monitor_def = "id:" , identifier ,
"observe:" , ( observation | { "-" , observation } ) ,
[ "sampling:" , sampling_spec ] ,
"rule:" , rule ,
[ "on_match:" , on_match_body ] ;
on_match_body = [ "violation:" , identifier ] ,
[ "transition:" , identifier ] ,
[ "severity:" , severity ] ;
event = message_event | rule ; (* string *)
message_event = actor_ref , "->" , actor_ref , ":" , step_expr ;
rule = predicate ; (* string *)
membership = arith_expr , "IN" ,
"[" , arith_expr , { "," , arith_expr } , "]" ;
observation = member_access | identifier ; (* string *)
sampling_spec = "event"
| "periodic" , "(" , duration_lit , ")" ; (* string *)
identifier,actor_ref,duration_lit,step_expr,predicate,arith_expr,
member_access は付録A(A.1,A.5,A.10)で定義している。rule の中では,
membership がA.10の生成規則 comparison の選択肢として加わり,IN は予約語となる。
たとえば state IN [Assigned, Accepted] と書く。IN は,付録Aの小文字の in
(量化子と内包表記の定義域を導入する)とは別のキーワードである。
sampling: の既定値は event,severity: の既定値は Major である。
on:,when:,rule: の値,および observe: の要素のうち [,->,": " を
含むものは,E.6の例のように引用符で囲む必要がある。キー on は引用符を付けずに
書くことが望ましい。YAML 1.1のローダーは引用符のない on を真偽値として読むが,
そのようなローダーの上に作られた処理系も,これをこのキーとして認識しなければならない。
E.4 静的意味論
SoS-DSL拡張に対応する処理系は,既存の検査に加えて,次の規則を検査しなければならない。
- L-1.
lifecycle.initialの状態とlifecycle.terminalの各状態は,lifecycle.statesに現れなければならない。 - L-2.
lifecycle.transitions[*].fromと.toの各状態は,lifecycle.statesに現れなければならない。 - L-3.
lifecycle.terminalは少なくとも1つの状態を挙げなければならず, 初期状態から少なくとも1つの終端状態に到達可能でなければならない。 - L-4. 遷移に
deadline:を書くと,その遷移のfromの状態を有効範囲とするTimeoutイベントが暗黙に導入される。同じ状態から出る2つの遷移がともに期限を 持ってもよく,それらの意味は互いに独立である。deadline:を持つ遷移は,on_violation:ブロックも持つことが望ましい。遷移がdeadline:を持ちon_violation.transitionを持たない場合も,期限を過ぎることは違反である (重大度は,上書きしない限りMajor)が,状態は変化しない。sos-dsl0.1は,暗黙のTimeoutイベントをon:から参照する手段を定義していない。 - L-5.
lifecycle.transitions[*].on_violation.transitionの値は,lifecycle.statesに現れる状態の名前でなければならない(強制的な移動の 遷移先の状態である。たとえばViolated)。 - M-1.
monitor.observeの各要素は,宣言済みのアクターの属性 (たとえばROBOT[i].battery),メッセージ名,または予約済みの識別子timeとstateのいずれかでなければならない。 - M-2.
on_match.violationは,報告する違反(破られた規範。たとえばcollision)に付けるラベルとなる識別子である。ほかの場所で宣言されている 必要はない。省略した場合は,モニターのidをラベルとして用いる。 - M-3.
on_match.transitionの値は,同じ契約のlifecycle.statesに現れる 状態の名前でなければならない。この値は,強制的な移動の遷移先の状態 (たとえばViolated)であり,宣言された遷移のidではない。
したがって,on_violation: と on_match: のどちらにおいても,キー transition: の
値はつねに遷移先の状態の名前である。これは,E.6の例,IRのフィールド
on_violation_transition / on_match_transition(E.7),およびリファレンス実装の
Unity C#ジェネレータと一致する。このジェネレータは,そのような移動を,現在の状態から
指定された状態への <jump> として記録する。
rule の中では(したがって when: と on: のイベントの中でも),3つの識別子が
予約されている。state はインスタンスの現在のライフサイクル状態,now は現在時刻,
request はそのインスタンスを生成した要求(たとえば request.deadline)である。
ライフサイクルの状態の名前を単独で書いた識別子(たとえば Assigned)は,
その状態を表す。sos-dsl 0.1には,インスタンスを生成するトリガと request のフィールドを
記述する構文がない。インスタンスの生成と request の値の供給は,ホストアプリケーションが
行う(E.8を参照)。
モニターの observe: は,モニターが読む量の名前を挙げるものであり,IRに引き継がれる。
sos-dsl 0.1では,observe: は rule に書ける名前を制限しない。observe: の time は,
rule の中で now が読む時計を表す。
リファレンス実装(cadl 0.3)は,2つのブロックを構文解析してIR(E.7)に変換する。
cadl 0.3.10からは,cadl check が上記の規則を適用する。L-1,L-2,L-3,L-5,M-3への違反は
エラーとなる。初期状態のないライフサイクル,遷移やモニターのidの重複,rule: のないモニター,
on: の message_event に現れる未宣言のアクター,および識別子でない状態名,遷移のid,
モニターのid,違反ラベルも,同じくエラーとなる。cadl 0.3.11からは,M-1への違反もエラーとなる。
対象が宣言済みのアクターでない observe: の項目,およびアクターの属性でも名前でもない
observe: の項目がこれにあたる。cadl 0.3.10は,これを警告として報告していた。当時のE.6の例が,
アクター OBSTACLES を宣言しないまま OBSTACLES.positions を観測していたためである。
現在の例は DISPATCHER.obstacle_positions を観測し,診断なしで検査を通る。
次の2つは警告であり,ファイルは有効なままである。on_violation: ブロックのない deadline:
(L-4の「望ましい」),およびE.3の rule として読めない on:,when:,rule: である。
後者はテキストのまま保持される。単独の名前である observe: の項目は,メッセージ名と区別できないため受理される。
rule: と when: の中に現れるアクターは検査されない。cadl 0.3.7からは,cadl check が
Minor,Major,Critical 以外の severity: をエラーとして報告する。cadl 0.3.12からは,
duration_lit でない deadline:(負でない秒数は寛容な形式として受理する。付録A §A.12)と,
sampling_spec(またはA.12のマッピング形式)でない sampling: もエラーとして報告する。
それより前のリリースは,そのような deadline: を受理し,IRに期限を書かないか,yes / no や負の数では意味のない期限(yes,no,-5 に対してそれぞれ1000 ms,0 ms,-5000 ms)を書いていた。また,そのような sampling: を event として読んでいた。0.3.10より前のリリースは,規則L-1からM-3までを検査しなかった。
E.5 動的意味論(参考)
- 契約インスタンスは,初期トリガが発火したとき(たとえば
DeliveryRequestが 到着したとき)に生成される。sos-dsl0.1には,このトリガとrequestのフィールドを 記述する構文がない。インスタンスの生成とrequestの値の供給は,ホストアプリケーションが 行う(E.8を参照)。インスタンスはlifecycle.statesの状態を進む。 これを駆動するのは,lifecycle.transitions[*].onのイベント,期限のタイマー (インスタンスをon_violation.transitionの状態へ移す),およびmonitors[*].on_match.transitionのアクション(インスタンスを指定された状態へ移す) である。 - 遷移は,インスタンスがその
fromの状態のいずれかにあり,on:のイベントが発生し, かつwhen:のガードがあればそれが成り立つときに発火する。message_eventは, そのメッセージが送信されたときに発生する。イベントとして用いたruleは, それが真になったときに発生する。 - 遷移の
deadline:は,規範的な時間制約を定める。この時間は,インスタンスが その遷移のfromの状態のいずれかに入った時点から測る。それまでに遷移が 発火しなければ,違反となる。違反の重大度は,on_violation:ブロックで 上書きしない限りMajorであり,インスタンスはon_violation.transitionが 指定する状態へ移る。遷移がon_violation.transitionを持たない場合も, 期限を過ぎることは違反であるが,状態は変化しない。 emit:には,遷移の発火時に追加で発行するイベントの名前を列挙する。これらは 可視化ツールやログ記録ツールが利用する。リファレンス実装のジェネレータはemit:をIRに引き継ぐが,cadl0.3ではそれに基づく処理を行わない。emit:の有無に かかわらず,すべての遷移がライフサイクルイベントとして発行される。monitorsは,ライフサイクルの状態機械とは独立に評価されるが,rule:の中でstate(現在のライフサイクル状態)を参照できる。sampling: eventのモニターは, インスタンスに配送されるイベントごとに評価され,periodic(d)のモニターはdごとに評価される。規則が成り立つと,モニターはon_match.violationを ラベルとする違反を報告し,on_match.transitionがあれば,インスタンスを その状態へ移す。- 既存の
incentives.rulesと,拡張のmonitor.on_matchは連携する。本拡張によって 違反と遷移を名前で指せるようになり,報酬と罰則はそれらを参照する。
E.6 ロボット配送の例(抜粋)
次の契約は,cadl リポジトリの
examples/sos_dsl_robot_delivery.cadl から取ったものである。一部を省略しており,
契約の authority,information,incentives,violation の各ブロックと,
ファイルの残りの部分(actors:,metrics:)は示していない。
contracts:
- id: DELIVERY_SLA
parties: [DISPATCHER, "ROBOT[*]", "CUSTOMER[*]"]
assume:
- "ROBOT[i].battery > 20"
guarantee:
- "delivery_time <= 300s"
# === SoS-DSL拡張: インスタンスごとのライフサイクル ===
lifecycle:
states: [Proposed, Assigned, Accepted, Delivering, Completed, Violated, Terminated]
initial: Proposed
terminal: [Completed, Violated, Terminated]
transitions:
- id: assign
from: Proposed
to: Assigned
on: "DISPATCHER -> ROBOT[i] : route_assignment"
- id: accept
from: Assigned
to: Accepted
on: "ROBOT[i] -> DISPATCHER : ack(accepted)"
deadline: 5s
on_violation:
transition: Violated
severity: Major
- id: start_delivery
from: Accepted
to: Delivering
on: "ROBOT[i].status == InTransit"
- id: complete
from: Delivering
to: Completed
on: "ROBOT[i].status == Delivered"
when: "now <= request.deadline"
- id: late_failure
from: [Assigned, Accepted, Delivering]
to: Violated
on: "now > request.deadline"
# === SoS-DSL拡張: 宣言的なモニター ===
monitors:
- id: battery_guard
observe: "ROBOT[i].battery"
sampling: periodic(500ms)
rule: "ROBOT[i].battery < 20 AND state == Assigned"
on_match:
transition: Violated
severity: Major
- id: collision_watch
observe: ["ROBOT[i].position", "DISPATCHER.obstacle_positions"]
sampling: periodic(100ms)
rule: "min_dist(ROBOT[i].position, DISPATCHER.obstacle_positions) < 0.05"
on_match:
violation: collision
severity: Critical
- id: deadline_watch
observe: time
sampling: periodic(1s)
rule: "now > request.deadline"
on_match:
transition: Violated
severity: Critical
この例には,Terminated へ至る遷移がない。この状態は,網羅のために宣言している。
E.7 中間表現(IR)への追加
CADLシミュレータのIR(cadl リポジトリの
モジュール cadl.sim.ir)を拡張し,institution.contracts の下にある契約の
各IRオブジェクトに,フィールド lifecycle と monitors を追加する。その形は,
IRのデータクラスをそのままシリアライズしたもの(asdict())である。
IRは表層構文を正規化し,入れ子になった on_violation / on_match / sampling の
各ブロックを,親の名前を接頭辞とする平坦なフィールドに展開する
(これにより,フィールドを1つずつ読むジェネレータにとってJSONの形が安定する)。
{
"institution": {
"actors": [...],
"contracts": [
{
"id": "DELIVERY_SLA",
"parties": ["DISPATCHER", "ROBOT[*]", "CUSTOMER[*]"],
"assume": [...],
"guarantee": [...],
"governance": {...},
"violation_detect": "...",
"violation_action": "...",
"lifecycle": {
"states": ["Proposed", "Assigned", "Accepted", "Delivering",
"Completed", "Violated", "Terminated"],
"initial": "Proposed",
"terminal": ["Completed", "Violated", "Terminated"],
"transitions": [
{ "id": "assign",
"from_states": ["Proposed"],
"to_state": "Assigned",
"on": "DISPATCHER -> ROBOT[i] : route_assignment",
"when": null,
"deadline_ms": null,
"on_violation_transition": null,
"on_violation_severity": null,
"emit": [] },
{ "id": "accept",
"from_states": ["Assigned"],
"to_state": "Accepted",
"on": "ROBOT[i] -> DISPATCHER : ack(accepted)",
"when": null,
"deadline_ms": 5000,
"on_violation_transition": "Violated",
"on_violation_severity": "Major",
"emit": [] }
]
},
"monitors": [
{ "id": "battery_guard",
"observe": ["ROBOT[i].battery"],
"sampling_kind": "periodic",
"sampling_period_ms": 500,
"rule": "ROBOT[i].battery < 20 AND state == Assigned",
"on_match_violation": null,
"on_match_transition": "Violated",
"on_match_severity": "Major" }
]
}
]
}
}
IRへの変換時に行う正規化は次のとおりである。
| 表層構文 | 正規化後のIRフィールド |
|---|---|
from: Assigned(単一のid) | "from_states": ["Assigned"] |
from: [Assigned, Accepted] | "from_states": ["Assigned", "Accepted"] |
to: Accepted | "to_state": "Accepted" |
deadline: 5s | "deadline_ms": 5000 |
on_violation: { transition: V, severity: Major } | "on_violation_transition": "V","on_violation_severity": "Major" |
sampling: periodic(500ms) | "sampling_kind": "periodic","sampling_period_ms": 500 |
sampling: event | "sampling_kind": "event","sampling_period_ms": null |
on_match: { violation, transition, severity } | "on_match_violation","on_match_transition","on_match_severity" |
IRは,すべての既定値を補うわけではない。違反のラベル(on_match.violation)を省略すると,
IRには null が記録される。重大度のフィールドは,on_violation: または on_match: の
ブロック全体を省略したときに null となり,ブロックがあって severity: だけを省略したときは
Major が記録される。IRが null を記録している箇所に既定値(Major,およびラベルとしての
モニターの id)を適用することは,IRを利用する側に委ねる。
コマンド cadl sim-ir <file> --format json は,CADL ExplorerのContract Lifecycleページなどの
下流のツールに向けて,この形(上の例は一部を省略している)を出力する。
Unity C#ジェネレータ(cadl codegen --target unity-csharp)は,構文解析された
同じ契約をもとに動作する。PythonのリファレンスランタイムもこのJSONを読み込む。このランタイムは,
cadl-raspimouse-simulator
リポジトリの cadl/runtime/ で公開している。
E.8 コード生成に関する取り決め(参考)
コード生成ターゲットは,lifecycle ブロックと monitors ブロックからコードを
生成してよい。本拡張のリファレンスジェネレータは,unity-csharp ターゲット
(付録D)である。このジェネレータは,lifecycle: または
monitors: のブロックを持つ契約ごとに,次のものを生成する。
- 契約ごとに1つの状態機械クラス(状態は
lifecycle.statesから, 遷移はlifecycle.transitionsから得る) deadline_msを持つ遷移ごとに1つの期限タイマー(cadl0.3では,その遷移がon_violation.transitionにも状態を指定している場合に限る。これを指定していない期限は, 生成されたコードでは強制されない)monitorごとに1つの,周期駆動またはイベント駆動のモニタータスク- 発火した
on_violationまたはon_match.violationごとに1件の違反ログ
cadl 0.3では,生成されたランタイムは,on: のイベントの文字列を,ホストアプリケーションが
通知したイベントの名前と比較して照合する。また,rule を関数呼び出しなしで評価する。
E.6の collision_watch のように関数呼び出しを含む規則は,決して成り立たない。
生成された評価器は,rule の中の算術演算子 + - * / と時間長リテラル(5s)にも
対応していない。
now,request,および観測対象の属性の値は,ホストが与える。
報酬と制裁の実行は,この版の対象外である。ランタイムは報酬と制裁のイベントを 記録することが望ましいが,それに基づいて動作することは求めない。
E.9 後方互換性
拡張を含まないCADL v0.2に基づいて書かれたファイルは,引き続き有効である。
SoS-DSL拡張を用いるファイルは,sos: マッピングの extensions: の下で
sos-dsl: 0.1 を宣言することが望ましい。本拡張を実装していない処理系は,
そのようなファイルを拒否してはならない。そのような処理系は,ファイルごとに
1件の情報レベルの診断を出すことが望ましい。