package_spec (JSON array ↔ list[str], legacy scalar → one-element list) and unit-test round-trip + legacy casescrm_contract_product.package_spec (and quote product column if sharing codec) via SQL script + update docs/sql/shanxia2.0.sql; verify migration applies on local DBcontract_product CRUD _row_to_dict / create-update; expose packageSpecs (+ compat packageSpec); verify get/create/update with multi and legacy rowsproductId has no duplicate package across lines; verify API returns clear 4xx on violation_quote_products_to_contract_products so scalar quote package becomes one-element packageSpecs; verify create-from-quote still worksContractProductItem (and quote item if needed) for packageSpecs; run field-consistency check if applicablepl_contract_plan_qty matching: resolve contract line by productId + package ∈ line set; pool arranged = sum over all packages on that line; verify unit tests for shared remaining 715-10-10=695test_pl_contract_plan_qty.py / arrange-check tests for both patternsattach_plan_qty_fields_to_products to return line aggregates + per-package breakdown fields; verify contract detail payload shapeget_contract_product_arrange_qty_detail (+ API response) to return pool availableQty / contractQty and optional packageBreakdown; verify GET arrange-qty for either package on a shared line returns same remainingassert_contract_qty_for_arrange / package-against-contract validation messages for pool semantics; verify arrange hard-block when pool exceededdocs/superpowers/specs/2026-09-10-plan-product-package-key-design.md for multi-package shared linesContractFormDialog / form.vue): packaging multi-select + client uniqueness checks; verify save of multi-package line and reject duplicate package across rowsContractDetailDialog / detail.vue: multi-package cell + {{packageSpecs}} aggregation; verify printed HTML shows both specsapi/crm/contract for packageSpecs; smoke-check list/detail/form compilepl-plan-contract-product.ts (and consumers) to read packageSpecs[] / decode legacy; verify packageSpecsForProduct lists all packages from multi linesusePlContractToPlan / contract→plan mapping still sets single plan packageSpec; verify no regression on single-package contractspl_contract_plan_qty / arrange-check / package-validate suites; fix failures./scripts/update-graph.sh for touched backend/frontend after code changes