From dd4ae6c60567d57a005922a284db3837ec004db1 Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 06:49:30 +0000 Subject: [PATCH 01/12] 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 02/12] 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 1853a07972c1793c03bbc99dfeb80c8392412806 Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:02:49 +0000 Subject: [PATCH 03/12] Record the EDItEUR schema acquisition problem as ADR-0003 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code generation is blocked from two directions and neither is fixable by picking a different version number, so write down what was observed and what the options cost before anyone tries again. From CI, editeur.org answers bazel's request with `202 Accepted` instead of the zip, and repeating the run reproduced it, so it is not transient. From the development sandbox the host is refused outright by the egress policy, which also means the sha256 that http_archive requires cannot be computed here. The ADR stays Proposed on purpose: the two durable fixes (vendoring the schemas, or hosting a mirror) both redistribute a third party's files, and that is a licensing call for the maintainer rather than something to settle by committing files. Spoofing a User-Agent to get past the 202 is listed and rejected — it circumvents an access control the publisher put there. As an interim, the ADR documents bazel's --distdir, which reuses a manually downloaded zip while keeping the sha256 check intact. It also notes that a hand-placed schema/ directory is honoured; verified with a scratch Makefile reproducing the `schema/%` pattern rule, which skips a target whose directory already exists, per directory. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- docs/adr/0003-editeur-schema-acquisition.md | 101 ++++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 102 insertions(+) create mode 100644 docs/adr/0003-editeur-schema-acquisition.md diff --git a/docs/adr/0003-editeur-schema-acquisition.md b/docs/adr/0003-editeur-schema-acquisition.md new file mode 100644 index 0000000..18f9959 --- /dev/null +++ b/docs/adr/0003-editeur-schema-acquisition.md @@ -0,0 +1,101 @@ +# ADR-0003: EDItEUR スキーマの取得方法 + +- **ステータス**: Proposed (メンテナの判断待ち) +- **日付**: 2026-09-13 + +## 背景 + +このリポジトリは、EDItEUR が配布する zip を Bazel の `http_archive` で取得して +スキーマの実体を得ている (`WORKSPACE`)。 + +```python +http_archive( + name = "org_editeur_v2", + sha256 = "8fe93242...", + url = "https://www.editeur.org/files/ONIX%202.1/ONIX_for_Books_Release2-1_rev03_schema+codes_Issue_36.zip", +) +``` + +2026-09-13 時点で、この取得が機能しない。観測された事実は次の 2 つ。 + +**1. CI から: `202 Accepted` が返る** + +``` +WARNING: Download from https://www.editeur.org/files/ONIX%202.1/...Issue_36.zip + failed: UnrecoverableHttpException GET returned 202 Accepted +``` + +zip の代わりに 202 が返り、Bazel はこれを回復不能なエラーとして扱う。1 回再実行しても +同じ結果だったため、一時的な不調ではない。202 を返すのは bot 対策の中間応答として +一般的な挙動で、GitHub Actions のような自動化された経路が弾かれていると考えられる。 + +**2. 開発用のサンドボックス環境から: そもそも到達できない** + +egress ポリシーにより `www.editeur.org:443` への CONNECT が 403 で拒否される。 +このため、URL の確認も、`http_archive` が要求する `sha256` の計算もできない。 + +この 2 つが重なった結果、次のことがすべて止まっている。 + +- コード生成 (`make build`、`make generated/...`) +- 最新スキーマへの追従 (現在 ONIX 2.1 Issue 36 / 3.0 Issue 52。 + EDItEUR の最新は ONIX 3.1.3 + codelists Issue 73) + +なお、ユニットテストは ADR-0002 でこの依存から切り離したため影響を受けない。 +止まっているのは生成系だけである。 + +## 決定 + +**この ADR は決定を保留し、選択肢と判断材料を提示する。** 有力な選択肢のうち 2 つが +第三者著作物の再配布を伴い、ライセンスの判断はメンテナが行うべきものであるため。 + +暫定の運用としては、**手元に落とした zip を Bazel の `--distdir` 経由で使う**方法を推奨する。 +これは EDItEUR が意図する配布経路 (ブラウザでのダウンロード) をそのまま使い、 +何も回避せず、リポジトリに再配布物も置かない。 + +```sh +# ブラウザで zip を取得し、任意のディレクトリに置く +mkdir -p third_party/distdir +mv ~/Downloads/ONIX_BookProduct_XSD_schema+codes_Issue_52.zip third_party/distdir/ + +# WORKSPACE の sha256 と一致すれば、Bazel はネットワークに出ずにこれを使う +npx bazelisk build --distdir=third_party/distdir onix_v3 +``` + +`schema/v2` / `schema/v3` を手で用意した場合も動く。`schema/%` は prerequisite を持たない +パターンルールなので、ディレクトリが既に存在すれば make はそのターゲットを最新とみなし、 +ダウンロードを試みない。 + +## 理由 + +`--distdir` は Bazel が公式に用意している、まさにこの状況 (取得元に到達できないが、 +ファイルの実体はある) のための仕組みである。`sha256` による同一性の検証も効いたままなので、 +取得経路が変わっても再現性は落ちない。 + +決定を保留するのは、恒久的な解決策が技術的な選択ではなくライセンスの判断だからである。 +ここで勝手に vendoring すると、判断を経ずに再配布を既成事実にしてしまう。 + +## 検討した他の選択肢 + +- **スキーマをリポジトリに取り込む (vendoring)**: 最も確実で、CI もサンドボックスも + ネットワークなしで完結する。生成物の再現性も上がる。ただし EDItEUR の配布物の再配布に + あたるため、ライセンス条件の確認が要る。**メンテナが確認のうえ問題なければ、これが本命。** +- **自前のミラーを用意する (GitHub Releases、S3 など)**: `http_archive` の `urls` に + フォールバックとして並べれば、EDItEUR 側の可用性に左右されなくなる。ただし + 再配布である点は vendoring と変わらず、加えてミラーの維持コストが乗る。 +- **User-Agent を偽装してダウンロードする**: 202 が bot 対策なら、ブラウザを装えば通る可能性がある。 + ただし Bazel 3.7.0 の `http_archive` はカスタムヘッダに対応しておらず、独自の + repository rule が要る。何より、配布元が設けたアクセス制御を迂回する行為であり、 + 権利者の意図を確認せずに実装すべきではない。**採らない。** +- **`sha256` を省略する**: `http_archive` は `sha256` なしでも動くが、202 の問題は解決しないうえ、 + 取得物の同一性検証を捨てることになる。**採らない。** + +## 結果 + +- 生成系は、メンテナが手元に zip を用意できる環境でのみ実行できる。CI では実行できない。 +- CI で検証できるのは、ユニットテスト (ADR-0002) と、コミット済み生成物に対する + e2e スナップショット (`//e2e/go:snapshot_test`) の 2 つ。後者は生成物の実行時の挙動を + 守り続けるので、生成が止まっている間も回帰は検出できる。 +- 最新スキーマへの追従は、この ADR が Accepted になるまで着手できない。 + vendoring かミラーのいずれかが決まれば、CI でも生成と差分確認ができるようになり、 + AGENTS.md が要求する「生成物の diff を読んで後方互換性を確認する」手順が + レビューの中で回せるようになる。 diff --git a/docs/adr/README.md b/docs/adr/README.md index fa8323f..3bf78db 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -8,6 +8,7 @@ | ------------------------------------------------------------- | ---------------------------------------------- | ---------- | | [0001](0001-record-architecture-decisions.md) | 設計判断を ADR として記録する | Accepted | | [0002](0002-decouple-unit-tests-from-the-vendored-schema.md) | ユニットテストを取得済みスキーマから切り離す | Accepted | +| [0003](0003-editeur-schema-acquisition.md) | EDItEUR スキーマの取得方法 | Proposed | ## 書き方 From 5af27c01bae0a7cad4f482fd497cdc406905c85c Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:08:42 +0000 Subject: [PATCH 04/12] 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 05/12] 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 116f35aa794e140e55fce996ccbc73d62757cbb0 Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:13:23 +0000 Subject: [PATCH 06/12] Correct ADR-0003 after review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two things that would have misled a reader: The ADR asserted "EDItEUR の最新は ONIX 3.1.3 + codelists Issue 73" flatly, eight lines after saying editeur.org is unreachable. Secondary sources do corroborate the version, but the zip URL, filename and sha256 — what WORKSPACE actually needs — are unknown, so say that instead. "schema/v2 / schema/v3 を手で用意した場合も動く" was under-specified enough to be wrong. The archives carry a top-level directory while src/Lib.hs reads flat paths, matching what BUILD.bazel's genrule flattens, so a plain unzip does not work. Spell out `unzip -j`. Also: --distdir does not compose with `make build`, since the Makefile passes no flags through, so point at .bazelrc; note that a missing distdir is only an INFO, which makes a typo look exactly like the original 202; add --override_repository as a distinct option, since it skips the sha256 check and is therefore the one that works when chasing a release whose checksum is not known yet; correct the claim that the e2e snapshot guards the generated output, which holds only for generated/go/v2; and record that the schema version is hardcoded in three more places. .gitignore now covers third_party/distdir, which the ADR tells people to create. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .gitignore | 1 + docs/adr/0003-editeur-schema-acquisition.md | 57 +++++++++++++++++---- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index c442ee8..6cae55e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /bazel-* /*.prof /schema +/third_party/distdir/ diff --git a/docs/adr/0003-editeur-schema-acquisition.md b/docs/adr/0003-editeur-schema-acquisition.md index 18f9959..dfac1b6 100644 --- a/docs/adr/0003-editeur-schema-acquisition.md +++ b/docs/adr/0003-editeur-schema-acquisition.md @@ -37,8 +37,12 @@ egress ポリシーにより `www.editeur.org:443` への CONNECT が 403 で拒 この 2 つが重なった結果、次のことがすべて止まっている。 - コード生成 (`make build`、`make generated/...`) -- 最新スキーマへの追従 (現在 ONIX 2.1 Issue 36 / 3.0 Issue 52。 - EDItEUR の最新は ONIX 3.1.3 + codelists Issue 73) +- 最新スキーマへの追従 (現在 ONIX 2.1 Issue 36 / 3.0 Issue 52) + +追従先の最新版がどれかは、**この ADR の時点では確定できていない**。二次情報では +ONIX 3.1.3 + codelists Issue 73 (2026-04) とされているが、editeur.org に到達できない以上、 +一次情報での裏取りができていない。そして `WORKSPACE` が実際に必要とするもの、すなわち +**zip の URL とファイル名、その sha256 は完全に不明**である。追従作業は、まずここの確認から始まる。 なお、ユニットテストは ADR-0002 でこの依存から切り離したため影響を受けない。 止まっているのは生成系だけである。 @@ -61,9 +65,30 @@ mv ~/Downloads/ONIX_BookProduct_XSD_schema+codes_Issue_52.zip third_party/distdi npx bazelisk build --distdir=third_party/distdir onix_v3 ``` -`schema/v2` / `schema/v3` を手で用意した場合も動く。`schema/%` は prerequisite を持たない -パターンルールなので、ディレクトリが既に存在すれば make はそのターゲットを最新とみなし、 -ダウンロードを試みない。 +`schema/v2` / `schema/v3` を手で用意する場合は、**展開の仕方に注意が要る**。zip には +`ONIX_BookProduct_XSD_schema+codes_Issue_52/` のようなトップレベルディレクトリがあるが、 +`src/Lib.hs` は `./schema/v3/ONIX_BookProduct_3.0_reference.xsd` という平坦なパスを読む。 +これは `BUILD.bazel` の genrule (`cp $(SRCS) $(RULEDIR)/v3`) が平坦化した結果に合わせたもので、 +素の `unzip` では階層がひとつ深くなって読めない。`-j` で平坦に展開する。 + +```sh +mkdir -p schema/v3 +unzip -j ONIX_BookProduct_XSD_schema+codes_Issue_52.zip -d schema/v3 +``` + +こうして置いたディレクトリは尊重される。`schema/%` は prerequisite を持たないパターンルールなので、 +ディレクトリが既に存在すれば make はそのターゲットを最新とみなし、ダウンロードを試みない +(スクラッチの Makefile で再現して確認済み。`schema/v2` だけがある状態では `schema/v3` のみ取得しにいく)。 + +なお `make build` から `--distdir` を渡す口は今のところ無い。Makefile はフラグを素通ししないので、 +恒久的に使うなら `.bazelrc` に書くのが早い。 + +``` +common --distdir=third_party/distdir +``` + +指定したディレクトリが存在しない場合、Bazel はエラーにせず INFO を出して素通りする。 +つまり **パスを間違えても、元の 202 と見分けがつかない失敗になる**。まずディレクトリの存在を確かめること。 ## 理由 @@ -83,18 +108,30 @@ npx bazelisk build --distdir=third_party/distdir onix_v3 フォールバックとして並べれば、EDItEUR 側の可用性に左右されなくなる。ただし 再配布である点は vendoring と変わらず、加えてミラーの維持コストが乗る。 - **User-Agent を偽装してダウンロードする**: 202 が bot 対策なら、ブラウザを装えば通る可能性がある。 - ただし Bazel 3.7.0 の `http_archive` はカスタムヘッダに対応しておらず、独自の - repository rule が要る。何より、配布元が設けたアクセス制御を迂回する行為であり、 + ただし Bazel 3.7.0 の `http_archive` はカスタムヘッダに対応していない。`repository_ctx.download` + の `auth` も `Authorization` ヘッダ専用なので、独自の repository rule を書いても足りず、 + 結局 curl などを呼び出すことになる。何より、配布元が設けたアクセス制御を迂回する行為であり、 権利者の意図を確認せずに実装すべきではない。**採らない。** +- **`--override_repository` を使う**: `--override_repository=org_editeur_v3=/path/to/dir` で、 + 取得済みのディレクトリを外部リポジトリの代わりに使える (3.7.0 にある)。`--distdir` の変種ではなく、 + **sha256 の検証を経由しない**点が本質的に違う。この ADR の出発点は「sha256 が計算できない」ことなので、 + 新しいリリースを追う場面ではむしろこちらしか使えない。既知の版を再現するなら `--distdir`、 + 未知の版を試すなら `--override_repository`、と使い分ける。 - **`sha256` を省略する**: `http_archive` は `sha256` なしでも動くが、202 の問題は解決しないうえ、 - 取得物の同一性検証を捨てることになる。**採らない。** + 取得物の同一性検証を捨てることになる。さらに Bazel の distdir 探索は sha256 が + 与えられている場合にしか走らないので、`sha256` を捨てるとこの ADR が推奨する + `--distdir` 自体が効かなくなる。**採らない。** ## 結果 - 生成系は、メンテナが手元に zip を用意できる環境でのみ実行できる。CI では実行できない。 - CI で検証できるのは、ユニットテスト (ADR-0002) と、コミット済み生成物に対する - e2e スナップショット (`//e2e/go:snapshot_test`) の 2 つ。後者は生成物の実行時の挙動を - 守り続けるので、生成が止まっている間も回帰は検出できる。 + e2e スナップショット (`//e2e/go:snapshot_test`) の 2 つ。ただし後者が依存しているのは + `//generated/go/v2:go` だけで、`generated/go/v3` と `generated/typescript/v2` には + CI のカバレッジが無い。生成物の回帰を検出できるのは Go の v2 に限られる。 +- 取得の問題が解けても、3.1.x への追従はそれだけでは終わらない。版が + `org_editeur_v3.bazel` の glob、`BUILD.bazel` の filegroup 名 (`onix3p0p7`)、 + `src/Lib.hs` の `schemaRoot` の 3 箇所にハードコードされている。 - 最新スキーマへの追従は、この ADR が Accepted になるまで着手できない。 vendoring かミラーのいずれかが決まれば、CI でも生成と差分確認ができるようになり、 AGENTS.md が要求する「生成物の diff を読んで後方互換性を確認する」手順が From 2818911bcb02156cbb4b42cd2cd7897badebaafc Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:39:46 +0000 Subject: [PATCH 07/12] Add a temporary workflow to observe what editeur.org returns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-0003 records that CI gets 202 Accepted instead of the archive, and that this environment cannot reach the host at all to check anything first-hand — not the URLs, not the current release, not the sha256. That leaves the ADR resting partly on secondary sources, and leaves the next step guessing. A CI runner can reach the host, so ask it. This job issues plain GETs and prints the status, headers and first bytes of what comes back, then lists the zip links the download pages advertise. No headers are forged and nothing is worked around; it only observes. Manual trigger only, so it never runs on push. It is deleted again in the next commit once it has produced its output, and the findings go into ADR-0003. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .github/workflows/probe-editeur.yml | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/probe-editeur.yml diff --git a/.github/workflows/probe-editeur.yml b/.github/workflows/probe-editeur.yml new file mode 100644 index 0000000..0a6f1a0 --- /dev/null +++ b/.github/workflows/probe-editeur.yml @@ -0,0 +1,39 @@ +# Temporary diagnostic, not part of the build. It only observes what +# editeur.org returns to a plain request from a CI runner, so that ADR-0003 can +# record facts instead of guesses. Manual trigger only; delete once it has run. +name: probe editeur +on: workflow_dispatch + +jobs: + probe: + name: Probe editeur.org + runs-on: ubuntu-latest + steps: + - name: Request the pages and archives + run: | + probe() { + echo "==================================================" + echo "URL: $1" + curl -sS -L -o /tmp/body -D /tmp/head \ + -w 'result: http=%{http_code} size=%{size_download} type=%{content_type} redirects=%{num_redirects}\n' \ + "$1" || echo "curl exited $?" + echo "--- response headers ---" + cat /tmp/head || true + echo "--- what did we actually get? ---" + file /tmp/body || true + head -c 400 /tmp/body | tr -d '\0' || true + echo + } + + probe "https://www.editeur.org/93/Release-3.0-and-3.1-Downloads/" + probe "https://www.editeur.org/files/ONIX%202.1/ONIX_for_Books_Release2-1_rev03_schema+codes_Issue_36.zip" + probe "https://www.editeur.org/files/ONIX%203/ONIX_BookProduct_XSD_schema+codes_Issue_52.zip" + + echo "==================================================" + echo "zip links advertised on the download pages:" + for page in \ + "https://www.editeur.org/93/Release-3.0-and-3.1-Downloads/" \ + "https://www.editeur.org/15/Archived-Previous-Releases/" ; do + echo "--- $page" + curl -sS -L "$page" | grep -oE 'href="[^"]*\.(zip|ZIP)"' | sort -u || true + done From 11b32e272bd225ef13779c3435fb67f6872c7fdc Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:40:10 +0000 Subject: [PATCH 08/12] Trigger the probe on push workflow_dispatch cannot be dispatched through the API for a workflow that does not exist on the default branch, so the run never started. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .github/workflows/probe-editeur.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/probe-editeur.yml b/.github/workflows/probe-editeur.yml index 0a6f1a0..1336550 100644 --- a/.github/workflows/probe-editeur.yml +++ b/.github/workflows/probe-editeur.yml @@ -1,8 +1,8 @@ # Temporary diagnostic, not part of the build. It only observes what # editeur.org returns to a plain request from a CI runner, so that ADR-0003 can -# record facts instead of guesses. Manual trigger only; delete once it has run. +# record facts instead of guesses. Runs on push on this branch only; deleted once it has run. name: probe editeur -on: workflow_dispatch +on: push jobs: probe: From 1ba635deba5a0cc55329828750c8c6e556c05faa Mon Sep 17 00:00:00 2001 From: kogai Date: Sun, 13 Sep 2026 07:41:32 +0000 Subject: [PATCH 09/12] Identify the 202 as a SiteGround CAPTCHA, and drop the probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The probe ran and answered the question the ADR had been guessing at. The 202 is a CAPTCHA challenge: HTTP/2 202 server: nginx sg-captcha: challenge x-robots-tag: noindex It is not specific to the archives: the download page itself answers the same way, and the redirect embeds the requesting runner's IP, so the decision is made about the caller rather than the file. That settles three things the ADR previously left open. The URLs are not stale, so pointing at a newer release changes nothing. Retries and mirror URLs cannot help. And CI can never fetch these archives without solving the CAPTCHA — which is precisely the control the publisher put there, so that route is closed on purpose rather than by accident. It also makes the User-Agent option moot on its own terms: passing the challenge needs JavaScript and a cookie, not a header. The reason for rejecting it is unchanged, but it is no longer only a matter of principle. The practical consequence, now recorded: manual acquisition is not a stopgap until something is fixed upstream. It is permanent unless the schemas are vendored or mirrored — which is the licensing decision this ADR is waiting on. The probe workflow is removed again in the same change; it existed only to produce that output. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .github/workflows/probe-editeur.yml | 39 ----------------- docs/adr/0003-editeur-schema-acquisition.md | 48 ++++++++++++++++----- 2 files changed, 38 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/probe-editeur.yml diff --git a/.github/workflows/probe-editeur.yml b/.github/workflows/probe-editeur.yml deleted file mode 100644 index 1336550..0000000 --- a/.github/workflows/probe-editeur.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Temporary diagnostic, not part of the build. It only observes what -# editeur.org returns to a plain request from a CI runner, so that ADR-0003 can -# record facts instead of guesses. Runs on push on this branch only; deleted once it has run. -name: probe editeur -on: push - -jobs: - probe: - name: Probe editeur.org - runs-on: ubuntu-latest - steps: - - name: Request the pages and archives - run: | - probe() { - echo "==================================================" - echo "URL: $1" - curl -sS -L -o /tmp/body -D /tmp/head \ - -w 'result: http=%{http_code} size=%{size_download} type=%{content_type} redirects=%{num_redirects}\n' \ - "$1" || echo "curl exited $?" - echo "--- response headers ---" - cat /tmp/head || true - echo "--- what did we actually get? ---" - file /tmp/body || true - head -c 400 /tmp/body | tr -d '\0' || true - echo - } - - probe "https://www.editeur.org/93/Release-3.0-and-3.1-Downloads/" - probe "https://www.editeur.org/files/ONIX%202.1/ONIX_for_Books_Release2-1_rev03_schema+codes_Issue_36.zip" - probe "https://www.editeur.org/files/ONIX%203/ONIX_BookProduct_XSD_schema+codes_Issue_52.zip" - - echo "==================================================" - echo "zip links advertised on the download pages:" - for page in \ - "https://www.editeur.org/93/Release-3.0-and-3.1-Downloads/" \ - "https://www.editeur.org/15/Archived-Previous-Releases/" ; do - echo "--- $page" - curl -sS -L "$page" | grep -oE 'href="[^"]*\.(zip|ZIP)"' | sort -u || true - done diff --git a/docs/adr/0003-editeur-schema-acquisition.md b/docs/adr/0003-editeur-schema-acquisition.md index dfac1b6..0391075 100644 --- a/docs/adr/0003-editeur-schema-acquisition.md +++ b/docs/adr/0003-editeur-schema-acquisition.md @@ -18,16 +18,42 @@ http_archive( 2026-09-13 時点で、この取得が機能しない。観測された事実は次の 2 つ。 -**1. CI から: `202 Accepted` が返る** +**1. CI から: CAPTCHA チャレンジが返る** ``` WARNING: Download from https://www.editeur.org/files/ONIX%202.1/...Issue_36.zip failed: UnrecoverableHttpException GET returned 202 Accepted ``` -zip の代わりに 202 が返り、Bazel はこれを回復不能なエラーとして扱う。1 回再実行しても -同じ結果だったため、一時的な不調ではない。202 を返すのは bot 対策の中間応答として -一般的な挙動で、GitHub Actions のような自動化された経路が弾かれていると考えられる。 +zip の代わりに 202 が返り、Bazel はこれを回復不能なエラーとして扱う。 + +この 202 の正体は、CI ランナーから素の GET を投げて確認した。**SiteGround の +CAPTCHA チャレンジ**である。 + +``` +HTTP/2 202 +server: nginx +sg-captcha: challenge +x-robots-tag: noindex +content-type: text/html +content-length: 248 + + + +``` + +重要なのは、**これがファイル固有の問題ではない**ことである。同じ応答が返るのは +zip だけでなく、ダウンロードページ (`/93/Release-3.0-and-3.1-Downloads/`) 自体も同様だった。 +`sg-captcha: challenge` と、クライアントの IP を埋め込んだリダイレクト先 +(`y=ipr:`) から、判定はリクエスト元に対して行われていると分かる。 + +したがって次のことが言える。 + +- URL が古いから失敗しているのではない。新しいリリースの URL に変えても結果は同じである。 +- リトライやミラー URL の追加では解決しない。 +- **CI から自動でダウンロードすることは、CAPTCHA を解かない限り不可能である。** + そして CAPTCHA はまさに、配布元が自動アクセスを制限するために置いたものである。 **2. 開発用のサンドボックス環境から: そもそも到達できない** @@ -107,11 +133,11 @@ common --distdir=third_party/distdir - **自前のミラーを用意する (GitHub Releases、S3 など)**: `http_archive` の `urls` に フォールバックとして並べれば、EDItEUR 側の可用性に左右されなくなる。ただし 再配布である点は vendoring と変わらず、加えてミラーの維持コストが乗る。 -- **User-Agent を偽装してダウンロードする**: 202 が bot 対策なら、ブラウザを装えば通る可能性がある。 - ただし Bazel 3.7.0 の `http_archive` はカスタムヘッダに対応していない。`repository_ctx.download` - の `auth` も `Authorization` ヘッダ専用なので、独自の repository rule を書いても足りず、 - 結局 curl などを呼び出すことになる。何より、配布元が設けたアクセス制御を迂回する行為であり、 - 権利者の意図を確認せずに実装すべきではない。**採らない。** +- **User-Agent を偽装してダウンロードする**: 上の調査で、これは筋が悪いだけでなく + 技術的にも足りないことが分かった。返ってくるのは CAPTCHA チャレンジであり、 + 通過するには JS の実行とクッキーの保持が要る。ヘッダを 1 つ足して済む話ではない。 + そして何より、配布元が自動アクセスを制限するために置いた仕組みを迂回する行為である。 + **採らない。** - **`--override_repository` を使う**: `--override_repository=org_editeur_v3=/path/to/dir` で、 取得済みのディレクトリを外部リポジトリの代わりに使える (3.7.0 にある)。`--distdir` の変種ではなく、 **sha256 の検証を経由しない**点が本質的に違う。この ADR の出発点は「sha256 が計算できない」ことなので、 @@ -124,7 +150,9 @@ common --distdir=third_party/distdir ## 結果 -- 生成系は、メンテナが手元に zip を用意できる環境でのみ実行できる。CI では実行できない。 +- 生成系は、メンテナが手元に zip を用意できる環境でのみ実行できる。**CI では実行できない。** + これは当面の不便ではなく、CAPTCHA がある限り恒久的な制約である。この点は、 + vendoring かミラーかを判断する材料として重い。「そのうち直る」類の問題ではない。 - CI で検証できるのは、ユニットテスト (ADR-0002) と、コミット済み生成物に対する e2e スナップショット (`//e2e/go:snapshot_test`) の 2 つ。ただし後者が依存しているのは `//generated/go/v2:go` だけで、`generated/go/v3` と `generated/typescript/v2` には From 828d1e8511c13938a3e0bb4aef924cfc51e75fc7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 12:24:51 +0000 Subject: [PATCH 10/12] Record what the EDItEUR licence actually appears to say MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leaving the blocker at 'this needs a licensing decision' was too vague to act on, so look up the terms instead. EDItEUR describes the specifications and tools as free under a 'highly-permissive licence', no registration and no royalties, which reads as though redistribution were fine. The licence text itself is narrower: it has the user agree not to copy any part of the DTD or XML Schema for use outside strictly internal use in their own organisation, and to notify EDItEUR first for anything beyond that. If that is right, it moves both durable options. Vendoring into a public repository and hosting a mirror are not 'strictly internal', so they likely require notifying EDItEUR rather than merely reading a licence — which is more reason for the maintainer to make this call, not less. The --distdir route is unaffected: the maintainer downloads for their own use and nothing is redistributed. This is secondary-source reading. editeur.org, where the actual licence lives, is unreachable from here, and the ADR says so. Also correct the codelist issue number: newer information puts Issue 74 as the current one for 3.0 and 3.1, not 73. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- docs/adr/0003-editeur-schema-acquisition.md | 34 ++++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/docs/adr/0003-editeur-schema-acquisition.md b/docs/adr/0003-editeur-schema-acquisition.md index 0391075..ffc2ebf 100644 --- a/docs/adr/0003-editeur-schema-acquisition.md +++ b/docs/adr/0003-editeur-schema-acquisition.md @@ -66,17 +66,40 @@ egress ポリシーにより `www.editeur.org:443` への CONNECT が 403 で拒 - 最新スキーマへの追従 (現在 ONIX 2.1 Issue 36 / 3.0 Issue 52) 追従先の最新版がどれかは、**この ADR の時点では確定できていない**。二次情報では -ONIX 3.1.3 + codelists Issue 73 (2026-04) とされているが、editeur.org に到達できない以上、 +ONIX 3.1.x + codelists Issue 74 とされているが (当初 Issue 73 と書いていたが、より新しい情報では 74 が 3.0 / 3.1 共通の現行版)、editeur.org に到達できない以上、 一次情報での裏取りができていない。そして `WORKSPACE` が実際に必要とするもの、すなわち **zip の URL とファイル名、その sha256 は完全に不明**である。追従作業は、まずここの確認から始まる。 なお、ユニットテストは ADR-0002 でこの依存から切り離したため影響を受けない。 止まっているのは生成系だけである。 +## ライセンスについて調べたこと + +「ライセンスの判断が要る」で止めるのは雑なので、条件そのものを調べた。ただし +**editeur.org に到達できないため、一次情報は確認できていない**。以下は二次情報である。 + +EDItEUR は、仕様・XML ツール・ガイダンスの利用を「無料、登録不要、ロイヤリティ不要、 +highly-permissive licence のもとで提供」と説明している。ここだけ見ると再配布も +問題なさそうに読める。 + +しかし利用許諾の本文とされる記述は、より限定的である。要旨は次のとおり。 + +> DTD または XML Schema の一部を、**自組織内での厳密に内部的な利用を除いて**、 +> 追加・削除・改変したり、外部での利用のために複製したりしないことに同意する。 +> 内部的でない目的で追加・改変・抜粋を行いたい場合は、まず EDItEUR に通知すること。 + +これが正しければ、**公開リポジトリへの取り込みもミラーの設置も「厳密に内部的な利用」には +あたらない**。つまり vendoring とミラーは、単にライセンスを読めば済む話ではなく、 +**EDItEUR への事前通知を要する行為**ということになる。 + +一方 `--distdir` 運用は、メンテナが自分で使うために手元にダウンロードするだけなので、 +何も再配布しない。この点でも他の選択肢と質的に違う。 + ## 決定 **この ADR は決定を保留し、選択肢と判断材料を提示する。** 有力な選択肢のうち 2 つが -第三者著作物の再配布を伴い、ライセンスの判断はメンテナが行うべきものであるため。 +第三者著作物の再配布を伴い、上記のとおり EDItEUR への通知が前提になる可能性が高いため。 +これはメンテナが行うべき判断であり、実装で既成事実にしてよいものではない。 暫定の運用としては、**手元に落とした zip を Bazel の `--distdir` 経由で使う**方法を推奨する。 これは EDItEUR が意図する配布経路 (ブラウザでのダウンロード) をそのまま使い、 @@ -128,11 +151,12 @@ common --distdir=third_party/distdir ## 検討した他の選択肢 - **スキーマをリポジトリに取り込む (vendoring)**: 最も確実で、CI もサンドボックスも - ネットワークなしで完結する。生成物の再現性も上がる。ただし EDItEUR の配布物の再配布に - あたるため、ライセンス条件の確認が要る。**メンテナが確認のうえ問題なければ、これが本命。** + ネットワークなしで完結する。生成物の再現性も上がる。ただし上記のとおり、公開リポジトリへの + 取り込みは「内部的な利用」を超えるため、**EDItEUR への事前通知が要る可能性が高い**。 + 通知して問題なければ、これが本命。 - **自前のミラーを用意する (GitHub Releases、S3 など)**: `http_archive` の `urls` に フォールバックとして並べれば、EDItEUR 側の可用性に左右されなくなる。ただし - 再配布である点は vendoring と変わらず、加えてミラーの維持コストが乗る。 + 再配布である点は vendoring と変わらず (同じく通知が要る)、加えてミラーの維持コストが乗る。 - **User-Agent を偽装してダウンロードする**: 上の調査で、これは筋が悪いだけでなく 技術的にも足りないことが分かった。返ってくるのは CAPTCHA チャレンジであり、 通過するには JS の実行とクッキーの保持が要る。ヘッダを 1 つ足して済む話ではない。 From ca408a25ef02822350249790f516790bca4e4faf Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 12:28:09 +0000 Subject: [PATCH 11/12] Record that ONIX 2.1 is recoverable from this repo's own history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Missed on the first pass: commit 9352123 (2021-01-19) deleted 2_1_rev03_schema/, and the full ONIX 2.1 rev03 set is still reachable from its parent. Six XSDs, no network required, so v2 code generation can be unblocked today with git alone. It also sits differently from vendoring and mirroring on the licensing question. Those would start redistributing; these files have been in this repository's public history since 2021 and still are. Restoring them acknowledges a publication that already happened rather than making a new one. The maintainer deleted them deliberately, so it is still their call — but it is a smaller one. It does not reach the goal: only 2.1 is in the history, not 3.0 or 3.1. Reading those recovered files also settles what the ADR could say about the licence. readme.txt, readme2.txt and the XSD headers carry copyright notices and nothing else — no grant of any kind. The licence text exists only on editeur.org, which is unreachable, so the ADR's licensing discussion stays secondary-source and now says so explicitly. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- docs/adr/0003-editeur-schema-acquisition.md | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/adr/0003-editeur-schema-acquisition.md b/docs/adr/0003-editeur-schema-acquisition.md index ffc2ebf..be35e63 100644 --- a/docs/adr/0003-editeur-schema-acquisition.md +++ b/docs/adr/0003-editeur-schema-acquisition.md @@ -162,6 +162,24 @@ common --distdir=third_party/distdir 通過するには JS の実行とクッキーの保持が要る。ヘッダを 1 つ足して済む話ではない。 そして何より、配布元が自動アクセスを制限するために置いた仕組みを迂回する行為である。 **採らない。** +- **v2 だけは git 履歴から復元する**: 見落としていたが、**このリポジトリの履歴に + ONIX 2.1 rev03 のスキーマ一式が残っている**。2021-01-19 のコミット 9352123 が + `2_1_rev03_schema/` を削除した際の親コミットから、ネットワークなしで取り出せる。 + + ```sh + mkdir -p schema/v2 + for f in ONIX_BookProduct_CodeLists.xsd ONIX_BookProduct_Release2.1_reference.xsd \ + ONIX_BookProduct_Release2.1_short.xsd ONIX_XHTML_Subset.xsd \ + ONIX_XHTML_Subset_reference.xsd ONIX_XHTML_Subset_short.xsd ; do + git show 9352123^:2_1_rev03_schema/$f > schema/v2/$f + done + ``` + + これで v2 のコード生成は editeur.org なしで動く。**再配布という観点でも他の選択肢と + 質的に違う**: これらのファイルは 2021 年から現在まで、このリポジトリの公開履歴に + 存在し続けている。復元は新たな公開ではなく、既に起きている公開の追認である。 + ただしメンテナが意図的に削除した経緯があるため、戻すかどうかは判断が要る。 + そして**これは 2.1 のみで、3.0 / 3.1 は履歴にも無い**。 - **`--override_repository` を使う**: `--override_repository=org_editeur_v3=/path/to/dir` で、 取得済みのディレクトリを外部リポジトリの代わりに使える (3.7.0 にある)。`--distdir` の変種ではなく、 **sha256 の検証を経由しない**点が本質的に違う。この ADR の出発点は「sha256 が計算できない」ことなので、 @@ -172,6 +190,19 @@ common --distdir=third_party/distdir 与えられている場合にしか走らないので、`sha256` を捨てるとこの ADR が推奨する `--distdir` 自体が効かなくなる。**採らない。** +## ライセンス条項の一次情報について + +上記の履歴から、EDItEUR の配布物そのもの (`readme.txt` / `readme2.txt` および各 XSD の +ヘッダ) を確認した。記載されているのは著作権表示のみで、**許諾条項は含まれていない**。 + +``` +COPYRIGHT (c) EDItEUR 2005–2013 +(c) 2004-2006 EDItEUR / http://www.editeur.org/ +``` + +つまり許諾の本文は editeur.org 上にしか存在せず、到達できない以上、この ADR の +ライセンスに関する記述は二次情報のままである。この点は解消できなかった。 + ## 結果 - 生成系は、メンテナが手元に zip を用意できる環境でのみ実行できる。**CI では実行できない。** From 8ccf915f7c91661f65daa0dc6029844a6d8a2866 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 12:34:59 +0000 Subject: [PATCH 12/12] Accept ADR-0003 and make --distdir a real path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kogai approved the proposal on #58, so the ADR moves to Accepted and the interim it recommends stops being advice and becomes wiring: .bazelrc gains build --distdir=third_party/distdir, and that directory gets a README with the procedure. Archives stay gitignored, so nothing from EDItEUR is committed and the sha256 check is untouched — a wrong download still fails loudly. What the approval covers is written into the ADR explicitly, because a two-word approval on a document offering several options should not be read broadly: --distdir is adopted, and the choice between vendoring and a mirror stays open. Both of those likely require notifying EDItEUR, and this ADR defers them by design; approving the document is not the same as making that call. Note the review landed on 1ba635d, before the two commits that added the licence findings and the git-history recovery route. Both are material to the deferred choice, so they are worth a second look. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01P8rZakwAiz1jpViUX34x1Z --- .bazelrc | 7 +++++++ .gitignore | 3 ++- docs/adr/0003-editeur-schema-acquisition.md | 17 +++++++++++++++-- docs/adr/README.md | 2 +- third_party/distdir/README.md | 15 +++++++++++++++ 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 third_party/distdir/README.md diff --git a/.bazelrc b/.bazelrc index d7e09dd..c5c78a5 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,10 @@ build --verbose_failures run --verbose_failures test --verbose_failures + +# Accepted interim for schema acquisition (ADR-0003): editeur.org answers +# automated requests with a CAPTCHA, so archives are placed here by hand and +# bazel reuses them instead of fetching. The sha256 in MODULE.bazel/WORKSPACE +# still has to match, so this does not weaken verification. A missing or empty +# directory is only an INFO, so this is harmless when unused. +build --distdir=third_party/distdir diff --git a/.gitignore b/.gitignore index ac4163c..e68fa3d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ /*.prof /schema node_modules/ -/third_party/distdir/ +/third_party/distdir/* +!/third_party/distdir/README.md diff --git a/docs/adr/0003-editeur-schema-acquisition.md b/docs/adr/0003-editeur-schema-acquisition.md index be35e63..c04fa46 100644 --- a/docs/adr/0003-editeur-schema-acquisition.md +++ b/docs/adr/0003-editeur-schema-acquisition.md @@ -1,6 +1,6 @@ # ADR-0003: EDItEUR スキーマの取得方法 -- **ステータス**: Proposed (メンテナの判断待ち) +- **ステータス**: Accepted (2026-09-13, #58 にて承認) - **日付**: 2026-09-13 ## 背景 @@ -97,7 +97,20 @@ highly-permissive licence のもとで提供」と説明している。ここだ ## 決定 -**この ADR は決定を保留し、選択肢と判断材料を提示する。** 有力な選択肢のうち 2 つが +**暫定運用として `--distdir` を採用する。** 恒久策 (vendoring / ミラー) の選択は保留する。 + +この ADR は #58 で承認された。承認されたのは以下の内容であり、**vendoring と +ミラーの実施を承認したものではない** (それらは下記のとおり EDItEUR への通知を +伴う可能性が高く、この ADR 自身が判断を保留している)。 + +- `--distdir` を正式な取得手段とすること。`.bazelrc` に + `build --distdir=third_party/distdir` を追加し、`third_party/distdir/` に + 手順を置いた。アーカイブ自体は gitignore される。 +- 恒久策の選択は引き続き保留であること。 + +以下は承認時点の判断材料であり、記録として残す。 + +**選択肢と判断材料:** 有力な選択肢のうち 2 つが 第三者著作物の再配布を伴い、上記のとおり EDItEUR への通知が前提になる可能性が高いため。 これはメンテナが行うべき判断であり、実装で既成事実にしてよいものではない。 diff --git a/docs/adr/README.md b/docs/adr/README.md index 3bf78db..27f79d9 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -8,7 +8,7 @@ | ------------------------------------------------------------- | ---------------------------------------------- | ---------- | | [0001](0001-record-architecture-decisions.md) | 設計判断を ADR として記録する | Accepted | | [0002](0002-decouple-unit-tests-from-the-vendored-schema.md) | ユニットテストを取得済みスキーマから切り離す | Accepted | -| [0003](0003-editeur-schema-acquisition.md) | EDItEUR スキーマの取得方法 | Proposed | +| [0003](0003-editeur-schema-acquisition.md) | EDItEUR スキーマの取得方法 | Accepted | ## 書き方 diff --git a/third_party/distdir/README.md b/third_party/distdir/README.md new file mode 100644 index 0000000..23212b5 --- /dev/null +++ b/third_party/distdir/README.md @@ -0,0 +1,15 @@ +# distdir + +Bazel looks here for archives before going to the network, so the ONIX schema +zips can be placed by hand when editeur.org will not serve them to an automated +request. See `docs/adr/0003-editeur-schema-acquisition.md`. + +Download the archive named in `MODULE.bazel` (or `WORKSPACE`) with a browser, +drop the file in this directory unrenamed, and build as usual: + + npx bazelisk build onix_v3 + +Bazel matches the file by its sha256, so a wrong or truncated download fails +loudly rather than silently producing bad output. + +Archives themselves are gitignored — nothing from EDItEUR is committed here.