@harrylabsj/kiwi 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/a2a/negotiate.d.ts +6 -2
- package/dist/a2a/negotiate.js +10 -3
- package/dist/a2a/negotiate.js.map +1 -1
- package/dist/a2a/node.d.ts +4 -1
- package/dist/a2a/node.js +56 -3
- package/dist/a2a/node.js.map +1 -1
- package/dist/a2a/server/merchant-handler.d.ts +11 -3
- package/dist/a2a/server/merchant-handler.js +84 -26
- package/dist/a2a/server/merchant-handler.js.map +1 -1
- package/dist/agent/buyer/buyer-tools.js +2 -2
- package/dist/agent/buyer/buyer-tools.js.map +1 -1
- package/dist/agent/kernel.js +5 -1
- package/dist/agent/kernel.js.map +1 -1
- package/dist/agent/memory/schema.d.ts +1 -1
- package/dist/agent/memory/schema.js +9 -1
- package/dist/agent/memory/schema.js.map +1 -1
- package/dist/agent/merchant/action-candidate.d.ts +18 -0
- package/dist/agent/merchant/action-candidate.js +51 -0
- package/dist/agent/merchant/action-candidate.js.map +1 -1
- package/dist/agent/merchant/fake-merchant-client.js +6 -1
- package/dist/agent/merchant/fake-merchant-client.js.map +1 -1
- package/dist/agent/merchant/intelligence/default-backend.js +25 -2
- package/dist/agent/merchant/intelligence/default-backend.js.map +1 -1
- package/dist/agent/merchant/merchant-client.d.ts +50 -1
- package/dist/agent/merchant/merchant-client.js +157 -8
- package/dist/agent/merchant/merchant-client.js.map +1 -1
- package/dist/agent/merchant/merchant-presentations.js +25 -2
- package/dist/agent/merchant/merchant-presentations.js.map +1 -1
- package/dist/agent/merchant/merchant-tools.js +102 -188
- package/dist/agent/merchant/merchant-tools.js.map +1 -1
- package/dist/agent/merchant/types.d.ts +6 -2
- package/dist/agent/merchant/types.js +45 -0
- package/dist/agent/merchant/types.js.map +1 -1
- package/dist/auth/merchant-authorization.d.ts +49 -0
- package/dist/auth/merchant-authorization.js +61 -0
- package/dist/auth/merchant-authorization.js.map +1 -0
- package/dist/auth/merchant-oauth.d.ts +255 -0
- package/dist/auth/merchant-oauth.js +764 -0
- package/dist/auth/merchant-oauth.js.map +1 -0
- package/dist/auth/merchant-pairing.d.ts +62 -0
- package/dist/auth/merchant-pairing.js +199 -0
- package/dist/auth/merchant-pairing.js.map +1 -0
- package/dist/auth/merchant-sessions.d.ts +71 -0
- package/dist/auth/merchant-sessions.js +168 -0
- package/dist/auth/merchant-sessions.js.map +1 -0
- package/dist/buyer-core/build-service.d.ts +5 -0
- package/dist/buyer-core/build-service.js +12 -0
- package/dist/buyer-core/build-service.js.map +1 -1
- package/dist/buyer-core/errors.d.ts +1 -1
- package/dist/buyer-core/errors.js +2 -0
- package/dist/buyer-core/errors.js.map +1 -1
- package/dist/buyer-core/merchant-index.d.ts +19 -4
- package/dist/buyer-core/merchant-index.js +137 -16
- package/dist/buyer-core/merchant-index.js.map +1 -1
- package/dist/buyer-core/negotiator.d.ts +0 -10
- package/dist/buyer-core/negotiator.js +12 -1
- package/dist/buyer-core/negotiator.js.map +1 -1
- package/dist/buyer-core/quote-fetcher.js +2 -1
- package/dist/buyer-core/quote-fetcher.js.map +1 -1
- package/dist/buyer-core/service.d.ts +104 -0
- package/dist/buyer-core/service.js +105 -1
- package/dist/buyer-core/service.js.map +1 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +612 -20
- package/dist/cli.js.map +1 -1
- package/dist/config/profile.d.ts +30 -0
- package/dist/config/profile.js +126 -61
- package/dist/config/profile.js.map +1 -1
- package/dist/demo/demo-runner.js +12 -9
- package/dist/demo/demo-runner.js.map +1 -1
- package/dist/discovery/catalog-source/buyer-follows.d.ts +82 -0
- package/dist/discovery/catalog-source/buyer-follows.js +228 -0
- package/dist/discovery/catalog-source/buyer-follows.js.map +1 -0
- package/dist/discovery/catalog-source/connector-identity.d.ts +75 -0
- package/dist/discovery/catalog-source/connector-identity.js +175 -0
- package/dist/discovery/catalog-source/connector-identity.js.map +1 -0
- package/dist/discovery/catalog-source/errors.d.ts +1 -1
- package/dist/discovery/catalog-source/errors.js +2 -0
- package/dist/discovery/catalog-source/errors.js.map +1 -1
- package/dist/discovery/catalog-source/merchant-publications.d.ts +65 -0
- package/dist/discovery/catalog-source/merchant-publications.js +207 -0
- package/dist/discovery/catalog-source/merchant-publications.js.map +1 -0
- package/dist/discovery/catalog-source/register.d.ts +24 -0
- package/dist/discovery/catalog-source/register.js +46 -0
- package/dist/discovery/catalog-source/register.js.map +1 -1
- package/dist/discovery/catalog-source/source.js +2 -1
- package/dist/discovery/catalog-source/source.js.map +1 -1
- package/dist/fs/atomic-write.d.ts +18 -0
- package/dist/fs/atomic-write.js +29 -0
- package/dist/fs/atomic-write.js.map +1 -0
- package/dist/http/server.js +4 -1
- package/dist/http/server.js.map +1 -1
- package/dist/mcp/cli.d.ts +2 -0
- package/dist/mcp/cli.js +7 -6
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/merchant-auth.d.ts +78 -0
- package/dist/mcp/merchant-auth.js +146 -0
- package/dist/mcp/merchant-auth.js.map +1 -0
- package/dist/mcp/merchant-dirs.d.ts +26 -0
- package/dist/mcp/merchant-dirs.js +44 -0
- package/dist/mcp/merchant-dirs.js.map +1 -0
- package/dist/mcp/merchant-resources.d.ts +38 -0
- package/dist/mcp/merchant-resources.js +146 -0
- package/dist/mcp/merchant-resources.js.map +1 -0
- package/dist/mcp/merchant-server.d.ts +106 -0
- package/dist/mcp/merchant-server.js +615 -0
- package/dist/mcp/merchant-server.js.map +1 -0
- package/dist/mcp/merchant-tools.d.ts +103 -0
- package/dist/mcp/merchant-tools.js +517 -0
- package/dist/mcp/merchant-tools.js.map +1 -0
- package/dist/mcp/server.js +4 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts +5 -3
- package/dist/mcp/tools.js +102 -5
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/mcp/types.js +4 -0
- package/dist/mcp/types.js.map +1 -1
- package/dist/merchant/ops.d.ts +0 -11
- package/dist/merchant/ops.js +13 -1
- package/dist/merchant/ops.js.map +1 -1
- package/dist/merchant/workbench-service.d.ts +189 -0
- package/dist/merchant/workbench-service.js +417 -0
- package/dist/merchant/workbench-service.js.map +1 -0
- package/dist/merchant-admin/pending-page.d.ts +35 -0
- package/dist/merchant-admin/pending-page.js +69 -0
- package/dist/merchant-admin/pending-page.js.map +1 -0
- package/dist/merchant-core/commands.d.ts +99 -0
- package/dist/merchant-core/commands.js +179 -0
- package/dist/merchant-core/commands.js.map +1 -0
- package/dist/merchant-core/executor.d.ts +69 -0
- package/dist/merchant-core/executor.js +218 -0
- package/dist/merchant-core/executor.js.map +1 -0
- package/dist/merchant-core/negotiation-adapters.d.ts +55 -0
- package/dist/merchant-core/negotiation-adapters.js +55 -0
- package/dist/merchant-core/negotiation-adapters.js.map +1 -0
- package/dist/merchant-core/network-checks.d.ts +65 -0
- package/dist/merchant-core/network-checks.js +111 -0
- package/dist/merchant-core/network-checks.js.map +1 -0
- package/dist/merchant-core/operations.d.ts +64 -0
- package/dist/merchant-core/operations.js +136 -0
- package/dist/merchant-core/operations.js.map +1 -0
- package/dist/merchant-core/policy-runtime.d.ts +70 -0
- package/dist/merchant-core/policy-runtime.js +181 -0
- package/dist/merchant-core/policy-runtime.js.map +1 -0
- package/dist/merchant-core/product-import.d.ts +67 -0
- package/dist/merchant-core/product-import.js +160 -0
- package/dist/merchant-core/product-import.js.map +1 -0
- package/dist/merchant-core/service.d.ts +203 -0
- package/dist/merchant-core/service.js +402 -0
- package/dist/merchant-core/service.js.map +1 -0
- package/dist/merchant-gateway/catalog-publications.d.ts +96 -0
- package/dist/merchant-gateway/catalog-publications.js +212 -0
- package/dist/merchant-gateway/catalog-publications.js.map +1 -0
- package/dist/merchant-gateway/catalog-tools.d.ts +46 -0
- package/dist/merchant-gateway/catalog-tools.js +315 -0
- package/dist/merchant-gateway/catalog-tools.js.map +1 -0
- package/dist/merchant-gateway/cli.d.ts +75 -0
- package/dist/merchant-gateway/cli.js +461 -0
- package/dist/merchant-gateway/cli.js.map +1 -0
- package/dist/merchant-gateway/credential-vault.d.ts +44 -0
- package/dist/merchant-gateway/credential-vault.js +111 -0
- package/dist/merchant-gateway/credential-vault.js.map +1 -0
- package/dist/merchant-gateway/entry-server.d.ts +117 -0
- package/dist/merchant-gateway/entry-server.js +747 -0
- package/dist/merchant-gateway/entry-server.js.map +1 -0
- package/dist/merchant-gateway/instance-registration.d.ts +144 -0
- package/dist/merchant-gateway/instance-registration.js +391 -0
- package/dist/merchant-gateway/instance-registration.js.map +1 -0
- package/dist/merchant-gateway/instance-tools.d.ts +58 -0
- package/dist/merchant-gateway/instance-tools.js +186 -0
- package/dist/merchant-gateway/instance-tools.js.map +1 -0
- package/dist/merchant-gateway/mcp-proxy.d.ts +25 -0
- package/dist/merchant-gateway/mcp-proxy.js +192 -0
- package/dist/merchant-gateway/mcp-proxy.js.map +1 -0
- package/dist/merchant-gateway/pinned-fetch.d.ts +54 -0
- package/dist/merchant-gateway/pinned-fetch.js +231 -0
- package/dist/merchant-gateway/pinned-fetch.js.map +1 -0
- package/dist/merchant-gateway/tenant-registry.d.ts +82 -0
- package/dist/merchant-gateway/tenant-registry.js +190 -0
- package/dist/merchant-gateway/tenant-registry.js.map +1 -0
- package/dist/merchant-gateway/tool-bundle.d.ts +26 -0
- package/dist/merchant-gateway/tool-bundle.js +56 -0
- package/dist/merchant-gateway/tool-bundle.js.map +1 -0
- package/dist/merchant-runtime/backup.d.ts +48 -0
- package/dist/merchant-runtime/backup.js +226 -0
- package/dist/merchant-runtime/backup.js.map +1 -0
- package/dist/merchant-runtime/health.d.ts +99 -0
- package/dist/merchant-runtime/health.js +206 -0
- package/dist/merchant-runtime/health.js.map +1 -0
- package/dist/merchant-runtime/jobs.d.ts +64 -0
- package/dist/merchant-runtime/jobs.js +107 -0
- package/dist/merchant-runtime/jobs.js.map +1 -0
- package/dist/merchant-runtime/manager.d.ts +94 -0
- package/dist/merchant-runtime/manager.js +305 -0
- package/dist/merchant-runtime/manager.js.map +1 -0
- package/dist/merchant-runtime/services.d.ts +30 -0
- package/dist/merchant-runtime/services.js +51 -0
- package/dist/merchant-runtime/services.js.map +1 -0
- package/dist/net/url.d.ts +26 -0
- package/dist/net/url.js +32 -0
- package/dist/net/url.js.map +1 -0
- package/dist/product-cli.js +22 -3
- package/dist/product-cli.js.map +1 -1
- package/dist/product-compat.d.ts +9 -1
- package/dist/product-compat.js +12 -1
- package/dist/product-compat.js.map +1 -1
- package/dist/product-init.js +17 -5
- package/dist/product-init.js.map +1 -1
- package/dist/product-publish.js +2 -1
- package/dist/product-publish.js.map +1 -1
- package/package.json +3 -2
- package/skills/kiwi-buyer/SKILL.md +2 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 网络注册与域名接入检查(V2 阶段四:F25 + F03)。
|
|
18
|
+
*
|
|
19
|
+
* F25 注册状态检查:catalog 可达性 + 注册信息有效性(/v1/agents/{id} 能解析
|
|
20
|
+
* 出本商家记录且指向的 Agent Card URL 非空);失效可检测(结构化报告,
|
|
21
|
+
* ok:false + 各分项 error)。fail-closed:任何一步失败不假装已注册。
|
|
22
|
+
*
|
|
23
|
+
* F03 域名接入向导:产出结构化检查单(公开投影无私密字段 / public_url /
|
|
24
|
+
* 反代 TLS / DNS 人工核验项)——无法自动化的步骤显式标 manual,不假装完成。
|
|
25
|
+
*/
|
|
26
|
+
import type { AgentProfile } from "../config/profile.js";
|
|
27
|
+
export interface NetworkRegistrationReport {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
checked_at: string;
|
|
30
|
+
catalog: {
|
|
31
|
+
reachable: boolean;
|
|
32
|
+
base_url: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
};
|
|
35
|
+
registration: {
|
|
36
|
+
registered: boolean;
|
|
37
|
+
agent_id: string;
|
|
38
|
+
agent_card_url?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** F25:注册状态检查(结构化、可查询;失效可检测)。 */
|
|
43
|
+
export declare function checkNetworkRegistration(deps: {
|
|
44
|
+
catalogBaseUrl: string;
|
|
45
|
+
agentId: string;
|
|
46
|
+
fetchImpl?: typeof fetch;
|
|
47
|
+
now?: () => string;
|
|
48
|
+
}): Promise<NetworkRegistrationReport>;
|
|
49
|
+
export interface DomainChecklistItem {
|
|
50
|
+
id: string;
|
|
51
|
+
title: string;
|
|
52
|
+
status: "ok" | "pending" | "manual";
|
|
53
|
+
detail: string;
|
|
54
|
+
}
|
|
55
|
+
export interface DomainOnboardingChecklist {
|
|
56
|
+
ok: boolean;
|
|
57
|
+
merchant_id: string;
|
|
58
|
+
items: DomainChecklistItem[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* F03 域名接入向导检查单:自动可判定的给 ok/pending,DNS/TLS 等需人工的标
|
|
62
|
+
* manual(明确人工检查单)。公开投影分离检查:profile 的公开字段集合不包
|
|
63
|
+
* 含任何私密键(*_private / token / secret)。
|
|
64
|
+
*/
|
|
65
|
+
export declare function buildDomainOnboardingChecklist(profile: AgentProfile): DomainOnboardingChecklist;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/** F25:注册状态检查(结构化、可查询;失效可检测)。 */
|
|
17
|
+
export async function checkNetworkRegistration(deps) {
|
|
18
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
19
|
+
const checkedAt = (deps.now ?? (() => new Date().toISOString()))();
|
|
20
|
+
const base = deps.catalogBaseUrl.replace(/\/+$/, "");
|
|
21
|
+
const report = {
|
|
22
|
+
ok: false,
|
|
23
|
+
checked_at: checkedAt,
|
|
24
|
+
catalog: { reachable: false, base_url: base },
|
|
25
|
+
registration: { registered: false, agent_id: deps.agentId },
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetchImpl(`${base}/health`, { signal: AbortSignal.timeout(3_000) });
|
|
29
|
+
report.catalog.reachable = res.ok;
|
|
30
|
+
if (!res.ok)
|
|
31
|
+
report.catalog.error = `HTTP ${res.status}`;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
report.catalog.error = err instanceof Error ? err.message : String(err);
|
|
35
|
+
return report; // 不可达即失效(fail-closed)
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetchImpl(`${base}/v1/agents/${encodeURIComponent(deps.agentId)}`, {
|
|
39
|
+
signal: AbortSignal.timeout(3_000),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
report.registration.error = `HTTP ${res.status}(注册记录不可得或已失效)`;
|
|
43
|
+
return report;
|
|
44
|
+
}
|
|
45
|
+
const body = (await res.json());
|
|
46
|
+
const cardUrl = body.agent?.agent_card_url;
|
|
47
|
+
if (typeof cardUrl === "string" && cardUrl !== "") {
|
|
48
|
+
report.registration.registered = true;
|
|
49
|
+
report.registration.agent_card_url = cardUrl;
|
|
50
|
+
report.ok = true;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
report.registration.error = "注册记录缺 agent_card_url(无效注册)";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
report.registration.error = err instanceof Error ? err.message : String(err);
|
|
58
|
+
}
|
|
59
|
+
return report;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* F03 域名接入向导检查单:自动可判定的给 ok/pending,DNS/TLS 等需人工的标
|
|
63
|
+
* manual(明确人工检查单)。公开投影分离检查:profile 的公开字段集合不包
|
|
64
|
+
* 含任何私密键(*_private / token / secret)。
|
|
65
|
+
*/
|
|
66
|
+
export function buildDomainOnboardingChecklist(profile) {
|
|
67
|
+
const items = [];
|
|
68
|
+
const publicUrl = profile.merchant_public?.public_url;
|
|
69
|
+
items.push({
|
|
70
|
+
id: "public-url",
|
|
71
|
+
title: "公网 A2A 域名已配置(merchant_public.public_url)",
|
|
72
|
+
status: publicUrl !== undefined && publicUrl !== "" ? "ok" : "pending",
|
|
73
|
+
detail: publicUrl ?? "未配置——kiwi merchant setup-public 引导",
|
|
74
|
+
});
|
|
75
|
+
// 公开投影分离:公开配置绝不携带私密键(底价/成本/token)
|
|
76
|
+
const PUBLIC_FORBIDDEN = /(_private|token|secret|password)/i;
|
|
77
|
+
const publicSection = JSON.stringify(profile.merchant_public ?? {});
|
|
78
|
+
items.push({
|
|
79
|
+
id: "public-projection-separation",
|
|
80
|
+
title: "公开投影与私有数据分离(公开配置无私密键)",
|
|
81
|
+
status: PUBLIC_FORBIDDEN.test(publicSection) ? "pending" : "ok",
|
|
82
|
+
detail: PUBLIC_FORBIDDEN.test(publicSection)
|
|
83
|
+
? "merchant_public 段含疑似私密键——公开投影不得含 *_private/token/secret"
|
|
84
|
+
: "merchant_public 段无私密键",
|
|
85
|
+
});
|
|
86
|
+
items.push({
|
|
87
|
+
id: "dns",
|
|
88
|
+
title: "DNS 解析指向服务器(人工核验)",
|
|
89
|
+
status: "manual",
|
|
90
|
+
detail: "人工:dig <域名> 确认 A/AAAA 记录指向部署机;kiwi merchant setup-public --check 可辅助",
|
|
91
|
+
});
|
|
92
|
+
items.push({
|
|
93
|
+
id: "tls",
|
|
94
|
+
title: "TLS 证书有效(人工核验/反代自动续期)",
|
|
95
|
+
status: "manual",
|
|
96
|
+
detail: "人工:确认 Caddy/Nginx 反代 TLS 正常且自动续期;证书临期告警接 runtime 健康检查",
|
|
97
|
+
});
|
|
98
|
+
items.push({
|
|
99
|
+
id: "loopback-a2a",
|
|
100
|
+
title: "A2A 节点仅监听 loopback(反代前置)",
|
|
101
|
+
status: "ok",
|
|
102
|
+
detail: "startA2aNode 绑定 127.0.0.1,公网经反代;KIWI_A2A_AUTH 守卫 fail-closed(BUG-02)",
|
|
103
|
+
});
|
|
104
|
+
const ok = items.every((i) => i.status === "ok" || i.status === "manual");
|
|
105
|
+
return {
|
|
106
|
+
ok,
|
|
107
|
+
merchant_id: profile.owner_id,
|
|
108
|
+
items,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=network-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-checks.js","sourceRoot":"","sources":["../../src/merchant-core/network-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
17
|
+
/** running 状态的陈旧阈值(审查 P2):超过该时长无更新视为进程中断残留,
|
|
18
|
+
* 同幂等键重入时标记 failed 并放行新任务。30 分钟 > 单行 HTTP 超时(10s)
|
|
19
|
+
* × 批量行数的合理上限。 */
|
|
20
|
+
export declare const RUNNING_STALE_MS: number;
|
|
21
|
+
export declare const OPERATION_STATUSES: readonly ["queued", "running", "succeeded", "partially_failed", "failed"];
|
|
22
|
+
export type OperationStatus = (typeof OPERATION_STATUSES)[number];
|
|
23
|
+
export interface MerchantOperation {
|
|
24
|
+
operation_id: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
idempotency_key: string;
|
|
27
|
+
status: OperationStatus;
|
|
28
|
+
/** 逐项回执(部分成功:哪行/哪项成功失败及原因)。 */
|
|
29
|
+
receipts: Array<{
|
|
30
|
+
item: string;
|
|
31
|
+
ok: boolean;
|
|
32
|
+
detail?: string;
|
|
33
|
+
}>;
|
|
34
|
+
error?: string;
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at: string;
|
|
37
|
+
}
|
|
38
|
+
export declare class MerchantOperationStore {
|
|
39
|
+
private readonly db;
|
|
40
|
+
private readonly now;
|
|
41
|
+
constructor(options: {
|
|
42
|
+
db: DatabaseSync;
|
|
43
|
+
now?: () => string;
|
|
44
|
+
});
|
|
45
|
+
/** 幂等创建:同 (kind, idempotency_key) 返回既有 operation(created=false)。
|
|
46
|
+
* 审查 P2:命中 running 且长时间无更新(超过 RUNNING_STALE_MS,疑似进程
|
|
47
|
+
* 中断)→ 标 failed 后新建,避免同幂等键被永久卡死的 operation 吞掉。 */
|
|
48
|
+
createOrGet(input: {
|
|
49
|
+
kind: string;
|
|
50
|
+
idempotencyKey: string;
|
|
51
|
+
}): {
|
|
52
|
+
operation: MerchantOperation;
|
|
53
|
+
created: boolean;
|
|
54
|
+
};
|
|
55
|
+
get(operationId: string): MerchantOperation | undefined;
|
|
56
|
+
private set;
|
|
57
|
+
markRunning(operationId: string): MerchantOperation;
|
|
58
|
+
/** 终态:receipts 全 ok → succeeded;有成功有失败 → partially_failed;全失败/异常 → failed。 */
|
|
59
|
+
finish(operationId: string, receipts: Array<{
|
|
60
|
+
item: string;
|
|
61
|
+
ok: boolean;
|
|
62
|
+
detail?: string;
|
|
63
|
+
}>, error?: string): MerchantOperation;
|
|
64
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 长任务 operation 机制(V2 阶段四:src/merchant-core/operations.ts)。
|
|
18
|
+
*
|
|
19
|
+
* 状态机:queued → running → succeeded / partially_failed / failed。
|
|
20
|
+
* 幂等:相同 idempotency_key 返回同一 operation,不重复执行
|
|
21
|
+
* (先记录执行意图;远端返回不确定时先对账,不盲目重发)。
|
|
22
|
+
* 落盘:与命令记录同一 state.sqlite(单 owner 写),表操作幂等建表。
|
|
23
|
+
*/
|
|
24
|
+
import { randomBytes } from "node:crypto";
|
|
25
|
+
/** running 状态的陈旧阈值(审查 P2):超过该时长无更新视为进程中断残留,
|
|
26
|
+
* 同幂等键重入时标记 failed 并放行新任务。30 分钟 > 单行 HTTP 超时(10s)
|
|
27
|
+
* × 批量行数的合理上限。 */
|
|
28
|
+
export const RUNNING_STALE_MS = 30 * 60 * 1000;
|
|
29
|
+
export const OPERATION_STATUSES = ["queued",
|
|
30
|
+
"running",
|
|
31
|
+
"succeeded",
|
|
32
|
+
"partially_failed",
|
|
33
|
+
"failed",
|
|
34
|
+
];
|
|
35
|
+
const OPERATION_SCHEMA = `
|
|
36
|
+
CREATE TABLE IF NOT EXISTS merchant_operations (
|
|
37
|
+
operation_id TEXT PRIMARY KEY,
|
|
38
|
+
kind TEXT NOT NULL,
|
|
39
|
+
idempotency_key TEXT NOT NULL,
|
|
40
|
+
status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','partially_failed','failed')),
|
|
41
|
+
receipts_json TEXT NOT NULL,
|
|
42
|
+
error TEXT,
|
|
43
|
+
created_at TEXT NOT NULL,
|
|
44
|
+
updated_at TEXT NOT NULL
|
|
45
|
+
);
|
|
46
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_merchant_operations_idem
|
|
47
|
+
ON merchant_operations (kind, idempotency_key);
|
|
48
|
+
`;
|
|
49
|
+
export class MerchantOperationStore {
|
|
50
|
+
db;
|
|
51
|
+
now;
|
|
52
|
+
constructor(options) {
|
|
53
|
+
this.db = options.db;
|
|
54
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
55
|
+
this.db.exec(OPERATION_SCHEMA);
|
|
56
|
+
}
|
|
57
|
+
/** 幂等创建:同 (kind, idempotency_key) 返回既有 operation(created=false)。
|
|
58
|
+
* 审查 P2:命中 running 且长时间无更新(超过 RUNNING_STALE_MS,疑似进程
|
|
59
|
+
* 中断)→ 标 failed 后新建,避免同幂等键被永久卡死的 operation 吞掉。 */
|
|
60
|
+
createOrGet(input) {
|
|
61
|
+
const existing = this.db
|
|
62
|
+
.prepare("SELECT * FROM merchant_operations WHERE kind = ? AND idempotency_key = ?")
|
|
63
|
+
.get(input.kind, input.idempotencyKey);
|
|
64
|
+
if (existing !== undefined) {
|
|
65
|
+
if (existing.status === "running" &&
|
|
66
|
+
Date.parse(this.now()) - Date.parse(existing.updated_at) > RUNNING_STALE_MS) {
|
|
67
|
+
this.set(existing.operation_id, "failed", [
|
|
68
|
+
{
|
|
69
|
+
item: "(operation)",
|
|
70
|
+
ok: false,
|
|
71
|
+
detail: `任务在 running 状态停留超过 ${Math.round(RUNNING_STALE_MS / 60000)} 分钟(疑似进程中断),已标记失败`,
|
|
72
|
+
},
|
|
73
|
+
], "stale_running");
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
return { operation: rowToOperation(existing), created: false };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const now = this.now();
|
|
80
|
+
const id = `op_${randomBytes(12).toString("hex")}`;
|
|
81
|
+
this.db
|
|
82
|
+
.prepare(`INSERT INTO merchant_operations (operation_id, kind, idempotency_key, status, receipts_json, created_at, updated_at)
|
|
83
|
+
VALUES (?, ?, ?, 'queued', '[]', ?, ?)`)
|
|
84
|
+
.run(id, input.kind, input.idempotencyKey, now, now);
|
|
85
|
+
return { operation: this.get(id), created: true };
|
|
86
|
+
}
|
|
87
|
+
get(operationId) {
|
|
88
|
+
const row = this.db
|
|
89
|
+
.prepare("SELECT * FROM merchant_operations WHERE operation_id = ?")
|
|
90
|
+
.get(operationId);
|
|
91
|
+
return row === undefined ? undefined : rowToOperation(row);
|
|
92
|
+
}
|
|
93
|
+
set(operationId, status, receipts, error) {
|
|
94
|
+
this.db
|
|
95
|
+
.prepare("UPDATE merchant_operations SET status = ?, receipts_json = ?, error = ?, updated_at = ? WHERE operation_id = ?")
|
|
96
|
+
.run(status, JSON.stringify(receipts), error ?? null, this.now(), operationId);
|
|
97
|
+
return this.get(operationId);
|
|
98
|
+
}
|
|
99
|
+
markRunning(operationId) {
|
|
100
|
+
const op = this.get(operationId);
|
|
101
|
+
return this.set(operationId, "running", op?.receipts ?? []);
|
|
102
|
+
}
|
|
103
|
+
/** 终态:receipts 全 ok → succeeded;有成功有失败 → partially_failed;全失败/异常 → failed。 */
|
|
104
|
+
finish(operationId, receipts, error) {
|
|
105
|
+
let status;
|
|
106
|
+
if (error !== undefined) {
|
|
107
|
+
status = "failed";
|
|
108
|
+
}
|
|
109
|
+
else if (receipts.length === 0) {
|
|
110
|
+
status = "succeeded";
|
|
111
|
+
}
|
|
112
|
+
else if (receipts.every((r) => r.ok)) {
|
|
113
|
+
status = "succeeded";
|
|
114
|
+
}
|
|
115
|
+
else if (receipts.some((r) => r.ok)) {
|
|
116
|
+
status = "partially_failed";
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
status = "failed";
|
|
120
|
+
}
|
|
121
|
+
return this.set(operationId, status, receipts, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function rowToOperation(row) {
|
|
125
|
+
return {
|
|
126
|
+
operation_id: row.operation_id,
|
|
127
|
+
kind: row.kind,
|
|
128
|
+
idempotency_key: row.idempotency_key,
|
|
129
|
+
status: row.status,
|
|
130
|
+
receipts: JSON.parse(row.receipts_json),
|
|
131
|
+
...(row.error !== null ? { error: row.error } : {}),
|
|
132
|
+
created_at: row.created_at,
|
|
133
|
+
updated_at: row.updated_at,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/merchant-core/operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { type MerchantPolicy } from "../config/profile.js";
|
|
17
|
+
/** 当前运行策略快照(读端/写端统一返回口径)。 */
|
|
18
|
+
export interface RunningPolicy {
|
|
19
|
+
/** 当前生效策略(覆盖层文件内容;undefined = 尚未配置任何策略)。 */
|
|
20
|
+
policy: MerchantPolicy | undefined;
|
|
21
|
+
/** 单调递增版本(0 = 从未热更新过;持久化在覆盖层文件中)。 */
|
|
22
|
+
version: number;
|
|
23
|
+
/** 生效策略稳定序列化(键排序)的 sha256 hex。 */
|
|
24
|
+
digest: string;
|
|
25
|
+
updated_at: string;
|
|
26
|
+
}
|
|
27
|
+
/** applyPolicyOverride 的执行回执(进命令记录;不含策略数值本身——私密)。 */
|
|
28
|
+
export interface ApplyPolicyResult {
|
|
29
|
+
version: number;
|
|
30
|
+
digest: string;
|
|
31
|
+
updated_at: string;
|
|
32
|
+
/** 本次 patch 涉及的顶层键(值不透出)。 */
|
|
33
|
+
applied_keys: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface MerchantPolicyRuntimeOptions {
|
|
36
|
+
/** 启动 profile 的 merchant_policy(无覆盖层文件时的初始生效策略)。 */
|
|
37
|
+
basePolicy: MerchantPolicy | undefined;
|
|
38
|
+
/** 覆盖层文件绝对路径(`<merchantDataDir>/policy-overrides.json`)。 */
|
|
39
|
+
file: string;
|
|
40
|
+
now: () => string;
|
|
41
|
+
/** 读端告警出口(缺省 stderr)。 */
|
|
42
|
+
log?: (message: string) => void;
|
|
43
|
+
}
|
|
44
|
+
export declare class MerchantPolicyRuntime {
|
|
45
|
+
private readonly basePolicy;
|
|
46
|
+
private readonly file;
|
|
47
|
+
private readonly now;
|
|
48
|
+
private readonly log;
|
|
49
|
+
private state;
|
|
50
|
+
/** 上次成功加载/写入的文件签名(mtimeMs+size);与缓存一致则不重读。 */
|
|
51
|
+
private lastSig;
|
|
52
|
+
/** 已告警过的坏文件签名(同一坏文件只告警一次)。 */
|
|
53
|
+
private warnedSig;
|
|
54
|
+
constructor(options: MerchantPolicyRuntimeOptions);
|
|
55
|
+
/** 当前生效策略快照(读端每请求调用:stat 缓存命中时 O(1))。 */
|
|
56
|
+
current(): RunningPolicy;
|
|
57
|
+
/** 仅取生效策略(A2A handler provider 用)。 */
|
|
58
|
+
effective(): MerchantPolicy | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* 应用策略 patch(写端):合并 → 校验 → 原子写完整生效策略 → 更新内存。
|
|
61
|
+
* 校验失败抛错(ProfileError):不落盘、不改内存、版本不前进。
|
|
62
|
+
*/
|
|
63
|
+
apply(patch: Record<string, unknown>): ApplyPolicyResult;
|
|
64
|
+
private statSig;
|
|
65
|
+
/** 读端:文件签名变化才重载;坏文件保留上一个良好策略(同一坏文件告警一次)。
|
|
66
|
+
* 审查 P2:文件被删除(sig === undefined)→ 显式告警一次并保留最后良好
|
|
67
|
+
* 策略(内存态永不静默冻结——操作者删除覆盖层的语义是「回退 profile」,
|
|
68
|
+
* 需两个进程都重启或显式恢复,此处必须让告警可见)。 */
|
|
69
|
+
private reloadIfChanged;
|
|
70
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2026 harrylabsj
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 运行中商家策略提供器(BUG-07 修复)。
|
|
18
|
+
*
|
|
19
|
+
* 之前 `applyPolicyOverride` 只把 patch 落 `policy-overrides.json`,A2A handler
|
|
20
|
+
* 与写执行器仍用启动时加载的 profile.merchant_policy——商家看到"已生效"但报价
|
|
21
|
+
* 策略没变。现在:
|
|
22
|
+
*
|
|
23
|
+
* - 写端(MCP/core 进程):patch 与当前生效策略合并 → `parseMerchantPolicy`
|
|
24
|
+
* 校验(与 profile 同规则;未知/非法字段拒绝,失败不落盘不生效)→ 原子写
|
|
25
|
+
* **完整生效策略**(tmp+rename,0600)→ 更新内存;
|
|
26
|
+
* - 读端(A2A 进程,与写端不同进程):每次 `current()` stat 文件 mtime/size,
|
|
27
|
+
* 变化才重载(每报价一次 stat,开销可忽略);文件损坏/无效时保留上一个
|
|
28
|
+
* 良好策略并告警(fail-safe,不因覆盖层坏档拒绝报价);
|
|
29
|
+
* - 文件内容为完整生效策略(版本 + 时间 + policy):一旦存在即完全接管
|
|
30
|
+
* (重启后延续,profile 后续改动被覆盖层遮蔽——以文件为准);版本单调
|
|
31
|
+
* 递增并持久化;digest 为生效策略的稳定序列化 sha256。
|
|
32
|
+
*
|
|
33
|
+
* patch 合并语义:顶层浅合并;值为 `null` 的键表示删除该键;per-SKU 映射
|
|
34
|
+
* (price_floors/sku_max_discount_percent/promos)整体替换。
|
|
35
|
+
*/
|
|
36
|
+
import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
37
|
+
import { createHash } from "node:crypto";
|
|
38
|
+
import path from "node:path";
|
|
39
|
+
import { parseMerchantPolicy } from "../config/profile.js";
|
|
40
|
+
/** 键排序的稳定 JSON 序列化(digest 与键序无关)。 */
|
|
41
|
+
function stableStringify(value) {
|
|
42
|
+
if (value === null || typeof value !== "object")
|
|
43
|
+
return JSON.stringify(value) ?? "null";
|
|
44
|
+
if (Array.isArray(value))
|
|
45
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
46
|
+
const entries = Object.entries(value)
|
|
47
|
+
.filter(([, v]) => v !== undefined)
|
|
48
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
49
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
50
|
+
}
|
|
51
|
+
function digestOf(policy) {
|
|
52
|
+
return createHash("sha256").update(stableStringify(policy ?? {})).digest("hex");
|
|
53
|
+
}
|
|
54
|
+
export class MerchantPolicyRuntime {
|
|
55
|
+
basePolicy;
|
|
56
|
+
file;
|
|
57
|
+
now;
|
|
58
|
+
log;
|
|
59
|
+
state;
|
|
60
|
+
/** 上次成功加载/写入的文件签名(mtimeMs+size);与缓存一致则不重读。 */
|
|
61
|
+
lastSig;
|
|
62
|
+
/** 已告警过的坏文件签名(同一坏文件只告警一次)。 */
|
|
63
|
+
warnedSig;
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.basePolicy = options.basePolicy;
|
|
66
|
+
this.file = options.file;
|
|
67
|
+
this.now = options.now;
|
|
68
|
+
this.log = options.log ?? ((m) => process.stderr.write(`${m}\n`));
|
|
69
|
+
this.state = {
|
|
70
|
+
policy: options.basePolicy,
|
|
71
|
+
version: 0,
|
|
72
|
+
digest: digestOf(options.basePolicy),
|
|
73
|
+
updated_at: options.now(),
|
|
74
|
+
};
|
|
75
|
+
this.reloadIfChanged();
|
|
76
|
+
}
|
|
77
|
+
/** 当前生效策略快照(读端每请求调用:stat 缓存命中时 O(1))。 */
|
|
78
|
+
current() {
|
|
79
|
+
this.reloadIfChanged();
|
|
80
|
+
return this.state;
|
|
81
|
+
}
|
|
82
|
+
/** 仅取生效策略(A2A handler provider 用)。 */
|
|
83
|
+
effective() {
|
|
84
|
+
return this.current().policy;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 应用策略 patch(写端):合并 → 校验 → 原子写完整生效策略 → 更新内存。
|
|
88
|
+
* 校验失败抛错(ProfileError):不落盘、不改内存、版本不前进。
|
|
89
|
+
*/
|
|
90
|
+
apply(patch) {
|
|
91
|
+
if (Object.keys(patch).length === 0) {
|
|
92
|
+
throw new Error("策略 patch 为空:至少提供一个待变更字段");
|
|
93
|
+
}
|
|
94
|
+
const currentPolicy = this.current().policy ?? {};
|
|
95
|
+
const merged = { ...currentPolicy };
|
|
96
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
97
|
+
if (value === null)
|
|
98
|
+
delete merged[key];
|
|
99
|
+
else
|
|
100
|
+
merged[key] = value;
|
|
101
|
+
}
|
|
102
|
+
// 与 profile 启动加载同一套校验规则(BUG-07:拒绝未知/非法字段)。
|
|
103
|
+
const policy = parseMerchantPolicy(merged, "policy-overrides");
|
|
104
|
+
const version = this.state.version + 1;
|
|
105
|
+
const updated_at = this.now();
|
|
106
|
+
const next = { policy, version, digest: digestOf(policy), updated_at };
|
|
107
|
+
// 原子写完整生效策略(tmp + rename,0600):读者要么看到旧文件要么看到
|
|
108
|
+
// 新文件,绝不读到半截 JSON。
|
|
109
|
+
mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
|
|
110
|
+
const tmp = `${this.file}.tmp-${process.pid}-${Date.now()}`;
|
|
111
|
+
writeFileSync(tmp, `${JSON.stringify({ version, updated_at, policy }, null, 2)}\n`, { mode: 0o600 });
|
|
112
|
+
renameSync(tmp, this.file);
|
|
113
|
+
this.state = next;
|
|
114
|
+
this.lastSig = this.statSig();
|
|
115
|
+
this.warnedSig = undefined;
|
|
116
|
+
return {
|
|
117
|
+
version,
|
|
118
|
+
digest: next.digest,
|
|
119
|
+
updated_at,
|
|
120
|
+
applied_keys: Object.keys(patch),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
statSig() {
|
|
124
|
+
try {
|
|
125
|
+
const st = statSync(this.file);
|
|
126
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** 读端:文件签名变化才重载;坏文件保留上一个良好策略(同一坏文件告警一次)。
|
|
133
|
+
* 审查 P2:文件被删除(sig === undefined)→ 显式告警一次并保留最后良好
|
|
134
|
+
* 策略(内存态永不静默冻结——操作者删除覆盖层的语义是「回退 profile」,
|
|
135
|
+
* 需两个进程都重启或显式恢复,此处必须让告警可见)。 */
|
|
136
|
+
reloadIfChanged() {
|
|
137
|
+
const sig = this.statSig();
|
|
138
|
+
if (sig === undefined) {
|
|
139
|
+
if (this.lastSig !== undefined && sig !== this.warnedSig) {
|
|
140
|
+
this.log(`[policy-runtime] ${this.file} 已消失(被删除?):保留最后良好策略;` +
|
|
141
|
+
"如需回退 profile 策略请重启相关进程或重新写入完整策略文件");
|
|
142
|
+
this.warnedSig = sig;
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (sig === this.lastSig)
|
|
147
|
+
return;
|
|
148
|
+
try {
|
|
149
|
+
const raw = JSON.parse(readFileSync(this.file, "utf8"));
|
|
150
|
+
// legacy(BUG-07 修复前只落 patch):无 policy 字段 → 视为未生效,告警。
|
|
151
|
+
if (raw.policy === undefined) {
|
|
152
|
+
if (sig !== this.warnedSig) {
|
|
153
|
+
this.log(`[policy-runtime] ${this.file} 为 legacy patch 格式或缺少 policy 字段,忽略(以 profile 策略为准)`);
|
|
154
|
+
this.warnedSig = sig;
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const policy = parseMerchantPolicy(raw.policy, "policy-overrides");
|
|
159
|
+
const version = typeof raw.version === "number" && Number.isInteger(raw.version) && raw.version >= 1
|
|
160
|
+
? raw.version
|
|
161
|
+
: 1;
|
|
162
|
+
this.state = {
|
|
163
|
+
policy,
|
|
164
|
+
version,
|
|
165
|
+
digest: digestOf(policy),
|
|
166
|
+
updated_at: typeof raw.updated_at === "string" && raw.updated_at.length > 0
|
|
167
|
+
? raw.updated_at
|
|
168
|
+
: this.now(),
|
|
169
|
+
};
|
|
170
|
+
this.lastSig = sig;
|
|
171
|
+
this.warnedSig = undefined;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (sig !== this.warnedSig) {
|
|
175
|
+
this.log(`[policy-runtime] ${this.file} 读取/校验失败,保留上一个生效策略: ${error instanceof Error ? error.message : String(error)}`);
|
|
176
|
+
this.warnedSig = sig;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=policy-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-runtime.js","sourceRoot":"","sources":["../../src/merchant-core/policy-runtime.ts"],"names":[],"mappings":"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"}
|