@tbox.cn/app-toolkit 0.4.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 (39) 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 +315 -144
  4. package/dist/index.js +9 -9
  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 +138 -85
  9. package/src/core/contracts-expected.ts +2 -2
  10. package/src/core/contracts-resolver.ts +12 -1
  11. package/src/core/module-schema.ts +87 -14
  12. package/src/dto.ts +48 -33
  13. package/src/factory.ts +62 -18
  14. package/src/index.ts +14 -7
  15. package/src/integrations/mock-bindings.ts +1 -1
  16. package/src/integrations/module-binding.ts +60 -0
  17. package/src/integrations/predicate-io.ts +26 -5
  18. package/src/integrations/predicate.ts +97 -57
  19. package/src/integrations/prune-bindings.ts +33 -14
  20. package/src/integrations/write.ts +43 -27
  21. package/src/views/app.ts +4 -4
  22. package/src/views/context.ts +11 -12
  23. package/src/views/integration-schemas.ts +61 -17
  24. package/src/views/modules.ts +29 -23
  25. package/src/views/providers.ts +5 -5
  26. package/src/views/service-detail.ts +1 -1
  27. package/src/views/service-resolutions.ts +14 -13
  28. package/tests/credentials-view.test.ts +2 -2
  29. package/tests/demo-app.ts +11 -6
  30. package/tests/dev-manifest-catalog.test.ts +14 -14
  31. package/tests/file-cache.test.ts +5 -5
  32. package/tests/module-schema.test.ts +106 -20
  33. package/tests/resolve-key.test.ts +154 -0
  34. package/tests/vendor-schema-keywords.test.ts +74 -0
  35. package/tests/views.test.ts +296 -166
  36. package/tests/write-core.test.ts +83 -0
  37. package/dist/chunk-KPD3LUH4.js +0 -1
  38. package/dist/contracts-resolver-E4SECBOG.js +0 -1
  39. package/src/integrations/domain-binding.ts +0 -81
@@ -5,7 +5,7 @@ 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
7
  import { walkManifests, type WalkManifestsResult } from '../src/assembly/walk-manifests.js';
8
- import { buildIntegrationFieldRegistry, loadModuleFormViews, loadModuleIntegrationSchemas } from '../src/views/integration-schemas.js';
8
+ import { buildIntegrationFieldRegistry, loadModuleFormViews, loadModuleIntegrationSchemas, loadServiceFieldRegistry } from '../src/views/integration-schemas.js';
9
9
  import { resolveServiceDisplay } from '../src/views/context.js';
10
10
  import type { ViewSnapshot } from '../src/views/context.js';
11
11
  import { URL_METHOD_MAP } from './naming-alignment.test.js';
@@ -42,11 +42,12 @@ function harnessWithContracts(
42
42
  'packages/module-parking/tbox.module.json',
43
43
  JSON.stringify({
44
44
  schemaVersion: 1,
45
- name: 'module-parking',
45
+ id: 'module-parking',
46
46
  version: '0.1.0',
47
47
  kind: 'business',
48
- contributes: {
49
- services: [{ service: 'parking.query', optional: false, title: '车位查询' }],
48
+ declare: {
49
+ owns: [{ service: 'parking.query' }],
50
+ consumes: [{ service: 'parking.query', optional: false, name: '车位查询' }],
50
51
  },
51
52
  dependencies: { modules: [] },
52
53
  env: [],
@@ -62,7 +63,7 @@ function harnessWithContracts(
62
63
  { id: 'mall-sh-01', name: '上海店' },
63
64
  ],
64
65
  defaultInstanceId: 'mall-bj-01',
65
- domains: { parking: { provider: 'wanda' } },
66
+ modules: { 'module-parking': { provider: 'wanda' } },
66
67
  services: {
67
68
  'parking.query': { provider: { provider: 'my-custom-api', credentialType: '-' }, implementation: 'my-custom-parking@1' },
68
69
  'auth.alipay-login': {},
@@ -104,9 +105,12 @@ function layerHarness(): {
104
105
  ].join(' '),
105
106
  });
106
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 });
107
111
  const schemas = {
108
- query: { type: 'object', properties: { baseUrl: { type: 'string', format: 'uri' } }, required: ['baseUrl'] },
109
- 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: '车场组' } } },
110
114
  };
111
115
  // v4.6 joycity 代数 fixture:R1(baseUrl 双无 enum / env 同集异序 / envMixed 混布)/
112
116
  // R2(options 深相等 / meta 异构)/ R3(lotId string≠integer / timeout number≠integer / untyped 无 type)/
