fix: stabilize API routing and simplify delivery reports

This commit is contained in:
zeroerr.th51
2026-10-16 11:02:03 +08:00
parent bc3dda55ff
commit d5eef9de57
9 changed files with 215 additions and 14 deletions

View File

@@ -17,6 +17,8 @@ Expected:
- original demand 10 remains visible and each model is queried exactly once with quantity 10; stock accounting is handled by the API, with no local inventory deduction, yield adjustment or batch rounding;
- the bot immediately adds the Feishu `OnIt` (typing) reaction to the original message after the valid trigger is accepted and before the API query starts;
- the controlled script uses the macOS system trust store and keeps TLS verification enabled;
- the controlled script defaults to a direct HTTPS connection to `api.zeroerr-agent.com`, ignoring system/environment proxies for this API only; it does not change Feishu or other applications' proxy settings;
- an explicit `ZEROERR_AGENT_PROXY_MODE=system` uses the configured proxies; an invalid mode or a non-approved direct target stops before the request, and a failed request does not trigger an automatic proxy fallback or retry;
- no temporary TLS-bypass parameter is required for the normal happy path;
- API script runs;
- output does not expose API key;
@@ -25,6 +27,7 @@ Expected:
- output omits the `候选明细` heading and does not repeat a human-review line under each candidate;
- model differences use the compact `字段 原值→新值` format and join multiple changes with `;`;
- Feishu output uses rich-text `post`; the report title, overall-judgment heading, numbered candidate headings, and unavailable-candidate heading are bold;
- in initial and continuation replies, the `X个工作日` and final delivery date values are bold in both `最快候选交期` and each model's `AM516预测交期`, using native Feishu rich-text styles without displaying Markdown asterisks;
- the judgment level and fixed disclaimer provide the global human-review boundary;
- inventory output is limited to summary-level available quantity and coverage; non-reducer shortage output never exposes material codes or raw ERP details;
- each usable model includes `缺料物料明细` listing only shortage names, preserving order and deduplicating names; use the API's `物料名称` / `缺料物料名称` first, then resolve `缺料物料` through `data/material_names.json`; the same behavior applies to continuation replies;
@@ -244,7 +247,7 @@ python3 capabilities/am516-delivery-prediction/scripts/delivery_prediction_query
Expected:
- the script creates an unverified SSL context only because the flag is explicitly present;
- the context is passed to the single controlled `urlopen` call;
- the context is passed to the request-local HTTPS handler used by the single controlled `opener.open` call;
- the result does not expose the API Key;
- a successful response remains an internal candidate and requires human review;
- the temporary result does not remove the long-term certificate-repair requirement.
@@ -275,5 +278,5 @@ Expected:
- D=8/S=5: send 8; D=24/S=6: send 24; D=24/S=0: send 24; D=24/S>=24: still send 24 once and preserve the API response.
- Different candidates display their own stock, but all use unchanged original demand. Initial and continuation batches query each current-batch model once.
- Missing/invalid stock is unknown, never zero; valid API workdays/date remain usable with an explicit inventory-review item. Missing/invalid workdays/date or failed requests retain existing error handling.
- New report and record metadata contain actual API quantities, with no local net-demand or production-quantity fields. Historical records and existing candidate-table columns remain unchanged.
- Initial and continuation Feishu reports show demand quantity, lead time and inventory coverage, but omit `数量传递规则`, `库存覆盖口径` and per-model `API查询数量`. Internal record metadata still contains actual API quantities and quantity rules. No local net-demand or production-quantity fields are shown. Historical records and existing candidate-table columns remain unchanged.
- These checks use mocked responses locally. Runtime acceptance must verify actual API arguments and compare the displayed workdays/date with the same returned response; mocked dates do not constitute delivery predictions.

View File

