MaiML仕様(業務ルール・シリアライズ形式など)に影響する変更は、必ず
GitHub Issue/PRでの議論を経てから行い、CHANGELOG.mdに記載してください。
組織全体の方針は
MaiML-Library/.github
を参照してください。
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冒頭のバージョニング
ポリシーに従ってバージョンを上げてください。
開発初期はpyproject.tomlのdependenciesを
"maiml-domain @ git+https://github.com/MaiML-Library/MaiML-Domain.git@vX.Y.Z"
というdirect reference(git+URLでタグ固定)依存にしていました。これは
mainブランチ追従(再現性なし)よりは安全でしたが、PyPAの仕様
(PEP 508の"Direct References"節、およびPyPIのdirect reference依存に
関する方針)により、public index serverはアップロードされた
distributionの依存関係にdirect referenceを含めることを許可すべきでは
ないとされており、この形式のままではPyPIへアップロードできません
(distributionが外部の任意コードを取り込めてしまい、index server側の
検証をバイパスできてしまうため)。
MaiML-Domainが1.0.0に到達しPyPIへ公開されたことを受けて、以下の
順序で移行済みです。
MaiML-Domainが1.0.0に到達し、PyPIへ公開されるのを待った。0.y.zの間はマイナーyの変更が破壊的変更を意味するため、この段階 で通常のバージョン範囲指定に切り替えても実質的にgitタグ固定と安定性 が変わらなかった。1.0.0以降はSemVer本来の保証(破壊的変更はメジャーXのみで起きる)が働くため、この到達を待ってから切り替えた。pyproject.tomlのdependenciesをmaiml-domain>=1.0,<2.0という、 メジャーバージョン1全体を許容する通常のバージョン範囲指定へ 書き換えた。pymaiml自体も1.0.0として、本リポジトリのPyPI公開の準備を行った (対象範囲は本ファイルの「公開APIの範囲」節を参照)。
今後MaiML-Domainが2.0.0など次のメジャーバージョンへ進んだ場合は、
dependenciesの範囲指定(<2.0の部分)を明示的に見直してから
バージョンを上げてください(自動的には追従しません)。
1.0.0以降、本リポジトリのCHANGELOG.md冒頭のバージョニングポリシー
(破壊的変更でメジャーXを上げる)が対象とする「公開API」の範囲を、
ここで確定させます。
SemVer保証の対象(公開API)
pymaiml.__version__pymaiml直下でアンダースコアなしのモジュール名を持つ4モジュールの トップレベルに定義された、アンダースコアで始まらない関数・クラス・ そのpublicなメソッド/属性:pymaiml.builders(IdFactory/XsiTypeRegistry/infer_property/infer_content/new_complete_event/InsertionValue/create_instance/create_instances)pymaiml.query(get_uuids/get_keys/get_namespaces/get_insertion_uris/get_templates/get_instances)pymaiml.serialization(dumps/dump/LoadedMaiml/loads/load)pymaiml.validation(Finding/ValidationResult/validate)
- 上記関数・メソッドが送出する例外の型(
ValueError/TypeErrorなど)と、送出される条件。
SemVer保証の対象外
- 名前がアンダースコアで始まるモジュール(
pymaiml._xsi_registry/pymaiml._xml_securityなど)、および公開モジュール内のアンダースコア 始まりの関数・メソッド・属性。これらは実装の都合で予告なく変更・削除 されることがあります。 - 例外メッセージの文言そのもの。デバッグ・診断のために改善される ことがあり、文言の変更は破壊的変更として扱いません(例外の型と送出 条件のみが保証対象です)。
pymaiml/schema/MaiML-Schema-1_0/以下に同梱しているXSD/NOTICEファイル の内部レイアウト(ファイル名・ディレクトリ構成)。これは実装の詳細で あり、pymaiml.validation.validate()の入出力仕様のみが保証対象です。 MaiML-Schema-1_0自体の改版に伴う内容変更は、通常の互換性ポリシーに 従い判断します。- 依存パッケージ
maiml_domainが公開するクラス・関数(maiml_domainが 再エクスポートされていても、これは基盤パッケージのAPIです)。maiml_domain自体の互換性はMaiML-Domainリポジトリ 自身のバージョニングポリシーに従います。ただし、maiml_domainの 破壊的変更を取り込む(依存バージョンを上げる)ことでpymaimlの 公開APIの挙動が変わる場合は、本リポジトリの破壊的変更として扱います (「依存先であるmaiml_domainのバージョンを上げること自体もSDKに とっての変更として扱う」という、本ファイル冒頭寄りの既存の運用方針 のとおりです)。 - CLIコマンド。現時点で本パッケージはCLIエントリポイントを提供して いません(将来追加する場合は、この節を更新してから対象に含めて ください)。
新しい公開関数・クラスを追加する、または上記一覧にある関数・クラスの シグネチャ・戻り値の型・例外条件を変更する際は、この一覧をあわせて 更新してください。
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を組み立てる/編集する」処理と「署名する/検証する」 処理を、同じ関数呼び出しの流れに混在させないためです。