feat(agent-commerce): Product Feed/Catalog/Discovery を実装 (#6794 トラックA)#6815
feat(agent-commerce): Product Feed/Catalog/Discovery を実装 (#6794 トラックA)#6815nanasess wants to merge 10 commits into
Conversation
ACP/UCP 対応の共通基盤のうち CheckoutSession 非依存の先行スライスを実装。 トラック A (Product Feed / Discovery) を解放する最小集合。 - MinorUnitConverter: 通貨 minor unit 変換 (bcmath / ゼロデシマル / 負数) - AddressMappingService: 住所マッピング・国コード numeric→alpha-2 (全249件網羅) - AgentCommerceScopeRegistry: <protocol>:<capability> scope 照合 - KeyStoreInterface / FilesystemKeyStore: 鍵保管 (env パス上書き→既定ファイル、EC-CUBE#6797 雛形) - AgentCommerceMessageSignerInterface / UcpMessageSigner: RFC 9421 EC P-256 / JWK 公開鍵 / 鍵ローテーション grace - BaseInfo にフラグ5 (acp/ucp 有効化等、default false) + google_pay_merchant_id + migration - 秘密鍵は dtb_baseinfo でなく app/keystore/ に保管 (EC-CUBE#6797、.htaccess/.gitignore 多重防御) プライバシー/利用規約 URL はカラム化せず標準ページ (help_privacy/help_agreement) から自動生成する方針。 検証: PHPUnit 53 tests / 611 assertions / 0 失敗、PHPStan level6 No errors、php-cs-fixer 0件。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
minor-unit は通貨の小数桁数だけ桁が増える (一律 ×100 ではない) ことが 一目で分かるよう、docblock の例を JPY (×1) / USD (×100) の 2 桁までに統一。 3 桁通貨 (BHD 等) の 4 桁例は紛らわしいため削除。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ACP/UCP 用の 6 カラムは BaseInfo エンティティに #[ORM\Column] で定義済みのため、 公式アップデート手順 (doctrine:schema:update --force) で自動反映される。 カラム追加に ALTER TABLE マイグレーションを書かないのが EC-CUBE の慣例 (前例 PR EC-CUBE#4912: カラム追加に ALTER マイグレーション無し、INSERT のみ)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI の rector ジョブ (PR EC-CUBE#6802) で検出された 8 ファイルの指摘を vendor/bin/rector で適用。 - AddressMappingService: 冗長な (int) キャストと三項を null 合体演算子に簡約 - UcpMessageSigner: コンストラクタプロパティ昇格 - テスト各種: self:: → $this->、final class 化、strict_types 宣言等 AgentCommerce テスト 53 件すべて成功を確認。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
決済はプラグイン化方針 (Stripe 同様) のため、Google Pay の merchant_id は core BaseInfo に持たせない。UCP discovery の payment_handlers は決済ハンドラ プラグインが寄与する設計とする。あわせて rector 適用後の php-cs-fixer 整形 (ライセンスヘッダ直後の空行) をテストへ反映。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AddressMappingService の numeric->alpha-2 ハードコード const (249件) を廃止し、 新規マスタ mtb_country_iso_code で管理する。PR EC-CUBE#6802 レビュー指摘 (マスタテーブル化) に対応。 - 新規マスタ CountryIsoCode / CountryIsoCodeRepository を追加 - mtb_* の固定スキーマ (id/name/sort_no/discriminator_type) に準拠し、id=ISO numeric / name=alpha-2 を格納 (discriminator=countryisocode) - 新規インストールは import_csv (definition.yml 登録)、既存環境は INSERT データ migration で backfill (mtb_country は改変しない) - AddressMappingService はリポジトリ経由解決へ変更、テストを Layer 2 化 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI rector ジョブ (ContainerGetNameToTypeInTestsRector / AssertFuncCallToPHPUnitAssertRector)
の指摘に対応。get('doctrine') + ManagerRegistry 手動構築をやめ、services_test.yaml で
AddressMappingService を public 化しコンテナから FQCN 取得する方式へ変更。
- services_test.yaml: AddressMappingService を public 化 (consumer 未実装で private では除去されるため)
- AddressMappingServiceTest: self::getContainer()->get(AddressMappingService::class) に簡素化
検証: PHPUnit 52/608、PHPStan level6 No errors、php-cs-fixer 0、rector dry-run クリーン。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAgent Commerce 向けに、ACP フィード送受信、UCP カタログ/ディスカバリ、関連 DTO/変換、管理画面操作、設定、テスト、CI 用スキーマ取得を追加した。国コード、金額変換、署名鍵管理、カタログキャッシュ無効化も含まれる。 ChangesAgent Commerce
Sequence Diagram(s)sequenceDiagram
participant 管理画面
participant AgentCommerceController
participant AcpFeedClient
participant UcpCatalogCache
participant UcpCatalogResponseBuilder
participant UcpProfileBuilder
管理画面->>AgentCommerceController: ACP push / UCP cache 操作
AgentCommerceController->>AcpFeedClient: pushFullReplacement / upsertProducts
AgentCommerceController->>UcpCatalogCache: warmup / clear
AgentCommerceController->>UcpCatalogResponseBuilder: buildSearchResponse
AgentCommerceController->>UcpProfileBuilder: build()
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
d26f1bd to
35879ae
Compare
9d06d61 to
1b58753
Compare
BaseInfo の有効化フラグを「checkout の有無を制御する」意味へ整理する。 discovery / catalog は公開して害がないため常時公開とし (ゲート撤去は EC-CUBE#6794)、 ACP feed push は認証情報の有無で実質ガードされるためフラグ不要とする。 - 改名: acp_enabled → acp_checkout_enabled / ucp_enabled → ucp_checkout_enabled (getter は isAcpCheckoutEnabled / isUcpCheckoutEnabled) - 削除: acp_feed_enabled (push は base URL + API key の有無でガード) / ucp_catalog_api_enabled (UCP Catalog は常時公開) - 維持: ucp_catalog_requires_auth (catalog の OAuth 必須モードを api4 着手時に実装) - dtb_base_info.csv (ja/en) ヘッダを 3 フラグへ更新 - 店舗設定 (ShopMasterType / @admin/Setting/Shop/shop_master.twig) に acp_checkout_enabled / ucp_checkout_enabled のトグルを追加 (checkout は日本未提供の注記つき) - BaseInfoAgentCommerceFlagsTest を 3 フラグへ更新 PHPStan level 6 No errors / php-cs-fixer 0 / 関連テスト green。 カラム変更は schema:update 方式 (ALTER マイグレーションは書かない)。 Refs EC-CUBE#6777 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8c8cd82 to
c162752
Compare
…トラックA) エージェントコマース共通基盤 Phase 1a (EC-CUBE#6777) の上に、CheckoutSession 非依存の トラック A (Product Feed / Catalog / Discovery) を 1 PR で実装する。 - Foundation: protocol 非依存 DTO (AgentCatalogItemDto 等) / ProductReferenceResolver (sku/product_class_id/barcode→ProductClass、barcode は Customize seam) / CatalogProvider (toIterable でメモリ蓄積回避) / CatalogMapper (表示商品のみ・price02→minor unit・availability・ 画像絶対 URL は RequestContext の scheme/host/port/baseUrl から組み立て、generateUrl(ABSOLUTE_URL) と整合) - ACP Feed: products.jsonl/metadata.json 生成 + pre-push スキーマ検証 (justinrainbow/json-schema) + push クライアント (AcpFeedClientInterface、outbound Bearer、Error shape 変換)。 実機送出は認証情報 (ECCUBE_AGENT_COMMERCE_ACP_FEED_BASE_URL / _API_KEY) の有無でガード、 Bearer は #[SensitiveParameter] (有効化フラグは持たない) - UCP Catalog: POST /catalog/{search,lookup,product} (v2026-04-08 の RPC 形式)。 UcpCatalogResponseBuilder (ucp wrapper) / FilesystemAdapter+Lock キャッシュ (stampede 防止) / gzip。 公開商品データのため常時公開 (フラグ無効化なし)。認証必須モード・GraphQL は api4 未導入のため TODO - Discovery: GET /.well-known/ucp。常時公開。UcpProfileBuilder (signing_keys=getPublicJwks 公開鍵のみ・ catalog capability を常時宣言・endpoint は RequestContext 動的生成)、Cache-Control public max-age=300。 payment_handlers はプラグイン寄与の tagged seam (既定空)。checkout capability は EC-CUBE#6574 で追加 - 管理画面 (content/agent_commerce) + CLI の 2 系統を標準提供: eccube:acp-feed:push --full / eccube:ucp-catalog:cache:warmup / :clear、管理画面ボタン (CSRF 検証・検証目的限定の注記)。 checkout の有効化トグル (ucp_checkout_enabled/acp_checkout_enabled) は店舗設定 (EC-CUBE#6777) 側 - キャッシュ無効化 EventListener (Product/ProductClass 更新→UCP キャッシュ clear) - 依存追加: symfony/http-client (ACP push 用、first-party)。framework.lock: flock (外部インフラ不要) - テスト: Layer 0 適合性 / Layer 1 マッピング / Layer 2 スキーマ契約・DB 突合 / Layer 3 Web / Layer 5 push。177 tests / 941 assertions / 0 failures (incomplete 7)。 spec schema は同梱せず: ACP は src/ の runtime リソース再利用、UCP は CI で公式リポジトリの リリースタグ v2026-04-08 を取得 (var/・gitignore) + ローカル specifications/ フォールバック、無ければ skip PHPStan level 6 No errors / php-cs-fixer 0 / rector clean。本 PR は eccube-api4 非依存。 Refs EC-CUBE#6794 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
c162752 to
0c0cdc0
Compare
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (7)
tests/Eccube/Tests/Service/AgentCommerce/Catalog/Acp/AcpFeedClientTest.php (1)
63-325: ⚡ Quick win
pushFullReplacementの単体テストが欠けています。公開 API の主要メソッドは一通り検証されていますが、
AcpFeedClientInterface::pushFullReplacement()経路だけこのテストクラスで未カバーです。生成物・戻り値形状・送信前検証の回帰を防ぐため、1 ケースでも追加しておくのが安全です。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Eccube/Tests/Service/AgentCommerce/Catalog/Acp/AcpFeedClientTest.php` around lines 63 - 325, Add a unit test in AcpFeedClientTest that covers AcpFeedClientInterface::pushFullReplacement(): mirror the existing upsertProducts tests by using $this->createClient([...]) with a jsonResponse (e.g. returning ['id'=>'feed_1','accepted'=>true]), call $client->pushFullReplacement('feed_1', $products) (use $this->validProduct('p1') to build payload), assert the returned accepted boolean, assert a single outbound request was recorded, and assert the recorded request uses the full-replacement HTTP method (PUT) to the feed products endpoint (self::BASE_URL.'/feeds/feed_1/products') with the expected body shape (['products'=>$products]); also add a test case that verifies pre-send validation rejects invalid product payloads and that no request is recorded (analogous to testUpsertProductsRejectsInvalidPayloadBeforeSending).tests/Eccube/Tests/Service/AgentCommerce/AddressMappingServiceTest.php (1)
86-161: ⚡ Quick win
CustomerAddress分岐のテストを追加してください。Line 81 のシグネチャは
CustomerAddressも受け付けますが、現状はCustomer/Shippingしか検証していません。CustomerAddressの1ケースを追加して、3系統すべてのマッピング回帰を防ぐのが安全です。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Eccube/Tests/Service/AgentCommerce/AddressMappingServiceTest.php` around lines 86 - 161, Add a new unit test in AddressMappingServiceTest that covers the CustomerAddress branch by instantiating a CustomerAddress object, setting the same fields used in existing tests (name01/name02/kana/company/postal/pref/addr/country/phone as applicable), calling $this->service->toAddressArray($customerAddress) and asserting the mapped keys (family_name, given_name, family_name_kana, given_name_kana, company, postal_code, region, address1/address2, country, phone) match the source values; mirror the pattern of testToAddressArrayFromCustomer and testToAddressArrayFromShipping so all three input types (Customer, Shipping, CustomerAddress) are covered and prevent regression of the CustomerAddress mapping branch.src/Eccube/Service/AgentCommerce/Discovery/EmptyPaymentHandlerRegistry.php (1)
40-43: ⚡ Quick win同一 reverse-domain キーの衝突がサイレント上書きされています
現在は後勝ちで上書きされるため、プラグイン競合時の原因追跡が難しくなります。重複キー検知時に例外または警告ログを出す実装にしておくと安全です。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Eccube/Service/AgentCommerce/Discovery/EmptyPaymentHandlerRegistry.php` around lines 40 - 43, The current merge loop in EmptyPaymentHandlerRegistry (iterating $this->registries and calling collect()) silently overwrites duplicate keys; update the merge logic in the method containing that loop to detect duplicates by checking isset($handlers[$key]) before assignment and either throw a meaningful exception (e.g., DuplicateHandlerKeyException) or emit a warning via the existing logger with details (conflicting key, originating registry class or identifier and previous registry) so plugin conflicts are visible; include the registry identifier when reporting to aid debugging and ensure behavior is consistent across all $this->registries iterations.tests/Eccube/Tests/Web/AgentCommerce/UcpDiscoveryControllerTest.php (1)
95-107: ⚡ Quick win
servicesの{}直列化確認が不足していますこのテスト名は
services/payment_handlers両方を対象にしていますが、raw 文字列での否定検証はpayment_handlersのみです。"services":[]も同様に否定アサートを追加して、回帰検知を揃えてください。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/Eccube/Tests/Web/AgentCommerce/UcpDiscoveryControllerTest.php` around lines 95 - 107, In testServicesAndPaymentHandlersAreObjects in UcpDiscoveryControllerTest (the test method that fetches agent_ucp_discovery and inspects $raw), add the same raw-string negative assertion used for payment_handlers to also assert that '"services":[]' does not appear; i.e., after the existing assertStringNotContainsString for payment_handlers, add a matching assertStringNotContainsString('"services":[]', $raw, 'ucp.services MUST serialize as a JSON object {} (not an array []) even when empty') so the test verifies both services and payment_handlers serialize as JSON objects rather than arrays.src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogResponseBuilder.php (1)
36-36: ⚡ Quick winUCP バージョン定数の重複を解消してください。
UCP_VERSION定数がUcpCatalogResponseBuilder::UCP_VERSIONとUcpProfileBuilder::UCP_VERSIONの両方で'2026-04-08'として定義されています。バージョンが変更される際に複数箇所の更新が必要となり、不整合のリスクがあります。共通の定数クラス(例:
UcpConstantsやAgentCommerceConstants)を作成し、バージョンを一元管理することを推奨します。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogResponseBuilder.php` at line 36, UCP_VERSION is duplicated in UcpCatalogResponseBuilder::UCP_VERSION and UcpProfileBuilder::UCP_VERSION; create a single shared constant class (e.g., UcpConstants or AgentCommerceConstants) with public const UCP_VERSION = '2026-04-08' and replace both usages to reference that centralized constant (e.g., UcpConstants::UCP_VERSION), updating any use-sites and imports to remove the duplicated definitions.src/Eccube/Service/AgentCommerce/Discovery/UcpProfileBuilder.php (1)
99-105: ⚖️ Poor tradeoffエンドポイント導出ロジックに潜在的な脆弱性があります。
Line 105 の
preg_replace('#/search$#', '', $searchUrl)は、生成された検索 URL の末尾から/searchを削除してカタログサービスのベース URL を導出していますが、以下の問題があります:
- ルート名
'agent_ucp_catalog_search'から生成される URL が/searchで終わらない場合(例: ルート定義変更、ロケールプレフィックス付与、クエリパラメータ追加など)、正規表現がマッチせず$searchUrlがそのままendpointとして使用される- このフォールバック動作により、誤った endpoint(
/catalog/searchなど)が profile に含まれる可能性があるより堅牢な実装として、カタログサービスのベースパスを専用のルート(例:
'agent_ucp_catalog_base')として定義するか、または文字列操作ではなく URL パース(parse_url/dirname)を使用することを推奨します。代替実装案
案1: 専用ベースルートの定義
# routing agent_ucp_catalog_base: path: /catalog # no controller, metadata only agent_ucp_catalog_search: path: /catalog/search # ...$endpoint = $this->urlGenerator->generate( 'agent_ucp_catalog_base', [], UrlGeneratorInterface::ABSOLUTE_URL );案2: URL パース(現行コードの改善)
$searchUrl = $this->urlGenerator->generate( 'agent_ucp_catalog_search', [], UrlGeneratorInterface::ABSOLUTE_URL ); $parsed = parse_url($searchUrl); $pathParts = explode('/', trim($parsed['path'] ?? '', '/')); array_pop($pathParts); // remove 'search' $basePath = '/' . implode('/', $pathParts); $endpoint = ($parsed['scheme'] ?? 'https') . '://' . ($parsed['host'] ?? '') . $basePath;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Eccube/Service/AgentCommerce/Discovery/UcpProfileBuilder.php` around lines 99 - 105, The current endpoint derivation using preg_replace('`#/search`$#', '', $searchUrl) can fail when the generated URL from urlGenerator->generate('agent_ucp_catalog_search', ...) does not literally end with "/search"; update the code to derive a robust base endpoint by either generating a dedicated base route (use urlGenerator->generate('agent_ucp_catalog_base', ..., UrlGeneratorInterface::ABSOLUTE_URL) instead of manipulating the search URL) or, if keeping a single route, parse the generated $searchUrl with parse_url() and remove the last path segment (the 'search' segment) to build $endpoint from scheme, host and the trimmed path; replace the preg_replace usage and ensure $endpoint always contains the true catalog base URL.src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogProductSerializer.php (1)
208-210: ⚡ Quick winコメントの記述が不正確です。
Line 209 のコメント「DTO が空 (全 null) の場合でも plain を空文字で出力すると minProperties:1 を満たさない」は論理的に誤りです。実際には
['plain' => '']を返すことで minProperties:1(最低1つのプロパティ)の制約は満たされます。正しい意図は「DTO が空の場合でも、UCP の minProperties:1 制約を満たすために、最低限
plainを空文字として出力する」だと思われます。コメントを修正してください。修正案
- * UCP では Product / Variant の description は必須かつ minProperties:1。 - * DTO が空 (全 null) の場合でも plain を空文字で出力すると minProperties:1 を満たさない - * ため、最低 1 形式 (plain) を保証する。html / markdown も値があれば付与する。 + * UCP では Product / Variant の description は必須かつ minProperties:1。 + * DTO が空 (全 null) の場合でも、minProperties:1 制約を満たすために + * 最低 1 形式 (plain を空文字) を保証する。html / markdown も値があれば付与する。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogProductSerializer.php` around lines 208 - 210, Update the inaccurate inline comment in UcpCatalogProductSerializer (the block explaining UCP description minProperties behavior) to state that when the DTO is empty we emit ['plain' => ''] to satisfy UCP's minProperties:1 requirement; replace the current line claiming that emitting an empty plain string does not satisfy the constraint with wording that clearly says we output an empty 'plain' property to ensure at least one property is present, and that html/markdown are added only if non-null.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/coverage.yml:
- Around line 68-71: Replace the tag-only clone in the "Fetch UCP spec schemas
(tag v2026-04-08)" step with a commit-SHA pinned workflow: clone the repo
(shallow), checkout the specific commit SHA
bb409d7489f14b3425510dd6a14166bc7b959409, then verify HEAD equals that SHA
(using git rev-parse HEAD) and fail the job if it does not match; update any
references to the original git clone invocation to use the new
checkout-and-verify flow so the fetched UCP spec is immutably pinned.
In `@app/DoctrineMigrations/Version20260604120000.php`:
- Around line 48-55: The current down() unconditionally deletes all rows from
mtb_country_iso_code which can destroy pre-existing master data; change down()
to be irreversible instead of deleting data: replace the DELETE logic in the
down() method (method name: down, constant: self::NAME, table:
mtb_country_iso_code) with throwing an IrreversibleMigrationException
(Doctrine\Migrations\IrreversibleMigrationException) and include a brief message
stating the migration cannot be safely rolled back to avoid accidental data
loss.
In `@app/keystore/.htaccess`:
- Around line 1-2: 既存の古いディレクティブ (order allow,deny と deny from all) のみだと Apache
2.4 以降の環境で拒否が効かない可能性があるので、互換性のために同じファイルに Apache 2.4 用のディレクティブ Require all denied
を併記してください — 該当箇所は .htaccess 内の "order allow,deny" / "deny from all" のブロックで、そこに
"Require all denied" を追加して mod_access_compat が無効な環境でも確実にアクセスを拒否できるようにしてください。
In `@src/Eccube/Command/AgentCommerce/AcpFeedPushCommand.php`:
- Around line 81-95: The current flow in AcpFeedPushCommand uses
$this->feedClient->getFeedProducts($feedId) then immediately calls
$this->feedClient->upsertProducts($feedId, $products), which re-uploads remote
data and prevents any local changes from being applied; either add a guard to
fail fast while diffing is unimplemented or switch to using locally-generated
upsert payloads. Concretely, replace the no-op path by (a) detecting when
$products came from the remote feed and calling $io->error(...) and return
Command::FAILURE to prevent accidental success, or (b) call a local generator
(e.g. createLocalProductsForUpsert or similar) and pass its result to
upsertProducts instead of $products; update the success/warning messages
accordingly and reference getFeedProducts and upsertProducts to locate the
change.
In `@src/Eccube/Controller/Admin/AgentCommerce/AgentCommerceController.php`:
- Around line 58-59: ucpCatalogCacheWarmup() calls buildDefaultSearchBody()
which uses json_encode(..., JSON_THROW_ON_ERROR) and currently doesn't catch
JsonException; wrap the call to buildDefaultSearchBody() (from
ucpCatalogCacheWarmup()) in a try/catch(JsonException $e) and on catch log the
error via the controller logger (or process logger) and perform an appropriate
redirect/flash error response instead of letting the exception bubble, or
alternatively remove JSON_THROW_ON_ERROR in buildDefaultSearchBody() if you
prefer silent failure; ensure the catch includes the exception message and
context so failures during json_encode are handled gracefully.
- Around line 161-165: The call to buildDefaultSearchBody() can throw
JsonException (it uses JSON_THROW_ON_ERROR) and ucpCatalogCacheWarmup()/the loop
calling $this->ucpCatalogCache->warmup(...) currently doesn't catch it, causing
a 500; wrap the buildDefaultSearchBody() invocation (or the surrounding warmup
loop in AgentCommerceController::ucpCatalogCacheWarmup or the method that
contains this snippet) in a try/catch that catches JsonException (and Exception
as fallback), translate the error into a user-facing admin message (e.g. add
flash/error or use the controller's message display mechanism) and skip/abort
the warmup gracefully instead of rethrowing so the admin UI shows the message
rather than a 500; ensure you reference buildDefaultSearchBody and
$this->ucpCatalogCache->warmup in the fix.
In `@src/Eccube/Controller/AgentCommerce/UcpCatalogController.php`:
- Around line 238-246: decodeCursor currently accepts arbitrarily large offsets
which allows expensive paging; fix it by clamping the parsed offset to a safe
maximum: add a MAX_OFFSET constant (e.g. MAX_OFFSET) near the class and in
decodeCursor parse the offset as you already do, ensure non-negative, then
return min(parsedOffset, self::MAX_OFFSET) (keeping the existing base64/offset
validation). Also ensure callers that call setFirstResult use the clamped value
returned from decodeCursor (reference decodeCursor and any use sites that call
setFirstResult).
- Around line 119-137: The handler allows an unbounded ids array which can cause
excessive DB work; modify the logic in UcpCatalogController (the block using
$ids, $seen, and resolveProductByIdentifier) to first validate and normalize
inputs (cast/filter to strings/ints), deduplicate the identifiers (unique array)
before any calls to resolveProductByIdentifier, enforce a configurable maximum
count (reject or trim and return an error/status when exceeded), and then
iterate only over the limited, deduplicated list to call
resolveProductByIdentifier and build $products while preserving the existing
checks for null product and duplicate product IDs.
In `@src/Eccube/Resource/locale/messages.en.yaml`:
- Line 1210: Replace the inconsistent English label value for the key
admin.setting.shop.shop.agent_commerce: currently set to "Agentic Commerce",
change it to "Agent Commerce" so it matches other usages (e.g., the navigation
label and the entry at line ~1150); update the YAML value only, preserving the
key and surrounding formatting.
In `@src/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedClient.php`:
- Around line 185-196: 現在のレスポンス処理は json_decode が失敗した場合に空配列へフォールバックしているため、2xx
でも壊れたレスポンスを成功扱いしている問題があります。AcpFeedClient の該当処理で $content が空でない場合は json_decode
の戻り値と json_last_error()/json_last_error_msg() を確認し、配列でない・デコードエラーが発生したら空配列へ落とさずに
toTransportException(またはプロジェクトで定義されている AcpFeedTransportException
を作成して投げる)を使って例外を投げるように修正してください。エラー投げる際は $status、デコードエラー情報、$method、$path
を渡して異常を明示するようにしてください。
In `@src/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedGenerator.php`:
- Around line 60-69: In generateProductsJsonl() in AcpFeedGenerator (where you
iterate catalogProvider->provideDisplayProducts(), mapProduct() and call
serializer->serialize()), validate each serialized Product against the feed
schema (schema.feed.json) before appending/writing the JSONL line: after mapping
and serializing the DTO, run the schema validator on the resulting
object/string, and if validation fails, skip that product and log the validation
errors (include product id/sku/context) instead of emitting the line; ensure the
validator is used consistently for the full-replacement (全置換) flow so only
schema-conformant product lines are written.
In `@src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogCache.php`:
- Around line 85-91: UcpCatalogCache::set() と clear() で Symfony の
FilesystemAdapter::save()/clear()
の戻り値を無視しているため永続化/削除失敗を検知できません。UcpCatalogCache::set(string $key, string $value) と
UcpCatalogCache::clear() 内で $this->adapter->save($item) と
$this->adapter->clear() の戻り値をチェックし、false の場合はロギング(例:
$this->logger->error(...))するか例外を投げるようにしてください。合わせて UcpCatalogCacheClearCommand と
UcpCatalogController の呼び出し側で返り値/例外を検知して成功/失敗のレスポンスや出力を切り替える(失敗時はエラーメッセージを表示し
exit コード/HTTP ステータスを適切に設定)ように修正してください。
In
`@src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogProductSerializer.php`:
- Around line 157-161: The empty-variants fallback in
UcpCatalogProductSerializer (the branch returning ['min'=> $zero, 'max'=>
$zero]) hardcodes 'JPY'; change it to use the shop default currency from
injected BaseInfo/shop settings instead: add a dependency (e.g., BaseInfo or a
Settings service) to UcpCatalogProductSerializer (constructor/property), obtain
the default currency via the appropriate method (e.g.,
getDefaultCurrency()/getDefaultCurrencyCode()) and use that value for 'currency'
in the $zero fallback so the serializer no longer hardcodes 'JPY' for empty
$variants.
In `@src/Eccube/Service/AgentCommerce/Security/FilesystemKeyStore.php`:
- Around line 59-64: The write() method in FilesystemKeyStore currently ignores
failures when creating the directory, writing the PEM and setting permissions;
update it to check mkdir() return (when directory doesn't exist), verify
file_put_contents($path, $pem) !== false, and confirm chmod($path, 0600)
returned true, and if any step fails throw a meaningful exception (e.g.,
RuntimeException or the store's existing exception type) so callers can detect
that the key was not saved; keep the same path/$pem variables and ensure the
thrown message includes the failing operation and $path for debugging.
In `@tests/Eccube/Tests/Service/AgentCommerce/Catalog/Acp/AcpFeedClientTest.php`:
- Line 259: 現在のアサーション uses case-sensitive comparison ('Authorization: Bearer
'.self::API_KEY) against $this->normalizedHeaders($call), which can false-fail
if header case differs; update the test in AcpFeedClientTest to perform a
case-insensitive check instead (e.g. normalize both header names/values to
lower-case or use a case-insensitive search) targeting the assertion line that
uses assertContains and the helper normalizedHeaders($call) so it verifies
presence of the Bearer token regardless of header case.
In `@tests/Eccube/Tests/Service/AgentCommerce/README.md`:
- Around line 13-19: The README currently contradicts ACP/UCP packaging: update
the text so it clearly states that UCP schemas are not bundled while ACP is
bundled as a runtime resource; specifically modify the paragraph that mentions
"ACP / UCP の JSON Schema は**リポジトリに同梱しません**" to say UCP is not bundled but ACP is
included for runtime, and ensure the sections referencing
AcpFeedSchemaContractTest / AcpFeedConformanceTest and the resource path
src/Eccube/Resource/AgentCommerce/Acp/schema.feed.json explicitly state that ACP
is packaged with the product for runtime use; apply the same clarification to
the later duplicate lines (the lines around the second mention at 48-49) so both
places consistently state "UCP: not bundled" and "ACP: bundled as runtime
resource."
In `@tests/Eccube/Tests/Service/AgentCommerce/Schema/SchemaValidatorTrait.php`:
- Around line 118-121: The code in SchemaValidatorTrait that reads the ACP
schema (using projectRoot(), json_decode and accessing $bundle->{'$id'}) assumes
the file exists and decodes correctly; validate the file_get_contents result and
json_decode output, check json_last_error(), ensure $bundle is an object and
isset($bundle->{'$id'}) before assigning acpBundleId and calling
SchemaStorage->addSchema; if any check fails, throw or log a descriptive
RuntimeException (e.g., "ACP schema file missing", "Invalid JSON in ACP schema:
<json error>", or "ACP schema missing $id") so failures are explicit and
SchemaStorage->addSchema is only called with a valid bundle.
- Around line 100-103: In SchemaValidatorTrait (around the block that calls
file_get_contents($file->getPathname()), json_decode and $storage->addSchema),
stop silently skipping bad input and fail-fast: check that file_get_contents did
not return false, decode produced a valid object (use
json_last_error()/json_last_error_msg()), and that $schema has the required $id
($schema->{'$id'}); if any check fails throw a clear RuntimeException (include
$file->getPathname() and the json error or missing-$id detail) so consumers see
why schema loading failed rather than silently leaving SchemaStorage incomplete.
---
Nitpick comments:
In
`@src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogProductSerializer.php`:
- Around line 208-210: Update the inaccurate inline comment in
UcpCatalogProductSerializer (the block explaining UCP description minProperties
behavior) to state that when the DTO is empty we emit ['plain' => ''] to satisfy
UCP's minProperties:1 requirement; replace the current line claiming that
emitting an empty plain string does not satisfy the constraint with wording that
clearly says we output an empty 'plain' property to ensure at least one property
is present, and that html/markdown are added only if non-null.
In `@src/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogResponseBuilder.php`:
- Line 36: UCP_VERSION is duplicated in UcpCatalogResponseBuilder::UCP_VERSION
and UcpProfileBuilder::UCP_VERSION; create a single shared constant class (e.g.,
UcpConstants or AgentCommerceConstants) with public const UCP_VERSION =
'2026-04-08' and replace both usages to reference that centralized constant
(e.g., UcpConstants::UCP_VERSION), updating any use-sites and imports to remove
the duplicated definitions.
In `@src/Eccube/Service/AgentCommerce/Discovery/EmptyPaymentHandlerRegistry.php`:
- Around line 40-43: The current merge loop in EmptyPaymentHandlerRegistry
(iterating $this->registries and calling collect()) silently overwrites
duplicate keys; update the merge logic in the method containing that loop to
detect duplicates by checking isset($handlers[$key]) before assignment and
either throw a meaningful exception (e.g., DuplicateHandlerKeyException) or emit
a warning via the existing logger with details (conflicting key, originating
registry class or identifier and previous registry) so plugin conflicts are
visible; include the registry identifier when reporting to aid debugging and
ensure behavior is consistent across all $this->registries iterations.
In `@src/Eccube/Service/AgentCommerce/Discovery/UcpProfileBuilder.php`:
- Around line 99-105: The current endpoint derivation using
preg_replace('`#/search`$#', '', $searchUrl) can fail when the generated URL from
urlGenerator->generate('agent_ucp_catalog_search', ...) does not literally end
with "/search"; update the code to derive a robust base endpoint by either
generating a dedicated base route (use
urlGenerator->generate('agent_ucp_catalog_base', ...,
UrlGeneratorInterface::ABSOLUTE_URL) instead of manipulating the search URL) or,
if keeping a single route, parse the generated $searchUrl with parse_url() and
remove the last path segment (the 'search' segment) to build $endpoint from
scheme, host and the trimmed path; replace the preg_replace usage and ensure
$endpoint always contains the true catalog base URL.
In `@tests/Eccube/Tests/Service/AgentCommerce/AddressMappingServiceTest.php`:
- Around line 86-161: Add a new unit test in AddressMappingServiceTest that
covers the CustomerAddress branch by instantiating a CustomerAddress object,
setting the same fields used in existing tests
(name01/name02/kana/company/postal/pref/addr/country/phone as applicable),
calling $this->service->toAddressArray($customerAddress) and asserting the
mapped keys (family_name, given_name, family_name_kana, given_name_kana,
company, postal_code, region, address1/address2, country, phone) match the
source values; mirror the pattern of testToAddressArrayFromCustomer and
testToAddressArrayFromShipping so all three input types (Customer, Shipping,
CustomerAddress) are covered and prevent regression of the CustomerAddress
mapping branch.
In `@tests/Eccube/Tests/Service/AgentCommerce/Catalog/Acp/AcpFeedClientTest.php`:
- Around line 63-325: Add a unit test in AcpFeedClientTest that covers
AcpFeedClientInterface::pushFullReplacement(): mirror the existing
upsertProducts tests by using $this->createClient([...]) with a jsonResponse
(e.g. returning ['id'=>'feed_1','accepted'=>true]), call
$client->pushFullReplacement('feed_1', $products) (use $this->validProduct('p1')
to build payload), assert the returned accepted boolean, assert a single
outbound request was recorded, and assert the recorded request uses the
full-replacement HTTP method (PUT) to the feed products endpoint
(self::BASE_URL.'/feeds/feed_1/products') with the expected body shape
(['products'=>$products]); also add a test case that verifies pre-send
validation rejects invalid product payloads and that no request is recorded
(analogous to testUpsertProductsRejectsInvalidPayloadBeforeSending).
In `@tests/Eccube/Tests/Web/AgentCommerce/UcpDiscoveryControllerTest.php`:
- Around line 95-107: In testServicesAndPaymentHandlersAreObjects in
UcpDiscoveryControllerTest (the test method that fetches agent_ucp_discovery and
inspects $raw), add the same raw-string negative assertion used for
payment_handlers to also assert that '"services":[]' does not appear; i.e.,
after the existing assertStringNotContainsString for payment_handlers, add a
matching assertStringNotContainsString('"services":[]', $raw, 'ucp.services MUST
serialize as a JSON object {} (not an array []) even when empty') so the test
verifies both services and payment_handlers serialize as JSON objects rather
than arrays.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 268efb1b-8e0c-4144-b096-cc3278845309
⛔ Files ignored due to path filters (5)
composer.lockis excluded by!**/*.locksrc/Eccube/Resource/doctrine/import_csv/en/dtb_base_info.csvis excluded by!**/*.csvsrc/Eccube/Resource/doctrine/import_csv/en/mtb_country_iso_code.csvis excluded by!**/*.csvsrc/Eccube/Resource/doctrine/import_csv/ja/dtb_base_info.csvis excluded by!**/*.csvsrc/Eccube/Resource/doctrine/import_csv/ja/mtb_country_iso_code.csvis excluded by!**/*.csv
📒 Files selected for processing (83)
.github/workflows/coverage.yml.github/workflows/unit-test.yml.gitignore.htaccessapp/DoctrineMigrations/Version20260604120000.phpapp/config/eccube/packages/eccube_nav.yamlapp/config/eccube/packages/framework.yamlapp/config/eccube/services.yamlapp/config/eccube/services_test.yamlapp/keystore/.gitkeepapp/keystore/.htaccesscomposer.jsonsrc/Eccube/Command/AgentCommerce/AcpFeedPushCommand.phpsrc/Eccube/Command/AgentCommerce/UcpCatalogCacheClearCommand.phpsrc/Eccube/Command/AgentCommerce/UcpCatalogCacheWarmupCommand.phpsrc/Eccube/Controller/Admin/AgentCommerce/AgentCommerceController.phpsrc/Eccube/Controller/AgentCommerce/UcpCatalogController.phpsrc/Eccube/Controller/AgentCommerce/UcpDiscoveryController.phpsrc/Eccube/Entity/BaseInfo.phpsrc/Eccube/Entity/Master/CountryIsoCode.phpsrc/Eccube/EventListener/AgentCommerce/CatalogCacheInvalidationListener.phpsrc/Eccube/Form/Type/Admin/ShopMasterType.phpsrc/Eccube/Repository/Master/CountryIsoCodeRepository.phpsrc/Eccube/Resource/AgentCommerce/Acp/schema.feed.jsonsrc/Eccube/Resource/AgentCommerce/README.mdsrc/Eccube/Resource/doctrine/import_csv/en/definition.ymlsrc/Eccube/Resource/doctrine/import_csv/ja/definition.ymlsrc/Eccube/Resource/locale/messages.en.yamlsrc/Eccube/Resource/locale/messages.ja.yamlsrc/Eccube/Resource/template/admin/Content/AgentCommerce/index.twigsrc/Eccube/Resource/template/admin/Setting/Shop/shop_master.twigsrc/Eccube/Service/AgentCommerce/AddressMappingService.phpsrc/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedClient.phpsrc/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedClientInterface.phpsrc/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedGenerator.phpsrc/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedProductSerializer.phpsrc/Eccube/Service/AgentCommerce/Catalog/Acp/AcpFeedValidator.phpsrc/Eccube/Service/AgentCommerce/Catalog/AgentCatalogBarcodeDto.phpsrc/Eccube/Service/AgentCommerce/Catalog/AgentCatalogDescriptionDto.phpsrc/Eccube/Service/AgentCommerce/Catalog/AgentCatalogItemDto.phpsrc/Eccube/Service/AgentCommerce/Catalog/AgentCatalogMediaDto.phpsrc/Eccube/Service/AgentCommerce/Catalog/AgentCatalogOptionDto.phpsrc/Eccube/Service/AgentCommerce/Catalog/AgentCatalogVariantDto.phpsrc/Eccube/Service/AgentCommerce/Catalog/AvailabilityStatus.phpsrc/Eccube/Service/AgentCommerce/Catalog/CatalogMapper.phpsrc/Eccube/Service/AgentCommerce/Catalog/CatalogProvider.phpsrc/Eccube/Service/AgentCommerce/Catalog/CatalogProviderInterface.phpsrc/Eccube/Service/AgentCommerce/Catalog/Exception/AcpFeedException.phpsrc/Eccube/Service/AgentCommerce/Catalog/Exception/AcpFeedTransportException.phpsrc/Eccube/Service/AgentCommerce/Catalog/Exception/AcpFeedValidationException.phpsrc/Eccube/Service/AgentCommerce/Catalog/ProductReferenceResolver.phpsrc/Eccube/Service/AgentCommerce/Catalog/ProductReferenceResolverInterface.phpsrc/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogCache.phpsrc/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogProductSerializer.phpsrc/Eccube/Service/AgentCommerce/Catalog/Ucp/UcpCatalogResponseBuilder.phpsrc/Eccube/Service/AgentCommerce/Discovery/EmptyPaymentHandlerRegistry.phpsrc/Eccube/Service/AgentCommerce/Discovery/PaymentHandlerRegistryInterface.phpsrc/Eccube/Service/AgentCommerce/Discovery/UcpProfileBuilder.phpsrc/Eccube/Service/AgentCommerce/MinorUnitConverter.phpsrc/Eccube/Service/AgentCommerce/Security/AgentCommerceMessageSignerInterface.phpsrc/Eccube/Service/AgentCommerce/Security/AgentCommerceScopeRegistry.phpsrc/Eccube/Service/AgentCommerce/Security/FilesystemKeyStore.phpsrc/Eccube/Service/AgentCommerce/Security/KeyStoreInterface.phpsrc/Eccube/Service/AgentCommerce/Security/UcpMessageSigner.phptests/Eccube/Tests/Service/AgentCommerce/AddressMappingServiceTest.phptests/Eccube/Tests/Service/AgentCommerce/BaseInfoAgentCommerceFlagsTest.phptests/Eccube/Tests/Service/AgentCommerce/Catalog/Acp/AcpFeedClientTest.phptests/Eccube/Tests/Service/AgentCommerce/Catalog/Acp/AcpFeedProductSerializerTest.phptests/Eccube/Tests/Service/AgentCommerce/Catalog/CatalogMapperTest.phptests/Eccube/Tests/Service/AgentCommerce/Catalog/Ucp/UcpCatalogProductSerializerTest.phptests/Eccube/Tests/Service/AgentCommerce/Conformance/AcpFeedConformanceTest.phptests/Eccube/Tests/Service/AgentCommerce/Conformance/AgentCommerceBaseConformanceTest.phptests/Eccube/Tests/Service/AgentCommerce/Conformance/UcpCatalogConformanceTest.phptests/Eccube/Tests/Service/AgentCommerce/Conformance/UcpDiscoveryConformanceTest.phptests/Eccube/Tests/Service/AgentCommerce/MinorUnitConverterTest.phptests/Eccube/Tests/Service/AgentCommerce/README.mdtests/Eccube/Tests/Service/AgentCommerce/Schema/AcpFeedSchemaContractTest.phptests/Eccube/Tests/Service/AgentCommerce/Schema/SchemaValidatorTrait.phptests/Eccube/Tests/Service/AgentCommerce/Schema/UcpCatalogSchemaContractTest.phptests/Eccube/Tests/Service/AgentCommerce/Security/AgentCommerceScopeRegistryTest.phptests/Eccube/Tests/Service/AgentCommerce/Security/UcpMessageSignerTest.phptests/Eccube/Tests/Web/AgentCommerce/UcpCatalogControllerTest.phptests/Eccube/Tests/Web/AgentCommerce/UcpDiscoveryControllerTest.php
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/coverage.yml:
- Line 8: The `code-quality: write` permission scope on line 8 is invalid and
not recognized by GitHub Actions, which will cause workflow validation to fail.
Replace the `code-quality` scope with a valid permission scope such as `checks`
or `contents` depending on what the workflow actually needs to accomplish. Refer
to the list of valid scopes provided in the comment: actions, artifact-metadata,
attestations, checks, contents, deployments, discussions, id-token, issues,
models, packages, pages, pull-requests, repository-projects, security-events,
statuses.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b79c1237-83d4-4de5-9df9-0e8d2a8970b9
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
.github/workflows/coverage.yml.github/workflows/unit-test.ymlcomposer.json
🚧 Files skipped from review as they are similar to previous changes (1)
- composer.json
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/coverage.yml:
- Line 8: The `code-quality: write` permission scope on line 8 is invalid and
not recognized by GitHub Actions, which will cause workflow validation to fail.
Replace the `code-quality` scope with a valid permission scope such as `checks`
or `contents` depending on what the workflow actually needs to accomplish. Refer
to the list of valid scopes provided in the comment: actions, artifact-metadata,
attestations, checks, contents, deployments, discussions, id-token, issues,
models, packages, pages, pull-requests, repository-projects, security-events,
statuses.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b79c1237-83d4-4de5-9df9-0e8d2a8970b9
⛔ Files ignored due to path filters (1)
composer.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
.github/workflows/coverage.yml.github/workflows/unit-test.ymlcomposer.json
🚧 Files skipped from review as they are similar to previous changes (1)
- composer.json
🛑 Comments failed to post (1)
.github/workflows/coverage.yml (1)
8-8:
⚠️ Potential issue | 🟠 Major🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # 期待結果: unknown permission scope エラーが消えること actionlint .github/workflows/coverage.ymlRepository: EC-CUBE/ec-cube
Length of output: 1584
code-qualityは無効な permission スコープです。ワークフローの検証に失敗します。Line 8 の
code-quality: writeは GitHub Actions で認識されない permission スコープです。有効なスコープ(checks、contentsなど)に置き換えてください。利用可能なスコープ一覧:actions、artifact-metadata、attestations、checks、contents、deployments、discussions、id-token、issues、models、packages、pages、pull-requests、repository-projects、security-events、statuses🧰 Tools
🪛 actionlint (1.7.12)
[error] 8-8: unknown permission scope "code-quality". all available permission scopes are "actions", "artifact-metadata", "attestations", "checks", "contents", "deployments", "discussions", "id-token", "issues", "models", "packages", "pages", "pull-requests", "repository-projects", "security-events", "statuses"
(permissions)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/coverage.yml at line 8, The `code-quality: write` permission scope on line 8 is invalid and not recognized by GitHub Actions, which will cause workflow validation to fail. Replace the `code-quality` scope with a valid permission scope such as `checks` or `contents` depending on what the workflow actually needs to accomplish. Refer to the list of valid scopes provided in the comment: actions, artifact-metadata, attestations, checks, contents, deployments, discussions, id-token, issues, models, packages, pages, pull-requests, repository-projects, security-events, statuses.Source: Linters/SAST tools
概要 (Overview・Refs Issue)
エージェントコマース共通基盤 Phase 1a (#6802) の上に、CheckoutSession に依存しないトラック A (Product Feed / Catalog / Discovery) を 1 PR で実装します。UCP Discovery + UCP Catalog は国内で活用可能なエコシステムの入口として独立した価値があり、checkout (#6776 / #6574) の完成を待たず先行リリースできます。
方針 (Policy)
symfony/http-client(ACP push 用・first-party) のみ。/catalog/search・/catalog/lookup・/catalog/product)。一次仕様 v2026-04-08 (source/services/shopping/rest.openapi.json) で確認。capabilitydev.ucp.shopping.catalog.search/.lookup。read-only のため RFC 9421 署名は OPTIONAL (discovery の公開鍵広告のみ)。products.jsonl+metadata.jsonを生成し OpenAI がホストする Feed API へ送出。実機送出は国内 GA 前のため interface 分離 + 認証情報 (base URL + API key) の有無でガードし、テストはMockHttpClient。push 前のスキーマ検証 (justinrainbow/json-schema) を client 側で必須化 (サーバ ack は{accepted:bool}と粗いため)。*_checkout_enabled(ACP/UCP 共通基盤: エージェントコマース向け CheckoutSession・マッピング層の整備 #6777) は checkout の有無のみを制御する (日本未提供のため既定 OFF・本 PR では未配線、ユニバーサル コマース プロトコル(UCP)の対応 #6574/Agentic Commerce Protocol (ACP) の対応 #6776 で使用)。feed push は認証情報でガード(専用フラグなし)。ucp_catalog_requires_authは catalog の OAuth 必須モード (api4) 用に残置。/admin/content/agent_commerce(コンテンツ管理メニュー)。定期同期 (cron/Scheduler/Messenger) は opt-in。app/keystore/(共通の鍵保管ディレクトリapp/keystore/の導入 (各種暗号鍵・シークレットの標準置き場) #6797)、公開鍵 JWK はUcpMessageSigner::getPublicJwks()で実行時導出。signing_keys[]に秘密鍵を混入しない。RequestContext動的生成 (ハードコードしない)。画像絶対 URL もRequestContextの scheme/host/port/baseUrl から組み立て (TRUSTED_PROXIES は RequestContext 経由で考慮)。実装に関する補足 (Appendix)
新規クラス (主要):
Catalog/AgentCatalogItemDtoほか DTO 群Catalog/ProductReferenceResolverCatalog/CatalogProvidertoIterable+ 定期clear()でメモリ蓄積回避)Catalog/CatalogMapperCatalog/Acp/AcpFeedProductSerializer/AcpFeedGenerator/AcpFeedValidator/AcpFeedClientproducts.jsonl+metadata.json生成 / pre-push スキーマ検証 / push クライアントCatalog/Ucp/UcpCatalogProductSerializer/UcpCatalogResponseBuilder/UcpCatalogCacheucpwrapper / Filesystem+Lock+gzip キャッシュController/AgentCommerce/UcpCatalogControllerPOST /catalog/{search,lookup,product}(常時公開・gzip)Discovery/UcpProfileBuilder/Controller/AgentCommerce/UcpDiscoveryController/.well-known/ucpprofile 組み立て / 配信 (Cache-Control public max-age=300)Discovery/PaymentHandlerRegistryInterface(+ Empty 実装)Command/AgentCommerce/*eccube:acp-feed:push --full/eccube:ucp-catalog:cache:warmup/:clearController/Admin/AgentCommerce/AgentCommerceController/admin/content/agent_commerce・push / キャッシュ操作・CSRF 検証・検証目的限定の注記)EventListener/AgentCommerce/CatalogCacheInvalidationListener設定・本体:
composer.json/lock:symfony/http-client追加app/config/eccube/services.yaml: interface alias / env bind (ACP feed base URL・API key) / cache dir bindapp/config/eccube/packages/framework.yaml:lock: flock(外部インフラ不要・共有レンサバ適合)app/config/eccube/packages/eccube_nav.yaml: コンテンツ管理メニューにagent_commerce追加src/Eccube/Resource/locale/messages.{ja,en}.yaml: 翻訳キー追加src/Eccube/Resource/AgentCommerce/Acp/schema.feed.jsonのみ同梱 (runtime の pre-push 検証に必須・出所/Apache-2.0 をsrc/Eccube/Resource/AgentCommerce/README.mdに明記)。契約テストもこれを再利用unit-test.yml/coverage.yml) で公式リポジトリをvar/に clone (commit 固定)。ローカルはECCUBE_UCP_SCHEMA_DIRかspecifications/ucpフォールバック、無ければ該当テスト skip。手順はtests/Eccube/Tests/Service/AgentCommerce/README.mdレビュー時の注意:
ucp_catalog_requires_auth、検証は Phase 1b のAgentCommerceOAuth2Authenticator待ち) と GraphQL 版。/.well-known/acp.json) は Status:Proposal/unreleased のため未実装。variants[].inputs(input_correlation) は未実装:lookup_responseは要求 id との相関を MUST とするが、要求 id→variant 相関の実装は本 PR 範囲外。スキーマ契約テストがmarkTestIncompleteで要件を追跡 (UcpCatalogController::lookupに TODO)。search / get_product は適合済。テスト (Test)
tests/Eccube/Tests/Service/AgentCommerce/(+Web/AgentCommerce) に Layer 0 (適合性) / 1 (マッピング) / 2 (スキーマ契約・DB 突合) / 3 (Web: catalog/discovery) / 5 (push) を追加。markTestIncomplete。Layer 3'/Layer 9 + UCP lookupinputsへ委譲)paths: src): No errorscurl での動作確認 (誰でも試せる)
discovery / catalog は認証不要・常時公開なのでそのまま叩けます。ベース URL は環境に合わせて変更してください (例は
symfony serveのhttps://127.0.0.1:8000、自己署名証明書のため-kを付与)。確認ポイント:
price=price02_inc_tax)。list_priceはprice01 > price02(割引時) のみ税込で出力。商品詳細ページと一致します。RequestContext由来 (例https://127.0.0.1:8000/...)。bin/console eccube:acp-feed:push --feed-id <id> --fullを実行 (未設定時はAcpFeedExceptionで停止)。bin/console eccube:ucp-catalog:cache:clear(または管理画面のキャッシュクリア)。商品更新時は EventListener が自動クリア。相談 (Discussion)
4.4へ rebase して diff を整理します。マイナーバージョン互換性保持のための制限事項チェックリスト
レビュワー確認項目
🤖 Generated with Claude Code
Summary by CodeRabbit
リリースノート