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
diff --git a/.gitignore b/.gitignore
index c442ee8..4f32e1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
/bazel-*
/*.prof
/schema
+node_modules/
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..3369a77
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+lockfile-version=1
diff --git a/Makefile b/Makefile
index b08d1dd..e761507 100644
--- a/Makefile
+++ b/Makefile
@@ -12,8 +12,14 @@ generated/ts/%: build
debug: build
stack exec --trace -- onix-exe +RTS -xc --RTS --schemaVersion v3 --language go
+# 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: 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..2de7097
--- /dev/null
+++ b/docs/adr/0002-decouple-unit-tests-from-the-vendored-schema.md
@@ -0,0 +1,106 @@
+# 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 時点で、この取得が機能しなくなった。
+
+```
+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
+```
+
+その結果、`Test haskell codes` ジョブはテストを 1 件も実行しないまま失敗する。
+取得できない理由そのものは ADR-0003 で扱う。
+
+### この依存は本物だった
+
+当初、この依存は単なる過剰指定だと考えた。テストコードが直接読むのは
+`fixtures/test_*.xsd` だけで、`./schema` という文字列は `src/Lib.hs` の `schemaRoot`
+にしか現れないからである。しかしこれは誤りだった。
+
+`fixtures/test_mixed_html.xsd` が、EDItEUR の配布物を直接 include していた。
+
+```xml
+
+```
+
+`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` 依存は残す。実際にコードを生成するにはスキーマの実体が
+要るため、こちらは今も本物の依存である。
+
+## 理由
+
+代替物は、テストが実際に検証している性質を保つように書いた。
+`test/TestModel.hs` は、このフィクスチャを読んだうえで `Model.collectElements` が
+空になることを表明している。`collectElements` は `complexMixed = False` の要素だけを
+拾うフィルタなので、この表明の意味は「XHTML の要素がモデルに漏れてこない」ことである。
+XHTML の内容要素はいずれも mixed content なので、代替物でも全要素を
+`` として宣言した。要素名の集合が実物と一致していること、
+フィクスチャ側の `ref` 40 個すべてに宣言が対応することは機械的に確認した。
+
+代替物で足りるのは、テストがこのファイルから必要としているのが**要素の宣言の存在と
+mixed であること**だけだからである。ONIX_XHTML_Subset.xsd の完全な内容 (属性、
+コンテンツモデルの詳細) は、ここで検証されている性質に寄与していない。
+
+そのうえで、テストを外部サービスの可用性から切り離す価値は大きい。パーサのバグを
+直したいときに editeur.org の状態に左右されるのは、依存の向きとして誤っている。
+
+なお、この変更は 202 の問題そのものを解決しない。コード生成と、生成物を最新スキーマへ
+追従させる作業は依然としてダウンロードを必要とする (ADR-0003)。
+
+## 検討した他の選択肢
+
+- **`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/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 の配布物、ツールチェーンのバージョン) の扱い方
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 45ad666..0013ef7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,22 +5,31 @@
"requires": true,
"dependencies": {
"@bazel/bazelisk": {
- "version": "1.7.3",
- "resolved": "https://registry.npmjs.org/@bazel/bazelisk/-/bazelisk-1.7.3.tgz",
- "integrity": "sha512-A+QLZvKifKnbFawH5aaCnooPx0Ia5JS3S8SckKB034GVB3BjtUTCwjaxzxG3ARQ6Jq1vDcQwWgF2bjT9FjrdDg==",
+ "version": "1.28.1",
+ "resolved": "https://registry.npmjs.org/@bazel/bazelisk/-/bazelisk-1.28.1.tgz",
+ "integrity": "sha512-K21x83NXOtd0yb2qzjMES3UV4xEWZ1q1vnXFhADA1u7IoiMVQkJAVQRK3oZ5txpnrGafY15HS+YYr2nmsEP4Tg==",
"dev": true
},
"@types/node": {
- "version": "14.14.25",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz",
- "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==",
- "dev": true
+ "version": "22.20.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz",
+ "integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==",
+ "dev": true,
+ "requires": {
+ "undici-types": "~6.21.0"
+ }
},
"fast-xml-parser": {
"version": "3.17.6",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.17.6.tgz",
"integrity": "sha512-40WHI/5d2MOzf1sD2bSaTXlPn1lueJLAX6j1xH5dSAr6tNeut8B9ktEL6sjAK9yVON4uNj9//axOdBJUuruCzw==",
"dev": true
+ },
+ "undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true
}
}
}
diff --git a/package.json b/package.json
index c6b52e4..a0f3d89 100644
--- a/package.json
+++ b/package.json
@@ -8,8 +8,8 @@
},
"dependencies": {},
"devDependencies": {
- "@bazel/bazelisk": "1.7.3",
- "@types/node": "14.14.25",
+ "@bazel/bazelisk": "1.28.1",
+ "@types/node": "22.20.2",
"fast-xml-parser": "3.17.6"
},
"peerDependencies": {