@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,48 @@
|
|
|
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
|
+
export interface BackupManifest {
|
|
17
|
+
created_at: string;
|
|
18
|
+
source_dir: string;
|
|
19
|
+
files: Array<{
|
|
20
|
+
path: string;
|
|
21
|
+
bytes: number;
|
|
22
|
+
sha256: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** 审查 P2:跳过未备份的路径(如 symlink),让恢复侧的数据缺口可见。 */
|
|
25
|
+
skipped?: string[];
|
|
26
|
+
}
|
|
27
|
+
export interface BackupResult {
|
|
28
|
+
snapshot_dir: string;
|
|
29
|
+
manifest: BackupManifest;
|
|
30
|
+
rotated_out: string[];
|
|
31
|
+
}
|
|
32
|
+
/** 快照备份 + 轮换(keepLatest 份)。幂等:同时间戳目录复用覆盖。
|
|
33
|
+
* 启动时清理孤儿快照目录(审查 P2:优雅关闭/崩溃打断备份留下的无 manifest
|
|
34
|
+
* 目录既不参与轮换也永不清理,反复在备份窗口重启可累积占满磁盘)。 */
|
|
35
|
+
export declare function runBackup(options: {
|
|
36
|
+
dataDir: string;
|
|
37
|
+
backupsDir: string;
|
|
38
|
+
now?: () => string;
|
|
39
|
+
keepLatest?: number;
|
|
40
|
+
}): BackupResult;
|
|
41
|
+
/** 恢复(恢复演练同路径):按 manifest 校验快照完整性后回写目标目录。 */
|
|
42
|
+
export declare function restoreBackup(options: {
|
|
43
|
+
snapshotDir: string;
|
|
44
|
+
targetDir: string;
|
|
45
|
+
}): {
|
|
46
|
+
restored: number;
|
|
47
|
+
verified: true;
|
|
48
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
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 阶段四 + BUG-06 修复:src/merchant-runtime/backup.ts)。
|
|
18
|
+
*
|
|
19
|
+
* 事务一致性:
|
|
20
|
+
* - SQLite 文件用 `VACUUM INTO`(读事务一致性快照;源库 WAL 模式下并发写
|
|
21
|
+
* 安全;busy 时短暂重试,仍失败则本轮备份 fail-closed 记错误);
|
|
22
|
+
* - 快照完成后实际打开备份库跑 `PRAGMA integrity_check`,不通过即删除该轮
|
|
23
|
+
* 快照并抛错(绝不留下不可恢复的快照);
|
|
24
|
+
* - Ledger 等文件类数据在 SQLite 快照之后复制——最终一致性边界:append
|
|
25
|
+
* 中的最后一行可能是部分写入,恢复端按 Ledger 链校验容错(文档化,
|
|
26
|
+
* 见 deploy/merchant-bundle/README.md)。
|
|
27
|
+
*
|
|
28
|
+
* RPO 口径(工程诚实):周期快照的 RPO ≤ 备份周期(缺省 5 分钟),
|
|
29
|
+
* 不宣称 RPO=0;RPO=0 需同步持久化/持续复制(留主备阶段)。
|
|
30
|
+
* 快照写 manifest(文件清单 + 大小 + sha256)供恢复校验;轮换保留最近 N 份。
|
|
31
|
+
*/
|
|
32
|
+
import { createHash } from "node:crypto";
|
|
33
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
34
|
+
import path from "node:path";
|
|
35
|
+
import { DatabaseSync } from "node:sqlite";
|
|
36
|
+
import { writeFileAtomic } from "../fs/atomic-write.js";
|
|
37
|
+
function walk(dir, base, out, skipped) {
|
|
38
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
39
|
+
const full = path.join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory())
|
|
41
|
+
walk(full, base, out, skipped);
|
|
42
|
+
else if (entry.isFile())
|
|
43
|
+
out.push(path.relative(base, full));
|
|
44
|
+
else if (entry.isSymbolicLink()) {
|
|
45
|
+
// 审查 P2:symlink 数据静默丢弃会让恢复后出现「校验全绿」的数据缺口
|
|
46
|
+
// ——至少记入 manifest 跳过清单,让缺口可见。
|
|
47
|
+
skipped.push(path.relative(base, full));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function sha256File(file) {
|
|
52
|
+
return createHash("sha256").update(readFileSync(file)).digest("hex");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* SQLite 事务一致性快照(BUG-06):VACUUM INTO(读事务一致;源库 WAL 下并发
|
|
56
|
+
* 写安全)。busy/失败短暂重试,仍失败抛错(本轮备份 fail-closed)。
|
|
57
|
+
* 快照后打开备份库跑 integrity_check——不通过即抛错(调用方删除该轮快照)。
|
|
58
|
+
*/
|
|
59
|
+
function snapshotSqlite(src, dst) {
|
|
60
|
+
mkdirSync(path.dirname(dst), { recursive: true, mode: 0o700 });
|
|
61
|
+
rmSync(dst, { force: true });
|
|
62
|
+
if (!existsSync(src)) {
|
|
63
|
+
// 审查 P2:DatabaseSync 缺省会静默建空库——源在 walk 与快照之间消失时
|
|
64
|
+
// 会产出「校验全绿」的空库快照(静默数据丢失)。fail-closed。
|
|
65
|
+
throw new Error(`SQLite 源库不存在,拒绝快照(fail-closed):${src}`);
|
|
66
|
+
}
|
|
67
|
+
let lastErr;
|
|
68
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
69
|
+
let db;
|
|
70
|
+
try {
|
|
71
|
+
db = new DatabaseSync(src, { readOnly: true });
|
|
72
|
+
db.exec("PRAGMA busy_timeout = 2000");
|
|
73
|
+
// VACUUM INTO 目标路径防注入:单引号转义
|
|
74
|
+
db.exec(`VACUUM INTO '${dst.replaceAll("'", "''")}'`);
|
|
75
|
+
db.close();
|
|
76
|
+
db = undefined;
|
|
77
|
+
const check = new DatabaseSync(dst);
|
|
78
|
+
try {
|
|
79
|
+
const rows = check.prepare("PRAGMA integrity_check").all();
|
|
80
|
+
if (rows.length !== 1 || rows[0]?.integrity_check !== "ok") {
|
|
81
|
+
throw new Error(`备份库 integrity_check 未通过:${dst}(${JSON.stringify(rows)})`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
check.close();
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
lastErr = err;
|
|
91
|
+
if (db !== undefined) {
|
|
92
|
+
try {
|
|
93
|
+
db.close();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// 忽略
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
rmSync(dst, { force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
throw new Error(`SQLite 快照失败(${src}):${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
103
|
+
}
|
|
104
|
+
/** 快照备份 + 轮换(keepLatest 份)。幂等:同时间戳目录复用覆盖。
|
|
105
|
+
* 启动时清理孤儿快照目录(审查 P2:优雅关闭/崩溃打断备份留下的无 manifest
|
|
106
|
+
* 目录既不参与轮换也永不清理,反复在备份窗口重启可累积占满磁盘)。 */
|
|
107
|
+
export function runBackup(options) {
|
|
108
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
109
|
+
const keep = options.keepLatest ?? 10;
|
|
110
|
+
// 孤儿快照清理:无 manifest.json 且修改时间超过 1 小时的目录(1 小时缓冲
|
|
111
|
+
// 避免误删并发进行中的快照——jobs 已串行,此处是双保险)。
|
|
112
|
+
try {
|
|
113
|
+
for (const entry of readdirSync(options.backupsDir, { withFileTypes: true })) {
|
|
114
|
+
if (!entry.isDirectory())
|
|
115
|
+
continue;
|
|
116
|
+
const dir = path.join(options.backupsDir, entry.name);
|
|
117
|
+
if (existsSync(path.join(dir, "manifest.json")))
|
|
118
|
+
continue;
|
|
119
|
+
try {
|
|
120
|
+
if (Date.now() - statSync(dir).mtimeMs > 60 * 60 * 1000) {
|
|
121
|
+
rmSync(dir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// 单个目录清理失败不影响本轮备份
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// backups 目录不存在等:交给下方 mkdirSync
|
|
131
|
+
}
|
|
132
|
+
const stamp = now.replace(/[:.]/g, "-");
|
|
133
|
+
const snapshotDir = path.join(options.backupsDir, stamp);
|
|
134
|
+
mkdirSync(snapshotDir, { recursive: true, mode: 0o700 });
|
|
135
|
+
const files = [];
|
|
136
|
+
const skipped = [];
|
|
137
|
+
walk(options.dataDir, options.dataDir, files, skipped);
|
|
138
|
+
// 不备份 backups 自身(若 backups 在 dataDir 内)、锁文件与 SQLite WAL/SHM 临时文件
|
|
139
|
+
const included = files.filter((f) => !f.startsWith("backups") &&
|
|
140
|
+
!f.endsWith(".lock") &&
|
|
141
|
+
!f.endsWith(".tmp") &&
|
|
142
|
+
!f.endsWith(".sqlite-wal") &&
|
|
143
|
+
!f.endsWith(".sqlite-shm"));
|
|
144
|
+
try {
|
|
145
|
+
// SQLite 先行(VACUUM INTO 一致性快照),文件类随后(最终一致性边界见文件头)
|
|
146
|
+
const sorted = included.sort((a, b) => {
|
|
147
|
+
const aSql = a.endsWith(".sqlite") ? 0 : 1;
|
|
148
|
+
const bSql = b.endsWith(".sqlite") ? 0 : 1;
|
|
149
|
+
return aSql - bSql || (a < b ? -1 : 1);
|
|
150
|
+
});
|
|
151
|
+
const manifest = {
|
|
152
|
+
created_at: now,
|
|
153
|
+
source_dir: options.dataDir,
|
|
154
|
+
files: [],
|
|
155
|
+
...(skipped.length > 0 ? { skipped } : {}),
|
|
156
|
+
};
|
|
157
|
+
for (const rel of sorted) {
|
|
158
|
+
const src = path.join(options.dataDir, rel);
|
|
159
|
+
const dst = path.join(snapshotDir, rel);
|
|
160
|
+
if (rel.endsWith(".sqlite")) {
|
|
161
|
+
snapshotSqlite(src, dst);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
mkdirSync(path.dirname(dst), { recursive: true, mode: 0o700 });
|
|
165
|
+
cpSync(src, dst);
|
|
166
|
+
}
|
|
167
|
+
// manifest 记录的是**快照内容**的摘要(dst),恢复校验的是快照自身完整性
|
|
168
|
+
manifest.files.push({ path: rel, bytes: statSync(dst).size, sha256: sha256File(dst) });
|
|
169
|
+
}
|
|
170
|
+
writeFileSync(path.join(snapshotDir, "manifest.json"), JSON.stringify(manifest, null, 2), {
|
|
171
|
+
mode: 0o600,
|
|
172
|
+
});
|
|
173
|
+
// 审查 P2:成功备份的最新时间戳(health 的 backup_stale 告警读取)。
|
|
174
|
+
writeFileAtomic(path.join(options.backupsDir, "latest-backup.json"), `${JSON.stringify({ created_at: now, snapshot_dir: snapshotDir }, null, 2)}\n`, { mode: 0o600 });
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
// 失败轮次不留半成品快照
|
|
178
|
+
rmSync(snapshotDir, { recursive: true, force: true });
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
// 轮换:保留最近 keep 份
|
|
182
|
+
const snapshots = readdirSync(options.backupsDir, { withFileTypes: true })
|
|
183
|
+
.filter((e) => e.isDirectory() && existsSync(path.join(options.backupsDir, e.name, "manifest.json")))
|
|
184
|
+
.map((e) => e.name)
|
|
185
|
+
.sort()
|
|
186
|
+
.reverse();
|
|
187
|
+
const rotatedOut = [];
|
|
188
|
+
for (const old of snapshots.slice(keep)) {
|
|
189
|
+
rmSync(path.join(options.backupsDir, old), { recursive: true, force: true });
|
|
190
|
+
rotatedOut.push(old);
|
|
191
|
+
}
|
|
192
|
+
const manifest = JSON.parse(readFileSync(path.join(snapshotDir, "manifest.json"), "utf8"));
|
|
193
|
+
return { snapshot_dir: snapshotDir, manifest, rotated_out: rotatedOut };
|
|
194
|
+
}
|
|
195
|
+
/** 恢复(恢复演练同路径):按 manifest 校验快照完整性后回写目标目录。 */
|
|
196
|
+
export function restoreBackup(options) {
|
|
197
|
+
const manifest = JSON.parse(readFileSync(path.join(options.snapshotDir, "manifest.json"), "utf8"));
|
|
198
|
+
// 路径越界守卫(审查 P2):manifest 可能来自异地/他人,files[].path 含
|
|
199
|
+
// `..` 段或绝对路径即是任意文件写入原语——逐条校验后再落盘。
|
|
200
|
+
const targetRoot = path.resolve(options.targetDir);
|
|
201
|
+
const safeRel = (rel) => {
|
|
202
|
+
const normalized = path.normalize(rel);
|
|
203
|
+
if (path.isAbsolute(normalized) || normalized.split(path.sep).includes("..")) {
|
|
204
|
+
throw new Error(`备份清单路径越界(拒绝恢复):${rel}`);
|
|
205
|
+
}
|
|
206
|
+
return normalized;
|
|
207
|
+
};
|
|
208
|
+
// 先校验完整性(manifest 内每个文件存在且 sha256 一致)——不完整拒绝恢复。
|
|
209
|
+
for (const f of manifest.files) {
|
|
210
|
+
const rel = safeRel(f.path);
|
|
211
|
+
const src = path.join(options.snapshotDir, rel);
|
|
212
|
+
if (!existsSync(src))
|
|
213
|
+
throw new Error(`备份快照缺文件 ${f.path}(不完整,拒绝恢复)`);
|
|
214
|
+
if (sha256File(src) !== f.sha256) {
|
|
215
|
+
throw new Error(`备份快照文件 ${f.path} 摘要不一致(损坏,拒绝恢复)`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
mkdirSync(options.targetDir, { recursive: true, mode: 0o700 });
|
|
219
|
+
for (const f of manifest.files) {
|
|
220
|
+
const dst = path.join(targetRoot, safeRel(f.path));
|
|
221
|
+
mkdirSync(path.dirname(dst), { recursive: true, mode: 0o700 });
|
|
222
|
+
cpSync(path.join(options.snapshotDir, safeRel(f.path)), dst);
|
|
223
|
+
}
|
|
224
|
+
return { restored: manifest.files.length, verified: true };
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=backup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/merchant-runtime/backup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
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 { ManagedServiceState } from "./manager.js";
|
|
17
|
+
export interface MerchantAlert {
|
|
18
|
+
code: "process_down" | "product_source_unavailable" | "registration_invalid" | "backlog" | "disk_low" | "cert_expiring" | "backup_stale";
|
|
19
|
+
severity: "critical" | "warning";
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MerchantHealthReport {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
checked_at: string;
|
|
25
|
+
checks: {
|
|
26
|
+
processes: {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
services: ManagedServiceState[];
|
|
29
|
+
};
|
|
30
|
+
product_source: {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
version?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
};
|
|
35
|
+
data_dir: {
|
|
36
|
+
ok: boolean;
|
|
37
|
+
path: string;
|
|
38
|
+
writable: boolean;
|
|
39
|
+
};
|
|
40
|
+
disk: {
|
|
41
|
+
ok: boolean;
|
|
42
|
+
free_bytes?: number;
|
|
43
|
+
};
|
|
44
|
+
registration: {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
error?: string;
|
|
47
|
+
evaluated: boolean;
|
|
48
|
+
};
|
|
49
|
+
backlog: {
|
|
50
|
+
ok: true;
|
|
51
|
+
pending: number;
|
|
52
|
+
threshold: number;
|
|
53
|
+
};
|
|
54
|
+
certificate: {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
days_left?: number;
|
|
57
|
+
evaluated: boolean;
|
|
58
|
+
};
|
|
59
|
+
backup: {
|
|
60
|
+
ok: true;
|
|
61
|
+
evaluated: boolean;
|
|
62
|
+
last_success_at?: string;
|
|
63
|
+
error?: string;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/** 结构化告警事件(V2 阶段四 7×24;超阈值/失效即产生,可供通知通道消费)。 */
|
|
67
|
+
alerts: MerchantAlert[];
|
|
68
|
+
}
|
|
69
|
+
/** 磁盘余量下限(字节;缺省 100MB)。 */
|
|
70
|
+
export declare const MIN_FREE_BYTES: number;
|
|
71
|
+
/** 待处理命令积压告警阈值(缺省 50)。 */
|
|
72
|
+
export declare const BACKLOG_ALERT_THRESHOLD = 50;
|
|
73
|
+
/** 证书临期告警阈值(天;缺省 14)。 */
|
|
74
|
+
export declare const CERT_EXPIRING_DAYS = 14;
|
|
75
|
+
/** 能力探测记录最大有效期(BUG-05;缺省 3 分钟 = 3 个健康轮询周期)。 */
|
|
76
|
+
export declare const PROBE_MAX_AGE_MS = 180000;
|
|
77
|
+
export interface MerchantHealthDeps {
|
|
78
|
+
/** merchantDataDir(V2 §5.1)。 */
|
|
79
|
+
dataDir: string;
|
|
80
|
+
/** 受管服务状态(来自 MerchantRuntimeManager.status())。 */
|
|
81
|
+
services: ManagedServiceState[];
|
|
82
|
+
now?: () => string;
|
|
83
|
+
minFreeBytes?: number;
|
|
84
|
+
/** 待处理命令数(积压告警;缺省 0)。 */
|
|
85
|
+
pendingCommands?: number;
|
|
86
|
+
/** 注册检查报告(F25;缺省不评)。 */
|
|
87
|
+
registration?: {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
error?: string;
|
|
90
|
+
};
|
|
91
|
+
/** TLS 证书剩余天数(可选;配置了才评)。 */
|
|
92
|
+
certDaysLeft?: number;
|
|
93
|
+
/** 能力探测记录最大有效期(BUG-05;过期记录判 unhealthy)。 */
|
|
94
|
+
probeMaxAgeMs?: number;
|
|
95
|
+
/** 备份新鲜度(审查 P2:备份停摆进告警通道,不再只活在内存 jobs 里)。
|
|
96
|
+
* 读 <backupsDir>/latest-backup.json(runBackup 成功后原子落盘)。 */
|
|
97
|
+
backupMaxAgeMs?: number;
|
|
98
|
+
}
|
|
99
|
+
export declare function collectMerchantHealth(deps: MerchantHealthDeps): MerchantHealthReport;
|
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
* Merchant 实例分项健康检查(V2 阶段一:src/merchant-runtime/health.ts)。
|
|
18
|
+
*
|
|
19
|
+
* 分项:受管进程存活、商品源(shopping-cli 能力探测结果,读
|
|
20
|
+
* capability-probe.json 落盘记录——探测本身在 merchant-client.ts)、状态
|
|
21
|
+
* 目录可写、磁盘余量。输出结构化健康报告;任一分项失败 → ok:false,
|
|
22
|
+
* 不吞错不编造(fail-closed 口径与能力探测一致)。
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readFileSync, statfsSync, writeFileSync, rmSync } from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
/** 磁盘余量下限(字节;缺省 100MB)。 */
|
|
27
|
+
export const MIN_FREE_BYTES = 100 * 1024 * 1024;
|
|
28
|
+
/** 待处理命令积压告警阈值(缺省 50)。 */
|
|
29
|
+
export const BACKLOG_ALERT_THRESHOLD = 50;
|
|
30
|
+
/** 证书临期告警阈值(天;缺省 14)。 */
|
|
31
|
+
export const CERT_EXPIRING_DAYS = 14;
|
|
32
|
+
/** 能力探测记录最大有效期(BUG-05;缺省 3 分钟 = 3 个健康轮询周期)。 */
|
|
33
|
+
export const PROBE_MAX_AGE_MS = 180_000;
|
|
34
|
+
export function collectMerchantHealth(deps) {
|
|
35
|
+
const checkedAt = (deps.now ?? (() => new Date().toISOString()))();
|
|
36
|
+
const minFree = deps.minFreeBytes ?? MIN_FREE_BYTES;
|
|
37
|
+
const processes = {
|
|
38
|
+
ok: deps.services.every((s) => s.running),
|
|
39
|
+
services: deps.services,
|
|
40
|
+
};
|
|
41
|
+
// 商品源:读能力探测落盘记录(探测动作在 mcp serve 启动/健康轮询时执行)。
|
|
42
|
+
// 商品源:读能力探测落盘记录(BUG-05:加新鲜度判定——超时/连接失败/HTTP
|
|
43
|
+
// 异常/记录过期均判 unhealthy;实时探测由周期任务执行,见 cli runtime start)。
|
|
44
|
+
const probePath = path.join(deps.dataDir, "capability-probe.json");
|
|
45
|
+
const probeMaxAgeMs = deps.probeMaxAgeMs ?? PROBE_MAX_AGE_MS;
|
|
46
|
+
let productSource;
|
|
47
|
+
if (!existsSync(probePath)) {
|
|
48
|
+
productSource = { ok: false, error: "无能力探测记录(capability-probe.json 不存在)" };
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
try {
|
|
52
|
+
const probe = JSON.parse(readFileSync(probePath, "utf8"));
|
|
53
|
+
const probedAt = Date.parse(probe.probed_at);
|
|
54
|
+
const stale = !Number.isFinite(probedAt) || Date.parse(checkedAt) - probedAt > probeMaxAgeMs;
|
|
55
|
+
productSource = {
|
|
56
|
+
ok: probe.ok === true && !stale,
|
|
57
|
+
...(probe.version !== undefined ? { version: probe.version } : {}),
|
|
58
|
+
...(probe.error !== undefined ? { error: probe.error } : {}),
|
|
59
|
+
...(stale ? { error: `能力探测记录过期(probed_at ${probe.probed_at},超过 ${probeMaxAgeMs}ms 未刷新)` } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
productSource = {
|
|
64
|
+
ok: false,
|
|
65
|
+
error: `能力探测记录损坏:${err instanceof Error ? err.message : String(err)}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// 状态目录可写(写探针文件即删)
|
|
70
|
+
let writable = false;
|
|
71
|
+
const probe = path.join(deps.dataDir, "runtime", ".health-probe");
|
|
72
|
+
try {
|
|
73
|
+
writeFileSync(probe, checkedAt, { mode: 0o600 });
|
|
74
|
+
rmSync(probe, { force: true });
|
|
75
|
+
writable = true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
writable = false;
|
|
79
|
+
}
|
|
80
|
+
let freeBytes;
|
|
81
|
+
try {
|
|
82
|
+
const fs = statfsSync(deps.dataDir);
|
|
83
|
+
freeBytes = fs.bavail * fs.bsize;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
freeBytes = undefined;
|
|
87
|
+
}
|
|
88
|
+
const pending = deps.pendingCommands ?? 0;
|
|
89
|
+
// 备份新鲜度(审查 P2):runBackup 每次成功后落 latest-backup.json;
|
|
90
|
+
// 超过 maxAge(缺省 3×5min 备份周期)或从未成功 → 告警(warning,不翻转
|
|
91
|
+
// 整体 ok——进程/数据面健康不受备份停摆影响,但必须可见)。
|
|
92
|
+
const backupMaxAgeMs = deps.backupMaxAgeMs ?? 15 * 60_000;
|
|
93
|
+
const latestBackupPath = path.join(deps.dataDir, "backups", "latest-backup.json");
|
|
94
|
+
// backup 与 backlog 同为 warning 面:不翻转整体 ok(进程/数据面健康不受
|
|
95
|
+
// 备份停摆影响),停摆经 backup_stale 告警进入通知通道。
|
|
96
|
+
let backupStale;
|
|
97
|
+
let backupLastSuccessAt;
|
|
98
|
+
if (!existsSync(latestBackupPath)) {
|
|
99
|
+
backupStale = "尚无成功备份记录(latest-backup.json 不存在)";
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
try {
|
|
103
|
+
const record = JSON.parse(readFileSync(latestBackupPath, "utf8"));
|
|
104
|
+
const at = typeof record.created_at === "string" ? record.created_at : undefined;
|
|
105
|
+
const age = at !== undefined ? Date.parse(checkedAt) - Date.parse(at) : Number.NaN;
|
|
106
|
+
if (!Number.isFinite(age)) {
|
|
107
|
+
backupStale = "latest-backup.json 缺少有效 created_at";
|
|
108
|
+
}
|
|
109
|
+
else if (age > backupMaxAgeMs) {
|
|
110
|
+
backupStale = `最近成功备份距今 ${Math.round(age / 60000)} 分钟(阈值 ${Math.round(backupMaxAgeMs / 60000)})`;
|
|
111
|
+
}
|
|
112
|
+
else if (at !== undefined) {
|
|
113
|
+
backupLastSuccessAt = at;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
backupStale = `latest-backup.json 损坏:${err instanceof Error ? err.message : String(err)}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const checks = {
|
|
121
|
+
processes,
|
|
122
|
+
product_source: productSource,
|
|
123
|
+
data_dir: { ok: writable, path: deps.dataDir, writable },
|
|
124
|
+
disk: {
|
|
125
|
+
ok: freeBytes !== undefined && freeBytes >= minFree,
|
|
126
|
+
...(freeBytes !== undefined ? { free_bytes: freeBytes } : {}),
|
|
127
|
+
},
|
|
128
|
+
// registration/certificate are optional inputs, but when supplied their
|
|
129
|
+
// critical failures must affect health.ok rather than only producing an
|
|
130
|
+
// alert that callers might ignore.
|
|
131
|
+
registration: {
|
|
132
|
+
ok: deps.registration?.ok ?? true,
|
|
133
|
+
evaluated: deps.registration !== undefined,
|
|
134
|
+
...(deps.registration?.error !== undefined ? { error: deps.registration.error } : {}),
|
|
135
|
+
},
|
|
136
|
+
// Backlog is a warning condition and should not trigger process restart.
|
|
137
|
+
backlog: { ok: true, pending, threshold: BACKLOG_ALERT_THRESHOLD },
|
|
138
|
+
backup: {
|
|
139
|
+
ok: true,
|
|
140
|
+
evaluated: true,
|
|
141
|
+
...(backupLastSuccessAt !== undefined ? { last_success_at: backupLastSuccessAt } : {}),
|
|
142
|
+
...(backupStale !== undefined ? { error: backupStale } : {}),
|
|
143
|
+
},
|
|
144
|
+
certificate: {
|
|
145
|
+
ok: deps.certDaysLeft === undefined || deps.certDaysLeft > 0,
|
|
146
|
+
evaluated: deps.certDaysLeft !== undefined,
|
|
147
|
+
...(deps.certDaysLeft !== undefined ? { days_left: deps.certDaysLeft } : {}),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
// 结构化告警(7×24;每个告警有明确 code/severity,供通知通道消费)。
|
|
151
|
+
const alerts = [];
|
|
152
|
+
for (const s of deps.services) {
|
|
153
|
+
if (!s.running) {
|
|
154
|
+
alerts.push({
|
|
155
|
+
code: "process_down",
|
|
156
|
+
severity: "critical",
|
|
157
|
+
message: `受管进程 ${s.name} 未运行`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!productSource.ok) {
|
|
162
|
+
alerts.push({
|
|
163
|
+
code: "product_source_unavailable",
|
|
164
|
+
severity: "critical",
|
|
165
|
+
message: `商品源不可用:${productSource.error ?? "未知"}`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (deps.registration !== undefined && !deps.registration.ok) {
|
|
169
|
+
alerts.push({
|
|
170
|
+
code: "registration_invalid",
|
|
171
|
+
severity: "critical",
|
|
172
|
+
message: `catalog 注册失效:${deps.registration.error ?? "未知"}`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (pending > BACKLOG_ALERT_THRESHOLD) {
|
|
176
|
+
alerts.push({
|
|
177
|
+
code: "backlog",
|
|
178
|
+
severity: "warning",
|
|
179
|
+
message: `待处理命令积压 ${pending}(阈值 ${BACKLOG_ALERT_THRESHOLD})`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (!checks.disk.ok) {
|
|
183
|
+
alerts.push({ code: "disk_low", severity: "warning", message: "磁盘余量低于下限" });
|
|
184
|
+
}
|
|
185
|
+
if (!checks.backup.ok) {
|
|
186
|
+
alerts.push({
|
|
187
|
+
code: "backup_stale",
|
|
188
|
+
severity: "warning",
|
|
189
|
+
message: `备份停摆:${checks.backup.error ?? "未知"}`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (deps.certDaysLeft !== undefined && deps.certDaysLeft <= CERT_EXPIRING_DAYS) {
|
|
193
|
+
alerts.push({
|
|
194
|
+
code: "cert_expiring",
|
|
195
|
+
severity: deps.certDaysLeft <= 3 ? "critical" : "warning",
|
|
196
|
+
message: `TLS 证书 ${deps.certDaysLeft} 天后到期`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
ok: Object.values(checks).every((c) => c.ok),
|
|
201
|
+
checked_at: checkedAt,
|
|
202
|
+
checks,
|
|
203
|
+
alerts,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/merchant-runtime/health.ts"],"names":[],"mappings":"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"}
|