Contributing¶
MaiML仕様(業務ルール・シリアライズ形式など)に影響する変更は、必ず
GitHub Issue/PRでの議論を経てから行い、CHANGELOG.mdに記載してください。
組織全体の方針は
MaiML-Library/.github
を参照してください。
MaiML-Schema-1_0が更新されたときの手順¶
pymaimlは、property/content(約70種類の値型)の層は
maiml_domain.property.__all__をリフレクションで読むことでXSDの変更に
かなり自動追従しますが、document/protocol/data/eventLog/pnml
などの構造部分(pymaiml/serialization.pyの_write_*/_read_*)は、
maiml_domainの各クラスの今のコンストラクタシグネチャをそのまま
決め打ちで呼んでいます。この結合を無理に汎用化するより、「XSD更新時に
差分を確実に検出できるチェックリストを用意しておく」方針を取っています。
XSDを更新する際は、以下を順に確認してください。
同梱XSDの再配布条件に注意してください。
pymaiml/schema/MaiML-Schema-1_0/配下のXSDファイルはJAIMAの第三者 著作物であり、無改変での再配布は許諾されていますが、改変は許諾 されていません(詳細は同ディレクトリのNOTICEを参照)。以下の 手順1で「XSD本体を新版に置き換える」際も、公式配布版をそのまま 上書きしてください。手作業での書き換えは行わないでください。なお
maiml.xsd/maiml-core.xsd/maiml-document.xsd/maiml-property.xsd/xenc-schema.xsdの5本は、この制約を記録する より前にxs:importが追記された既知の・未解消の逸脱です (NOTICE参照)。新しいXSD更新作業のついでにこの逸脱を拡大しない でください。追加の補完が必要な場合は、ファイルを書き換えるのでは なく読み込み時にメモリ上で補う方式を優先してください。
- XSD本体を3箇所に反映する。 MaiMLの公式XSDは1つのリポジトリに 集約されていません。以下すべてを新版に置き換えてください(片方だけ 更新すると「pymaiml側は新XSDで通るのにスキーマ検証だけ古いまま」と いった食い違いが起きます)。
pymaiml/schema/MaiML-Schema-1_0/*.xsd(pymaiml.validationが実際に 検証で使う束)maiml-schema-validatorスキルのreference/MaiML-Schema-1_0/(同じ 内容のコピーを独立に保持しています)-
MaiML-Domainリポジトリ側のクラス定義そのもの(下記2) -
MaiML-Domainを新XSDへ追従させる。maiml_domainは別リポジトリ なので、まずそちらで新しい/変更されたcomplexTypeに対応するクラスを 追加・修正し、リリースタグを打ちます。pyproject.tomlのdependenciesにあるgit+タグ指定 (maiml-domain @ git+...@vX.Y.Z)をこの新タグに上げてから、以降の 手順に進んでください。 -
pymaiml._xsi_registryの全型を検証するテストを実行する。_xsi_registry.XSI_TYPE_TO_CLASS/CLASS_TO_XSI_TYPEはmaiml_domain.property.__all__から自動生成されるため、新しい property/content型は基本的に無修正で使えるようになりますが、それは 「クラス名の先頭一文字を小文字化したものがxsi:type名と一致する」と いう命名規則が守られている場合に限ります。新旧の型一覧を突き合わせ、 各xsi:typeについて代表値を1つ用意して_format_value()→_parse_scalar_text()の往復でPython型が 元に戻ることを確認するテストを実行してください(既存のtest_bare_scalar_types_round_trip_with_correct_python_typeと同じ パターンで、新しく増えた型ぶんパラメータを追加する形が最も簡単です)。 -
pymaiml.buildersのPython型マッピングとの差分を確認する。_xsi_registryとは対照的に、builders.pyの_SCALAR_CLASS_BY_TYPE/_LIST_CLASS_BY_TYPE(Pythonの値の型 →maiml_domainクラスの対応表)は自動生成ではなく手書きのテーブルです。 XSDに新しいプリミティブ型が追加されて、それをPythonのどの組み込み型 から推定すべきかが自明でない場合は、ここへの追記が必要です(単に xsi:type名の命名規則に従っただけの新型なら、infer_property()/infer_content()をxsi_type=明示で使う分には対応不要です)。 -
各
_write_*をXSDのxs:sequenceと突き合わせる。_write_document/_write_protocol/_write_data/_write_event_log/_write_pnmlなど、構造要素を書き出す関数それぞれについて、対応する complexTypeのxs:sequence(子要素の出現順序・出現数)が変わっていない かを確認してください。順序が変わっていれば、書き出し関数内でET.SubElementを呼ぶ順序をその通りに直す必要があります。 -
各
_read_*も同様に確認する。 読み込み側は特に影響を受けやすい 箇所です。_read_document/_read_protocol/_read_data/_read_event_logなどはmaiml_domainの各クラスをm.DocumentType(id=..., date=..., creators=..., ...)のように具体的な キーワード引数で直接呼び出しています。手順2でクラスの必須引数が 増減・改名されていれば、ここが真っ先にTypeErrorで壊れるので、 その通りに引数を追従させてください。逆に任意引数が増えただけの 場合はエラーにはなりませんが、対応するXML子要素の読み書きをここに 追加しない限り、その新フィールドは読み込み時に静かに失われます (往復で消える)。 -
_parse_scalar_text()/_format_value()に新しいプリミティブ型が ないか確認する。 これらはxsi_type.lower()に対する固定の トークン一覧("boolean","datetime","base64binary","hexbinary","uuid","decimal","double"/"float","long"/"short"/"byte"/"int")で分岐しています。新XSDが上記に ない全く新しいプリミティブ型(例:xs:anyURI、xs:languageなど)を 導入した場合は、ここに分岐を追加してください。 -
ルート型・名前空間・
version属性を確認する。maimlRootType/protocolFileRootTypeのxsi:type名、xmlnsのURI (http://www.maiml.org/schemas)、ルート要素のversion属性の期待値 が変わっていないかを確認してください。 -
round-tripテストを実施する。
dumps()した結果をloads()で 読み戻し、再度dumps()した結果が最初の出力とバイト単位で一致する ことを確認してください(既存のテストで使われているパターンと同じ です)。手順6で見落としたフィールドは、たいていここで最初に 検出されます。 -
生成した全XMLを新XSDで検証する。 6つの実サンプルファイル (
tests/配下のフィクスチャ、またはこのリポジトリで過去に検証した ESEMstandard/FSEMgold/DAFMgoldcorrected/CCORRELATION/AAFM系のような 構造)を新しいpymaiml.builders/serializationだけで組み立て直し、pymaiml.validation.validate()がエラー0件で通ることを確認します。 -
補足業務ルール(supplementary business rules)も実行する。
pymaiml.validationが実装しているMaiML AI Common Specification由来の ルール(EVT-02のlifecycle complete判定、ref参照先の型チェック、 XES名前空間の厳密一致、秘匿禁止要素など)が、XSDの変更によって 意味的に変わっていないか確認してください。pymaiml.validationはmaiml-schema-validatorスキルの検証スクリプトと同等のロジックを 独立に実装したものなので、業務ルール自体が変わった場合は両方を 更新してください(手順1のXSD本体3箇所と同じ理由で、片方だけ直すと 食い違います)。 -
xsi:type値は名前空間プレフィックスを解決しない、素の文字列比較で 判定されていることに注意する。xsi:typeはXML Schema上xs:QName型 であり、本来はプレフィックスが束縛する名前空間URIによって意味が決まる ため、xsi:type="maimlRootType"(デフォルト名前空間xmlns= "http://www.maiml.org/schemas"に依存)とxsi:type="maiml: maimlRootType"(xmlns:maiml="http://www.maiml.org/schemas"という 明示的プレフィックス)はXMLの仕様上まったく同じ意味であり、実際に lxmlのXMLSchema.validate()でもどちらも合格することを確認済み。 しかしpymaiml.serializationのルート型判定(xsi_type == "maimlRootType"という直接比較)と、pymaiml._xsi_registry. class_for_xsi_type()(property/contentの約70種のxsi:type すべてが対象)は、どちらもQName解決を行わずに素の文字列一致で実装 されているため、maiml:のような明示的プレフィックス付きで書かれた ファイルはloads()時に「Unknown xsi:type」として読み込みに失敗する (実際に検証済み:xsi:type="maiml:maimlRootType"のファイルはXSD 構造検証には合格するが、pymaiml.serialization.loads()はValueErrorを送出する)。実サンプルファイル・pymaimlの書き出し処理 自体は一貫して「デフォルト名前空間+プレフィックスなし」を使っている ため、pymaimlだけで完結する限り実害はないが、外部ツールが生成した (プレフィックス付きの)ファイルを読み込む可能性がある場合は、_read_maiml_file/class_for_xsi_type()側でQName解決 (etree.QName(...).localname相当の処理)を追加する改修を検討する こと。XSD更新そのものとは独立した既知の制約だが、手順8(ルート型・ 名前空間の確認)や手順3(xsi_registryの全型検証)を行う際に、 プレフィックス付き入力への対応要否も併せて検討するとよい。
上記を一通り終えたら、CHANGELOG.mdに変更内容を記載し(依存先
maiml-domainのバージョンを上げること自体もSDKにとっての変更として
扱います)、破壊的変更かどうかに応じてCHANGELOG.md冒頭のバージョニング
ポリシーに従ってバージョンを上げてください。
PyPI公開前の対応¶
現在pyproject.tomlのdependenciesは、開発段階として
というdirect reference(git+URLでタグ固定)依存にしています。これは
mainブランチ追従(再現性なし)よりはるかに安全なので、開発中はこのまま
で問題ありません。
ただし本パッケージをPyPI等のpublic indexへ公開する場合は、この形式の ままではアップロードできません。PyPAの仕様(PEP 508の"Direct References"節、およびPyPIのdirect reference依存に関する方針)では、 public index serverはアップロードされたdistributionの依存関係に direct referenceを含めることを許可すべきではないとされており、実際 PyPIへのアップロードもこの形式のままでは拒否されます(distributionが 外部の任意コードを取り込めてしまい、index server側の検証をバイパス できてしまうため)。
公開する際は、次の順序で対応してください。
MaiML-Domainを先にPyPIへ公開する。pymaimlが依存する具体的な バージョン範囲を確定させるため、必ずこちらを先に公開します。pyproject.tomlのdependenciesを通常のバージョン指定へ書き換える。 のように、公開されたバージョンに対する範囲指定へ置き換えます (pyproject.toml内に、この置き換え後の形をコメントで併記して あります)。バージョン範囲の上限は、MaiML-Domain側のCHANGELOG.md冒頭のバージョニングポリシー(破壊的変更でマイナーyを上げる方針)に合わせて、常に現在追従しているマイナーバージョン までに絞ってください。pymaiml自体をPyPIへ公開する。 1・2を終えてから公開します。
なお、この制約はpymaiml自身がPyPIへ公開する側になった場合の話であり、
MaiML-Domainを先にGitHubのタグ経由で追従する現在の開発フローそのものを
変える必要はありません。
XML Signature(電子署名)の扱い¶
pymaimlはMaiMLの<Signature>(XML電子署名)の生成・検証そのものを担当
しません。理由と責務分担は以下のとおりです。
- MaiMLの
<Signature>は、単なるXMLDSIGではなく、JIS X 5093 / ETSI TS 101 903(XAdES)に準拠したenveloped署名として扱うべきものです。 Digestは署名時点の厳密なバイト列(<Signature>挿入前のMaiMLファイル) に対して計算され、JISの規定上、署名済みMaiMLはそれ以降 (</Signature>終了タグ直後の改行を除き)内容を書き換えないことが 前提です。 pymaiml.serialization.loads()は<Signature>を読み込み、DocumentType.signatureに文字列としてそのまま保持します(外部の 検証ツールに渡す、などの用途に使えます)が、dumps()/dump()は 既存のSignatureを常に出力から除外します。dumps()はオブジェクト ツリーからインデント・namespace宣言位置・属性順序・空要素表現などを 含めてXMLを再構築するため、内容を一切変更していなくても署名時点の 厳密なバイト列を再現できる保証がなく、それをpymaimlが判断・保証 すること自体が責務外だからです(「内容が変わっていないから署名を 維持してよい」という判断はしません)。- したがって、署名済みMaiMLは「署名前に編集する対象」と「署名後は
原則不変な成果物」として明確に分けて扱ってください。署名は、MaiMLの
生成・編集が完了した後に、JIS X 5093 / ETSI TS 101 903準拠の署名機能を
持つ外部ツールを使って、
dumps()/dump()が出力した バイト列に対して直接行ってください。 - 署名済みMaiMLファイルを保存する際は、XMLの再整形(pretty print)・
コメント削除・不要な空白削除だけでなく、改行コード(CRLF/LF)の変換・
属性の並び替え・空要素の記法(
<a></a>⇄<a/>)の変更など、署名対象 XMLの正準化(C14N)結果を変化させうる処理を一切行わないこと。 これらはXMLとして意味的に同一に見える変更であっても、署名時に計算 された正準化結果とは一致しなくなり、検証時に不一致となり得ます (Gitのautocrlf設定やエディタの「保存時に改行コードを統一」機能が、 意図せずこれを引き起こすことがあります)。現状のdumps()/dump()は 既存のSignatureを常に出力から除外するためこの問題には直面しません が(上記の通り)、将来「署名済みファイルをそのまま保存・コピーする」 機能や、署名対応モジュール(pymaiml.signature等)を追加する場合は、 この制約を必ず守ってください。署名済みファイルをGitリポジトリで 管理する場合は、.gitattributesで該当ファイルを-text(改行コード 正規化の対象外)に設定することも検討してください。 - 将来
pymaimlへ署名対応を追加する場合も、通常のシリアライズ (pymaiml.serialization)とは独立したモジュール(例:pymaiml.signatureのsign()/verify())として分離することを推奨 します。「MaiMLを組み立てる/編集する」処理と「署名する/検証する」 処理を、同じ関数呼び出しの流れに混在させないためです。