@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
@@ -4,8 +4,9 @@ import { mkdirSync, readFileSync, symlinkSync } from 'node:fs';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { createAppToolkit, type AppToolkit } from '../src/factory.js';
6
6
  import { invalidateContractsResolver } from '../src/core/contracts-resolver.js';
7
- import { walkManifests } from '../src/assembly/walk-manifests.js';
8
- import { buildIntegrationFieldRegistry, loadModuleFormViews, loadModuleIntegrationSchemas } from '../src/views/integration-schemas.js';
7
+ import { walkManifests, type WalkManifestsResult } from '../src/assembly/walk-manifests.js';
8
+ import { buildIntegrationFieldRegistry, loadModuleFormViews, loadModuleIntegrationSchemas, loadServiceFieldRegistry } from '../src/views/integration-schemas.js';
9
+ import { resolveServiceDisplay } from '../src/views/context.js';
9
10
  import type { ViewSnapshot } from '../src/views/context.js';
10
11
  import { URL_METHOD_MAP } from './naming-alignment.test.js';
11
12
  import { createTempApp, writeFakeContractsPackage, linkContracts, writeAppFile, type TempApp } from './demo-app.js';
@@ -41,11 +42,12 @@ function harnessWithContracts(
41
42
  'packages/module-parking/tbox.module.json',
42
43
  JSON.stringify({
43
44
  schemaVersion: 1,
44
- name: 'module-parking',
45
+ id: 'module-parking',
45
46
  version: '0.1.0',
46
47
  kind: 'business',
47
- contributes: {
48
- services: [{ service: 'parking.query', optional: false, title: '车位查询' }],
48
+ declare: {
49
+ owns: [{ service: 'parking.query' }],
50
+ consumes: [{ service: 'parking.query', optional: false, name: '车位查询' }],
49
51
  },
50
52
  dependencies: { modules: [] },
51
53
  env: [],
@@ -61,7 +63,7 @@ function harnessWithContracts(
61
63
  { id: 'mall-sh-01', name: '上海店' },
62
64
  ],
63
65
  defaultInstanceId: 'mall-bj-01',
64
- domains: { parking: { provider: 'wanda' } },
66
+ modules: { 'module-parking': { provider: 'wanda' } },
65
67
  services: {
66
68
  'parking.query': { provider: { provider: 'my-custom-api', credentialType: '-' }, implementation: 'my-custom-parking@1' },
67
69
  'auth.alipay-login': {},
@@ -103,9 +105,12 @@ function layerHarness(): {
103
105
  ].join(' '),
104
106
  });
105
107
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
108
+ // G 批:fixture 全字段标 x-tbox-global(表单收窄过滤后 E1/E2/E5 字段面逐字不变);
109
+ // wanda parking.json 另含未标记键 parkLotId(P16 负例——layer.properties 在场、registry.fields 不收)
110
+ const g = (sub: Record<string, unknown>): Record<string, unknown> => ({ ...sub, 'x-tbox-global': true });
106
111
  const schemas = {
107
- query: { type: 'object', properties: { baseUrl: { type: 'string', format: 'uri' } }, required: ['baseUrl'] },
108
- parking: { type: 'object', properties: { mallId: { type: 'string' } } },
112
+ query: { type: 'object', properties: { baseUrl: g({ type: 'string', format: 'uri' }) }, required: ['baseUrl'] },
113
+ parking: { type: 'object', properties: { mallId: g({ type: 'string' }), parkLotId: { type: 'string', title: '车场组' } } },
109
114
  };
110
115
  // v4.6 joycity 代数 fixture:R1(baseUrl 双无 enum / env 同集异序 / envMixed 混布)/
111
116
  // R2(options 深相等 / meta 异构)/ R3(lotId string≠integer / timeout number≠integer / untyped 无 type)/
@@ -114,14 +119,14 @@ function layerHarness(): {
114
119
  query: {
115
120
  type: 'object',
116
121
  properties: {
117
- baseUrl: { type: 'string', format: 'uri' },
118
- lotId: { type: 'string' },
119
- env: { type: 'string', enum: ['dev', 'staging', 'prod'] },
120
- envMixed: { type: 'string', enum: ['a', 'b'] },
121
- meta: { type: 'object', properties: { region: { type: 'string' } } },
122
- options: { type: 'object', properties: { x: { type: 'string' } } },
123
- untyped: { description: 'x' },
124
- timeout: { type: 'number' },
122
+ baseUrl: { 'x-tbox-global': true, type: 'string', format: 'uri' },
123
+ lotId: { 'x-tbox-global': true, type: 'string' },
124
+ env: { 'x-tbox-global': true, type: 'string', enum: ['dev', 'staging', 'prod'] },
125
+ envMixed: { 'x-tbox-global': true, type: 'string', enum: ['a', 'b'] },
126
+ meta: { 'x-tbox-global': true, type: 'object', properties: { region: { 'x-tbox-global': true, type: 'string' } } },
127
+ options: { 'x-tbox-global': true, type: 'object', properties: { x: { 'x-tbox-global': true, type: 'string' } } },
128
+ untyped: { 'x-tbox-global': true, description: 'x' },
129
+ timeout: { 'x-tbox-global': true, type: 'number' },
125
130
  },
126
131
  required: ['baseUrl', 'lotId'],
127
132
  },
@@ -129,15 +134,15 @@ function layerHarness(): {
129
134
  type: 'object',
130
135
  additionalProperties: false,
131
136
  properties: {
132
- apiKey: { type: 'string' },
133
- baseUrl: { type: 'string' },
134
- lotId: { type: 'integer' },
135
- env: { type: 'string', enum: ['prod', 'dev', 'staging'] },
136
- envMixed: { type: 'string' },
137
- meta: { type: 'object', properties: { zone: { type: 'number' } } },
138
- options: { type: 'object', properties: { x: { type: 'string' } } },
139
- untyped: { type: 'string' },
140
- timeout: { type: 'integer' },
137
+ apiKey: { 'x-tbox-global': true, type: 'string' },
138
+ baseUrl: { 'x-tbox-global': true, type: 'string' },
139
+ lotId: { 'x-tbox-global': true, type: 'integer' },
140
+ env: { 'x-tbox-global': true, type: 'string', enum: ['prod', 'dev', 'staging'] },
141
+ envMixed: { 'x-tbox-global': true, type: 'string' },
142
+ meta: { 'x-tbox-global': true, type: 'object', properties: { zone: { 'x-tbox-global': true, type: 'number' } } },
143
+ options: { 'x-tbox-global': true, type: 'object', properties: { x: { 'x-tbox-global': true, type: 'string' } } },
144
+ untyped: { 'x-tbox-global': true, type: 'string' },
145
+ timeout: { 'x-tbox-global': true, type: 'integer' },
141
146
  },
142
147
  required: ['apiKey', 'options'],
143
148
  },
@@ -147,10 +152,10 @@ function layerHarness(): {
147
152
  'packages/provider-w/tbox.module.json',
148
153
  JSON.stringify({
149
154
  schemaVersion: 1,
150
- name: 'provider-w',
155
+ id: 'provider-w',
151
156
  version: '0.1.0',
152
157
  kind: 'business',
153
- contributes: {
158
+ declare: {
154
159
  providers: {
155
160
  slots: [
156
161
  { service: 'parking.query', provider: 'wanda', implementation: 'wanda-parking@1', credentialType: 'wanda-c', configSchema: 'schemas/query.json' },
@@ -170,10 +175,10 @@ function layerHarness(): {
170
175
  'packages/provider-j/tbox.module.json',
171
176
  JSON.stringify({
172
177
  schemaVersion: 1,
173
- name: 'provider-j',
178
+ id: 'provider-j',
174
179
  version: '0.1.0',
175
180
  kind: 'business',
176
- contributes: {
181
+ declare: {
177
182
  providers: {
178
183
  slots: [
179
184
  { service: 'parking.query', provider: 'joycity', implementation: 'joycity-parking@1', credentialType: 'joycity-c', configSchema: 'schemas/query.json' },
@@ -193,10 +198,10 @@ function layerHarness(): {
193
198
  'packages/provider-p/tbox.module.json',
194
199
  JSON.stringify({
195
200
  schemaVersion: 1,
196
- name: 'provider-p',
201
+ id: 'provider-p',
197
202
  version: '0.1.0',
198
203
  kind: 'business',
199
- contributes: {
204
+ declare: {
200
205
  providers: {
201
206
  slots: [{ service: 'parking.payment', provider: 'partial', implementation: 'partial-payment@1', credentialType: 'partial-c' }],
202
207
  },
@@ -210,10 +215,13 @@ function layerHarness(): {
210
215
  'packages/module-parking/tbox.module.json',
211
216
  JSON.stringify({
212
217
  schemaVersion: 1,
213
- name: 'module-parking',
218
+ id: 'module-parking',
214
219
  version: '0.1.0',
215
220
  kind: 'business',
216
- contributes: { services: [{ service: 'parking.query', optional: false }, { service: 'parking.payment', optional: true }] },
221
+ declare: {
222
+ owns: [{ service: 'parking.query' }, { service: 'parking.payment' }],
223
+ consumes: [{ service: 'parking.query', optional: false }, { service: 'parking.payment', optional: true }],
224
+ },
217
225
  dependencies: { modules: [] },
218
226
  env: [],
219
227
  }),
@@ -225,7 +233,7 @@ function layerHarness(): {
225
233
  provider: 'wanda',
226
234
  instances: [{ id: 'mall-bj-01', name: '北京店' }],
227
235
  defaultInstanceId: 'mall-bj-01',
228
- domains: { parking: { provider: 'wanda' } },
236
+ modules: { 'module-parking': { provider: 'wanda' } },
229
237
  services: { 'parking.query': { provider: 'wanda', implementation: 'wanda-parking@1' } },
230
238
  }),
231
239
  );
@@ -258,7 +266,8 @@ describe('views/工厂(O 矩阵核心族)', () => {
258
266
  expect(Object.keys(appView)).toEqual(['services', 'integration']);
259
267
  const mod = await h.tk.loadModule('module-parking');
260
268
  // v4.4 D34:domainKeys/providerBindings/configEditable 显式化(include 两键可选缺席——F4)
261
- expect(Object.keys(mod)).toEqual(['domainKeys', 'integration', 'providerBindings', 'configEditable', 'services']);
269
+ // v7 词汇统一:模块展示名 name 前置(恒发——兜底 id)
270
+ expect(Object.keys(mod)).toEqual(['name', 'description', 'ownedServices', 'integration', 'binding', 'configEditable', 'services']);
262
271
  const svc = await h.tk.loadService('parking.query');
263
272
  expect(Object.keys(svc).some((k) => ['resolution', 'issues', 'contracts'].includes(k))).toBe(false);
264
273
  // 静态端点在 <0.9 下照常(永不 503——O13 反例联动)
@@ -279,7 +288,7 @@ describe('views/工厂(O 矩阵核心族)', () => {
279
288
  });
280
289
 
281
290
  it('O3 键集 = 服务词汇 ∪ 已配置;required echo(OR 聚合 + 平台 false)', async () => {
282
- // vocabulary 空(无契约包 fixture)→ 键集 = 已配置(parking.query + auth.alipay-login
291
+ // vocabulary 空(无契约包 fixture 物化 slots——退化态回归锁;词汇在场态见 B1 T-O3
283
292
  const batch = await h.tk.loadServiceResolutions();
284
293
  expect(Object.keys(batch.resolutions).sort()).toEqual(['auth.alipay-login', 'parking.query']);
285
294
  // parking.query 由 module-parking demand required:0 → required true
@@ -511,8 +520,8 @@ describe('O6/O7/O10/O14(FX-3 补全)', () => {
511
520
  'packages/provider-multi/tbox.module.json',
512
521
  JSON.stringify({
513
522
  schemaVersion: 1,
514
- name: 'provider-multi',
515
- contributes: {
523
+ id: 'provider-multi',
524
+ declare: {
516
525
  providers: {
517
526
  slots: [
518
527
  { service: 'parking.query', provider: 'joycity', implementation: 'joycity-parking@1', credentialType: 'joycity-c', configSchema: 'schemas/query.json' },
@@ -583,8 +592,11 @@ describe('O6/O7/O10/O14(FX-3 补全)', () => {
583
592
  // api.md §5 表
584
593
  'CREDENTIAL_FILE_MISSING', 'CREDENTIAL_TYPE_MISMATCH', 'IMPL_WITHOUT_PROVIDER', 'INSTANCE_SUPPLY_UNMATCHED',
585
594
  'SCHEMA_FILE_MISSING', 'DOMAIN_CONFIG_UNUSED', 'DOMAIN_CONFIG_INVALID', 'INSTANCE_KEY_NOT_REGISTERED',
586
- 'REALM_SWITCH_HINT', 'WILDCARD_ALIGNMENT', 'VENDOR_NOT_INSTALLED', 'BINDING_UNRESOLVED', 'DOMAIN_KEY_UNMATCHED',
595
+ 'REALM_SWITCH_HINT', 'WILDCARD_ALIGNMENT', 'VENDOR_NOT_INSTALLED', 'BINDING_UNRESOLVED',
587
596
  'RESOURCE_TYPE_UNKNOWN', 'RESOURCE_NOT_DECLARED',
597
+ // v6 additive(api.md §5 表已收口):MODULE_CONFIG_DEAD / #17 GLOBAL_FIELD_UNDECLARED;
598
+ // v6 退役:DOMAIN_KEY_UNMATCHED(#14——domains 键退场)
599
+ 'MODULE_CONFIG_DEAD', 'GLOBAL_FIELD_UNDECLARED',
588
600
  // 谓词头映射表 additive(检查族编号映射:#3/#4/#5 内联/#13/v4)
589
601
  'SLOT_NOT_DECLARED', 'SLOT_NOT_CONFIGURED', 'INLINE_PROVIDER_INVALID', 'MULTI_INSTANCE_NO_DEFAULT', 'INSTANCE_DISABLED',
590
602
  ]);
@@ -634,18 +646,18 @@ describe('A2 错误面(P-4 notes 优先 / P-5 畸形包 503)', () => {
634
646
  });
635
647
  });
636
648
 
637
- describe('D34 多域模块钉死(v4.4 F3)', () => {
638
- it('D1 单域回归:domainKeys/providerBindings/configEditable 装配 + integration 原值', async () => {
649
+ describe('v6 所有权族(D34 多域钉死退役 → ownedServices/binding 单数重写)', () => {
650
+ it('D1 单槽回归:ownedServices/binding/configEditable 装配 + integration 集成位原值', async () => {
639
651
  const h2 = harnessWithContracts();
640
652
  const mod = await h2.tk.loadModule('module-parking');
641
- expect(mod.domainKeys).toEqual(['parking']);
653
+ expect(mod.ownedServices).toEqual(['parking.query']);
642
654
  expect(mod.configEditable).toBe(true);
643
- expect(mod.providerBindings).toEqual([{ domain: 'parking', effective: 'wanda', layer: 'module' }]);
655
+ expect(mod.binding).toEqual({ provider: 'wanda', layer: 'module' });
644
656
  expect(mod.integration).toEqual({ provider: 'wanda' });
645
657
  h2.app.dispose();
646
658
  });
647
659
 
648
- it('D2 多域模块:GET integration null + providerBindings 双条 + PUT 400 分支①(含 {} 清域)', async () => {
660
+ it('D2 多域归属合法化(v6 正例化——跨域 owns 单键落位):binding 单值作用全 owned + PUT 全量替换', async () => {
649
661
  const app = createTempApp('tbox-d2-');
650
662
  invalidateContractsResolver();
651
663
  const store = joinStore(app.appDir, 'contracts');
@@ -656,33 +668,30 @@ describe('D34 多域模块钉死(v4.4 F3)', () => {
656
668
  'packages/module-multi/tbox.module.json',
657
669
  JSON.stringify({
658
670
  schemaVersion: 1,
659
- name: 'module-multi',
671
+ id: 'module-multi',
660
672
  version: '0.1.0',
661
673
  kind: 'business',
662
- contributes: { services: [{ service: 'parking.query', optional: false }, { service: 'member.account', optional: false }] },
674
+ declare: { owns: [{ service: 'parking.query' }, { service: 'member.account' }], consumes: [{ service: 'parking.query', optional: false }] },
663
675
  dependencies: { modules: [] },
664
676
  env: [],
665
677
  }),
666
678
  );
667
679
  const tk = createAppToolkit(app.appDir);
668
680
  const mod = await tk.loadModule('module-multi');
669
- expect(mod.domainKeys).toEqual(['member', 'parking']);
681
+ expect(mod.ownedServices).toEqual(['member.account', 'parking.query']);
670
682
  expect(mod.integration).toBeNull();
671
- expect(mod.configEditable).toBe(false);
672
- expect(mod.providerBindings).toEqual([
673
- { domain: 'member', effective: null, layer: null },
674
- { domain: 'parking', effective: null, layer: null },
675
- ]);
676
- await expect(tk.writeModuleIntegration('module-multi', { provider: 'wanda' })).rejects.toMatchObject({
677
- code: 'VALIDATION_FAILED',
678
- httpStatus: 400,
679
- message: expect.stringContaining('跨 2 个域(member、parking)'),
680
- });
681
- await expect(tk.writeModuleIntegration('module-multi', {})).rejects.toMatchObject({ code: 'VALIDATION_FAILED' });
683
+ expect(mod.configEditable).toBe(true);
684
+ expect(mod.binding).toEqual({ provider: null, layer: null });
685
+ // v6 写通道:多域归属合法——模块级 provider 单值落 modules[module-multi]
686
+ const r = await tk.writeModuleIntegration('module-multi', { provider: 'wanda' });
687
+ expect(r.written).toBe(true);
688
+ const mod2 = await tk.loadModule('module-multi');
689
+ expect(mod2.integration).toEqual({ provider: 'wanda' });
690
+ expect(mod2.binding).toEqual({ provider: 'wanda', layer: 'module' });
682
691
  app.dispose();
683
692
  });
684
693
 
685
- it('D3 零域模块:domainKeys [] + PUT 400 分支②', async () => {
694
+ it('D3 零归属模块:ownedServices [] + configEditable false + PUT 400(zero-ownership)', async () => {
686
695
  const app = createTempApp('tbox-d3-');
687
696
  invalidateContractsResolver();
688
697
  const store = joinStore(app.appDir, 'contracts');
@@ -693,63 +702,95 @@ describe('D34 多域模块钉死(v4.4 F3)', () => {
693
702
  'packages/module-zero/tbox.module.json',
694
703
  JSON.stringify({
695
704
  schemaVersion: 1,
696
- name: 'module-zero',
705
+ id: 'module-zero',
697
706
  version: '0.1.0',
698
707
  kind: 'business',
699
- contributes: {},
708
+ declare: {},
700
709
  dependencies: { modules: [] },
701
710
  env: [],
702
711
  }),
703
712
  );
704
713
  const tk = createAppToolkit(app.appDir);
705
714
  const mod = await tk.loadModule('module-zero');
706
- expect(mod.domainKeys).toEqual([]);
707
- expect(mod.providerBindings).toEqual([]);
715
+ expect(mod.ownedServices).toEqual([]);
716
+ expect(mod.binding).toEqual({ provider: null, layer: null });
708
717
  expect(mod.integration).toBeNull();
709
718
  await expect(tk.writeModuleIntegration('module-zero', { provider: 'x' })).rejects.toMatchObject({
710
719
  code: 'VALIDATION_FAILED',
711
720
  httpStatus: 400,
712
- message: expect.stringContaining('无服务需求声明'),
721
+ message: expect.stringContaining('未声明任何 declare.owns'),
713
722
  });
714
723
  app.dispose();
715
724
  });
716
725
 
717
- it('D4 域键共享(P13):双方 configEditable false + PUT 400 分支③(他方同域异服务——并集计数)', async () => {
726
+ it('D4 v6 互斥归属:双模块 owns 同服务 OWNERSHIP_CONFLICT(先声明胜出)+ 双方可写(归属冲突≠写禁)', async () => {
718
727
  const app = createTempApp('tbox-d4-');
719
728
  invalidateContractsResolver();
720
729
  const store = joinStore(app.appDir, 'contracts');
721
730
  writeFakeContractsPackage(store, { version: '0.9.0' });
722
731
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
723
- const manifest = (name: string, service: string): string =>
732
+ const manifest = (id: string, service: string): string =>
724
733
  JSON.stringify({
725
734
  schemaVersion: 1,
726
- name,
735
+ id,
727
736
  version: '0.1.0',
728
737
  kind: 'business',
729
- contributes: { services: [{ service, optional: false }] },
738
+ declare: { owns: [{ service }], consumes: [{ service, optional: false }] },
730
739
  dependencies: { modules: [] },
731
740
  env: [],
732
741
  });
733
742
  writeAppFile(app.appDir, 'packages/module-a/tbox.module.json', manifest('module-a', 'parking.alpha'));
734
- writeAppFile(app.appDir, 'packages/module-b/tbox.module.json', manifest('module-b', 'parking.beta'));
743
+ writeAppFile(app.appDir, 'packages/module-b/tbox.module.json', manifest('module-b', 'parking.alpha'));
735
744
  const tk = createAppToolkit(app.appDir);
745
+ const walk = tk.walkManifests();
746
+ // 先声明胜出(packages/ 目录字母序:module-a 先)
747
+ expect(walk.ownership['parking.alpha']).toBe('module-a');
748
+ // N4 执法分层:冲突 = declarationIssues error(doctor 面向第三方组合态执法;写通道不拒——归属冲突非写禁)
749
+ const conflict = walk.declarationIssues.find((i) => i.code === 'OWNERSHIP_CONFLICT');
750
+ expect(conflict).toBeDefined();
751
+ expect(conflict!.severity).toBe('error');
752
+ expect(conflict!.message).toContain('module-b');
753
+ // 双方 configEditable 均 true(owns>0 唯一判据——v6 无 shared-key 写禁)
736
754
  const a = await tk.loadModule('module-a');
737
755
  const b = await tk.loadModule('module-b');
738
- expect(a.configEditable).toBe(false);
739
- expect(b.configEditable).toBe(false);
740
- await expect(tk.writeModuleIntegration('module-a', { provider: 'x' })).rejects.toMatchObject({
741
- code: 'VALIDATION_FAILED',
742
- httpStatus: 400,
743
- message: expect.stringContaining(' module-a、module-b 共用'),
756
+ expect(a.configEditable).toBe(true);
757
+ expect(b.configEditable).toBe(true);
758
+ app.dispose();
759
+ });
760
+
761
+ it('D6 偏斜态 503 第四分支(P1 标记门):v5 contracts 假件(无 OWNERSHIP_BINDING_V6)+ v6 toolkit → 503', async () => {
762
+ const app = createTempApp('tbox-d6-');
763
+ invalidateContractsResolver();
764
+ const store = joinStore(app.appDir, 'contracts');
765
+ writeFakeContractsPackage(store, { version: '0.9.0', withMarker: false });
766
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
767
+ writeAppFile(app.appDir, 'packages/module-parking/tbox.module.json', JSON.stringify({
768
+ schemaVersion: 1, id: 'module-parking', version: '0.1.0', kind: 'business',
769
+ declare: { owns: [{ service: 'parking.query' }] },
770
+ }));
771
+ const tk = createAppToolkit(app.appDir);
772
+ // 写路径门控(求值轴同此门——assertContractsUsable 单点)
773
+ await expect(tk.writeModuleIntegration('module-parking', { provider: 'wanda' })).rejects.toMatchObject({
774
+ code: 'CONTRACTS_NOT_RESOLVED',
775
+ httpStatus: 503,
776
+ message: expect.stringContaining('OWNERSHIP_BINDING_V6'),
744
777
  });
778
+ await expect(tk.loadServiceResolutions()).rejects.toMatchObject({ code: 'CONTRACTS_NOT_RESOLVED', httpStatus: 503 });
779
+ // 静态端点照常(永不 503——门控 policy at edge 只拦求值/写/migrate)
780
+ const mod = await tk.loadModule('module-parking');
781
+ expect(mod.ownedServices).toEqual(['parking.query']);
745
782
  app.dispose();
746
783
  });
747
784
 
748
- it('D5 死键扫尾:moduleDomainKey/moduleDomainKeyOf 全链删除(源文件内容断言)', () => {
785
+ it('D5 死键扫尾:providerBindings/moduleDomainWritable/domain-binding 全链删除(源文件内容断言)', () => {
749
786
  const read = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
750
- expect(read('../src/views/modules.ts')).not.toContain('moduleDomainKeyOf');
751
- expect(read('../src/integrations/write.ts')).not.toContain('moduleDomainKey');
752
- expect(read('../src/index.ts')).not.toContain('moduleDomainKeyOf');
787
+ const exists = (rel: string): boolean => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8').length >= 0;
788
+ void exists;
789
+ expect(read('../src/views/modules.ts')).not.toContain('providerBindings:');
790
+ expect(read('../src/integrations/write.ts')).not.toContain('moduleDomain');
791
+ expect(read('../src/index.ts')).not.toContain('domain-binding');
792
+ // v6 新面在场(正向锚)
793
+ expect(read('../src/integrations/module-binding.ts')).toContain('moduleOwnershipWritable');
753
794
  });
754
795
  });
755
796
 
@@ -843,7 +884,7 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
843
884
  const h2 = layerHarness();
844
885
  const mod = await h2.tk.loadModule('module-parking', { include: ['integrationSchemas'] });
845
886
  expect(mod.integrationSchemas).toEqual({
846
- provider: 'wanda', // resolveDomainBinding 单源(domains.parking)
887
+ provider: 'wanda', // resolveModuleBinding 单源(modules['module-parking']
847
888
  services: {
848
889
  'parking.query': { configSchema: h2.schemas.query },
849
890
  'parking.payment': { configSchema: h2.schemas.parking },
@@ -872,7 +913,7 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
872
913
  'config/integrations.json',
873
914
  JSON.stringify({
874
915
  instances: [{ id: 'mall-bj-01', name: '北京店' }],
875
- domains: { parking: { provider: 'wanda' } },
916
+ modules: { 'module-parking': { provider: 'wanda' } },
876
917
  services: { 'parking.query': { provider: 'wanda', implementation: 'wanda-parking@1' } },
877
918
  }),
878
919
  );
@@ -901,10 +942,10 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
901
942
  'packages/module-multi/tbox.module.json',
902
943
  JSON.stringify({
903
944
  schemaVersion: 1,
904
- name: 'module-multi',
945
+ id: 'module-multi',
905
946
  version: '0.1.0',
906
947
  kind: 'business',
907
- contributes: { services: [{ service: 'parking.query', optional: false }, { service: 'member.account', optional: false }] },
948
+ declare: { owns: [{ service: 'parking.query' }], consumes: [{ service: 'parking.query', optional: false }, { service: 'member.account', optional: false }] },
908
949
  dependencies: { modules: [] },
909
950
  env: [],
910
951
  }),
@@ -928,10 +969,10 @@ describe('模块清单 provider 家族排除(2026-09-16 双缺陷批——供
928
969
  'packages/provider-custom/tbox.module.json',
929
970
  JSON.stringify({
930
971
  schemaVersion: 1,
931
- name: 'provider-custom',
972
+ id: 'provider-custom',
932
973
  version: '0.1.0',
933
974
  kind: 'business',
934
- contributes: { providers: { slots: [] }, services: [] },
975
+ declare: { providers: { slots: [] }, consumes: [], owns: [] },
935
976
  dependencies: { modules: [] },
936
977
  env: [],
937
978
  }),
@@ -949,8 +990,8 @@ describe('模块清单 provider 家族排除(2026-09-16 双缺陷批——供
949
990
  join('node_modules', '@tbox.cn', 'app-provider-mock', 'tbox.module.json'),
950
991
  JSON.stringify({
951
992
  schemaVersion: 1,
952
- name: 'provider-mock',
953
- contributes: {
993
+ id: 'provider-mock',
994
+ declare: {
954
995
  providers: { slots: [{ service: 'parking.query', provider: 'mock', implementation: 'mock-parking@1', credentialType: '-' }] },
955
996
  },
956
997
  }),
@@ -968,11 +1009,11 @@ describe('模块清单 provider 家族排除(2026-09-16 双缺陷批——供
968
1009
  expect(walk.modules.map((m) => m.id).sort()).toEqual(['module-parking', 'provider-custom', 'vendor-mock']);
969
1010
  // 详情 200 保持(装包仍合法——零域空态;写通道零域由 D34 守卫拦)
970
1011
  const custom = await h2.tk.loadModule('provider-custom');
971
- expect(custom.domainKeys).toEqual([]);
1012
+ expect(custom.ownedServices).toEqual([]);
972
1013
  expect(custom.configEditable).toBe(false);
973
1014
  expect(custom.services).toEqual([]);
974
1015
  const mock = await h2.tk.loadModule('vendor-mock');
975
- expect(mock.domainKeys).toEqual([]);
1016
+ expect(mock.ownedServices).toEqual([]);
976
1017
  // 供给轴不受影响:provider-mock 槽位照常进 catalog(清单排除不伤聚合)
977
1018
  const providers = await h2.tk.loadProviders();
978
1019
  expect(providers.providers.map((p) => p.provider)).toContain('mock');
@@ -1007,10 +1048,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1007
1048
  'packages/provider-w/tbox.module.json',
1008
1049
  JSON.stringify({
1009
1050
  schemaVersion: 1,
1010
- name: 'provider-w',
1051
+ id: 'provider-w',
1011
1052
  version: '0.1.0',
1012
1053
  kind: 'business',
1013
- contributes: {
1054
+ declare: {
1014
1055
  providers: {
1015
1056
  slots: [
1016
1057
  { service: 'parking.query', provider: 'wanda', implementation: 'wanda-parking@1', credentialType: 'wanda-c', configSchema: 'schemas/query.json' },
@@ -1029,10 +1070,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1029
1070
  'packages/provider-multi/tbox.module.json',
1030
1071
  JSON.stringify({
1031
1072
  schemaVersion: 1,
1032
- name: 'provider-multi',
1073
+ id: 'provider-multi',
1033
1074
  version: '0.1.0',
1034
1075
  kind: 'business',
1035
- contributes: {
1076
+ declare: {
1036
1077
  providers: {
1037
1078
  slots: [
1038
1079
  { service: 'parking.query', provider: 'joycity', implementation: 'joycity-parking@1', credentialType: 'joycity-c', configSchema: 'schemas/query.json' },
@@ -1052,10 +1093,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1052
1093
  'packages/module-parking/tbox.module.json',
1053
1094
  JSON.stringify({
1054
1095
  schemaVersion: 1,
1055
- name: 'module-parking',
1096
+ id: 'module-parking',
1056
1097
  version: '0.1.0',
1057
1098
  kind: 'business',
1058
- contributes: { services: [{ service: 'parking.query', optional: false }] },
1099
+ declare: { owns: [{ service: 'parking.query' }], consumes: [{ service: 'parking.query', optional: false }] },
1059
1100
  dependencies: { modules: [] },
1060
1101
  env: [],
1061
1102
  }),
@@ -1137,12 +1178,12 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1137
1178
 
1138
1179
  it('E6 域级联(分支①)+ ghost 域厂商诚实态', async () => {
1139
1180
  const h = eHarness();
1140
- writeIntegrations(h.app.appDir, { domains: { parking: { provider: 'wanda' } }, services: { 'parking.query': {} } });
1181
+ writeIntegrations(h.app.appDir, { modules: { 'module-parking': { provider: 'wanda' } }, services: { 'parking.query': {} } });
1141
1182
  const view = await h.tk.loadService('parking.query', { include: ['integrationSchemas'] });
1142
1183
  expect(view.integrationSchemas).toEqual({ provider: 'wanda', configSchema: h.schemas.wQuery });
1143
1184
  // ghost 域厂商(catalog 外)→ configSchema null(诊断归谓词 #11——富化非门控)
1144
1185
  const h2 = eHarness();
1145
- writeIntegrations(h2.app.appDir, { domains: { parking: { provider: 'ghost-vendor' } }, services: { 'parking.query': {} } });
1186
+ writeIntegrations(h2.app.appDir, { modules: { 'module-parking': { provider: 'ghost-vendor' } }, services: { 'parking.query': {} } });
1146
1187
  const ghost = await h2.tk.loadService('parking.query', { include: ['integrationSchemas'] });
1147
1188
  expect(ghost.integrationSchemas).toEqual({ provider: 'ghost-vendor', configSchema: null });
1148
1189
  h.app.dispose();
@@ -1214,10 +1255,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1214
1255
  });
1215
1256
 
1216
1257
  it('E14 malformed → 整字段 null(不落域级联——分支回归锚)', async () => {
1217
- // ① 坏对象(缺 credentialType)——配 domains 佐证未走域级联
1258
+ // ① 坏对象(缺 credentialType)——配 modules 佐证未走模块级联
1218
1259
  const h = eHarness();
1219
1260
  writeIntegrations(h.app.appDir, {
1220
- domains: { parking: { provider: 'wanda' } },
1261
+ modules: { 'module-parking': { provider: 'wanda' } },
1221
1262
  services: { 'parking.query': { provider: { provider: 'my-api' } } },
1222
1263
  });
1223
1264
  const bad = await h.tk.loadService('parking.query', { include: ['integrationSchemas'] });
@@ -1225,7 +1266,7 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1225
1266
  // ② provider: null(谓词 #5 同判 INLINE_PROVIDER_INVALID——坏形态非缺席)
1226
1267
  const h2 = eHarness();
1227
1268
  writeIntegrations(h2.app.appDir, {
1228
- domains: { parking: { provider: 'wanda' } },
1269
+ modules: { 'module-parking': { provider: 'wanda' } },
1229
1270
  services: { 'parking.query': { provider: null } },
1230
1271
  });
1231
1272
  const nul = await h2.tk.loadService('parking.query', { include: ['integrationSchemas'] });
@@ -1237,7 +1278,7 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1237
1278
  it('E15 provider 空串 → 走域级联(非 null)', async () => {
1238
1279
  const h = eHarness();
1239
1280
  writeIntegrations(h.app.appDir, {
1240
- domains: { parking: { provider: 'wanda' } },
1281
+ modules: { 'module-parking': { provider: 'wanda' } },
1241
1282
  services: { 'parking.query': { provider: '' } },
1242
1283
  });
1243
1284
  const view = await h.tk.loadService('parking.query', { include: ['integrationSchemas'] });
@@ -1262,15 +1303,15 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1262
1303
  unsupplied: [],
1263
1304
  additionalPropertiesAllowed: false,
1264
1305
  fields: [
1265
- { key: 'baseUrl', schema: { type: 'string', format: 'uri' }, sources: [src('parking.query', true), src('parking.payment', false)] },
1266
- { key: 'lotId', schema: null, sources: [src('parking.query', true), src('parking.payment', false)] },
1267
- { key: 'env', schema: { type: 'string', enum: ['dev', 'staging', 'prod'] }, sources: [src('parking.query', false), src('parking.payment', false)] },
1268
- { key: 'envMixed', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1269
- { key: 'meta', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1270
- { key: 'options', schema: { type: 'object', properties: { x: { type: 'string' } } }, sources: [src('parking.query', false), src('parking.payment', true)] },
1271
- { key: 'untyped', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1272
- { key: 'timeout', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1273
- { key: 'apiKey', schema: { type: 'string' }, sources: [src('parking.payment', true)] },
1306
+ { key: 'baseUrl', schema: { type: 'string', format: 'uri', 'x-tbox-global': true }, global: true, sources: [src('parking.query', true), src('parking.payment', false)] },
1307
+ { key: 'lotId', schema: null, global: true, sources: [src('parking.query', true), src('parking.payment', false)] },
1308
+ { key: 'env', schema: { type: 'string', enum: ['dev', 'staging', 'prod'], 'x-tbox-global': true }, global: true, sources: [src('parking.query', false), src('parking.payment', false)] },
1309
+ { key: 'envMixed', schema: null, global: true, sources: [src('parking.query', false), src('parking.payment', false)] },
1310
+ { key: 'meta', schema: null, global: true, sources: [src('parking.query', false), src('parking.payment', false)] },
1311
+ { key: 'options', schema: { type: 'object', properties: { x: { type: 'string', 'x-tbox-global': true } }, 'x-tbox-global': true }, global: true, sources: [src('parking.query', false), src('parking.payment', true)] },
1312
+ { key: 'untyped', schema: null, global: true, sources: [src('parking.query', false), src('parking.payment', false)] },
1313
+ { key: 'timeout', schema: null, global: true, sources: [src('parking.query', false), src('parking.payment', false)] },
1314
+ { key: 'apiKey', schema: { type: 'string', 'x-tbox-global': true }, global: true, sources: [src('parking.payment', true)] },
1274
1315
  ],
1275
1316
  };
1276
1317
 
@@ -1281,15 +1322,15 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1281
1322
  unsupplied: [],
1282
1323
  additionalPropertiesAllowed: false,
1283
1324
  fields: [
1284
- { key: 'apiKey', schema: { type: 'string' }, sources: [src('parking.payment', true)] },
1285
- { key: 'baseUrl', schema: { type: 'string' }, sources: [src('parking.payment', false), src('parking.query', true)] },
1286
- { key: 'lotId', schema: null, sources: [src('parking.payment', false), src('parking.query', true)] },
1287
- { key: 'env', schema: { type: 'string', enum: ['prod', 'dev', 'staging'] }, sources: [src('parking.payment', false), src('parking.query', false)] },
1288
- { key: 'envMixed', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1289
- { key: 'meta', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1290
- { key: 'options', schema: { type: 'object', properties: { x: { type: 'string' } } }, sources: [src('parking.payment', true), src('parking.query', false)] },
1291
- { key: 'untyped', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1292
- { key: 'timeout', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1325
+ { key: 'apiKey', schema: { type: 'string', 'x-tbox-global': true }, global: true, sources: [src('parking.payment', true)] },
1326
+ { key: 'baseUrl', schema: { type: 'string', 'x-tbox-global': true }, global: true, sources: [src('parking.payment', false), src('parking.query', true)] },
1327
+ { key: 'lotId', schema: null, global: true, sources: [src('parking.payment', false), src('parking.query', true)] },
1328
+ { key: 'env', schema: { type: 'string', enum: ['prod', 'dev', 'staging'], 'x-tbox-global': true }, global: true, sources: [src('parking.payment', false), src('parking.query', false)] },
1329
+ { key: 'envMixed', schema: null, global: true, sources: [src('parking.payment', false), src('parking.query', false)] },
1330
+ { key: 'meta', schema: null, global: true, sources: [src('parking.payment', false), src('parking.query', false)] },
1331
+ { key: 'options', schema: { type: 'object', properties: { x: { type: 'string', 'x-tbox-global': true } }, 'x-tbox-global': true }, global: true, sources: [src('parking.payment', true), src('parking.query', false)] },
1332
+ { key: 'untyped', schema: null, global: true, sources: [src('parking.payment', false), src('parking.query', false)] },
1333
+ { key: 'timeout', schema: null, global: true, sources: [src('parking.payment', false), src('parking.query', false)] },
1293
1334
  ],
1294
1335
  };
1295
1336
 
@@ -1299,15 +1340,15 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1299
1340
  /** E4:module 轴 moduleProvider=ghost-x(未知厂商——非 null 空集 + 全 demand 回填,demand 序) */
1300
1341
  const E4 = { provider: 'ghost-x', schemaless: [], unsupplied: ['parking.query', 'parking.payment'], additionalPropertiesAllowed: true, fields: [] };
1301
1342
 
1302
- /** E5:实际态(无轴参,wanda;scope = [query, payment] */
1343
+ /** E5:实际态(无轴参,wanda;scope = [query, payment])——parkLotId 未标记 global 被表单收窄(G 批负例) */
1303
1344
  const E5 = {
1304
1345
  provider: 'wanda',
1305
1346
  schemaless: [],
1306
1347
  unsupplied: [],
1307
1348
  additionalPropertiesAllowed: true,
1308
1349
  fields: [
1309
- { key: 'baseUrl', schema: { type: 'string', format: 'uri' }, sources: [src('parking.query', true)] },
1310
- { key: 'mallId', schema: { type: 'string' }, sources: [src('parking.payment', false)] },
1350
+ { key: 'baseUrl', schema: { type: 'string', format: 'uri', 'x-tbox-global': true }, global: true, sources: [src('parking.query', true)] },
1351
+ { key: 'mallId', schema: { type: 'string', 'x-tbox-global': true }, global: true, sources: [src('parking.payment', false)] },
1311
1352
  ],
1312
1353
  };
1313
1354
 
@@ -1389,7 +1430,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1389
1430
 
1390
1431
  it('S5a-8/9 单源直通 + required 聚合(some;conflict 照常聚合)', () => {
1391
1432
  const single = reg({ 'a.one': { configSchema: { type: 'object', properties: { only: { type: 'string', enum: ['z'] } } } } });
1392
- expect(single.fields[0]).toEqual({ key: 'only', schema: { type: 'string', enum: ['z'] }, sources: [src('a.one', false)] });
1433
+ expect(single.fields[0]).toEqual({ key: 'only', schema: { type: 'string', enum: ['z'] }, global: false, sources: [src('a.one', false)] });
1393
1434
  const some = reg({
1394
1435
  'a.one': { configSchema: { type: 'object', properties: { k: { type: 'string' } }, required: ['k'] } },
1395
1436
  'a.two': { configSchema: { type: 'object', properties: { k: { type: 'string' } } } },
@@ -1445,7 +1486,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1445
1486
  const appInc = await h2.tk.loadApp({ include: ['integrationFieldRegistry'], appProvider: 'joycity' });
1446
1487
  expect(appInc.integrationFieldRegistry).toEqual(E2);
1447
1488
  expect(appInc.integrationFieldRegistry?.fields[0].key).toBe('apiKey'); // 足迹字母序(vs E1 首键 baseUrl)
1448
- expect(appInc.integrationFieldRegistry?.fields[1].schema).toEqual({ type: 'string' }); // 首源漂移(payment 先)
1489
+ expect(appInc.integrationFieldRegistry?.fields[1].schema).toEqual({ type: 'string', 'x-tbox-global': true }); // 首源漂移(payment 先)
1449
1490
  const partial = await h2.tk.loadModule('module-parking', { include: ['integrationFieldRegistry'], moduleProvider: 'partial' });
1450
1491
  expect(partial.integrationFieldRegistry).toEqual(E3);
1451
1492
  const actual = await h2.tk.loadModule('module-parking', { include: ['integrationFieldRegistry'] });
@@ -1483,7 +1524,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1483
1524
  const actJoy = await h2.tk.loadModule('module-parking', { include: both });
1484
1525
  expect(actJoy.integrationFieldRegistry).toEqual(hypJoy.integrationFieldRegistry);
1485
1526
  expect(actJoy.integrationSchemas).toEqual(hypJoy.integrationSchemas);
1486
- // ② 跟随应用级(root.provider=wanda)≡ 清域(N1 删 domains.parking → root 兜底)后实读——非平凡:清域前实际态 = E1
1527
+ // ② 跟随应用级(root.provider=wanda)≡ 清模块位(N1 删 modules['module-parking'] → root 兜底)后实读——非平凡:清位前实际态 = E1
1487
1528
  const hypFollow = await h2.tk.loadModule('module-parking', { include: both, moduleProvider: 'wanda' });
1488
1529
  expect(hypFollow.integrationFieldRegistry).toEqual(E5);
1489
1530
  await h2.tk.writeModuleIntegration('module-parking', {});
@@ -1496,7 +1537,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1496
1537
  h2.app.dispose();
1497
1538
  });
1498
1539
 
1499
- it('S6 app 轴同构:假想 ≡ 写 root {provider:joycity} 后实读(domains/services 不受扰)', async () => {
1540
+ it('S6 app 轴同构:假想 ≡ 写 root {provider:joycity} 后实读(modules/services 不受扰)', async () => {
1500
1541
  const h2 = layerHarness();
1501
1542
  const hyp = await h2.tk.loadApp({ include: ['integrationFieldRegistry'], appProvider: 'joycity' });
1502
1543
  const cur = await h2.tk.loadApp();
@@ -1555,7 +1596,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1555
1596
  moduleProvider: 'joycity',
1556
1597
  });
1557
1598
  expect(v.integration).toEqual({ provider: 'wanda' });
1558
- expect(v.providerBindings).toEqual([{ domain: 'parking', effective: 'wanda', layer: 'module' }]);
1599
+ expect(v.binding).toEqual({ provider: 'wanda', layer: 'module' });
1559
1600
  expect(v.configEditable).toBe(true);
1560
1601
  expect(v.integrationFieldRegistry?.provider).toBe('joycity'); // 表单层重定向
1561
1602
  expect(v.integrationSchemas?.provider).toBe('joycity');
@@ -1571,7 +1612,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1571
1612
  'config/integrations.json',
1572
1613
  JSON.stringify({
1573
1614
  instances: [{ id: 'mall-bj-01', name: '北京店' }],
1574
- domains: { parking: { provider: 'joycity' } },
1615
+ modules: { 'module-parking': { provider: 'joycity' } },
1575
1616
  services: { 'parking.query': { provider: 'wanda', implementation: 'wanda-parking@1' } },
1576
1617
  }),
1577
1618
  );
@@ -1581,7 +1622,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1581
1622
  ).toBeNull();
1582
1623
  h3.app.dispose();
1583
1624
 
1584
- // 可写未绑定(无 domains root.provider——无轴参 → null)
1625
+ // 可写未绑定(无 modules 位无 root.provider——无轴参 → null)
1585
1626
  const h4 = layerHarness();
1586
1627
  writeAppFile(
1587
1628
  h4.app.appDir,
@@ -1597,29 +1638,39 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1597
1638
  expect(unbound.configEditable).toBe(true);
1598
1639
  h4.app.dispose();
1599
1640
 
1600
- // D34 三形态(多域/零域/共享键——带 moduleProvider 仍 null
1641
+ // v6 形态(D34 三形态退役):零归属 → null(带 moduleProvider 仍 null——不豁免);
1642
+ // 多域/跨域归属合法化 → 非 null(override 可计算;unsupplied 回填非供给归属槽)
1601
1643
  const d34 = createTempApp('tbox-v46-d34-');
1602
1644
  invalidateContractsResolver();
1603
1645
  const store34 = joinStore(d34.appDir, 'contracts');
1604
1646
  writeFakeContractsPackage(store34, { version: '0.9.0' });
1605
1647
  linkContracts(d34.appDir, store34, { subdir: 'apps/server' });
1606
1648
  const need = (service: string): { service: string; optional: boolean } => ({ service, optional: false });
1607
- const manifest34 = (name: string, services: Array<{ service: string; optional: boolean }>): string =>
1608
- JSON.stringify({ schemaVersion: 1, name, version: '0.1.0', kind: 'business', contributes: { services }, dependencies: { modules: [] }, env: [] });
1649
+ const manifest34 = (id: string, services: Array<{ service: string; optional: boolean }>): string =>
1650
+ JSON.stringify({ schemaVersion: 1, id, version: '0.1.0', kind: 'business', declare: { owns: services.map((x) => ({ service: x.service })), consumes: services }, dependencies: { modules: [] }, env: [] });
1609
1651
  writeAppFile(d34.appDir, 'packages/module-multi/tbox.module.json', manifest34('module-multi', [need('parking.query'), need('member.account')]));
1610
1652
  writeAppFile(
1611
1653
  d34.appDir,
1612
1654
  'packages/module-zero/tbox.module.json',
1613
- JSON.stringify({ schemaVersion: 1, name: 'module-zero', version: '0.1.0', kind: 'business', contributes: {}, dependencies: { modules: [] }, env: [] }),
1655
+ JSON.stringify({ schemaVersion: 1, id: 'module-zero', version: '0.1.0', kind: 'business', declare: {}, dependencies: { modules: [] }, env: [] }),
1614
1656
  );
1615
1657
  writeAppFile(d34.appDir, 'packages/module-a/tbox.module.json', manifest34('module-a', [need('parking.alpha')]));
1616
1658
  writeAppFile(d34.appDir, 'packages/module-b/tbox.module.json', manifest34('module-b', [need('parking.beta')]));
1617
1659
  const tk34 = createAppToolkit(d34.appDir);
1618
- for (const id of ['module-multi', 'module-zero', 'module-a']) {
1619
- const v = await tk34.loadModule(id, { include: ['integrationFieldRegistry', 'integrationSchemas'], moduleProvider: 'joycity' });
1620
- expect(v.integrationFieldRegistry, id).toBeNull();
1621
- expect(v.integrationSchemas, id).toBeNull();
1622
- }
1660
+ // 零归属:null(唯一 null 形态——override 不豁免)
1661
+ const zero = await tk34.loadModule('module-zero', { include: ['integrationFieldRegistry', 'integrationSchemas'], moduleProvider: 'joycity' });
1662
+ expect(zero.integrationFieldRegistry).toBeNull();
1663
+ expect(zero.integrationSchemas).toBeNull();
1664
+ expect(zero.configEditable).toBe(false);
1665
+ // 多域归属:可写(owns>0)——override joycity 供给 parking.query(member.account → unsupplied)
1666
+ const multi = await tk34.loadModule('module-multi', { include: ['integrationFieldRegistry', 'integrationSchemas'], moduleProvider: 'joycity' });
1667
+ expect(multi.configEditable).toBe(true);
1668
+ expect(multi.integrationSchemas?.provider).toBe('joycity');
1669
+ // d34 fixture 无 provider 包 → joycity 零供给 → 双 owned 全回填(manifest owns 序)
1670
+ expect(multi.integrationFieldRegistry?.unsupplied).toEqual(['parking.query', 'member.account']);
1671
+ // 跨域归属:scope = owns ∩ 供给 = [](parking.alpha 非 joycity 供给)→ 非 null 空集 + unsupplied 全回填
1672
+ const a = await tk34.loadModule('module-a', { include: ['integrationFieldRegistry'], moduleProvider: 'joycity' });
1673
+ expect(a.integrationFieldRegistry).toEqual({ provider: 'joycity', schemaless: [], unsupplied: ['parking.alpha'], additionalPropertiesAllowed: true, fields: [] });
1623
1674
  d34.dispose();
1624
1675
  });
1625
1676
 
@@ -1680,7 +1731,13 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1680
1731
  const props = c.properties;
1681
1732
  const declared = props && typeof props === 'object' && !Array.isArray(props) ? Object.keys(props) : []; // 同款守卫
1682
1733
  const rev = registry.fields.filter((f) => f.sources.some((x) => x.service === s)).map((f) => f.key);
1683
- expect([...declared].sort(), s).toEqual([...rev].sort());
1734
+ // G 批收窄互证:表单键 ⊆ 声明键,且声明侧标记键 ≡ 表单键(未标记键落 ExtraFields——不进 fields)
1735
+ const marked = declared.filter((k) => {
1736
+ const sub = (props as Record<string, Record<string, unknown>>)[k];
1737
+ return !!sub && sub['x-tbox-global'] === true;
1738
+ });
1739
+ expect(rev.every((k) => declared.includes(k)), s).toBe(true);
1740
+ expect([...marked].sort(), s).toEqual([...rev].sort());
1684
1741
  }
1685
1742
  for (const s of registry.schemaless) expect(layer.services[s]?.configSchema).toBeNull();
1686
1743
  // unsupplied ≡ demand − scope 键集
@@ -1690,6 +1747,39 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1690
1747
  h2.app.dispose();
1691
1748
  });
1692
1749
 
1750
+ it('G 批收窄负例(P16 复合可见面):未标记键在 layer.properties 在场、registry.fields 不收(wanda 实际态 parkLotId)', async () => {
1751
+ const h2 = layerHarness();
1752
+ const v = await h2.tk.loadModule('module-parking', { include: ['integrationSchemas', 'integrationFieldRegistry'] });
1753
+ const layer = v.integrationSchemas!;
1754
+ const registry = v.integrationFieldRegistry!;
1755
+ expect(layer.provider).toBe('wanda');
1756
+ const parkingProps = (layer.services['parking.payment'].configSchema as Record<string, Record<string, unknown>>).properties as Record<string, Record<string, unknown>>;
1757
+ expect(parkingProps.mallId['x-tbox-global']).toBe(true);
1758
+ expect(parkingProps.parkLotId).toBeDefined(); // 原始投影在场
1759
+ expect(parkingProps.parkLotId['x-tbox-global']).toBeUndefined();
1760
+ expect(registry.fields.some((f) => f.key === 'parkLotId')).toBe(false); // 表单收窄不收
1761
+ expect(registry.fields.some((f) => f.key === 'mallId')).toBe(true);
1762
+ // app 轴同律(足迹含 query——baseUrl 标记在场)
1763
+ const appV = await h2.tk.loadApp({ include: ['integrationSchemas', 'integrationFieldRegistry'] });
1764
+ expect(appV.integrationFieldRegistry!.fields.some((f) => f.key === 'baseUrl')).toBe(true);
1765
+ h2.app.dispose();
1766
+ });
1767
+
1768
+ it('G 批服务轴不过滤锁:loadServiceFieldRegistry 全字段带旗标(unmarked → global:false 在场)', async () => {
1769
+ const h2 = layerHarness();
1770
+ const snap = (await import('../src/views/context.js')).loadViewSnapshotSync({ appDir: h2.app.appDir }, await h2.tk.resolveContracts());
1771
+ const reg = loadServiceFieldRegistry(snap, 'parking.payment');
1772
+ expect(reg).not.toBeNull();
1773
+ const keys = reg!.fields.map((f) => f.key).sort();
1774
+ expect(keys).toEqual(['mallId', 'parkLotId']); // 服务轴全量(未标记也入列——旗标 false 如实)
1775
+ const lot = reg!.fields.find((f) => f.key === 'parkLotId')!;
1776
+ expect(lot.global).toBe(false);
1777
+ expect(lot.schema).toEqual({ type: 'string', title: '车场组' }); // 无标记污染
1778
+ const mall = reg!.fields.find((f) => f.key === 'mallId')!;
1779
+ expect(mall.global).toBe(true);
1780
+ h2.app.dispose();
1781
+ });
1782
+
1693
1783
  it('S8 app 轴:unsupplied ≡ [](结构性:足迹 ⊆ 供给)', async () => {
1694
1784
  const h2 = layerHarness();
1695
1785
  const v = await h2.tk.loadApp({ include: ['integrationFieldRegistry'], appProvider: 'joycity' });
@@ -1697,3 +1787,199 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1697
1787
  h2.app.dispose();
1698
1788
  });
1699
1789
  });
1790
+
1791
+ /**
1792
+ * B1 展示名链(模块 meta / 词汇 meta 单源 / resolveServiceDisplay 兜底链):
1793
+ * 兜底链 = 词汇 meta(slotMeta)> demand meta(catalog.services)> service id(逐字段回落)。
1794
+ * fixture:词汇 meta 停车查询 vs demand meta 车位查询——断言词汇胜出(防漂移锁)。
1795
+ */
1796
+ describe('B1 展示名链', () => {
1797
+ /** 展示名 fixture 基座:词汇物化(3 槽 2 meta)+ 停车模块(带 meta + demand meta)+ demo 厂商 */
1798
+ function displayHarness(): Harness {
1799
+ const app = createTempApp('tbox-display-');
1800
+ invalidateContractsResolver();
1801
+ const store = joinStore(app.appDir, 'contracts');
1802
+ writeFakeContractsPackage(store, {
1803
+ version: '0.9.0',
1804
+ slots: ['parking.query', 'auth.alipay-login', 'mall.info'],
1805
+ meta: {
1806
+ 'parking.query': { name: '停车查询', description: '费用报价、车场信息、可享权益' },
1807
+ 'auth.alipay-login': { name: '支付宝登录' },
1808
+ },
1809
+ });
1810
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
1811
+ mkdirDeep(join(app.appDir, 'packages', 'module-parking'));
1812
+ writeAppFile(
1813
+ app.appDir,
1814
+ 'packages/module-parking/tbox.module.json',
1815
+ JSON.stringify({
1816
+ schemaVersion: 1,
1817
+ id: 'module-parking',
1818
+ version: '0.1.0',
1819
+ kind: 'business',
1820
+ name: '停车缴费',
1821
+ description: '车辆查询与绑定、停车费用查询、缴费支付与开票',
1822
+ declare: {
1823
+ owns: [{ service: 'parking.query' }],
1824
+ // demand meta name=车位查询——应被词汇 meta 停车查询压过(词汇主源)
1825
+ consumes: [{ service: 'parking.query', optional: false, name: '车位查询' }],
1826
+ },
1827
+ dependencies: { modules: [] },
1828
+ env: [],
1829
+ }),
1830
+ );
1831
+ mkdirDeep(join(app.appDir, 'packages', 'provider-demo'));
1832
+ writeAppFile(
1833
+ app.appDir,
1834
+ 'packages/provider-demo/tbox.module.json',
1835
+ JSON.stringify({
1836
+ schemaVersion: 1,
1837
+ id: 'provider-demo',
1838
+ version: '0.1.0',
1839
+ kind: 'third-party',
1840
+ name: '万达',
1841
+ description: '万达厂商集成',
1842
+ declare: {
1843
+ providers: {
1844
+ slots: [{ service: 'parking.query', provider: 'wanda', implementation: 'wanda-parking@1', credentialType: 'apikey' }],
1845
+ },
1846
+ },
1847
+ dependencies: { modules: [] },
1848
+ env: [],
1849
+ }),
1850
+ );
1851
+ const tk = createAppToolkit(app.appDir);
1852
+ return { app, tk };
1853
+ }
1854
+
1855
+ it('T1 resolveServiceDisplay 三态:词汇 > demand > id;逐字段回落(词汇仅 description 时 demand title 可达)', () => {
1856
+ const walk = {
1857
+ slotMeta: {
1858
+ 'a.b': { name: '甲', description: '甲描述' },
1859
+ 'c.d': { description: '只有描述' },
1860
+ },
1861
+ catalog: {
1862
+ services: {
1863
+ 'a.b': { name: '乙', description: '乙描述' },
1864
+ 'c.d': { name: '丙' },
1865
+ },
1866
+ },
1867
+ } as unknown as WalkManifestsResult;
1868
+ // 词汇全量在场 → 词汇胜出
1869
+ expect(resolveServiceDisplay(walk, 'a.b')).toEqual({ name: '甲', description: '甲描述' });
1870
+ // 词汇仅 description → title 回落 demand、description 取词汇(逐字段回落)
1871
+ expect(resolveServiceDisplay(walk, 'c.d')).toEqual({ name: '丙', description: '只有描述' });
1872
+ // 双缺席 → demand
1873
+ expect(resolveServiceDisplay(walk, 'e.f')).toEqual({ name: 'e.f' });
1874
+ // 双双缺席 → id 兜底(title 恒有值)
1875
+ const bare = { slotMeta: {}, catalog: { services: {} } } as unknown as WalkManifestsResult;
1876
+ expect(resolveServiceDisplay(bare, 'x.y')).toEqual({ name: 'x.y' });
1877
+ });
1878
+
1879
+ it('T2 loadModulesView:模块 meta 填充;name 兜底', async () => {
1880
+ const h2 = displayHarness();
1881
+ const v = await h2.tk.loadModules();
1882
+ const parking = v.modules.find((m) => m.id === 'module-parking');
1883
+ expect(parking?.name).toBe('停车缴费');
1884
+ expect(parking?.description).toBe('车辆查询与绑定、停车费用查询、缴费支付与开票');
1885
+ // 无 meta 模块(provider 家族不在清单——用第二基座验证兜底)
1886
+ const h3 = harnessWithContracts();
1887
+ const bare = (await h3.tk.loadModules()).modules.find((m) => m.id === 'module-parking');
1888
+ expect(bare?.name).toBe('module-parking');
1889
+ expect(bare?.description).toBe('');
1890
+ h3.app.dispose();
1891
+ h2.app.dispose();
1892
+ });
1893
+
1894
+ it('T3 loadModuleDetailView:meta 恒发;services[] 词汇优先(demand 车位查询被词汇停车查询压过——防漂移锁)', async () => {
1895
+ const h2 = displayHarness();
1896
+ const v = await h2.tk.loadModule('module-parking');
1897
+ expect(v.name).toBe('停车缴费');
1898
+ expect(v.description).toContain('停车费用查询');
1899
+ const svc = v.services.find((s) => s.service === 'parking.query');
1900
+ expect(svc?.name).toBe('停车查询');
1901
+ expect(svc?.description).toBe('费用报价、车场信息、可享权益');
1902
+ expect(svc?.required).toBe(true);
1903
+ h2.app.dispose();
1904
+ });
1905
+
1906
+ it('T-O3 词汇在场键集:resolutions 键集 = 词汇(退化态 = 既有 O3——无 slots 物化基座)', async () => {
1907
+ const h2 = displayHarness();
1908
+ const batch = await h2.tk.loadServiceResolutions();
1909
+ expect(Object.keys(batch.resolutions).sort()).toEqual(['auth.alipay-login', 'mall.info', 'parking.query']);
1910
+ h2.app.dispose();
1911
+ });
1912
+
1913
+ // ── v7 双键寻址(factory 级——resolve-key 单点经七方法入口;id 优先/展示名兜底/多命中 404)──
1914
+
1915
+ it('v7-DK1 模块双键:展示名「停车缴费」≡ id module-parking(同一视图);读侧 id 恒优先', async () => {
1916
+ const h2 = displayHarness();
1917
+ const byName = await h2.tk.loadModule('停车缴费');
1918
+ const byId = await h2.tk.loadModule('module-parking');
1919
+ expect(byName.name).toBe(byId.name);
1920
+ expect(byName.ownedServices).toEqual(byId.ownedServices);
1921
+ h2.app.dispose();
1922
+ });
1923
+
1924
+ it('v7-DK2 服务双键:展示名「停车查询」→ parking.query(词汇 meta 源);含 include 富化同键', async () => {
1925
+ const h2 = displayHarness();
1926
+ const byName = await h2.tk.loadService('停车查询');
1927
+ expect(byName.service).toBe('parking.query');
1928
+ expect(byName.name).toBe('停车查询');
1929
+ h2.app.dispose();
1930
+ });
1931
+
1932
+ it('v7-DK3 多展示名命中 → 404 带候选(两模块同名;fail-visible 不静默取首)', async () => {
1933
+ const app = createTempApp('tbox-dk3-');
1934
+ invalidateContractsResolver();
1935
+ const store = joinStore(app.appDir, 'contracts');
1936
+ writeFakeContractsPackage(store, { version: '0.9.0' });
1937
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
1938
+ const manifest = (id: string): string =>
1939
+ JSON.stringify({ schemaVersion: 1, id, name: '同名模块', version: '0.1.0', kind: 'business', declare: {}, dependencies: { modules: [] }, env: [] });
1940
+ writeAppFile(app.appDir, 'packages/module-a/tbox.module.json', manifest('module-a'));
1941
+ writeAppFile(app.appDir, 'packages/module-b/tbox.module.json', manifest('module-b'));
1942
+ const tk = createAppToolkit(app.appDir);
1943
+ await expect(tk.loadModule('同名模块')).rejects.toMatchObject({
1944
+ code: 'MODULE_NOT_FOUND',
1945
+ httpStatus: 404,
1946
+ message: expect.stringContaining('id=module-a'),
1947
+ });
1948
+ await expect(tk.loadModule('同名模块')).rejects.toMatchObject({
1949
+ message: expect.stringContaining('改用模块 id'),
1950
+ });
1951
+ app.dispose();
1952
+ });
1953
+
1954
+ it('T4 loadAppView 平台条目:title 走链恒发;词汇无 meta 槽 = id 兜底', async () => {
1955
+ const h2 = displayHarness();
1956
+ const v = await h2.tk.loadApp();
1957
+ const byKey = Object.fromEntries(v.services.map((s) => [s.service, s]));
1958
+ // 平台条目 = 词汇 − 占用(parking.query 被模块占用)
1959
+ expect(Object.keys(byKey).sort()).toEqual(['auth.alipay-login', 'mall.info']);
1960
+ expect(byKey['auth.alipay-login']?.name).toBe('支付宝登录');
1961
+ expect(byKey['mall.info']?.name).toBe('mall.info');
1962
+ expect(byKey['auth.alipay-login']?.required).toBe(false);
1963
+ h2.app.dispose();
1964
+ });
1965
+
1966
+ it('T5 loadServiceDetailView:词汇优先 + id 兜底恒有值', async () => {
1967
+ const h2 = displayHarness();
1968
+ expect((await h2.tk.loadService('parking.query')).name).toBe('停车查询');
1969
+ expect((await h2.tk.loadService('mall.info')).name).toBe('mall.info');
1970
+ h2.app.dispose();
1971
+ });
1972
+
1973
+ it('T6 厂商 title 传播(walk 层首声明)+ /providers/:p services[] 走链', async () => {
1974
+ const h2 = displayHarness();
1975
+ const list = await h2.tk.loadProviders();
1976
+ const wanda = list.providers.find((p) => p.provider === 'wanda');
1977
+ expect(wanda?.name).toBe('万达');
1978
+ expect(wanda?.description).toBe('万达厂商集成');
1979
+ const detail = await h2.tk.loadProvider('wanda');
1980
+ expect(detail.name).toBe('万达');
1981
+ expect(detail.services['parking.query']?.name).toBe('停车查询');
1982
+ expect(detail.services['parking.query']?.description).toBe('费用报价、车场信息、可享权益');
1983
+ h2.app.dispose();
1984
+ });
1985
+ });