@tbox.cn/app-toolkit 0.3.0 → 0.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.
Files changed (41) hide show
  1. package/dist/chunk-WUULQDOV.js +1 -0
  2. package/dist/contracts-resolver-PS2UH2G6.js +1 -0
  3. package/dist/index.d.ts +382 -129
  4. package/dist/index.js +9 -8
  5. package/package.json +2 -2
  6. package/src/assembly/provider-catalog.ts +1 -1
  7. package/src/assembly/resolve-key.ts +75 -0
  8. package/src/assembly/walk-manifests.ts +216 -96
  9. package/src/core/contracts-expected.ts +2 -2
  10. package/src/core/contracts-resolver.ts +12 -1
  11. package/src/core/env-expansion.ts +14 -12
  12. package/src/core/module-schema.ts +88 -12
  13. package/src/dto.ts +53 -25
  14. package/src/factory.ts +62 -18
  15. package/src/index.ts +18 -7
  16. package/src/integrations/evaluate-app.ts +41 -0
  17. package/src/integrations/mock-bindings.ts +1 -1
  18. package/src/integrations/module-binding.ts +60 -0
  19. package/src/integrations/predicate-io.ts +26 -5
  20. package/src/integrations/predicate.ts +97 -57
  21. package/src/integrations/prune-bindings.ts +205 -0
  22. package/src/integrations/write.ts +43 -27
  23. package/src/views/app.ts +8 -4
  24. package/src/views/context.ts +26 -7
  25. package/src/views/integration-schemas.ts +61 -17
  26. package/src/views/modules.ts +45 -27
  27. package/src/views/providers.ts +13 -1
  28. package/src/views/service-detail.ts +5 -3
  29. package/src/views/service-resolutions.ts +14 -13
  30. package/tests/credentials-view.test.ts +2 -2
  31. package/tests/demo-app.ts +34 -5
  32. package/tests/dev-manifest-catalog.test.ts +86 -6
  33. package/tests/file-cache.test.ts +5 -5
  34. package/tests/module-schema.test.ts +117 -16
  35. package/tests/resolve-key.test.ts +154 -0
  36. package/tests/vendor-schema-keywords.test.ts +86 -0
  37. package/tests/views.test.ts +425 -139
  38. package/tests/write-core.test.ts +83 -0
  39. package/dist/chunk-KPD3LUH4.js +0 -1
  40. package/dist/contracts-resolver-E4SECBOG.js +0 -1
  41. package/src/integrations/domain-binding.ts +0 -81
@@ -0,0 +1,41 @@
1
+ import type { ResolvedContracts } from '../core/contracts-resolver.js';
2
+ import { walkManifests } from '../assembly/walk-manifests.js';
3
+ import { loadProviderCatalog } from '../assembly/provider-catalog.js';
4
+ import { buildPredicateAssembly } from './predicate-io.js';
5
+ import {
6
+ evaluateIntegrationServices,
7
+ type IntegrationServiceIssue,
8
+ type PredicateContracts,
9
+ type SupplyLookup,
10
+ } from './predicate.js';
11
+
12
+ /**
13
+ * 应用级 integrations 求值(cli-remove-prune-and-new D2.4):doctor integration-services 规则内
14
+ * "装配+求值"序列的单源抽取(FX-2b/F1 轨迹延续——此前 buildPredicateAssembly 已单源,本函数
15
+ * 将 walk→supplies→assembly→evaluate 全序列收口)。
16
+ *
17
+ * 消费方:
18
+ * - doctor integration-services 规则(外壳保留:stale 防线/文件读取/env 展开/形状守卫/分级投射)
19
+ * - pruneIntegrationBindings(prune 判据 ≡ doctor 判据的构造性同源载体)
20
+ *
21
+ * 契约:调用方须先 `resolveContractsForApp` 并确保 `strictValidation`(本函数不再分级门控——
22
+ * doctor 的 warning/error 升级指引与 prune 的 skip 语义分属两消费方,不收口)。
23
+ */
24
+ export async function evaluateAppIntegrationIssues(
25
+ appDir: string,
26
+ integrations: Record<string, unknown>,
27
+ contracts: ResolvedContracts,
28
+ env: Readonly<Record<string, string | undefined>> = process.env,
29
+ ): Promise<IntegrationServiceIssue[]> {
30
+ const module = contracts.module as PredicateContracts;
31
+ const walk = walkManifests(appDir);
32
+ const supplies: SupplyLookup = module.buildSupplyLookup(loadProviderCatalog(appDir) as never);
33
+ const assembly = buildPredicateAssembly(appDir, walk, integrations, env);
34
+
35
+ return evaluateIntegrationServices({
36
+ contracts: module,
37
+ integrations: integrations as never,
38
+ supplies,
39
+ ...assembly,
40
+ });
41
+ }
@@ -29,7 +29,7 @@ export function ensureMockBindings(appDir: string): MockBindingsResult {
29
29
  }
30
30
  const slots: Array<{ service: string; provider: string; implementation: string }> = [];