@@ -115,14 +119,14 @@ function layerHarness(): {
115
119
  query: {
116
120
  type: 'object',
117
121
  properties: {
118
- baseUrl: { type: 'string', format: 'uri' },
119
- lotId: { type: 'string' },
120
- env: { type: 'string', enum: ['dev', 'staging', 'prod'] },
121
- envMixed: { type: 'string', enum: ['a', 'b'] },
122
- meta: { type: 'object', properties: { region: { type: 'string' } } },
123
- options: { type: 'object', properties: { x: { type: 'string' } } },
124
- untyped: { description: 'x' },
125
- 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' },
126
130
  },
127
131
  required: ['baseUrl', 'lotId'],
128
132
  },
@@ -130,15 +134,15 @@ function layerHarness(): {
130
134
  type: 'object',
131
135
  additionalProperties: false,
132
136
  properties: {
133
- apiKey: { type: 'string' },
134
- baseUrl: { type: 'string' },
135
- lotId: { type: 'integer' },
136
- env: { type: 'string', enum: ['prod', 'dev', 'staging'] },
137
- envMixed: { type: 'string' },
138
- meta: { type: 'object', properties: { zone: { type: 'number' } } },
139
- options: { type: 'object', properties: { x: { type: 'string' } } },
140
- untyped: { type: 'string' },
141
- 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' },
142
146
  },
143
147
  required: ['apiKey', 'options'],
144
148
  },
@@ -148,10 +152,10 @@ function layerHarness(): {
148
152
  'packages/provider-w/tbox.module.json',
149
153
  JSON.stringify({
150
154
  schemaVersion: 1,
151
- name: 'provider-w',
155
+ id: 'provider-w',
152
156
  version: '0.1.0',
153
157
  kind: 'business',
154
- contributes: {
158
+ declare: {
155
159
  providers: {
156
160
  slots: [
157
161
  { service: 'parking.query', provider: 'wanda', implementation: 'wanda-parking@1', credentialType: 'wanda-c', configSchema: 'schemas/query.json' },
@@ -171,10 +175,10 @@ function layerHarness(): {
171
175
  'packages/provider-j/tbox.module.json',
172
176
  JSON.stringify({
173
177
  schemaVersion: 1,
174
- name: 'provider-j',
178
+ id: 'provider-j',
175
179
  version: '0.1.0',
176
180
  kind: 'business',
177
- contributes: {
181
+ declare: {
178
182
  providers: {
179
183
  slots: [
180
184
  { service: 'parking.query', provider: 'joycity', implementation: 'joycity-parking@1', credentialType: 'joycity-c', configSchema: 'schemas/query.json' },
@@ -194,10 +198,10 @@ function layerHarness(): {
194
198
  'packages/provider-p/tbox.module.json',
195
199
  JSON.stringify({
196
200
  schemaVersion: 1,
197
- name: 'provider-p',
201
+ id: 'provider-p',
198
202
  version: '0.1.0',
199
203
  kind: 'business',
200
- contributes: {
204
+ declare: {
201
205
  providers: {
202
206
  slots: [{ service: 'parking.payment', provider: 'partial', implementation: 'partial-payment@1', credentialType: 'partial-c' }],
203
207
  },
@@ -211,10 +215,13 @@ function layerHarness(): {
211
215
  'packages/module-parking/tbox.module.json',
212
216
  JSON.stringify({
213
217
  schemaVersion: 1,
214
- name: 'module-parking',
218
+ id: 'module-parking',
215
219
  version: '0.1.0',
216
220
  kind: 'business',
217
- 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
+ },
218
225
  dependencies: { modules: [] },
219
226
  env: [],
220
227
  }),
@@ -226,7 +233,7 @@ function layerHarness(): {
226
233
  provider: 'wanda',
227
234
  instances: [{ id: 'mall-bj-01', name: '北京店' }],
228
235
  defaultInstanceId: 'mall-bj-01',
229
- domains: { parking: { provider: 'wanda' } },
236
+ modules: { 'module-parking': { provider: 'wanda' } },
230
237
  services: { 'parking.query': { provider: 'wanda', implementation: 'wanda-parking@1' } },
231
238
  }),
232
239
  );
@@ -259,8 +266,8 @@ describe('views/工厂(O 矩阵核心族)', () => {
259
266
  expect(Object.keys(appView)).toEqual(['services', 'integration']);
260
267
  const mod = await h.tk.loadModule('module-parking');
261
268
  // v4.4 D34:domainKeys/providerBindings/configEditable 显式化(include 两键可选缺席——F4)
262
- // B1 additive:模块 meta title/description 前置(恒发——兜底 name/''
263
- expect(Object.keys(mod)).toEqual(['title', 'description', 'domainKeys', 'integration', 'providerBindings', 'configEditable', 'services']);
269
+ // v7 词汇统一:模块展示名 name 前置(恒发——兜底 id
270
+ expect(Object.keys(mod)).toEqual(['name', 'description', 'ownedServices', 'integration', 'binding', 'configEditable', 'services']);
264
271
  const svc = await h.tk.loadService('parking.query');
265
272
  expect(Object.keys(svc).some((k) => ['resolution', 'issues', 'contracts'].includes(k))).toBe(false);
266
273
  // 静态端点在 <0.9 下照常(永不 503——O13 反例联动)
@@ -513,8 +520,8 @@ describe('O6/O7/O10/O14(FX-3 补全)', () => {
513
520
  'packages/provider-multi/tbox.module.json',
514
521
  JSON.stringify({
515
522
  schemaVersion: 1,
516
- name: 'provider-multi',
517
- contributes: {
523
+ id: 'provider-multi',
524
+ declare: {
518
525
  providers: {
519
526
  slots: [
520
527
  { service: 'parking.query', provider: 'joycity', implementation: 'joycity-parking@1', credentialType: 'joycity-c', configSchema: 'schemas/query.json' },
@@ -585,8 +592,11 @@ describe('O6/O7/O10/O14(FX-3 补全)', () => {
585
592
  // api.md §5 表
586
593
  'CREDENTIAL_FILE_MISSING', 'CREDENTIAL_TYPE_MISMATCH', 'IMPL_WITHOUT_PROVIDER', 'INSTANCE_SUPPLY_UNMATCHED',
587
594
  'SCHEMA_FILE_MISSING', 'DOMAIN_CONFIG_UNUSED', 'DOMAIN_CONFIG_INVALID', 'INSTANCE_KEY_NOT_REGISTERED',
588
- 'REALM_SWITCH_HINT', 'WILDCARD_ALIGNMENT', 'VENDOR_NOT_INSTALLED', 'BINDING_UNRESOLVED', 'DOMAIN_KEY_UNMATCHED',
595
+ 'REALM_SWITCH_HINT', 'WILDCARD_ALIGNMENT', 'VENDOR_NOT_INSTALLED', 'BINDING_UNRESOLVED',
589
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',
590
600
  // 谓词头映射表 additive(检查族编号映射:#3/#4/#5 内联/#13/v4)
591
601
  'SLOT_NOT_DECLARED', 'SLOT_NOT_CONFIGURED', 'INLINE_PROVIDER_INVALID', 'MULTI_INSTANCE_NO_DEFAULT', 'INSTANCE_DISABLED',
592
602
  ]);
@@ -636,18 +646,18 @@ describe('A2 错误面(P-4 notes 优先 / P-5 畸形包 503)', () => {
636
646
  });
637
647
  });
638
648
 
639
- describe('D34 多域模块钉死(v4.4 F3)', () => {
640
- it('D1 单域回归:domainKeys/providerBindings/configEditable 装配 + integration 原值', async () => {
649
+ describe('v6 所有权族(D34 多域钉死退役 → ownedServices/binding 单数重写)', () => {
650
+ it('D1 单槽回归:ownedServices/binding/configEditable 装配 + integration 集成位原值', async () => {
641
651
  const h2 = harnessWithContracts();
642
652
  const mod = await h2.tk.loadModule('module-parking');
643
- expect(mod.domainKeys).toEqual(['parking']);
653
+ expect(mod.ownedServices).toEqual(['parking.query']);
644
654
  expect(mod.configEditable).toBe(true);
645
- expect(mod.providerBindings).toEqual([{ domain: 'parking', effective: 'wanda', layer: 'module' }]);
655
+ expect(mod.binding).toEqual({ provider: 'wanda', layer: 'module' });
646
656
  expect(mod.integration).toEqual({ provider: 'wanda' });
647
657
  h2.app.dispose();
648
658
  });
649
659
 
650
- it('D2 多域模块:GET integration null + providerBindings 双条 + PUT 400 分支①(含 {} 清域)', async () => {
660
+ it('D2 多域归属合法化(v6 正例化——跨域 owns 单键落位):binding 单值作用全 owned + PUT 全量替换', async () => {
651
661
  const app = createTempApp('tbox-d2-');
652
662
  invalidateContractsResolver();
653
663
  const store = joinStore(app.appDir, 'contracts');
@@ -658,33 +668,30 @@ describe('D34 多域模块钉死(v4.4 F3)', () => {
658
668
  'packages/module-multi/tbox.module.json',
659
669
  JSON.stringify({
660
670
  schemaVersion: 1,
661
- name: 'module-multi',
671
+ id: 'module-multi',
662
672
  version: '0.1.0',
663
673
  kind: 'business',
664
- 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 }] },
665
675
  dependencies: { modules: [] },
666
676
  env: [],
667
677
  }),
668
678
  );
669
679
  const tk = createAppToolkit(app.appDir);
670
680
  const mod = await tk.loadModule('module-multi');
671
- expect(mod.domainKeys).toEqual(['member', 'parking']);
681
+ expect(mod.ownedServices).toEqual(['member.account', 'parking.query']);
672
682
  expect(mod.integration).toBeNull();
673
- expect(mod.configEditable).toBe(false);
674
- expect(mod.providerBindings).toEqual([
675
- { domain: 'member', effective: null, layer: null },
676
- { domain: 'parking', effective: null, layer: null },
677
- ]);
678
- await expect(tk.writeModuleIntegration('module-multi', { provider: 'wanda' })).rejects.toMatchObject({
679
- code: 'VALIDATION_FAILED',
680
- httpStatus: 400,
681
- message: expect.stringContaining('跨 2 个域(member、parking)'),
682
- });
683
- 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' });
684
691
  app.dispose();
685
692
  });
686
693
 
687
- it('D3 零域模块:domainKeys [] + PUT 400 分支②', async () => {
694
+ it('D3 零归属模块:ownedServices [] + configEditable false + PUT 400(zero-ownership)', async () => {
688
695
  const app = createTempApp('tbox-d3-');
689
696
  invalidateContractsResolver();
690
697
  const store = joinStore(app.appDir, 'contracts');
@@ -695,63 +702,95 @@ describe('D34 多域模块钉死(v4.4 F3)', () => {
695
702
  'packages/module-zero/tbox.module.json',
696
703
  JSON.stringify({
697
704
  schemaVersion: 1,
698
- name: 'module-zero',
705
+ id: 'module-zero',
699
706
  version: '0.1.0',
700
707
  kind: 'business',
701
- contributes: {},
708
+ declare: {},
702
709
  dependencies: { modules: [] },
703
710
  env: [],
704
711
  }),
705
712
  );
706
713
  const tk = createAppToolkit(app.appDir);
707
714
  const mod = await tk.loadModule('module-zero');
708
- expect(mod.domainKeys).toEqual([]);
709
- expect(mod.providerBindings).toEqual([]);
715
+ expect(mod.ownedServices).toEqual([]);
716
+ expect(mod.binding).toEqual({ provider: null, layer: null });
710
717
  expect(mod.integration).toBeNull();
711
718
  await expect(tk.writeModuleIntegration('module-zero', { provider: 'x' })).rejects.toMatchObject({
712
719
  code: 'VALIDATION_FAILED',
713
720
  httpStatus: 400,
714
- message: expect.stringContaining('无服务需求声明'),
721
+ message: expect.stringContaining('未声明任何 declare.owns'),
715
722
  });
716
723
  app.dispose();
717
724
  });
718
725
 
719
- it('D4 域键共享(P13):双方 configEditable false + PUT 400 分支③(他方同域异服务——并集计数)', async () => {
726
+ it('D4 v6 互斥归属:双模块 owns 同服务 OWNERSHIP_CONFLICT(先声明胜出)+ 双方可写(归属冲突≠写禁)', async () => {
720
727
  const app = createTempApp('tbox-d4-');
721
728
  invalidateContractsResolver();
722
729
  const store = joinStore(app.appDir, 'contracts');
723
730
  writeFakeContractsPackage(store, { version: '0.9.0' });
724
731
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
725
- const manifest = (name: string, service: string): string =>
732
+ const manifest = (id: string, service: string): string =>
726
733
  JSON.stringify({
727
734
  schemaVersion: 1,
728
- name,
735
+ id,
729
736
  version: '0.1.0',
730
737
  kind: 'business',
731
- contributes: { services: [{ service, optional: false }] },
738
+ declare: { owns: [{ service }], consumes: [{ service, optional: false }] },
732
739
  dependencies: { modules: [] },
733
740
  env: [],
734
741
  });
735
742
  writeAppFile(app.appDir, 'packages/module-a/tbox.module.json', manifest('module-a', 'parking.alpha'));
736
- 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'));
737
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 写禁)
738
754
  const a = await tk.loadModule('module-a');
739
755
  const b = await tk.loadModule('module-b');
740
- expect(a.configEditable).toBe(false);
741
- expect(b.configEditable).toBe(false);
742
- await expect(tk.writeModuleIntegration('module-a', { provider: 'x' })).rejects.toMatchObject({
743
- code: 'VALIDATION_FAILED',
744
- httpStatus: 400,
745
- 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'),
746
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']);
747
782
  app.dispose();
748
783
  });
749
784
 
750
- it('D5 死键扫尾:moduleDomainKey/moduleDomainKeyOf 全链删除(源文件内容断言)', () => {
785
+ it('D5 死键扫尾:providerBindings/moduleDomainWritable/domain-binding 全链删除(源文件内容断言)', () => {
751
786
  const read = (rel: string): string => readFileSync(fileURLToPath(new URL(rel, import.meta.url)), 'utf8');
752
- expect(read('../src/views/modules.ts')).not.toContain('moduleDomainKeyOf');
753
- expect(read('../src/integrations/write.ts')).not.toContain('moduleDomainKey');
754
- 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');
755
794
  });
756
795
  });
757
796
 
@@ -845,7 +884,7 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
845
884
  const h2 = layerHarness();
846
885
  const mod = await h2.tk.loadModule('module-parking', { include: ['integrationSchemas'] });
847
886
  expect(mod.integrationSchemas).toEqual({
848
- provider: 'wanda', // resolveDomainBinding 单源(domains.parking)
887
+ provider: 'wanda', // resolveModuleBinding 单源(modules['module-parking']
849
888
  services: {
850
889
  'parking.query': { configSchema: h2.schemas.query },
851
890
  'parking.payment': { configSchema: h2.schemas.parking },
@@ -874,7 +913,7 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
874
913
  'config/integrations.json',
875
914
  JSON.stringify({
876
915
  instances: [{ id: 'mall-bj-01', name: '北京店' }],
877
- domains: { parking: { provider: 'wanda' } },
916
+ modules: { 'module-parking': { provider: 'wanda' } },
878
917
  services: { 'parking.query': { provider: 'wanda', implementation: 'wanda-parking@1' } },
879
918
  }),
880
919
  );
@@ -903,10 +942,10 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
903
942
  'packages/module-multi/tbox.module.json',
904
943
  JSON.stringify({
905
944
  schemaVersion: 1,
906
- name: 'module-multi',
945
+ id: 'module-multi',
907
946
  version: '0.1.0',
908
947
  kind: 'business',
909
- 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 }] },
910
949
  dependencies: { modules: [] },
911
950
  env: [],
912
951
  }),
@@ -930,10 +969,10 @@ describe('模块清单 provider 家族排除(2026-09-16 双缺陷批——供
930
969
  'packages/provider-custom/tbox.module.json',
931
970
  JSON.stringify({
932
971
  schemaVersion: 1,
933
- name: 'provider-custom',
972
+ id: 'provider-custom',
934
973
  version: '0.1.0',
935
974
  kind: 'business',
936
- contributes: { providers: { slots: [] }, services: [] },
975
+ declare: { providers: { slots: [] }, consumes: [], owns: [] },
937
976
  dependencies: { modules: [] },
938
977
  env: [],
939
978
  }),
@@ -951,8 +990,8 @@ describe('模块清单 provider 家族排除(2026-09-16 双缺陷批——供
951
990
  join('node_modules', '@tbox.cn', 'app-provider-mock', 'tbox.module.json'),
952
991
  JSON.stringify({
953
992
  schemaVersion: 1,
954
- name: 'provider-mock',
955
- contributes: {
993
+ id: 'provider-mock',
994
+ declare: {
956
995
  providers: { slots: [{ service: 'parking.query', provider: 'mock', implementation: 'mock-parking@1', credentialType: '-' }] },
957
996
  },
958
997
  }),
@@ -970,11 +1009,11 @@ describe('模块清单 provider 家族排除(2026-09-16 双缺陷批——供
970
1009
  expect(walk.modules.map((m) => m.id).sort()).toEqual(['module-parking', 'provider-custom', 'vendor-mock']);
971
1010
  // 详情 200 保持(装包仍合法——零域空态;写通道零域由 D34 守卫拦)
972
1011
  const custom = await h2.tk.loadModule('provider-custom');
973
- expect(custom.domainKeys).toEqual([]);
1012
+ expect(custom.ownedServices).toEqual([]);
974
1013
  expect(custom.configEditable).toBe(false);
975
1014
  expect(custom.services).toEqual([]);
976
1015
  const mock = await h2.tk.loadModule('vendor-mock');
977
- expect(mock.domainKeys).toEqual([]);
1016
+ expect(mock.ownedServices).toEqual([]);
978
1017
  // 供给轴不受影响:provider-mock 槽位照常进 catalog(清单排除不伤聚合)
979
1018
  const providers = await h2.tk.loadProviders();
980
1019
  expect(providers.providers.map((p) => p.provider)).toContain('mock');
@@ -1009,10 +1048,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1009
1048
  'packages/provider-w/tbox.module.json',
1010
1049
  JSON.stringify({
1011
1050
  schemaVersion: 1,
1012
- name: 'provider-w',
1051
+ id: 'provider-w',
1013
1052
  version: '0.1.0',
1014
1053
  kind: 'business',
1015
- contributes: {
1054
+ declare: {
1016
1055
  providers: {
1017
1056
  slots: [
1018
1057
  { service: 'parking.query', provider: 'wanda', implementation: 'wanda-parking@1', credentialType: 'wanda-c', configSchema: 'schemas/query.json' },
@@ -1031,10 +1070,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1031
1070
  'packages/provider-multi/tbox.module.json',
1032
1071
  JSON.stringify({
1033
1072
  schemaVersion: 1,
1034
- name: 'provider-multi',
1073
+ id: 'provider-multi',
1035
1074
  version: '0.1.0',
1036
1075
  kind: 'business',
1037
- contributes: {
1076
+ declare: {
1038
1077
  providers: {
1039
1078
  slots: [
1040
1079
  { service: 'parking.query', provider: 'joycity', implementation: 'joycity-parking@1', credentialType: 'joycity-c', configSchema: 'schemas/query.json' },
@@ -1054,10 +1093,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1054
1093
  'packages/module-parking/tbox.module.json',
1055
1094
  JSON.stringify({
1056
1095
  schemaVersion: 1,
1057
- name: 'module-parking',
1096
+ id: 'module-parking',
1058
1097
  version: '0.1.0',
1059
1098
  kind: 'business',
1060
- contributes: { services: [{ service: 'parking.query', optional: false }] },
1099
+ declare: { owns: [{ service: 'parking.query' }], consumes: [{ service: 'parking.query', optional: false }] },
1061
1100
  dependencies: { modules: [] },
1062
1101
  env: [],
1063
1102
  }),
@@ -1139,12 +1178,12 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1139
1178
 
1140
1179
  it('E6 域级联(分支①)+ ghost 域厂商诚实态', async () => {
1141
1180
  const h = eHarness();
1142
- writeIntegrations(h.app.appDir, { domains: { parking: { provider: 'wanda' } }, services: { 'parking.query': {} } });
1181
+ writeIntegrations(h.app.appDir, { modules: { 'module-parking': { provider: 'wanda' } }, services: { 'parking.query': {} } });
1143
1182
  const view = await h.tk.loadService('parking.query', { include: ['integrationSchemas'] });
1144
1183
  expect(view.integrationSchemas).toEqual({ provider: 'wanda', configSchema: h.schemas.wQuery });
1145
1184
  // ghost 域厂商(catalog 外)→ configSchema null(诊断归谓词 #11——富化非门控)
1146
1185
  const h2 = eHarness();
1147
- 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': {} } });
1148
1187
  const ghost = await h2.tk.loadService('parking.query', { include: ['integrationSchemas'] });
1149
1188
  expect(ghost.integrationSchemas).toEqual({ provider: 'ghost-vendor', configSchema: null });
1150
1189
  h.app.dispose();
@@ -1216,10 +1255,10 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1216
1255
  });
1217
1256
 
1218
1257
  it('E14 malformed → 整字段 null(不落域级联——分支回归锚)', async () => {
1219
- // ① 坏对象(缺 credentialType)——配 domains 佐证未走域级联
1258
+ // ① 坏对象(缺 credentialType)——配 modules 佐证未走模块级联
1220
1259
  const h = eHarness();
1221
1260
  writeIntegrations(h.app.appDir, {
1222
- domains: { parking: { provider: 'wanda' } },
1261
+ modules: { 'module-parking': { provider: 'wanda' } },
1223
1262
  services: { 'parking.query': { provider: { provider: 'my-api' } } },
1224
1263
  });
1225
1264
  const bad = await h.tk.loadService('parking.query', { include: ['integrationSchemas'] });
@@ -1227,7 +1266,7 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1227
1266
  // ② provider: null(谓词 #5 同判 INLINE_PROVIDER_INVALID——坏形态非缺席)
1228
1267
  const h2 = eHarness();
1229
1268
  writeIntegrations(h2.app.appDir, {
1230
- domains: { parking: { provider: 'wanda' } },
1269
+ modules: { 'module-parking': { provider: 'wanda' } },
1231
1270
  services: { 'parking.query': { provider: null } },
1232
1271
  });
1233
1272
  const nul = await h2.tk.loadService('parking.query', { include: ['integrationSchemas'] });
@@ -1239,7 +1278,7 @@ describe('v4.7 服务级 include(D38——生效绑定单体)', () => {
1239
1278
  it('E15 provider 空串 → 走域级联(非 null)', async () => {
1240
1279
  const h = eHarness();
1241
1280
  writeIntegrations(h.app.appDir, {
1242
- domains: { parking: { provider: 'wanda' } },
1281
+ modules: { 'module-parking': { provider: 'wanda' } },
1243
1282
  services: { 'parking.query': { provider: '' } },
1244
1283
  });
1245
1284
  const view = await h.tk.loadService('parking.query', { include: ['integrationSchemas'] });
@@ -1264,15 +1303,15 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1264
1303
  unsupplied: [],
1265
1304
  additionalPropertiesAllowed: false,
1266
1305
  fields: [
1267
- { key: 'baseUrl', schema: { type: 'string', format: 'uri' }, sources: [src('parking.query', true), src('parking.payment', false)] },
1268
- { key: 'lotId', schema: null, sources: [src('parking.query', true), src('parking.payment', false)] },
1269
- { key: 'env', schema: { type: 'string', enum: ['dev', 'staging', 'prod'] }, sources: [src('parking.query', false), src('parking.payment', false)] },
1270
- { key: 'envMixed', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1271
- { key: 'meta', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1272
- { key: 'options', schema: { type: 'object', properties: { x: { type: 'string' } } }, sources: [src('parking.query', false), src('parking.payment', true)] },
1273
- { key: 'untyped', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1274
- { key: 'timeout', schema: null, sources: [src('parking.query', false), src('parking.payment', false)] },
1275
- { 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)] },
1276
1315
  ],
1277
1316
  };
1278
1317
 
@@ -1283,15 +1322,15 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1283
1322
  unsupplied: [],
1284
1323
  additionalPropertiesAllowed: false,
1285
1324
  fields: [
1286
- { key: 'apiKey', schema: { type: 'string' }, sources: [src('parking.payment', true)] },
1287
- { key: 'baseUrl', schema: { type: 'string' }, sources: [src('parking.payment', false), src('parking.query', true)] },
1288
- { key: 'lotId', schema: null, sources: [src('parking.payment', false), src('parking.query', true)] },
1289
- { key: 'env', schema: { type: 'string', enum: ['prod', 'dev', 'staging'] }, sources: [src('parking.payment', false), src('parking.query', false)] },
1290
- { key: 'envMixed', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1291
- { key: 'meta', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1292
- { key: 'options', schema: { type: 'object', properties: { x: { type: 'string' } } }, sources: [src('parking.payment', true), src('parking.query', false)] },
1293
- { key: 'untyped', schema: null, sources: [src('parking.payment', false), src('parking.query', false)] },
1294
- { 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)] },
1295
1334
  ],
1296
1335
  };
1297
1336
 
@@ -1301,15 +1340,15 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1301
1340
  /** E4:module 轴 moduleProvider=ghost-x(未知厂商——非 null 空集 + 全 demand 回填,demand 序) */
1302
1341
  const E4 = { provider: 'ghost-x', schemaless: [], unsupplied: ['parking.query', 'parking.payment'], additionalPropertiesAllowed: true, fields: [] };
1303
1342
 
1304
- /** E5:实际态(无轴参,wanda;scope = [query, payment] */
1343
+ /** E5:实际态(无轴参,wanda;scope = [query, payment])——parkLotId 未标记 global 被表单收窄(G 批负例) */
1305
1344
  const E5 = {
1306
1345
  provider: 'wanda',
1307
1346
  schemaless: [],
1308
1347
  unsupplied: [],
1309
1348
  additionalPropertiesAllowed: true,
1310
1349
  fields: [
1311
- { key: 'baseUrl', schema: { type: 'string', format: 'uri' }, sources: [src('parking.query', true)] },
1312
- { 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)] },
1313
1352
  ],
1314
1353
  };
1315
1354
 
@@ -1391,7 +1430,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1391
1430
 
1392
1431
  it('S5a-8/9 单源直通 + required 聚合(some;conflict 照常聚合)', () => {
1393
1432
  const single = reg({ 'a.one': { configSchema: { type: 'object', properties: { only: { type: 'string', enum: ['z'] } } } } });
1394
- 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)] });
1395
1434
  const some = reg({
1396
1435
  'a.one': { configSchema: { type: 'object', properties: { k: { type: 'string' } }, required: ['k'] } },
1397
1436
  'a.two': { configSchema: { type: 'object', properties: { k: { type: 'string' } } } },
@@ -1447,7 +1486,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1447
1486
  const appInc = await h2.tk.loadApp({ include: ['integrationFieldRegistry'], appProvider: 'joycity' });
1448
1487
  expect(appInc.integrationFieldRegistry).toEqual(E2);
1449
1488
  expect(appInc.integrationFieldRegistry?.fields[0].key).toBe('apiKey'); // 足迹字母序(vs E1 首键 baseUrl)
1450
- 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 先)
1451
1490
  const partial = await h2.tk.loadModule('module-parking', { include: ['integrationFieldRegistry'], moduleProvider: 'partial' });
1452
1491
  expect(partial.integrationFieldRegistry).toEqual(E3);
1453
1492
  const actual = await h2.tk.loadModule('module-parking', { include: ['integrationFieldRegistry'] });
@@ -1485,7 +1524,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1485
1524
  const actJoy = await h2.tk.loadModule('module-parking', { include: both });
1486
1525
  expect(actJoy.integrationFieldRegistry).toEqual(hypJoy.integrationFieldRegistry);
1487
1526
  expect(actJoy.integrationSchemas).toEqual(hypJoy.integrationSchemas);
1488
- // ② 跟随应用级(root.provider=wanda)≡ 清域(N1 删 domains.parking → root 兜底)后实读——非平凡:清域前实际态 = E1
1527
+ // ② 跟随应用级(root.provider=wanda)≡ 清模块位(N1 删 modules['module-parking'] → root 兜底)后实读——非平凡:清位前实际态 = E1
1489
1528
  const hypFollow = await h2.tk.loadModule('module-parking', { include: both, moduleProvider: 'wanda' });
1490
1529
  expect(hypFollow.integrationFieldRegistry).toEqual(E5);
1491
1530
  await h2.tk.writeModuleIntegration('module-parking', {});
@@ -1498,7 +1537,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1498
1537
  h2.app.dispose();
1499
1538
  });
1500
1539
 
1501
- it('S6 app 轴同构:假想 ≡ 写 root {provider:joycity} 后实读(domains/services 不受扰)', async () => {
1540
+ it('S6 app 轴同构:假想 ≡ 写 root {provider:joycity} 后实读(modules/services 不受扰)', async () => {
1502
1541
  const h2 = layerHarness();
1503
1542
  const hyp = await h2.tk.loadApp({ include: ['integrationFieldRegistry'], appProvider: 'joycity' });
1504
1543
  const cur = await h2.tk.loadApp();
@@ -1557,7 +1596,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1557
1596
  moduleProvider: 'joycity',
1558
1597
  });
1559
1598
  expect(v.integration).toEqual({ provider: 'wanda' });
1560
- expect(v.providerBindings).toEqual([{ domain: 'parking', effective: 'wanda', layer: 'module' }]);
1599
+ expect(v.binding).toEqual({ provider: 'wanda', layer: 'module' });
1561
1600
  expect(v.configEditable).toBe(true);
1562
1601
  expect(v.integrationFieldRegistry?.provider).toBe('joycity'); // 表单层重定向
1563
1602
  expect(v.integrationSchemas?.provider).toBe('joycity');
@@ -1573,7 +1612,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1573
1612
  'config/integrations.json',
1574
1613
  JSON.stringify({
1575
1614
  instances: [{ id: 'mall-bj-01', name: '北京店' }],
1576
- domains: { parking: { provider: 'joycity' } },
1615
+ modules: { 'module-parking': { provider: 'joycity' } },
1577
1616
  services: { 'parking.query': { provider: 'wanda', implementation: 'wanda-parking@1' } },
1578
1617
  }),
1579
1618
  );
@@ -1583,7 +1622,7 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1583
1622
  ).toBeNull();
1584
1623
  h3.app.dispose();
1585
1624
 
1586
- // 可写未绑定(无 domains root.provider——无轴参 → null)
1625
+ // 可写未绑定(无 modules 位无 root.provider——无轴参 → null)
1587
1626
  const h4 = layerHarness();
1588
1627
  writeAppFile(
1589
1628
  h4.app.appDir,
@@ -1599,29 +1638,39 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1599
1638
  expect(unbound.configEditable).toBe(true);
1600
1639
  h4.app.dispose();
1601
1640
 
1602
- // D34 三形态(多域/零域/共享键——带 moduleProvider 仍 null
1641
+ // v6 形态(D34 三形态退役):零归属 → null(带 moduleProvider 仍 null——不豁免);
1642
+ // 多域/跨域归属合法化 → 非 null(override 可计算;unsupplied 回填非供给归属槽)
1603
1643
  const d34 = createTempApp('tbox-v46-d34-');
1604
1644
  invalidateContractsResolver();
1605
1645
  const store34 = joinStore(d34.appDir, 'contracts');
1606
1646
  writeFakeContractsPackage(store34, { version: '0.9.0' });
1607
1647
  linkContracts(d34.appDir, store34, { subdir: 'apps/server' });
1608
1648
  const need = (service: string): { service: string; optional: boolean } => ({ service, optional: false });
1609
- const manifest34 = (name: string, services: Array<{ service: string; optional: boolean }>): string =>
1610
- 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: [] });
1611
1651
  writeAppFile(d34.appDir, 'packages/module-multi/tbox.module.json', manifest34('module-multi', [need('parking.query'), need('member.account')]));
1612
1652
  writeAppFile(
1613
1653
  d34.appDir,
1614
1654
  'packages/module-zero/tbox.module.json',
1615
- 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: [] }),
1616
1656
  );
1617
1657
  writeAppFile(d34.appDir, 'packages/module-a/tbox.module.json', manifest34('module-a', [need('parking.alpha')]));
1618
1658
  writeAppFile(d34.appDir, 'packages/module-b/tbox.module.json', manifest34('module-b', [need('parking.beta')]));
1619
1659
  const tk34 = createAppToolkit(d34.appDir);
1620
- for (const id of ['module-multi', 'module-zero', 'module-a']) {
1621
- const v = await tk34.loadModule(id, { include: ['integrationFieldRegistry', 'integrationSchemas'], moduleProvider: 'joycity' });
1622
- expect(v.integrationFieldRegistry, id).toBeNull();
1623
- expect(v.integrationSchemas, id).toBeNull();
1624
- }
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: [] });
1625
1674
  d34.dispose();
1626
1675
  });
1627
1676
 
@@ -1682,7 +1731,13 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1682
1731
  const props = c.properties;
1683
1732
  const declared = props && typeof props === 'object' && !Array.isArray(props) ? Object.keys(props) : []; // 同款守卫
1684
1733
  const rev = registry.fields.filter((f) => f.sources.some((x) => x.service === s)).map((f) => f.key);
1685
- 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());
1686
1741
  }
1687
1742
  for (const s of registry.schemaless) expect(layer.services[s]?.configSchema).toBeNull();
1688
1743
  // unsupplied ≡ demand − scope 键集
@@ -1692,6 +1747,39 @@ describe('v4.6 D36/D37(integrationFieldRegistry + 轴向假想绑定)', () =
1692
1747
  h2.app.dispose();
1693
1748
  });
1694
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
+
1695
1783
  it('S8 app 轴:unsupplied ≡ [](结构性:足迹 ⊆ 供给)', async () => {
1696
1784
  const h2 = layerHarness();
1697
1785
  const v = await h2.tk.loadApp({ include: ['integrationFieldRegistry'], appProvider: 'joycity' });
@@ -1715,8 +1803,8 @@ describe('B1 展示名链', () => {
1715
1803
  version: '0.9.0',
1716
1804
  slots: ['parking.query', 'auth.alipay-login', 'mall.info'],
1717
1805
  meta: {
1718
- 'parking.query': { title: '停车查询', description: '费用报价、车场信息、可享权益' },
1719
- 'auth.alipay-login': { title: '支付宝登录' },
1806
+ 'parking.query': { name: '停车查询', description: '费用报价、车场信息、可享权益' },
1807
+ 'auth.alipay-login': { name: '支付宝登录' },
1720
1808
  },
1721
1809
  });
1722
1810
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
@@ -1726,14 +1814,15 @@ describe('B1 展示名链', () => {
1726
1814
  'packages/module-parking/tbox.module.json',
1727
1815
  JSON.stringify({
1728
1816
  schemaVersion: 1,
1729
- name: 'module-parking',
1817
+ id: 'module-parking',
1730
1818
  version: '0.1.0',
1731
1819
  kind: 'business',
1732
- title: '停车缴费',
1820
+ name: '停车缴费',
1733
1821
  description: '车辆查询与绑定、停车费用查询、缴费支付与开票',
1734
- contributes: {
1735
- // demand meta title=车位查询——应被词汇 meta 停车查询压过(词汇主源)
1736
- services: [{ service: 'parking.query', optional: false, title: '车位查询' }],
1822
+ declare: {
1823
+ owns: [{ service: 'parking.query' }],
1824
+ // demand meta name=车位查询——应被词汇 meta 停车查询压过(词汇主源)
1825
+ consumes: [{ service: 'parking.query', optional: false, name: '车位查询' }],
1737
1826
  },
1738
1827
  dependencies: { modules: [] },
1739
1828
  env: [],
@@ -1745,12 +1834,12 @@ describe('B1 展示名链', () => {
1745
1834
  'packages/provider-demo/tbox.module.json',
1746
1835
  JSON.stringify({
1747
1836
  schemaVersion: 1,
1748
- name: 'provider-demo',
1837
+ id: 'provider-demo',
1749
1838
  version: '0.1.0',
1750
1839
  kind: 'third-party',
1751
- title: '万达',
1840
+ name: '万达',
1752
1841
  description: '万达厂商集成',
1753
- contributes: {
1842
+ declare: {
1754
1843
  providers: {
1755
1844
  slots: [{ service: 'parking.query', provider: 'wanda', implementation: 'wanda-parking@1', credentialType: 'apikey' }],
1756
1845
  },
@@ -1766,37 +1855,37 @@ describe('B1 展示名链', () => {
1766
1855
  it('T1 resolveServiceDisplay 三态:词汇 > demand > id;逐字段回落(词汇仅 description 时 demand title 可达)', () => {
1767
1856
  const walk = {
1768
1857
  slotMeta: {
1769
- 'a.b': { title: '甲', description: '甲描述' },
1858
+ 'a.b': { name: '甲', description: '甲描述' },
1770
1859
  'c.d': { description: '只有描述' },
1771
1860
  },
1772
1861
  catalog: {
1773
1862
  services: {
1774
- 'a.b': { title: '乙', description: '乙描述' },
1775
- 'c.d': { title: '丙' },
1863
+ 'a.b': { name: '乙', description: '乙描述' },
1864
+ 'c.d': { name: '丙' },
1776
1865
  },
1777
1866
  },
1778
1867
  } as unknown as WalkManifestsResult;
1779
1868
  // 词汇全量在场 → 词汇胜出
1780
- expect(resolveServiceDisplay(walk, 'a.b')).toEqual({ title: '甲', description: '甲描述' });
1869
+ expect(resolveServiceDisplay(walk, 'a.b')).toEqual({ name: '甲', description: '甲描述' });
1781
1870
  // 词汇仅 description → title 回落 demand、description 取词汇(逐字段回落)
1782
- expect(resolveServiceDisplay(walk, 'c.d')).toEqual({ title: '丙', description: '只有描述' });
1871
+ expect(resolveServiceDisplay(walk, 'c.d')).toEqual({ name: '丙', description: '只有描述' });
1783
1872
  // 双缺席 → demand
1784
- expect(resolveServiceDisplay(walk, 'e.f')).toEqual({ title: 'e.f' });
1873
+ expect(resolveServiceDisplay(walk, 'e.f')).toEqual({ name: 'e.f' });
1785
1874
  // 双双缺席 → id 兜底(title 恒有值)
1786
1875
  const bare = { slotMeta: {}, catalog: { services: {} } } as unknown as WalkManifestsResult;
1787
- expect(resolveServiceDisplay(bare, 'x.y')).toEqual({ title: 'x.y' });
1876
+ expect(resolveServiceDisplay(bare, 'x.y')).toEqual({ name: 'x.y' });
1788
1877
  });
1789
1878
 
1790
1879
  it('T2 loadModulesView:模块 meta 填充;name 兜底', async () => {
1791
1880
  const h2 = displayHarness();
1792
1881
  const v = await h2.tk.loadModules();
1793
1882
  const parking = v.modules.find((m) => m.id === 'module-parking');
1794
- expect(parking?.title).toBe('停车缴费');
1883
+ expect(parking?.name).toBe('停车缴费');
1795
1884
  expect(parking?.description).toBe('车辆查询与绑定、停车费用查询、缴费支付与开票');
1796
1885
  // 无 meta 模块(provider 家族不在清单——用第二基座验证兜底)
1797
1886
  const h3 = harnessWithContracts();
1798
1887
  const bare = (await h3.tk.loadModules()).modules.find((m) => m.id === 'module-parking');
1799
- expect(bare?.title).toBe('module-parking');
1888
+ expect(bare?.name).toBe('module-parking');
1800
1889
  expect(bare?.description).toBe('');
1801
1890
  h3.app.dispose();
1802
1891
  h2.app.dispose();
@@ -1805,10 +1894,10 @@ describe('B1 展示名链', () => {
1805
1894
  it('T3 loadModuleDetailView:meta 恒发;services[] 词汇优先(demand 车位查询被词汇停车查询压过——防漂移锁)', async () => {
1806
1895
  const h2 = displayHarness();
1807
1896
  const v = await h2.tk.loadModule('module-parking');
1808
- expect(v.title).toBe('停车缴费');
1897
+ expect(v.name).toBe('停车缴费');
1809
1898
  expect(v.description).toContain('停车费用查询');
1810
1899
  const svc = v.services.find((s) => s.service === 'parking.query');
1811
- expect(svc?.title).toBe('停车查询');
1900
+ expect(svc?.name).toBe('停车查询');
1812
1901
  expect(svc?.description).toBe('费用报价、车场信息、可享权益');
1813
1902
  expect(svc?.required).toBe(true);
1814
1903
  h2.app.dispose();
@@ -1821,22 +1910,63 @@ describe('B1 展示名链', () => {
1821
1910
  h2.app.dispose();
1822
1911
  });
1823
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
+
1824
1954
  it('T4 loadAppView 平台条目:title 走链恒发;词汇无 meta 槽 = id 兜底', async () => {
1825
1955
  const h2 = displayHarness();
1826
1956
  const v = await h2.tk.loadApp();
1827
1957
  const byKey = Object.fromEntries(v.services.map((s) => [s.service, s]));
1828
1958
  // 平台条目 = 词汇 − 占用(parking.query 被模块占用)
1829
1959
  expect(Object.keys(byKey).sort()).toEqual(['auth.alipay-login', 'mall.info']);
1830
- expect(byKey['auth.alipay-login']?.title).toBe('支付宝登录');
1831
- expect(byKey['mall.info']?.title).toBe('mall.info');
1960
+ expect(byKey['auth.alipay-login']?.name).toBe('支付宝登录');
1961
+ expect(byKey['mall.info']?.name).toBe('mall.info');
1832
1962
  expect(byKey['auth.alipay-login']?.required).toBe(false);
1833
1963
  h2.app.dispose();
1834
1964
  });
1835
1965
 
1836
1966
  it('T5 loadServiceDetailView:词汇优先 + id 兜底恒有值', async () => {
1837
1967
  const h2 = displayHarness();
1838
- expect((await h2.tk.loadService('parking.query')).title).toBe('停车查询');
1839
- expect((await h2.tk.loadService('mall.info')).title).toBe('mall.info');
1968
+ expect((await h2.tk.loadService('parking.query')).name).toBe('停车查询');
1969
+ expect((await h2.tk.loadService('mall.info')).name).toBe('mall.info');
1840
1970
  h2.app.dispose();
1841
1971
  });
1842
1972
 
@@ -1844,11 +1974,11 @@ describe('B1 展示名链', () => {
1844
1974
  const h2 = displayHarness();
1845
1975
  const list = await h2.tk.loadProviders();
1846
1976
  const wanda = list.providers.find((p) => p.provider === 'wanda');
1847
- expect(wanda?.title).toBe('万达');
1977
+ expect(wanda?.name).toBe('万达');
1848
1978
  expect(wanda?.description).toBe('万达厂商集成');
1849
1979
  const detail = await h2.tk.loadProvider('wanda');
1850
- expect(detail.title).toBe('万达');
1851
- expect(detail.services['parking.query']?.title).toBe('停车查询');
1980
+ expect(detail.name).toBe('万达');
1981
+ expect(detail.services['parking.query']?.name).toBe('停车查询');
1852
1982
  expect(detail.services['parking.query']?.description).toBe('费用报价、车场信息、可享权益');
1853
1983
  h2.app.dispose();
1854
1984
  });