Skip to content

Latest commit

 

History

History
287 lines (253 loc) · 19.6 KB

CONTRIBUTING.md

File metadata and controls

287 lines (253 loc) · 19.6 KB

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更新作業のついでにこの逸脱を拡大しない でください。追加の補完が必要な場合は、ファイルを書き換えるのでは なく読み込み時にメモリ上で補う方式を優先してください。

  1. 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)
  2. MaiML-Domainを新XSDへ追従させる。 maiml_domainは別リポジトリ なので、まずそちらで新しい/変更されたcomplexTypeに対応するクラスを 追加・修正し、リリースタグを打ちます。pyproject.tomlの dependenciesにあるgit+タグ指定 (maiml-domain @ git+...@vX.Y.Z)をこの新タグに上げてから、以降の 手順に進んでください。

  3. 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と同じ パターンで、新しく増えた型ぶんパラメータを追加する形が最も簡単です)。

  4. 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=明示で使う分には対応不要です)。

  5. 各_write_*をXSDのxs:sequenceと突き合わせる。 _write_document/_write_protocol/_write_data/_write_event_log/ _write_pnmlなど、構造要素を書き出す関数それぞれについて、対応する complexTypeのxs:sequence(子要素の出現順序・出現数)が変わっていない かを確認してください。順序が変わっていれば、書き出し関数内で ET.SubElementを呼ぶ順序をその通りに直す必要があります。

  6. 各_read_*も同様に確認する。 読み込み側は特に影響を受けやすい 箇所です。_read_document/_read_protocol/_read_data/ _read_event_logなどはmaiml_domainの各クラスを m.DocumentType(id=..., date=..., creators=..., ...)のように具体的な キーワード引数で直接呼び出しています。手順2でクラスの必須引数が 増減・改名されていれば、ここが真っ先にTypeErrorで壊れるので、 その通りに引数を追従させてください。逆に任意引数が増えただけの 場合はエラーにはなりませんが、対応するXML子要素の読み書きをここに 追加しない限り、その新フィールドは読み込み時に静かに失われます (往復で消える)。

  7. _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など)を 導入した場合は、ここに分岐を追加してください。

  8. ルート型・名前空間・version属性を確認する。 maimlRootType/ protocolFileRootTypeのxsi:type名、xmlnsのURI (http://www.maiml.org/schemas)、ルート要素のversion属性の期待値 が変わっていないかを確認してください。

  9. round-tripテストを実施する。 dumps()した結果をloads()で 読み戻し、再度dumps()した結果が最初の出力とバイト単位で一致する ことを確認してください(既存のテストで使われているパターンと同じ です)。手順6で見落としたフィールドは、たいていここで最初に 検出されます。

  10. 生成した全XMLを新XSDで検証する。 6つの実サンプルファイル (tests/配下のフィクスチャ、またはこのリポジトリで過去に検証した ESEMstandard/FSEMgold/DAFMgoldcorrected/CCORRELATION/AAFM系のような 構造)を新しいpymaiml.builders/serializationだけで組み立て直し、 pymaiml.validation.validate()がエラー0件で通ることを確認します。

  11. 補足業務ルール(supplementary business rules)も実行する。 pymaiml.validationが実装しているMaiML AI Common Specification由来の ルール(EVT-02のlifecycle complete判定、ref参照先の型チェック、 XES名前空間の厳密一致、秘匿禁止要素など)が、XSDの変更によって 意味的に変わっていないか確認してください。pymaiml.validationは maiml-schema-validatorスキルの検証スクリプトと同等のロジックを 独立に実装したものなので、業務ルール自体が変わった場合は両方を 更新してください(手順1のXSD本体3箇所と同じ理由で、片方だけ直すと 食い違います)。

  12. 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を

"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へ公開されたことを受けて、以下の 順序で移行済みです。

  1. MaiML-Domainが1.0.0に到達し、PyPIへ公開されるのを待った。 0.y.zの間はマイナーyの変更が破壊的変更を意味するため、この段階 で通常のバージョン範囲指定に切り替えても実質的にgitタグ固定と安定性 が変わらなかった。1.0.0以降はSemVer本来の保証(破壊的変更はメジャー Xのみで起きる)が働くため、この到達を待ってから切り替えた。
  2. pyproject.tomlのdependenciesをmaiml-domain>=1.0,<2.0という、 メジャーバージョン1全体を許容する通常のバージョン範囲指定へ 書き換えた。
  3. pymaiml自体も1.0.0として、本リポジトリのPyPI公開の準備を行った (対象範囲は本ファイルの「公開APIの範囲」節を参照)。

今後MaiML-Domainが2.0.0など次のメジャーバージョンへ進んだ場合は、 dependenciesの範囲指定(<2.0の部分)を明示的に見直してから バージョンを上げてください(自動的には追従しません)。

公開APIの範囲(1.0.0のSemVer保証対象)

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エントリポイントを提供して いません(将来追加する場合は、この節を更新してから対象に含めて ください)。

新しい公開関数・クラスを追加する、または上記一覧にある関数・クラスの シグネチャ・戻り値の型・例外条件を変更する際は、この一覧をあわせて 更新してください。

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を組み立てる/編集する」処理と「署名する/検証する」 処理を、同じ関数呼び出しの流れに混在させないためです。