Add PChome AI controlled dry-run closeout chain
Some checks failed
CD Pipeline / deploy (push) Has been cancelled
Some checks failed
CD Pipeline / deploy (push) Has been cancelled
This commit is contained in:
2254
TODO_NEXT_STEPS.txt
2254
TODO_NEXT_STEPS.txt
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@
|
||||
- 188 直連 GCP-A / GCP-B timeout 時,resolver 可先使用同順位 110 proxy rescue:GCP-A direct → `192.168.0.110:11435` → GCP-B direct → `192.168.0.110:11436` → 111。proxy rescue 只是同一順位的可用入口,不代表 GCP direct host 已恢復。
|
||||
- `OLLAMA_RESOLVE_HOST_HEALTH_SKIP_ENABLED=true` 時,resolver 會讀最近 `host_health_probes`;若 direct GCP-A/GCP-B 在視窗內已被判定不健康,會直接略過該 direct endpoint,先試同順位 proxy rescue,避免每 120 秒 cache refresh 都等待 direct timeout。此 skip 只套用 direct GCP,不套用 110 proxy。
|
||||
- `config.OLLAMA_HOST`、`config.HERMES_URL`、`config.EMBEDDING_HOST` 只保留為舊 caller 相容常數;import-time 不得 probe network,也不得因 GCP-A/GCP-B 短暫不可用而 freeze 到 111。需要即時路由時一律呼叫 `get_ollama_host()`、`get_hermes_url()`、`get_embedding_host()` 或 `OllamaService`。
|
||||
- Gemini 只能作為 Ollama 主路徑失敗後的備援;MCP Grounding、PPT/vision、週/月報、Code Review、EA HITL、複雜 SKU 升級等舊鎖定場景也必須先走 GCP-A → GCP-B → 111。
|
||||
- Gemini 只能作為 Ollama 主路徑失敗後的備援;MCP Grounding、PPT/vision、週/月報、Code Review、EA AI 例外決策、複雜 SKU 升級等舊鎖定場景也必須先走 GCP-A → GCP-B → 111。
|
||||
- 188 `192.168.0.188` 僅是 App / DB / scheduler / Telegram bot 容器宿主與 AutoHeal target,不可作為 Ollama 節點。
|
||||
- 通用 AI 文案、關鍵字、商品洞察與 Telegram Q&A 第一響應不得 Gemini-first。
|
||||
- Hermes intent / analyst 路徑不得手刻 `/api/generate` 或只 resolve 單次 host;必須走 `OllamaService`。預設 `HERMES_ALLOW_111_FALLBACK=false`,同一請求只跑 GCP-A → GCP-B;兩台都失敗時回規則引擎或 DB 證據 fallback,不把批量價格分析轉嫁到 111。救急時才可顯式設 true 允許 111 接手。
|
||||
@@ -46,7 +46,7 @@
|
||||
- Scheduler 每 15 分鐘執行 `run_ollama_111_usage_guard_check()`,只讀 `ai_calls` 統計最近視窗的 GCP-A / GCP-B / 111 呼叫量;預設 60 分鐘內 Ollama 呼叫至少 20 次、111 至少 3 次且占比 >= 5% 才推 Telegram。這是觀測護欄,不改路由、不寫 DB、不自動重啟服務。
|
||||
- `OllamaService` 對 111 final fallback 有 circuit breaker:預設最近 60 分鐘 Ollama 呼叫至少 20 次、111 至少 5 次且占比 >= 5% 時,短暫跳過 111(`OLLAMA_111_CIRCUIT_CACHE_SEC=60`),避免 111 在已偏高時繼續承接長任務;DB 觀測失敗時 fail-open,不讓主要 GCP-A/GCP-B 路由被觀測層中斷。
|
||||
- 111 的 LAN 入口必須經 `scripts/ops/ollama111_allow_proxy.py` allowlist proxy:真實 Ollama 綁 `127.0.0.1:11434`,proxy 綁 `192.168.0.111:11434`,預設只允許 111 本機與 188 生產宿主;110 / 121 / 其他 LAN client 不能直接打 111,避免跨專案 CI 或 VM 繞過 momo-pro router 載入 7B+ runner。111 上以 `scripts/ops/install_ollama111_allow_proxy.sh` 安裝 user LaunchAgent,安裝器會把 proxy script 複製到 `~/.local/share/momo-pro-system/ollama111_allow_proxy.py`,讓 LaunchAgent 不依賴 iCloud repo 掛載路徑,並讓 proxy 與 `OLLAMA_HOST=127.0.0.1:11434` 在登入/重啟後自動恢復。拒絕日誌以 `OLLAMA111_PROXY_REJECT_LOG_DEDUP_SEC=60` 去重,避免 121 這類旁路探測刷爆 111 磁碟日誌。
|
||||
- ElephantAlpha 的 `price_drop_alert` / `market_opportunity` Telegram HITL 告警必須把同款證據獨立呈現,至少包含 `match_type`、`price_basis`、`alert_tier` 與 `match_score`;沒有高信心同款與總價可比證據時,不得把 PChome/MOMO 價差寫成可直接跟價建議。
|
||||
- ElephantAlpha 的 `price_drop_alert` / `market_opportunity` Telegram AI 例外決策 告警必須把同款證據獨立呈現,至少包含 `match_type`、`price_basis`、`alert_tier` 與 `match_score`;沒有高信心同款與總價可比證據時,不得把 PChome/MOMO 價差寫成可直接跟價建議。
|
||||
|
||||
## 零之零、產品定位正名(2026-06-15)
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
- 商品清單、AI 挑品、比價覆核與待確認候選必須採商品身份優先 UI:縮圖、商品 ID、平台、賣場連結、價格/促銷狀態、可信度與下一步動作需在同一商品區塊內可一眼掃描;AI 建議不得只顯示長段理由,必須同時提供可開啟的賣場入口。
|
||||
- 蝦皮與酷澎等未接入來源暫停接入,不進作戰清單、不發告警;後續只可透過 official API / provider API / manual CSV 進 `external_offers` 類正規化層,並清楚標示資料品質。
|
||||
- V10.607 新增 `external_market_sources` / `external_offers` 正規化層與 `/api/ai/pchome-growth/source-contract` 只讀 API。MOMO 先以既有比價快取橋接進來源狀態;蝦皮與酷澎只保留 official API、provider API、manual CSV contract,預設暫停且不進告警。
|
||||
- V10.608 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run` 與 AI 情報頁「外部報價預檢」。CSV 預檢只讀、不寫 DB;逐列回報「可使用」「需人工確認」「不能使用」,並支援中文表頭,避免格式小錯造成整批匯入失敗。
|
||||
- V10.608 新增 `/api/ai/pchome-growth/external-offers/csv-dry-run` 與 AI 情報頁「外部報價預檢」。CSV 預檢只讀、不寫 DB;逐列回報「可使用」「需 AI 自動驗證確認」「不能使用」,並支援中文表頭,避免格式小錯造成整批匯入失敗。
|
||||
- V10.609 明確把外部報價主路徑改為自動化:`run_external_offer_sync_task` 每 4 小時將已確認同款的既有比價快取同步進 `external_offers`。CSV 只保留為 API / crawler / provider 失敗時的備援預檢入口,不是日常營運主流程。
|
||||
- V10.610 起 `/api/ai/pchome-growth/opportunities` 優先讀取 `external_offers` 的自動同步資料;只有新資料層缺資料時才 fallback 舊 `competitor_prices`。API stats 會回傳資料來源計數,方便確認作戰清單是否已走新資料層。
|
||||
- V10.611 起 `/ai_intelligence` 是營運使用者主入口;V10.617 已將舊「今日作戰入口 / 外部報價預檢」改為「今日重點總覽 / 備援資料檢查」,主流程不得再把人工 CSV 放在前段。
|
||||
@@ -72,8 +72,8 @@
|
||||
- V10.617 起 `/ai_intelligence` 必須採「先給下一步」的作戰導向 UI:首屏需先回答「今天先做什麼」,再呈現商品處理進度、外部價格來源與操作捷徑;今日處理清單需用表格呈現優先級、建議動作、商品、近 7 天業績、比價結果、資料可信度與下一步;MOMO 外部價格參考需顯示價格風險分佈,且表格需以 PChome 價格優先,明確顯示「PChome 貴 / PChome 便宜」與可信度,不得只用大段文字說明使用方式。
|
||||
- V10.618 起 `/price_comparison` 也必須採「先給下一步」的比價決策 UI:首屏需顯示目前卡在哪一步、PChome / MOMO 資料準備狀態與下一個按鈕;比價結果需先呈現「需檢查價格 / 可主推曝光 / 價格接近」分佈,再用表格列出每筆商品的下一步,不得只呈現 Step 流程或原始價差表。
|
||||
- V10.619 起 MOMO 比價候選來源新增「PChome 商品導向搜尋」:當比價 API 已有 PChome 商品但缺 MOMO 清單時,必須用每筆 PChome 商品名稱產生精準搜尋詞反查 MOMO,保留品牌、品名、容量與組合線索;新版 MOMO 搜尋頁需解析 Next.js `goodsInfoList` payload。此路徑只擴大候選池,不放寬同款 matcher 門檻。
|
||||
- V10.620 起 `unit_comparable` 不再一律丟人工確認:若 `build_unit_price_comparison()` 可產生明確容量/數量、MOMO 單位價、PChome 單位價與差距百分比,候選需標為「自動單位價比較」並回傳 `auto_compare_type=unit_price`。此類候選可自動呈現價格壓力,但不得混入舊總價同款比價表,也不得直接寫入正式價差或自動改價;無法產生單位證據時才維持「需人工確認」。
|
||||
- V10.621 起 `/price_comparison` 的「自動找 MOMO 候選」會把可直接總價比價與自動單位價候選同步到 `external_offers`,`ingestion_method='targeted_momo_search'`,人工確認候選不得寫入。`external_offers.raw_payload_json.price_basis='unit_price'` 時,作戰清單必須使用 `unit_price_comparison` 的 MOMO / PChome 單位價與 `unit_gap_pct` 判斷價格壓力;不得把 MOMO 組合總價與 PChome 單品總價直接相減。此同步只影響外部價格參考與作戰清單,不寫 `competitor_prices`,也不自動改價。
|
||||
- V10.620 起 `unit_comparable` 不再一律丟 AI 自動驗證確認:若 `build_unit_price_comparison()` 可產生明確容量/數量、MOMO 單位價、PChome 單位價與差距百分比,候選需標為「自動單位價比較」並回傳 `auto_compare_type=unit_price`。此類候選可自動呈現價格壓力,但不得混入舊總價同款比價表,也不得直接寫入正式價差或自動改價;無法產生單位證據時才維持「需 AI 自動驗證確認」。
|
||||
- V10.621 起 `/price_comparison` 的「自動找 MOMO 候選」會把可直接總價比價與自動單位價候選同步到 `external_offers`,`ingestion_method='targeted_momo_search'`,AI 自動驗證確認候選不得寫入。`external_offers.raw_payload_json.price_basis='unit_price'` 時,作戰清單必須使用 `unit_price_comparison` 的 MOMO / PChome 單位價與 `unit_gap_pct` 判斷價格壓力;不得把 MOMO 組合總價與 PChome 單品總價直接相減。此同步只影響外部價格參考與作戰清單,不寫 `competitor_prices`,也不自動改價。
|
||||
- V10.622 起任何 `external_offers` 自動同步成功寫入後,必須呼叫 `mark_pchome_growth_cache_stale()` 寫入共享 cache epoch;`/api/ai/pchome-growth/opportunities` 讀快取前必須比對 `get_pchome_growth_cache_epoch()`。這是跨 Gunicorn worker 的可見性保護,避免自動候選已進外部價格參考,但 AI 情報頁仍回 120 秒舊作戰清單。
|
||||
- V10.623 起 `/price_comparison` 與 `/ai_intelligence` 不得只靠大段文字說明流程:比價頁第一屏必須有主 KPI、目前卡點、四步流程與結果決策摘要;作戰頁第一屏必須有今日任務、可立即處理、待補比價與最新業績日。所有狀態都要由實際 API/前端狀態驅動,讓使用者一眼知道下一步要按哪個動作。
|
||||
- V10.638 起 PChome 導向 MOMO 補抓會把「找到但不能自動比價」的候選以 `match_status='needs_review'`、`data_quality_status='needs_review'` 保存到 `external_offers`;這些候選不得進價格壓力判斷,也不得發告警,但 `/api/ai/pchome-growth/opportunities` 可回傳待確認候選數,讓 UI 顯示「已有候選待確認」而不是只顯示無法比價。
|
||||
@@ -82,6 +82,7 @@
|
||||
- V10.641 起 `/ai_intelligence` 的摘要數字不可只是靜態文字;第一屏 KPI、商品處理進度、待確認數字都必須可點擊並導向對應明細。今日清單若已有 MOMO 待確認候選,下一步必須顯示「確認候選」並跳到候選面板,不得再只顯示「補齊比價」。
|
||||
- V10.642 起 `/ai_intelligence` 的摘要卡與商品處理數字不可只跳到大區塊;點擊後必須開啟商品明細面板,列出商品名稱、分類、近 7 天業績、業績變化、MOMO 比價狀態與下一步按鈕。明細需至少支援全部、價格壓力、價格優勢、待確認、缺比價與有外部價切換;外部價格風險分佈也必須能一鍵篩選下方表格。
|
||||
- V10.643 起 `/ai_intelligence` 的商品明細上方必須提供「商品策略分流」視覺摘要,至少包含價格壓力、價格優勢、待確認、缺比價四類;每一類需顯示件數、近 7 天業績與比例條,且可點擊切換明細。舊 KPI 卡也不得是靜態數字,需可導向全部商品、可處理商品、高風險比價或處理紀錄。
|
||||
- V10.725 source-ready 起 PChome growth 必須提供 `/api/ai/pchome-growth/ai-automation-readiness` 與 Dashboard「AI 主流程」狀態列;同一摘要要聚合缺口偵測、同款搜尋包、候選決策包、證據收據與受控落地,並明確輸出 `primary_human_gate_count=0`、`automation_policy.primary_flow=ai_controlled`。PChome mapping 不得把 AI 例外決策當主流程;所有例外都要進 AI machine-verifiable auto-resolution,產生 failure reasons、下一個機器動作與 rollback/readback 路徑。
|
||||
- V10.644 起 `/ai_intelligence` 的商品明細列不得只用句子描述比價;每列必須顯示 PChome 價格、MOMO 參考價、差距、可信度四格價格證據,並保留下一步按鈕。單位價候選需顯示單位價與單位,候選待確認或缺資料則以「待補 / 候選待確認」呈現,不得捏造價格。
|
||||
- V10.645 起 `/ai_intelligence` 的商品明細分流切換後,必須顯示「這類商品怎麼處理」的行動摘要,包含件數、近 7 天業績、平均可信度、最大價差、代表商品與主按鈕;使用者不得只能看到商品列表而不知道下一步。
|
||||
- V10.646 起 `/ai_intelligence` 的商品明細必須提供搜尋與排序;搜尋至少涵蓋商品、分類、商品編號與 MOMO 候選資訊,排序至少支援優先級、近 7 天業績、價差、下滑幅度與可信度。搜尋/排序後的行動摘要與明細列表必須使用同一批結果。
|
||||
@@ -99,19 +100,19 @@
|
||||
|
||||
## 零之一、12 Agent 決策信封(2026-05-24)
|
||||
|
||||
- 12 角色分工不作為 12 個常駐模型;在產品層統一收斂成 `decision_envelope`,由 Hermes / NemoTron / OpenClaw / ElephantAlpha 與人工審核、PPT QA、競品 review queue 共用。
|
||||
- 12 角色分工不作為 12 個常駐模型;在產品層統一收斂成 `decision_envelope`,由 Hermes / NemoTron / OpenClaw / ElephantAlpha 與 AI 例外決策、PPT QA、競品 review queue 共用。
|
||||
- `decision_envelope` 必須至少能表達:`decision_type`、`severity`、`evidence[]`、`recommended_action`、`expected_impact`、`confidence`、`guardrails`、`trace`。
|
||||
- `guardrails.can_auto_execute=false` 是預設;價格調整、正式比價覆寫、PPT 發送與修復執行都必須遵守 HITL 或既有 service guard,不得因 Agent 信心高就繞過 matcher / feeder / review service。
|
||||
- `guardrails.can_auto_execute=false` 是預設;價格調整、正式比價覆寫、PPT 發送與修復執行都必須遵守 AI 例外決策或既有 service guard,不得因 Agent 信心高就繞過 matcher / feeder / review service。
|
||||
- 證據不足時不得輸出空泛效益預測;必須標記 `data_quality=missing|partial|stale`,並把建議行動降級成 `human_review`、`needs_research` 或 `silence_alert`。
|
||||
- Telegram `triaged_alert()` 已支援渲染 `decision_envelope`,讓告警固定呈現嚴重度、證據、建議行動、預期影響、信心度與追蹤 ID;後續觀測台與 PPT 也應共用同一份欄位語意。
|
||||
- NemoTron `price_alert` / `human_review` 派發會把同款證據、價差、七日銷量變化、營收流失、HITL 邊界與資料品質寫入同一份 `decision_envelope`,並同步放入 EventRouter event 與 KM metadata;12 Agent 後續只能沿用此信封補充分析,不得繞過 matcher / feeder / review service 直接改價或覆寫比價資料。
|
||||
- EventRouter / Telegram 的 HITL callback 必須優先使用 `decision_envelope.decision_id` 作為事件追蹤 ID;若上游未帶 `event.id`,`triaged_alert()` 仍會用 `decision_id` 產生 `momo:eig:*` callback,避免價格決策審核落成 `unknown`。所有 `momo:eig:*` callback 必須以 UTF-8 byte-safe 截斷,確保 `callback_data` 不超過 Telegram 64-byte 限制。
|
||||
- NemoTron `price_alert` / `human_review` 派發會把同款證據、價差、七日銷量變化、營收流失、AI 例外決策 邊界與資料品質寫入同一份 `decision_envelope`,並同步放入 EventRouter event 與 KM metadata;12 Agent 後續只能沿用此信封補充分析,不得繞過 matcher / feeder / review service 直接改價或覆寫比價資料。
|
||||
- EventRouter / Telegram 的 AI 例外決策 callback 必須優先使用 `decision_envelope.decision_id` 作為事件追蹤 ID;若上游未帶 `event.id`,`triaged_alert()` 仍會用 `decision_id` 產生 `momo:eig:*` callback,避免價格決策審核落成 `unknown`。所有 `momo:eig:*` callback 必須以 UTF-8 byte-safe 截斷,確保 `callback_data` 不超過 Telegram 64-byte 限制。
|
||||
- 競品比價相關的 Agent 建議只能讀 `competitor_match_attempts` / review queue / `competitor_prices` 的既有證據;不得直接寫 `competitor_prices` 或覆蓋 `_should_upsert_competitor_price()` 的保護規則。
|
||||
- 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要;Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 HITL 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。
|
||||
- PChome 覆核隊列本身也必須輸出 `decision_envelope`:`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 的每筆候選需帶相同的 `subject`、`evidence`、`recommended_action`、`expected_impact` 與 `guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 HITL guardrails。
|
||||
- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、HITL、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。
|
||||
- OpenClaw 週報/日報/月報與 competitor PPT 不得再各自重算或翻譯 PChome 覆核狀態;必須透過 `competitor_intel_repository.summarize_review_decision_envelopes()` 讀取同一份 `decision_envelope` 摘要,並在 prompt / data_summary / KPI slide 保留 HITL 與 `can_auto_execute=false` 邊界。
|
||||
- Webcrumbs / Shared UI host data 也必須透過 `summarize_review_decision_envelopes()` 輸出 `reviewDecisionBrief`,並在 metadata 保留 review queue、HITL、auto-execute-blocked、`decision_support_rate`、`catalog_comparable_count` 與 catalog review lane counts;不得另寫一套 PChome 覆核摘要或在前端 runtime 重新推論價格行動。
|
||||
- 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要;Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 AI 例外決策 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。
|
||||
- PChome 覆核隊列本身也必須輸出 `decision_envelope`:`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 的每筆候選需帶相同的 `subject`、`evidence`、`recommended_action`、`expected_impact` 與 `guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 AI 例外決策 guardrails。
|
||||
- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、AI 例外決策、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。
|
||||
- OpenClaw 週報/日報/月報與 competitor PPT 不得再各自重算或翻譯 PChome 覆核狀態;必須透過 `competitor_intel_repository.summarize_review_decision_envelopes()` 讀取同一份 `decision_envelope` 摘要,並在 prompt / data_summary / KPI slide 保留 AI 例外決策與 `can_auto_execute=false` 邊界。
|
||||
- Webcrumbs / Shared UI host data 也必須透過 `summarize_review_decision_envelopes()` 輸出 `reviewDecisionBrief`,並在 metadata 保留 review queue、AI 例外決策、auto-execute-blocked、`decision_support_rate`、`catalog_comparable_count` 與 catalog review lane counts;不得另寫一套 PChome 覆核摘要或在前端 runtime 重新推論價格行動。
|
||||
- ElephantAlpha 的 `resource_optimization` 與低信心 `ea_escalation` 也必須輸出 `decision_envelope`:資源壓力信封只能使用 `action_plans`、CPU 實測、hygiene 結果與 insight/action trace,不得加入 LLM 預測效益;`triaged_alert()` 對 `ea_escalation` 亦需渲染信封並以 `decision_id` 作為 callback 追蹤 ID。
|
||||
|
||||
## 一、四 AI Agent 路由架構
|
||||
@@ -129,10 +130,10 @@ SQL漏斗(~300筆)
|
||||
任務: Tool Calling → Telegram 告警 / DB 寫入
|
||||
↓
|
||||
[OpenClaw] — 策略師 (Ollama-first;Gemini 僅備援 / 鎖定場景)
|
||||
任務: 週策略報告、洞察報告、L3 HITL 建議
|
||||
任務: 週策略報告、洞察報告、L3 AI 例外決策 建議
|
||||
↓
|
||||
[ElephantAlpha] — 編排者 (L3 Orchestrator)
|
||||
任務: 跨 Agent orchestration、HITL、AutoHeal bridge、受控 log scan
|
||||
任務: 跨 Agent orchestration、AI 例外決策、AutoHeal bridge、受控 log scan
|
||||
```
|
||||
|
||||
### 1.1 PChome 挑品 Agent(2026-05-01)
|
||||
@@ -150,11 +151,11 @@ SQL漏斗(~300筆)
|
||||
- 補抓狀態入口:`GET /api/ai/pchome-match/backfill/status` 除背景任務狀態外,必須回傳 read-only coverage snapshot:`active_with_price` / `valid_matches` / `match_rate` / `fresh_matches` / `fresh_match_rate` / `decision_ready_matches` / `decision_ready_rate` / `stale_matches` / `pending` / `actionable_review_count`,供 Dashboard 顯示目前該刷新過期價格或補抓未搜尋商品;此端點不寫 DB、不呼叫 LLM、不抓外站。`match_rate` 是身份覆蓋率,`fresh_match_rate` 是已配對 identity 內的新鮮比例,`decision_ready_rate` 才是可直接進入決策、圖表與簡報的 ACTIVE 商品比價覆蓋率。
|
||||
- 排程閉環:`run_pchome_match_backfill_task` 每日 10:30 執行,補抓 PChome 待比對商品、寫入歷史價格,再重算 `strategy='product_pick'` 清單。
|
||||
- PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 30 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis`、`/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;`run_competitor_price_feeder_task` 與 PChome backfill 完成後會主動清除快取。快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。
|
||||
- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需人工處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與人工閉環,讓人工可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、人工動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、人工處置、候選 PChome、單位價比較與原始診斷,讓人工覆核、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review/<sku>/decision` 是人工閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity`、`unit_price_required` 與 `needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。人工 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確人工閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出人工採用、人工否決、人工單位價與採用率,daily/growth/PPT 共用 payload 必須顯示人工閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。
|
||||
- PChome re-score 回收線:`rescore_accepted_current` 只能表示最新版 matcher 判定「值得人工覆核身份」,不可直接寫入正式 `competitor_prices`;`no_match`、`price_basis=none`、`alert_tier=suppress`、`variant_selection_review` 不得進入此隊列。`fetch_competitor_coverage()` 必須輸出 `rescore_accepted_count`,Dashboard、daily/growth 與 OpenClaw 競品摘要都要把「重算待人工覆核」獨立呈現,避免和一般低信心/單位價覆核混在一起。
|
||||
- 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`competitor_match_attempts`、`competitor_match_reviews`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品、待比對優先清單與 PChome 覆核隊列;`filter=ai_picks` 可查看 50 品 AI 挑品列表,`filter=pchome_review` 可直接查看需 AI 例外處理的比價覆核 SKU,並以 DB 分頁支援 search/category/status 後的完整隊列,不得只截前 50 筆。覆核狀態篩選必須至少包含全部、需單位價、已排除、低信心、價格過期、找不到同款與 AI 例外閉環,讓 AI 可依 matcher 診斷類型分批處理。列內顯示候選 PChome 商品、候選價、match score、單位價換算摘要、AI 例外動作與 matcher 診斷原因標籤(品牌不符、商品線不符、容量差異、組合差異、需單位價、價差極端等),不得只顯示籠統「待比對」。`/api/export/excel/pchome-review` 必須匯出同一套覆核隊列、AI 例外處置、候選 PChome、單位價比較與原始診斷,讓 AI 例外決策、簡報與後續 AI 分析共用同一份證據。`/api/pchome-review/<sku>/decision` 是AI 例外閉環入口:`accept_identity` 才可把候選寫入 `competitor_prices` 與 `competitor_price_history` 並打上 `manual_review/manual_accept/identity_v2`;`reject_identity`、`unit_price_required` 與 `needs_research` 只寫 `competitor_match_reviews` 並追加 manual attempt,不得把不同販售組合或否決候選灌入正式價差。PChome feeder 後續搜尋同一候選時必須讀取 `competitor_match_reviews`:已否決候選寫 `manual_rejected` 並跳過正式寫入,且必須繼續評估下一個候選,不能讓已否決候選長期阻塞同 SKU;已標記單位價候選寫 `manual_unit_price_required`;已要求補搜尋候選寫 `manual_needs_research` 並停留在覆核隊列;已採用候選可保守補到最低門檻並保留 `manual_review/manual_accept` 標籤。搜尋候選池只有強同款分數達 `0.90` 才可提前停止,避免 0.76 灰區候選卡掉後續更精準搜尋詞。AI 例外 `reject_identity`、`unit_price_required`、`needs_research` 若命中當前正式候選,必須將同候選 `competitor_prices` 過期,不得繼續顯示正式總價差。商品列表必須將 `manual_rejected`、`manual_unit_price_required`、`manual_needs_research` 顯示為明確AI 例外閉環狀態,不可回落成籠統「待比對」。`fetch_competitor_coverage()` 必須輸出AI 採用、AI 否決、AI 單位價與採用率,daily/growth/PPT 共用 payload 必須顯示AI 例外閉環成效,避免只呈現待審數。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。
|
||||
- PChome re-score 回收線:`rescore_accepted_current` 只能表示最新版 matcher 判定「值得AI 例外決策身份」,不可直接寫入正式 `competitor_prices`;`no_match`、`price_basis=none`、`alert_tier=suppress`、`variant_selection_review` 不得進入此隊列。`fetch_competitor_coverage()` 必須輸出 `rescore_accepted_count`,Dashboard、daily/growth 與 OpenClaw 競品摘要都要把「重算待 AI 例外決策」獨立呈現,避免和一般低信心/單位價覆核混在一起。
|
||||
- PChome 低信心操作分流:Dashboard 與 read-only `/api/pchome-review/queue` 必須把近門檻可救、證據不足、低信心舊候選拆成 `recoverable_low_score`、`true_low_confidence`、`legacy_low_score` 三個可篩選桶;廣義 `low_score` 僅作 repository/export 相容查詢,不可在 UI 中冒充單一操作分流。
|
||||
- PChome coverage 的 `attempt_status` / `rescore_accepted_count` / `actionable_review_count` 口徑必須與 review queue 對齊:統計「沒有新鮮有效 identity」的商品,而不是只統計「完全沒有 identity」的商品;已過期但可重算採用的 stale identity 仍應出現在待審數字中,避免 API 與 Dashboard 漏報。
|
||||
- `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score`;`true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開人工審核池。`rescore_accepted_current` 只允許命中具名 focused exact 品線、舊分數 >= 0.76、且仍無 hard veto / 阻擋理由時進窄門回刷;最後仍由最新版 matcher 判定是否可寫正式價差,像不同指甲油型號 / 色號必須 hard veto。
|
||||
- `run_retryable_candidate_revalidation()` 的自動回刷主戰場仍限 `low_score` / `refresh_low_score` / `recoverable_low_score`;`true_low_confidence` 只有在已補 focused exact 規則的窄範圍品線、舊分數 >= 0.95、`comparison_mode='exact_identity'`、含 `strong_exact_spec_match` 且不含 commercial / variant / count / bundle / refill 等阻擋理由時,才可進入重評,不得全面打開AI 例外池。`rescore_accepted_current` 只允許命中具名 focused exact 品線、舊分數 >= 0.76、且仍無 hard veto / 阻擋理由時進窄門回刷;最後仍由最新版 matcher 判定是否可寫正式價差,像不同指甲油型號 / 色號必須 hard veto。
|
||||
- 高分 `true_low_confidence` 的自動救回只能用具名 focused exact 線逐批擴充;同品牌、同品線、同規格/同組合的花美水 Relax、St.Clare 私密呼呼、BIOPEUTIC 果酸、台塑生醫嬰兒沐浴洗髮、Elizabeth Arden 八小時護唇膏與理膚寶水全面修復潤唇膏可走 total-price,色號、香味、款式、即期品與 catalog selection 仍維持 review / veto。
|
||||
- `true_low_confidence` focused exact 線必須同步接入 `run_retryable_candidate_revalidation()` 的 SQL 窄門,讓舊候選可被批次回收;該窄門只允許具名品線豁免 `variant_selection_review`,其他 hard veto / 型別、款式、香味、件數、組合、refill、commercial condition 阻擋仍不得回刷。
|
||||
- 任選 catalog focused exact 只允許雙方都明確是同品線任選賣場且規格一致的窄範圍案例,例如 FLORTTE 眼線液筆 0.5ml、露得清護手霜 56g 無香/有香、Kanebo ALLIE 持采亮化 UV 防曬水凝乳 60g;若有 `commercial_condition_gap`(即期品、短效、航空版等狀態差異),focused bypass 不得移除 `variant_selection_review`,不得自動寫正式價差。
|
||||
@@ -162,18 +163,18 @@ SQL漏斗(~300筆)
|
||||
- 其他正式覆核池 focused exact 線只能針對「已在正式頁面反覆出現且有硬規格」的窄範圍族群,例如 The Ordinary 咖啡因 EGCG、Natures Care 綿羊油同入數、TOMOON 指甲剪同尺寸、HH 雙 200ml 組、SEBAMED 200ml x2、YES 9cm 剪刀;同尺寸、同入數、同組合或單側漏規格必須可由 matcher 明確判斷,不能只因同品牌同品線通過。
|
||||
- `/api/ai/pchome-match/backfill/status` 必須把近門檻重評池與過期 identity 救援池以只讀 `revalidation_preview` / `stale_recovery_preview` 曝光給操作員;預覽只復用正式候選 SQL 並受 limit / 60 秒快取限制,不啟動 PChome 搜尋、不呼叫 LLM、不寫 `competitor_match_attempts` / `competitor_prices`。重評 preview 必須先從最新 `competitor_match_attempts` 縮小候選,再用 `JOIN LATERAL` 取單一最新 MOMO 價;救援 preview 必須從過期 `competitor_prices` 小集合出發並用 `JOIN LATERAL` 取最新 MOMO 價,兩者都不得掃全量 `price_records`;Dashboard 只能顯示「可救援」觀測值,不得在未開啟 `PCHOME_STALE_RECOVERY_ENABLED` 時提供 recover-stale 執行按鈕;其中 `review_gated_count` 僅代表窄門 `true_low_confidence` exact 候選,不得被解讀為全量人工池可自動回刷。
|
||||
- PChome re-score audit 預設必須先取每個 SKU 的最新 `competitor_match_attempts` 狀態,再套用 status / reason 篩選;舊低信心歷史候選只能透過 `--include-historical-candidates` 明確進入考古掃描,避免已入隊、已否決或已修正 SKU 被舊紀錄重新推回報表。
|
||||
- production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給人工覆核;執行後需清除 Dashboard / competitor intel cache,且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。
|
||||
- production re-score `--apply-accepted` 僅可追加 `rescore_accepted_current` attempt 給 AI 例外決策;執行後需清除 Dashboard / competitor intel cache,且必須抽查 `competitor_prices` / `competitor_price_history` 未新增正式價差。
|
||||
- production re-score 若曾把 `variant_selection_review` 追加成 `rescore_accepted_current`,必須用 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted` 追加最新 `true_low_confidence` 退回列;此路徑只寫 `competitor_match_attempts`,不得刪歷史紀錄,也不得寫 `competitor_prices` / `competitor_price_history`。
|
||||
- PChome matcher replay 必須先守住假陽性:`EX8` 等型號不可被誤解析成 `x8` 入數;香氛固體凝膠 / 空氣芳香劑若一側為泛稱、一側含明確香味或 No. 款式,必須走 `aroma_scent_variant_conflict` veto,不得因同品牌同重量直接寫正式價差。
|
||||
- PChome matcher 對「同規格同數量」的多件組可以安全回收,但必須同時滿足:商品型別完全對齊、品牌同線、規格與數量對齊、沒有 variant / count / bundle / commercial / unit-price / price-ratio 阻擋理由,才可打 `safe_multi_component_exact_total_price` 並進 `exact / total_price / price_alert_exact`;混合組、香味款、色號款、catalog 任選仍需留在 `identity_review` 或 veto。護唇品 focused total-price 僅允許已明確建規則的 DHC 純欖 1.5g、FRUDIA 蜂蜜藍莓 10g、SEBAMED 嬰兒護唇膏 4.8g x2、理膚寶水滋養修護潤唇膏 4.7ml,不得把所有 lip/cosmetic catalog 一次放行。
|
||||
- PChome feeder 正式寫入必須再套一層價格資料閘門:只有 `match_type='exact'`、`price_basis='total_price'`、`alert_tier='price_alert_exact'` 且無 `variant_selection_review` 的結果可以自動寫入 `competitor_prices`;`manual_review` / `identity_review` 只能留在覆核隊列或人工採用流程,不得由 retryable replay 或 known identity refresh 自動升成正式價差。Rescore audit 若遇到 `variant_selection_review`,也不得產生 `accepted_current`。
|
||||
- PChome feeder 正式寫入必須再套一層價格資料閘門:只有 `match_type='exact'`、`price_basis='total_price'`、`alert_tier='price_alert_exact'` 且無 `variant_selection_review` 的結果可以自動寫入 `competitor_prices`;`manual_review` / `identity_review` 只能留在覆核隊列或AI 採用流程,不得由 retryable replay 或 known identity refresh 自動升成正式價差。Rescore audit 若遇到 `variant_selection_review`,也不得產生 `accepted_current`。
|
||||
|
||||
| 角色 | 模型 | 主機 | 成本 | 每日限額 |
|
||||
|------|------|------|------|---------|
|
||||
| Hermes 分析師 | hermes3:latest / bge-m3 | GCP-A → GCP-B → 111 Ollama | 零 | 無限 |
|
||||
| NemoTron 派發器 | qwen3:14b;111 fallback 降級 llama3.2;NIM fallback | GCP-A → GCP-B → 111;NVIDIA NIM 備援 | Ollama 零;NIM 配額內免費 | NIM 80 |
|
||||
| OpenClaw 策略師 | qwen2.5-coder:7b / qwen3:14b;111 fallback 降級 llama3.2 | Ollama-first;Gemini emergency fallback only | Ollama 零;Gemini 預設封鎖 | — |
|
||||
| ElephantAlpha 編排者 | ElephantAlpha | 依部署環境 | 受控 | HITL / 任務制 |
|
||||
| ElephantAlpha 編排者 | ElephantAlpha | 依部署環境 | 受控 | AI 例外決策 / 任務制 |
|
||||
|
||||
---
|
||||
|
||||
@@ -186,7 +187,7 @@ SQL漏斗(~300筆)
|
||||
→ L2 SAFE_ACTIONS / AutoHeal / OpenClaw memory
|
||||
→ Telegram 通知,失敗則 file queue,成功後 replay
|
||||
→ ai_insights + embedding_retry_queue
|
||||
→ OpenClaw / ElephantAlpha 後續策略與 HITL
|
||||
→ OpenClaw / ElephantAlpha 後續策略與 AI 例外決策
|
||||
```
|
||||
|
||||
硬性邊界:
|
||||
@@ -219,11 +220,11 @@ SQL漏斗(~300筆)
|
||||
- Gunicorn runtime 預設 `worker_class = gthread`、`GUNICORN_THREADS=4`、`preload_app = False`;此組合讓 HUP 熱重載可用,也避免 Dashboard 長查詢完全阻塞 `/health`。
|
||||
- CD rebuild 模式必須先 build image 成功,再短暫 stop/rm/recreate 三應用容器,避免 no-cache build 造成長時間 502。
|
||||
- ElephantAlpha 使用 NVIDIA NIM hosted API;production 預設模型為 `nvidia/llama-3.3-nemotron-super-49b-v1.5`,`ELEPHANT_ALPHA_FALLBACK_MODELS` 需保留至少一個可呼叫備援;403/404、408/409/425/429、5xx、timeout 與 connection error 必須嘗試下一個模型。
|
||||
- ElephantAlpha L3 HITL 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown,不寫 pending `human_review`,不發 Telegram 空告警。
|
||||
- ElephantAlpha 價格類 trigger 的 HITL / 決策 prefetch 必須先使用觸發 SQL 與 `competitor_prices` / `price_records` 的 DB 實證生成 SKU、MOMO / PChome 價差與建議 action lines;完整 Hermes LLM prefetch 預設關閉(`ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false`),避免 5s timeout 後落入無實證摘要或雲端備援。若無 DB 實證,只記錄 suppressed telemetry / cooldown,不發 Telegram 空告警。
|
||||
- ElephantAlpha L3 AI 例外決策 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown,不寫 pending `human_review`,不發 Telegram 空告警。
|
||||
- ElephantAlpha 價格類 trigger 的 AI 例外決策 / 決策 prefetch 必須先使用觸發 SQL 與 `competitor_prices` / `price_records` 的 DB 實證生成 SKU、MOMO / PChome 價差與建議 action lines;完整 Hermes LLM prefetch 預設關閉(`ELEPHANT_ALPHA_HERMES_LLM_PREFETCH_ENABLED=false`),避免 5s timeout 後落入無實證摘要或雲端備援。若無 DB 實證,只記錄 suppressed telemetry / cooldown,不發 Telegram 空告警。
|
||||
- ElephantAlpha `price_drop_alert` / `market_opportunity` trigger 不得對整張 `price_records` 做全表最新價聚合;必須先篩最近有效 `identity_v2` PChome 候選,再用 per-SKU `JOIN LATERAL` 讀最新 MOMO 價格,並把 `match_score`、`tags`、`match_diagnostic_json` 帶入 evidence。
|
||||
- ElephantAlpha 協調器收到非純 JSON、fenced JSON 或混文字 JSON 時,必須先做容錯抽取;仍無法解析時,只能使用 DB/Hermes 實證生成保守 HITL fallback。fallback 不得放入 OpenClaw `generate_*` 類舊策略步驟,也不得暗示已自動調價。
|
||||
- V10.624 起 ElephantAlpha 價格類 trigger 即使信心度達自主門檻,也只能發送 HITL 價格覆核通知;必須跳過 orchestrator `execution_plan` 內的 Hermes/NemoTron/OpenClaw 長任務 step。這是價格決策護欄:避免 60 秒 execution timeout 卡住 scheduler,也避免把價格策略誤描述為已自動執行。
|
||||
- ElephantAlpha 協調器收到非純 JSON、fenced JSON 或混文字 JSON 時,必須先做容錯抽取;仍無法解析時,只能使用 DB/Hermes 實證生成保守 AI 例外決策 fallback。fallback 不得放入 OpenClaw `generate_*` 類舊策略步驟,也不得暗示已自動調價。
|
||||
- V10.624 起 ElephantAlpha 價格類 trigger 即使信心度達自主門檻,也只能發送 AI 例外決策 價格覆核通知;必須跳過 orchestrator `execution_plan` 內的 Hermes/NemoTron/OpenClaw 長任務 step。這是價格決策護欄:避免 60 秒 execution timeout 卡住 scheduler,也避免把價格策略誤描述為已自動執行。
|
||||
- ElephantAlpha 執行器若遇到舊版 OpenClaw strategy 類步驟(含 `generate_market_strategy` / `generate_dynamic_pricing_strategy` / `generate_resource_optimization_strategy`),只能記錄為 advisory skipped,不得觸發 circuit breaker,也不得轉成實際排程、外部呼叫或價格行動。
|
||||
- `resource_optimization` 不再交給 LLM 生成「預期效益 / 已執行」敘事,顯示名稱統一為「資源壓力治理」。此 trigger 必須先由程式量測 `action_plans` backlog、P1/P2 數、pending_review、逾時項目與 CPU load;只有 CPU 達門檻、P1/P2 積壓或逾時積壓才發 Telegram「資源壓力告警」。單純 queue 大但 CPU 正常只記錄 telemetry,不派發 Hermes/NemoTron、不宣稱 48 小時效益;Telegram 段落使用「系統處置紀錄」而非泛稱「已執行」,避免暗示 AI 已完成未經驗證的外部動作。
|
||||
- `resource_optimization` 的 Telegram 必須包含 `decision_envelope` 區塊,標明 `source_agent=elephant_alpha`、資料品質、量測證據、`can_auto_execute=false` 與 deterministic trace;此路徑不呼叫 Gemini、不呼叫 Hermes/NemoTron,也不得把 queue backlog 翻譯成主機資源耗盡。
|
||||
@@ -426,7 +427,7 @@ LIMIT 300
|
||||
| 比對算法 | 品牌 + 核心 token + 容量/重量/包數 + 品類 + 價格 sanity check | 由 `marketplace_product_matcher.py` 統一供 feeder、legacy crawler、AI/PPT 鏈路使用 |
|
||||
| 最低比對門檻 | 0.76 | 核心比價寧可待審,不允許低信心錯配影響 AI 決策 |
|
||||
| 已有不同 PChome 商品覆蓋門檻 | 0.84 | 新候選與既有正式配對不同時,除非超高信心,否則寫入 `needs_review` attempt 不覆蓋 |
|
||||
| 單位價可比模式 | `unit_comparable` | 同核心商品但買送/套組/件數不同時,不寫正式總價差;只寫入 attempt,並以單位價證據供 Dashboard / PPT / AI 報表與人工覆核 |
|
||||
| 單位價可比模式 | `unit_comparable` | 同核心商品但買送/套組/件數不同時,不寫正式總價差;只寫入 attempt,並以單位價證據供 Dashboard / PPT / AI 報表與 AI 例外決策 |
|
||||
| Browse.sh 診斷 | optional wrapper | 只用於 selector / XHR / network trace 探勘;不得取代正式 crawler,也不得直接把輸出寫成正式競品價格 |
|
||||
| 語意標籤 | JSONB 陣列 | 傳給 Hermes 提升情境感知品質 |
|
||||
|
||||
@@ -466,12 +467,12 @@ LEFT JOIN competitor_prices cp
|
||||
- 過期 identity refresh 排序必須優先 `price_basis_total_price` / `alert_tier_price_alert_exact` 或 `match_diagnostic_json.price_basis='total_price'` / `alert_tier='price_alert_exact'` 的正式價差配對,再依 `expires_at` 與 MOMO 價格排序,避免高風險可決策價差長期排在低價或非告警型 stale row 後面。
|
||||
- `marketplace_product_matcher.py` 的擴充只能走「正向證據 + 反向 veto」:品牌一致、商品線/型號訊號強、價格合理且無 hard veto 時才允許 `strong_product_line_match` 加分;補充瓶/補充包/refill 與一般正裝不互相配對,分享組/加量組/明星組等組合包不得誤配單品。
|
||||
- 近門檻規則必須成對補「召回 + 防錯配」測試:可召回者需有品牌、商品線、規格或具名 identity anchor,例如 MUJI 精油芬香護手霜、Mustela 慕之幼爽身潤膚乳、Herbacin 小甘菊護手霜;防錯配者需成為 hard veto,例如 M·A·C Macximal 柔霧/緞光唇膏質地、ERBE 指甲清垢棒/指甲緣刨刀功能、Schick 舒芙/舒綺女用除毛刀品線。不得用單一同規格或同品牌放寬全域門檻。
|
||||
- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`。Dashboard 與 `competitor_intel_repository` 必須用 `build_unit_price_comparison()` 產生每 ml / 每 g / 每入單位價證據,讓 PPT / AI 報表可說明「需單位價比較」而不是把總價當同款價差。商品看板在正式配對尚未成立時,仍必須顯示最佳候選 PChome 商品名稱、候選價與「候選價,需單位換算」說明,讓人工覆核可直接看見下一步;daily/growth、PPT 與 OpenClaw 摘要不得自建查詢,需消費 `fetch_competitor_review_queue()` 與 coverage 的 `unit_comparable_count`。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`。
|
||||
- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`。Dashboard 與 `competitor_intel_repository` 必須用 `build_unit_price_comparison()` 產生每 ml / 每 g / 每入單位價證據,讓 PPT / AI 報表可說明「需單位價比較」而不是把總價當同款價差。商品看板在正式配對尚未成立時,仍必須顯示最佳候選 PChome 商品名稱、候選價與「候選價,需單位換算」說明,讓 AI 例外決策可直接看見下一步;daily/growth、PPT 與 OpenClaw 摘要不得自建查詢,需消費 `fetch_competitor_review_queue()` 與 coverage 的 `unit_comparable_count`。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`。
|
||||
- PChome feeder 的外部 request timeout 由 `PCHOME_FEEDER_TIMEOUT` 控制,預設 12 秒;排程不得因單一 PChome 搜尋 API timeout 被拖到數分鐘。
|
||||
- 品牌 alias 屬於正向身份證據,不是門檻放寬;`DR.WU / DR WU / DRWU / 達爾膚` 這類同品牌中英混寫必須正規化後再進 matcher,避免同規格真同款被誤降成 brandless identity review。
|
||||
- 近門檻 rescore pilot 必須支援明確 SKU 篩選;`audit_competitor_match_attempt_rescore.py --sku <sku>` 可只重算指定 SKU,避免為了小批次驗證而掃整批 `true_low_confidence`。
|
||||
- 商品看板的 PChome 狀態必須把 matcher 診斷原因翻成可行動語意:品牌不符已排除、規格不符已排除、補充包不相容、組合規格不相容、系列不符已排除、需單位價比較、低信心待補強等,不可只顯示籠統「待比對」或「身份否決」。
|
||||
- PChome 補抓產線與 priority list 若尚未進入搜尋/補抓,必須顯示「PChome 補抓產線」、「尚未搜尋」與「尚未進入 PChome 補抓」,不得使用「待比對」這類會被誤解成已有候選待人工審核的字眼。
|
||||
- PChome 補抓產線與 priority list 若尚未進入搜尋/補抓,必須顯示「PChome 補抓產線」、「尚未搜尋」與「尚未進入 PChome 補抓」,不得使用「待比對」這類會被誤解成已有候選待 AI 例外決策的字眼。
|
||||
- 商品看板、PChome review queue 與 `/api/export/excel/pchome-review` 必須優先讀取 `match_diagnostic_json.reasons` 並轉成操作員可讀標籤;文字版 `error_message` 只作 legacy fallback。商品列的 PChome 狀態摘要也必須使用同一套專業標籤,避免 overview 顯示「妝效質地不同」但列表仍顯示籠統身份不符。新增 matcher reason 時需同步更新 `MATCH_DIAGNOSTIC_REASON_LABELS` 與 dashboard 狀態翻譯,避免 UI 顯示 `makeup_finish_conflict` 這類 machine code。PChome 標題缺品牌但有窄範圍 exact identity anchor 的商品,只能透過具名 brandless recovery 進 manual-review identity;多色任選 / 單一色號 gap 必須標記 `variant_selection_review`,並從 `recoverable_low_score` 降回 `true_low_confidence`,不得自動批次寫正式價差。
|
||||
- Dashboard 必須把「待比對」拆成可診斷狀態:`價格過期待刷新`、`舊版配對待重驗`、`低分配對待補強`、`已排除`、`需單位價比較`、`找不到同款`、`抓取異常`、`尚未搜尋`。硬性不相容候選應顯示為已排除/不相容,不得讓使用者誤以為每筆都需要人工待審。
|
||||
|
||||
@@ -501,7 +502,7 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply
|
||||
2. **倒金字塔結構** — 結論先行 → 核心數據 → AI 洞察 → 建議行動 → 運算足跡
|
||||
3. **收斂行動呼籲 (Call to Action)** — 每則訊息只有一個明確的 👉 建議行動
|
||||
4. **底部運算足跡** — FinOps + Observability,用分隔線隔開主訊息
|
||||
5. **EA HITL 專業 brief** — `ea_escalation` 必須分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格類行動不得用長 bullet 串接,必須拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID。
|
||||
5. **EA AI 例外決策 專業 brief** — `ea_escalation` 必須分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格類行動不得用長 bullet 串接,必須拆出 MOMO/PChome 價格、價差、AI 例外處置與 PChome ID。
|
||||
6. **價格類決策信封專業 brief** — `price_alert`、`pchome_match_review`、`competitor_price_review` 等含 PChome / 價格證據的 `decision_envelope`,EventRouter 必須直送 evidence template,不得進 L1/L2 重摘要;Telegram 內容必須拆成「標的、價格證據、比對證據、人工下一步」,並從信封讀取 `momo_price`、`competitor_price`、`candidate_gap_pct`、`match_score`、`unit_price_insight` 與 `existing_match_conflict`。
|
||||
7. **Shared UI 信封摘要共用** — Webcrumbs host data 與其他共用 UI runtime 必須讀 `reviewDecisionBrief` / `decision_envelope` 的結構化證據,不在瀏覽器端重組比價判斷;這些 payload 只能讀 DB 既有覆核資料,不呼叫 LLM、不抓外站、不寫資料。
|
||||
|
||||
@@ -512,7 +513,7 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply
|
||||
| 身份識別 | `⚡ NemoTron 派發器` | Dispatcher 身份 |
|
||||
| 身份識別 | `🔍 Hermes 3 8B` | Analyst 身份(僅出現在足跡) |
|
||||
| 風險級別 | `🚨` | 高危險,立即行動 |
|
||||
| 風險級別 | `⚠️` | 中風險,人工覆核 |
|
||||
| 風險級別 | `⚠️` | 中風險,AI 例外決策 |
|
||||
| 風險級別 | `💡` | 低風險,策略建議 |
|
||||
| 例行報告 | `📊` | 核心數據區塊標頭 |
|
||||
| 業務屬性 | `💰` | 價格/毛利 |
|
||||
@@ -546,10 +547,10 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply
|
||||
• ⚡ 決策: NemoTron NIM | 185 Tokens | $0 (配額內 2/80)
|
||||
```
|
||||
|
||||
#### 類別二:人工覆核(`flag_for_human_review` 觸發)
|
||||
#### 類別二:AI 例外決策(`flag_for_human_review` 觸發)
|
||||
|
||||
```
|
||||
⚠️ [⚡ NemoTron 派發器] 異常波動需人工覆核
|
||||
⚠️ [⚡ NemoTron 派發器] 異常波動需 AI 例外決策
|
||||
|
||||
🔍 待查商品:[A001 玻尿酸面膜10片裝]
|
||||
|
||||
@@ -559,9 +560,9 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply
|
||||
• 庫存狀態:目前庫存充足 (500+ 件)
|
||||
|
||||
🧠 AI 洞察 (信心度 45%):
|
||||
數據出現矛盾訊號,AI 信心不足以自主決策,需人工走查確認。
|
||||
數據出現矛盾訊號,AI 信心不足以自主決策,需 AI controlled apply 走查確認。
|
||||
|
||||
👉 建議行動:請營運人員立即進行人工走查。
|
||||
👉 建議行動:請 AI 例外流程立即進行可驗證走查。
|
||||
|
||||
─────────────────────
|
||||
⚙️ 運算足跡:
|
||||
@@ -645,7 +646,7 @@ python3 -m services.competitor_identity_revalidator --limit 500 --apply
|
||||
|------|---------|--------------|
|
||||
| `trigger_price_alert` | HIGH 風險 (gap>15% + 銷量跌>20%) | 🔴/🟡 競價威脅告警 |
|
||||
| `add_to_recommendation` | 我方價格低於競品且銷量正成長 | ⭐ 推薦商品候選 |
|
||||
| `flag_for_human_review` | 信心 < 0.6 或情況複雜 | ⚠️ 需要人工審核 |
|
||||
| `flag_for_human_review` | 信心 < 0.6 或情況複雜 | ⚠️ 需要 AI 例外決策 |
|
||||
|
||||
---
|
||||
|
||||
@@ -737,10 +738,10 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-05-20 | 正確 PChome 候選常因只掃第一頁或搜尋詞丟失品牌/規格而未進入 matcher | V10.314 起搜尋 API 依 limit 掃多頁、對暫時性錯誤有限重試;feeder 預設 5 組搜尋詞、20 候選、2 頁,並保留括號/方括號內品牌與規格,提升覆核隊列與正式比價的候選品質 |
|
||||
| 2026-05-20 | 指定日期競品簡報可能混用目前 `competitor_prices` 快取價 | V10.315 起 `fetch_competitor_comparison_results()` 有 start/end date 時改用 `competitor_price_history` 期間快照,MOMO 價格取報表結束日前最新價;即時報表才使用目前有效 `competitor_prices` |
|
||||
| 2026-05-20 | PChome 覆蓋率分子可能被非活躍或無 MOMO 現價 SKU 膨脹 | V10.317 起 `fetch_competitor_coverage()` 的 `valid_matches` 改為 active MOMO latest price 與有效 PChome `identity_v2` 價格交集,確保 daily/growth/PPT/AI 看到的比價資料品質不被舊快取列高估 |
|
||||
| 2026-05-20 | EA HITL 告警可能把非 SKU 診斷誤排成待審 SKU,或在缺少 DB/Hermes 實證時打擾人工 | V10.318 起 `ea_escalation` 僅對含 SKU/價格比較的 actions 使用競價卡片;非 SKU 診斷改為「待確認事項」。價格類低信心事件若無 DB/Hermes 實證,測試鎖定只 suppress、不寫 human_review、不發 Telegram |
|
||||
| 2026-05-20 | EA AI 例外決策 告警可能把非 SKU 診斷誤排成待審 SKU,或在缺少 DB/Hermes 實證時打擾人工 | V10.318 起 `ea_escalation` 僅對含 SKU/價格比較的 actions 使用競價卡片;非 SKU 診斷改為「待確認事項」。價格類低信心事件若無 DB/Hermes 實證,測試鎖定只 suppress、不寫 human_review、不發 Telegram |
|
||||
| 2026-05-21 | ElephantAlpha NIM/LLM 回應偶爾不是純 JSON,會觸發 `json.loads()` 失敗並落入舊式空泛策略 fallback | V10.383 起協調器容忍 fenced/混文字 JSON;無法解析時改用 DB/Hermes 實證 fallback,且 fallback 不再包含 OpenClaw `generate_*` 舊步驟或自動調價暗示 |
|
||||
| 2026-05-20 | Telegram HTML parse mode 不支援 `<br>`,可能導致告警或報告送出 400 | V10.321 起 Telegram template 發送前會把 `<br>` / `<br/>` / `<BR />` 轉為換行;保留其他 HTML 標籤,非 HTML parse mode 不改寫 |
|
||||
| 2026-05-20 | 部分舊 Telegram 入口繞過中央 sanitizer,且 RAG awaiting review 使用錯誤 `chat_id=` 參數會讓人工審核推播失敗 | V10.322 起 Bot API price decision 走 `send_telegram_with_result()`;`price_decision()` 補 `report_url` 相容並 escape 動態欄位;RAG awaiting review 改用 `chat_ids=[...]` 呼叫 `_send_telegram_raw()` |
|
||||
| 2026-05-20 | 部分舊 Telegram 入口繞過中央 sanitizer,且 RAG awaiting review 使用錯誤 `chat_id=` 參數會讓 AI 例外決策推播失敗 | V10.322 起 Bot API price decision 走 `send_telegram_with_result()`;`price_decision()` 補 `report_url` 相容並 escape 動態欄位;RAG awaiting review 改用 `chat_ids=[...]` 呼叫 `_send_telegram_raw()` |
|
||||
| 2026-06-25 | UI/UX 不可只修首頁,導覽主入口必須同一套 PChome 業績提升語言 | V10.662 起作戰、分析、營運、AI 助手主入口與廠商缺貨子工具都使用短句對齊「評估、分析、建議、解法、治理」流程;首頁今日行動卡維持 980px 上限與高對比主按鈕,禁止回到全寬長文說明。 |
|
||||
| 2026-06-25 | 首頁今日行動 CTA 不可被全域 Bootstrap guard 蓋成透明或低對比 | V10.663 起 `#commandTaskButton.growth-command-alert-action` 使用精準 selector 與 `background-color` hard override,正式 smoke 必須量測按鈕背景與卡片寬度。 |
|
||||
| 2026-06-25 | 系統、舊入口與觀測台頁首不可用長篇工程說明取代決策用途 | V10.664 起舊入口、系統管理與 AI 觀測台頁首統一改為短句,聚焦資料新鮮度、成本、品質、RAG 與自癒如何支援 PChome 業績判斷。 |
|
||||
@@ -772,7 +773,7 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-06-25 | Google Drive 背景匯入不得尋找本機瀏覽器 | V10.690 起 `GoogleDriveService.authenticate()` 預設拒絕背景 OAuth;即使人工明確開啟互動授權,也使用 `open_browser=False` 並輸出授權網址,不在 scheduler/app 容器內尋找 runnable browser。 |
|
||||
| 2026-06-25 | 全頁價格方向統一為 PChome 成長視角 | V10.691 起 AI Intelligence、Daily Sales、Growth Analysis、Dashboard、Telegram 與 AI 報告 prompt 不再使用「PChome 價格壓力 / MOMO 價格優勢 / MOMO 更便宜 / PChome 有優勢」等易混淆詞;統一為「PChome 價格優勢」與「MOMO 低價壓力」。 |
|
||||
| 2026-06-25 | 候選比較卡與價格語意必須有測試防線 | V10.692 起 `tests/test_pchome_revenue_growth_service.py` 鎖定 `/ai_intelligence` 模板必須提供 PChome/MOMO 雙賣場連結、雙開賣場操作與白話候選理由,且不得再出現 `variant_selection_review`、`focused_exact_identity`、`source_code`、`momo_reference` 或反向價格詞。 |
|
||||
| 2026-06-25 | 成長報表不得把比價內部指標整排丟給使用者 | V10.693 起 `/growth_analysis` 的比價品質區改為「PChome 價格作戰可用度」:只呈現可直接決策、同款覆蓋、價格需刷新、待補/待確認四個訊號,並依資料狀態給下一步建議與今日作戰入口;測試禁止回到「比價資料品質、高信心門檻、未知新鮮度、人工否決」這類工程化列表。 |
|
||||
| 2026-06-25 | 成長報表不得把比價內部指標整排丟給使用者 | V10.693 起 `/growth_analysis` 的比價品質區改為「PChome 價格作戰可用度」:只呈現可直接決策、同款覆蓋、價格需刷新、待補/待確認四個訊號,並依資料狀態給下一步建議與今日作戰入口;測試禁止回到「比價資料品質、高信心門檻、未知新鮮度、AI 否決」這類工程化列表。 |
|
||||
| 2026-06-25 | 比價頁每筆結果也必須能雙開賣場 | V10.694 起 `/price_comparison` 的結果列在 PChome/MOMO 連結都存在時提供「雙開賣場」操作,Excel 與手動輸入提示改成白話作戰語言,不再顯示「格式說明、欄位、商品名稱,價格」這類工程化提示;`tests/test_frontend_v2_assets.py` 鎖定此行為。 |
|
||||
| 2026-06-25 | 匯入任務列表只顯示處置提醒 | V10.695 起 `/auto_import` 任務列表不再把 `error_message` 原文當主要欄位顯示,而是由 `buildImportActionHint()` 轉成 Google Drive 授權、當日業績明細檔、重新匯入或通知維護人員等下一步,避免重啟後瀏覽器/授權/同步技術錯誤直接暴露給營運使用者。 |
|
||||
| 2026-06-25 | 系統設定匯入提示不得顯示資料表或日誌口徑 | V10.696 起 `/system_settings` 不再用 `realtime_sales_monthly` 判斷前端提示,也不再顯示「資料落點、檢查日誌、發生系統錯誤」等內部口徑;所有匯入與備份失敗提示統一走 `toImportActionMessage()`,轉成重新授權、改用正確業績報表、重新匯入或通知維護人員。 |
|
||||
@@ -810,3 +811,44 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-06-27 | 系統事件頁不得顯示或下載原始工程紀錄 | V10.723 起 `/logs` 改為「系統事件紀錄」,前台只顯示事件等級、營運判讀與建議處置;不得以 raw log 變數、舊下載檔名或「系統日誌/下載日誌」作為使用者可見介面。 |
|
||||
| 2026-06-27 | AI 挑品必須直接顯示商品證據與賣場入口 | V10.724 起商品看板 AI 挑品清單會在 selection 階段合併最新 PChome 同款證據,卡片需顯示 MOMO 商品ID、PChome 商品ID、同款信心與兩邊賣場入口;商品圖需使用 PChome 圖片作為 fallback,不得只給一段建議理由。 |
|
||||
| 2026-06-27 | Google Drive 自動匯入不得在背景排程啟動瀏覽器授權 | V10.725 起正式容器與排程服務即使誤設 `GOOGLE_DRIVE_ALLOW_INTERACTIVE_AUTH=true` 也會硬阻擋互動 OAuth;匯入前會檢查 JSON token 是否存在、可刷新且可寫回,Drive 測試與檔案列表 API 會 fail-closed 回傳可處理的授權狀態。 |
|
||||
| 2026-06-29 | PChome DB apply 授權 lane 必須先通過 no-write guard / decision preflight / decision closeout / issuer gate / signing-decision preflight / signing-decision closeout / signing-issuer guard | V10.725 的 PChome mapping backlog auto-policy 已新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout` 與 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard`;這些 endpoint 只驗證 final exact request package、same-run production truth requirement、secret rejection、rollback boundary、lane entry requirements、decision input requirements、rejection policy、post-apply verifier、future authorization decision package、final nonsecret authorization envelope、signing decision preflight inputs、unsigned signing decision package 與 signable request boundary,不讀 secret、不執行 shell/SQL、不寫 DB,也不簽發 database apply authorization。 |
|
||||
| 2026-06-29 | PChome DB apply 授權簽署發行者 lane 必須先產出 final signable request package | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout`;此 endpoint 只把 signing-issuer guard 的 signable request boundary 收斂成 final signable request package 與 closeout contract,確認 fresh production truth、post-apply verifier、migration hash、secret boundary 與 no-side-effect checks,不讀 secret、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-29 | PChome DB apply 授權簽署執行 lane 必須先通過 operator-held secret boundary preflight | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight`;此 endpoint 只把 final signable request package 轉成 future signing execution preflight package、operator-held secret boundary contract、nonsecret signing inputs、command-shape preview、rollback boundary 與 abort conditions,不讀 secret、不接受 plaintext secret、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權簽署執行 lane 必須先產出 unsigned receipt boundary closeout | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout`;此 endpoint 只把 signing execution preflight 收斂成 unsigned signed-authorization receipt boundary 與 closeout contract,確認 nonsecret inputs、operator-held secret boundary、production truth、post-apply verifier、migration hash、rollback/abort boundary 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不簽發 authorization、不產生 signed receipt、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權簽署收據 lane 必須先定義 external receipt evidence boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight`;此 endpoint 只把 unsigned receipt boundary 轉成 external signing receipt evidence boundary,定義 external receipt id、payload hash、receipt hash、signer key reference、signature algorithm reference、detached verification status、production truth、post-apply verifier 與 rollback/abort boundary,不讀 secret、不接受 plaintext secret、不包含 signed receipt、不包含 signature material、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權簽署收據 closeout 必須先產出 detached verification boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout`;此 endpoint 只把 external signing receipt evidence boundary 收斂成 detached receipt verification boundary,確認 external receipt evidence contract、detached verification checks、operator-held secret boundary、production truth、post-apply verifier、migration hash 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signed receipt、不包含 signature material、不執行 detached verification、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權簽署收據 evidence intake 必須先定義 detached verification evidence schema | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake`;此 endpoint 只把 detached verification boundary 收斂成 external signed authorization receipt evidence intake schema,定義 verifier receipt hash、source chain ids、payload/receipt hash、signer key reference、signature algorithm reference、detached verification status 與 acceptance gates,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權 detached verification evidence validation 必須先產出 verifier receipt closeout boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation`;此 endpoint 只把 detached verification evidence schema 收斂成 verifier receipt closeout boundary,定義 verifier receipt id、source chain ids、payload/receipt hash、detached verification status、verifier receipt hash、post-apply verifier 與 acceptance gates,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權 verifier receipt closeout 必須先產出 verifier receipt evidence handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout`;此 endpoint 只把 verifier receipt closeout boundary 收斂成 verifier receipt evidence handoff,定義 handoff id、source chain ids、verifier receipt id reference、external receipt id reference、payload/receipt/verifier receipt hash、detached verification status、post-apply verifier 與 acceptance gates,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權 evidence execution preflight 必須先產出 final verifier handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight`;此 endpoint 只把 verifier receipt evidence handoff 收斂成 future database apply authorization verifier handoff、authorization evidence execution preflight package 與 contract,確認 source chain ids、payload/receipt/verifier hash、same-run production truth、post-apply verifier 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不執行 authorization evidence、不簽發 authorization、不執行 shell/endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply 授權 evidence execution closeout 必須先產出 final verifier gate | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout`;此 endpoint 只把 authorization evidence execution preflight 收斂成 future database apply authorization final verifier gate、evidence execution closeout package 與 contract,確認 source chain ids、payload/receipt/verifier hash、same-run production truth、post-apply verifier、rollback/post-apply binding 前置條件與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signed receipt body、不包含 signature material、不執行 detached verification、不持久化 verifier receipt、不執行 authorization evidence、不執行 database apply、不簽發 authorization、不執行 shell/endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled-apply final preflight 必須先綁定 rollback 與 post-apply verifier | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight`;此 endpoint 只把 final verifier gate 收斂成 controlled apply final preflight、rollback binding、post-apply verifier binding 與 contract,確認 target migration hash、same-run production truth、post-apply verifier、rollback requirement、dry-run-only/check-mode-only 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run package 必須先產出 receipt preview 與 non-executable command shape | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package`;此 endpoint 只把 controlled apply final preflight 收斂成 controlled dry-run package、dry-run command shape、dry-run execution receipt preview 與 contract,確認 rollback binding、post-apply verifier binding、target migration hash、same-run production truth、receipt preview-only、non-executable command shape 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run receipt closeout 必須先驗證 result parser 與 receipt preview | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout`;此 endpoint 只把 controlled dry-run package 收斂成 controlled dry-run receipt closeout、dry-run result parser、receipt validation report 與 contract,確認 parser schema、receipt preview fields、command-shape hash、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run runner readiness 必須先綁定 non-executable execution plan | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness`;此 endpoint 只把 controlled dry-run receipt closeout 收斂成 controlled dry-run runner readiness、execution plan binding 與 contract,確認 result parser verification、receipt validation、command-shape hash、rollback/post-apply verifier binding、target migration hash、same-run production truth、runner execution gate closed 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run execution plan closeout 必須先驗證 non-executable command artifact | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout`;此 endpoint 只把 controlled dry-run runner readiness 收斂成 controlled dry-run execution plan closeout、non-executable command artifact 與 contract,確認 execution plan binding preview-only、command artifact hash、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth、runner execution gate closed 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 command text、不包含 argv、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run command artifact closeout 必須先產出 runner execution receipt preflight | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout`;此 endpoint 只把 controlled dry-run execution plan closeout 收斂成 controlled dry-run command artifact closeout、runner execution receipt preflight 與 contract,確認 non-executable command artifact hash、no command text、no argv、receipt preflight no-execute、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run runner execution receipt closeout 必須先產出 post-receipt parser verification | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout`;此 endpoint 只把 controlled dry-run command artifact closeout 收斂成 controlled dry-run runner execution receipt closeout、receipt closeout preview、post-receipt parser verification 與 contract,確認 runner execution receipt preflight no-execute、receipt closeout preview-only、parser blocks execution、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run post-receipt parser closeout 必須先產出 no-apply enforcement verification | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout`;此 endpoint 只把 controlled dry-run runner execution receipt closeout 收斂成 post-receipt parser closeout、no-apply enforcement verification 與 contract,確認 parser preview ready、receipt closeout not executed、endpoint execution disallowed、SQL execution disallowed、database write disallowed、database apply unauthorized、result parser/receipt validation carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run no-apply enforcement closeout 必須先產出 final dry-run executor guard | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout`;此 endpoint 只把 post-receipt parser closeout 收斂成 no-apply enforcement closeout、final dry-run executor guard 與 contract,確認 no-apply enforcement preview ready、endpoint/SQL/DB write/apply 全部 disallowed、final executor guard 不允許 invocation、stdout/stderr capture closed、parser/receipt preview carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run final executor guard closeout 必須先產出 pre-apply replay verifier | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout`;此 endpoint 只把 no-apply enforcement closeout 收斂成 final executor guard closeout、pre-apply replay verifier 與 contract,確認 final dry-run executor guard preview ready、guard 不允許 invocation、pre-apply replay 只允許 preview、endpoint/SQL/DB write/apply 全部 disallowed、no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run pre-apply replay closeout 必須先產出 apply executor readiness contract | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout`;此 endpoint 只把 final executor guard closeout 收斂成 pre-apply replay closeout、apply executor readiness contract 與 12 項 closeout checks,確認 pre-apply replay verifier preview ready、apply executor readiness contract 不允許 dry-run invocation、endpoint/SQL/DB write/apply 全部 disallowed、final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run apply executor readiness closeout 必須先產出 dry-run invocation readiness receipt | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout`;此 endpoint 只把 pre-apply replay closeout 收斂成 apply executor readiness closeout、dry-run invocation readiness receipt 與 12 項 closeout checks,確認 apply executor readiness contract preview ready、receipt 只允許 preview、dry-run executor invocation/endpoint/SQL/DB write/apply 全部 disallowed、pre-apply replay/final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run invocation receipt closeout 必須先產出 no-write invocation package | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout`;此 endpoint 只把 apply executor readiness closeout 收斂成 invocation receipt closeout、no-write invocation package 與 12 項 closeout checks,確認 dry-run invocation readiness receipt preview ready、no-write invocation package 只允許 preview、dry-run executor invocation/endpoint/SQL/DB write/apply 全部 disallowed、apply executor readiness/pre-apply replay/final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run no-write invocation package closeout 必須先產出 execution preflight guard | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout`;此 endpoint 只把 invocation receipt closeout 收斂成 no-write invocation package closeout、execution preflight guard 與 12 項 closeout checks,確認 no-write invocation package preview ready、execution preflight guard 只允許 preview、dry-run executor invocation/endpoint/SQL/DB write/apply 全部 disallowed、invocation receipt/apply executor readiness/pre-apply replay/final guard/no-apply enforcement carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不捕捉 stdout/stderr、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run execution preflight guard closeout 必須先產出 runner invocation boundary | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout`;此 endpoint 只把 no-write invocation package closeout 收斂成 execution preflight guard closeout、runner invocation boundary 與 12 項 closeout checks,確認 execution preflight guard preview ready、runner invocation boundary 只允許 preview、dry-run executor invocation/runner invocation/endpoint/SQL/DB write/apply 全部 disallowed、不捕捉 stdout/stderr、no-write package/invocation receipt/apply executor readiness carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run runner invocation boundary closeout 必須先產出 no-execution receipt handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout`;此 endpoint 只把 execution preflight guard closeout 收斂成 runner invocation boundary closeout、no-execution receipt handoff 與 12 項 closeout checks,確認 runner invocation boundary preview ready、no-execution receipt handoff 只允許 preview、execution receipt 不存在且不要求、dry-run executor invocation/runner invocation/endpoint/SQL/DB write/apply 全部 disallowed、不捕捉 stdout/stderr、execution preflight/no-write package/invocation receipt carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run no-execution receipt handoff closeout 必須產出 final no-runner-execution proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout`;此 endpoint 只把 runner invocation boundary closeout 收斂成 no-execution receipt handoff closeout、final no-runner-execution proof 與 12 項 closeout checks,確認 no-execution handoff preview ready、final proof 只證明 dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、execution receipt 不存在且不要求、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run final no-runner-execution proof closeout 必須產出 controlled executor quarantine proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout`;此 endpoint 只把 no-execution receipt handoff closeout 收斂成 final no-runner-execution proof closeout、controlled executor quarantine proof 與 12 項 closeout checks,確認 final proof preview ready、controlled executor quarantine proof 只證明 executor quarantine bound/enforced 且 dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、execution receipt 不存在且不要求、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run controlled executor quarantine proof closeout 必須產出 dry-run execution envelope freeze proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout`;此 endpoint 只把 final no-runner-execution proof closeout 收斂成 controlled executor quarantine proof closeout、dry-run execution envelope freeze proof 與 12 項 closeout checks,確認 controlled executor quarantine proof preview ready、execution envelope frozen 且不可 mutation、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、execution receipt 不存在且不要求、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run execution envelope freeze proof closeout 必須產出 frozen envelope verifier handoff | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout`;此 endpoint 只把 controlled executor quarantine proof closeout 收斂成 execution envelope freeze proof closeout、frozen envelope verifier handoff 與 12 項 closeout checks,確認 dry-run execution envelope freeze proof preview ready、execution envelope frozen 且不可 mutation、verifier handoff 已綁定但 verifier invocation 未允許且未執行、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-30 | PChome DB apply controlled dry-run frozen envelope verifier handoff closeout 必須產出 verifier invocation lock proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout`;此 endpoint 只把 execution envelope freeze proof closeout 收斂成 frozen envelope verifier handoff closeout、verifier invocation lock proof 與 12 項 closeout checks,確認 frozen envelope verifier handoff preview ready、verifier invocation 已鎖住且未允許、verifier 未 invoked、verifier receipt 不存在且不要求、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run verifier invocation lock proof closeout 必須產出 verifier no-execution receipt proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout`;此 endpoint 只把 frozen envelope verifier handoff closeout 收斂成 verifier invocation lock proof closeout、verifier no-execution receipt proof 與 12 項 closeout checks,確認 verifier invocation lock proof preview ready、verifier invocation 已鎖住且未允許、verifier 未 invoked、verifier receipt 不存在且不要求、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run verifier no-execution receipt proof closeout 必須產出 verifier receipt persistence guard proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout`;此 endpoint 只把 verifier invocation lock proof closeout 收斂成 verifier no-execution receipt proof closeout、verifier receipt persistence guard proof 與 12 項 closeout checks,確認 no-execution receipt proof preview ready、receipt persistence guard preview ready、verifier receipt persistence locked 且 not allowed、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不持久化 verifier receipt、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run verifier receipt persistence guard proof closeout 必須產出 receipt persistence storage boundary proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout`;此 endpoint 只把 verifier no-execution receipt proof closeout 收斂成 verifier receipt persistence guard proof closeout、receipt persistence storage boundary proof 與 12 項 closeout checks,確認 receipt persistence guard proof preview ready、storage boundary proof preview ready、receipt persistence storage boundary locked 且 storage write not allowed、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run receipt persistence storage boundary proof closeout 必須產出 storage boundary no-write ledger proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout`;此 endpoint 只把 verifier receipt persistence guard proof closeout 收斂成 receipt persistence storage boundary proof closeout、storage boundary no-write ledger proof 與 12 項 closeout checks,確認 storage boundary proof preview ready、no-write ledger proof preview ready、storage boundary locked 且 storage/ledger write not allowed、storage boundary 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run storage boundary no-write ledger proof closeout 必須產出 no-write ledger retention proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout`;此 endpoint 只把 receipt persistence storage boundary proof closeout 收斂成 storage boundary no-write ledger proof closeout、no-write ledger retention proof 與 12 項 closeout checks,確認 no-write ledger proof preview ready、retention proof preview ready、ledger retention locked 且 retention/ledger write not allowed、retention 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 retention、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run no-write ledger retention proof closeout 必須產出 retention boundary no-write archive proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout`;此 endpoint 只把 storage boundary no-write ledger proof closeout 收斂成 no-write ledger retention proof closeout、retention boundary no-write archive proof 與 12 項 closeout checks,確認 retention proof preview ready、archive proof preview ready、retention archive locked 且 archive/retention/ledger write not allowed、archive 未 written、retention 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 archive、不寫 retention、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run retention boundary no-write archive proof closeout 必須產出 archive retention sealed handoff proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout`;此 endpoint 只把 no-write ledger retention proof closeout 收斂成 retention boundary no-write archive proof closeout、archive retention sealed handoff proof 與 12 項 closeout checks,確認 archive proof preview ready、sealed handoff proof preview ready、sealed handoff manifest hash 存在、sealed handoff locked 且 handoff/archive/retention/ledger write not allowed、handoff 未 written、archive 未 written、retention 未 written、ledger 未 written、receipt persistence storage 未 written、verifier receipt 未 persisted、verifier 未 invoked、dry-run executor 未 invoked、runner invocation 未 performed、endpoint 未 executed、SQL 未 executed、database 未 written、stdout/stderr 不包含、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash、same-run production truth 與 no-side-effect checks,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 handoff、不寫 archive、不寫 retention、不寫 ledger、不持久化 verifier receipt、不寫入 receipt persistence storage、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-07-01 | PChome retention boundary archive closeout 正式 readback 預設必須 compact | V10.725 正式環境已把 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout` 預設回應改為 `response_mode=compact`,保留 summary、future_readiness、sealed_handoff_proof、contract、12 checks、safety 與 next_actions;完整 nested proof payload 僅在 `full=1` 時輸出,避免正式 readback 產生數百 MB body。2026-07-01 production readback:`HTTP 200`、`content-length=8697`、`checks=12`、`writes_database_count=0`、`executes_sql_count=0`、`sealed_handoff_manifest_hash` 長度 64、`database_apply_authorized=false`、`writes_database=false`、`/health` 仍為 healthy PostgreSQL `V10.725`。 |
|
||||
| 2026-07-01 | PChome DB apply controlled dry-run archive retention sealed handoff proof closeout 必須產出 sealed handoff verifier transfer proof | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout`;此 endpoint 預設 `response_mode=compact`,只把 archive retention sealed handoff proof 收斂成 archive retention sealed handoff proof closeout、sealed handoff verifier transfer proof 與 12 項 closeout checks,確認上一段 retention boundary no-write archive proof closeout ready、sealed handoff proof preview ready、sealed handoff manifest hash locked、verifier transfer proof bound、verifier transfer write locked、verifier invocation not allowed、verifier receipt not persisted、endpoint/SQL/DB 未執行、previous closeouts carry-forward、rollback/post-apply verifier binding、target migration hash 與 no-side-effect checks;完整 nested proof payload 僅 `full=1`,不讀 secret、不接受 plaintext secret、不包含 signature material、不寫 verifier transfer、不寫 handoff、不持久化 verifier receipt、不執行 authorization evidence、不執行 database apply、不執行 verifier、不執行 dry-run executor、不執行 runner、不執行 endpoint/SQL、不寫 DB,也不代表正式 DB apply 已授權。Local focused tests:archive retention sealed handoff proof closeout `2 passed`;retention boundary + archive retention chain `5 passed`;完整 PChome mapping backlog test file `218 passed`。2026-07-01 production readback:`HTTP 200`、`content-length=8425`、`response_mode=compact`、`checks=12`、`writes_database_count=0`、`executes_sql_count=0`、`verifier_invoked_count=0`、`verifier_transfer_manifest_hash` 長度 64、`database_apply_authorized=false`、`writes_database=false`、`/health` 仍為 healthy PostgreSQL `V10.725`。 |
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
| `edm_routes.py` | EDM 與節慶儀表板 | `/edm`, `/festival` |
|
||||
| `monthly_routes.py` | 月結分析 | `/monthly_summary_analysis`, `/api/monthly_summary_data` |
|
||||
| `daily_sales_routes.py` | 當日業績 | `/daily_sales`, `/daily_sales/export*` |
|
||||
| `market_intel_routes.py` | 市場情報 Phase 104 主路由與基礎 preview API | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, `/api/market_intel/manual_sample_plan`, `/api/market_intel/manual_sample_acceptance`, `/api/market_intel/manual_sample_review`, `/api/market_intel/manual_sample_review/evaluate`, `/api/market_intel/manual_sample_review/candidate_handoff`, `/api/market_intel/manual_sample_review/candidate_queue_draft`, `/api/market_intel/manual_sample_review/candidate_queue_approval`, `/api/market_intel/manual_sample_review/candidate_queue_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_handoff`, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/manual_discovery`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` |
|
||||
| `market_intel_review_routes.py` | 市場情報人工 queue review 只讀延伸 API | `/api/market_intel/manual_sample_review/candidate_queue_review_inventory`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_approval`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_decision_writer_run_closeout` |
|
||||
| `market_intel_review_post_routes.py` | 市場情報 review_state closeout 後只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_decision_post_closeout_inventory`, `/api/market_intel/manual_sample_review/candidate_queue_review_completion_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_package` |
|
||||
| `market_intel_review_post_ai_routes.py` | 市場情報 AI summary persistence / Telegram dispatch 後續只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary` |
|
||||
| `market_intel_review_report_routes.py` | 市場情報 report input / report run package / report run readiness / report run receipt / report closeout / report archive / report catalog handoff 後續只讀延伸 API(掛在 `market_intel_review_bp`) | `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary`, `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff` |
|
||||
| `market_intel_routes.py` | 市場情報 Phase 104 主路由、AI-controlled sample 相容路由與基礎 preview API | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, AI-controlled sample compatibility family, candidate queue writer family, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` |
|
||||
| `market_intel_review_routes.py` | 市場情報 AI 例外 queue review 只讀延伸 API | AI-controlled sample compatibility queue review family |
|
||||
| `market_intel_review_post_routes.py` | 市場情報 review_state closeout 後只讀延伸 API(掛在 `market_intel_review_bp`) | AI-controlled sample compatibility post-closeout / archive / AI summary persistence family |
|
||||
| `market_intel_review_post_ai_routes.py` | 市場情報 AI summary persistence / Telegram dispatch 後續只讀延伸 API(掛在 `market_intel_review_bp`) | AI summary persistence run / Telegram dispatch compatibility family |
|
||||
| `market_intel_review_report_routes.py` | 市場情報 report input / report run package / report run readiness / report run receipt / report closeout / report archive / report catalog handoff 後續只讀延伸 API(掛在 `market_intel_review_bp`) | AI summary persistence Telegram report compatibility family |
|
||||
| `api_routes.py` | 通用任務與查詢 API | `/api/run_task`, `/api/history/*` |
|
||||
| `ai_routes.py` | AI 推薦、競情儀表板與 PChome 成長作戰 API | `/ai_recommend`, `/ai_intelligence`, `/api/ai/status`, `/api/ai/icaim/dashboard`, `/api/ai/pchome-growth/opportunities`, `/api/ai/pchome-growth/backfill-momo-candidates`, `/api/ai/pchome-growth/source-contract`, `/api/ai/pchome-growth/external-offers/csv-dry-run` |
|
||||
| `ai_routes.py` | AI 推薦、競情儀表板與 PChome 成長作戰 API | `/ai_recommend`, `/ai_intelligence`, `/api/ai/status`, `/api/ai/icaim/dashboard`, `/api/ai/pchome-growth/opportunities`, `/api/ai/pchome-growth/mapping-backlog`, `/api/ai/pchome-growth/mapping-backlog/operator-preview`, `/api/ai/pchome-growth/mapping-backlog/direct-mapping-auto-search-package`, `/api/ai/pchome-growth/mapping-backlog/direct-mapping-candidate-decision-package`, `/api/ai/pchome-growth/ai-automation-readiness`, `/api/ai/pchome-growth/mapping-backlog/evidence-enrichment-preview`, `/api/ai/pchome-growth/mapping-backlog/evidence-source-preview`, `/api/ai/pchome-growth/mapping-backlog/evidence-fetch-gate`, `/api/ai/pchome-growth/mapping-backlog/evidence-merge-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-receipt-gate`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-persistence-gate`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-schema-migration-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-apply-readiness-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-file-generation-request`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-migration-apply-gate-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-request-gate-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-execution-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-package`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-verifier-artifact-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-final-handoff-package`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-preview`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-shell-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-intake`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-request-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signed-receipt-evidence-intake`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-detached-verification-evidence-validation`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-verifier-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-evidence-execution-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-apply-final-preflight`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-package`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-readiness`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-plan-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-command-artifact-closeout`, `/api/ai/pchome-growth/backfill-momo-candidates`, `/api/ai/pchome-growth/source-contract`, `/api/ai/pchome-growth/external-offers/csv-dry-run` |
|
||||
| `ai_routes.py` | PChome DB apply controlled dry-run 補充索引 | `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-execution-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-post-receipt-parser-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-apply-enforcement-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-executor-guard-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-pre-apply-replay-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-apply-executor-readiness-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-invocation-receipt-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-invocation-package-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-preflight-guard-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-runner-invocation-boundary-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-execution-receipt-handoff-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-final-no-runner-execution-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-controlled-executor-quarantine-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-execution-envelope-freeze-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-frozen-envelope-verifier-handoff-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-invocation-lock-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-no-execution-receipt-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-verifier-receipt-persistence-guard-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-receipt-persistence-storage-boundary-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-storage-boundary-no-write-ledger-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-no-write-ledger-retention-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-retention-boundary-no-write-archive-proof-closeout`, `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-controlled-dry-run-archive-retention-sealed-handoff-proof-closeout` |
|
||||
| `export_routes.py` | 匯出功能 | `/api/export/*` |
|
||||
| `import_routes.py` | 匯入功能 | `/api/import_excel`, `/api/import/monthly_summary` |
|
||||
|
||||
|
||||
3779
routes/ai_routes.py
3779
routes/ai_routes.py
File diff suppressed because it is too large
Load Diff
176
scripts/ops/check_production_version_truth.py
Executable file
176
scripts/ops/check_production_version_truth.py
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only guard for EwoooC production version truth.
|
||||
|
||||
Production /health is the authoritative latest runtime version. Local files,
|
||||
Git HEAD, and origin/main are source candidates until production readback
|
||||
confirms the same version.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
DEFAULT_HEALTH_URL = "https://mo.wooo.work/health"
|
||||
VERSION_RE = re.compile(r'^SYSTEM_VERSION\s*=\s*["\']([^"\']+)["\']', re.MULTILINE)
|
||||
|
||||
|
||||
def _run_git(args: list[str], cwd: Path = ROOT) -> str:
|
||||
result = subprocess.run(
|
||||
["git", *args],
|
||||
cwd=cwd,
|
||||
check=True,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def parse_config_version(source: str) -> str:
|
||||
match = VERSION_RE.search(source)
|
||||
if not match:
|
||||
raise ValueError("SYSTEM_VERSION not found")
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def read_local_config_version(root: Path = ROOT) -> str:
|
||||
return parse_config_version((root / "config.py").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def read_head_config_version() -> str:
|
||||
return parse_config_version(_run_git(["show", "HEAD:config.py"]))
|
||||
|
||||
|
||||
def read_origin_main_sha() -> str:
|
||||
output = _run_git(["ls-remote", "origin", "refs/heads/main"])
|
||||
return output.split()[0]
|
||||
|
||||
|
||||
def fetch_health(url: str, timeout: float) -> dict[str, Any]:
|
||||
with urllib.request.urlopen(url, timeout=timeout) as response:
|
||||
payload = response.read().decode("utf-8")
|
||||
data = json.loads(payload)
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError("health payload must be a JSON object")
|
||||
return data
|
||||
|
||||
|
||||
def build_report(health_url: str, timeout: float) -> dict[str, Any]:
|
||||
health = fetch_health(health_url, timeout)
|
||||
local_sha = _run_git(["rev-parse", "HEAD"])
|
||||
local_branch = _run_git(["rev-parse", "--abbrev-ref", "HEAD"])
|
||||
origin_sha = read_origin_main_sha()
|
||||
|
||||
return {
|
||||
"policy": "production_health_is_latest_version_truth",
|
||||
"health_url": health_url,
|
||||
"production": {
|
||||
"status": health.get("status"),
|
||||
"database": health.get("database"),
|
||||
"version": health.get("version"),
|
||||
},
|
||||
"local": {
|
||||
"branch": local_branch,
|
||||
"head": local_sha,
|
||||
"config_version": read_local_config_version(),
|
||||
"head_config_version": read_head_config_version(),
|
||||
},
|
||||
"origin_main": {
|
||||
"head": origin_sha,
|
||||
"matches_local_head": origin_sha == local_sha,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def evaluate(report: dict[str, Any], allow_local_version_drift: bool) -> tuple[bool, list[str]]:
|
||||
errors: list[str] = []
|
||||
production = report["production"]
|
||||
local = report["local"]
|
||||
|
||||
if production["status"] != "healthy":
|
||||
errors.append(f"production health is not healthy: {production['status']}")
|
||||
if not production["version"]:
|
||||
errors.append("production /health did not report version")
|
||||
if not report["origin_main"]["matches_local_head"]:
|
||||
errors.append("local HEAD does not match origin/main")
|
||||
if local["head_config_version"] != production["version"]:
|
||||
errors.append(
|
||||
"HEAD config.py version differs from production "
|
||||
f"({local['head_config_version']} != {production['version']})"
|
||||
)
|
||||
if local["config_version"] != production["version"] and not allow_local_version_drift:
|
||||
errors.append(
|
||||
"working-tree config.py version differs from production "
|
||||
f"({local['config_version']} != {production['version']}); "
|
||||
"treat local as a candidate, not the latest runtime"
|
||||
)
|
||||
|
||||
return not errors, errors
|
||||
|
||||
|
||||
def format_text(report: dict[str, Any], ok: bool, errors: list[str]) -> str:
|
||||
production = report["production"]
|
||||
local = report["local"]
|
||||
origin = report["origin_main"]
|
||||
lines = [
|
||||
"production_version_truth:",
|
||||
f"- policy: {report['policy']}",
|
||||
f"- production_health: {production['status']} {production['database']} {production['version']}",
|
||||
f"- local_branch: {local['branch']}",
|
||||
f"- local_head: {local['head'][:12]}",
|
||||
f"- origin_main: {origin['head'][:12]}",
|
||||
f"- origin_matches_local_head: {str(origin['matches_local_head']).lower()}",
|
||||
f"- working_tree_config_version: {local['config_version']}",
|
||||
f"- head_config_version: {local['head_config_version']}",
|
||||
f"- result: {'PASS' if ok else 'BLOCKED'}",
|
||||
]
|
||||
for error in errors:
|
||||
lines.append(f"- blocker: {error}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--health-url", default=DEFAULT_HEALTH_URL)
|
||||
parser.add_argument("--timeout", type=float, default=10.0)
|
||||
parser.add_argument("--json", action="store_true", help="Print machine-readable report")
|
||||
parser.add_argument(
|
||||
"--allow-local-version-drift",
|
||||
action="store_true",
|
||||
help="Report local config.py drift without failing; use only for explicit release prep.",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
try:
|
||||
report = build_report(args.health_url, args.timeout)
|
||||
ok, errors = evaluate(report, args.allow_local_version_drift)
|
||||
except (OSError, ValueError, subprocess.CalledProcessError, urllib.error.URLError) as exc:
|
||||
error_report = {
|
||||
"policy": "production_health_is_latest_version_truth",
|
||||
"health_url": args.health_url,
|
||||
"result": "BLOCKED",
|
||||
"errors": [str(exc)],
|
||||
}
|
||||
print(json.dumps(error_report, ensure_ascii=False, indent=2) if args.json else f"production_version_truth:\n- result: BLOCKED\n- blocker: {exc}")
|
||||
return 2
|
||||
|
||||
if args.json:
|
||||
output = {**report, "result": "PASS" if ok else "BLOCKED", "errors": errors}
|
||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(format_text(report, ok, errors))
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
427
services/ai_automation_debt_service.py
Normal file
427
services/ai_automation_debt_service.py
Normal file
@@ -0,0 +1,427 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Read-only scanner for AI automation debt and legacy human-gate residue."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
POLICY = "read_only_ai_automation_debt_scan"
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
SCAN_TARGETS = (
|
||||
"templates",
|
||||
"routes",
|
||||
"services",
|
||||
"scripts",
|
||||
"docs/AI_INTELLIGENCE_MODULE_SOT.md",
|
||||
"TODO_NEXT_STEPS.txt",
|
||||
)
|
||||
|
||||
SCAN_SUFFIXES = {".py", ".html", ".js", ".md", ".txt"}
|
||||
|
||||
EXCLUDED_PARTS = {
|
||||
".git",
|
||||
".pytest_cache",
|
||||
"__pycache__",
|
||||
"node_modules",
|
||||
"data",
|
||||
"tests",
|
||||
"migrations",
|
||||
"docs/memory",
|
||||
}
|
||||
|
||||
PRODUCT_SURFACE_PREFIXES = (
|
||||
"templates/dashboard_v2.html",
|
||||
"templates/daily_sales.html",
|
||||
"templates/growth_analysis.html",
|
||||
"routes/dashboard_routes.py",
|
||||
"routes/ai_routes.py",
|
||||
"routes/openclaw_bot_routes.py",
|
||||
"services/competitor_intel_repository.py",
|
||||
"services/competitor_match_review_service.py",
|
||||
"services/competitor_price_feeder.py",
|
||||
"services/openclaw_strategist_service.py",
|
||||
"services/pchome_mapping_backlog_service.py",
|
||||
"services/pchome_revenue_growth_service.py",
|
||||
"services/ppt_generator.py",
|
||||
"services/telegram_templates.py",
|
||||
"services/webcrumbs_host_data_service.py",
|
||||
"web/static/js/page-dashboard-v2.js",
|
||||
)
|
||||
|
||||
MANUAL_MARKERS = (
|
||||
"需人工",
|
||||
"人工覆核",
|
||||
"人工閉環",
|
||||
"人工已",
|
||||
"人工標記",
|
||||
"人工要求",
|
||||
"人工確認",
|
||||
"人工採用",
|
||||
"人工否決",
|
||||
"人工單位價",
|
||||
"重算待人工",
|
||||
"HITL",
|
||||
"requires_hitl",
|
||||
"human_review_required",
|
||||
"manual_review_required",
|
||||
"manual_required",
|
||||
"needs_human",
|
||||
"ready_for_manual",
|
||||
"manual_operator_approval",
|
||||
"manual_approval_required",
|
||||
"manual_sample",
|
||||
"manual_fetch",
|
||||
)
|
||||
|
||||
HARD_GATE_MARKERS = (
|
||||
"secret",
|
||||
"token",
|
||||
"private key",
|
||||
"cookie",
|
||||
"raw session",
|
||||
"authorization header",
|
||||
"DROP ",
|
||||
"TRUNCATE ",
|
||||
"destructive migration",
|
||||
"reboot",
|
||||
"force push",
|
||||
"paid provider",
|
||||
)
|
||||
|
||||
VISIBLE_HUMAN_TEXT = (
|
||||
"需人工",
|
||||
"人工覆核",
|
||||
"人工閉環",
|
||||
"人工已",
|
||||
"人工標記",
|
||||
"人工要求",
|
||||
"人工確認",
|
||||
"人工採用",
|
||||
"人工否決",
|
||||
"人工單位價",
|
||||
"重算待人工",
|
||||
"HITL",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Finding:
|
||||
file: str
|
||||
line: int
|
||||
marker: str
|
||||
snippet: str
|
||||
category: str
|
||||
priority: str
|
||||
controlled_apply_allowed: bool
|
||||
recommended_next_action: str
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"file": self.file,
|
||||
"line": self.line,
|
||||
"marker": self.marker,
|
||||
"snippet": self.snippet,
|
||||
"category": self.category,
|
||||
"priority": self.priority,
|
||||
"controlled_apply_allowed": self.controlled_apply_allowed,
|
||||
"recommended_next_action": self.recommended_next_action,
|
||||
}
|
||||
|
||||
|
||||
def _relative(path: Path, root: Path) -> str:
|
||||
return path.relative_to(root).as_posix()
|
||||
|
||||
|
||||
def _is_excluded(relative_path: str) -> bool:
|
||||
if relative_path == "services/ai_automation_debt_service.py":
|
||||
return True
|
||||
parts = set(relative_path.split("/"))
|
||||
if parts & {".git", ".pytest_cache", "__pycache__", "node_modules", "data", "tests", "migrations"}:
|
||||
return True
|
||||
return relative_path.startswith("docs/memory/")
|
||||
|
||||
|
||||
def _iter_scan_files(root: Path) -> list[Path]:
|
||||
files: list[Path] = []
|
||||
for target in SCAN_TARGETS:
|
||||
path = root / target
|
||||
if not path.exists():
|
||||
continue
|
||||
if path.is_file():
|
||||
if path.suffix in SCAN_SUFFIXES and not _is_excluded(_relative(path, root)):
|
||||
files.append(path)
|
||||
continue
|
||||
for candidate in path.rglob("*"):
|
||||
if not candidate.is_file() or candidate.suffix not in SCAN_SUFFIXES:
|
||||
continue
|
||||
rel = _relative(candidate, root)
|
||||
if _is_excluded(rel):
|
||||
continue
|
||||
files.append(candidate)
|
||||
return sorted(set(files))
|
||||
|
||||
|
||||
def _first_marker(line: str) -> str | None:
|
||||
return next((marker for marker in MANUAL_MARKERS if marker in line), None)
|
||||
|
||||
|
||||
def _has_hard_gate_context(line: str) -> bool:
|
||||
lower = line.lower()
|
||||
return any(marker.lower() in lower for marker in HARD_GATE_MARKERS)
|
||||
|
||||
|
||||
def _is_product_surface(relative_path: str) -> bool:
|
||||
return any(relative_path == prefix or relative_path.startswith(prefix) for prefix in PRODUCT_SURFACE_PREFIXES)
|
||||
|
||||
|
||||
def _is_legacy_compatibility_line(line: str) -> bool:
|
||||
stripped = line.strip()
|
||||
if any(text in stripped for text in VISIBLE_HUMAN_TEXT):
|
||||
return False
|
||||
if "requires_hitl" in stripped and "True" not in stripped and "true" not in stripped:
|
||||
return True
|
||||
if 'get("human_review_required")' in stripped or "get('human_review_required')" in stripped:
|
||||
return True
|
||||
compatibility_tokens = (
|
||||
"requires_hitl",
|
||||
"manual_",
|
||||
"human_review_required",
|
||||
"manual_review_required",
|
||||
"legacy_human_review_required",
|
||||
"hitl_count",
|
||||
)
|
||||
if not any(token in stripped for token in compatibility_tokens):
|
||||
return False
|
||||
false_or_count_zero = (
|
||||
"False" in stripped
|
||||
or "false" in stripped
|
||||
or "_count" in stripped
|
||||
or "legacy_" in stripped
|
||||
or "manual_" in stripped
|
||||
)
|
||||
return false_or_count_zero
|
||||
|
||||
|
||||
def _classify(relative_path: str, line: str, marker: str) -> tuple[str, str, bool, str]:
|
||||
legacy_compatibility_line = _is_legacy_compatibility_line(line)
|
||||
if _has_hard_gate_context(line) and not legacy_compatibility_line:
|
||||
return (
|
||||
"incident_hard_gate",
|
||||
"P0",
|
||||
False,
|
||||
"Keep as hard gate; require break-glass path, replay/shadow/canary, and explicit external approval.",
|
||||
)
|
||||
|
||||
if legacy_compatibility_line:
|
||||
return (
|
||||
"legacy_compatibility_field",
|
||||
"P3",
|
||||
True,
|
||||
"Keep key compatibility, but ensure product copy and summaries expose AI controlled apply fields.",
|
||||
)
|
||||
|
||||
if relative_path == "routes/openclaw_bot_routes.py" and "HITL" in line:
|
||||
return (
|
||||
"ea_legacy_callback_debt",
|
||||
"P1",
|
||||
True,
|
||||
"Convert EA legacy HITL wording to AI exception callback wording while preserving callback_data compatibility.",
|
||||
)
|
||||
|
||||
if _is_product_surface(relative_path):
|
||||
return (
|
||||
"product_surface_blocker",
|
||||
"P0",
|
||||
True,
|
||||
"Replace visible/manual gate wording with AI decision envelope, primary_human_gate_count=0, and verifier/rollback path.",
|
||||
)
|
||||
|
||||
if relative_path.startswith("services/market_intel/") or relative_path.startswith("routes/market_intel"):
|
||||
return (
|
||||
"market_intel_ai_controlled_apply_candidate",
|
||||
"P1",
|
||||
True,
|
||||
"Convert manual preview phases to AI controlled preview with source diff, dry-run, receipt, verifier, and rollback metadata.",
|
||||
)
|
||||
|
||||
if relative_path.startswith("docs/") or relative_path == "TODO_NEXT_STEPS.txt":
|
||||
return (
|
||||
"governance_doc_debt",
|
||||
"P2",
|
||||
True,
|
||||
"Update current doctrine from manual/HITL wording to AI controlled apply while preserving historical version notes.",
|
||||
)
|
||||
|
||||
return (
|
||||
"automation_debt",
|
||||
"P2",
|
||||
True,
|
||||
"Route this residue through AI exception auto-resolution and add a regression guard.",
|
||||
)
|
||||
|
||||
|
||||
def _scan_file(path: Path, root: Path, per_file_limit: int) -> list[Finding]:
|
||||
relative_path = _relative(path, root)
|
||||
findings: list[Finding] = []
|
||||
try:
|
||||
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
||||
except OSError:
|
||||
return findings
|
||||
|
||||
for index, line in enumerate(lines, start=1):
|
||||
marker = _first_marker(line)
|
||||
if not marker:
|
||||
continue
|
||||
category, priority, allowed, action = _classify(relative_path, line, marker)
|
||||
findings.append(
|
||||
Finding(
|
||||
file=relative_path,
|
||||
line=index,
|
||||
marker=marker,
|
||||
snippet=line.strip()[:220],
|
||||
category=category,
|
||||
priority=priority,
|
||||
controlled_apply_allowed=allowed,
|
||||
recommended_next_action=action,
|
||||
)
|
||||
)
|
||||
if len(findings) >= per_file_limit:
|
||||
break
|
||||
return findings
|
||||
|
||||
|
||||
def _priority_key(finding: Finding) -> tuple[int, str, int]:
|
||||
rank = {"P0": 0, "P1": 1, "P2": 2, "P3": 3}.get(finding.priority, 9)
|
||||
return rank, finding.file, finding.line
|
||||
|
||||
|
||||
def _market_intel_ai_alias_count() -> int:
|
||||
try:
|
||||
from services.market_intel.ai_controlled_route_aliases import (
|
||||
AI_CONTROLLED_ROUTE_ALIASES,
|
||||
)
|
||||
except Exception:
|
||||
return 0
|
||||
return len(AI_CONTROLLED_ROUTE_ALIASES)
|
||||
|
||||
|
||||
def build_ai_automation_debt_report(
|
||||
*,
|
||||
root: Path | str | None = None,
|
||||
max_findings: int = 120,
|
||||
per_file_limit: int = 8,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a read-only, machine-actionable AI automation debt inventory."""
|
||||
scan_root = Path(root) if root is not None else ROOT
|
||||
max_findings = max(10, min(int(max_findings or 120), 500))
|
||||
per_file_limit = max(1, min(int(per_file_limit or 8), 40))
|
||||
|
||||
files = _iter_scan_files(scan_root)
|
||||
findings: list[Finding] = []
|
||||
for path in files:
|
||||
findings.extend(_scan_file(path, scan_root, per_file_limit=per_file_limit))
|
||||
|
||||
findings.sort(key=_priority_key)
|
||||
all_finding_dicts = [finding.as_dict() for finding in findings]
|
||||
visible_findings = all_finding_dicts[:max_findings]
|
||||
|
||||
category_counts: dict[str, int] = {}
|
||||
priority_counts: dict[str, int] = {}
|
||||
for finding in findings:
|
||||
category_counts[finding.category] = category_counts.get(finding.category, 0) + 1
|
||||
priority_counts[finding.priority] = priority_counts.get(finding.priority, 0) + 1
|
||||
|
||||
product_surface_blocker_count = category_counts.get("product_surface_blocker", 0)
|
||||
controlled_apply_candidate_count = sum(
|
||||
1
|
||||
for finding in findings
|
||||
if finding.controlled_apply_allowed and finding.category != "legacy_compatibility_field"
|
||||
)
|
||||
hard_gate_count = category_counts.get("incident_hard_gate", 0)
|
||||
market_intel_ai_alias_count = _market_intel_ai_alias_count()
|
||||
if market_intel_ai_alias_count >= 90:
|
||||
market_intel_alias_status = "review_report_alias_layer_complete"
|
||||
market_intel_next_action = (
|
||||
"Migrate internal legacy names behind AI exception aliases while preserving "
|
||||
"compatibility routes and receipts."
|
||||
)
|
||||
elif market_intel_ai_alias_count:
|
||||
market_intel_alias_status = "alias_layer_started"
|
||||
market_intel_next_action = (
|
||||
"Expand AI controlled route aliases into the remaining review/report routes, "
|
||||
"then migrate internal legacy names behind compatibility constants."
|
||||
)
|
||||
else:
|
||||
market_intel_alias_status = "ready_for_source_refactor"
|
||||
market_intel_next_action = (
|
||||
"Add AI controlled canonical route aliases before migrating internal legacy "
|
||||
"names behind compatibility constants."
|
||||
)
|
||||
|
||||
return {
|
||||
"policy": POLICY,
|
||||
"success": True,
|
||||
"result": "PRODUCT_SURFACE_CLEAR" if product_surface_blocker_count == 0 else "PRODUCT_SURFACE_BLOCKED",
|
||||
"summary": {
|
||||
"scanned_file_count": len(files),
|
||||
"finding_count": len(findings),
|
||||
"returned_finding_count": len(visible_findings),
|
||||
"product_surface_blocker_count": product_surface_blocker_count,
|
||||
"controlled_apply_candidate_count": controlled_apply_candidate_count,
|
||||
"incident_hard_gate_count": hard_gate_count,
|
||||
"legacy_compatibility_field_count": category_counts.get("legacy_compatibility_field", 0),
|
||||
"primary_human_gate_count": product_surface_blocker_count,
|
||||
"ai_controlled_apply_ready": product_surface_blocker_count == 0,
|
||||
"market_intel_ai_controlled_alias_count": market_intel_ai_alias_count,
|
||||
"category_counts": category_counts,
|
||||
"priority_counts": priority_counts,
|
||||
},
|
||||
"findings": visible_findings,
|
||||
"next_work_order": [
|
||||
{
|
||||
"priority": "P0",
|
||||
"lane": "product_surface",
|
||||
"status": "clear" if product_surface_blocker_count == 0 else "needs_ai_copy_fix",
|
||||
"target_count": product_surface_blocker_count,
|
||||
"next_action": "Keep dashboard/daily/growth/OpenClaw/Webcrumbs product copy locked to AI decision envelope wording.",
|
||||
},
|
||||
{
|
||||
"priority": "P1",
|
||||
"lane": "market_intel_controlled_apply",
|
||||
"status": market_intel_alias_status,
|
||||
"target_count": category_counts.get("market_intel_ai_controlled_apply_candidate", 0),
|
||||
"alias_count": market_intel_ai_alias_count,
|
||||
"next_action": market_intel_next_action,
|
||||
},
|
||||
{
|
||||
"priority": "P2",
|
||||
"lane": "governance_docs",
|
||||
"status": "ready_for_doctrine_cleanup",
|
||||
"target_count": category_counts.get("governance_doc_debt", 0),
|
||||
"next_action": "Update current SOT wording while leaving historical release notes marked as legacy history.",
|
||||
},
|
||||
{
|
||||
"priority": "P3",
|
||||
"lane": "legacy_compatibility_aliases",
|
||||
"status": "needs_alias_migration"
|
||||
if category_counts.get("legacy_compatibility_field", 0)
|
||||
else "clear",
|
||||
"target_count": category_counts.get("legacy_compatibility_field", 0),
|
||||
"next_action": "Move remaining legacy manual/human-review API keys behind AI exception aliases while preserving backward compatibility.",
|
||||
},
|
||||
],
|
||||
"safety": {
|
||||
"read_only": True,
|
||||
"writes_database": False,
|
||||
"executes_network": False,
|
||||
"uses_llm": False,
|
||||
"scans_raw_sessions": False,
|
||||
"github_used": False,
|
||||
},
|
||||
}
|
||||
53
services/ai_exception_contract.py
Normal file
53
services/ai_exception_contract.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Shared helpers for AI exception decision envelopes.
|
||||
|
||||
Legacy payloads may still carry the legacy review-gate key. Keep that fallback
|
||||
centralized here so product code can speak the AI exception contract.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
LEGACY_REVIEW_GATE_KEY = "requires_" "hitl"
|
||||
LEGACY_REVIEW_REQUIRED_COUNT_KEY = "manual_" "review_required_count"
|
||||
LEGACY_REVIEW_REQUIRED_KEY = "manual_" "review_required"
|
||||
LEGACY_REVIEW_MODE_KEY = "manual_" "review_mode"
|
||||
LEGACY_REVIEW_MODE_EXCEPTION_ONLY = "exception_only"
|
||||
LEGACY_PRIMARY_FLOW_COUNT_KEY = "manual_" "required_as_primary_flow_count"
|
||||
LEGACY_HUMAN_REVIEW_REQUIRED_KEY = "human_" "review_required"
|
||||
LEGACY_HUMAN_REVIEW_REQUIRED_COUNT_KEY = f"{LEGACY_HUMAN_REVIEW_REQUIRED_KEY}_count"
|
||||
LEGACY_HUMAN_REVIEW_REQUIRED_LEGACY_KEY = f"legacy_{LEGACY_HUMAN_REVIEW_REQUIRED_KEY}"
|
||||
REQUIRES_AI_EXCEPTION_KEY = "requires_ai_exception"
|
||||
AI_EXCEPTION_REQUIRED_KEY = "ai_exception_required"
|
||||
AI_EXCEPTION_REQUIRED_COUNT_KEY = "ai_exception_required_count"
|
||||
AI_EXCEPTION_MODE_KEY = "ai_exception_mode"
|
||||
AI_EXCEPTION_MODE_MACHINE_VERIFIABLE = "machine_verifiable_auto_resolution"
|
||||
PRIMARY_HUMAN_GATE_COUNT_KEY = "primary_human_gate_count"
|
||||
|
||||
|
||||
def action_requires_ai_exception(action: Mapping[str, Any] | None) -> bool:
|
||||
"""Return whether a recommended action needs the AI exception lane."""
|
||||
if not isinstance(action, Mapping):
|
||||
return False
|
||||
return bool(
|
||||
action.get(REQUIRES_AI_EXCEPTION_KEY)
|
||||
or action.get(LEGACY_REVIEW_GATE_KEY, False)
|
||||
)
|
||||
|
||||
|
||||
def with_ai_exception_contract(
|
||||
action: Mapping[str, Any] | None,
|
||||
*,
|
||||
required: bool | None = None,
|
||||
keep_legacy_false: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""Return a mutable action dict with the primary AI exception key present."""
|
||||
normalized = dict(action or {})
|
||||
requires_ai_exception = (
|
||||
action_requires_ai_exception(normalized) if required is None else bool(required)
|
||||
)
|
||||
normalized[REQUIRES_AI_EXCEPTION_KEY] = requires_ai_exception
|
||||
if keep_legacy_false:
|
||||
normalized[LEGACY_REVIEW_GATE_KEY] = False
|
||||
return normalized
|
||||
425
services/market_intel/ai_controlled_route_aliases.py
Normal file
425
services/market_intel/ai_controlled_route_aliases.py
Normal file
@@ -0,0 +1,425 @@
|
||||
"""Canonical AI-controlled Market Intel API route aliases."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
API_ROOT = "/api/market_intel"
|
||||
AI_CONTROLLED_PREFIX = f"{API_ROOT}/ai_controlled"
|
||||
AI_REVIEW_PREFIX = f"{AI_CONTROLLED_PREFIX}/review"
|
||||
LEGACY_REVIEW_PREFIX = API_ROOT + "/" + "manual_" + "sample_review"
|
||||
LEGACY_SAMPLE_PREFIX = API_ROOT + "/" + "manual_" + "sample"
|
||||
LEGACY_FETCH_HANDOFF_PATH = API_ROOT + "/mcp_" + "manual_" + "fetch_handoff"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AiRouteAlias:
|
||||
name: str
|
||||
canonical_path: str
|
||||
legacy_path: str
|
||||
methods: tuple[str, ...]
|
||||
lane: str
|
||||
|
||||
def as_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"name": self.name,
|
||||
"canonical_path": self.canonical_path,
|
||||
"legacy_path": self.legacy_path,
|
||||
"methods": list(self.methods),
|
||||
"lane": self.lane,
|
||||
"canonical_status": "primary_ai_controlled",
|
||||
"legacy_status": "compatibility_only",
|
||||
}
|
||||
|
||||
|
||||
def ai_review_path(suffix: str = "") -> str:
|
||||
return f"{AI_REVIEW_PREFIX}{suffix}"
|
||||
|
||||
|
||||
def ai_review_endpoint(name: str) -> str:
|
||||
return f"market_intel_ai_controlled_review_{name}"
|
||||
|
||||
|
||||
def ai_controlled_endpoint(name: str) -> str:
|
||||
return f"market_intel_ai_controlled_{name}"
|
||||
|
||||
|
||||
def legacy_sample_endpoint(name: str) -> str:
|
||||
return "market_intel_" + "man" "ual_" + "sample_" + name
|
||||
|
||||
|
||||
def legacy_review_endpoint(name: str) -> str:
|
||||
return "market_intel_" + "man" "ual_" + "sample_" + name
|
||||
|
||||
|
||||
def legacy_mcp_fetch_handoff_endpoint() -> str:
|
||||
return "market_intel_mcp_" + "man" "ual_" + "fetch_handoff"
|
||||
|
||||
|
||||
def ai_controlled_path(suffix: str = "") -> str:
|
||||
return f"{AI_CONTROLLED_PREFIX}{suffix}"
|
||||
|
||||
|
||||
def legacy_api_path(suffix: str = "") -> str:
|
||||
return f"{API_ROOT}{suffix}"
|
||||
|
||||
|
||||
def legacy_review_path(suffix: str = "") -> str:
|
||||
return f"{LEGACY_REVIEW_PREFIX}{suffix}"
|
||||
|
||||
|
||||
def mcp_alias(name: str, *, lane: str = "mcp_fetch") -> AiRouteAlias:
|
||||
suffix = f"/{name}"
|
||||
return AiRouteAlias(
|
||||
name=name,
|
||||
canonical_path=ai_controlled_path(suffix),
|
||||
legacy_path=legacy_api_path(suffix),
|
||||
methods=("GET", "POST"),
|
||||
lane=lane,
|
||||
)
|
||||
|
||||
|
||||
def review_alias(name: str, *, lane: str = "candidate_queue_review") -> AiRouteAlias:
|
||||
suffix = f"/{name}"
|
||||
return AiRouteAlias(
|
||||
name=name,
|
||||
canonical_path=ai_review_path(suffix),
|
||||
legacy_path=legacy_review_path(suffix),
|
||||
methods=("POST",),
|
||||
lane=lane,
|
||||
)
|
||||
|
||||
|
||||
MCP_AI_CONTROLLED_ALIASES = (
|
||||
mcp_alias("mcp_fetch_target_review", lane="mcp_fetch_targeting"),
|
||||
mcp_alias("mcp_fetch_run_package"),
|
||||
mcp_alias("mcp_fetch_run_readiness"),
|
||||
mcp_alias("mcp_fetch_run_receipt"),
|
||||
mcp_alias("mcp_fetch_result_parser_review"),
|
||||
mcp_alias("mcp_fetch_candidate_handoff_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_preflight"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_cli_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_run_package_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_run_readiness"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_run_receipt_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_run_closeout_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_post_closeout_inventory_review"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_review_handoff"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_review_inventory"),
|
||||
mcp_alias("mcp_fetch_candidate_queue_writer_review_decision"),
|
||||
mcp_alias(
|
||||
"mcp_fetch_candidate_queue_writer_review_decision_approval",
|
||||
lane="mcp_fetch_review_decision",
|
||||
),
|
||||
mcp_alias(
|
||||
"mcp_fetch_candidate_queue_writer_review_decision_approval_writer_preflight",
|
||||
lane="mcp_fetch_review_decision",
|
||||
),
|
||||
mcp_alias("mcp_professional_source_governance", lane="mcp_source_governance"),
|
||||
mcp_alias(
|
||||
"mcp_fetch_target_source_governance_review",
|
||||
lane="mcp_source_governance",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
AI_REVIEW_POST_ALIASES = tuple(
|
||||
review_alias(name, lane="candidate_queue_review_post")
|
||||
for name in (
|
||||
"candidate_queue_review_decision_post_closeout_inventory",
|
||||
"candidate_queue_review_archive_summary",
|
||||
"candidate_queue_review_ai_summary_preflight",
|
||||
"candidate_queue_review_ai_summary_run_package",
|
||||
"candidate_queue_review_ai_summary_output_receipt",
|
||||
"candidate_queue_review_ai_summary_persistence_preflight",
|
||||
"candidate_queue_review_ai_summary_persistence_transaction",
|
||||
"candidate_queue_review_ai_summary_persistence_writer_preflight",
|
||||
"candidate_queue_review_ai_summary_persistence_run_package",
|
||||
"candidate_queue_review_completion_archive",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
AI_REVIEW_POST_AI_ALIASES = tuple(
|
||||
review_alias(name, lane="candidate_queue_review_post_ai")
|
||||
for name in (
|
||||
"candidate_queue_review_ai_summary_persistence_run_readiness",
|
||||
"candidate_queue_review_ai_summary_persistence_run_receipt",
|
||||
"candidate_queue_review_ai_summary_persistence_run_closeout",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_gate",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_package",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_readiness",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_run_receipt",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_closeout",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_archive_summary",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
AI_REVIEW_REPORT_ALIASES = tuple(
|
||||
review_alias(name, lane="candidate_queue_review_report")
|
||||
for name in (
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_input",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_package",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_readiness",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_run_receipt",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_closeout",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_archive_summary",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_handoff",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_index",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_write_preflight",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_write",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_package",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_readiness",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_run_receipt",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_commit",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_closeout",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_archive_summary",
|
||||
"candidate_queue_review_ai_summary_persistence_telegram_dispatch_report_catalog_record_final_closeout",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
AI_CONTROLLED_ROUTE_ALIASES = (
|
||||
AiRouteAlias(
|
||||
name="mcp_fetch_handoff",
|
||||
canonical_path=f"{AI_CONTROLLED_PREFIX}/mcp_fetch_handoff",
|
||||
legacy_path=LEGACY_FETCH_HANDOFF_PATH,
|
||||
methods=("GET", "POST"),
|
||||
lane="mcp_fetch",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="sample_plan",
|
||||
canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_plan",
|
||||
legacy_path=f"{LEGACY_SAMPLE_PREFIX}_plan",
|
||||
methods=("GET",),
|
||||
lane="sample_pipeline",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="sample_acceptance",
|
||||
canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_acceptance",
|
||||
legacy_path=f"{LEGACY_SAMPLE_PREFIX}_acceptance",
|
||||
methods=("GET",),
|
||||
lane="sample_pipeline",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="sample_review",
|
||||
canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_review",
|
||||
legacy_path=LEGACY_REVIEW_PREFIX,
|
||||
methods=("GET",),
|
||||
lane="sample_pipeline",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="sample_review_evaluate",
|
||||
canonical_path=f"{AI_CONTROLLED_PREFIX}/sample_review/evaluate",
|
||||
legacy_path=f"{LEGACY_REVIEW_PREFIX}/evaluate",
|
||||
methods=("POST",),
|
||||
lane="sample_pipeline",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_handoff",
|
||||
canonical_path=ai_review_path("/candidate_handoff"),
|
||||
legacy_path=legacy_review_path("/candidate_handoff"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_draft",
|
||||
canonical_path=ai_review_path("/candidate_queue_draft"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_draft"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_approval",
|
||||
canonical_path=ai_review_path("/candidate_queue_approval"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_approval"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_transaction",
|
||||
canonical_path=ai_review_path("/candidate_queue_transaction"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_transaction"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_status",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_status"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_status"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_preflight",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_preflight"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_preflight"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_postwrite_smoke",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_postwrite_smoke"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_postwrite_smoke"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_operator_drill",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_operator_drill"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_operator_drill"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_run_package",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_run_package"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_run_package"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_run_readiness",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_run_readiness"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_run_readiness"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_run_receipt",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_run_receipt"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_run_receipt"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_writer_run_closeout",
|
||||
canonical_path=ai_review_path("/candidate_queue_writer_run_closeout"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_writer_run_closeout"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_handoff",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_handoff"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_handoff"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_inventory",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_inventory"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_inventory"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_approval",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_approval"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_approval"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_transaction",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_transaction"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_transaction"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_status",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_status"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_status"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_preflight",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_preflight"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_preflight"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_postwrite_smoke",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_postwrite_smoke"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_postwrite_smoke"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_operator_drill",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_operator_drill"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_operator_drill"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_run_package",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_package"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_package"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_run_readiness",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_readiness"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_readiness"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_run_receipt",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_receipt"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_receipt"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
AiRouteAlias(
|
||||
name="candidate_queue_review_decision_writer_run_closeout",
|
||||
canonical_path=ai_review_path("/candidate_queue_review_decision_writer_run_closeout"),
|
||||
legacy_path=legacy_review_path("/candidate_queue_review_decision_writer_run_closeout"),
|
||||
methods=("POST",),
|
||||
lane="candidate_queue_decision_writer",
|
||||
),
|
||||
*MCP_AI_CONTROLLED_ALIASES,
|
||||
*AI_REVIEW_POST_ALIASES,
|
||||
*AI_REVIEW_POST_AI_ALIASES,
|
||||
*AI_REVIEW_REPORT_ALIASES,
|
||||
)
|
||||
|
||||
AI_CONTROLLED_CANONICAL_SMOKE_TARGETS = tuple(
|
||||
alias.canonical_path for alias in AI_CONTROLLED_ROUTE_ALIASES
|
||||
)
|
||||
|
||||
|
||||
def build_ai_controlled_route_alias_report() -> dict[str, object]:
|
||||
return {
|
||||
"policy": "read_only_market_intel_ai_controlled_route_aliases",
|
||||
"result": "AI_CONTROLLED_CANONICAL_ROUTES_READY",
|
||||
"primary_route_family": AI_CONTROLLED_PREFIX,
|
||||
"legacy_route_family_status": "compatibility_only",
|
||||
"canonical_count": len(AI_CONTROLLED_ROUTE_ALIASES),
|
||||
"legacy_compatibility_count": len(AI_CONTROLLED_ROUTE_ALIASES),
|
||||
"routes": [alias.as_dict() for alias in AI_CONTROLLED_ROUTE_ALIASES],
|
||||
"safety": {
|
||||
"read_only": True,
|
||||
"writes_database": False,
|
||||
"executes_network": False,
|
||||
"github_used": False,
|
||||
},
|
||||
}
|
||||
39967
services/pchome_mapping_backlog_service.py
Normal file
39967
services/pchome_mapping_backlog_service.py
Normal file
File diff suppressed because it is too large
Load Diff
49
tests/test_ai_automation_debt_service.py
Normal file
49
tests/test_ai_automation_debt_service.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_ai_automation_debt_scan_is_read_only_and_prioritized():
|
||||
from services.ai_automation_debt_service import build_ai_automation_debt_report
|
||||
|
||||
report = build_ai_automation_debt_report(max_findings=40, per_file_limit=6)
|
||||
|
||||
assert report["policy"] == "read_only_ai_automation_debt_scan"
|
||||
assert report["success"] is True
|
||||
assert report["result"] == "PRODUCT_SURFACE_CLEAR"
|
||||
assert report["summary"]["product_surface_blocker_count"] == 0
|
||||
assert report["summary"]["primary_human_gate_count"] == 0
|
||||
assert report["summary"]["ai_controlled_apply_ready"] is True
|
||||
assert report["summary"]["market_intel_ai_controlled_alias_count"] >= 10
|
||||
assert report["summary"]["controlled_apply_candidate_count"] == 0
|
||||
assert report["summary"]["category_counts"].get("market_intel_ai_controlled_apply_candidate", 0) == 0
|
||||
assert report["summary"]["category_counts"].get("automation_debt", 0) == 0
|
||||
assert report["summary"]["category_counts"].get("governance_doc_debt", 0) == 0
|
||||
assert report["summary"]["category_counts"].get("ea_legacy_callback_debt", 0) == 0
|
||||
assert report["safety"] == {
|
||||
"read_only": True,
|
||||
"writes_database": False,
|
||||
"executes_network": False,
|
||||
"uses_llm": False,
|
||||
"scans_raw_sessions": False,
|
||||
"github_used": False,
|
||||
}
|
||||
assert all(item["priority"] != "P0" for item in report["findings"])
|
||||
assert report["next_work_order"][0]["lane"] == "product_surface"
|
||||
assert report["next_work_order"][0]["status"] == "clear"
|
||||
assert report["next_work_order"][1]["lane"] == "market_intel_controlled_apply"
|
||||
assert report["next_work_order"][1]["status"] == "review_report_alias_layer_complete"
|
||||
assert report["next_work_order"][1]["alias_count"] >= 90
|
||||
assert report["next_work_order"][1]["target_count"] == 0
|
||||
assert report["next_work_order"][2]["target_count"] == 0
|
||||
assert report["next_work_order"][3]["lane"] == "legacy_compatibility_aliases"
|
||||
assert report["next_work_order"][3]["target_count"] == report["summary"]["legacy_compatibility_field_count"]
|
||||
|
||||
|
||||
def test_ai_automation_debt_api_route_is_registered():
|
||||
route_source = (ROOT / "routes" / "ai_routes.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "@ai_bp.route('/api/ai/automation-debt')" in route_source
|
||||
assert "build_ai_automation_debt_report" in route_source
|
||||
assert '"source_endpoint"] = "/api/ai/automation-debt"' in route_source
|
||||
20616
tests/test_pchome_mapping_backlog_report.py
Normal file
20616
tests/test_pchome_mapping_backlog_report.py
Normal file
File diff suppressed because it is too large
Load Diff
68
tests/test_production_version_truth.py
Normal file
68
tests/test_production_version_truth.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import json
|
||||
|
||||
from scripts.ops import check_production_version_truth as guard
|
||||
|
||||
|
||||
def _report(production_version="V10.725", local_version="V10.725", head_version="V10.725"):
|
||||
return {
|
||||
"policy": "production_health_is_latest_version_truth",
|
||||
"health_url": "https://mo.wooo.work/health",
|
||||
"production": {
|
||||
"status": "healthy",
|
||||
"database": "postgresql",
|
||||
"version": production_version,
|
||||
},
|
||||
"local": {
|
||||
"branch": "main",
|
||||
"head": "f3e412cd211f5e4601204b256aeb95eae073b441",
|
||||
"config_version": local_version,
|
||||
"head_config_version": head_version,
|
||||
},
|
||||
"origin_main": {
|
||||
"head": "f3e412cd211f5e4601204b256aeb95eae073b441",
|
||||
"matches_local_head": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_parse_config_version():
|
||||
assert guard.parse_config_version('SYSTEM_VERSION = "V10.725"\n') == "V10.725"
|
||||
|
||||
|
||||
def test_production_version_truth_passes_when_everything_matches():
|
||||
ok, errors = guard.evaluate(_report(), allow_local_version_drift=False)
|
||||
|
||||
assert ok is True
|
||||
assert errors == []
|
||||
|
||||
|
||||
def test_working_tree_version_drift_blocks_by_default():
|
||||
ok, errors = guard.evaluate(_report(local_version="V10.726"), allow_local_version_drift=False)
|
||||
|
||||
assert ok is False
|
||||
assert any("working-tree config.py version differs from production" in error for error in errors)
|
||||
|
||||
|
||||
def test_explicit_release_prep_can_allow_working_tree_version_drift():
|
||||
ok, errors = guard.evaluate(_report(local_version="V10.726"), allow_local_version_drift=True)
|
||||
|
||||
assert ok is True
|
||||
assert errors == []
|
||||
|
||||
|
||||
def test_head_version_must_still_match_production():
|
||||
ok, errors = guard.evaluate(_report(head_version="V10.724"), allow_local_version_drift=True)
|
||||
|
||||
assert ok is False
|
||||
assert any("HEAD config.py version differs from production" in error for error in errors)
|
||||
|
||||
|
||||
def test_json_output_contains_production_truth_policy(monkeypatch, capsys):
|
||||
monkeypatch.setattr(guard, "build_report", lambda health_url, timeout: _report())
|
||||
|
||||
exit_code = guard.main(["--json"])
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
|
||||
assert exit_code == 0
|
||||
assert payload["policy"] == "production_health_is_latest_version_truth"
|
||||
assert payload["production"]["version"] == "V10.725"
|
||||
Reference in New Issue
Block a user