31
31
  for (const mod of walkManifests(appDir).modules) {
32
- for (const slot of mod.descriptor.contributes.providers?.slots ?? []) {
32
+ for (const slot of mod.descriptor.declare.providers?.slots ?? []) {
33
33
  if (slot.local !== true) continue;
34
34
  slots.push({ service: slot.service, provider: slot.provider, implementation: slot.implementation });
35
35
  }
@@ -0,0 +1,60 @@
1
+ import { walkManifests, type WalkCatalog, type WalkManifestsResult, type WalkModuleInfo } from '../assembly/walk-manifests.js';
2
+
3
+ /**
4
+ * 模块级联绑定单源(integrations v6 所有权模型——原 domain-binding.ts v4.4 F1/F3 重写)。
5
+ * integrations 层落位依据:谓词(同层)/ 写核(同层)/ views(下层可向上 import)三向消费
6
+ * 的唯一公共下层;import 前缀 guard 覆盖。
7
+ *
8
+ * 知识锚(module-tab note 单源锚表,v6 形态):
9
+ * 模块级联(值 + 层) = resolveModuleBinding —— 谓词 #7(per-module 生效厂商 ≡
10
+ * `modules[m].provider ?? root.provider`)/ deriveBindingLayer 模块级段 /
11
+ * buildInheritance module 段 / binding(单数)
12
+ * 归属表 = walk.ownership(单源聚合产物;buildOwnershipMap 桥直通——
13
+ * registry 显式参 / 谓词 / 写守卫 / views 四消费方零第二实现)
14
+ * 来源层四层推导 = deriveBindingLayer(views/service-resolutions,模块级段改调本文件)
15
+ * 写通道可用性 = moduleOwnershipWritable —— configEditable 读标志 / applyModule 400 守卫
16
+ * 共用(owns>0 唯一判据——多域归属合法化、共享键/多域判据随 D34 退役)
17
+ */
18
+
19
+ /** 归属表构建(walk 单源直通——sdk 桥 re-export 消费;appDir 直入对齐 loadProviderCatalog
20
+ * 桥工效——模板装配第六行:`ownerOf: buildOwnershipMap(appDir)`)。 */
21
+ export function buildOwnershipMap(appDir: string): Record<string, string> {
22
+ return walkManifests(appDir).ownership;
23
+ }
24
+
25
+ /** 模块级联绑定解析(值 + 来源层):provider 取非空字符串(模块级优先 → root 兜底;空串 = 缺席)。
26
+ * layer:modules[moduleId].provider 声明 → 'module';root 兜底 → 'app';双缺 → null(binding 双缺诚实态)。 */
27
+ export function resolveModuleBinding(
28
+ config: Record<string, unknown> | null,
29
+ moduleId: string,
30
+ ): { provider: string | null; layer: 'module' | 'app' | null } {
31
+ const moduleProvider = (config?.modules as Record<string, { provider?: unknown }> | undefined)?.[moduleId]?.provider;
32
+ if (typeof moduleProvider === 'string' && moduleProvider.length > 0) {
33
+ return { provider: moduleProvider, layer: 'module' };
34
+ }
35
+ const rootProvider = config?.provider;
36
+ if (typeof rootProvider === 'string' && rootProvider.length > 0) {
37
+ return { provider: rootProvider, layer: 'app' };
38
+ }
39
+ return { provider: null, layer: null };
40
+ }
41
+
42
+ /** 模块集成位可写性(v6 判别联合——configEditable 读标志与 applyModule 写守卫共用单源):
43
+ * owns>0 唯一判据(D34 多域/shared-key 判据随 v6 退役——服务归属唯一真源 = declare.owns)。 */
44
+ export type ModuleOwnershipWritability =
45
+ | { writable: true; ownedServices: string[] }
46
+ | { writable: false; reason: 'zero-ownership'; ownedServices: [] };
47
+
48
+ export function moduleOwnershipWritable(
49
+ _walk: Pick<WalkManifestsResult, 'modules' | 'catalog' | 'ownership'>,
50
+ mod: WalkModuleInfo,
51
+ ): ModuleOwnershipWritability {
52
+ const ownedServices = (mod.descriptor.declare.owns ?? []).map((o) => o.service).sort();
53
+ if (ownedServices.length === 0) return { writable: false, reason: 'zero-ownership', ownedServices: [] };
54
+ return { writable: true, ownedServices };
55
+ }
56
+
57
+ /** 供给足迹(该厂商全部供给服务并集——应用级 integrationSchemas 作用域,D32) */
58
+ export function providerFootprint(catalog: WalkCatalog, provider: string): string[] {
59
+ return [...new Set(Object.values(catalog.providers[provider] ?? {}).flatMap((e) => e.services))].sort();
60
+ }
@@ -27,21 +27,26 @@ export interface PredicateIoInputs {
27
27
  credentialSchemaMissing: Record<string, boolean>;
28
28
  /** #7:厂商 → configSchema properties 键集并集 */
29
29
  vendorConfigKeys: Record<string, Set<string>>;
30
+ /** #17(G 批):厂商 → x-tbox-global 标记键集(与 vendorConfigKeys 同循环收集——零新增 IO) */
31
+ vendorGlobalConfigKeys: Record<string, Set<string>>;
30
32
  /** #7:厂商 → configSchema 引用缺失(跳过该厂商死配置判定) */
31
33
  vendorConfigSchemaMissing: Record<string, boolean>;
32
34
  /** #9:实现覆盖跨厂商提示文案(已成形——谓词直投) */
33
35
  realmSwitchHints: string[];
34
36
  /** #8:root instances 注册表 ids(在场才传;缺席 → 键检查跳过) */
35
37
  malls?: string[];
36
- /** #15-#16:已装模块 id → manifest contributes.resources 声明 id 集 */
38
+ /** #15-#16:已装模块 id → manifest declare.resources 声明 id 集 */
37
39
  moduleResourceNeeds: Record<string, ReadonlySet<string>>;
38
40
  }
39
41
 
40
- /** 装配全产物(= PredicateIoInputs + 供给/需求聚合——喂 evaluateIntegrationServices 的
42
+ /** 装配全产物(= PredicateIoInputs + 供给/需求/归属聚合——喂 evaluateIntegrationServices 的
41
43
  * 非 contracts/integrations 入参全集,两消费方 spread 直投,零各自组装) */
42
44
  export interface PredicateAssembly extends PredicateIoInputs {
43
45
  suppliedProviders: Record<string, { services: string[]; owner: string; credentialType?: string }>;
44
46
  demandedServices: Record<string, { optional: boolean; owners: string[] }>;
47
+ /** v6:服务归属表 + 已装模块 id 集(MODULE_CONFIG_DEAD / #12 ownerOf 透传) */
48
+ ownership?: Record<string, string>;
49
+ installedModuleIds?: ReadonlySet<string>;
45
50
  }
46
51
 
47
52
  /** stem → env 键(kebab → UPPER_SNAKE;与 SDK/provider-secret env 兜底同公式) */
@@ -95,19 +100,30 @@ export function buildPredicateAssembly(
95
100
  demandedServices[service] = { optional: entry.optional, owners: [...entry.owners] };
96
101
  }
97
102
 
98
- // ── #6/#7 schema 档案 + #15/#16 需求面(manifest 引用 → 存在性 + 键集;双通道)──
103
+ // ── #6/#7/#17 schema 档案 + #15/#16 需求面(manifest 引用 → 存在性 + 键集;双通道)──
99
104
  const vendorConfigKeys: Record<string, Set<string>> = {};
105
+ const vendorGlobalConfigKeys: Record<string, Set<string>> = {};
100
106
  const vendorConfigSchemaMissing: Record<string, boolean> = {};
101
107
  const credentialSchemaMissing: Record<string, boolean> = {};
102
108
  const moduleResourceNeeds: Record<string, ReadonlySet<string>> = {};
103
109
  for (const mod of walk.modules) {
104
- moduleResourceNeeds[mod.id] = new Set((mod.descriptor.contributes.resources ?? []).map((r) => r.id));
105
- for (const slot of mod.descriptor.contributes.providers?.slots ?? []) {
110
+ moduleResourceNeeds[mod.id] = new Set((mod.descriptor.declare.resources ?? []).map((r) => r.id));
111
+ for (const slot of mod.descriptor.declare.providers?.slots ?? []) {
106
112
  if (slot.configSchema) {
107
113
  const schema = readJsonOrNull(join(mod.dir, slot.configSchema));
108
114
  if (schema) {
109
115
  const bucket = (vendorConfigKeys[slot.provider] ??= new Set<string>());
116
+ const globalBucket = (vendorGlobalConfigKeys[slot.provider] ??= new Set<string>());
110
117
  for (const k of collectPropertyKeys(schema)) bucket.add(k);
118
+ // G 批:x-tbox-global 标记键同循环收集(properties[k]['x-tbox-global'] === true)
119
+ const props = schema.properties;
120
+ if (props && typeof props === 'object' && !Array.isArray(props)) {
121
+ for (const [k, v] of Object.entries(props as Record<string, unknown>)) {
122
+ if (v && typeof v === 'object' && (v as Record<string, unknown>)['x-tbox-global'] === true) {
123
+ globalBucket.add(k);
124
+ }
125
+ }
126
+ }
111
127
  } else {
112
128
  vendorConfigSchemaMissing[slot.provider] = true;
113
129
  }
@@ -191,9 +207,14 @@ export function buildPredicateAssembly(
191
207
  credentialFileType,
192
208
  credentialSchemaMissing,
193
209
  vendorConfigKeys,
210
+ vendorGlobalConfigKeys,
194
211
  vendorConfigSchemaMissing,
195
212
  realmSwitchHints,
196
213
  moduleResourceNeeds,
197
214
  ...(mallIds.length > 0 ? { malls: mallIds } : {}),
215
+ // v6:归属面(MODULE_CONFIG_DEAD 判据 + #12 ownerOf 透传——walk 单源直取;
216
+ // installed 并入归属方——dev 通道模块 manifest id ∉ walk.modules,聚合宽/声明窄 N3)
217
+ ownership: walk.ownership,
218
+ installedModuleIds: new Set([...walk.modules.map((m) => m.id), ...new Set(Object.values(walk.ownership))]),
198
219
  };
199
220
  }
@@ -15,19 +15,24 @@
15
15
  * IMPL_WITHOUT_PROVIDER #5/#13 error impl-only(implementation 缺 provider 绑定组头)
16
16
  * INLINE_PROVIDER_INVALID #5 error 内联厂商声明超受控形状(槽级/实例级)
17
17
  * SCHEMA_FILE_MISSING #6 error credentialType schema 引用不存在
18
- * DOMAIN_CONFIG_INVALID #7 error root/domains 死配置(键 ∉ 引用厂商 schema properties;
18
+ * DOMAIN_CONFIG_INVALID #7 error root/modules 死配置(键 ∉ 引用厂商 schema properties;
19
19
  * api.md DOMAIN_CONFIG_UNUSED 行预留 warning 级,
20
20
  * 现行为 error 恒等旧谓词——doctor 门控等价)
21
21
  * INSTANCE_KEY_NOT_REGISTERED #8 error ByInstance 键/实例键 ∉ 注册表白名单
22
22
  * REALM_SWITCH_HINT #9 warning 实现覆盖跨厂商(凭据域切换确认)
23
23
  * WILDCARD_ALIGNMENT #10 warning 通配形态齐步提示
24
24
  * MULTI_INSTANCE_NO_DEFAULT v4 warning 多实例无 defaultInstanceId(无 scope 访问落全局会话)
25
- * VENDOR_NOT_INSTALLED #11 error root/domains 厂商未命中已装供给
25
+ * VENDOR_NOT_INSTALLED #11 error root/modules 厂商未命中已装供给
26
26
  * BINDING_UNRESOLVED #12 error 绑定不可解析(resolveEffectiveService 求值失败)
27
- * DOMAIN_KEY_UNMATCHED #14 warning domains 键 ∉ 匹配域集(typo)
27
+ * IMPL_WITHOUT_PROVIDER #5/#13 error impl-only(implementation 缺 provider 绑定组头)
28
28
  * RESOURCE_TYPE_UNKNOWN #15 error modules 资源节 type ∉ 词汇表
29
29
  * RESOURCE_NOT_DECLARED #16 warning 资源键/资源 id ∉ 模块声明集
30
+ * MODULE_CONFIG_DEAD v6 error modules[m](未安装 ∨ owns=0)携带 provider/config
31
+ * (统一吸收原「#16 升级」特例——P2 一码一义)
32
+ * GLOBAL_FIELD_UNDECLARED #17 warning 全局层键过 #7 但 ∉ 引用厂商 x-tbox-global 并集(G 批;
33
+ * 零声明厂商整体跳过——#7 同遍历双判定,P4)
30
34
  * ORPHANED_CREDENTIAL 写后对账 warning 凭据文件不再被引用(toolkit 写路径产出,非本谓词)
35
+ * (v6 退役:DOMAIN_KEY_UNMATCHED #14——domains 键随归属模型退场)
31
36
  *
32
37
  * 输入形状(调用方负责装载):
33
38
  * - integrations:config/integrations.json 解析产物(宽松 Record 透传——严格 zod 校验归模板 loader)
@@ -44,28 +49,31 @@
44
49
  * 继承绑定(槽级 provider 缺席)经 effective 求值取厂商——supplies 在场时)
45
50
  * #5 实例 impl-only 禁止 + 覆盖命中供给(含槽级内联形状;provider-only = 派生位,合法)
46
51
  * #6 manifest configSchema/credentialSchema 引用文件存在性(内联与 manifest 绑定同查)
47
- * #7 root/domains 死配置:root.config/configByInstance/domains[d].config 键 ⊆ 引用厂商(并集)
48
- * 各槽 schema properties——引用面 = root.provider ∪ domains.*.provider ∪ 槽级(terse 化防退化)
52
+ * #7 root/modules 死配置:root.config/configByInstance/modules[m].config 键 ⊆ 引用厂商(并集)
53
+ * 各槽 schema properties——引用面 = root.provider ∪ modules.*.provider ∪ 槽级(terse 化防退化);
54
+ * modules 层 per-module 生效厂商 = resolveModuleBinding 单源(≡ modules[m].provider ?? root.provider)
49
55
  * #8 ByInstance/实例键 ∈ malls 白名单(v4:源 = root instances 注册表 ids——参考表在场时
50
- * 激活;'**' 通配键豁免;注册表缺席 → 跳过;domains 键 = 域前缀非 instanceId,不适用)
56
+ * 激活;'**' 通配键豁免;注册表缺席 → 跳过;modules 键 = install id 非 instanceId,不适用)
51
57
  * #9 实现覆盖跨厂商提示(realm switch——凭据域切换确认)
52
- * #11 root/domains 厂商未命中已装供给(vendor 集合 = suppliedProviders 键前缀展开)
58
+ * #11 root/modules 厂商未命中已装供给(vendor 集合 = suppliedProviders 键前缀展开)
53
59
  * #12 绑定可解析性:逐 (service, instanceId) 经 contracts resolveEffectiveService 求值
54
- * (级联单源复用,谓词零级联实现),binding-unresolved → error(supplies 缺席 → 跳过)
60
+ * (级联单源复用,谓词零级联实现;v6 ownerOf=ownership 显式参透传),binding-unresolved → error
55
61
  * #13 槽级/实例级 impl-only(implementation 需伴随 provider——provider 是绑定组头;
56
62
  * zod 之外的第二道防线——doctor 读裸 JSON)
57
- * #14 domains 键 ∈ 匹配域集(已配置槽 ∪ 需求声明的 moduleDomainOf 并集;typo → warning)
58
- * #15-#16 modules 资源节对账(v5):type ∉ RESOURCE_TYPE_IDS → error(裸 JSON 预启动反馈);
59
- * 键 ∉ 已装模块 id 集 / 资源 id ∉ 模块 manifest contributes.resources 声明集(声明非空时)
60
- * → warning(moduleResourceNeeds 缺席 → 需求面检查跳过,M3 降级先例)
63
+ * MODULE_CONFIG_DEAD:modules[m] 携带 provider/config 且(m ∉ installedModuleIds ∨ ownership
64
+ * 无任何 service 归属 m)→ error(键级 #16 warning 与本检查可同节点共发——键级 vs 值级不同断言面)
65
+ * #17(G 批):与 #7 同遍历双判定(P4)——键 ∈ schema properties 但 ∉ x-tbox-global 并集 →
66
+ * warning;引用厂商零全局声明(集空/缺席)→ 该厂商键整体跳过;检查序在 MODULE_CONFIG_DEAD 后
67
+ * #15-#16 modules 资源节对账(v5 延续):type ∉ RESOURCE_TYPE_IDS → error;键 ∉ 已装模块 id 集 /
68
+ * 资源 id ∉ 模块 manifest declare.resources 声明集 → warning
61
69
  */
62
70
  import type { EvaluationExports } from '../core/contracts-expected.js';
63
- import { resolveDomainBinding } from './domain-binding.js';
71
+ import { resolveModuleBinding } from './module-binding.js';
64
72
 
65
73
  /** contracts 注入面(resolver 装配产物运行时视图 = EvaluationExports 求值子集) */
66
74
  export type PredicateContracts = Pick<
67
75
  EvaluationExports,
68
- 'resolveEffectiveService' | 'buildSupplyLookup' | 'moduleDomainOf' | 'normalizeInstances' | 'RESOURCE_TYPE_IDS'
76
+ 'resolveEffectiveService' | 'buildSupplyLookup' | 'normalizeInstances' | 'RESOURCE_TYPE_IDS'
69
77
  >;
70
78
 
71
79
  /** 派生查表形状(contracts buildSupplyLookup 产物;结构等价 TIER0 SupplyLookup——谓词零 contracts 静态依赖) */
@@ -74,7 +82,6 @@ export type SupplyLookup = Record<string, readonly string[]>;
74
82
  export interface IntegrationServicePredicateInput {
75
83
  integrations: {
76
84
  provider?: string;
77
- domains?: Record<string, { provider?: string; config?: Record<string, unknown> }>;
78
85
  config?: Record<string, unknown>;
79
86
  configByInstance?: Record<string, Record<string, unknown>>;
80
87
  credentialRef?: string;
@@ -93,8 +100,8 @@ export interface IntegrationServicePredicateInput {
93
100
  instances?: Array<{ id: string; name?: string; attributes?: Record<string, unknown> }>;
94
101
  /** v4 默认实例(normalizeInstances 解析语义:显式 ?? 单候选唯一解) */
95
102
  defaultInstanceId?: string | null;
96
- /** v5 模块资源绑定(modules 节——宽松透传;严格 zod 归模板 loader) */
97
- modules?: Record<string, { resources?: Record<string, { type?: unknown }> }>;
103
+ /** v6 模块集成位(modules 节三键——宽松透传;严格 zod 归模板 loader) */
104
+ modules?: Record<string, { provider?: string; config?: Record<string, unknown>; resources?: Record<string, { type?: unknown }> }>;
98
105
  };
99
106
  /** 已声明供给(`${vendor}\u0000${implementation}` → 槽集合;credentialType 供 #2/#6 绑定侧对齐) */
100
107
  suppliedProviders: Record<string, { services: string[]; owner: string; credentialType?: string }>;
@@ -102,12 +109,20 @@ export interface IntegrationServicePredicateInput {
102
109
  demandedServices: Record<string, { optional: boolean; owners: string[] }>;
103
110
  /** 派生查表(buildSupplyLookup 产物;缺席 → #12 跳过 + #2 继承绑定不查 credentialType) */
104
111
  supplies?: SupplyLookup;
112
+ /** v6 服务归属表(walk.ownership——#12 ownerOf 透传 + MODULE_CONFIG_DEAD owns=0 判据;
113
+ * 缺席 = 归属面不可判 → MODULE_CONFIG_DEAD 退化为仅「未安装」分支) */
114
+ ownership?: Record<string, string>;
115
+ /** v6 已装模块 id 集(.tbox/app.json entry.id ∪ packages/ 目录名;缺席 → 「未安装」分支跳过) */
116
+ installedModuleIds?: ReadonlySet<string>;
105
117
  /** malls 白名单(deployments 控制面 extract;缺席 → ByInstance 键检查跳过) */
106
118
  malls?: string[];
107
119
  /** credentialType → schema 文件引用缺失(manifest 声明指向缺失文件;#6) */
108
120
  credentialSchemaMissing?: Record<string, boolean>;
109
121
  /** 厂商 → 槽 schema properties 键集(#7 用;调用方从 manifest configSchema 引用解析) */
110
122
  vendorConfigKeys?: Record<string, Set<string>>;
123
+ /** G 批:厂商 → x-tbox-global 标记键集(#17 用;#7 同循环收集——零新增 IO);
124
+ * 缺席/空集 = 该厂商零声明 → #17 对其键整体跳过 */
125
+ vendorGlobalConfigKeys?: Record<string, Set<string>>;
111
126
  /** 厂商 → configSchema 引用文件缺失(#7 跳过该厂商——无 schema 不能判死配置) */
112
127
  vendorConfigSchemaMissing?: Record<string, boolean>;
113
128
  /** 凭据文件缺失(#1:credentialRef stem → 档内 credentials/<stem>.json 在场;域中立注入) */
@@ -116,7 +131,7 @@ export interface IntegrationServicePredicateInput {
116
131
  credentialFileType?: Record<string, string | undefined>;
117
132
  /** #9:实现覆盖跨厂商提示位(CLI 侧组装) */
118
133
  realmSwitchHints?: string[];
119
- /** v5:已装模块 id → manifest contributes.resources 声明 id 集(值空集 = 无声明,资源级跳过;
134
+ /** v5:已装模块 id → manifest declare.resources 声明 id 集(值空集 = 无声明,资源级跳过;
120
135
  * 缺席 = 无需求面信息——键未知/资源未声明检查跳过,type 检查恒跑) */
121
136
  moduleResourceNeeds?: Record<string, ReadonlySet<string>>;
122
137
  }
@@ -189,7 +204,7 @@ export function evaluateIntegrationServices(
189
204
  ): IntegrationServiceIssue[] {
190
205
  const issues: IntegrationServiceIssue[] = [];
191
206
  const contracts = input.contracts;
192
- const { integrations, suppliedProviders, demandedServices, supplies, malls, credentialSchemaMissing, vendorConfigKeys, vendorConfigSchemaMissing, credentialFileMissing, credentialFileType, realmSwitchHints, moduleResourceNeeds } = input;
207
+ const { integrations, suppliedProviders, demandedServices, supplies, malls, credentialSchemaMissing, vendorConfigKeys, vendorGlobalConfigKeys, vendorConfigSchemaMissing, credentialFileMissing, credentialFileType, realmSwitchHints, moduleResourceNeeds, ownership, installedModuleIds } = input;
193
208
 
194
209
  // #8 白名单索引(malls 缺席 → 跳过 ByInstance 键检查)
195
210
  const mallsIndex = malls ? new Set(malls) : undefined;
@@ -230,10 +245,11 @@ export function evaluateIntegrationServices(
230
245
  }
231
246
  const ct = (service.provider as Record<string, unknown> | undefined)?.credentialType;
232
247
  if (typeof ct === 'string') return ct;
233
- // v3 继承绑定:effective 求值取 (provider, implementation)(supplies 在场才可判)
248
+ // v3 继承绑定:effective 求值取 (provider, implementation)(supplies 在场才可判;
249
+ // v6 ownerOf=ownership 显式参——modules 层绑定纳入求值)
234
250
  if (!supplies) return undefined;
235
251
  const sk = instanceId ?? Object.keys(service.instances ?? {})[0] ?? '';
236
- const r = contracts.resolveEffectiveService(integrations as never, serviceId, sk, supplies);
252
+ const r = contracts.resolveEffectiveService(integrations as never, serviceId, sk, supplies, ownership);
237
253
  if (r.status !== 'ok' || typeof r.effective.provider !== 'string') return undefined;
238
254
  return suppliedProviders[providerKey(r.effective.provider, r.effective.implementation)]?.credentialType;
239
255
  };
@@ -415,18 +431,18 @@ export function evaluateIntegrationServices(
415
431
  }
416
432
  }
417
433
 
418
- // #11:root/domains 厂商存在性(继承层声明必须命中已装供给——vendor 集合 = 供给键前缀展开;
434
+ // #11:root/modules 厂商存在性(继承层声明必须命中已装供给——vendor 集合 = 供给键前缀展开;
419
435
  // 槽级内联声明不在本检查范围——F5 先声明后实施闭环归 #2/#6 形状检查)
420
436
  for (const [where, vendor] of [
421
437
  ['root', integrations.provider],
422
- ...Object.entries(integrations.domains ?? {}).map(([d, cfg]): [string, string | undefined] => [`domains.${d}`, cfg?.provider]),
438
+ ...Object.entries(integrations.modules ?? {}).map(([m, cfg]): [string, string | undefined] => [`modules.${m}`, cfg?.provider]),
423
439
  ] as Array<[string, string | undefined]>) {
424
440
  if (typeof vendor !== 'string' || vendor.length === 0) continue;
425
441
  if (!installedVendors.has(vendor)) {
426
442
  issues.push({
427
443
  severity: 'error',
428
444
  code: 'VENDOR_NOT_INSTALLED',
429
- module: where.startsWith('domains.') ? where.slice('domains.'.length) : undefined,
445
+ module: where.startsWith('modules.') ? where.slice('modules.'.length) : undefined,
430
446
  message: `integrations: ${where} provider ${vendor} 未命中已装供给——装对应 provider 包或 provider use 改绑`,
431
447
  });
432
448
  }
@@ -434,12 +450,13 @@ export function evaluateIntegrationServices(
434
450
 
435
451
  // #12:绑定可解析性(级联单源复用 contracts resolveEffectiveService——谓词零级联实现;
436
452
  // supplies 缺席 → 整体跳过,M3 降级兼容 F5 页面无 catalog 形态。
437
- // v4:instances 键缺席槽 = 通配激活——以 '' 探测求值(继承链可解析性仍需覆盖))
453
+ // v4:instances 键缺席槽 = 通配激活——以 '' 探测求值(继承链可解析性仍需覆盖);
454
+ // v6:ownership 作 ownerOf 显式参透传——modules 层绑定/配置纳入求值与 fail-visible)
438
455
  if (supplies) {
439
456
  for (const [serviceId, service] of Object.entries(integrations.services)) {
440
457
  const instanceIds = service.instances ? Object.keys(service.instances) : [''];
441
458
  for (const instanceId of instanceIds) {
442
- const r = contracts.resolveEffectiveService(integrations as never, serviceId, instanceId, supplies);
459
+ const r = contracts.resolveEffectiveService(integrations as never, serviceId, instanceId, supplies, ownership);
443
460
  if (r.status === 'binding-unresolved') {
444
461
  issues.push({ severity: 'error', code: 'BINDING_UNRESOLVED', service: serviceId, instance: instanceId || undefined, message: `integrations: ${r.message}` });
445
462
  }
@@ -459,30 +476,40 @@ export function evaluateIntegrationServices(
459
476
  }
460
477
  }
461
478
 
462
- // #14:domains 键 ∈ 匹配域集(已配置槽 ∪ 需求声明的 moduleDomainOf 并集;
463
- // typo 域 = 永不生效的静默死配置 → warning 级,staged 域(先行声明)不告警)
464
- const matchedDomains = new Set<string>([
465
- ...Object.keys(integrations.services).map((s) => contracts.moduleDomainOf(s)),
466
- ...Object.keys(demandedServices).map((s) => contracts.moduleDomainOf(s)),
467
- ]);
468
- for (const d of Object.keys(integrations.domains ?? {})) {
469
- if (!matchedDomains.has(d)) {
479
+ // MODULE_CONFIG_DEAD(v6):modules[m] 携带 provider/config 且(未安装 ∨ owns=0)→ error。
480
+ // 统一定义吸收原「#16 升级」特例(P2 一码一义);与 #16 可同节点共发(键级 vs 值级不同断言面)。
481
+ // 依赖面双保险:installedModuleIds/ownership 缺席 → 对应分支跳过(谓词纯函数——面信息由调用方喂)。
482
+ for (const [moduleId, cfg] of Object.entries(integrations.modules ?? {})) {
483
+ const carriesBinding = cfg?.provider !== undefined || cfg?.config !== undefined;
484
+ if (!carriesBinding) continue;
485
+ const notInstalled = installedModuleIds ? !installedModuleIds.has(moduleId) : false;
486
+ let ownsCount = 0;
487
+ if (ownership) {
488
+ for (const owner of Object.values(ownership)) if (owner === moduleId) ownsCount += 1;
489
+ }
490
+ const ownsUnknown = ownership === undefined;
491
+ if (notInstalled || (!ownsUnknown && ownsCount === 0)) {
492
+ const why = notInstalled ? '未安装(键 = install id)' : '未声明任何 owns(无模块级配置位)';
470
493
  issues.push({
471
- severity: 'warning',
472
- code: 'DOMAIN_KEY_UNMATCHED',
473
- module: d,
474
- message: `integrations: domains.${d} 无对应槽位或需求声明(域前缀拼写错误?——键 = 槽位 id 点分前缀)`,
494
+ severity: 'error',
495
+ code: 'MODULE_CONFIG_DEAD',
496
+ module: moduleId,
497
+ message: `integrations: modules.${moduleId} 携带 provider/config 但模块${why}(MODULE_CONFIG_DEAD——模块级集成位作用于 declare.owns 服务,改 declare.owns 或删除该节点)`,
475
498
  });
476
499
  }
477
500
  }
478
501
 
479
- // #7:root/domains 死配置 —— root.config/configByInstance/domains[d].config 键 ⊆ 引用厂商
502
+ // #7:root/modules 死配置 —— root.config/configByInstance/modules[m].config 键 ⊆ 引用厂商
480
503
  // (跨厂商取并集)各槽 schema properties 键集。并集语义:混布部署中属 A 厂商的 root 键
481
- // 不应被 B 厂商判死。引用面(v3)= root.provider ∪ domains.*.provider ∪ 槽级(含内联
482
- // .provider)——terse 化后槽级双字段退场,引用面不上移即静默漏检(N2)。
504
+ // 不应被 B 厂商判死。引用面(v3 延续 + v6 面)= root.provider ∪ modules.*.provider ∪
505
+ // 槽级(含内联 .provider)——terse 化后槽级双字段退场,引用面不上移即静默漏检(N2)。
483
506
  // 保守跳过条件(无法确证即不判死——误报会阻断部署):任一引用厂商 configSchema 文件缺失,
484
507
  // 或被绑定但零 configSchema 声明(如 local 供给未声明 configSchema——无 schema 知识。
485
- // 注:provider-mock 0.3.0 起全槽带 schema,mock 绑定下本检查已生效)。
508
+ // 注:provider-mock 0.5.0 起零 configSchema(保守跳过);config 检查对 mocktest 绑定
509
+ // 生效(全槽 schema——2026-09-20 拆分迁入 provider-mocktest)。
510
+ // #17(G 批,与 #7 同遍历双判定 P4):键 ∈ schema properties 但 ∉ 该层生效厂商
511
+ // x-tbox-global 并集 → GLOBAL_FIELD_UNDECLARED warning;生效厂商零声明(集空/缺席)→
512
+ // 整体跳过(零声明厂商不告警)。检查序:#7 判定先行,过 #7 才进 #17(N5)。
486
513
  if (vendorConfigKeys) {
487
514
  const referenced = new Set<string>();
488
515
  let schemaIncomplete = false;
@@ -494,7 +521,7 @@ export function evaluateIntegrationServices(
494
521
  if (vendorConfigSchemaMissing?.[vendor]) schemaIncomplete = true;
495
522
  };
496
523
  noteVendor(integrations.provider);
497
- for (const cfg of Object.values(integrations.domains ?? {})) noteVendor(cfg?.provider);
524
+ for (const cfg of Object.values(integrations.modules ?? {})) noteVendor(cfg?.provider);
498
525
  for (const slot of Object.values(integrations.services)) {
499
526
  const inline = slot.provider as Record<string, unknown> | undefined;
500
527
  noteVendor(typeof slot.provider === 'string' ? slot.provider : inline?.provider as string | undefined);
@@ -502,7 +529,10 @@ export function evaluateIntegrationServices(
502
529
  if (referenced.size > 0 && !schemaIncomplete) {
503
530
  const allowed = new Set<string>();
504
531
  for (const v of referenced) for (const k of vendorConfigKeys[v] ?? []) allowed.add(k);
505
- const checkLayer = (layer: Record<string, unknown> | undefined, where: string, allowedKeys?: Set<string>): void => {
532
+ // 全局并集(root 层):引用厂商 x-tbox-global 键并集;空 = 全体零声明 → root 层 #17 跳过
533
+ const globalUnion = new Set<string>();
534
+ for (const v of referenced) for (const k of vendorGlobalConfigKeys?.[v] ?? []) globalUnion.add(k);
535
+ const checkLayer = (layer: Record<string, unknown> | undefined, where: string, allowedKeys?: Set<string>, globalKeys?: Set<string>): void => {
506
536
  for (const [k] of Object.entries(layer ?? {})) {
507
537
  if (!(allowedKeys ?? allowed).has(k)) {
508
538
  issues.push({
@@ -510,24 +540,34 @@ export function evaluateIntegrationServices(
510
540
  code: 'DOMAIN_CONFIG_INVALID',
511
541
  message: `integrations: ${where} 键 ${k} 不在引用厂商(${[...referenced].join('/')})任何槽 schema properties 内(死配置)`,
512
542
  });
543
+ } else if (globalKeys && globalKeys.size > 0 && !globalKeys.has(k)) {
544
+ // #17:过 #7 但未声明全局——仅表单收窄提示(已配置值全链保全,解析不拒)
545
+ issues.push({
546
+ severity: 'warning',
547
+ code: 'GLOBAL_FIELD_UNDECLARED',
548
+ message: `integrations: ${where} 键 ${k} 过 schema 校验但未声明 x-tbox-global(不进应用/模块全局表单;已配置值照常生效——如需全局编辑请在 provider schema 标记)`,
549
+ });
513
550
  }
514
551
  }
515
552
  };
516
- checkLayer(integrations.config, 'root.config');
517
- for (const [k, v] of Object.entries(integrations.configByInstance ?? {})) checkLayer(v, `root.configByInstance[${k}]`);
518
- // 域级 config:键 ⊆ 该域生效厂商 schema(resolveDomainBinding 单源——v4.4 F1 等价重构,
519
- // 语义 ≡ 现状 domains[d].provider ?? root.provider)——生效厂商缺席(全走槽级覆盖)→
520
- // 用全局并集;生效厂商 schema 缺失 → 跳过该域
521
- for (const [d, cfg] of Object.entries(integrations.domains ?? {})) {
553
+ checkLayer(integrations.config, 'root.config', undefined, globalUnion);
554
+ for (const [k, v] of Object.entries(integrations.configByInstance ?? {})) {
555
+ checkLayer(v, `root.configByInstance[${k}]`, undefined, globalUnion);
556
+ }
557
+ // 模块层 config:键 ⊆ 该模块生效厂商 schema(resolveModuleBinding 单源——v6 语义
558
+ // ≡ modules[m].provider ?? root.provider)——生效厂商缺席(全走槽级覆盖)→
559
+ // 用全局并集;生效厂商 schema 缺失 → 跳过该模块
560
+ for (const [m, cfg] of Object.entries(integrations.modules ?? {})) {
522
561
  if (!cfg?.config) continue;
523
- const domainVendor = resolveDomainBinding(integrations, d).provider ?? undefined;
524
- if (domainVendor === undefined) {
525
- checkLayer(cfg.config, `domains.${d}.config`);
562
+ const moduleVendor = resolveModuleBinding(integrations as never, m).provider ?? undefined;
563
+ if (moduleVendor === undefined) {
564
+ checkLayer(cfg.config, `modules.${m}.config`, undefined, globalUnion);
526
565
  continue;
527
566
  }
528
- const vendorKeys = vendorConfigKeys[domainVendor];
529
- if (!vendorKeys || vendorConfigSchemaMissing?.[domainVendor]) continue;
530
- checkLayer(cfg.config, `domains.${d}.config`, new Set(vendorKeys));
567
+ const vendorKeys = vendorConfigKeys[moduleVendor];
568
+ if (!vendorKeys || vendorConfigSchemaMissing?.[moduleVendor]) continue;
569
+ const vendorGlobals = vendorGlobalConfigKeys?.[moduleVendor];
570
+ checkLayer(cfg.config, `modules.${m}.config`, new Set(vendorKeys), vendorGlobals);
531
571
  }
532
572
  }
533
573
  }
@@ -585,7 +625,7 @@ export function evaluateIntegrationServices(
585
625
  severity: 'warning',
586
626
  code: 'RESOURCE_NOT_DECLARED',
587
627
  module: moduleId,
588
- message: `integrations: modules.${moduleId}.resources.${resourceId} 未被模块 manifest contributes.resources 声明`,
628
+ message: `integrations: modules.${moduleId}.resources.${resourceId} 未被模块 manifest declare.resources 声明`,
589
629
  });
590
630
  }
591
631
  }