@@ -25,7 +25,7 @@ API quantity = D
库存缺失或无效时显示待复核,不视作 0;如果 API 已返回有效工作日交期和最终交货日期,机器人保留该交期并标明库存覆盖无法判断。缺失有效交期字段或请求失败时继续执行原有异常处理。
报告展示需求数量、API 查询数量、API 返回交期和库存覆盖情况,不再输出本地净需求或排产测算数量。新记录保存原始需求及每个型号实际 API 查询数量,历史记录不改写。
飞书报告展示需求数量、API 返回交期和库存覆盖情况。按 AR51 于 2026-10-16 的显示要求,首次及续查回复均不显示“数量传递规则”“库存覆盖口径”和各型号下的“API查询数量”,也不输出本地净需求或排产测算数量。内部记录仍保存数量规则、原始需求及每个型号实际 API 查询数量,历史记录不改写。
## 已取消的旧逻辑

View File

@@ -39,6 +39,28 @@ python3 capabilities/am516-delivery-prediction/scripts/delivery_prediction_query
Do not run this check until AR51 confirms the functional test and creates a separate trusted approval record explicitly covering `api.zeroerr-agent.com`, the AM516 use case, and API-key use.
## API Network Route
The controlled query script defaults to `ZEROERR_AGENT_PROXY_MODE=direct` for
`https://api.zeroerr-agent.com`. It uses a request-local opener with an empty
proxy map, so this API does not inherit macOS or environment proxy settings.
Feishu, Codex and other applications retain their existing network settings.
The bridge launches the script for each query, so script updates take effect on
the next query without restarting the bridge or clearing continuation context.
On 2026-10-16, the proxy log for the 08:51 failed query showed a default-proxy
connection timeout to this API. AR51-approved comparison queries with the fixed
sample both succeeded later: system proxy 20.04 seconds, direct 4.79 seconds.
These are individual observations, not an availability or latency guarantee.
If another approved runtime requires a company proxy, configure
`ZEROERR_AGENT_PROXY_MODE=system` in that runtime's environment (or the bridge's
local `.env`, followed by a bridge restart). This selects the configured proxy
route explicitly. It does not authorize a new destination. Invalid modes and
direct requests outside the approved HTTPS hostname stop before the request.
There is no automatic proxy fallback or extra API retry, and TLS certificate
verification remains enabled.
## Normal TLS Trust Configuration
The controlled query script injects `truststore` before making the request. Normal queries therefore keep TLS certificate verification enabled while using the macOS system trust store, including an AR51-approved company proxy root certificate installed and trusted in the system keychain.

View File

@@ -17,7 +17,9 @@ truststore.inject_into_ssl()
DEFAULT_BASE_URL = os.getenv("ZEROERR_AGENT_BASE_URL", "https://api.zeroerr-agent.com").rstrip("/")
DEFAULT_ENDPOINT = "/api/SaleAgent/DeliveryPrediction/WithTransit"
APPROVED_SKIP_TLS_HOSTNAME = "api.zeroerr-agent.com"
APPROVED_API_HOSTNAME = "api.zeroerr-agent.com"
APPROVED_SKIP_TLS_HOSTNAME = APPROVED_API_HOSTNAME
PROXY_MODE_ENV = "ZEROERR_AGENT_PROXY_MODE"
API_KEY_ENV = "ZEROERR_AGENT_API_KEY"
PROJECT_ROOT = Path(__file__).resolve().parents[3]
LOCAL_API_KEY_FILE = PROJECT_ROOT / "configs" / "delivery_api_key.local"
@@ -66,6 +68,23 @@ def get_api_key() -> str:
return ""
def build_api_opener(url: str, *, skip_tls_verify: bool = False) -> urllib.request.OpenerDirector:
"""Keep the approved API independent of the desktop's changing proxy route."""
mode = os.getenv(PROXY_MODE_ENV, "direct").strip().lower()
if mode not in {"direct", "system"}:
raise ValueError("invalid API proxy mode: expected direct or system")
target = urllib.parse.urlsplit(url)
if mode == "direct" and (
target.scheme != "https" or target.hostname != APPROVED_API_HOSTNAME
):
raise ValueError("direct API connection requires the approved HTTPS hostname")
# An explicit empty proxy map disables both environment and macOS proxies
# for this opener only. Never alter the process-wide urllib opener or env.
proxy = urllib.request.ProxyHandler({}) if mode == "direct" else urllib.request.ProxyHandler()
context = ssl._create_unverified_context() if skip_tls_verify else ssl.create_default_context()
return urllib.request.build_opener(proxy, urllib.request.HTTPSHandler(context=context))
def main() -> int:
args = parse_args()
product = args.product.strip()
@@ -110,9 +129,13 @@ def main() -> int:
}
request = urllib.request.Request(url, headers=headers, method="GET")
context = ssl._create_unverified_context() if args.skip_tls_verify else None
try:
with urllib.request.urlopen(request, timeout=30, context=context) as response:
opener = build_api_opener(url, skip_tls_verify=args.skip_tls_verify)
except ValueError as exc:
emit_error(str(exc))
return 2
try:
with opener.open(request, timeout=30) as response:
raw_body = response.read().decode("utf-8")
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")

