SearchAdd Legacy Rule Matrix
更新日期:2026-08-10
這份文件是 legacy rule ledger。每條規則都要能追到 legacy source、current owner 與驗證狀態。
閱讀方式:
- 先看 Summary,找 rule 與狀態。
- 再看 Rule Details,確認 source、side effect、rollback 邊界。
- 若要改 current,先把對應 rule 補到至少
legacy_traced。
狀態定義與 phase 順序見 legacy_canonical_rules.md。
Summary
000-500 Request Materialization
| Rule ID | Phase | Rule | Status | Owner | Verification |
|---|---|---|---|---|---|
SA-LEGACY-0001 | 000 | raw request / form normalization | needs_legacy_reaudit | SearchAddRequestNormalizer, SearchAddInput | SearchAddInputTest, SearchAddStep2IntegrationTest |
SA-LEGACY-0101 | 100 | session / credential actor resolution | gap | CredentialActorResolver, SearchAddLineIdentityRepository | LINE 已由 fb8897d7 完成;device-only pending |
SA-LEGACY-0102 | 100 | auto signup and login side effects | gap | AutoSignupService, ApiSession, UserLogin | LINE link 已由 fb8897d7 完成;existing-session reuse pending |
SA-LEGACY-0201 | 200 | pre-create category / form guards | needs_legacy_reaudit | SearchAddHandler | SearchAddStep2IntegrationTest |
SA-LEGACY-0301 | 300 | match type normalization and fee preparation | needs_legacy_reaudit | QuoteRequestCreator, RequestFeeUpdater | SearchAddStep2IntegrationTest |
SA-LEGACY-0401 | 400 | quote request materialization | needs_legacy_reaudit | QuoteRequestCreator | SearchAddStep2IntegrationTest |
SA-LEGACY-0501 | 500 | form submission and summary writers | gap | FormSubmissionWriter, SearchAddHandler | inner_text tests pending |
SA-LEGACY-0502 | 500 | tracking / abuse / abnormal scan writers | needs_legacy_reaudit | SearchAddHandler | SearchAddStep4IntegrationTest |
600-900 Matching, Special Flows, Async
| Rule ID | Phase | Rule | Status | Owner | Verification |
|---|---|---|---|---|---|
SA-LEGACY-0601 | 600 | auto quote candidate source and filter order | needs_legacy_reaudit | AutoQuoteDispatcher, AutoQuoteMatcher | SearchAddStep5IntegrationTest |
SA-LEGACY-0602 | 600 | narrow fallback and related-category fill | needs_legacy_reaudit | NarrowMatchBuilder | SearchAddStep5IntegrationTest |
SA-LEGACY-0701 | 700 | bid row finalize, pricing, activity, counters | needs_legacy_reaudit | QuoteBidFinalizer, BidPricingResolver | SearchAddStep6IntegrationTest |
SA-LEGACY-0801 | 800 | preserve provider branch | needs_legacy_reaudit | SpecialFlowDispatcher, AutoQuoteMatcher | SearchAddStep7IntegrationTest |
SA-LEGACY-0802 | 800 | authorized contact branch | verified | AuthorizedMatchProcessor, AuthorizedContactProcessor, QuoteBid::processWantToContactProvider | SearchAddStep7IntegrationTest, QuoteBidsWantToContactProviderTest |
SA-LEGACY-0803 | 800 | direct reserve branch | needs_legacy_reaudit | DirectReserveProcessor, QuoteBid::processWantToContactProvider | SearchAddStep7IntegrationTest |
SA-LEGACY-0804 | 800 | direct user branch | needs_legacy_reaudit | DirectUserProcessor, QuoteBid::processWantToContactProvider | SearchAddStep7IntegrationTest |
SA-LEGACY-0805 | 800 | booking branch | needs_legacy_reaudit | SpecialFlowDispatcher | SearchAddStep8IntegrationTest |
SA-LEGACY-0901 | 900 | second-link manual / related async fallback | needs_legacy_reaudit | QuoteRequest::checkR1ManualMatchStartNoAutoQuote, NewQuoteRequestHandler | SearchAddStep9IntegrationTest |
1000-1100 Response And Shared Downstream
| Rule ID | Phase | Rule | Status | Owner | Verification |
|---|---|---|---|---|---|
SA-LEGACY-1001 | 1000 | public response and error mapping | needs_legacy_reaudit | Endpoint/V1/SearchAdd.php | SearchAddStep2IntegrationTest, SearchAddStep7IntegrationTest |
SA-LEGACY-1101 | 1100 | shared contact provider payment / transaction / notification method | verified | QuoteBid::processWantToContactProvider | QuoteBidsWantToContactProviderTest, QuoteBidsProviderAcceptNarrowMatchTest, SearchAddStep7IntegrationTest |
Rule Details
SA-LEGACY-0101 Session / Credential Actor Resolution
Status: gap
Legacy Source
| File | Lines | Rule |
|---|---|---|
/Users/mattsu/Documents/Site/get-lancer-docker-2/get-lancer/app/Plugin/Quotes/Controller/QuoteRequestsController.php | 2061-2111 | social input 是互斥 chain:Google → Facebook → Facebook limited → Apple → LINE。 |
| 同上 | 2122-2188 | linked social actor、email mismatch、password、device、LINE same-email 綁定。 |
| 同上 | 2355-2378 | actor failure response。 |
app/Model/LineVerifyLog.php | 13-17 | 完整 id token 精確查 verify log;無 expiry / status / order guard。 |
app/Model/LineUser.php | 13-52 | sub 找 user、user link guard、line_users insert。 |
Current Legacy Rules
User[line_id_token]不替 request 補 email;request email missing 時回error=11。- linked LINE user lookup 不加
users.is_active=1。 - token sub 未綁、但 token email 等於既有 user email 時,不需 password / device,即採用 user 並新增 link。
- token 已綁不同 email:無 password 回
error=11;有 password 回error=3。 - device id 相同不再設定 user id,因此 device-only 最後回
error=2。 - LINE success 不增加 response 欄位。
Current Mapping / Gap
| Area | PHP 8 current | Gap |
|---|---|---|
| input | SearchAddInput.php:318-335 | LINE getter 與「前序 social input 未出現才處理」已實作。 |
| identity | CredentialActorResolver.php:23-155,259-275,407-425,467-492、SearchAddLineIdentityRepository.php:15-109 | LINE actor / link 已實作;不改這次範圍外的既有 social 行為。 |
| device | CredentialActorResolver.php:116-130 | 仍允許 device-only actor,尚未對齊。 |
| OAuth precursor | Endpoint/V1/Line.php:27-190 | verify log 已存在並由 search_add 沿用,沒有重打 LINE verify API。 |
Verification:SearchAddInputTest 9/24、CredentialActorResolverTest 12/62、LINE repository integration 2/20 均通過;成功 actor 的 user_id 皆驗證為 string。Next step 是 device-only negative test;若要保留 current 行為,先記 migration decision。
SA-LEGACY-0102 Auto Signup And Login Side Effects
Status: gap
Legacy Source
| File | Lines | Rule |
|---|---|---|
QuoteRequestsController.php | 2206-2282 | 建 user 後,以 user id 寫 line_users。 |
| 同上 | 2343-2347 | user/device 已有 session 時原 token reuse。 |
| 同上 | 3147-3152 | session 不存在才建立。 |
app/Model/ApiSession.php | 34-80 | 新 session 寫 api_sessions 與 api_session_logs.action=login。 |
| 同上 | 102-140 | rotate 是 shared upsert 行為;正常 search_add 因先 load existing 而不會到達。 |
Side Effects
line_users explicit fields:created/user_id/line_user_id/name/email;Legacy 不寫 refresh_token。
新 api_session_logs explicit fields:created/user_id/params/session_id/device_id/api_key/action;action=login,admin_user_id 保持 default。
Current Mapping / Gap
AutoSignupService.php:29-115,451-467已在 user insert 後依 Legacy allowlist 寫line_users;integration test 2/20 通過。CredentialActorResolver.php:144-146,421-425與AutoSignupService.php:271-292仍呼叫 sharedApiSession::upsert()。- merge 後的
ApiSession.php:66-140會依 user/device/API key 對同 app token寫exist_logout、刪除、重建並寫login;新 session login log 已存在。 - Legacy search_add 對 existing user/device session 是原 token reuse,因此仍需 search_add scoped find-or-create;不可改 shared upsert或套用 rotate。
php -d output_buffering=4096 ... SearchAddStep8IntegrationTest.php為 9/93 通過;一般 PHPUnit CLI 會因 shared session header warning 出現 1 error,測試執行相容性仍待處理。
SA-LEGACY-0501 Form Submission And Summary Writers
Status: gap
NewStaging inner_text Source
| File | Lines | Rule |
|---|---|---|
QuoteRequestsController.php | 1897-1914 | pre-create category validation 仍看完整 Form。 |
| 同上 | 2896-2918 | active / same-category inner_text 寫 quote_request_inners 後從 Form unset。 |
| 同上 | 2925-2931 | unset 後才建立一般 form submission。 |
Contract
isset():missing /NULL不寫;空字串與"0"會寫。- insert fields:
created/quote_category_id/quote_request_id/quote_form_field_id/input_value。 - 不進
quote_form_submission_fields,也不參與後續 summary reload、variable fee、abnormal scan、special flow、matching。 - request create 前的完整 Form 與已提供的
FormSummary不回溯修改。
Current Mapping / Gap
FormSubmissionWriter.php:18-74目前把 inner field 當一般 field 寫入。- 只 skip submission 不足;
RequestFeeUpdater、AbnormalScanWriter、special flow 與 matcher仍讀同一份 Form。 - 應在 request create 後寫
quote_request_inners,再以 filteredSearchAddInput交給所有 downstream owner。 - 本地 DB 已核對 active fixture field
14293/ category614;測試不可假設其他環境 id 相同。
SA-LEGACY-0802 Authorized Contact Branch
Status: verified
這條規則只描述 search_add authorized caller。contact / payment shared method 的完整 contract 見 SA-LEGACY-1101。
Legacy Source
| File | Symbol | Lines |
|---|---|---|
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteService.php | _handleAutoQuoteMatch | 2570, 2864-2866, 2908-2915, 2981-2985, 3160-3165, 3233-3250 |
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php | processWantToContactProvider | 4106-4474 |
Preconditions
| Condition | Legacy rule |
|---|---|
request match_type | final quote_requests.match_type = Authorize |
| candidate source | _handleAutoQuoteMatch() receives auto quote candidate services |
| candidate count | authorized mode uses the first accepted auto quote candidate, then stops |
| fee source | authorized bid uses quote_requests.direct_fee as total_site_fee before contact |
| no candidate | logs Operation and sends BD email |
Legacy Order
| Step | Lines | Rule |
|---|---|---|
| 1 | 2570 | Determine authorized mode from final request row match_type. |
| 2 | 2864-2866 | Shuffle auto quote candidates in authorized mode. |
| 3 | 2908-2915 | Use direct_fee; skip candidate if category balance is lower than this fee. |
| 4 | 2940-2963 | Build one auto quote bid payload with total_site_fee = direct_fee. |
| 5 | 2981-2985 | Record authorized match and break after the first accepted candidate. |
| 6 | 2991-2995 | Update quote_requests.auto_quote_sent_count. |
| 7 | 3107-3165 | Insert bid, run handleAutoQuote(), capture first bid id, commit. |
| 8 | 3178-3203 | After commit, modify category balance and update auto_quote_last_matched; exceptions are logged, not main-flow fatal. |
| 9 | 3233-3235 | If a bid exists, call processWantToContactProvider($user_id, $last_quote_bid_id, "特約獨佔"). |
| 10 | 3238-3250 | If no auto quote was sent, write Operation log and send BD email. |
Current Mapping
| Legacy area | Current owner | Verification |
|---|---|---|
| category clamp before authorized | QuoteRequestCreator::normalizeMatchType, SpecialFlowDispatcher::resolveMatchType | testAuthorizedModeDoesNotRunWhenCategoryClampsMatchTypeToMultiple |
| specified provider bid creation | AuthorizedMatchProcessor::process | authorized Step7 tests create a bid for payload quote_service_id |
| authorized fee source | AuthorizedMatchProcessor::process initializes bid from quote_requests.direct_fee; contact mainline charges the same direct fee | testAuthorizedModeContactsFirstMatchedProvider covers auto_fee != direct_fee, bid total_site_fee, subscription log amount, and transaction amount |
| first bid contact | AuthorizedContactProcessor::process loads first bid by id ASC | testAuthorizedModeContactsFirstMatchedProvider |
| no-match notification | AuthorizedContactProcessor::notifyAuthorizedNoMatch writes Operation log and enqueues BD email | testAuthorizedModeNoMatchWritesOperationLogAndBdEmail |
| contact / payment side effects | QuoteBid::processWantToContactProvider | covered by SA-LEGACY-1101 |
| reason | SearchAddInput::getAuthorizeReason() with fallback 特約獨佔 | contact event / reason covered indirectly by authorized tests |
| no payment method | shared method no-card/no-prime guard | testAuthorizedModeFailsWhenProviderHasNoPaymentMethod |
| card success / debt only / auto refill | shared method and Stripe helpers | testAuthorizedModeCreatesDebtTransactionWhenCardExistsButChargeDebtDisabled, testAuthorizedModeChargesDebtImmediatelyWhenCardChargeSucceeds, testAuthorizedModeAutoRefillPaysOffDebtWhenPackageChargeSucceeds |
| card failure fallback | shared method charge failure handling | testAuthorizedModeFailsWhenCardChargeFails, testAuthorizedModeFailsWhenCardChargeIsCoolingDown, testAuthorizedModeFailsWhenCardChargeNeedsRetry |
| foreign card | shared method bank-result mapping | testAuthorizedModeFailsWhenCardIsForeign |
| retry next card | shared method retry loop | testAuthorizedModeRetriesNextCardWhenRetryCreditCardFails |
| guard failures | shared method contact guards | testAuthorizedModeFailsWhenRequestorIsDisableMatchUser, testAuthorizedModeFailsWhenProviderAndRequestorAreBlockedInChat |
Gaps
| Gap | Details | Next step |
|---|---|---|
| none | Audited authorized caller gaps are closed. | Keep future changes tied to the tests listed in this rule and to SA-LEGACY-1101 for shared contact/payment behavior. |
Verified Fixes
| Area | Result | Verification |
|---|---|---|
| authorized fee source | AuthorizedMatchProcessor now initializes authorized bid total_site_fee from quote_requests.direct_fee. The shared contact method also resolves authorized charge from direct_fee when match_type = Authorize and auto_quote_sent_count = 1. | tests/SearchAddStep7IntegrationTest.php --filter testAuthorizedModeContactsFirstMatchedProvider uses auto_fee = 109, direct_fee = 333, and verifies request fee, bid total_site_fee, quote_user_subscription_logs.amount, and transactions.amount are aligned to 333. |
| no-match notification | AuthorizedContactProcessor no longer writes the unconsumed Authorized_Mode_No_Match event. It matches legacy by writing monolog_error.channel = Operation with the admin match-check link and enqueuing a BD email through email_queue. | tests/SearchAddStep7IntegrationTest.php --filter testAuthorizedModeNoMatchWritesOperationLogAndBdEmail verifies no bid is created, Operation log contains the match-check link, and bdEmailList receives the email. |
Current Status
The authorized caller-specific gaps are closed. Successful contact/payment behavior remains delegated to SA-LEGACY-1101, and authorized no-match behavior is verified at the caller level.
SA-LEGACY-1101 Shared Contact Provider Method
Status: verified
這條規則覆蓋 search_add special flow 呼叫 shared contact/payment method 後的完整 downstream contract。只對照 special flow caller 會漏掉 payment、transaction、notification 與 external event。
Legacy Source
| File | Symbol | Lines |
|---|---|---|
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php | processWantToContactProvider | 4106-4474 |
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php | processCharge | 3572 |
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php | chargeByCreditCard | 3895 |
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php | completeChargeByCreditCard | 3987 |
../get-lancer-php56/app/Plugin/Quotes/Model/QuoteBid.php | switchToNarrowMatch | 4509-4563 |
Preconditions
| Input / Option | Legacy default / meaning |
|---|---|
| existing quote bid | requestor wants to contact provider through this bid |
is_switch_fail | default true; final payment failure may switch to new leads / narrow |
add_transaction_only | default false; true only logs transaction without card charge |
add_contact_activity | default true; controls AutoQuoteContact activity |
is_direct_reserve | default false; suppresses general provider notification when true |
is_check_budget | default true; controls weekly budget undercharge for normal auto quote contact |
Legacy Order
| Step | Lines | Rule |
|---|---|---|
| 1 | 4109-4113 | Read options and defaults. |
| 2 | 4124-4129 | Acquire payoff lock by user_id; lock failure throws code 11. |
| 3 | 4138-4143 | Enter retry loop, up to 10 attempts; begin DB transaction unless in_transaction=true. |
| 4 | 4144-4158 | Narrow accept path first converts bid to auto quote / accepted / in-progress state. |
| 5 | 4161-4186 | Run basicAutoQuoteChecking() and blocked / disabled / blocked-chat guards. Service archived check follows is_narrow_match. |
| 6 | 4212-4217 | Narrow accept creates NarrowProviderAccept, then clears reason for later contact activity. |
| 7 | 4219-4235 | Load quote request and reject archived request. |
| 8 | 4241-4311 | If no subscription log: fee calc, contact-charge snapshot, budget undercharge, processCharge(), coupon fulfill, free trial decrement, category balance update. |
| 9 | 4313-4328 | Update bid contact flags, contact time, paid subscription, interested, reason, requestor note. |
| 10 | 4331-4351 | Create AutoQuoteContact when add_contact_activity=true; narrow path uses previous WantProviderQuote.param2 or narrow_match as param1. |
| 11 | 4354-4362 | Update my-work unread count, then call chargeByCreditCard(). |
| 12 | 4364-4375 | Queue bid_contact_count, then commit transaction. |
| 13 | 4377-4416 | Charge failures 101/102/104/105: rollback, retry-card may invalidate one card and retry; final failure may switch to new leads / narrow and throw public payment error. |
| 14 | 4425-4427 | After transaction loop succeeds, call completeChargeByCreditCard(). |
| 15 | 4429-4449 | If request has life token, send Life55688 expert quoting event using latest transaction type 33/40 metadata. |
| 16 | 4451-4465 | Notify provider only when non-narrow and non-direct-reserve; notification exception is logged and does not fail main flow. |
| 17 | 4467-4472 | Write timing log and release payoff lock in finally. |
Side Effects
| Area | Side effects |
|---|---|
| lock / guard | Redis payoff lock, basicAutoQuoteChecking(), blocked user / chat guards |
| fee | contact-charge snapshot, getSiteFee(), budget undercharge, coupon fulfill, free trial decrement, category balance update |
| payment | processCharge(), wallet / card / TapPay path, transaction write, subscription log |
| bid state | contact flags, contact time, paid flag, interested flag, reason, requestor note |
| activity / count | NarrowProviderAccept, AutoQuoteContact, my-work unread count, bid_contact_count |
| after commit | completeChargeByCreditCard(), Life55688 expert quoting, provider notification, timing log |
| failure | payment logs, retry-card invalidation, final switch-to-new-leads / narrow fallback |
Rollback Boundary
- fee calculation,
processCharge(), bid update, activity, unread count,chargeByCreditCard(), andbid_contact_countare inside the DB transaction. completeChargeByCreditCard(), Life55688, provider notification, and timing log run after the transaction loop succeeds.- charge failure codes
101/102/104/105rollback the transaction before retry / fallback handling. RetryCreditCardcan invalidate one auto quote card and retry.- final charge failure can switch to narrow/new leads when
is_switch_fail=trueandis_narrow_match=false.
Current Owner
| File | Symbol |
|---|---|
Lib/Model/QuoteBid.php | processWantToContactProvider at 683 |
Lib/Model/QuoteBid.php | applyWantToContactContactChargeFeeSnapshot at 1271 |
Lib/Model/QuoteBid.php | applyWantToContactBudgetLimit at 1328 |
Lib/Model/QuoteBid.php | processWantToContactCharge at 1400 |
Lib/Model/QuoteBid.php | chargeAutoQuoteDebtIfNeeded at 1616 |
Lib/Model/QuoteBid.php | chargeAutoQuoteDebtByPrimeIfNeeded at 1661 |
Lib/Model/QuoteBid.php | sendLife55688ExpertQuotingEvent at 1703 |
Lib/Model/StripeCustomer.php | chargeAllAutoQuoteDebt |
Lib/Util/CreditCardUtil.php | chargeByAutoQuoteCustomer |
Lib/Util/Life55688Util.php | addEventExpertQuoting |
Current Mapping Checklist
| Legacy area | Current owner | Verification |
|---|---|---|
| option defaults and payoff lock | QuoteBid::processWantToContactProvider | direct contact and special-flow tests acquire same mainline behavior |
| non-narrow service archived boundary | basicAutoQuoteChecking(..., $isNarrowMatch) | testConsumerCanContactAutoQuoteProviderWhenServiceIsArchivedForNonNarrowLegacyPath |
| no subscription log fee recalculation | resolveWantToContactSiteFee call in contact mainline | testContactRecomputesSiteFeeInsteadOfUsingStaleBidTotalSiteFee |
| contact-charge fee snapshot | applyWantToContactContactChargeFeeSnapshot | testContactChargeUsesQuoteBidSiteFeeSnapshot |
| weekly budget undercharge | applyWantToContactBudgetLimit | testContactAutoQuoteUnderchargesWhenWeeklyBudgetRemainingIsLowerThanSiteFee |
| coupon fulfill | fulfillWantToContactCoupon | testProviderAcceptNarrowMatchFulfillsAvailableCoupon |
| free auto quote trial | consumeFreeAutoQuoteTrialIfNeeded | testContactConsumesFreeAutoQuoteTrialWhenFeeReasonUsesTrial |
| wallet payment path | processWantToContactCharge | testConsumerCanContactAutoQuoteProvider |
| existing type 40 must not skip contact charge | subscription log check plus new charge decision | testExistingAutoQuoteSubmissionTransactionDoesNotSkipContactCharge |
| prime pay-by-prime success | chargeAutoQuoteDebtByPrimeIfNeeded | testConsumerCanContactProviderAndPayAutoQuoteDebtByPrimeWhenWalletInsufficient |
| prime foreign-card failure rollback | throwForTapPayResult through contact API | testConsumerContactProviderPrimeForeignCardFailureHasNoContactSideEffects |
| retry-card single-card failure | handleRetryCreditCardFailure / fallback | testAuthorizedModeFailsWhenCardChargeNeedsRetry |
| retry-card next-card success | chargeAutoQuoteDebtIfNeeded loop | testAuthorizedModeRetriesNextCardWhenRetryCreditCardFails |
| auto-refill debt charge | processWantToContactCharge plus StripeCustomer helper | testAuthorizedModeAutoRefillPaysOffDebtWhenPackageChargeSucceeds |
| narrow accept activity params | createNarrowProviderAcceptActivity and contact activity logic | testProviderCanAcceptWaitingNarrowMatch |
| direct reserve notification boundary | is_direct_reserve option and direct reserve processor | testCreatesDirectReserveArtifacts, testProviderAcceptDirectReserveNarrowMatchSendsDirectReserveConsumerEvent |
| direct user full-fee boundary | caller passes is_check_budget=false; contact mainline preserves full fee | testCreatesDirectUserMatch |
| no payment method error | processWantToContactCharge no-card/no-prime guard | testAuthorizedModeFailsWhenProviderHasNoPaymentMethod, testProviderAcceptWithoutPrimeFailsWhenWalletAndCardAreUnavailable |
| blocked / disabled / blocked chat guards | assertWantToContactAllowed | testAuthorizedModeFailsWhenRequestorIsDisableMatchUser, testAuthorizedModeFailsWhenProviderAndRequestorAreBlockedInChat |
| Life55688 expert quoting | sendLife55688ExpertQuotingEvent | covered by current owner and detail doc; add fixture when life-token API test is needed |
Verification
| Test | Coverage |
|---|---|
tests/QuoteBidsWantToContactProviderTest.php | direct contact provider API, fee/payment/activity/prime cases |
tests/QuoteBidsProviderAcceptNarrowMatchTest.php | narrow accept reuse of contact provider path |
tests/SearchAddStep7IntegrationTest.php | search_add special flows that call this shared method |
Last verified commands:
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteBidsWantToContactProviderTest.php
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/QuoteBidsProviderAcceptNarrowMatchTest.php
docker exec -w /project-data cd63f9147e8d vendor/bin/phpunit tests/SearchAddStep7IntegrationTest.phpResult during this migration audit:
QuoteBidsWantToContactProviderTest: OK (11 tests, 175 assertions)
QuoteBidsProviderAcceptNarrowMatchTest: OK (7 tests, 113 assertions)
SearchAddStep7IntegrationTest: OK (20 tests, 285 assertions)Remaining Gap
No current behavior gap is recorded for this rule.
Life55688 has current owner coverage and detail documentation, but a dedicated life-token fixture can still be added if future changes touch that integration. That would be additive coverage, not a known parity gap.
Notes
search_addauthorized / direct reserve / direct user 只要呼叫這個 shared method,就必須回到本 rule 判斷完整 side effect。- helper-level payment internals 目前先由這條 rule 指向;若後續需要 SQL-level audit,再拆 child rule。