@tbox.cn/app-toolkit 0.6.0 → 0.7.1
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 +2 -2
- package/dist/index.d.ts +47 -16
- package/dist/index.js +9 -9
- package/package.json +2 -2
- package/src/assembly/resolve-key.ts +4 -2
- package/src/assembly/walk-manifests.ts +28 -12
- package/src/core/errors.ts +12 -2
- package/src/dto.ts +10 -5
- package/src/factory.ts +34 -20
- package/src/index.ts +2 -2
- package/src/integrations/predicate.ts +6 -3
- package/src/integrations/read.ts +11 -4
- package/src/integrations/write.ts +10 -5
- package/src/views/service-detail.ts +4 -3
- package/src/views/service-resolutions.ts +124 -28
- package/tests/dev-manifest-catalog.test.ts +39 -1
- package/tests/views.test.ts +266 -31
|
@@ -28,9 +28,10 @@ export function resolveModuleKey(walk: WalkManifestsResult, key: string): WalkMo
|
|
|
28
28
|
throw new AppToolkitError('MODULE_NOT_FOUND', 404, `模块 ${key} 非已装模块(id / 展示名均未命中)`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** 服务双键解析 → 真实 service id:服务键集(词汇 ∪ demand ∪ 供给足迹 ∪ configuredKeys
|
|
31
|
+
/** 服务双键解析 → 真实 service id:服务键集(词汇 ∪ demand ∪ 供给足迹 ∪ configuredKeys ∪ 归属)
|
|
32
32
|
* 精确命中 → 展示名(slotMeta.name ?? catalog.services[].name)唯一命中 → 404。
|
|
33
|
-
* configuredKeys = 当前集成配置 services 节键集(调用方注入)。
|
|
33
|
+
* configuredKeys = 当前集成配置 services 节键集(调用方注入)。
|
|
34
|
+
* v6.1 += 归属键(walk.ownership——纯归属无词汇/demand/供给的 owns 合法,O6 对称)。 */
|
|
34
35
|
export function resolveServiceKey(
|
|
35
36
|
walk: WalkManifestsResult,
|
|
36
37
|
configuredKeys: readonly string[],
|
|
@@ -50,6 +51,7 @@ export function resolveServiceKey(
|
|
|
50
51
|
|| Object.hasOwn(walk.catalog.services, key)
|
|
51
52
|
|| supplied.has(key)
|
|
52
53
|
|| configuredKeys.includes(key)
|
|
54
|
+
|| Object.hasOwn(walk.ownership, key)
|
|
53
55
|
) {
|
|
54
56
|
return key;
|
|
55
57
|
}
|
|
@@ -238,15 +238,28 @@ function readDevManifestRedirects(appDir: string): DevManifestRedirect[] {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
/**
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
241
|
+
/** 词汇源条目(via 标记供 doctor 幻影槽判定——config 快照 vs 真源;walkManifests 消费忽略此字段) */
|
|
242
|
+
export interface DiscoveredSlotSource {
|
|
243
|
+
dir: string;
|
|
244
|
+
slots: string[];
|
|
245
|
+
meta: Record<string, SlotMeta>;
|
|
246
|
+
/** ⓪ config-override(应用级快照/override)|①② package(node_modules 真源)|③ local(候选自身副本) */
|
|
247
|
+
via: 'config-override' | 'package' | 'local';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** 契约包发现链(探测四跳——词汇源真实布局全覆盖,B0;跳⓪ v7 新增):
|
|
251
|
+
* ⓪ <appDir>/config/service-slots.json(应用级 override 层——meta 首声明胜出 = 用户改名
|
|
252
|
+
* per-service 覆盖;slots 并集 = 无依赖词汇引导快照。CLI seed-once 生成:仅 slots +
|
|
253
|
+
* $comment 指纹,meta 恒空——真源 meta 恒可穿透,用户手写才生效);
|
|
254
|
+
* ① <候选>/node_modules/@tbox.cn/<pkg>/service-slots.json(包根)
|
|
255
|
+
* ② 同包 src/service-slots.json(发布包 src 附带 / 模板 shim 链接)
|
|
256
|
+
* ③ 候选自身 {service-slots.json, src/service-slots.json}(local/codegen 全量副本——
|
|
257
|
+
* mall 生成应用主形态,stage rewriteExportsToSrc 展开后永驻 packages/*,不依赖 install)
|
|
258
|
+
* 词汇覆盖 = Set 并集(跳⓪ 不遮蔽真源新增);meta = 发现序首声明胜出(跳⓪ 最前 = 覆盖权)。
|
|
246
259
|
* 同包双在场(异常态)root 优先。slots 与 meta 单次解析同产(目录枚举零缓存,F8;
|
|
247
260
|
* FX-3:文件**内容**经 file-cache)。 */
|
|
248
|
-
function discoverContractPackages(appDir: string, cache?: FileCache):
|
|
249
|
-
const found:
|
|
261
|
+
export function discoverContractPackages(appDir: string, cache?: FileCache): DiscoveredSlotSource[] {
|
|
262
|
+
const found: DiscoveredSlotSource[] = [];
|
|
250
263
|
const candidates: Array<string> = [appDir];
|
|
251
264
|
for (const sub of ['apps', 'packages']) {
|
|
252
265
|
const parent = join(appDir, sub);
|
|
@@ -260,10 +273,13 @@ function discoverContractPackages(appDir: string, cache?: FileCache): Array<{ di
|
|
|
260
273
|
}
|
|
261
274
|
}
|
|
262
275
|
const seenRoots = new Set<string>();
|
|
263
|
-
const addDiscovered = (dir: string, file: string): void => {
|
|
276
|
+
const addDiscovered = (dir: string, file: string, via: DiscoveredSlotSource['via']): void => {
|
|
264
277
|
const parsed = cache ? cache.read(file, parseSlotsFile) : parseSlotsFile(readFileSync(file, 'utf8'));
|
|
265
|
-
if (parsed !== null && parsed !== undefined) found.push({ dir, slots: parsed.slots, meta: parsed.meta });
|
|
278
|
+
if (parsed !== null && parsed !== undefined) found.push({ dir, slots: parsed.slots, meta: parsed.meta, via });
|
|
266
279
|
};
|
|
280
|
+
// 跳⓪:应用级 override/引导快照(发现序最前——meta 覆盖权,词汇并集不遮蔽)
|
|
281
|
+
const overrideFile = join(appDir, 'config', 'service-slots.json');
|
|
282
|
+
if (existsSync(overrideFile)) addDiscovered(appDir, overrideFile, 'config-override');
|
|
267
283
|
for (const dir of candidates) {
|
|
268
284
|
// 跳 1/2:node_modules 位(包根 → src——显式安装真源优先)
|
|
269
285
|
const scopeDir = join(dir, 'node_modules', '@tbox.cn');
|
|
@@ -280,14 +296,14 @@ function discoverContractPackages(appDir: string, cache?: FileCache): Array<{ di
|
|
|
280
296
|
seenRoots.add(pkgDir);
|
|
281
297
|
const rootFile = join(pkgDir, 'service-slots.json');
|
|
282
298
|
const srcFile = join(pkgDir, 'src', 'service-slots.json');
|
|
283
|
-
if (existsSync(rootFile)) addDiscovered(pkgDir, rootFile);
|
|
284
|
-
else if (existsSync(srcFile)) addDiscovered(pkgDir, srcFile);
|
|
299
|
+
if (existsSync(rootFile)) addDiscovered(pkgDir, rootFile, 'package');
|
|
300
|
+
else if (existsSync(srcFile)) addDiscovered(pkgDir, srcFile, 'package');
|
|
285
301
|
}
|
|
286
302
|
}
|
|
287
303
|
// 跳 3:候选自身(local/codegen 全量副本形态——后置于 nm 位:真源 > 副本,meta 首声明序随此)
|
|
288
304
|
for (const dir of candidates) {
|
|
289
305
|
for (const file of [join(dir, 'service-slots.json'), join(dir, 'src', 'service-slots.json')]) {
|
|
290
|
-
if (existsSync(file)) addDiscovered(dir, file);
|
|
306
|
+
if (existsSync(file)) addDiscovered(dir, file, 'local');
|
|
291
307
|
}
|
|
292
308
|
}
|
|
293
309
|
return found;
|
package/src/core/errors.ts
CHANGED
|
@@ -36,8 +36,15 @@ export class AppToolkitError extends Error {
|
|
|
36
36
|
/** CONTRACTS_NOT_RESOLVED message(N1 构建指引——api.md 错误信封示例)。
|
|
37
37
|
* P-4:notes 优先——resolver 在全部不可解析路径均已 push 携带操作指引的 notes(导入失败/
|
|
38
38
|
* 产物缺失/未安装/<0.9 升级);现行 version===null 分支会把 notes 内细分指引吞成通用文案。
|
|
39
|
-
*
|
|
40
|
-
|
|
39
|
+
* 分支保留为兜底(防御性——正常路径 notes 恒非空)。
|
|
40
|
+
* v7:+= ownershipBindingV6 分支(标记门第四分支单源化——写门控 assertContractsUsable 与
|
|
41
|
+
* 读求值轴降级 statusMessage 共用同一文案;可选参向后兼容)。 */
|
|
42
|
+
export function contractsNotResolvedMessage(contracts: {
|
|
43
|
+
version: string | null;
|
|
44
|
+
strictValidation: boolean;
|
|
45
|
+
notes: readonly string[];
|
|
46
|
+
ownershipBindingV6?: boolean;
|
|
47
|
+
}): string {
|
|
41
48
|
if (contracts.notes.length > 0) {
|
|
42
49
|
return contracts.notes[0];
|
|
43
50
|
}
|
|
@@ -47,6 +54,9 @@ export function contractsNotResolvedMessage(contracts: { version: string | null;
|
|
|
47
54
|
if (!contracts.strictValidation) {
|
|
48
55
|
return `contracts ${contracts.version} 无严格校验面(需 ≥0.9.0)——升级应用 @tbox.cn/app-contracts 依赖后可用`;
|
|
49
56
|
}
|
|
57
|
+
if (contracts.ownershipBindingV6 === false) {
|
|
58
|
+
return `contracts ${contracts.version ?? '(版本未知)'} 不支持 v6 modules 绑定形状(OWNERSHIP_BINDING_V6 标记缺席)——升级应用 @tbox.cn/app-contracts 依赖后可用`;
|
|
59
|
+
}
|
|
50
60
|
// 版本达标但解析态异常(notes 携带细分——导入拒绝/产物缺失)
|
|
51
61
|
return 'contracts workspace 源码态未构建——执行 pnpm --filter @tbox.cn/app-contracts build 后重试';
|
|
52
62
|
}
|
package/src/dto.ts
CHANGED
|
@@ -12,13 +12,17 @@
|
|
|
12
12
|
|
|
13
13
|
// ===== 词汇基线 =====
|
|
14
14
|
|
|
15
|
-
/** contracts 求值五态原样(EffectiveServiceResolution.status
|
|
15
|
+
/** contracts 求值五态原样(EffectiveServiceResolution.status)+ 视图轴降级态
|
|
16
|
+
* (evaluation-unavailable——toolkit 视图独有,非 contracts 求值产物:contracts 不可用
|
|
17
|
+
* (未安装/无严格面/v6 标记缺席)时读求值轴不再 503,改投静态骨架 + 本态标记;
|
|
18
|
+
* 消费方按非 ok 态渲染,statusMessage 携带 N1 分支指引) */
|
|
16
19
|
export type ServiceStatus =
|
|
17
20
|
| 'ok'
|
|
18
21
|
| 'service-not-configured'
|
|
19
22
|
| 'instance-not-found'
|
|
20
23
|
| 'instance-disabled'
|
|
21
|
-
| 'binding-unresolved'
|
|
24
|
+
| 'binding-unresolved'
|
|
25
|
+
| 'evaluation-unavailable';
|
|
22
26
|
|
|
23
27
|
/** 四配置层(instance 为 map 字段层) */
|
|
24
28
|
export type BindingLayer = 'app' | 'module' | 'service' | 'instance';
|
|
@@ -79,7 +83,7 @@ export interface ModuleDetailView {
|
|
|
79
83
|
configEditable: boolean;
|
|
80
84
|
/** 本模块 demand(declare.consumes;卡片展开 + 编辑器静态上下文自足) */
|
|
81
85
|
services: ServiceDemand[];
|
|
82
|
-
/** v4.4 D31:include=serviceResolutions 内嵌(demand 服务过滤 ≡ 批量 ?service= 语义;<0.9/形态异常 → null——永不 503) */
|
|
86
|
+
/** v4.4 D31:include=serviceResolutions 内嵌(v6.1 键集 = owned ∪ demand 服务过滤 ≡ 批量 ?service= 语义;<0.9/形态异常 → null——永不 503) */
|
|
83
87
|
serviceResolutions?: ServiceResolutionsView | null;
|
|
84
88
|
/** v4.4 D32:include=integrationSchemas 层级富化(零归属/未绑定 → null) */
|
|
85
89
|
integrationSchemas?: LayerIntegrationSchemas | null;
|
|
@@ -178,7 +182,7 @@ export interface ServiceIntegrationSchemas {
|
|
|
178
182
|
export interface ServiceResolutionsView {
|
|
179
183
|
/** P6 contracts echo(仅求值轴两端点携带) */
|
|
180
184
|
contracts: ContractsResolution;
|
|
181
|
-
/** 键集 = 服务词汇 ∪
|
|
185
|
+
/** 键集 = 服务词汇 ∪ 已配置 ∪ 归属(v6.1 += 归属——owned 服务恒可求值;模块/平台/孤儿全量) */
|
|
182
186
|
resolutions: Record<string, ServiceResolutionEntry>;
|
|
183
187
|
/** 谓词全量(doctor 同源;恒全量——subset 消费方客户端过滤) */
|
|
184
188
|
issues: IntegrationIssue[];
|
|
@@ -196,7 +200,8 @@ export interface ServiceResolutionEntry {
|
|
|
196
200
|
layer?: BindingLayer;
|
|
197
201
|
/** provider 在场时在场:(provider, service) ∈ 供给关系 */
|
|
198
202
|
supplied?: boolean;
|
|
199
|
-
/** owner 模块 install id
|
|
203
|
+
/** owner 模块 install id(v6.1 = walk.ownership 归属单源——install id 轴,与模块清单 id 恒可 join;
|
|
204
|
+
* 旧实现 demand owners[0] 为包名/@app 轴且漏 owned-not-consumed,已退役);平台服务/孤儿缺席 */
|
|
200
205
|
module?: string;
|
|
201
206
|
/** demand echo(OR 聚合);平台/孤儿恒 false;集成就绪判定单源(§7) */
|
|
202
207
|
required?: boolean;
|
package/src/factory.ts
CHANGED
|
@@ -18,7 +18,13 @@ import { loadViewSnapshotSync } from './views/context.js';
|
|
|
18
18
|
import { loadModulesView, loadModuleDetailView } from './views/modules.js';
|
|
19
19
|
import { loadAppView } from './views/app.js';
|
|
20
20
|
import { loadServiceDetailView } from './views/service-detail.js';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
loadServiceResolutionsView,
|
|
23
|
+
loadServiceResolutionDetail,
|
|
24
|
+
loadServiceResolutionsDegradeView,
|
|
25
|
+
loadServiceResolutionDegradeDetail,
|
|
26
|
+
contractsEvaluable,
|
|
27
|
+
} from './views/service-resolutions.js';
|
|
22
28
|
import { loadAppFormViews, loadModuleFormViews, loadServiceIntegrationSchemas, loadServiceFieldRegistry } from './views/integration-schemas.js';
|
|
23
29
|
import { loadCredentialsView } from './views/credentials.js';
|
|
24
30
|
import { loadCredentialTypesView } from './views/credential-types.js';
|
|
@@ -51,11 +57,13 @@ import type {
|
|
|
51
57
|
* Read+Write = HTTP 直通面 additive 冻结(L1 端点↔方法 1:1,L3 命名镜像:Integration=节点级 /
|
|
52
58
|
* Config=文件级);Internals = CLI/doctor/桥消费(自由演进)。
|
|
53
59
|
*
|
|
54
|
-
* **门控断言单点(policy at edge
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
60
|
+
* **门控断言单点(policy at edge——v7 读求值轴退役后范围 = 写 + 迁移面)**:写(4)+
|
|
61
|
+
* ensureMockBindings / writeIntegrationsConfig / evaluateIntegrationServices 入口
|
|
62
|
+
* assertContractsUsable——strictValidation === false → 503 CONTRACTS_NOT_RESOLVED;静态方法不门控
|
|
63
|
+
* (永不 503——铁律 #3);**读求值轴(loadServiceResolutions/loadServiceResolution 及 include 装配)
|
|
64
|
+
* v7 起不再 503**——contracts 不可用 → 200 降级骨架(evaluation-unavailable,doctor 分级/D31 先例
|
|
65
|
+
* 对齐;依据链与裁决见 Agent Note evaluation-axis-degrade);**include 装配为条件分支非门控**
|
|
66
|
+
* (D31 null 分支退役——三条件门 + 内嵌轴同步降级,四面统一骨架);integrations/ views/ 机制层保持纯函数。
|
|
59
67
|
*
|
|
60
68
|
* 并发:进程内写串行(per-factory promise chain)+ 原子写 + 写后本实例缓存即刻失效(D26)。
|
|
61
69
|
* onApplied(D7):toolkit 判定「需要重启」(实际落盘且非 dryRun/no-diff)→ 调用一次 +
|
|
@@ -193,11 +201,8 @@ export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}
|
|
|
193
201
|
throw new AppToolkitError('CONTRACTS_NOT_RESOLVED', 503, contractsNotResolvedMessage(contracts));
|
|
194
202
|
}
|
|
195
203
|
if (!contracts.ownershipBindingV6) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
503,
|
|
199
|
-
`contracts ${contracts.version ?? '(版本未知)'} 不支持 v6 modules 绑定形状(OWNERSHIP_BINDING_V6 标记缺席)——升级应用 @tbox.cn/app-contracts 依赖后可用`,
|
|
200
|
-
);
|
|
204
|
+
// v6 标记门第四分支(文案单源 = contractsNotResolvedMessage——降级轴 statusMessage 同源)
|
|
205
|
+
throw new AppToolkitError('CONTRACTS_NOT_RESOLVED', 503, contractsNotResolvedMessage(contracts));
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
|
|
@@ -263,10 +268,14 @@ export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}
|
|
|
263
268
|
const mod = resolveModuleKey(snap.walk, moduleId);
|
|
264
269
|
const view = loadModuleDetailView(snap, mod.id);
|
|
265
270
|
if (include.has('serviceResolutions')) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
// v7:三条件门 + 内嵌轴同步降级(P14 (b) 四面统一骨架——D31 null 分支退役:
|
|
272
|
+
// include 请求了即恒返回视图;老消费方 null 分支保留为兼容死码)
|
|
273
|
+
// 过滤键 = owned ∪ demand(v6.1——v4.4 demand-only 遗留;owned 服务是模块页
|
|
274
|
+
// 集成面一半,mall-core 形态 owns>0/consumes=0 时 demand-only 内嵌恒空)
|
|
275
|
+
const resolutionFilter = [...new Set([...view.ownedServices, ...view.services.map((s) => s.service)])];
|
|
276
|
+
view.serviceResolutions = contractsEvaluable(contracts)
|
|
277
|
+
? await predicateIssues().then((issues) => loadServiceResolutionsView(snap, issues, resolutionFilter))
|
|
278
|
+
: loadServiceResolutionsDegradeView(snap, contracts, resolutionFilter); // owned ∪ demand 服务过滤(≡ 批量 ?service= 语义)
|
|
270
279
|
}
|
|
271
280
|
if (include.has('integrationSchemas') || include.has('integrationFieldRegistry')) {
|
|
272
281
|
// v4.6 D37:表单层双投影同源一次计算(双取单算 + I1——假想重定向天然同步)
|
|
@@ -283,9 +292,10 @@ export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}
|
|
|
283
292
|
const snap = snapshot(contracts);
|
|
284
293
|
const view = loadAppView(snap);
|
|
285
294
|
if (include.has('serviceResolutions')) {
|
|
286
|
-
|
|
295
|
+
// v7:内嵌轴同步降级(同 loadModule——D31 null 退役,app = 全量键集)
|
|
296
|
+
view.serviceResolutions = contractsEvaluable(contracts)
|
|
287
297
|
? await predicateIssues().then((issues) => loadServiceResolutionsView(snap, issues)) // app = 全量键集
|
|
288
|
-
:
|
|
298
|
+
: loadServiceResolutionsDegradeView(snap, contracts);
|
|
289
299
|
}
|
|
290
300
|
if (include.has('integrationSchemas') || include.has('integrationFieldRegistry')) {
|
|
291
301
|
const form = loadAppFormViews(snap, o?.appProvider); // 双取单算 + I1(同 loadModule)
|
|
@@ -316,15 +326,19 @@ export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}
|
|
|
316
326
|
},
|
|
317
327
|
async loadServiceResolutions(services?: string[]) {
|
|
318
328
|
const contracts = await resolveContractsForApp(appDir);
|
|
319
|
-
|
|
329
|
+
const snap = snapshot(contracts);
|
|
330
|
+
// v7 读求值轴降级门(policy-at-edge 读轴退役):contracts 不可用 → 200 静态骨架 +
|
|
331
|
+
// evaluation-unavailable(fail-visible 保留在数据内;写路径 503 门控不变)
|
|
332
|
+
if (!contractsEvaluable(contracts)) return loadServiceResolutionsDegradeView(snap, contracts, services);
|
|
320
333
|
const issues = await predicateIssues();
|
|
321
|
-
return loadServiceResolutionsView(
|
|
334
|
+
return loadServiceResolutionsView(snap, issues, services);
|
|
322
335
|
},
|
|
323
336
|
async loadServiceResolution(service: string) {
|
|
324
337
|
const contracts = await resolveContractsForApp(appDir);
|
|
325
|
-
assertContractsUsable(contracts);
|
|
326
338
|
const snap = snapshot(contracts);
|
|
327
339
|
const serviceId = resolveServiceKey(snap.walk, configuredServiceKeys(snap), service);
|
|
340
|
+
// v7 降级门同批量端点(三条件与;降级单体携带声明实例枚举 + inheritance 静态投影)
|
|
341
|
+
if (!contractsEvaluable(contracts)) return loadServiceResolutionDegradeDetail(serviceId, snap, contracts);
|
|
328
342
|
const issues = await predicateIssues();
|
|
329
343
|
return loadServiceResolutionDetail(serviceId, snap, issues);
|
|
330
344
|
},
|
package/src/index.ts
CHANGED
|
@@ -64,10 +64,10 @@ export { pruneIntegrationBindings } from './integrations/prune-bindings.js';
|
|
|
64
64
|
export type { PruneResult } from './integrations/prune-bindings.js';
|
|
65
65
|
|
|
66
66
|
// ── assembly 层(目录知识——walkManifests 四产物 + catalog 双形态 + 组合单体装配)──
|
|
67
|
-
export { walkManifests } from './assembly/walk-manifests.js';
|
|
67
|
+
export { walkManifests, discoverContractPackages } from './assembly/walk-manifests.js';
|
|
68
68
|
// 双键寻址(v7 词汇统一——factory 七方法入口单点消费的解析 helper;纯参数式零 IO)
|
|
69
69
|
export { resolveModuleKey, resolveServiceKey } from './assembly/resolve-key.js';
|
|
70
|
-
export type { WalkManifestsResult, WalkModuleInfo, WalkCatalog, WalkProviderEntry, DeclarationIssue, SlotMeta } from './assembly/walk-manifests.js';
|
|
70
|
+
export type { WalkManifestsResult, WalkModuleInfo, WalkCatalog, WalkProviderEntry, DeclarationIssue, SlotMeta, DiscoveredSlotSource } from './assembly/walk-manifests.js';
|
|
71
71
|
export { readAppManifest, writeAppManifest, manifestPath } from './assembly/app-manifest.js';
|
|
72
72
|
export type { AppManifest, NpmModuleEntry, ModuleMode } from './assembly/app-manifest.js';
|
|
73
73
|
export { loadProviderCatalog, loadProviderCatalogRaw } from './assembly/provider-catalog.js';
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* **code 枚举单源 = 本文件头映射表(additive-only;api.md §5 表与本表同步)**:
|
|
10
10
|
* CREDENTIAL_FILE_MISSING #1 error effective credentialRef 引用凭据文件不在场
|
|
11
11
|
* CREDENTIAL_TYPE_MISMATCH #2 error 凭据文件 .type 与绑定 credentialType 不一致
|
|
12
|
-
* SLOT_NOT_DECLARED #3 warning
|
|
12
|
+
* SLOT_NOT_DECLARED #3 warning 服务槽无消费方声明(拼写/需求方未安装/旧槽位词汇;
|
|
13
|
+
* v6.1 归属豁免——owned 服务不报)
|
|
13
14
|
* SLOT_NOT_CONFIGURED #4 warning 需求方缺口(服务槽未配置)
|
|
14
15
|
* INSTANCE_SUPPLY_UNMATCHED #5 error 绑定未命中已装供给(槽级/实例级覆盖)
|
|
15
16
|
* IMPL_WITHOUT_PROVIDER #5/#13 error impl-only(implementation 缺 provider 绑定组头)
|
|
@@ -333,9 +334,11 @@ export function evaluateIntegrationServices(
|
|
|
333
334
|
}
|
|
334
335
|
|
|
335
336
|
for (const [serviceId, service] of Object.entries(integrations.services)) {
|
|
336
|
-
// 1. 槽未知(不在任何 manifest
|
|
337
|
+
// 1. 槽未知(不在任何 manifest 需求声明,也非已知归属/供给域)——v6.1 += 归属豁免:
|
|
338
|
+
// owned+已配置+无人 consume(mall-core 形态)非「无消费方声明」,误报退役;
|
|
339
|
+
// ownership 缺席 = 面信息未喂(谓词纯函数)→ 恒查(与 MODULE_CONFIG_DEAD 防御模式同律)
|
|
337
340
|
const demanded = demandedServices[serviceId];
|
|
338
|
-
if (!demanded) {
|
|
341
|
+
if (!demanded && ownership?.[serviceId] === undefined) {
|
|
339
342
|
issues.push({
|
|
340
343
|
severity: 'warning',
|
|
341
344
|
code: 'SLOT_NOT_DECLARED',
|
package/src/integrations/read.ts
CHANGED
|
@@ -66,8 +66,15 @@ export async function readIntegrationsStrict(
|
|
|
66
66
|
return { config: raw, strict: false, contracts };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** P9 服务身份判据单源(A2):GET/PUT/DELETE 共用「词汇 ∪
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/** P9 服务身份判据单源(A2):GET/PUT/DELETE 共用「词汇 ∪ 已配置 ∪ 归属」——service-detail 判据
|
|
70
|
+
* 逐字冻结(api.md P9 的「demanded 退化」口径为文档层 nuance,现行实现与 GET 均不含 demanded——冻结现状即单源)。
|
|
71
|
+
* v6.1 += 归属键(O6 对称——纯归属无词汇/demand/配置的 owns 合法,resolveServiceKey 供给足迹先例):
|
|
72
|
+
* 求值轴可见(resolutionKeys 同批接入)而静态/写轴 404 会造死锁(诊断指引的编辑被拒)。 */
|
|
73
|
+
export function isKnownService(
|
|
74
|
+
vocabulary: readonly string[],
|
|
75
|
+
configuredKeys: readonly string[],
|
|
76
|
+
service: string,
|
|
77
|
+
ownedKeys: readonly string[],
|
|
78
|
+
): boolean {
|
|
79
|
+
return vocabulary.includes(service) || configuredKeys.includes(service) || ownedKeys.includes(service);
|
|
73
80
|
}
|
|
@@ -449,9 +449,12 @@ export function createWriteCore(appDir: string, opts: WriteCoreOptions = {}): Wr
|
|
|
449
449
|
assertSafeInstanceKeys(node, writeOpts, 'slot');
|
|
450
450
|
const issues: IntegrationServiceIssue[] = [];
|
|
451
451
|
const current = loadConfig(appDir, cache);
|
|
452
|
-
// P9 服务身份判据单源(A2):写入集 = 词汇 ∪
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
// P9 服务身份判据单源(A2):写入集 = 词汇 ∪ 已配置 ∪ 归属——未知服务(含 typo)404(api.md §5;
|
|
453
|
+
// v6.1 += 归属键——owned ∖ 词汇服务可配置,与求值轴 resolutionKeys 同批接入)。
|
|
454
|
+
// walkManifests 单次调用(目录枚举零缓存——双调 = 每写多一整轮遍历)
|
|
455
|
+
const walk = walkManifests(appDir, cache);
|
|
456
|
+
if (!isKnownService(walk.vocabulary, Object.keys((current?.services as Record<string, unknown> | undefined) ?? {}), service, Object.keys(walk.ownership))) {
|
|
457
|
+
throw new AppToolkitError('SERVICE_NOT_FOUND', 404, `服务 ${service} 不在服务词汇(已装契约包 ∪ 已配置 ∪ 归属)`);
|
|
455
458
|
}
|
|
456
459
|
if (current === null) {
|
|
457
460
|
issues.push({
|
|
@@ -505,8 +508,10 @@ export function createWriteCore(appDir: string, opts: WriteCoreOptions = {}): Wr
|
|
|
505
508
|
const issues: IntegrationServiceIssue[] = [];
|
|
506
509
|
const current = loadConfig(appDir, cache);
|
|
507
510
|
// P9 服务身份判据单源(A2):判据外未知服务 404;判据内缺席节点维持幂等 written:false
|
|
508
|
-
|
|
509
|
-
|
|
511
|
+
// (v6.1 判据 += 归属键——同 applyService;walkManifests 单次调用)
|
|
512
|
+
const walk = walkManifests(appDir, cache);
|
|
513
|
+
if (!isKnownService(walk.vocabulary, Object.keys((current?.services as Record<string, unknown> | undefined) ?? {}), service, Object.keys(walk.ownership))) {
|
|
514
|
+
throw new AppToolkitError('SERVICE_NOT_FOUND', 404, `服务 ${service} 不在服务词汇(已装契约包 ∪ 已配置 ∪ 归属)`);
|
|
510
515
|
}
|
|
511
516
|
const services = { ...((current?.services as Record<string, unknown>) ?? {}) };
|
|
512
517
|
if (!(service in services)) {
|
|
@@ -11,9 +11,10 @@ import { resolveServiceDisplay, suppliedByVendors, type ViewSnapshot } from './c
|
|
|
11
11
|
export function loadServiceDetailView(snapshot: ViewSnapshot, service: string): ServiceDetail {
|
|
12
12
|
const configured = (snapshot.integrations as { services?: Record<string, unknown> } | null)?.services ?? {};
|
|
13
13
|
const demand = snapshot.catalog.services[service];
|
|
14
|
-
// 404 判据与写路径单源(P9——A2 isKnownService;词汇成员零 demand/供给/config 仍 200
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// 404 判据与写路径单源(P9——A2 isKnownService;词汇成员零 demand/供给/config 仍 200;
|
|
15
|
+
// v6.1 += 归属键——owned ∖ 词汇服务静态单体可达,与求值/写轴同批接入)
|
|
16
|
+
if (!isKnownService(snapshot.walk.vocabulary, Object.keys(configured), service, Object.keys(snapshot.walk.ownership))) {
|
|
17
|
+
throw new AppToolkitError('SERVICE_NOT_FOUND', 404, `服务 ${service} 不在服务词汇(已装契约包 ∪ 已配置 ∪ 归属)`);
|
|
17
18
|
}
|
|
18
19
|
// integration = 文件 services[s] 逐字原值(编辑预填源 D21);无声明 = null
|
|
19
20
|
const integration = (configured[service] as ServiceDetail['integration'] | undefined) ?? null;
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
ServiceResolutionDetail,
|
|
11
11
|
ServiceStatus,
|
|
12
12
|
} from '../dto.js';
|
|
13
|
-
import { AppToolkitError } from '../core/errors.js';
|
|
13
|
+
import { AppToolkitError, contractsNotResolvedMessage } from '../core/errors.js';
|
|
14
14
|
import { resolveModuleBinding } from '../integrations/module-binding.js';
|
|
15
15
|
import type { ResolvedContracts } from '../core/contracts-resolver.js';
|
|
16
16
|
import type { WalkCatalog } from '../assembly/walk-manifests.js';
|
|
@@ -83,6 +83,10 @@ function evaluateInstance(
|
|
|
83
83
|
service,
|
|
84
84
|
instanceId,
|
|
85
85
|
supplies as never,
|
|
86
|
+
// v6 ownerOf:服务归属表(walk.ownership——buildOwnershipMap 同源产物)显式参。
|
|
87
|
+
// 缺席 + 文件含 modules 级绑定/配置 → contracts v6 fail-visible(binding-unresolved),
|
|
88
|
+
// root/modules 级联全断——视图轴与谓词轴同源透传,勿再遗漏(bug 复盘:fef9a71a 漏改点)。
|
|
89
|
+
snapshot.walk.ownership as Record<string, string>,
|
|
86
90
|
);
|
|
87
91
|
if (r.status === 'ok' && r.effective) {
|
|
88
92
|
const raw: unknown = r.effective.provider;
|
|
@@ -124,6 +128,7 @@ function evaluationContracts(snapshot: ViewSnapshot) {
|
|
|
124
128
|
service: string,
|
|
125
129
|
instanceId: string,
|
|
126
130
|
supplies?: never,
|
|
131
|
+
ownerOf?: Record<string, string>,
|
|
127
132
|
) => EffectiveResolutionShape;
|
|
128
133
|
normalizeInstances: (input: unknown) => { instances: Array<{ id: string }>; defaultInstanceId: string | null };
|
|
129
134
|
buildSupplyLookup: (catalog: unknown) => unknown;
|
|
@@ -187,8 +192,9 @@ export function resolveServiceSummary(
|
|
|
187
192
|
const demand = catalog.services[service];
|
|
188
193
|
const required = demand !== undefined ? !demand.optional : undefined;
|
|
189
194
|
|
|
190
|
-
// module = owner 模块(
|
|
191
|
-
|
|
195
|
+
// module = owner 模块(v6 归属单源 walk.ownership——install id 轴;平台/孤儿缺席)。
|
|
196
|
+
// demand owners 是消费方集合且键轴为包名/@app 形态,不作归因源(与模块清单 id 恒可 join)
|
|
197
|
+
const module = snapshot.walk.ownership[service];
|
|
192
198
|
|
|
193
199
|
return {
|
|
194
200
|
status: evaluation.status,
|
|
@@ -205,18 +211,28 @@ function isSuppliedCheck(catalog: WalkCatalog, provider: string, service: string
|
|
|
205
211
|
return isSupplied(catalog, provider, service);
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
/**
|
|
214
|
+
/** 批量键集(求值轴/降级轴共用单源:服务词汇 ∪ 需求侧 ∪ 已配置 ∪ 归属)。
|
|
215
|
+
* v6.1 += 归属键——owned 服务恒可求值(embed 自足前置:迭代基不是 filter,
|
|
216
|
+
* 缺此键则 owned ∖ 词汇服务在内嵌轴静默丢键,fail-visible 破功)。 */
|
|
217
|
+
function resolutionKeys(snapshot: ViewSnapshot): string[] {
|
|
218
|
+
const keys = new Set<string>([
|
|
219
|
+
...snapshot.walk.vocabulary,
|
|
220
|
+
...Object.keys(snapshot.catalog.services),
|
|
221
|
+
...Object.keys(snapshot.walk.ownership),
|
|
222
|
+
]);
|
|
223
|
+
const configured = (snapshot.integrations as { services?: Record<string, unknown> } | null)?.services ?? {};
|
|
224
|
+
for (const k of Object.keys(configured)) keys.add(k);
|
|
225
|
+
return [...keys].sort();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** 批量求值(键集 = resolutionKeys 单源:词汇 ∪ 需求侧 ∪ 已配置 ∪ 归属;?service= 过滤语义——未知键缺席;issues 恒全量) */
|
|
209
229
|
export function loadServiceResolutionsView(
|
|
210
230
|
snapshot: ViewSnapshot,
|
|
211
231
|
issues: IntegrationIssue[],
|
|
212
232
|
filter?: string[],
|
|
213
233
|
): ServiceResolutionsView {
|
|
214
|
-
const keys = new Set<string>([...snapshot.walk.vocabulary, ...Object.keys(snapshot.catalog.services)]);
|
|
215
|
-
const configured = (snapshot.integrations as { services?: Record<string, unknown> } | null)?.services ?? {};
|
|
216
|
-
for (const k of Object.keys(configured)) keys.add(k);
|
|
217
|
-
|
|
218
234
|
const resolutions: Record<string, ServiceResolutionEntry> = {};
|
|
219
|
-
for (const service of
|
|
235
|
+
for (const service of resolutionKeys(snapshot)) {
|
|
220
236
|
if (filter && !filter.includes(service)) continue;
|
|
221
237
|
resolutions[service] = resolveServiceSummary(service, snapshot);
|
|
222
238
|
}
|
|
@@ -238,20 +254,12 @@ export function loadServiceResolutionDetail(
|
|
|
238
254
|
});
|
|
239
255
|
|
|
240
256
|
// P4 枚举规则:root 注册表 id 逐一 + 显式 '*' 条目(若在);无注册表且节点通配激活 → 单 '*' 合成;
|
|
241
|
-
// 无注册表无节点 → []
|
|
257
|
+
// 无注册表无节点 → [](枚举单源 = enumerateInstanceIds——降级轴声明实例投影共用)
|
|
242
258
|
const declaredIds = normalized.instances.map((i) => i.id);
|
|
243
259
|
const slot = (snapshot.integrations as { services?: Record<string, { instances?: Record<string, unknown> }> } | null)
|
|
244
260
|
?.services?.[service];
|
|
245
261
|
const slotKeys = slot?.instances ? Object.keys(slot.instances) : undefined;
|
|
246
|
-
|
|
247
|
-
if (declaredIds.length > 0) {
|
|
248
|
-
instanceIds = [...declaredIds];
|
|
249
|
-
if (slotKeys?.includes('*')) instanceIds.push('*');
|
|
250
|
-
} else if (slotKeys !== undefined && slotKeys.length > 0) {
|
|
251
|
-
instanceIds = slotKeys.includes('*') ? ['*'] : slotKeys;
|
|
252
|
-
} else {
|
|
253
|
-
instanceIds = [];
|
|
254
|
-
}
|
|
262
|
+
const instanceIds = enumerateInstanceIds(slotKeys, declaredIds);
|
|
255
263
|
|
|
256
264
|
// 默认实例口径(摘要 status = 默认实例求值——不重复头部;原 defaultEval 死代码已删 FX-2c)
|
|
257
265
|
|
|
@@ -267,7 +275,7 @@ export function loadServiceResolutionDetail(
|
|
|
267
275
|
});
|
|
268
276
|
|
|
269
277
|
// inheritance 四层(app → module → service → instance——effective 链声明位投影)
|
|
270
|
-
const inheritance = buildInheritance(snapshot, service);
|
|
278
|
+
const inheritance = buildInheritance(snapshot, service, normalized.defaultInstanceId ?? '*');
|
|
271
279
|
|
|
272
280
|
const summary = resolveServiceSummary(service, snapshot);
|
|
273
281
|
const resolution: ServiceResolution = {
|
|
@@ -283,24 +291,31 @@ export function loadServiceResolutionDetail(
|
|
|
283
291
|
};
|
|
284
292
|
}
|
|
285
293
|
|
|
294
|
+
/** P4 实例枚举单源(求值轴/降级轴共用):root 注册表 id 逐一 + 显式 '*' 条目(若在);
|
|
295
|
+
* 无注册表且节点通配激活 → 单 '*' 合成;无注册表无节点 → []。 */
|
|
296
|
+
function enumerateInstanceIds(slotKeys: string[] | undefined, declaredIds: string[]): string[] {
|
|
297
|
+
if (declaredIds.length > 0) {
|
|
298
|
+
const ids = [...declaredIds];
|
|
299
|
+
if (slotKeys?.includes('*')) ids.push('*');
|
|
300
|
+
return ids;
|
|
301
|
+
}
|
|
302
|
+
if (slotKeys !== undefined && slotKeys.length > 0) return slotKeys.includes('*') ? ['*'] : slotKeys;
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
|
|
286
306
|
/** inheritance 投影:四层各取该层声明 provider/implementation(缺席层 = 继承位,provider 缺席)。
|
|
287
|
-
* 实例层 = **默认实例口径**(FX-2c 修——与摘要 status
|
|
288
|
-
*
|
|
307
|
+
* 实例层 = **默认实例口径**(FX-2c 修——与摘要 status 同轴);defaultInstanceId 由调用方解析
|
|
308
|
+
* (求值轴 = normalizeInstances 委托;降级轴 = 原始直读——本函数零 contracts 依赖,双轴共用单源)。 */
|
|
289
309
|
function buildInheritance(
|
|
290
310
|
snapshot: ViewSnapshot,
|
|
291
311
|
service: string,
|
|
312
|
+
defaultInstanceId: string,
|
|
292
313
|
): ServiceResolution['inheritance'] {
|
|
293
314
|
const integrations = snapshot.integrations as {
|
|
294
315
|
provider?: string;
|
|
295
316
|
services?: Record<string, { provider?: unknown; implementation?: string; instances?: Record<string, { provider?: string; implementation?: string }> }>;
|
|
296
317
|
} | null;
|
|
297
318
|
const slot = integrations?.services?.[service];
|
|
298
|
-
const cfg = snapshot.integrations as { instances?: unknown; defaultInstanceId?: unknown } | null;
|
|
299
|
-
const normalized = evaluationContracts(snapshot).normalizeInstances({
|
|
300
|
-
instances: cfg?.instances,
|
|
301
|
-
defaultInstanceId: cfg?.defaultInstanceId,
|
|
302
|
-
});
|
|
303
|
-
const defaultInstanceId = normalized.defaultInstanceId ?? '*';
|
|
304
319
|
const defaultInstance = slot?.instances?.[defaultInstanceId] ?? slot?.instances?.['*'];
|
|
305
320
|
// module 段消费 resolveModuleBinding 单源(v6——与 binding / 谓词 #7 同源;ownerOf=walk.ownership)
|
|
306
321
|
const owner = snapshot.walk.ownership[service];
|
|
@@ -328,3 +343,84 @@ function buildInheritance(
|
|
|
328
343
|
|
|
329
344
|
/** 谓词全量 issues 装配(写路径/求值轴共用——工厂经 evaluateIntegrationServices 装配后传入) */
|
|
330
345
|
export type { IntegrationIssue };
|
|
346
|
+
|
|
347
|
+
// ── 降级轴(v7——policy-at-edge 读求值轴退役)──
|
|
348
|
+
// contracts 不可用(未安装 / 无严格面 / 求值面缺席 / v6 标记缺席)时,读求值轴不再 503,
|
|
349
|
+
// 改投静态骨架(walk + integrations 纯文件投影,零 contracts 依赖)+ evaluation-unavailable 标记。
|
|
350
|
+
// 依据链:doctor 分级先例(未安装 = warning 正常态)+ D31 include→null 先例;fail-visible 语义
|
|
351
|
+
// 保留在数据内(status + statusMessage N1 分支文案),写路径与 internals 门控不变。
|
|
352
|
+
|
|
353
|
+
/** 读求值轴可用判据(三条件与——降级门单源;写路径 assertContractsUsable 另有 503 语义,不复用) */
|
|
354
|
+
export function contractsEvaluable(contracts: ResolvedContracts): boolean {
|
|
355
|
+
return contracts.evaluation && contracts.strictValidation && contracts.ownershipBindingV6;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** 降级单条目(静态可算字段:module = walk.ownership(v6 归属单源,install id 轴)/
|
|
359
|
+
* required = OR 聚合;provider/layer 不投——保持「求值产物」语义纯净,layer 单独在场违 D31 孤儿层口径) */
|
|
360
|
+
function degradeEntry(snapshot: ViewSnapshot, service: string, message: string): ServiceResolutionEntry {
|
|
361
|
+
const demand = snapshot.catalog.services[service];
|
|
362
|
+
const owner = snapshot.walk.ownership[service];
|
|
363
|
+
return {
|
|
364
|
+
status: 'evaluation-unavailable',
|
|
365
|
+
statusMessage: message,
|
|
366
|
+
...(owner !== undefined ? { module: owner } : {}),
|
|
367
|
+
...(demand !== undefined ? { required: !demand.optional } : {}),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** 批量降级视图(键集与求值轴同源 resolutionKeys;?service= 过滤语义一致;issues 恒 []——
|
|
372
|
+
* 谓词与求值同源缺席;声明面诊断走 declarationIssues 通道不在此混装) */
|
|
373
|
+
export function loadServiceResolutionsDegradeView(
|
|
374
|
+
snapshot: ViewSnapshot,
|
|
375
|
+
contracts: ResolvedContracts,
|
|
376
|
+
filter?: string[],
|
|
377
|
+
): ServiceResolutionsView {
|
|
378
|
+
const message = contractsNotResolvedMessage(contracts);
|
|
379
|
+
const resolutions: Record<string, ServiceResolutionEntry> = {};
|
|
380
|
+
for (const service of resolutionKeys(snapshot)) {
|
|
381
|
+
if (filter && !filter.includes(service)) continue;
|
|
382
|
+
resolutions[service] = degradeEntry(snapshot, service, message);
|
|
383
|
+
}
|
|
384
|
+
return { contracts: toContractsResolution(contracts), resolutions, issues: [] };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** 单体降级视图(P14 extends 结构保证——摘要基 + instances 声明枚举 + inheritance 四层静态投影) */
|
|
388
|
+
export function loadServiceResolutionDegradeDetail(
|
|
389
|
+
service: string,
|
|
390
|
+
snapshot: ViewSnapshot,
|
|
391
|
+
contracts: ResolvedContracts,
|
|
392
|
+
): ServiceResolutionDetail {
|
|
393
|
+
const message = contractsNotResolvedMessage(contracts);
|
|
394
|
+
const resolution: ServiceResolution = {
|
|
395
|
+
...degradeEntry(snapshot, service, message),
|
|
396
|
+
instances: degradeInstances(snapshot, service),
|
|
397
|
+
inheritance: buildInheritance(snapshot, service, degradeDefaultInstanceId(snapshot)),
|
|
398
|
+
};
|
|
399
|
+
return { contracts: toContractsResolution(contracts), resolution, issues: [] };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** 声明实例投影(原始数据——root instances[].id ∪ slot 实例键,P4 枚举单源共用;
|
|
403
|
+
* 非 normalizeInstances 派生语义,单源不破)。逐实例同标记,statusMessage 不重复携带
|
|
404
|
+
* (resolution 顶层已携带)。 */
|
|
405
|
+
function degradeInstances(snapshot: ViewSnapshot, service: string): ResolvedInstance[] {
|
|
406
|
+
const slot = (snapshot.integrations as { services?: Record<string, { instances?: Record<string, unknown> }> } | null)
|
|
407
|
+
?.services?.[service];
|
|
408
|
+
const cfg = snapshot.integrations as { instances?: unknown } | null;
|
|
409
|
+
const declaredIds = Array.isArray(cfg?.instances)
|
|
410
|
+
? (cfg.instances as Array<{ id?: unknown }>)
|
|
411
|
+
.map((i) => (i && typeof i === 'object' ? i.id : undefined))
|
|
412
|
+
.filter((id): id is string => typeof id === 'string')
|
|
413
|
+
: [];
|
|
414
|
+
const slotKeys = slot?.instances ? Object.keys(slot.instances) : undefined;
|
|
415
|
+
return enumerateInstanceIds(slotKeys, declaredIds).map((id) => ({
|
|
416
|
+
instanceId: id,
|
|
417
|
+
status: 'evaluation-unavailable' as const,
|
|
418
|
+
}));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** 降级默认实例口径(近似声明):defaultInstanceId 原始直读,缺席 → '*' 通配位。
|
|
422
|
+
* normalizeInstances 的「单候选唯一解」推导不可复制(求值面单源)——近似口径见 api 契约。 */
|
|
423
|
+
function degradeDefaultInstanceId(snapshot: ViewSnapshot): string {
|
|
424
|
+
const cfg = snapshot.integrations as { defaultInstanceId?: unknown } | null;
|
|
425
|
+
return typeof cfg?.defaultInstanceId === 'string' && cfg.defaultInstanceId.length > 0 ? cfg.defaultInstanceId : '*';
|
|
426
|
+
}
|