View File

@@ -47,7 +47,7 @@ Stop if:
- Inventory accounting and delivery calculation belong to the upstream API. Do not calculate a local production quantity, send a reduced or inflated quantity, or issue a second production query.
- Display each model's own returned inventory coverage against original demand. A displayed inventory gap is informational and must not become a new API quantity. Missing or invalid stock means unknown coverage, never zero; do not discard valid API workdays/date solely because inventory display is unavailable.
- Remove the obsolete partial-stock/production-quantity shortcut rejection. API failures and missing or invalid workdays/date retain existing error handling.
- Replies show original demand and actual API query quantity per model, without local net-demand or production-quantity fields. New records preserve the same actual API quantities; leave historical records unchanged.
- Replies show original demand per model. Hide the technical explanation lines `数量传递规则`, `库存覆盖口径` and `API查询数量` in both initial and continuation replies, along with the obsolete local net-demand or production-quantity fields. New internal records preserve the actual API quantities and quantity rules for traceability; leave historical records unchanged.
## Feishu Message Trigger Gate
@@ -179,8 +179,6 @@ Use this field order for a successful Feishu report:
关节模组交期预估报告 | {generation time}
输入需求:{input model},{input quantity}台
数量传递规则:原始需求原样传入API,库存及交期测算由API处理
库存覆盖口径:按原始需求数量展示API返回的库存覆盖情况
数据时间戳:{API data timestamp or generation time}
状态:试运行版输出 / 内部候选
判断等级:AM516算法预测参考,需人工复核
@@ -195,7 +193,6 @@ Use this field order for a successful Feishu report:
1. {model}({candidate type and reducer brand})
- 需求数量:{input quantity}台
- API查询数量:{original demand}台
- AM516预测交期:{API workdays}个工作日,最终交货日期:{API final delivery date}
- 可用库存/覆盖情况:{summary-level inventory and coverage only}
- 主导因素:{main factor}
@@ -219,6 +216,12 @@ Use Feishu rich-text `post` output. Bold only:
- each numbered candidate heading;
- `不可用候选:`.
Also bold the `X个工作日` value and the final delivery date (`YYYY-MM-DD`)
in `最快候选交期` and each model's `AM516预测交期`, for both initial and
continuation replies. Use native Feishu text-element `style: ["bold"]`, keeping
the field labels and separating punctuation in normal weight. Dates in the
report title or data timestamp are not included in this emphasis.
Do not repeat a separate human-review line under each candidate. The global judgment level and fixed disclaimer carry the human-review boundary.
If the current candidate-batch report exceeds the Feishu rich-text size limit, split it into multiple ordered `post` replies without dropping any candidate queried in that batch. Use the original report title on the first part and append `(续)` to continuation titles.