dsh-punky-swarm 0.3.6

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.
Files changed (101) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +661 -0
  3. package/README.en.md +281 -0
  4. package/README.md +281 -0
  5. package/cordis.patch.yml +59 -0
  6. package/lib/acps/certs.js +310 -0
  7. package/lib/acps/discovery-client.js +283 -0
  8. package/lib/acps/registry-client.js +527 -0
  9. package/lib/acps/server.js +302 -0
  10. package/lib/aip/agent-descriptor.js +170 -0
  11. package/lib/aip/identity.js +469 -0
  12. package/lib/aip/tool-descriptor.js +93 -0
  13. package/lib/api.js +266 -0
  14. package/lib/artifact-types.js +48 -0
  15. package/lib/assembly/audit-blind-review.js +170 -0
  16. package/lib/assembly/schema.js +253 -0
  17. package/lib/assembly.js +41 -0
  18. package/lib/assets.js +103 -0
  19. package/lib/bridge/trajectory.js +202 -0
  20. package/lib/client.js +704 -0
  21. package/lib/comms/acps-bridge.js +219 -0
  22. package/lib/comms/aip-format.js +141 -0
  23. package/lib/comms/budget.js +124 -0
  24. package/lib/comms/mailbox.js +221 -0
  25. package/lib/comms/topic-runtime.js +76 -0
  26. package/lib/comms/topic.js +115 -0
  27. package/lib/discovery/filter.js +199 -0
  28. package/lib/discovery/schema.js +157 -0
  29. package/lib/discovery/service.js +251 -0
  30. package/lib/hot/config-watch.js +252 -0
  31. package/lib/index.js +433 -0
  32. package/lib/lock.js +69 -0
  33. package/lib/panel/batch-detail.js +163 -0
  34. package/lib/panel/batch-list.js +84 -0
  35. package/lib/panel/locales.js +93 -0
  36. package/lib/panel/main.js +270 -0
  37. package/lib/panel/stream.js +348 -0
  38. package/lib/panel/theme.js +103 -0
  39. package/lib/panel/widgets.js +63 -0
  40. package/lib/schema.d.ts +192 -0
  41. package/lib/schema.js +259 -0
  42. package/lib/schema.ts +398 -0
  43. package/lib/state/archive.js +174 -0
  44. package/lib/state/command-exec.js +148 -0
  45. package/lib/state/constants.js +37 -0
  46. package/lib/state/corrupt-registry.js +103 -0
  47. package/lib/state/event-types.js +65 -0
  48. package/lib/state/gates.d.ts +226 -0
  49. package/lib/state/gates.js +428 -0
  50. package/lib/state/gates.ts +348 -0
  51. package/lib/state/machine-rules.d.ts +14 -0
  52. package/lib/state/machine-rules.js +76 -0
  53. package/lib/state/machine-rules.ts +88 -0
  54. package/lib/state/machine.js +68 -0
  55. package/lib/state/resume.js +201 -0
  56. package/lib/state/schema-v3.d.ts +10 -0
  57. package/lib/state/schema-v3.js +57 -0
  58. package/lib/state/schema-v3.ts +80 -0
  59. package/lib/state/store.js +607 -0
  60. package/lib/state/task-utils.js +40 -0
  61. package/lib/tools/core.js +323 -0
  62. package/lib/tools/git-utils.js +44 -0
  63. package/lib/tools/lane-tools.js +455 -0
  64. package/lib/tools/log-tools.js +173 -0
  65. package/lib/tools/mailbox-tools.js +121 -0
  66. package/lib/tools/merge-agent.js +127 -0
  67. package/lib/tools/register.js +85 -0
  68. package/lib/tools/shared.js +32 -0
  69. package/lib/types/contracts.d.ts +277 -0
  70. package/lib/types/contracts.js +17 -0
  71. package/lib/types/contracts.ts +279 -0
  72. package/lib/verify/evidence.js +249 -0
  73. package/lib/verify/gate.js +161 -0
  74. package/lib/verify/mount.js +48 -0
  75. package/lib/verify/selector.js +101 -0
  76. package/lib/watch/lane-heartbeat.js +333 -0
  77. package/lib/wave-plan.d.ts +48 -0
  78. package/lib/wave-plan.js +473 -0
  79. package/lib/wave-plan.ts +465 -0
  80. package/package.json +71 -0
  81. package/presets/jiufeng/NOTICE +5 -0
  82. package/presets/jiufeng/agent.cordis.yml +275 -0
  83. package/presets/jiufeng/preset.yml +3 -0
  84. package/skills/jiufeng-team/SKILL.md +214 -0
  85. package/skills/jiufeng-team/references/constitution.md +62 -0
  86. package/skills/jiufeng-team/references/roles/coder.md +17 -0
  87. package/skills/jiufeng-team/references/roles/coordinator.md +13 -0
  88. package/skills/jiufeng-team/references/roles/designer.md +14 -0
  89. package/skills/jiufeng-team/references/roles/doc-manager.md +13 -0
  90. package/skills/jiufeng-team/references/roles/manager.md +22 -0
  91. package/skills/jiufeng-team/references/roles/reviewer.md +19 -0
  92. package/skills/jiufeng-team/references/roles/supervisor.md +13 -0
  93. package/skills/jiufeng-team/references/roles/tester.md +13 -0
  94. package/skills/jiufeng-team/references/templates/call-chain-matrix-template.md +121 -0
  95. package/skills/jiufeng-team/references/templates/endpoint-behavior-template.md +60 -0
  96. package/skills/jiufeng-team/references/templates/gap-list.json +33 -0
  97. package/skills/jiufeng-team/references/templates/leader-decision-pack.md +92 -0
  98. package/skills/jiufeng-team/references/templates/plan-template.md +61 -0
  99. package/skills/jiufeng-team/references/templates/success-pattern-seeds.md +126 -0
  100. package/skills/jiufeng-team/references/workflow.md +98 -0
  101. package/skills/jiufeng-team/scripts/check-v3-density.mjs +98 -0
