@tbox.cn/app-toolkit 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{chunk-VV3ERZXS.js → chunk-KPD3LUH4.js} +1 -1
- package/dist/contracts-resolver-E4SECBOG.js +1 -0
- package/dist/index.d.ts +234 -43
- package/dist/index.js +8 -8
- package/package.json +2 -2
- package/src/assembly/app-manifest.ts +6 -0
- package/src/assembly/provider-catalog.ts +6 -1
- package/src/assembly/walk-manifests.ts +2 -1
- package/src/core/contracts-resolver.ts +3 -2
- package/src/core/credential-format.ts +4 -2
- package/src/core/errors.ts +9 -3
- package/src/dto.ts +127 -15
- package/src/factory.ts +129 -16
- package/src/index.ts +15 -4
- package/src/integrations/credentials.ts +1 -1
- package/src/integrations/domain-binding.ts +81 -0
- package/src/integrations/predicate.ts +7 -6
- package/src/integrations/read.ts +23 -3
- package/src/integrations/write.ts +93 -34
- package/src/views/app.ts +7 -5
- package/src/views/context.ts +3 -6
- package/src/views/credential-types.ts +28 -0
- package/src/views/credentials.ts +57 -0
- package/src/views/integration-schemas.ts +226 -0
- package/src/views/modules.ts +33 -14
- package/src/views/providers.ts +37 -22
- package/src/views/service-detail.ts +3 -4
- package/src/views/service-resolutions.ts +31 -25
- package/tests/credentials-view.test.ts +152 -0
- package/tests/demo-app.ts +14 -1
- package/tests/file-cache.test.ts +3 -1
- package/tests/naming-alignment.test.ts +8 -5
- package/tests/vendor-schema-keywords.test.ts +101 -0
- package/tests/views.test.ts +1285 -13
- package/tests/write-core.test.ts +107 -8
- package/dist/contracts-resolver-QM4FBQQV.js +0 -1
package/src/dto.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* DTO 全量(api.md §3 字段级单源落源——
|
|
2
|
+
* DTO 全量(api.md §3 字段级单源落源——36 类型;演进 additive-only,铁律 #8)。
|
|
3
3
|
* agtcodingbox / tbox-ai-coding 经 `import type` 消费(L2 DTO 单源——API 层零类型镜像)。
|
|
4
4
|
* 词汇七概念:app / module / service / instance / provider / integration / resolution。
|
|
5
5
|
* 弃用词汇清单见 api.md §3 尾(component/slot/scope/configMode/…——防复活)。
|
|
@@ -20,13 +20,15 @@ export type BindingLayer = 'app' | 'module' | 'service' | 'instance';
|
|
|
20
20
|
|
|
21
21
|
// ===== 需求轴(GET /modules · 静态)=====
|
|
22
22
|
|
|
23
|
-
/** 纯模块清单(无 services——卡片展开走 /modules/:m
|
|
23
|
+
/** 纯模块清单(无 services——卡片展开走 /modules/:m);provider 家族包不在清单
|
|
24
|
+
* (供给/实施载体非可配置业务模块——供给轴归 /providers;判定 = install id / 包名
|
|
25
|
+
* basename 去 scope 后 `provider-` 前缀;已装 provider 详情 GET /modules/:m 仍 200) */
|
|
24
26
|
export interface ModulesView {
|
|
25
27
|
modules: ModuleDeclaration[];
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export interface ModuleDeclaration {
|
|
29
|
-
/** install id(packages/ 目录名 / .tbox/app.json entry.id
|
|
31
|
+
/** install id(packages/ 目录名 / .tbox/app.json entry.id)——恒业务模块(provider 家族不在本清单) */
|
|
30
32
|
id: string;
|
|
31
33
|
/** 包名 */
|
|
32
34
|
name: string;
|
|
@@ -34,13 +36,13 @@ export interface ModuleDeclaration {
|
|
|
34
36
|
description: string;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
/** 服务需求声明(模块 demand
|
|
39
|
+
/** 服务需求声明(模块 demand 单元 / 平台服务条目——v4.4 D29 双端点同型;defaults 唯一落位 ServiceDetail) */
|
|
38
40
|
export interface ServiceDemand {
|
|
39
41
|
service: string;
|
|
40
42
|
/** manifest demand meta;缺席 UI 回退 service id */
|
|
41
43
|
title?: string;
|
|
42
44
|
description?: string;
|
|
43
|
-
/** demand optional
|
|
45
|
+
/** demand optional === false(多模块 OR 聚合见 resolution echo;平台服务条目恒 false) */
|
|
44
46
|
required: boolean;
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -48,18 +50,78 @@ export interface ServiceDemand {
|
|
|
48
50
|
|
|
49
51
|
/** GET /modules/:module(模块静态一站式 · PUT 读对偶) */
|
|
50
52
|
export interface ModuleDetailView {
|
|
51
|
-
/**
|
|
53
|
+
/** v4.4 D34:demand 服务首段去重(排序;1..N;零域 = []) */
|
|
54
|
+
domainKeys: string[];
|
|
55
|
+
/** domains[换算键] 节点原值;未配置 = null;v4.4 D34:仅单域(writable)时在场——多域/零域/共享键无单键落点 */
|
|
52
56
|
integration: ModuleIntegrationNode | null;
|
|
57
|
+
/** v4.4 D34:每域一条厂商绑定解析(单域 = 单元素;零域 = [];resolveDomainBinding 单源) */
|
|
58
|
+
providerBindings: ProviderBinding[];
|
|
59
|
+
/** v4.4 D34:单域 && 域键无跨模块共享(moduleDomainWritable 单源——读标志与写守卫共用) */
|
|
60
|
+
configEditable: boolean;
|
|
53
61
|
/** 本模块 demand(卡片展开 + 编辑器静态上下文自足) */
|
|
54
62
|
services: ServiceDemand[];
|
|
63
|
+
/** v4.4 D31:include=serviceResolutions 内嵌(demand 服务过滤 ≡ 批量 ?service= 语义;<0.9/形态异常 → null——永不 503) */
|
|
64
|
+
serviceResolutions?: ServiceResolutionsView | null;
|
|
65
|
+
/** v4.4 D32:include=integrationSchemas 层级富化(多域/零域/共享键/未绑定 → null) */
|
|
66
|
+
integrationSchemas?: LayerIntegrationSchemas | null;
|
|
67
|
+
/** v4.6 D36:include=integrationFieldRegistry 集成字段注册表(null 语义镜像 integrationSchemas;
|
|
68
|
+
* 未知厂商/零足迹 → 非 null 空集 + unsupplied 回填——D37 轴参假想计算同此) */
|
|
69
|
+
integrationFieldRegistry?: IntegrationFieldRegistryView | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 模块域厂商绑定解析(v4.4 D34——静态文件事实;规则 ≡ 谓词 #7 域级,resolveDomainBinding 单源) */
|
|
73
|
+
export interface ProviderBinding {
|
|
74
|
+
/** 域键(= domainKeys 成员;多域模块每域一条) */
|
|
75
|
+
domain: string;
|
|
76
|
+
/** domains[domain].provider ?? root.provider(双缺 = null) */
|
|
77
|
+
effective: string | null;
|
|
78
|
+
/** 生效来源层(双缺 = null) */
|
|
79
|
+
layer: 'module' | 'app' | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 层级 config schema 计算(v4.4 D32——静态轴富化,零 contracts) */
|
|
83
|
+
export interface LayerIntegrationSchemas {
|
|
84
|
+
/** 层生效厂商(resolveDomainBinding 单源) */
|
|
85
|
+
provider: string;
|
|
86
|
+
/** 模块级 = 本模块 demand ∩ 该厂商供给;应用级 = 供给足迹全量(configSchema = raw draft 2020-12 原文;供给在场未声明 = null) */
|
|
87
|
+
services: Record<string, { configSchema: object | null }>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** v4.6 D36:集成字段注册表(层级表单服务端预计算——合并代数 R1/R2/R3 服务端单源;
|
|
91
|
+
* 消费 resolveLayerIntegrationSchemas 产物构造,禁旁路直取 serviceSchemas) */
|
|
92
|
+
export interface IntegrationFieldRegistryView {
|
|
93
|
+
/** echo:本注册表计算口径(假想时 = 轴参值——D37) */
|
|
94
|
+
provider: string;
|
|
95
|
+
/** 作用域内 configSchema === null 的服务(UI「未提供参数模板」提示行) */
|
|
96
|
+
schemaless: string[];
|
|
97
|
+
/** module 轴 = demand − 该厂商供给(Set 去重保序);app 轴恒 [](结构性:足迹 ⊆ 供给) */
|
|
98
|
+
unsupplied: string[];
|
|
99
|
+
/** 作用域内全部非 null schema 的 additionalProperties 均 ≠ false(聚合;空真) */
|
|
100
|
+
additionalPropertiesAllowed: boolean;
|
|
101
|
+
/** 逐 key 合并产物(首声明插入序 = scope 遍历序:app 字母序 / module demand 序) */
|
|
102
|
+
fields: Array<{
|
|
103
|
+
key: string;
|
|
104
|
+
/** null = 类型不可调和(R3/R2 异构/R1 enum 不等——冲突折叠,客户端 RawJson 保真) */
|
|
105
|
+
schema: object | null;
|
|
106
|
+
/** required 聚合 = sources.some(s => s.required);conflict 字段照常聚合 */
|
|
107
|
+
sources: Array<{ service: string; required: boolean }>;
|
|
108
|
+
}>;
|
|
55
109
|
}
|
|
56
110
|
|
|
57
111
|
/** GET /app(应用静态视图:平台服务 + root 原值) */
|
|
58
112
|
export interface AppView {
|
|
59
|
-
/**
|
|
60
|
-
|
|
113
|
+
/** 平台服务(v4.4 D29:服务词汇 − 模块占用——与模块 demand 同型 ServiceDemand[];
|
|
114
|
+
* title/description 缺席 = UI 回退 service id;required 恒 false——平台服务无 demand 源) */
|
|
115
|
+
services: ServiceDemand[];
|
|
61
116
|
/** root 节点原值;文件缺席 = null */
|
|
62
117
|
integration: AppIntegrationNode | null;
|
|
118
|
+
/** v4.4 D31:include=serviceResolutions 内嵌(全量键集 ≡ 批量无参;<0.9/形态异常 → null——永不 503) */
|
|
119
|
+
serviceResolutions?: ServiceResolutionsView | null;
|
|
120
|
+
/** v4.4 D32:include=integrationSchemas 层级富化(root.provider 缺席 → null) */
|
|
121
|
+
integrationSchemas?: LayerIntegrationSchemas | null;
|
|
122
|
+
/** v4.6 D36:include=integrationFieldRegistry 集成字段注册表(root.provider 缺席且无轴参 → null;
|
|
123
|
+
* 未知厂商/零足迹 → 非 null 空集——D37 轴参假想计算同此) */
|
|
124
|
+
integrationFieldRegistry?: IntegrationFieldRegistryView | null;
|
|
63
125
|
}
|
|
64
126
|
|
|
65
127
|
/** GET /services/:service(服务静态单体) */
|
|
@@ -70,6 +132,18 @@ export interface ServiceDetail extends ServiceDemand {
|
|
|
70
132
|
integration: ServiceIntegrationNode | null;
|
|
71
133
|
/** 供给该服务的厂商名单(catalog 静态切片——picker 数据源) */
|
|
72
134
|
suppliedBy: string[];
|
|
135
|
+
/** v4.7 D38:include=integrationSchemas 生效绑定富化(三态:null 全链未绑定 /
|
|
136
|
+
* 绑定无可计算 schema / 含 schema;malformed 节点 → null——不放行域级联) */
|
|
137
|
+
integrationSchemas?: ServiceIntegrationSchemas | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** 服务级生效绑定 schema(v4.7 D38 静态轴富化——零 contracts,永不 503;实例层无条件排除:
|
|
141
|
+
* node 级口径 ≠ resolution 默认实例口径,api.md §4.4 注) */
|
|
142
|
+
export interface ServiceIntegrationSchemas {
|
|
143
|
+
/** 生效厂商:显式(含内联声明名)?? domains[首段].provider ?? root.provider */
|
|
144
|
+
provider: string;
|
|
145
|
+
/** catalog 复合键(多 impl 声明序首个)> 内联 ref;miss(未知厂商/未供给/内联缺 ref)→ null(富化非门控) */
|
|
146
|
+
configSchema: object | null;
|
|
73
147
|
}
|
|
74
148
|
|
|
75
149
|
// ===== 求值轴 =====
|
|
@@ -91,6 +165,9 @@ export interface ServiceResolutionEntry {
|
|
|
91
165
|
statusMessage?: string;
|
|
92
166
|
/** 最特异声明层厂商名(跨层事实) */
|
|
93
167
|
provider?: string;
|
|
168
|
+
/** v4.4:provider 来源声明层(默认实例口径,与 status 同轴);恒 = provider 字段来源层
|
|
169
|
+
* (ok/binding-unresolved 取声明层;未配置双缺席);≡ 单体 inheritance 链逆序首个有 provider 的层 */
|
|
170
|
+
layer?: BindingLayer;
|
|
94
171
|
/** provider 在场时在场:(provider, service) ∈ 供给关系 */
|
|
95
172
|
supplied?: boolean;
|
|
96
173
|
/** owner 模块 install id;平台服务/孤儿缺席 */
|
|
@@ -194,8 +271,7 @@ export interface ResolvedInstance {
|
|
|
194
271
|
statusMessage?: string;
|
|
195
272
|
/** 该实例合并结果 */
|
|
196
273
|
effective?: EffectiveBinding;
|
|
197
|
-
|
|
198
|
-
credential?: CredentialEcho;
|
|
274
|
+
// v4.4 D33:credential(掩码回显)删净——凭据状态归 GET /credentials(求值路径零凭据文件 I/O)
|
|
199
275
|
}
|
|
200
276
|
|
|
201
277
|
// ===== 供给轴(静态)=====
|
|
@@ -222,7 +298,11 @@ export interface ProviderDetail {
|
|
|
222
298
|
* 多元素 = 升级窗口,UI 出 impl 选择器,PUT 显式钉版) */
|
|
223
299
|
services: Record<
|
|
224
300
|
string,
|
|
225
|
-
{
|
|
301
|
+
{
|
|
302
|
+
implementations: Array<{ implementation: string; credentialType: string }>;
|
|
303
|
+
/** v4.4 F5:include=integrationSchemas 切换态批量(纯 config——凭据结构归 /credential-types) */
|
|
304
|
+
integrationSchemas?: { configSchema: object | null };
|
|
305
|
+
}
|
|
226
306
|
>;
|
|
227
307
|
}
|
|
228
308
|
|
|
@@ -230,16 +310,47 @@ export interface ProviderDetail {
|
|
|
230
310
|
export interface ProviderServiceDetail {
|
|
231
311
|
provider: string;
|
|
232
312
|
service: string;
|
|
233
|
-
/** ★ 恒数组(通常 1
|
|
313
|
+
/** ★ 恒数组(通常 1);credentialType 保留(/credential-types 寻址键) */
|
|
234
314
|
implementations: Array<{ implementation: string; credentialType: string }>;
|
|
235
|
-
/** 厂商集成该服务的表单 schema(integration
|
|
315
|
+
/** 厂商集成该服务的表单 schema(integration 域专属)——v4.4 D33 纯 config(credentialSchema 删净) */
|
|
236
316
|
integrationSchemas: {
|
|
237
317
|
/** raw draft 2020-12 原文零转换;供给在场未声明 = null(mock 合法);解析自默认 impl */
|
|
238
318
|
configSchema: object | null;
|
|
239
|
-
credentialSchema: object | null;
|
|
240
319
|
};
|
|
241
320
|
}
|
|
242
321
|
|
|
322
|
+
// ===== 凭据域(v4.4 D33 独立端点)=====
|
|
323
|
+
|
|
324
|
+
/** GET /credentials:绑定位全量掩码回显(应用状态轴;GET 永无真值) */
|
|
325
|
+
export interface CredentialsView {
|
|
326
|
+
/** root 位;byInstance 键集 = 注册表 ids ∩ 有 ref(无 ref 的注册实例不出现;未注册 ref 跳过不虚报) */
|
|
327
|
+
app: CredentialBindingEcho;
|
|
328
|
+
/** 键集 = 已配置服务;byInstance 键集 = 节点 instances map 全键(含 '*')——键域不对称 = 文件语义忠实镜像 */
|
|
329
|
+
services: Record<string, CredentialBindingEcho>;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** 一个形状两处(镜像文件层「标量 + ByInstance」模式) */
|
|
333
|
+
export interface CredentialBindingEcho {
|
|
334
|
+
/** 本位 credentialRef(root.credentialRef / services[s].credentialRef);ref 缺席/文件缺失 → null */
|
|
335
|
+
credential: CredentialEcho | null;
|
|
336
|
+
/** 覆盖位(credentialRefByInstance / instances[k].credentialRef) */
|
|
337
|
+
byInstance: Record<string, CredentialEcho | null>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** GET /credential-types[?provider=]:凭据结构清单(供给轴静态;?provider= 过滤语义 ≡ ?service=) */
|
|
341
|
+
export interface CredentialTypesView {
|
|
342
|
+
types: CredentialTypeEntry[];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface CredentialTypeEntry {
|
|
346
|
+
/** '-' = 无凭据语义(mock/local——UI 判 '-' 不渲染凭据表单) */
|
|
347
|
+
credentialType: string;
|
|
348
|
+
/** 声明来源包名 */
|
|
349
|
+
owner: string;
|
|
350
|
+
/** raw draft 2020-12 原文;未声明 = null(ExtraFields 自由键值同轨) */
|
|
351
|
+
credentialSchema: object | null;
|
|
352
|
+
}
|
|
353
|
+
|
|
243
354
|
// ===== 通用 =====
|
|
244
355
|
|
|
245
356
|
export interface EffectiveBinding {
|
|
@@ -289,7 +400,8 @@ export interface SaveResult {
|
|
|
289
400
|
written: boolean;
|
|
290
401
|
/** 恒 false 当 dryRun / no-diff / 应用未运行(onApplied 协作点回填) */
|
|
291
402
|
restartScheduled: boolean;
|
|
292
|
-
/**
|
|
403
|
+
/** 触碰清单;dryRun = 将触碰预览(D35——与同 body 真实写逐字对称,含凭据工件路径;
|
|
404
|
+
* no-diff 早退 = 预览零触碰) */
|
|
293
405
|
files: string[];
|
|
294
406
|
/** 凭据工件(0..n;dryRun = 将创建的工件——确定性 stem 派生预览) */
|
|
295
407
|
credentials: Array<{ stem: string; ref: string; overwrote: boolean }>;
|
package/src/factory.ts
CHANGED
|
@@ -19,10 +19,15 @@ 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
21
|
import { loadServiceResolutionsView, loadServiceResolutionDetail } from './views/service-resolutions.js';
|
|
22
|
+
import { loadAppFormViews, loadModuleFormViews, loadServiceIntegrationSchemas } from './views/integration-schemas.js';
|
|
23
|
+
import { loadCredentialsView } from './views/credentials.js';
|
|
24
|
+
import { loadCredentialTypesView } from './views/credential-types.js';
|
|
22
25
|
import type { WalkManifestsResult } from './assembly/walk-manifests.js';
|
|
23
26
|
import type {
|
|
24
27
|
AppIntegrationNode,
|
|
25
28
|
AppView,
|
|
29
|
+
CredentialTypesView,
|
|
30
|
+
CredentialsView,
|
|
26
31
|
ModuleDetailView,
|
|
27
32
|
ModuleIntegrationNode,
|
|
28
33
|
ProviderDetail,
|
|
@@ -36,15 +41,19 @@ import type {
|
|
|
36
41
|
} from './dto.js';
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
|
-
* createAppToolkit(C4
|
|
44
|
+
* createAppToolkit(C4 工厂;v4.4——24 方法 = 11 读(含 credentials/credential-types 双端点)
|
|
45
|
+
* + 4 写 + 9 迁移面;loadApp/loadModule/loadProvider 增 include 选项;v4.6——loadApp/loadModule
|
|
46
|
+
* += 轴向假想绑定选项(appProvider/moduleProvider,D37);表单双投影经 formViews 单点装配;
|
|
47
|
+
* v4.7——loadService 增 include 选项(服务级生效绑定 schema 单体))。
|
|
40
48
|
* AppToolkit = AppToolkitRead & AppToolkitWrite & AppToolkitInternals——
|
|
41
49
|
* Read+Write = HTTP 直通面 additive 冻结(L1 端点↔方法 1:1,L3 命名镜像:Integration=节点级 /
|
|
42
50
|
* Config=文件级);Internals = CLI/doctor/桥消费(自由演进)。
|
|
43
51
|
*
|
|
44
52
|
* **门控断言单点(policy at edge)**:求值(2)+ 写(4)+ ensureMockBindings / writeIntegrationsConfig /
|
|
45
53
|
* evaluateIntegrationServices 入口 assertContractsUsable——strictValidation === false → 503
|
|
46
|
-
* CONTRACTS_NOT_RESOLVED
|
|
47
|
-
*
|
|
54
|
+
* CONTRACTS_NOT_RESOLVED;静态方法不门控(永不 503——铁律 #3);**include 装配为条件分支非门控**
|
|
55
|
+
* (D31——serviceResolutions 按 evaluation && strictValidation 双条件装配,<0.9/形态异常 → null,
|
|
56
|
+
* 静态端点永不 503;integrationSchemas 纯静态轴零 contracts);integrations/ views/ 机制层保持纯函数。
|
|
48
57
|
*
|
|
49
58
|
* 并发:进程内写串行(per-factory promise chain)+ 原子写 + 写后本实例缓存即刻失效(D26)。
|
|
50
59
|
* onApplied(D7):toolkit 判定「需要重启」(实际落盘且非 dryRun/no-diff)→ 调用一次 +
|
|
@@ -56,17 +65,71 @@ export interface AppToolkitOptions {
|
|
|
56
65
|
onApplied?: () => void;
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
/**
|
|
68
|
+
/** include 协议白名单(v4.4 D30/P15——键名 = 产出字段名 1:1;per 端点;注册表 additive-only;
|
|
69
|
+
* v4.6 D36 += integrationFieldRegistry:app/module 三键,provider 轴不加——YAGNI;
|
|
70
|
+
* v4.7 D38 += service 键——服务级生效绑定 schema) */
|
|
71
|
+
const INCLUDE_WHITELIST = {
|
|
72
|
+
app: ['serviceResolutions', 'integrationSchemas', 'integrationFieldRegistry'],
|
|
73
|
+
module: ['serviceResolutions', 'integrationSchemas', 'integrationFieldRegistry'],
|
|
74
|
+
provider: ['integrationSchemas'],
|
|
75
|
+
service: ['integrationSchemas'],
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
/** include 校验(P15 单点在 toolkit 方法内——壳层仅解析可重复参数,零校验纪律不变):
|
|
79
|
+
* 未知/越权键 → 400 VALIDATION_FAILED fields ['include'];重复键静默去重。 */
|
|
80
|
+
function validateInclude(kind: keyof typeof INCLUDE_WHITELIST, include?: string[]): Set<string> {
|
|
81
|
+
if (!include || include.length === 0) return new Set();
|
|
82
|
+
const allowed = INCLUDE_WHITELIST[kind] as readonly string[];
|
|
83
|
+
const rejected = [...new Set(include)].filter((k) => !allowed.includes(k));
|
|
84
|
+
if (rejected.length > 0) {
|
|
85
|
+
throw new AppToolkitError(
|
|
86
|
+
'VALIDATION_FAILED',
|
|
87
|
+
400,
|
|
88
|
+
`include 含未知或不适用于本端点的键:${rejected.join('、')}(白名单:${allowed.join('、')})`,
|
|
89
|
+
['include'],
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return new Set(include);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 表单层 include 键(D37 轴参严检依据——serviceResolutions 不算) */
|
|
96
|
+
const FORM_INCLUDE_KEYS = ['integrationSchemas', 'integrationFieldRegistry'] as const;
|
|
97
|
+
|
|
98
|
+
/** v4.6 D37 轴参严检(400 先于 404——镜像输入校验先行):非空轴参需与表单 include 键同现;
|
|
99
|
+
* 缺席/空串 ≡ 按实际配置解析(I3——不触发严检);错轴参数由壳层按名读取,天然静默忽略。 */
|
|
100
|
+
function assertAxisProviderUsable(
|
|
101
|
+
paramName: 'appProvider' | 'moduleProvider',
|
|
102
|
+
value: string | undefined,
|
|
103
|
+
include: ReadonlySet<string>,
|
|
104
|
+
): void {
|
|
105
|
+
if (typeof value !== 'string' || value.length === 0) return;
|
|
106
|
+
if (!FORM_INCLUDE_KEYS.some((k) => include.has(k))) {
|
|
107
|
+
throw new AppToolkitError(
|
|
108
|
+
'VALIDATION_FAILED',
|
|
109
|
+
400,
|
|
110
|
+
`参数 ${paramName} 需与表单 include 键(${FORM_INCLUDE_KEYS.join('、')})同现——轴参仅重定向表单层双投影`,
|
|
111
|
+
[paramName],
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** ── HTTP 直通面(11 读;L1 镜像律:方法名 = load + URL 资源段;query → 选项字段 L4)── */
|
|
60
117
|
export interface AppToolkitRead {
|
|
61
118
|
loadModules(): Promise<ModulesView>;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
119
|
+
/** v4.6 D37:moduleProvider = 轴向假想绑定(缺席/空串 ≡ 按实际配置解析;需与表单 include 键同现) */
|
|
120
|
+
loadModule(moduleId: string, o?: { include?: string[]; moduleProvider?: string }): Promise<ModuleDetailView>;
|
|
121
|
+
/** v4.6 D37:appProvider = 轴向假想绑定(缺席/空串 ≡ 按实际配置解析;需与表单 include 键同现) */
|
|
122
|
+
loadApp(o?: { include?: string[]; appProvider?: string }): Promise<AppView>;
|
|
123
|
+
loadService(service: string, o?: { include?: string[] }): Promise<ServiceDetail>;
|
|
65
124
|
loadServiceResolutions(services?: string[]): Promise<ServiceResolutionsView>;
|
|
66
125
|
loadServiceResolution(service: string): Promise<ServiceResolutionDetail>;
|
|
67
126
|
loadProviders(): Promise<ProvidersView>;
|
|
68
|
-
loadProvider(provider: string): Promise<ProviderDetail>;
|
|
127
|
+
loadProvider(provider: string, o?: { include?: string[] }): Promise<ProviderDetail>;
|
|
69
128
|
loadProviderService(provider: string, service: string): Promise<ProviderServiceDetail>;
|
|
129
|
+
/** GET /credentials(v4.4 D33——应用状态轴;零门控,永不 503) */
|
|
130
|
+
loadCredentials(): Promise<CredentialsView>;
|
|
131
|
+
/** GET /credential-types[?provider=](v4.4 D33——供给轴静态;零门控,永不 503) */
|
|
132
|
+
loadCredentialTypes(provider?: string): Promise<CredentialTypesView>;
|
|
70
133
|
}
|
|
71
134
|
|
|
72
135
|
/** ── HTTP 直通面(4 写;body ≡ 文件节点全量替换;DELETE 无 dryRun/无 options)── */
|
|
@@ -163,17 +226,57 @@ export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}
|
|
|
163
226
|
const contracts = await resolveContractsForApp(appDir);
|
|
164
227
|
return loadModulesView(snapshot(contracts));
|
|
165
228
|
},
|
|
166
|
-
|
|
229
|
+
// v4.4 D31:门控与内嵌分离——静态方法零门控分支;include=serviceResolutions 按
|
|
230
|
+
// evaluation && strictValidation 双条件装配(<0.9/形态异常 → null,永不 503——技术方案 §2.4:
|
|
231
|
+
// 仅判 strictValidation 会被「严格面在场+求值面缺席」畸形包击穿)
|
|
232
|
+
async loadModule(moduleId: string, o?: { include?: string[]; moduleProvider?: string }): Promise<ModuleDetailView> {
|
|
233
|
+
const include = validateInclude('module', o?.include); // ① 输入校验先行(400 先于 404)
|
|
234
|
+
assertAxisProviderUsable('moduleProvider', o?.moduleProvider, include); // ①' D37 轴参严检(先于 404)
|
|
167
235
|
const contracts = await resolveContractsForApp(appDir);
|
|
168
|
-
|
|
236
|
+
const snap = snapshot(contracts);
|
|
237
|
+
const view = loadModuleDetailView(snap, moduleId); // ② MODULE_NOT_FOUND
|
|
238
|
+
if (include.has('serviceResolutions')) {
|
|
239
|
+
view.serviceResolutions = contracts.evaluation && contracts.strictValidation
|
|
240
|
+
? await predicateIssues().then((issues) =>
|
|
241
|
+
loadServiceResolutionsView(snap, issues, view.services.map((s) => s.service)))
|
|
242
|
+
: null; // module = demand 服务过滤(≡ 批量 ?service= 语义;issues 恒全量)
|
|
243
|
+
}
|
|
244
|
+
if (include.has('integrationSchemas') || include.has('integrationFieldRegistry')) {
|
|
245
|
+
// v4.6 D37:表单层双投影同源一次计算(双取单算 + I1——假想重定向天然同步)
|
|
246
|
+
const form = loadModuleFormViews(snap, moduleId, o?.moduleProvider);
|
|
247
|
+
if (include.has('integrationSchemas')) view.integrationSchemas = form.integrationSchemas;
|
|
248
|
+
if (include.has('integrationFieldRegistry')) view.integrationFieldRegistry = form.integrationFieldRegistry;
|
|
249
|
+
}
|
|
250
|
+
return view;
|
|
169
251
|
},
|
|
170
|
-
async loadApp() {
|
|
252
|
+
async loadApp(o?: { include?: string[]; appProvider?: string }): Promise<AppView> {
|
|
253
|
+
const include = validateInclude('app', o?.include);
|
|
254
|
+
assertAxisProviderUsable('appProvider', o?.appProvider, include); // D37 轴参严检(先于任何计算)
|
|
171
255
|
const contracts = await resolveContractsForApp(appDir);
|
|
172
|
-
|
|
256
|
+
const snap = snapshot(contracts);
|
|
257
|
+
const view = loadAppView(snap);
|
|
258
|
+
if (include.has('serviceResolutions')) {
|
|
259
|
+
view.serviceResolutions = contracts.evaluation && contracts.strictValidation
|
|
260
|
+
? await predicateIssues().then((issues) => loadServiceResolutionsView(snap, issues)) // app = 全量键集
|
|
261
|
+
: null;
|
|
262
|
+
}
|
|
263
|
+
if (include.has('integrationSchemas') || include.has('integrationFieldRegistry')) {
|
|
264
|
+
const form = loadAppFormViews(snap, o?.appProvider); // 双取单算 + I1(同 loadModule)
|
|
265
|
+
if (include.has('integrationSchemas')) view.integrationSchemas = form.integrationSchemas;
|
|
266
|
+
if (include.has('integrationFieldRegistry')) view.integrationFieldRegistry = form.integrationFieldRegistry;
|
|
267
|
+
}
|
|
268
|
+
return view;
|
|
173
269
|
},
|
|
174
|
-
|
|
270
|
+
// v4.7 D38:include 镜像 loadModule 三段式——① 输入校验 400 先于 404 ② 视图 ③ 静态富化(永不 503)
|
|
271
|
+
async loadService(service: string, o?: { include?: string[] }): Promise<ServiceDetail> {
|
|
272
|
+
const include = validateInclude('service', o?.include);
|
|
175
273
|
const contracts = await resolveContractsForApp(appDir);
|
|
176
|
-
|
|
274
|
+
const snap = snapshot(contracts); // 单快照——detail 与 include 装配共享
|
|
275
|
+
const view = loadServiceDetailView(snap, service);
|
|
276
|
+
if (include.has('integrationSchemas')) {
|
|
277
|
+
view.integrationSchemas = loadServiceIntegrationSchemas(snap, service);
|
|
278
|
+
}
|
|
279
|
+
return view;
|
|
177
280
|
},
|
|
178
281
|
async loadServiceResolutions(services?: string[]) {
|
|
179
282
|
const contracts = await resolveContractsForApp(appDir);
|
|
@@ -191,14 +294,24 @@ export function createAppToolkit(appDir: string, options: AppToolkitOptions = {}
|
|
|
191
294
|
const contracts = await resolveContractsForApp(appDir);
|
|
192
295
|
return loadProvidersView(snapshot(contracts));
|
|
193
296
|
},
|
|
194
|
-
async loadProvider(provider: string) {
|
|
297
|
+
async loadProvider(provider: string, o?: { include?: string[] }): Promise<ProviderDetail> {
|
|
298
|
+
const include = validateInclude('provider', o?.include);
|
|
195
299
|
const contracts = await resolveContractsForApp(appDir);
|
|
196
|
-
return loadProviderDetailView(snapshot(contracts), provider);
|
|
300
|
+
return loadProviderDetailView(snapshot(contracts), provider, include);
|
|
197
301
|
},
|
|
198
302
|
async loadProviderService(provider: string, service: string) {
|
|
199
303
|
const contracts = await resolveContractsForApp(appDir);
|
|
200
304
|
return loadProviderServiceDetailView(snapshot(contracts), provider, service);
|
|
201
305
|
},
|
|
306
|
+
// v4.4 D33:凭据域双端点(应用状态轴 / 供给轴静态——零门控零求值,永不 503)
|
|
307
|
+
async loadCredentials(): Promise<CredentialsView> {
|
|
308
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
309
|
+
return loadCredentialsView(snapshot(contracts));
|
|
310
|
+
},
|
|
311
|
+
async loadCredentialTypes(provider?: string): Promise<CredentialTypesView> {
|
|
312
|
+
const contracts = await resolveContractsForApp(appDir);
|
|
313
|
+
return loadCredentialTypesView(snapshot(contracts), provider);
|
|
314
|
+
},
|
|
202
315
|
};
|
|
203
316
|
|
|
204
317
|
const core = createWriteCore(appDir, {
|
package/src/index.ts
CHANGED
|
@@ -79,19 +79,22 @@ export type { AppliedCredential, CredentialInput, OrphanIssue, PlannedCredential
|
|
|
79
79
|
export { AppToolkitError, contractsNotResolvedMessage } from './core/errors.js';
|
|
80
80
|
export type { AppToolkitErrorCode } from './core/errors.js';
|
|
81
81
|
|
|
82
|
-
// ── views
|
|
83
|
-
export { loadModulesView, loadModuleDetailView
|
|
82
|
+
// ── views(静态四轴 + 求值轴 + v4.4 层级 schema/凭据域;铁律 #3 静态/求值分离由投影结构承载)──
|
|
83
|
+
export { loadModulesView, loadModuleDetailView } from './views/modules.js';
|
|
84
84
|
export { loadAppView } from './views/app.js';
|
|
85
85
|
export { loadServiceDetailView } from './views/service-detail.js';
|
|
86
86
|
export { loadProvidersView, loadProviderDetailView, loadProviderServiceDetailView } from './views/providers.js';
|
|
87
87
|
export { loadServiceResolutionsView, loadServiceResolutionDetail, resolveServiceSummary } from './views/service-resolutions.js';
|
|
88
|
+
export { loadAppIntegrationSchemas, loadModuleIntegrationSchemas, loadServiceIntegrationSchemas, resolveLayerIntegrationSchemas } from './views/integration-schemas.js';
|
|
89
|
+
export { echoForRef, loadCredentialsView } from './views/credentials.js';
|
|
90
|
+
export { loadCredentialTypesView } from './views/credential-types.js';
|
|
88
91
|
export type { ViewContext, ViewSnapshot } from './views/context.js';
|
|
89
92
|
|
|
90
|
-
// ── 工厂(
|
|
93
|
+
// ── 工厂(v4.4:24 方法 = 11 读 + 4 写 + 9 Internals;门控 policy at edge;include 条件装配非门控)──
|
|
91
94
|
export { createAppToolkit } from './factory.js';
|
|
92
95
|
export type { AppToolkit, AppToolkitRead, AppToolkitWrite, AppToolkitInternals, AppToolkitOptions } from './factory.js';
|
|
93
96
|
|
|
94
|
-
// ── DTO(api.md §3 字段级单源——
|
|
97
|
+
// ── DTO(api.md §3 字段级单源——36 类型;agtcodingbox/tbox-ai-coding import type 消费)──
|
|
95
98
|
export type {
|
|
96
99
|
ServiceStatus,
|
|
97
100
|
BindingLayer,
|
|
@@ -101,6 +104,14 @@ export type {
|
|
|
101
104
|
ModuleDetailView,
|
|
102
105
|
AppView,
|
|
103
106
|
ServiceDetail,
|
|
107
|
+
ServiceIntegrationSchemas,
|
|
108
|
+
ProviderBinding,
|
|
109
|
+
LayerIntegrationSchemas,
|
|
110
|
+
IntegrationFieldRegistryView,
|
|
111
|
+
CredentialsView,
|
|
112
|
+
CredentialBindingEcho,
|
|
113
|
+
CredentialTypesView,
|
|
114
|
+
CredentialTypeEntry,
|
|
104
115
|
ServiceResolutionsView,
|
|
105
116
|
ServiceResolutionEntry,
|
|
106
117
|
ServiceResolution,
|
|
@@ -141,7 +141,7 @@ export function reconcileOrphanCredentials(appDir: string): OrphanIssue[] {
|
|
|
141
141
|
issues.push({
|
|
142
142
|
severity: 'warning',
|
|
143
143
|
code: 'ORPHANED_CREDENTIAL',
|
|
144
|
-
message:
|
|
144
|
+
message: `${stemToFilePath(stem)} 不再被引用(凭据文件保留,可手动清理)`,
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
return issues;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { WalkCatalog, WalkManifestsResult, WalkModuleInfo } from '../assembly/walk-manifests.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 域级联绑定单源(v4.4 F1/F3——integrations 层落位依据:谓词(同层)/ 写核(同层)/
|
|
5
|
+
* views(下层可向上 import)三向消费的唯一公共下层;import 前缀 guard 覆盖)。
|
|
6
|
+
*
|
|
7
|
+
* 知识锚(module-tab note 单源锚表):
|
|
8
|
+
* 域级联(值 + 层) = resolveDomainBinding —— 谓词 #7(等价重构,语义 ≡
|
|
9
|
+
* `domains[d].provider ?? root.provider`)/ deriveBindingLayer 域级段 /
|
|
10
|
+
* buildInheritance module 段 / ProviderBinding
|
|
11
|
+
* 来源层四层推导 = deriveBindingLayer(views/service-resolutions,域级段改调本文件)
|
|
12
|
+
* 写通道可用性 = moduleDomainWritable —— configEditable 读标志 / applyModule 400 守卫共用
|
|
13
|
+
* (D34:读标志与写守卫一函数)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** 域前缀换算(service → 首点段;自 views/context.ts 迁入——文件格式词汇,静态面零 contracts 依赖) */
|
|
17
|
+
export function domainOfService(service: string): string {
|
|
18
|
+
const i = service.indexOf('.');
|
|
19
|
+
return i === -1 ? service : service.slice(0, i);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 域级联绑定解析(值 + 来源层):provider 取非空字符串(域级优先 → root 兜底;空串 = 缺席)。
|
|
23
|
+
* layer:域级声明 → 'module';root 兜底 → 'app';双缺 → null(ProviderBinding 双缺诚实态)。 */
|
|
24
|
+
export function resolveDomainBinding(
|
|
25
|
+
config: Record<string, unknown> | null,
|
|
26
|
+
domainKey: string,
|
|
27
|
+
): { provider: string | null; layer: 'module' | 'app' | null } {
|
|
28
|
+
const domainProvider = (config?.domains as Record<string, { provider?: unknown }> | undefined)?.[domainKey]?.provider;
|
|
29
|
+
if (typeof domainProvider === 'string' && domainProvider.length > 0) {
|
|
30
|
+
return { provider: domainProvider, layer: 'module' };
|
|
31
|
+
}
|
|
32
|
+
const rootProvider = config?.provider;
|
|
33
|
+
if (typeof rootProvider === 'string' && rootProvider.length > 0) {
|
|
34
|
+
return { provider: rootProvider, layer: 'app' };
|
|
35
|
+
}
|
|
36
|
+
return { provider: null, layer: null };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 多域模块可写性(D34 判别联合——configEditable 读标志与 applyModule 写守卫共用单源) */
|
|
40
|
+
export type ModuleDomainWritability =
|
|
41
|
+
| { writable: true; domainKeys: [string]; domainKey: string }
|
|
42
|
+
| { writable: false; reason: 'zero-domain'; domainKeys: [] }
|
|
43
|
+
| { writable: false; reason: 'multi-domain'; domainKeys: string[] }
|
|
44
|
+
| { writable: false; reason: 'shared-key'; domainKeys: [string]; domainKey: string; coOwners: string[] };
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 多域模块可写性判定(v4.4 F3):domainKeys = demand 服务首段去重;
|
|
48
|
+
* shared-key 判据 = 域级 owner 并集计数(catalog.services 聚合含 dev-manifest 通道需求——
|
|
49
|
+
* P13 真源;>1 个 distinct owner ⟺ 共享。免 owner↔moduleId 身份映射——本地模块 catalog
|
|
50
|
+
* owner = entry.package ≠ mod.pkg `@app/<id>`,身份比较必踩;「他方与本模块需求同一服务」
|
|
51
|
+
* 形态亦被并集计数天然覆盖)。
|
|
52
|
+
*/
|
|
53
|
+
export function moduleDomainWritable(
|
|
54
|
+
walk: Pick<WalkManifestsResult, 'modules' | 'catalog'>,
|
|
55
|
+
mod: WalkModuleInfo,
|
|
56
|
+
): ModuleDomainWritability {
|
|
57
|
+
const domainKeys = [...new Set((mod.descriptor.contributes.services ?? []).map((s) => domainOfService(s.service)))].sort();
|
|
58
|
+
if (domainKeys.length === 0) return { writable: false, reason: 'zero-domain', domainKeys: [] };
|
|
59
|
+
if (domainKeys.length > 1) return { writable: false, reason: 'multi-domain', domainKeys };
|
|
60
|
+
const domainKey = domainKeys[0];
|
|
61
|
+
const owners = new Set<string>();
|
|
62
|
+
for (const [service, demand] of Object.entries(walk.catalog.services)) {
|
|
63
|
+
if (domainOfService(service) === domainKey) for (const o of demand.owners) owners.add(o);
|
|
64
|
+
}
|
|
65
|
+
if (owners.size > 1) {
|
|
66
|
+
return { writable: false, reason: 'shared-key', domainKeys: [domainKey], domainKey, coOwners: [...owners].sort() };
|
|
67
|
+
}
|
|
68
|
+
return { writable: true, domainKeys: [domainKey], domainKey };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** owner 串 → 显示名(尽力映射 module install id;未命中回退原始串——message 质量问题非正确性) */
|
|
72
|
+
export function displayOwnerName(modules: ReadonlyArray<{ id: string; pkg: string }>, owner: string): string {
|
|
73
|
+
const mod = modules.find((m) => m.pkg === owner || `@app/${m.id}` === owner);
|
|
74
|
+
if (mod) return mod.id;
|
|
75
|
+
return owner.startsWith('@app/') ? owner.slice('@app/'.length) : owner;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 供给足迹(该厂商全部供给服务并集——应用级 integrationSchemas 作用域,D32) */
|
|
79
|
+
export function providerFootprint(catalog: WalkCatalog, provider: string): string[] {
|
|
80
|
+
return [...new Set(Object.values(catalog.providers[provider] ?? {}).flatMap((e) => e.services))].sort();
|
|
81
|
+
}
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
* → warning(moduleResourceNeeds 缺席 → 需求面检查跳过,M3 降级先例)
|
|
61
61
|
*/
|
|
62
62
|
import type { EvaluationExports } from '../core/contracts-expected.js';
|
|
63
|
+
import { resolveDomainBinding } from './domain-binding.js';
|
|
63
64
|
|
|
64
65
|
/** contracts 注入面(resolver 装配产物运行时视图 = EvaluationExports 求值子集) */
|
|
65
66
|
export type PredicateContracts = Pick<
|
|
@@ -480,7 +481,8 @@ export function evaluateIntegrationServices(
|
|
|
480
481
|
// 不应被 B 厂商判死。引用面(v3)= root.provider ∪ domains.*.provider ∪ 槽级(含内联
|
|
481
482
|
// .provider)——terse 化后槽级双字段退场,引用面不上移即静默漏检(N2)。
|
|
482
483
|
// 保守跳过条件(无法确证即不判死——误报会阻断部署):任一引用厂商 configSchema 文件缺失,
|
|
483
|
-
// 或被绑定但零 configSchema 声明(如
|
|
484
|
+
// 或被绑定但零 configSchema 声明(如 local 供给未声明 configSchema——无 schema 知识。
|
|
485
|
+
// 注:provider-mock 0.3.0 起全槽带 schema,mock 绑定下本检查已生效)。
|
|
484
486
|
if (vendorConfigKeys) {
|
|
485
487
|
const referenced = new Set<string>();
|
|
486
488
|
let schemaIncomplete = false;
|
|
@@ -513,13 +515,12 @@ export function evaluateIntegrationServices(
|
|
|
513
515
|
};
|
|
514
516
|
checkLayer(integrations.config, 'root.config');
|
|
515
517
|
for (const [k, v] of Object.entries(integrations.configByInstance ?? {})) checkLayer(v, `root.configByInstance[${k}]`);
|
|
516
|
-
// 域级 config:键 ⊆
|
|
517
|
-
//
|
|
518
|
+
// 域级 config:键 ⊆ 该域生效厂商 schema(resolveDomainBinding 单源——v4.4 F1 等价重构,
|
|
519
|
+
// 语义 ≡ 现状 domains[d].provider ?? root.provider)——生效厂商缺席(全走槽级覆盖)→
|
|
520
|
+
// 用全局并集;生效厂商 schema 缺失 → 跳过该域
|
|
518
521
|
for (const [d, cfg] of Object.entries(integrations.domains ?? {})) {
|
|
519
522
|
if (!cfg?.config) continue;
|
|
520
|
-
const domainVendor =
|
|
521
|
-
? cfg.provider
|
|
522
|
-
: (typeof integrations.provider === 'string' ? integrations.provider : undefined);
|
|
523
|
+
const domainVendor = resolveDomainBinding(integrations, d).provider ?? undefined;
|
|
523
524
|
if (domainVendor === undefined) {
|
|
524
525
|
checkLayer(cfg.config, `domains.${d}.config`);
|
|
525
526
|
continue;
|