From dd4ae6c60567d57a005922a284db3837ec004db1 Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 06:49:30 +0000 Subject: [PATCH 1/5] Bump deprecated GitHub Actions in the test workflow Both CI jobs currently fail two seconds after starting, before any build step runs: ##[error]This request has been automatically failed because it uses a deprecated version of `actions/cache: v2`. GitHub has closed down actions/cache v1 and v2 and now auto-fails runs that reference them at action-download time, so the workflow cannot get as far as `make test` or the bazel e2e target. Bump actions/cache to v4 to unblock that, and bump the remaining actions still on the Node 12 runtime at the same time: - actions/checkout v2 -> v4 - actions/setup-node v2.1.4 -> v4 - haskell/actions/setup v1 -> haskell-actions/setup v2 (repository moved) Tool versions are left as they are (GHC 8.8.3, stack 2.5.1, Node 12.x) so this change is limited to action versions. haskell-actions/setup v2 still exposes the stack-path output the cache step depends on. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .github/workflows/test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a87029..3b21b44 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,15 +5,15 @@ jobs: name: Test haskell codes runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: haskell/actions/setup@v1 + - uses: actions/checkout@v4 + - uses: haskell-actions/setup@v2 id: haskell-setup with: ghc-version: "8.8.3" enable-stack: true stack-version: "2.5.1" - name: Restore caches - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ${{steps.haskell-setup.outputs.stack-path}} @@ -28,13 +28,13 @@ jobs: name: Test e2e runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup Node.js environment - uses: actions/setup-node@v2.1.4 + uses: actions/setup-node@v4 with: node-version: 12.x - name: Restore caches - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.npm From 8843e5131eb2e2a3ddf6d4501d72862fe851d490 Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 06:55:58 +0000 Subject: [PATCH 2/5] Decouple `make test` from the schema download With the deprecated actions bumped, the Haskell job now reaches `make test` and fails there instead: `make test` depends on the `schema` target, which fetches the EDItEUR archives, and editeur.org now answers bazel's request with `202 Accepted` instead of the zip. The test suite does not need those archives. Everything under test/ reads fixtures/test_*.xsd; the only reader of ./schema is schemaRoot in src/Lib.hs, which is the code-generation path. The dependency was an over-specification that tied the whole feedback loop for the parser to the availability of an external service. Drop it from `test` and keep it on `build`, where generating code really does need the schema. This does not fix the 202 itself: generation and tracking new schema releases still need the download. Also introduce docs/adr/ to record decisions like this one, with a template, an index, and the two decisions made here. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- Makefile | 6 +- docs/adr/0000-template.md | 26 ++++++ .../adr/0001-record-architecture-decisions.md | 52 ++++++++++++ ...ple-unit-tests-from-the-vendored-schema.md | 81 +++++++++++++++++++ docs/adr/README.md | 26 ++++++ 5 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 docs/adr/0000-template.md create mode 100644 docs/adr/0001-record-architecture-decisions.md create mode 100644 docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md create mode 100644 docs/adr/README.md diff --git a/Makefile b/Makefile index b08d1dd..6a06415 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,12 @@ generated/ts/%: build debug: build stack exec --trace -- onix-exe +RTS -xc --RTS --schemaVersion v3 --language go +# The unit tests read only fixtures/*.xsd, so they deliberately do not depend +# on the `schema` target: `make schema` downloads the EDItEUR archives over the +# network, and requiring it here made the test suite unrunnable whenever +# editeur.org was unreachable. See docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md .PHONY: test -test: schema +test: stack test --trace --fast .stack-work: $(HS_FILES) package.yaml stack.yaml diff --git a/docs/adr/0000-template.md b/docs/adr/0000-template.md new file mode 100644 index 0000000..f32c01b --- /dev/null +++ b/docs/adr/0000-template.md @@ -0,0 +1,26 @@ +# ADR-0000: タイトル + +- **ステータス**: Proposed | Accepted | Superseded by ADR-XXXX +- **日付**: YYYY-MM-DD + +## 背景 + +どういう状況で、何が問題になっているか。観測された事実 (エラーメッセージ、計測値、 +外部サービスの挙動) を具体的に書く。 + +## 決定 + +何を決めたか。 + +## 理由 + +なぜその選択肢を選んだか。 + +## 検討した他の選択肢 + +- **案 A**: 内容と、採らなかった理由 +- **案 B**: 内容と、採らなかった理由 + +## 結果 + +この決定によって何が変わるか。新しく発生する制約や、将来見直すべき条件も書く。 diff --git a/docs/adr/0001-record-architecture-decisions.md b/docs/adr/0001-record-architecture-decisions.md new file mode 100644 index 0000000..043bc9c --- /dev/null +++ b/docs/adr/0001-record-architecture-decisions.md @@ -0,0 +1,52 @@ +# ADR-0001: 設計判断を ADR として記録する + +- **ステータス**: Accepted +- **日付**: 2026-09-13 + +## 背景 + +onix-codegen には、コードを読むだけでは意図が復元できない判断がいくつもある。例えば次のようなもの。 + +- スキーマの版を `v2` / `v3` というディレクトリで分け、既存の出力を置き換えない +- 言語固有の知識をテンプレートに閉じ込め、`src/` を言語非依存に保つ +- 対応していない XSD 構造は暗黙に無視せず `unimplemented` / `unreachable` で落とす + +これらはいずれも「後方互換性を保ちながら ONIX 仕様に追従する」「サポート言語を増やしやすくする」という +このリポジトリの価値 (AGENTS.md) から導かれているが、根拠がコミットログに散っているため、 +後から来た人が「なぜこうなっているのか」を再構成しづらい。結果として、良かれと思った変更が +その判断を静かに壊すことがある。 + +外部依存の扱いも同じ問題を抱えている。EDItEUR の配布物、Bazel、Stackage のスナップショットは +どれも外部の都合で壊れるが、「なぜこのバージョンに固定しているのか」が残っていないと、 +更新のたびに同じ調査をやり直すことになる。 + +## 決定 + +設計判断を `docs/adr/` 配下の ADR として記録する。フォーマットは MADR を簡略化したもので、 +`0000-template.md` を雛形とする。連番は一度振ったら変えず、判断が変わったときは既存の ADR を +書き換えるのではなく、新しい ADR を書いて古いものを `Superseded` にする。 + +AGENTS.md には、どういう判断を ADR にすべきかの基準を書き、両者を相互に参照させる。 + +## 理由 + +ADR は「決定そのもの」ではなく「決定に至った制約」を残せる点が、コメントやコミットメッセージより優れている。 +このリポジトリで重要なのは、まさにその制約 (EDItEUR の配布形態、生成物の互換性、テンプレートの責務分担) が +時間とともに変わることなので、当時の前提ごと記録に残せる形式が要る。 + +Markdown をリポジトリ内に置くのは、スキーマや生成物と同じコミットで変更履歴が追えるため。 +外部の Wiki やイシューに書くと、コードとの対応が切れる。 + +## 検討した他の選択肢 + +- **AGENTS.md / README.md に全部書く**: 判断の履歴が積み上がると読みものとして破綻する。 + AGENTS.md は「今どうすべきか」を書く場所として保ち、「なぜそうなったか」は ADR に分ける。 +- **GitHub Issues / Discussions に残す**: 検索性はあるが、リポジトリを clone しただけでは読めず、 + コードとの対応も切れる。オフラインで完結しない。 +- **記録しない (現状維持)**: 調査のやり直しと、意図しない互換性破壊が続く。 + +## 結果 + +- 新しい設計判断、および後から言語化された既知の判断は ADR に書く。 +- ADR を追加したら `docs/adr/README.md` の一覧も更新する。 +- ADR はレビュー対象になる。判断に異論があれば、実装ではなく ADR に対してコメントできる。 diff --git a/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md b/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md new file mode 100644 index 0000000..af219d2 --- /dev/null +++ b/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md @@ -0,0 +1,81 @@ +# ADR-0002: ユニットテストを取得済みスキーマから切り離す + +- **ステータス**: Accepted +- **日付**: 2026-09-13 + +## 背景 + +Makefile のテストターゲットは `schema` に依存していた。 + +```make +.PHONY: test +test: schema + stack test --trace --fast +``` + +`schema` ターゲットは Bazel の `http_archive` 経由で EDItEUR の zip をダウンロードし、 +`schema/v2` / `schema/v3` に展開する。つまり `make test` は毎回 editeur.org への +ネットワークアクセスを要求していた。 + +2026-09-13 時点で、CI からこのダウンロードが失敗するようになった。 + +``` +WARNING: Download from https://www.editeur.org/files/ONIX%202.1/ONIX_for_Books_Release2-1_rev03_schema+codes_Issue_36.zip + failed: UnrecoverableHttpException GET returned 202 Accepted +ERROR: An error occurred during the fetch of repository 'org_editeur_v2' +make: *** [Makefile:35: schema/v2] Error 1 +``` + +editeur.org が zip の代わりに `202 Accepted` を返しており、Bazel はこれを回復不能な +ダウンロードエラーとして扱う。結果として、`Test haskell codes` ジョブはテストを 1 件も +実行しないまま失敗する。 + +ここで重要なのは、**テストコードはそもそも `schema/` を読んでいない**という事実である。 +`test/` 配下が読むのは `fixtures/test_*.xsd` だけで、`./schema` を参照するのは +`src/Lib.hs` の `schemaRoot` (コード生成の実行時パス) のみ。つまりこの依存は、 +テストの実行に必要ではないのに、テストの実行可能性を外部サービスの可用性に縛り付けていた。 + +## 決定 + +`test` ターゲットから `schema` 依存を外す。 + +```make +.PHONY: test +test: + stack test --trace --fast +``` + +`build` ターゲットの `schema` 依存はそのまま残す。実際にコードを生成するには +スキーマの実体が要るため、こちらは本物の依存である。 + +## 理由 + +ユニットテストは「XSD をどう解釈して中間表現に落とすか」を検証するものであり、 +その入力は意図的に最小化された `fixtures/test_*.xsd` である。EDItEUR の完全なスキーマは +テストの対象ではない。依存として書かれていたのは事実の反映ではなく、単なる過剰指定だった。 + +この過剰指定には実害がある。外部サービスの都合で、リポジトリ内のロジックに対する +フィードバックループ全体が止まる。パーサのバグを直したいときに editeur.org の状態に +左右されるのは、依存の向きとして誤っている。 + +なお、この変更は 202 の問題そのものを解決しない。コード生成と、生成物を最新スキーマへ +追従させる作業は依然としてダウンロードを必要とする。これは別の判断として切り出す。 + +## 検討した他の選択肢 + +- **`http_archive` にリトライやフォールバック URL を足す**: Bazel 3.7.0 の `http_archive` は + カスタムヘッダに対応しておらず、202 を返す bot 対策を回避する手段がない。 + そもそもテストにダウンロードは不要なので、この層で解決するのは筋が悪い。 +- **スキーマをリポジトリに取り込む (vendoring)**: テストは通るようになるが、 + EDItEUR の配布物の再配布はライセンス上の検討を要する。テストを通すためだけに + 踏み込む判断ではない。生成のために必要かどうかは別途検討する。 +- **CI でだけ `stack test` を直接呼ぶ**: CI とローカルで手順が食い違い、 + 「手元で通るのに CI で落ちる」を生む。Makefile が唯一の入口である状態を保つ。 + +## 結果 + +- `make test` はネットワークなしで実行できる。CI の Haskell ジョブは editeur.org に依存しない。 +- コード生成 (`make build`、`make generated/...`) は引き続きダウンロードを必要とする。 + 最新スキーマへの追従が止まっている場合、原因はこちら側にある。 +- 新しいテストを書くときは `fixtures/` に最小の XSD を足す、という既存の慣習が + そのまま「テストを外部依存から切り離す」ことにもなっている。この性質は維持する。 diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..fa8323f --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,26 @@ +# Architecture Decision Records (ADR) + +このディレクトリには、onix-codegen の設計判断を記録する ADR を置く。 + +## 一覧 + +| # | タイトル | ステータス | +| ------------------------------------------------------------- | ---------------------------------------------- | ---------- | +| [0001](0001-record-architecture-decisions.md) | 設計判断を ADR として記録する | Accepted | +| [0002](0002-decouple-unit-tests-from-the-vendored-schema.md) | ユニットテストを取得済みスキーマから切り離す | Accepted | + +## 書き方 + +`0000-template.md` をコピーして、次の連番を付ける。番号は一度振ったら変えない。 + +ADR は「その時点で、どういう制約のもとに、なぜそう決めたか」を残すもの。 +後から判断が変わったら、既存の ADR を書き換えるのではなく、新しい ADR を書いて +古いものの Status を `Superseded by ADR-XXXX` に更新する。決定の履歴が消えないことが重要。 + +## どういう判断を ADR にするか + +このリポジトリの価値 (AGENTS.md 参照) に照らして、次のいずれかに影響するもの。 + +- **後方互換性**: 生成コードの公開 API に影響する判断、スキーマの版の扱い方 +- **サポート言語の増やしやすさ**: 中間表現とテンプレートの責務分担、言語追加の手順に影響する判断 +- **ビルドと CI の前提**: 外部依存 (EDItEUR の配布物、ツールチェーンのバージョン) の扱い方 From 5af27c01bae0a7cad4f482fd497cdc406905c85c Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:08:42 +0000 Subject: [PATCH 3/5] Make the mixed-HTML fixture self-contained MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit dropped the `schema` prerequisite from `make test` on the claim that nothing under test/ reads schema/. That claim was wrong, and review caught it: fixtures/test_mixed_html.xsd included ../schema/v2/ONIX_XHTML_Subset.xsd directly. Xsd.getSchema follows includes recursively and resolves that relative path with readFile, which throws when the file is absent, and schema/ is gitignored — so on a clean checkout the suite would have failed instead of running. Commit 9352123 confirms the dependency was deliberate: it added `test: schema` in the same change that deleted the vendored 2_1_rev03_schema/ tree and repointed this fixture at ../schema/v2/. Rather than restore the prerequisite, move what the fixture needs into the repository. test_mixed_html_xhtml_subset.xsd declares the 40 element names the fixture refers to, each as a mixed complex type. That is the property the assertions actually rest on: TestModel expects Model.collectElements to come back empty, and that filter keeps only elements with complexMixed = False, so the test means "XHTML elements do not leak into models". Deleting the include instead would have made the assertion vacuous. The stand-in is written here rather than copied from the EDItEUR distribution, so it raises no redistribution question. ADR-0002 is rewritten around what is actually true, including why the dependency was real and why a stand-in is enough. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- Makefile | 10 +- ...ple-unit-tests-from-the-vendored-schema.md | 91 ++++++++++++------- fixtures/test_mixed_html.xsd | 2 +- fixtures/test_mixed_html_xhtml_subset.xsd | 53 +++++++++++ 4 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 fixtures/test_mixed_html_xhtml_subset.xsd diff --git a/Makefile b/Makefile index 6a06415..e761507 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,12 @@ generated/ts/%: build debug: build stack exec --trace -- onix-exe +RTS -xc --RTS --schemaVersion v3 --language go -# The unit tests read only fixtures/*.xsd, so they deliberately do not depend -# on the `schema` target: `make schema` downloads the EDItEUR archives over the -# network, and requiring it here made the test suite unrunnable whenever -# editeur.org was unreachable. See docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md +# The fixtures under fixtures/ are self-contained, so the tests deliberately do +# not depend on the `schema` target: `make schema` downloads the EDItEUR +# archives over the network, and requiring it here made the test suite +# unrunnable whenever editeur.org was unreachable. Keep fixtures from including +# anything outside fixtures/, or this dependency comes back. +# See docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md .PHONY: test test: stack test --trace --fast diff --git a/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md b/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md index af219d2..2de7097 100644 --- a/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md +++ b/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md @@ -17,7 +17,7 @@ test: schema `schema/v2` / `schema/v3` に展開する。つまり `make test` は毎回 editeur.org への ネットワークアクセスを要求していた。 -2026-09-13 時点で、CI からこのダウンロードが失敗するようになった。 +2026-09-13 時点で、この取得が機能しなくなった。 ``` WARNING: Download from https://www.editeur.org/files/ONIX%202.1/ONIX_for_Books_Release2-1_rev03_schema+codes_Issue_36.zip @@ -26,56 +26,81 @@ ERROR: An error occurred during the fetch of repository 'org_editeur_v2' make: *** [Makefile:35: schema/v2] Error 1 ``` -editeur.org が zip の代わりに `202 Accepted` を返しており、Bazel はこれを回復不能な -ダウンロードエラーとして扱う。結果として、`Test haskell codes` ジョブはテストを 1 件も -実行しないまま失敗する。 +その結果、`Test haskell codes` ジョブはテストを 1 件も実行しないまま失敗する。 +取得できない理由そのものは ADR-0003 で扱う。 -ここで重要なのは、**テストコードはそもそも `schema/` を読んでいない**という事実である。 -`test/` 配下が読むのは `fixtures/test_*.xsd` だけで、`./schema` を参照するのは -`src/Lib.hs` の `schemaRoot` (コード生成の実行時パス) のみ。つまりこの依存は、 -テストの実行に必要ではないのに、テストの実行可能性を外部サービスの可用性に縛り付けていた。 +### この依存は本物だった -## 決定 +当初、この依存は単なる過剰指定だと考えた。テストコードが直接読むのは +`fixtures/test_*.xsd` だけで、`./schema` という文字列は `src/Lib.hs` の `schemaRoot` +にしか現れないからである。しかしこれは誤りだった。 -`test` ターゲットから `schema` 依存を外す。 +`fixtures/test_mixed_html.xsd` が、EDItEUR の配布物を直接 include していた。 -```make -.PHONY: test -test: - stack test --trace --fast +```xml + ``` -`build` ターゲットの `schema` 依存はそのまま残す。実際にコードを生成するには -スキーマの実体が要るため、こちらは本物の依存である。 +`Xsd.getSchema` は include を再帰的にたどり (`src/Xsd.hs` の `go` / `goInclude`)、 +相対パスを `combineURIs` でローカルパスに解決して `Text.XML.readFile` で読む。 +ファイルが無ければ IOException で落ちる。このフィクスチャは +`test/TestMixed.hs` と `test/TestModel.hs` の両方から読まれており、`schema/` は +`.gitignore` されているので、clean checkout では必ず存在しない。 + +つまり `test: schema` は正しい依存だった。git の履歴もそれを裏づけている。 +`test: schema` を追加したコミット (9352123) は、リポジトリに置かれていた +`2_1_rev03_schema/` を削除し、このフィクスチャの include を `../schema/v2/` に +向け直した、まさにそのコミットである。 + +## 決定 + +依存を消すのではなく、**依存の対象をリポジトリ内に移す**。 + +1. `fixtures/test_mixed_html_xhtml_subset.xsd` を追加する。`test_mixed_html.xsd` が + `ref` している 40 個の要素名だけを宣言した、ONIX_XHTML_Subset.xsd の代替物である。 +2. `test_mixed_html.xsd` の include をそちらに向ける。 +3. そのうえで `test` ターゲットから `schema` 依存を外す。 + +`build` ターゲットの `schema` 依存は残す。実際にコードを生成するにはスキーマの実体が +要るため、こちらは今も本物の依存である。 ## 理由 -ユニットテストは「XSD をどう解釈して中間表現に落とすか」を検証するものであり、 -その入力は意図的に最小化された `fixtures/test_*.xsd` である。EDItEUR の完全なスキーマは -テストの対象ではない。依存として書かれていたのは事実の反映ではなく、単なる過剰指定だった。 +代替物は、テストが実際に検証している性質を保つように書いた。 +`test/TestModel.hs` は、このフィクスチャを読んだうえで `Model.collectElements` が +空になることを表明している。`collectElements` は `complexMixed = False` の要素だけを +拾うフィルタなので、この表明の意味は「XHTML の要素がモデルに漏れてこない」ことである。 +XHTML の内容要素はいずれも mixed content なので、代替物でも全要素を +`` として宣言した。要素名の集合が実物と一致していること、 +フィクスチャ側の `ref` 40 個すべてに宣言が対応することは機械的に確認した。 + +代替物で足りるのは、テストがこのファイルから必要としているのが**要素の宣言の存在と +mixed であること**だけだからである。ONIX_XHTML_Subset.xsd の完全な内容 (属性、 +コンテンツモデルの詳細) は、ここで検証されている性質に寄与していない。 -この過剰指定には実害がある。外部サービスの都合で、リポジトリ内のロジックに対する -フィードバックループ全体が止まる。パーサのバグを直したいときに editeur.org の状態に -左右されるのは、依存の向きとして誤っている。 +そのうえで、テストを外部サービスの可用性から切り離す価値は大きい。パーサのバグを +直したいときに editeur.org の状態に左右されるのは、依存の向きとして誤っている。 なお、この変更は 202 の問題そのものを解決しない。コード生成と、生成物を最新スキーマへ -追従させる作業は依然としてダウンロードを必要とする。これは別の判断として切り出す。 +追従させる作業は依然としてダウンロードを必要とする (ADR-0003)。 ## 検討した他の選択肢 -- **`http_archive` にリトライやフォールバック URL を足す**: Bazel 3.7.0 の `http_archive` は - カスタムヘッダに対応しておらず、202 を返す bot 対策を回避する手段がない。 - そもそもテストにダウンロードは不要なので、この層で解決するのは筋が悪い。 -- **スキーマをリポジトリに取り込む (vendoring)**: テストは通るようになるが、 - EDItEUR の配布物の再配布はライセンス上の検討を要する。テストを通すためだけに - 踏み込む判断ではない。生成のために必要かどうかは別途検討する。 -- **CI でだけ `stack test` を直接呼ぶ**: CI とローカルで手順が食い違い、 - 「手元で通るのに CI で落ちる」を生む。Makefile が唯一の入口である状態を保つ。 +- **`test: schema` を残す**: 事実としては正しい依存なので、これは筋が通っている。 + ただし、たった 1 ファイルの XSD のためにテスト全体を外部サービスに縛り続けることになる。 +- **ONIX_XHTML_Subset.xsd をそのまま `fixtures/` に取り込む**: 代替物より忠実だが、 + EDItEUR の配布物の再配布にあたる。ライセンスの判断が要るため、テストを通すためだけに + 踏み込むべきではない (ADR-0003 と同じ理由)。代替物は自前で書いたものなのでこの問題がない。 +- **include を単に削除する**: `Annotation` 以外に要素が無くなるので `collectElements` は + 空になり、表明は通ってしまう。だが「XHTML 要素が漏れてこない」ことを何も検証しなくなり、 + テストが意味を失う。**採らない。** ## 結果 - `make test` はネットワークなしで実行できる。CI の Haskell ジョブは editeur.org に依存しない。 +- `fixtures/test_mixed_html_xhtml_subset.xsd` は実物の代替物である。実物側の構造が変わって + テストの前提が動くことはあり得るので、スキーマの版を上げるときはこのファイルも見直す。 - コード生成 (`make build`、`make generated/...`) は引き続きダウンロードを必要とする。 - 最新スキーマへの追従が止まっている場合、原因はこちら側にある。 - 新しいテストを書くときは `fixtures/` に最小の XSD を足す、という既存の慣習が - そのまま「テストを外部依存から切り離す」ことにもなっている。この性質は維持する。 + そのまま「テストを外部依存から切り離す」ことにもなる。この性質は維持する。 + フィクスチャから `fixtures/` の外を include しないこと。 diff --git a/fixtures/test_mixed_html.xsd b/fixtures/test_mixed_html.xsd index 110ce0e..642cdf2 100644 --- a/fixtures/test_mixed_html.xsd +++ b/fixtures/test_mixed_html.xsd @@ -1,6 +1,6 @@ - + diff --git a/fixtures/test_mixed_html_xhtml_subset.xsd b/fixtures/test_mixed_html_xhtml_subset.xsd new file mode 100644 index 0000000..5be1621 --- /dev/null +++ b/fixtures/test_mixed_html_xhtml_subset.xsd @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From aa338951dd8c653c0cdc18c5098043fea7bbd28d Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:12:04 +0000 Subject: [PATCH 4/5] Stop `make test` from invoking bazel, and drop a debug step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BZL_BIN used `:=`, so `$(shell npx bazel info bazel-bin)` ran while make parsed the file — on every target, `make test` included. The Haskell job runs `make test` without ever installing node_modules, so that expansion just fails there. Measured in this environment: `make -n test` took 18.1s and printed a bazel download error before doing anything, and 0.011s with the assignment deferred to `=`. Nothing outside the schema recipes reads BZL_BIN, so deferring it costs nothing. Also drop the `ls -lah` step that printed the stack tool path; it was debugging output for the cache setup, not a check anything depends on. Both spotted in review of this PR. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .github/workflows/test.yml | 1 - Makefile | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3b21b44..b8f4f2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,6 @@ jobs: key: ${{ runner.os }}-${{ hashFiles('**/package.yaml') }}-${{ hashFiles('**/onix.cabal') }}-${{ hashFiles('**/stack.yaml.lock') }} restore-keys: | ${{ runner.os }}- - - run: ls -lah ${{steps.haskell-setup.outputs.stack-path}} - run: make test e2e: name: Test e2e diff --git a/Makefile b/Makefile index e761507..46ad7d1 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ TS_FILES := $(shell find ./ -type f -name '*.ts' | grep -v 'node_modules') HS_FILES := $(shell find ./ -type f -name '*.hs' | grep -v '.stack-work') BZL := npx bazelisk -BZL_BIN := $(shell npx bazel info bazel-bin) +# Deferred on purpose: `:=` would run bazel on every make invocation, including +# `make test`, which runs in a job with no node_modules and no need for bazel. +BZL_BIN = $(shell $(BZL) info bazel-bin) generated/go/%: build stack exec onix-exe -- --schemaVersion $(@F) --language go From f4f834e9d1d99954d92b8f1fc88e27ede5b76557 Mon Sep 17 00:00:00 2001 From: Shinichi Kogai Date: Sun, 13 Sep 2026 17:00:06 +0900 Subject: [PATCH 5/5] Add AGENTS.md (#55) * Add AGENTS.md describing repository values and workflows Document the two things this repository exists for: following the ONIX spec while keeping backward compatibility, and growing the set of target languages through code generation. Covers the schema -> XSD parser -> intermediate representation -> mustache pipeline, the make/bazel commands, how to bump an EDItEUR schema release, a step-by-step procedure for adding a new target language, the fixture- driven test convention, and which paths are generated and must not be hand-edited. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z * Align AGENTS.md with the CI and ADR changes `make test` no longer depends on the schema download, so the command table said something that is about to stop being true. Also point at ADR-0003 where `make schema` is described, since that is the command that is currently broken. Add the rule that came out of getting this wrong once: a fixture must not include anything outside fixtures/. Xsd.getSchema follows xs:include recursively, so a single `../schema/` reference puts the whole suite back on editeur.org's availability, which is exactly what test_mixed_html.xsd did. Add a section on recording design decisions as ADRs, with the criteria for what warrants one, so the reference from docs/adr/README.md is reciprocated. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --------- Co-authored-by: Claude Opus 5 --- AGENTS.md | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..880a49a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,159 @@ +# AGENTS.md + +このリポジトリで作業するエージェント/コントリビュータ向けのガイド。 +まず「このリポジトリの価値」を読み、それに反する変更をしないこと。 + +## このリポジトリの価値 + +onix-codegen は EDItEUR が配布する ONIX for Books の XSD スキーマから、 +各言語向けのクライアントコードを自動生成するジェネレータ。価値は次の 2 点に集約される。 + +1. **ONIX 仕様への追従を、なるべく後方互換性を保ったまま行うこと。** + スキーマの Issue 更新・リリース更新に追従しつつ、既に生成済みのコードを使っている + 利用者のビルドを壊さない。互換性を壊す変更は、壊さない選択肢が本当に無いときだけ。 +2. **コード生成の仕組みで、サポートできる言語をなるべく増やすこと。** + 言語固有の知識はテンプレート (`template///*.mustache`) に閉じ込め、 + スキーマ解析側 (`src/`) は言語非依存に保つ。新しい言語の追加が「テンプレートを書くだけ」に + 近づくほど、この設計は正しい方向にある。 + +判断に迷ったら、この 2 つのどちらをより良くするかで選ぶ。 + +## 全体のパイプライン + +``` +EDItEUR の zip (WORKSPACE の http_archive) + └─ bazel genrule (BUILD.bazel: onix_v2 / onix_v3) + └─ schema/v2, schema/v3 … .gitignore 済み。生成物なのでコミットしない + └─ src/Xsd/*.hs … XSD を Haskell の AST へパース (言語非依存) + └─ src/{Model,Code,Mixed}.hs + … AST を「生成用の中間表現」へ変換 (言語非依存) + └─ src/Lib.hs + mustache テンプレート + └─ generated///{model,code,mixed,reader}. +``` + +中間表現は `Util.GenSchema` クラス (`readSchema :: Schema -> a`) と +`Text.Mustache.ToMustache` インスタンスで表現される。テンプレートから見えるキーは +各 `toMustache` 実装がすべて。テンプレートに新しい情報が必要になったら、 +まず `toMustache` にキーを足す。 + +- `Model` … 要素・属性の木 (`shortname` / `xmlReferenceName` / `typeName` / `optional` / `iterable`) +- `Code` … コードリスト (列挙値と説明) +- `Mixed` … mixed content を持つ要素 + +## セットアップとよく使うコマンド + +前提: `stack` (GHC 8.8.3 / lts-16.27)、`node` (bazelisk を npm 経由で実行)、`go`。 +スキーマ取得で editeur.org への外部ネットワークアクセスが必要。 + +```sh +npm install # bazelisk などを入れる +make schema # EDItEUR の zip を取得して schema/v2, schema/v3 に展開 +make build # schema + stack build --fast +make test # stack test (HUnit) のみ。ネットワーク不要 +make generated/go/v3 # v3 の Go コードを再生成 (ターゲット名の末尾がスキーマ版) +make generated/ts/v2 # v2 の TypeScript コードを再生成 +npx bazelisk test //e2e/go:snapshot_test # 生成済み Go クライアントの e2e スナップショット +make debug # プロファイル付きで v3/go を生成 (例外の発生箇所を追うとき) +``` + +CI (`.github/workflows/test.yml`) は `make test` と `//e2e/go:snapshot_test` の 2 ジョブ。 +この 2 つがローカルで通ることを、push 前に確認する。どちらもネットワークを必要としない。 + +`make schema` は現在 editeur.org から取得できない (202 が返る)。生成系を動かすには +手元に zip を用意する必要がある。事情と手順は `docs/adr/0003-editeur-schema-acquisition.md`。 + +既知の古さ: Makefile の `json` ターゲットは存在しない `run` に依存し、 +import path も `go/helper` と古い (実体は `e2e/go`)。スナップショットの更新は +`//e2e/go:snapshot` の出力を `fixtures/20201200.json` に反映する形で行う。 + +## 後方互換性の守り方 + +生成コードの「公開 API」は、型名・フィールド名・XML/JSON タグ。ここが実質的な互換性境界。 + +- **生成物の diff を必ず読む。** `make generated/go/v2` 等を実行し、`git diff generated/` を確認する。 + 既存の型・フィールドの **削除やリネーム** が出ていたら、それは破壊的変更。意図した場合のみ、 + PR 本文に理由と影響範囲を書く。追加のみの diff は基本的に安全。 +- **スキーマの版はディレクトリで分ける。** 新しい Issue / リリースに対応するときは、 + 既存の `v2` / `v3` の出力を置き換えるのではなく、必要なら新しいバージョンとして足す。 +- **未対応は黙って落とさない。** 扱えない構造に遭遇したら `Util.unimplemented`、 + 起こり得ないはずの分岐は `Util.unreachable` で、理由の文字列を付けて明示する。 + 暗黙にフィールドを落とすと、後方互換性の問題が静かに発生する。 +- **`generated/` を手で編集しない。** 差分は必ずテンプレートか `src/` を直して再生成する。 +- **`fixtures/20201200.json` は e2e のスナップショット。** ここが変わる = 生成物の + ランタイム挙動が変わっている。変更する場合は、それが意図した互換性変更か確認する。 + +### ONIX スキーマの版を上げる手順 + +1. `WORKSPACE` の `http_archive` (`org_editeur_v2` / `org_editeur_v3`) に URL と `sha256` を設定する。 + 既存の版を差し替えるのではなく、追従先を増やす方向を先に検討する。 +2. `org_editeur_*.bazel` の `glob` と `BUILD.bazel` の `filegroup` (`onix2p1` / `onix3p0p7`) に + ファイル名を反映する。 +3. `src/Lib.hs` の `schemaRoot` にあるルート XSD のパスを確認する。 +4. `make test` と生成物の diff で、既存の型が消えていないことを確認する。 + +## 新しい言語を足す手順 + +言語非依存な `src/` には手を入れないのが理想。触る必要が出たら、それは中間表現に +情報が足りていないサインなので、言語別分岐ではなく中間表現の拡張として実装する。 + +1. `src/Lib.hs` + - `data Language` にコンストラクタを追加 + - `ext` … 出力拡張子 + - `template` … `template//` のパス + - `generateTo` … `generated//` のパス +2. `app/Main.hs` の `run` に `--language ` のパターンを追加する。 + 未対応の組み合わせは `unimplemented` を返す (v3 × typescript が既にその例)。 +3. `template///` に 4 つの mustache を置く。 + `model` / `code` / `mixed` / `reader` の 4 つは `Renderer` と 1 対 1 で、すべて必須。 + `reader` だけはスキーマを受け取らない (`substitute t ()`) ので、静的なテンプレートでよい。 +4. `generated///` を作り、生成物をコミットする。 +5. `Makefile` に生成ターゲットを追加する (`generated/go/%` を参考に)。 +6. 可能なら e2e を足す。`e2e/go` が手本: 生成クライアントで `fixtures/20201200.onix` を読み、 + JSON にして `fixtures/20201200.json` と突き合わせる。言語をまたいで同じ + スナップショットに一致することが、生成器の正しさの一番強い証拠になる。 +7. `README.md` の Current Status 表を更新する。 + +## テストの書き方 + +`test/` は HUnit。`test/Spec.hs` が `TestModel` / `TestParser` / `TestCode` / `TestMixed` を束ねる。 + +新しい XSD の構文や生成パターンに対応するときは、**まず `fixtures/test_*.xsd` に +最小の XSD を足す**。既存の fixture は 1 ファイル 1 論点で、命名は +`test_<領域>_<論点>.xsd` (`test_code_space_separated.xsd`、`test_model_iterable_choice.xsd` など)。 +テスト側では `getSchema "./fixtures/test_xxx.xsd"` で読み、期待値を AST リテラルで書いて +`assertEqual` する。コードリストを参照する fixture は `*_codelists.xsd` を隣に置く慣習。 + +**fixture から `fixtures/` の外を include しないこと。** `Xsd.getSchema` は `xs:include` を +再帰的にたどってローカルパスを解決するので、`../schema/` を参照した瞬間、テストは +`make schema` のダウンロードに依存する。実際に `test_mixed_html.xsd` がそうなっていて、 +テスト全体が editeur.org の可用性に縛られていた (ADR-0002)。必要な定義は +`test_mixed_html_xhtml_subset.xsd` のように、代替物を `fixtures/` 内に置く。 + +## コーディング規約 + +- Haskell は ormolu 相当の整形。既存ファイルのスタイル (import の並び、レコード記法) に合わせる。 +- `package.yaml` の `library` は `-Wall -fwarn-incomplete-patterns -fwarn-incomplete-uni-patterns`。 + 警告を増やさない。パターンマッチは網羅するか、`unreachable` / `unimplemented` で明示的に落とす。 +- 依存は `package.yaml` でバージョン固定。追加したら `stack.yaml.lock` の更新も確認する。 +- モジュールの役割を混ぜない。XSD の形の話は `src/Xsd/`、中間表現は `Model`/`Code`/`Mixed`、 + 出力先や言語の対応表は `Lib`、CLI は `app/Main.hs`。 + +## 設計判断は ADR に残す + +「なぜそうなっているか」は `docs/adr/` に ADR として記録する。この AGENTS.md は +「今どうすべきか」を書く場所、ADR は「どういう制約のもとにそう決めたか」を残す場所。 + +次のいずれかに影響する判断をしたら ADR を足す (`docs/adr/0000-template.md` が雛形)。 + +- **後方互換性**: 生成コードの公開 API に影響する判断、スキーマの版の扱い方 +- **サポート言語の増やしやすさ**: 中間表現とテンプレートの責務分担、言語追加の手順 +- **ビルドと CI の前提**: 外部依存 (EDItEUR の配布物、ツールチェーンのバージョン) の扱い方 + +判断が変わったときは既存の ADR を書き換えず、新しい ADR を書いて古いものを +`Superseded` にする。決定の履歴が消えないことが重要。 + +## 触らない / コミットしないもの + +- `schema/` … `make schema` の生成物 (`.gitignore` 済み) +- `.stack-work/`, `bazel-*` … ビルド成果物 +- `generated/` を手編集したもの … 必ず再生成した結果をコミットする