@@ -0,0 +1,469 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // 文件 identity:国标 P2/P3 身份体系(AIC 身份码 + CAI 身份证书)
19
+ // -----------------------------------------------------------------------------
20
+ // 校准基准(plan/spec.md §3.4/§3.5,[参考] = ACPs-community v2.1.0 原文):
21
+ // P2 身份码 AIC:10 级编码(前缀 1.2.156.3088 + 版本/ARSP/供应商/本体/实体 + 校验码),
22
+ // CRC-16/CCITT-FALSE(poly=0x1021, init=0xFFFF, refin/refout=false, xorout=0x0000)+
23
+ // ARSP 盐值(>=2 字节)+ Base36 固定 4 位校验码(ACPs-spec-AIC-v02.01 §4)。
24
+ // P3 身份管理:CAI(Certificate of Agent Identity)证书——CN=AIC、SAN=URI:acps://{AIC}、
25
+ // extendedKeyUsage 分用途(clientAuth/serverAuth)、有效期 requestedValidity(默认 49 天、
26
+ // 建议 365~1825、上限 3650);EAB(External Account Binding)凭证绑定 ACME 账户与 AIC;
27
+ // 认证走 TLS1.3 mTLS(ACPs-spec-ATR-v02.01 §3.2/§3.3,ACPs-spec-AIA-v02.01)。
28
+ // SM2 校准:参考实现 v2.1.0 证书体系为 X.509/ACME(ECDSA/RSA),无 SM2 证据——
29
+ // 本模块 sign 实现为可插拔接口:默认 ECDSA/RSA(node:crypto 原生,可跑可验签),
30
+ // 算法可配置;SM2 留接口位(algorithm='sm2' 显式拒绝并提示),标注『SM2 待正式文本校准』。
31
+ // 装配开关:identity 默认关(config.aip.identity.enabled === true 时调用方才激活本模块 API,
32
+ // 由 lib/assembly/schema.js CAPABILITY_REGISTRY 声明);不注册新治理工具(20 工具契约不变),
33
+ // 身份能力经本模块 API 暴露。零运行时副作用(纯函数 + 可选 store 注入)。
34
+ // -----------------------------------------------------------------------------
35
+ import crypto from 'node:crypto';
36
+
37
+ // ---- AIC 常量(ACPs-spec-AIC-v02.01)----
38
+ export const AIC_PREFIX = '1.2.156.3088'; // 国家 OID 注册中心分配的前缀
39
+ export const BASE36_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
40
+ const BASE36_INDEX = Object.fromEntries([...BASE36_ALPHABET].map((ch, i) => [ch, i]));
41
+
42
+ // 默认分段(对齐参考实现 registry-server/app/utils/aic.py 常量)
43
+ export const DEFAULT_VERSION = '1'; // 第 5 级:身份码版本号(1~Z)
44
+ export const DEFAULT_ARSP = '0001'; // 第 6 级:智能体注册服务商序号(1~ZZZZZZ)
45
+ export const DEFAULT_VENDOR = '00001'; // 第 7 级:智能体供应商序号(1~ZZZZZZ)
46
+ export const DEFAULT_ONTOLOGY_SERIAL_LEN = 6; // 第 8 级:本体序列号长度(1~9)
47
+ export const DEFAULT_INSTANCE_SERIAL_LEN = 6; // 第 9 级:实体序列号长度(1~9)
48
+
49
+ // 校验码(第 10 级)固定 4 位 Base36
50
+ export const CHECKSUM_LEN = 4;
51
+
52
+ // 签名算法注册表(可插拔接口;sm2 仅占位,待正式文本校准)
53
+ export const SIGN_ALGORITHMS = {
54
+ 'ecdsa-p256': { curve: 'prime256v1', hash: 'sha256', nodeName: 'ecdsa' },
55
+ 'rsa-2048': { modulusLength: 2048, hash: 'sha256', nodeName: 'rsa' },
56
+ sm2: null, // SM2 待正式文本校准(参考实现 v2.1.0 无证据,不硬实现)
57
+ };
58
+ export const DEFAULT_SIGN_ALGORITHM = 'ecdsa-p256';
59
+
60
+ // CAI 证书默认参数(ACPs-spec-ATR-v02.01 §3.2(12))
61
+ export const CAI_DEFAULT_VALIDITY_DAYS = 49; // 默认 49 天
62
+ export const CAI_MAX_VALIDITY_DAYS = 3650; // 上限 3650 天
63
+ export const CAI_EAB_EXPIRE_HOURS = 24; // EAB 凭证有效期(对齐参考实现 settings 语义)
64
+
65
+ // ---- Base36 编解码 ----
66
+ export function base36Encode(num, length = 0) {
67
+ if (!Number.isInteger(num) || num < 0) throw new Error('base36Encode: num must be a non-negative integer');
68
+ if (num === 0) return '0'.padStart(length, '0');
69
+ let s = '';
70
+ let n = num;
71
+ while (n > 0) {
72
+ s = BASE36_ALPHABET[n % 36] + s;
73
+ n = Math.floor(n / 36);
74
+ }
75
+ return length > 0 ? s.padStart(length, '0') : s;
76
+ }
77
+
78
+ export function base36Decode(str) {
79
+ const s = String(str ?? '').trim().toUpperCase();
80
+ if (s === '') return 0;
81
+ let val = 0;
82
+ for (const ch of s) {
83
+ if (!(ch in BASE36_INDEX)) throw new Error('base36Decode: invalid base36 char ' + JSON.stringify(ch));
84
+ val = val * 36 + BASE36_INDEX[ch];
85
+ }
86
+ return val;
87
+ }
88
+
89
+ // ---- CRC-16/CCITT-FALSE(ACPs-spec-AIC-v02.01 §4.1;向量已对照参考实现)----
90
+ export function crc16CcittFalse(data, salt = Buffer.alloc(0)) {
91
+ const buf = Buffer.concat([Buffer.from(data, 'ascii'), Buffer.from(salt)]);
92
+ let crc = 0xFFFF;
93
+ for (const b of buf) {
94
+ crc ^= (b << 8) & 0xFFFF;
95
+ for (let i = 0; i < 8; i++) {
96
+ crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) & 0xFFFF : (crc << 1) & 0xFFFF;
97
+ }
98
+ }
99
+ return crc & 0xFFFF;
100
+ }
101
+
102
+ // 盐值解析:'' | '0x1234' | '1234' → Buffer;hex 非偶数字符左补 0(对齐参考实现)
103
+ export function parseSalt(salt) {
104
+ if (salt === undefined || salt === null || salt === '') return Buffer.alloc(0);
105
+ if (Buffer.isBuffer(salt)) return salt;
106
+ let hex = String(salt).trim();
107
+ if (hex.toLowerCase().startsWith('0x')) hex = hex.slice(2);
108
+ if (hex.length % 2 !== 0) hex = '0' + hex;
109
+ return Buffer.from(hex, 'hex');
110
+ }
111
+
112
+ // 计算第 10 级校验码(固定 4 位 Base36)
113
+ export function calculateAicChecksum(body1to9, salt = '') {
114
+ const normalized = String(body1to9).replace(/\s+/g, '').toUpperCase();
115
+ const crc = crc16CcittFalse(normalized, parseSalt(salt));
116
+ return base36Encode(crc, CHECKSUM_LEN);
117
+ }
118
+
119
+ // AIC 规范化(大写、去空白,保留 '.' 分隔符)
120
+ export function normalizeAic(aic) {
121
+ return String(aic ?? '').replace(/\s+/g, '').toUpperCase();
122
+ }
123
+
124
+ export function splitAic(aic) {
125
+ const n = normalizeAic(aic);
126
+ if (n === '') return [];
127
+ const parts = n.split('.');
128
+ return parts.some((p) => p === '') ? [] : parts;
129
+ }
130
+
131
+ // 格式 + CRC 校验(对齐参考实现 validate_aic;expectedPrefix 默认 1.2.156.3088)
132
+ export function validateAic(aic, { salt = '', expectedPrefix = AIC_PREFIX } = {}) {
133
+ const parts = splitAic(aic);
134
+ if (parts.length !== 10) return false;
135
+ const prefixParts = expectedPrefix ? expectedPrefix.split('.') : [];
136
+ if (prefixParts.length && JSON.stringify(parts.slice(0, prefixParts.length)) !== JSON.stringify(prefixParts)) return false;
137
+ // 1~4 级纯数字(OID 前缀)
138
+ if (!parts.slice(0, 4).every((p) => /^[0-9]+$/.test(p))) return false;
139
+ // 第 5 级:Base36 单字符
140
+ if (!/^[0-9A-Z]{1}$/.test(parts[4])) return false;
141
+ // 第 6/7 级:Base36 1~6 位
142
+ if (!/^[0-9A-Z]{1,6}$/.test(parts[5])) return false;
143
+ if (!/^[0-9A-Z]{1,6}$/.test(parts[6])) return false;
144
+ // 第 8/9 级:Base36 1~9 位
145
+ if (!/^[0-9A-Z]{1,9}$/.test(parts[7])) return false;
146
+ if (!/^[0-9A-Z]{1,9}$/.test(parts[8])) return false;
147
+ // 第 10 级:固定 4 位 Base36
148
+ if (!/^[0-9A-Z]{4}$/.test(parts[9])) return false;
149
+ const body = parts.slice(0, 9).join('.');
150
+ return calculateAicChecksum(body, salt) === parts[9];
151
+ }
152
+
153
+ export function isOntologyAic(aic) {
154
+ const parts = splitAic(aic);
155
+ if (parts.length !== 10) return false;
156
+ return /^0+$/.test(parts[8]) && parts[8].length > 0;
157
+ }
158
+
159
+ export function isEntityAic(aic) {
160
+ return !isOntologyAic(aic);
161
+ }
162
+
163
+ // 随机 Base36 序列号(非全 0;len 1~9;确定性测试可注入)
164
+ function randomBase36Serial(len, avoidZero = true) {
165
+ let serial;
166
+ do {
167
+ const bytes = crypto.randomBytes(Math.ceil((len * 5) / 4) + 1); // 冗余熵,避免截断偏置
168
+ const num = parseInt(bytes.toString('hex').slice(0, 12), 16);
169
+ serial = base36Encode(num % Math.pow(36, len), len);
170
+ } while (avoidZero && /^0+$/.test(serial));
171
+ return serial;
172
+ }
173
+
174
+ // 生成 AIC(10 级);opts:{ version, arsp, vendor, ontologySerial, instanceSerial, salt }
175
+ // 本体 AIC:instanceSerial 全 0;实体 AIC:instanceSerial 非全 0(entity=true)
176
+ export function generateAic(opts = {}) {
177
+ const version = opts.version ?? DEFAULT_VERSION;
178
+ const arsp = opts.arsp ?? DEFAULT_ARSP;
179
+ const vendor = opts.vendor ?? DEFAULT_VENDOR;
180
+ if (!/^[0-9A-Z]{1}$/.test(String(version))) throw new Error('generateAic: version must be 1~Z (1 char)');
181
+ if (!/^[0-9A-Z]{1,6}$/.test(String(arsp))) throw new Error('generateAic: arsp must be base36 1~6 chars');
182
+ if (!/^[0-9A-Z]{1,6}$/.test(String(vendor))) throw new Error('generateAic: vendor must be base36 1~6 chars');
183
+
184
+ const ontologySerial = opts.ontologySerial ?? randomBase36Serial(DEFAULT_ONTOLOGY_SERIAL_LEN);
185
+ const isEntity = opts.entity === true || opts.instanceSerial !== undefined;
186
+ const instanceSerial = opts.instanceSerial
187
+ ?? (isEntity ? randomBase36Serial(DEFAULT_INSTANCE_SERIAL_LEN) : '0'.repeat(DEFAULT_INSTANCE_SERIAL_LEN));
188
+ if (!/^[0-9A-Z]{1,9}$/.test(ontologySerial)) throw new Error('generateAic: ontologySerial must be base36 1~9 chars');
189
+ if (!/^[0-9A-Z]{1,9}$/.test(instanceSerial)) throw new Error('generateAic: instanceSerial must be base36 1~9 chars');
190
+
191
+ const body = `${AIC_PREFIX}.${version}.${arsp}.${vendor}.${ontologySerial}.${instanceSerial}`;
192
+ const checksum = calculateAicChecksum(body, opts.salt ?? '');
193
+ return `${body}.${checksum}`;
194
+ }
195
+
196
+ // ---- 存储(最小侵入:内存 store;调用方可注入持久化实现)----
197
+ export function createMemoryStore() {
198
+ const map = new Map();
199
+ return {
200
+ get: (k) => map.get(k),
201
+ set: (k, v) => { map.set(k, v); return v; },
202
+ has: (k) => map.has(k),
203
+ delete: (k) => map.delete(k),
204
+ list: () => [...map.keys()],
205
+ };
206
+ }
207
+
208
+ // ---- 密钥对生成(node:crypto 原生)----
209
+ function generateKeyPair(algorithm) {
210
+ if (algorithm === 'ecdsa-p256') {
211
+ return crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
212
+ }
213
+ if (algorithm === 'rsa-2048') {
214
+ return crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
215
+ }
216
+ throw new Error('generateKeyPair: unsupported algorithm ' + JSON.stringify(algorithm));
217
+ }
218
+
219
+ // ---- P2/P3 四函数 ----
220
+
221
+ /**
222
+ * AIC 身份码注册:分配 10 级身份码(前缀 1.2.156.3088 + 版本/ARSP/供应商/本体/实体 + CRC 校验码),
223
+ * 持久化并查重(同 name 已注册 → 幂等返回既有记录)。
224
+ * @param {object} identity 待注册身份 { kind: 'agent'|'tool', name: string, provider? }
225
+ * @param {object} [opts] { store?, salt?, arsp?, vendor?, entity?, version? }
226
+ * store:{ get, set }(缺省内存 store,跨进程不持久;生产传状态根 identity/ 目录实现)
227
+ * @returns {Promise<{ oid: string, aic: string, record: object, reused: boolean }>}
228
+ */
229
+ export async function registerIdentity(identity, opts = {}) {
230
+ const kind = identity?.kind ?? 'agent';
231
+ const name = identity?.name;
232
+ if (typeof name !== 'string' || name.trim() === '') throw new Error('registerIdentity: identity.name is required');
233
+ const store = opts.store ?? (registerIdentity._defaultStore ??= createMemoryStore());
234
+
235
+ const key = 'identity:' + kind + ':' + name.trim();
236
+ const existing = store.get(key);
237
+ if (existing) return { oid: existing.aic, aic: existing.aic, record: existing, reused: true };
238
+
239
+ const aic = generateAic({
240
+ version: opts.version,
241
+ arsp: opts.arsp,
242
+ vendor: opts.vendor,
243
+ entity: opts.entity,
244
+ salt: opts.salt,
245
+ });
246
+ const record = {
247
+ aic,
248
+ kind,
249
+ name: name.trim(),
250
+ provider: identity.provider ?? null,
251
+ createdAt: new Date().toISOString(),
252
+ isOntology: isOntologyAic(aic),
253
+ };
254
+ store.set(key, record);
255
+ store.set('aic:' + aic, record); // 反查索引
256
+ return { oid: aic, aic, record, reused: false };
257
+ }
258
+
259
+ /**
260
+ * CAI 身份证书发行:按 ACPs-ATR 语义构造证书(CN=AIC、SAN=URI:acps://{AIC}、extendedKeyUsage 分用途、
261
+ * 有效期 requestedValidity)+ EAB 凭证(绑定 ACME 账户与 AIC)+ 证书存证。
262
+ * @param {object} identity 已注册身份 { aic: string }
263
+ * @param {object} [claims] 附加声明 { role?, layer?, org?, usage?: 'clientAuth'|'serverAuth',
264
+ * requestedValidityDays?, altNames? }
265
+ * @param {object} [opts] { store?, algorithm?, salt?, issuer?, signingKey? }
266
+ * issuer:证书签发者描述 { aic, name }(缺省默认 CASP 根)
267
+ * signingKey:issuer 的签发密钥 { algorithm, privateKey }(信任链语义:证书由 issuer 私钥签发,
268
+ * 验签用 issuer 公钥);缺省回退:store 中有 issuer AIC 的 key 则用之,否则用证书自身私钥(自签场景)
269
+ * @returns {Promise<{ credential: object, eab: object, keyRef: object }>}
270
+ */
271
+ export async function issueCredential(identity, claims = {}, opts = {}) {
272
+ const aic = identity?.aic ?? identity?.oid;
273
+ if (typeof aic !== 'string' || !validateAic(aic, { salt: opts.salt })) {
274
+ throw new Error('issueCredential: valid identity.aic (AIC) is required');
275
+ }
276
+ const store = opts.store ?? (issueCredential._defaultStore ??= createMemoryStore());
277
+
278
+ const algorithm = opts.algorithm ?? DEFAULT_SIGN_ALGORITHM;
279
+ if (!SIGN_ALGORITHMS[algorithm]) throw new Error('issueCredential: unsupported algorithm ' + JSON.stringify(algorithm));
280
+ const { publicKey, privateKey } = generateKeyPair(algorithm);
281
+
282
+ const usage = claims.usage === 'serverAuth' ? 'serverAuth' : 'clientAuth'; // 分用途签发
283
+ const validityDays = Math.min(
284
+ Math.max(Number.isInteger(claims.requestedValidityDays) ? claims.requestedValidityDays : CAI_DEFAULT_VALIDITY_DAYS, 1),
285
+ CAI_MAX_VALIDITY_DAYS,
286
+ );
287
+ const now = new Date();
288
+ const notAfter = new Date(now.getTime() + validityDays * 24 * 60 * 60 * 1000);
289
+
290
+ const credential = {
291
+ version: '02.01',
292
+ type: 'CAI',
293
+ aic,
294
+ cn: aic, // CN=AIC(不附加域名后缀)
295
+ san: ['URI:acps://' + aic, ...(claims.altNames ?? [])],
296
+ extendedKeyUsage: [usage],
297
+ notBefore: now.toISOString(),
298
+ notAfter: notAfter.toISOString(),
299
+ validityDays,
300
+ publicKey: publicKey.export({ type: 'spki', format: 'pem' }),
301
+ algorithm,
302
+ subject: {
303
+ aic,
304
+ role: claims.role ?? null,
305
+ layer: claims.layer ?? null,
306
+ org: claims.org ?? null,
307
+ },
308
+ issuer: opts.issuer ?? { aic: '1.2.156.3088.1.0001.00001.000000.000000.ROOT', name: 'CASP' },
309
+ };
310
+
311
+ // EAB 凭证(ACPs-ATR §3.2(4):keyId + macKey 一次性绑定 AIC 与 ACME 账户)
312
+ const eab = {
313
+ keyId: crypto.randomUUID().replace(/-/g, ''),
314
+ macKey: crypto.randomBytes(32).toString('base64url'),
315
+ aic,
316
+ expiresAt: new Date(now.getTime() + CAI_EAB_EXPIRE_HOURS * 60 * 60 * 1000).toISOString(),
317
+ };
318
+
319
+ // 证书签名(证书核心载荷 → sign;验签走 verifySignature)
320
+ // 信任链语义:证书由 issuer 的私钥签发,验签用 issuer 的公钥(见 verifyTrustChain)
321
+ const core = JSON.stringify({ ...credential, signature: undefined });
322
+ let signingKey = opts.signingKey;
323
+ if (!signingKey) {
324
+ const issuerKey = store.get('key:' + (opts.issuer?.aic ?? aic));
325
+ if (issuerKey) signingKey = issuerKey; // issuer 已在本 store 持有密钥 → 用之签发
326
+ }
327
+ const signature = signingKey
328
+ ? await sign(core, signingKey)
329
+ : await sign(core, { algorithm, privateKey }); // 无 issuer 密钥 → 自签(根锚/单凭证场景)
330
+ credential.signature = signature;
331
+
332
+ const keyRef = { keyId: 'key:' + aic, algorithm, privateKey: privateKey.export({ type: 'pkcs8', format: 'pem' }) };
333
+ store.set('credential:' + aic, credential);
334
+ store.set('eab:' + eab.keyId, eab);
335
+ store.set('key:' + aic, keyRef);
336
+ return { credential, eab, keyRef };
337
+ }
338
+
339
+ /**
340
+ * 数字签名:可插拔算法接口。默认 ECDSA P-256(node:crypto 原生,可跑可验签),
341
+ * 可选 RSA-2048;SM2 仅占位(参考实现 v2.1.0 无 SM2 证据)——algorithm='sm2' 显式拒绝。
342
+ * @param {string|Buffer} payload 待签内容
343
+ * @param {object} keyRef { algorithm, privateKey } 或 { keyId }(经 store 解析)
344
+ * @param {object} [opts] { store? }
345
+ * @returns {Promise<{ signature: string, algorithm: string }>} base64url 签名
346
+ */
347
+ export async function sign(payload, keyRef, opts = {}) {
348
+ const data = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload), 'utf8');
349
+ let algorithm = keyRef?.algorithm;
350
+ let privateKey = keyRef?.privateKey;
351
+ if (!privateKey && keyRef?.keyId) {
352
+ const stored = (opts.store ?? (sign._defaultStore ??= createMemoryStore())).get(keyRef.keyId);
353
+ if (!stored?.privateKey) throw new Error('sign: key not found for keyRef ' + keyRef.keyId);
354
+ algorithm = stored.algorithm;
355
+ privateKey = stored.privateKey;
356
+ }
357
+ if (!privateKey) throw new Error('sign: keyRef.privateKey (or keyId with store) is required');
358
+ if (algorithm === 'sm2') {
359
+ // SM2 待正式文本校准:参考实现 v2.1.0 无 SM2 证据,不硬实现
360
+ throw new Error('sign: SM2 待正式文本校准(ACPs v2.1.0 无 SM2 证据,暂不实现;请用 ecdsa-p256 / rsa-2048)');
361
+ }
362
+ if (!algorithm || !SIGN_ALGORITHMS[algorithm]) {
363
+ throw new Error('sign: unsupported algorithm ' + JSON.stringify(algorithm));
364
+ }
365
+ const spec = SIGN_ALGORITHMS[algorithm];
366
+ const key = typeof privateKey === 'string'
367
+ ? crypto.createPrivateKey(privateKey)
368
+ : privateKey;
369
+ const signature = crypto.sign(spec.hash, data, { key });
370
+ return { signature: signature.toString('base64url'), algorithm };
371
+ }
372
+
373
+ /**
374
+ * 验签(与 sign 配对;供信任链验证与外部调用方使用)
375
+ * @param {string|Buffer} payload 原始内容
376
+ * @param {string} signature base64url 签名
377
+ * @param {object} keyRef { algorithm, publicKey }
378
+ * @returns {Promise<boolean>}
379
+ */
380
+ export async function verifySignature(payload, signature, keyRef) {
381
+ const data = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload), 'utf8');
382
+ const algorithm = keyRef?.algorithm;
383
+ if (!algorithm || !SIGN_ALGORITHMS[algorithm]) return false;
384
+ const spec = SIGN_ALGORITHMS[algorithm];
385
+ const key = typeof keyRef.publicKey === 'string'
386
+ ? crypto.createPublicKey(keyRef.publicKey)
387
+ : keyRef.publicKey;
388
+ try {
389
+ return crypto.verify(spec.hash, data, key, Buffer.from(signature, 'base64url'));
390
+ } catch {
391
+ return false;
392
+ }
393
+ }
394
+
395
+ /**
396
+ * 信任链验证:沿凭证链自近而远验证至根信任锚。
397
+ * 规则:结构校验(CAI 类型 + AIC 格式)→ 有效期 → 吊销检查 → 逐级验签(证书签名由上级公钥验证)
398
+ * → 路径深度限制 → 根锚确认。
399
+ * @param {object} credential 待验凭证(issueCredential 产出)
400
+ * @param {Array<object>} chain 上级凭证链(自近而远;末级为根,其 issuer.aic 须命中 trustAnchors 或自签)
401
+ * @param {object} [opts] { trustAnchors?: string[], revokedAics?: string[], maxDepth?: number, salt? }
402
+ * @returns {Promise<{ ok: boolean, reason?: string, depth: number }>}
403
+ */
404
+ export async function verifyTrustChain(credential, chain = [], opts = {}) {
405
+ const trustAnchors = opts.trustAnchors ?? [];
406
+ const revoked = new Set(opts.revokedAics ?? []);
407
+ const maxDepth = opts.maxDepth ?? 5;
408
+ const salt = opts.salt ?? '';
409
+
410
+ const fail = (reason) => ({ ok: false, reason, depth: 0 });
411
+
412
+ if (!credential || typeof credential !== 'object') return fail('credential must be an object');
413
+ if (credential.type !== 'CAI') return fail('credential.type must be CAI');
414
+ if (!validateAic(credential.aic, { salt })) return fail('credential.aic is not a valid AIC');
415
+
416
+ // 吊销检查(本级 + 链上全部 AIC)
417
+ const aicChain = [credential.aic, ...chain.map((c) => c?.aic).filter(Boolean)];
418
+ if (aicChain.some((a) => revoked.has(a))) return fail('revoked AIC in chain: ' + aicChain.filter((a) => revoked.has(a)).join(','));
419
+
420
+ // 有效期
421
+ const now = Date.now();
422
+ if (Date.parse(credential.notAfter) < now) return fail('credential expired (notAfter=' + credential.notAfter + ')');
423
+ if (Date.parse(credential.notBefore) > now) return fail('credential not yet valid (notBefore=' + credential.notBefore + ')');
424
+
425
+ // 路径深度限制
426
+ const depth = chain.length + 1;
427
+ if (depth > maxDepth) return fail('chain depth ' + depth + ' exceeds maxDepth ' + maxDepth);
428
+
429
+ // 逐级验签:本级 signature 用 issuer 公钥(chain[0] 的 publicKey)验证
430
+ const verifyOne = async (cert, issuerCert) => {
431
+ if (!issuerCert?.publicKey) return false;
432
+ const core = JSON.stringify({ ...cert, signature: undefined });
433
+ return verifySignature(core, cert?.signature?.signature, {
434
+ algorithm: cert?.signature?.algorithm ?? issuerCert.algorithm,
435
+ publicKey: issuerCert.publicKey,
436
+ });
437
+ };
438
+
439
+ if (chain.length === 0) {
440
+ // 单凭证:须为自签且命中根锚(或显式信任该 AIC)
441
+ const selfSigned = credential.issuer?.aic === credential.aic;
442
+ const anchored = trustAnchors.includes(credential.aic) || trustAnchors.includes(credential.issuer?.aic);
443
+ if (!selfSigned || !anchored) {
444
+ return fail('single credential must be self-signed and match a trust anchor (issuer.aic=' + credential.issuer?.aic + ')');
445
+ }
446
+ return { ok: true, depth };
447
+ }
448
+
449
+ // 链式验证:credential ← chain[0] ← chain[1] ← ... ← 根
450
+ let prev = credential;
451
+ for (let i = 0; i < chain.length; i++) {
452
+ const issuerCert = chain[i];
453
+ if (!issuerCert || typeof issuerCert !== 'object') return fail('chain[' + i + '] is not a certificate');
454
+ if (issuerCert.type !== 'CAI') return fail('chain[' + i + '].type must be CAI');
455
+ if (Date.parse(issuerCert.notAfter) < now) return fail('chain[' + i + '] expired');
456
+ const sigOk = await verifyOne(prev, issuerCert);
457
+ if (!sigOk) return fail('signature verification failed at chain[' + i + '] (issuer=' + issuerCert.aic + ')');
458
+ prev = issuerCert;
459
+ }
460
+
461
+ // 根锚:末级 issuer 须命中 trustAnchors(或末级自签且命中)
462
+ const root = chain[chain.length - 1];
463
+ const rootSelfSigned = root.issuer?.aic === root.aic;
464
+ const anchored = trustAnchors.includes(root.aic)
465
+ || (rootSelfSigned && trustAnchors.includes(root.issuer?.aic));
466
+ if (!anchored) return fail('root not in trust anchors (root.aic=' + root.aic + ', issuer=' + root.issuer?.aic + ')');
467
+
468
+ return { ok: true, depth };
469
+ }
@@ -0,0 +1,93 @@
1
+ /*
2
+ Copyright (C) 2025-2026 Punky
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ // 国标 AIP 工具 6 属性描述生成器(纯函数零副作用)
19
+ // 国标 P7 工具调用要求每工具提供 6 项属性:toolId / Name / Description / Version / InputParam / OutputParam
20
+ // (GB/Z 185-2026《人工智能 智能体互联》P7)
21
+ // 设计要点:4 项原样透传(name/description/inputParam/outputParam)、2 项派生(toolId/version),
22
+ // 保证「零语义改写」——国标字段与引擎既有描述同构透传而非二次建模,任何一方变更都不产生漂移。
23
+ // 本文件只依赖 Node 内建(structuredClone),不读文件系统,可独立单测。
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ // 引擎版本缺省值(可被 ctx.version 注入 / config.aip.toolVersion 覆盖)
27
+ export function engineVersion() {
28
+ try {
29
+ return JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version;
30
+ } catch {
31
+ return '0.0.0';
32
+ }
33
+ }
34
+
35
+ // toolId 命名规则(统一):dsh.punky-swarm.<既有 name>(反向域唯一性 + 双向可逆,预留 OID 前缀接入位)
36
+ export function toToolId(name) {
37
+ return 'dsh.punky-swarm.' + name;
38
+ }
39
+
40
+ // inputParam:defineTool 已把扁平属性表归一化为 JSON Schema 顶层结构
41
+ // {type:'object', properties, required}(required 提升为顶层数组,与国标 inputParam 同构)——
42
+ // 已归一化则原样透传(「原样透传」语义),扁平形态兜底展开(防御 defineTool 版本差异)。
43
+ // 国标要求 required 恒存在(全可选参数时补空数组)。
44
+ export function toInputParam(parameters = {}) {
45
+ if (parameters && typeof parameters === 'object' && 'type' in parameters) {
46
+ const clone = structuredClone(parameters);
47
+ if (!('required' in clone)) clone.required = [];
48
+ return clone;
49
+ }
50
+ const required = [];
51
+ const properties = {};
52
+ for (const [key, spec] of Object.entries(parameters)) {
53
+ const { required: isRequired, ...rest } = spec ?? {};
54
+ if (isRequired === true) required.push(key);
55
+ properties[key] = rest;
56
+ }
57
+ return { type: 'object', properties, required };
58
+ }
59
+
60
+ // outputParam:既有 output.schema 已是 JSON Schema 顶层结构,原样透传(深拷贝防共享引用漂移)
61
+ export function toOutputParam(output) {
62
+ return output?.schema ? structuredClone(output.schema) : { type: 'object', properties: {} };
63
+ }
64
+
65
+ // 单工具 6 属性生成器:输入 defineTool 对象 + ctx({ version?, config? }),输出国标 6 属性 JSON
66
+ // ctx.version:引擎版本(缺省 engineVersion());ctx.config.aip.toolVersion:可选覆盖
67
+ export function buildToolDescriptor(tool, ctx = {}) {
68
+ const config = ctx.config ?? {};
69
+ const version = config?.aip?.toolVersion ?? ctx.version ?? engineVersion();
70
+ return {
71
+ toolId: toToolId(tool.name),
72
+ name: tool.name,
73
+ description: tool.description,
74
+ version,
75
+ inputParam: toInputParam(tool.parameters),
76
+ outputParam: toOutputParam(tool.output),
77
+ };
78
+ }
79
+
80
+ // 工具目录快照:持有全量描述 + 只读 list()/get(name) 查询;注册完成后冻结(防误改)
81
+ export function buildToolCatalog(tools, ctx = {}) {
82
+ const descriptors = tools.map((t) => buildToolDescriptor(t, ctx));
83
+ return {
84
+ generatedAt: new Date().toISOString(),
85
+ descriptors: Object.freeze(descriptors),
86
+ list() {
87
+ return descriptors.slice();
88
+ },
89
+ get(name) {
90
+ return descriptors.find((d) => d.name === name) ?? null;
91
+ },
92
+ };
93
+ }