@lqc123qwe/car-runtime 1.4.0 → 1.5.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.
@@ -0,0 +1,145 @@
1
+ /**
2
+ * 1.5-S3 · 配置完整形态(S35):car.config.ts + zod(D-22)
3
+ *
4
+ * 覆盖:
5
+ * - zod 校验层 JSON 键位兼容(存量锚点):未知键 fail-visible(llm.temperature)/ 类型错
6
+ * (llm.maxTokens 必须为正整数)/ sandbox 扁平渲染 / 坏 JSON / 顶层非对象
7
+ * - car.config.ts 载体:发现序 ts > json(并存 = ts 胜出)/ 显式 --config *.ts /
8
+ * 未预热显式报错(禁静默回退 json)/ epoch 缓存击穿(热改重预热取新内容)
9
+ * - TS 形态校验:缺 default export / 非 object default / 坏 TS(Node 不支持时引导文案)
10
+ * - mcp url/headers 键位经 zod(s34 锚点:恰好其一 / http(s) URL / 通道专属键)
11
+ */
12
+ import { test } from 'node:test'
13
+ import assert from 'node:assert/strict'
14
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
15
+ import { tmpdir } from 'node:os'
16
+ import { join } from 'node:path'
17
+ import { loadCarConfig, warmCarConfig } from '../src/load/config.ts'
18
+ import { mergeSignatureGate, mergeLlmConfig } from '../src/load/config.ts'
19
+
20
+ function withTempDir(name: string, fn: (dir: string) => Promise<void> | void): Promise<void> {
21
+ const dir = mkdtempSync(join(tmpdir(), `car-s35-${name}-`))
22
+ try {
23
+ const r = fn(dir)
24
+ const cleanup = () => { try { rmSync(dir, { recursive: true, force: true }) } catch { /* 红线 8 */ } }
25
+ if (r instanceof Promise) return r.finally(cleanup)
26
+ cleanup()
27
+ return Promise.resolve()
28
+ } catch (e) {
29
+ try { rmSync(dir, { recursive: true, force: true }) } catch { /* 红线 8 */ }
30
+ throw e
31
+ }
32
+ }
33
+
34
+ // ==================== zod 校验层(JSON 键位兼容) ====================
35
+
36
+ test('S35: zod 校验层——存量锚点全过(未知键/类型错/坏 JSON/顶层非对象/扁平渲染)', () => {
37
+ withTempDir('zod-compat', dir => {
38
+ // s31 锚点:未知键 llm.temperature / maxTokens 类型错
39
+ writeFileSync(join(dir, 'a.json'), JSON.stringify({ llm: { temperature: 0.7 } }))
40
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'a.json') }).error!, /未知键 llm\.temperature/)
41
+ writeFileSync(join(dir, 'b.json'), JSON.stringify({ llm: { maxTokens: 1.5 } }))
42
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'b.json') }).error!, /llm\.maxTokens 必须为正整数/)
43
+ // 坏 JSON / 顶层非对象(CAR-E-CONFIG 前缀)
44
+ writeFileSync(join(dir, 'c.json'), '{ sandbox: {')
45
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'c.json') }).error!, /CAR-E-CONFIG/)
46
+ writeFileSync(join(dir, 'd.json'), '[]')
47
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'd.json') }).error!, /CAR-E-CONFIG/)
48
+ // 未知顶层键 / 未知嵌套键(unrecognized_keys 渲染)
49
+ writeFileSync(join(dir, 'e.json'), JSON.stringify({ plugins: [] }))
50
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'e.json') }).error!, /未知顶层键 "plugins"/)
51
+ writeFileSync(join(dir, 'f.json'), JSON.stringify({ sandbox: { unsigned: { allow: true, extra: 1 } } }))
52
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'f.json') }).error!, /未知键 sandbox\.unsigned\.extra/)
53
+ // 有效配置:扁平渲染(mergeSignatureGate 消费形态不变,s26 锚点)
54
+ writeFileSync(join(dir, 'g.json'), JSON.stringify({ sandbox: { unsigned: { allow: true }, sig: { enforce: true, trustRoot: 'PUBKEY' } }, llm: { baseUrl: 'https://x/v1', model: 'm' } }))
55
+ const ok = loadCarConfig({ explicitPath: join(dir, 'g.json') })
56
+ assert.equal(ok.error, undefined)
57
+ assert.deepEqual(ok.config.sandbox, { unsignedAllow: true, sigEnforce: true, sigTrustRoot: 'PUBKEY' })
58
+ assert.deepEqual(ok.config.llm, { baseUrl: 'https://x/v1', model: 'm' })
59
+ // mcp url/headers 键位经 zod(s34 锚点)
60
+ writeFileSync(join(dir, 'h.json'), JSON.stringify({ mcp: { servers: { a: { command: 'node', url: 'https://x' } } } }))
61
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'h.json') }).error!, /恰好其一/)
62
+ writeFileSync(join(dir, 'i.json'), JSON.stringify({ mcp: { servers: { a: { command: 'node', headers: { h: 'v' } } } } }))
63
+ assert.match(loadCarConfig({ explicitPath: join(dir, 'i.json') }).error!, /headers 仅远程通道/)
64
+ writeFileSync(join(dir, 'j.json'), JSON.stringify({ mcp: { servers: { a: { url: 'https://x/mcp', headers: { authorization: 'Bearer ${T}' } } } } }))
65
+ const ok2 = loadCarConfig({ explicitPath: join(dir, 'j.json') })
66
+ assert.equal(ok2.error, undefined)
67
+ assert.deepEqual(ok2.config.mcp?.servers?.a, { url: 'https://x/mcp', headers: { authorization: 'Bearer ${T}' } })
68
+ })
69
+ })
70
+
71
+ // ==================== car.config.ts 载体 ====================
72
+
73
+ test('S35: car.config.ts 载体——发现序 ts > json(并存 ts 胜出)+ 显式 --config *.ts + merge 全链', async () => {
74
+ await withTempDir('ts-carrier', async dir => {
75
+ writeFileSync(join(dir, 'car.config.ts'), `export default {\n llm: { baseUrl: 'https://ts.example/v1', model: 'ts-model', maxTokens: 512 },\n mcp: { servers: { remote: { url: 'https://mcp.example/mcp' } } },\n}\n`)
76
+ writeFileSync(join(dir, 'car.config.json'), JSON.stringify({ llm: { baseUrl: 'https://json.example/v1', model: 'json-model' } }))
77
+ await warmCarConfig({ cwd: dir })
78
+ const r = loadCarConfig({ cwd: dir })
79
+ assert.equal(r.error, undefined)
80
+ assert.match(r.path!, /car\.config\.ts$/, '发现序 ts > json:并存时 ts 胜出')
81
+ assert.equal(r.config.llm?.baseUrl, 'https://ts.example/v1')
82
+ assert.deepEqual(r.config.mcp?.servers?.remote, { url: 'https://mcp.example/mcp' }, 'ts 载体携带 1.5 新键位')
83
+ // merge 全链(D-16 优先级)不变
84
+ const merged = mergeLlmConfig({}, r.config)
85
+ assert.equal(merged.model, 'ts-model')
86
+ assert.equal(merged.maxTokens, 512)
87
+ // 显式 --config *.ts
88
+ await warmCarConfig({ explicitPath: join(dir, 'car.config.ts'), cwd: dir })
89
+ const r2 = loadCarConfig({ explicitPath: join(dir, 'car.config.ts'), cwd: dir })
90
+ assert.equal(r2.error, undefined)
91
+ assert.equal(r2.config.llm?.model, 'ts-model')
92
+ // 纯 json 目录不受影响(回归口径:无 ts 载体 = 原行为)
93
+ await withTempDir('json-only', async dir2 => {
94
+ writeFileSync(join(dir2, 'car.config.json'), JSON.stringify({ sandbox: { sig: { enforce: true } } }))
95
+ const r3 = loadCarConfig({ cwd: dir2 })
96
+ assert.equal(r3.error, undefined)
97
+ assert.equal(mergeSignatureGate({}, r3.config).mode, 'enforce')
98
+ })
99
+ })
100
+ })
101
+
102
+ test('S35: car.config.ts 未预热显式报错(禁静默回退 json)+ epoch 缓存击穿(热改重预热取新内容)', async () => {
103
+ await withTempDir('warm-protocol', async dir => {
104
+ writeFileSync(join(dir, 'car.config.ts'), `export default { llm: { model: 'v1' } }\n`)
105
+ writeFileSync(join(dir, 'car.config.json'), JSON.stringify({ llm: { model: 'json-fallback' } }))
106
+ // 未预热:显式报错(不静默回退 json——「配置写了但不生效」漂移面显式化)
107
+ const cold = loadCarConfig({ cwd: dir })
108
+ assert.match(cold.error!, /car\.config\.ts 未预热/)
109
+ assert.match(cold.error!, /warmCarConfig/)
110
+ // 预热后取 ts 内容
111
+ await warmCarConfig({ cwd: dir })
112
+ assert.equal(loadCarConfig({ cwd: dir }).config.llm?.model, 'v1')
113
+ // 热改:同路径重写 → 重预热(epoch 查询串击穿模块缓存)→ 新内容
114
+ writeFileSync(join(dir, 'car.config.ts'), `export default { llm: { model: 'v2' } }\n`)
115
+ await warmCarConfig({ cwd: dir })
116
+ assert.equal(loadCarConfig({ cwd: dir }).config.llm?.model, 'v2', 'epoch 击穿缓存')
117
+ })
118
+ })
119
+
120
+ test('S35: TS 形态校验——缺 default / 非 object default / 坏 TS fail-visible', async () => {
121
+ await withTempDir('ts-shape', async dir => {
122
+ writeFileSync(join(dir, 'car.config.ts'), `export const config = { llm: { model: 'x' } }\n`)
123
+ await warmCarConfig({ cwd: dir })
124
+ assert.match(loadCarConfig({ cwd: dir }).error!, /缺 default export/)
125
+
126
+ await withTempDir('ts-shape2', async dir2 => {
127
+ writeFileSync(join(dir2, 'car.config.ts'), `export default ['not', 'an', 'object']\n`)
128
+ await warmCarConfig({ cwd: dir2 })
129
+ assert.match(loadCarConfig({ cwd: dir2 }).error!, /default export 必须为对象/)
130
+ })
131
+
132
+ await withTempDir('ts-broken', async dir3 => {
133
+ writeFileSync(join(dir3, 'car.config.ts'), `export default { llm: ((( }\n`)
134
+ await warmCarConfig({ cwd: dir3 })
135
+ const r = loadCarConfig({ cwd: dir3 })
136
+ assert.match(r.error!, /CAR-E-CONFIG: car\.config\.ts 载入失败/)
137
+ })
138
+
139
+ await withTempDir('ts-zod-invalid', async dir4 => {
140
+ writeFileSync(join(dir4, 'car.config.ts'), `export default { llm: { temperature: 0.9 } }\n`)
141
+ await warmCarConfig({ cwd: dir4 })
142
+ assert.match(loadCarConfig({ cwd: dir4 }).error!, /未知键 llm\.temperature/, 'ts 载体与 json 同一 zod 校验层')
143
+ })
144
+ })
145
+ })