@tbox.cn/app-toolkit 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -119,7 +119,7 @@ describe('walkManifests · dev-manifest catalog 通道(聚合宽/声明窄)'
119
119
  * (local/codegen 全量副本——mall 生成应用主形态,不依赖 install)。
120
120
  * 修复前探测只扫包根 → 三形态全 miss(vocabulary 恒空)。
121
121
  */
122
- describe('walkManifests · 词汇探测三跳 + slotMeta(B0', () => {
122
+ describe('walkManifests · 词汇探测四跳 + slotMeta(B0;v7 += 跳⓪ config override 层)', () => {
123
123
  it('P1: nm 包根布局(跳①)→ vocabulary 命中;slotMeta 透传', () => {
124
124
  app = createTempApp('tbox-probe-');
125
125
  const store = join(app.appDir, '_store-a');
@@ -185,6 +185,44 @@ describe('walkManifests · 词汇探测三跳 + slotMeta(B0)', () => {
185
185
  expect(walk.slotMeta).toEqual({ 'e.f': { name: '甲' } }); // 空串 description 不入表(与 title 守卫对称)
186
186
  });
187
187
 
188
+ it('P6: 跳⓪ config override 层——meta per-service 覆盖真源 + 无键键穿透真源(首声明 = config 最前)', () => {
189
+ app = createTempApp('tbox-probe-');
190
+ const store = join(app.appDir, '_store-truth');
191
+ writeFakeContractsPackage(store, {
192
+ slots: ['a.b', 'c.d'],
193
+ meta: { 'a.b': { name: '真源甲' }, 'c.d': { name: '真源丙' } },
194
+ });
195
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
196
+ // 用户 override:只改名 a.b;c.d 不写(穿透真源);e.f 仅快照声明(幻影语义由 doctor 报告)
197
+ writeFileSync(
198
+ join(app.appDir, 'config', 'service-slots.json'),
199
+ JSON.stringify({ $comment: { seededAt: 'x' }, slots: ['a.b', 'e.f'], meta: { 'a.b': { name: '用户改名甲' } } }),
200
+ );
201
+
202
+ const walk = walkManifests(app.appDir);
203
+ // 词汇并集:config ∪ 真源(config 不遮蔽真源新增)
204
+ expect(walk.vocabulary).toEqual(['a.b', 'c.d', 'e.f']);
205
+ // meta 覆盖:config 先声明胜出;未覆盖键穿透真源
206
+ expect(walk.slotMeta['a.b']).toEqual({ name: '用户改名甲' });
207
+ expect(walk.slotMeta['c.d']).toEqual({ name: '真源丙' });
208
+ });
209
+
210
+ it('P7: 跳⓪ 快照 meta 恒空(seed 形态)→ 真源 meta 恒穿透;$comment 键零解析影响', () => {
211
+ app = createTempApp('tbox-probe-');
212
+ const store = join(app.appDir, '_store-seed');
213
+ writeFakeContractsPackage(store, { slots: ['a.b'], meta: { 'a.b': { name: '真源甲' } } });
214
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
215
+ // seed-once 产物形态:slots 全量 + meta 空对象
216
+ writeFileSync(
217
+ join(app.appDir, 'config', 'service-slots.json'),
218
+ JSON.stringify({ $comment: { seededAt: '2026-09-21', slots: 1 }, slots: ['a.b'], meta: {} }),
219
+ );
220
+
221
+ const walk = walkManifests(app.appDir);
222
+ expect(walk.vocabulary).toEqual(['a.b']);
223
+ expect(walk.slotMeta['a.b']).toEqual({ name: '真源甲' }); // 空 meta 不夺覆盖权
224
+ });
225
+
188
226
  it('P6: 无契约包(退化态)→ vocabulary 空 + slotMeta 空(P9 退化路径语义锁)', () => {
189
227
  app = createTempApp('tbox-probe-');
190
228
  const walk = walkManifests(app.appDir);
@@ -24,7 +24,7 @@ interface Harness {
24
24
  }
25
25
 
26
26
  function harnessWithContracts(
27
- opts: { version?: string; withStrict?: boolean; missingEvaluationExports?: boolean } = {},
27
+ opts: { version?: string; withStrict?: boolean; missingEvaluationExports?: boolean; resolveBody?: string[] } = {},
28
28
  ): Harness {
29
29
  const app = createTempApp('tbox-views-');
30
30
  invalidateContractsResolver();
@@ -33,6 +33,7 @@ function harnessWithContracts(
33
33
  version: opts.version ?? '0.9.0',
34
34
  withStrict: opts.withStrict ?? true,
35
35
  missingEvaluationExports: opts.missingEvaluationExports,
36
+ ...(opts.resolveBody ? { resolveBody: opts.resolveBody.join(' ') } : {}),
36
37
  });
37
38
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
38
39
  // 应用结构:parking 模块(packages/ 约定发现)+ 注册表两实例 + integrations.json
@@ -378,6 +379,41 @@ describe('views/工厂(O 矩阵核心族)', () => {
378
379
  expect(single.resolution.inheritance).toHaveLength(4);
379
380
  });
380
381
 
382
+ it('O17 v6 ownerOf 视图轴透传(回归锁——fef9a71a 漏改点):求值单源带 walk.ownership 第 5 参;modules 级 config 不触发 fail-visible', async () => {
383
+ // 假 contracts 复刻 v6 fail-visible 语义 + 回显 ownerOf:缺席 + modules 绑定面 → binding-unresolved;
384
+ // 在场 → effective.provider 前缀回显 owner 模块 id(证明透传值 = walk.ownership 内容本身)
385
+ const ownerEcho = harnessWithContracts({
386
+ resolveBody: [
387
+ 'const modBinding = integrations && integrations.modules',
388
+ ' && Object.values(integrations.modules).some((m) => m.provider != null || m.config != null);',
389
+ 'if ((ownerOf === undefined || ownerOf === null) && modBinding) return { status: "binding-unresolved", message: "OWNEROF_MISSING" };',
390
+ 'const owner = ownerOf ? ownerOf[service] : undefined;',
391
+ 'const slot = integrations && integrations.services && integrations.services[service];',
392
+ 'const prov = (slot && slot.provider) || "root-mock";',
393
+ 'return { status: "ok", effective: { provider: owner ? owner + ":" + prov : prov, implementation: "impl@1", config: {} } };',
394
+ ],
395
+ });
396
+ // 触发形态(bug 实景):modules 仅 config(无 provider)+ 空 slot——依赖 ownerOf 注入才能过 fail-visible
397
+ writeAppFile(
398
+ ownerEcho.app.appDir,
399
+ 'config/integrations.json',
400
+ JSON.stringify({
401
+ provider: 'root-mock',
402
+ modules: { 'module-parking': { config: { parkName: '示例车场' } } },
403
+ services: { 'parking.query': {}, 'auth.alipay-login': {} },
404
+ }),
405
+ );
406
+ const single = await ownerEcho.tk.loadServiceResolution('parking.query');
407
+ // ① fail-visible 未触发(ownerOf 已注入)
408
+ expect(single.resolution.status).toBe('ok');
409
+ // ② 透传值 = walk.ownership 内容(parking.query 归属 module-parking——harness manifest declare.owns)
410
+ expect(single.resolution.provider).toBe('module-parking:root-mock');
411
+ // ③ 无主服务回退无前缀(排除「任意 truthy 值即绿」的假阳性)
412
+ const orphan = await ownerEcho.tk.loadServiceResolution('auth.alipay-login');
413
+ expect(orphan.resolution.provider).toBe('root-mock');
414
+ ownerEcho.app.dispose();
415
+ });
416
+
381
417
  it('O3 键集 = 服务词汇 ∪ 已配置;required echo(OR 聚合 + 平台 false)', async () => {
382
418
  // vocabulary 空(无契约包 fixture 物化 slots——退化态回归锁;词汇在场态见 B1 族 T-O3)
383
419
  const batch = await h.tk.loadServiceResolutions();
@@ -393,28 +429,29 @@ describe('views/工厂(O 矩阵核心族)', () => {
393
429
  expect(batch.issues.length).toBeGreaterThanOrEqual(single.issues.length);
394
430
  });
395
431
 
396
- it('O13 门控正反例:<0.9 求值 503 三分支 + 静态 200;多实例无显式默认 → 通配口径', async () => {
432
+ it('O13 门控正反例:<0.9 读求值轴降级骨架(v7 503 退役)+ 静态 200 + 写端点 503 保留;多实例无显式默认 → 通配口径', async () => {
397
433
  // 正例:≥0.9 求值可用(harness 默认)
398
434
  const batch = await h.tk.loadServiceResolutions();
399
435
  expect(batch.contracts.strictValidation).toBe(true);
400
436
  h.app.dispose();
401
437
 
402
- // 反例:缺 zod 面(0.8.2)→ 求值 throw 503 + message 升级指引;静态照常
438
+ // 反例:缺 zod 面(0.8.2)→ 读求值轴降级骨架 + N1 升级指引入 statusMessage;静态照常
403
439
  const low = harnessWithContracts({ version: '0.8.2', withStrict: false });
404
- await expect(low.tk.loadServiceResolutions()).rejects.toMatchObject({
405
- code: 'CONTRACTS_NOT_RESOLVED',
406
- httpStatus: 503,
407
- });
408
- await expect(low.tk.loadServiceResolutions()).rejects.toThrow(/升级应用 @tbox.cn\/app-contracts/);
440
+ const lowBatch = await low.tk.loadServiceResolutions();
441
+ expect(lowBatch.contracts.strictValidation).toBe(false);
442
+ const lowEntries = Object.values(lowBatch.resolutions);
443
+ expect(lowEntries.length).toBeGreaterThan(0);
444
+ expect(lowEntries.every((e) => e.status === 'evaluation-unavailable')).toBe(true);
445
+ expect(lowEntries[0]?.statusMessage).toMatch(/升级应用 @tbox\.cn\/app-contracts/);
446
+ expect(lowBatch.issues).toEqual([]);
409
447
  // 静态永不 503
410
448
  const appView = await low.tk.loadApp();
411
449
  expect(appView.services).toBeInstanceOf(Array);
412
- // 写端点同样门控
450
+ // 写端点 503 门控保留(policy at edge 收窄至写/迁移面)
413
451
  await expect(
414
452
  low.tk.writeServiceIntegration('parking.query', { provider: 'mock' }),
415
453
  ).rejects.toMatchObject({ code: 'CONTRACTS_NOT_RESOLVED', httpStatus: 503 });
416
454
  low.app.dispose();
417
-
418
455
  // 默认实例委托:无 defaultInstanceId(多实例)→ '*' 通配口径(normalizeInstances null → '*')
419
456
  const wild = harnessWithContracts();
420
457
  writeAppFile(
@@ -698,41 +735,102 @@ describe('O6/O7/O10/O14(FX-3 补全)', () => {
698
735
  });
699
736
 
700
737
  describe('A2 错误面(P-4 notes 优先 / P-5 畸形包 503)', () => {
701
- it('M1 导入失败 message 含构建指引且不再误报未安装(P-4 notes 优先)', async () => {
738
+ it('M1 导入失败 message 含构建指引且不再误报未安装(P-4 notes 优先——v7 降级骨架 statusMessage 承载)', async () => {
702
739
  const app = createTempApp('tbox-m1-');
703
740
  invalidateContractsResolver();
704
741
  const store = joinStore(app.appDir, 'contracts');
705
742
  writeFakeContractsPackage(store, { version: '0.9.0', brokenDist: true });
706
743
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
744
+ writeAppFile(app.appDir, 'config/integrations.json', JSON.stringify({ provider: 'mock', services: { 'parking.query': {} } }));
707
745
  const tk = createAppToolkit(app.appDir);
708
- await expect(tk.loadServiceResolutions()).rejects.toThrow(/构建产物导入失败/);
709
- await expect(tk.loadServiceResolutions()).rejects.not.toThrow(/未安装/);
746
+ const batch = await tk.loadServiceResolutions();
747
+ const entry = batch.resolutions['parking.query'];
748
+ expect(entry.status).toBe('evaluation-unavailable');
749
+ expect(entry.statusMessage).toMatch(/构建产物导入失败/);
750
+ expect(entry.statusMessage).not.toMatch(/未安装/);
710
751
  app.dispose();
711
752
  });
712
753
 
713
- it('M2 <0.9 message 对齐 api.md 信封(需 ≥0.9.0——resolver note 文案)', async () => {
754
+ it('M4 无契约包(未安装)→ 降级骨架 + 声明实例枚举 + T2 空键集诚实态(问题②主形态)', async () => {
755
+ // T1 形态:contracts 未安装,walk 静态面在场(manifest demand/owns + integrations 配置)
756
+ const app = createTempApp('tbox-m4-');
757
+ invalidateContractsResolver();
758
+ writeAppFile(app.appDir, 'packages/module-parking/tbox.module.json', JSON.stringify({
759
+ schemaVersion: 1, id: 'module-parking', version: '0.1.0', kind: 'business',
760
+ declare: { owns: [{ service: 'parking.query' }], consumes: [{ service: 'parking.query', optional: false }] },
761
+ dependencies: { modules: [] },
762
+ env: [],
763
+ }));
764
+ writeAppFile(app.appDir, 'config/integrations.json', JSON.stringify({
765
+ provider: 'mock', instances: [{ id: 'mall-1' }], defaultInstanceId: 'mall-1',
766
+ services: { 'parking.query': { instances: { 'mall-1': { provider: 'wanda', implementation: 'wanda-parking@1' } } } },
767
+ }));
768
+ const tk = createAppToolkit(app.appDir);
769
+ const batch = await tk.loadServiceResolutions();
770
+ const entry = batch.resolutions['parking.query'];
771
+ expect(entry.status).toBe('evaluation-unavailable');
772
+ expect(entry.statusMessage).toMatch(/未安装/);
773
+ // 静态可算字段:module = walk.ownership(v6 归属单源,install id 轴——v6.1 起非 demand owners 包名轴);required = OR 聚合
774
+ expect(entry.module).toBe('module-parking');
775
+ expect(entry.required).toBe(true);
776
+ expect(batch.issues).toEqual([]);
777
+ // 单体:声明实例枚举(原始数据投影——root instances[].id)逐实例同标记;
778
+ // inheritance 四层静态投影(app=root.provider;instance=显式 defaultInstanceId 位声明——降级默认实例口径实证)
779
+ const detail = await tk.loadServiceResolution('parking.query');
780
+ expect(detail.resolution.instances.map((i) => i.instanceId)).toEqual(['mall-1']);
781
+ expect(detail.resolution.instances.every((i) => i.status === 'evaluation-unavailable')).toBe(true);
782
+ expect(detail.resolution.inheritance).toHaveLength(4);
783
+ expect(detail.resolution.inheritance.find((l) => l.layer === 'app')?.provider).toBe('mock');
784
+ const instLayer = detail.resolution.inheritance.find((l) => l.layer === 'instance');
785
+ expect(instLayer?.provider).toBe('wanda');
786
+ expect(instLayer?.implementation).toBe('wanda-parking@1');
787
+ // include 轴内嵌骨架(四面统一——P14 (b))
788
+ const appInc = await tk.loadApp({ include: ['serviceResolutions'] });
789
+ expect(appInc.serviceResolutions?.resolutions['parking.query']?.status).toBe('evaluation-unavailable');
790
+ app.dispose();
791
+
792
+ // T2 形态:无 manifest 无 integrations → 空键集诚实态(空列表非 503;envelope 携带原因)
793
+ const bare = createTempApp('tbox-m4b-');
794
+ invalidateContractsResolver();
795
+ const bareTk = createAppToolkit(bare.appDir);
796
+ const bareBatch = await bareTk.loadServiceResolutions();
797
+ expect(bareBatch.resolutions).toEqual({});
798
+ expect(bareBatch.contracts.version).toBeNull();
799
+ expect(bareBatch.contracts.notes.join()).toMatch(/未安装/);
800
+ bare.dispose();
801
+ });
802
+
803
+ it('M2 <0.9 message 对齐 api.md 信封(需 ≥0.9.0——resolver note 文案入降级 statusMessage)', async () => {
714
804
  const app = createTempApp('tbox-m2-');
715
805
  invalidateContractsResolver();
716
806
  const store = joinStore(app.appDir, 'contracts');
717
807
  writeFakeContractsPackage(store, { version: '0.8.2', withStrict: false });
718
808
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
809
+ writeAppFile(app.appDir, 'config/integrations.json', JSON.stringify({ provider: 'mock', services: { 'parking.query': {} } }));
719
810
  const tk = createAppToolkit(app.appDir);
720
- await expect(tk.loadServiceResolutions()).rejects.toThrow(/需 ≥0\.9\.0/);
811
+ const entry = (await tk.loadServiceResolutions()).resolutions['parking.query'];
812
+ expect(entry.status).toBe('evaluation-unavailable');
813
+ expect(entry.statusMessage).toMatch(/需 ≥0\.9\.0/);
721
814
  app.dispose();
722
815
  });
723
816
 
724
- it('M3 畸形包(求值面缺席)+ config 503 500(P-5——predicateIssues 早退绕过 assertFn 的唯一后续拦截点)', async () => {
817
+ it('M3 畸形包(严格面在场求值面缺席)→ 降级骨架非 5xx(v7——evaluationContracts throw 面不再可达;形态异常 note statusMessage)', async () => {
725
818
  const app = createTempApp('tbox-m3-');
726
819
  invalidateContractsResolver();
727
820
  const store = joinStore(app.appDir, 'contracts');
728
821
  writeFakeContractsPackage(store, { version: '0.9.0', missingEvaluationExports: true, slots: ['parking.query'] });
729
822
  linkContracts(app.appDir, store, { subdir: 'apps/server' });
730
- const tk = createAppToolkit(app.appDir); // 不写 integrations.json——config === null 早退路径
731
- await expect(tk.loadServiceResolutions()).rejects.toMatchObject({
732
- code: 'CONTRACTS_NOT_RESOLVED',
733
- httpStatus: 503,
734
- });
735
- await expect(tk.loadServiceResolutions()).rejects.not.toThrow(/工厂门控应先行拦截/);
823
+ writeAppFile(app.appDir, 'config/integrations.json', JSON.stringify({ provider: 'mock', services: { 'parking.query': {} } }));
824
+ const tk = createAppToolkit(app.appDir);
825
+ const batch = await tk.loadServiceResolutions();
826
+ const entry = batch.resolutions['parking.query'];
827
+ expect(entry.status).toBe('evaluation-unavailable');
828
+ expect(entry.statusMessage).toMatch(/求值面具名导出缺席/);
829
+ expect(entry.statusMessage).toMatch(/契约包形态异常/);
830
+ // 声明实例投影在场(降级 detail——原始数据非 normalizeInstances 派生)
831
+ const detail = await tk.loadServiceResolution('parking.query');
832
+ expect(detail.resolution.status).toBe('evaluation-unavailable');
833
+ expect(detail.resolution.inheritance).toHaveLength(4);
736
834
  app.dispose();
737
835
  });
738
836
  });
@@ -849,7 +947,133 @@ describe('v6 所有权族(D34 多域钉死退役 → ownedServices/binding 单
849
947
  app.dispose();
850
948
  });
851
949
 
852
- it('D6 偏斜态 503 第四分支(P1 标记门):v5 contracts 假件(无 OWNERSHIP_BINDING_V6)+ v6 toolkit → 503', async () => {
950
+ it('D5 v6.1 归属三副本修复:owned-not-consumed(mall-core 形态)批量归因 + 内嵌键集 + 谓词豁免', async () => {
951
+ const app = createTempApp('tbox-d5-');
952
+ invalidateContractsResolver();
953
+ const store = joinStore(app.appDir, 'contracts');
954
+ writeFakeContractsPackage(store, {
955
+ version: '0.9.0',
956
+ slots: ['auth.alipay-login', 'mall.info', 'mall.info-cache'],
957
+ });
958
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
959
+ // owns 3 / consumes 0(mall-core 形态——owns ⊄ consumes 的修复靶形态)
960
+ writeAppFile(
961
+ app.appDir,
962
+ 'packages/module-mall/tbox.module.json',
963
+ JSON.stringify({
964
+ schemaVersion: 1,
965
+ id: 'module-mall',
966
+ version: '0.1.0',
967
+ kind: 'business',
968
+ declare: {
969
+ owns: [{ service: 'auth.alipay-login' }, { service: 'mall.info' }, { service: 'mall.info-cache' }],
970
+ consumes: [],
971
+ },
972
+ dependencies: { modules: [] },
973
+ env: [],
974
+ }),
975
+ );
976
+ writeAppFile(
977
+ app.appDir,
978
+ 'config/integrations.json',
979
+ JSON.stringify({
980
+ provider: 'mock',
981
+ services: { 'mall.info': { provider: 'mock' }, 'orphan.svc': { provider: 'mock' } },
982
+ }),
983
+ );
984
+ const tk = createAppToolkit(app.appDir);
985
+ const batch = await tk.loadServiceResolutions();
986
+ // (a) 批量归因 = walk.ownership(install id 轴);required 缺席(demand echo——无人 consume)
987
+ expect(batch.resolutions['mall.info']).toMatchObject({ module: 'module-mall' });
988
+ expect(batch.resolutions['mall.info']?.required).toBeUndefined();
989
+ expect(batch.resolutions['auth.alipay-login']).toMatchObject({ module: 'module-mall' });
990
+ expect(batch.resolutions['mall.info-cache']).toMatchObject({ module: 'module-mall' });
991
+ // (d) 谓词 #3 豁免:owned+已配置不再误报「无消费方声明」;真孤儿仍报(语义收窄锁)
992
+ const slotNotDeclared = batch.issues.filter((i) => i.code === 'SLOT_NOT_DECLARED');
993
+ expect(slotNotDeclared.map((i) => i.service)).toEqual(['orphan.svc']);
994
+ // (a) 内嵌键集 = owned ∪ demand = owned(consumes 空——demand-only 过滤恒空缺陷回归锁)
995
+ const mod = await tk.loadModule('module-mall', { include: ['serviceResolutions'] });
996
+ expect(Object.keys(mod.serviceResolutions!.resolutions).sort()).toEqual([
997
+ 'auth.alipay-login',
998
+ 'mall.info',
999
+ 'mall.info-cache',
1000
+ ]);
1001
+ // owned 服务不落平台条目(moduleOccupiedServices 含 owns——AppView.services 排除不变)
1002
+ const appView = await tk.loadApp();
1003
+ expect(appView.services.map((s) => s.service)).not.toContain('mall.info');
1004
+ app.dispose();
1005
+ });
1006
+
1007
+ it('D6 v6.1 通道 1 键轴:npmModules 归因 = entry.id(install id,与模块清单可 join)≠ entry.package(demand owners 旧轴)', async () => {
1008
+ const app = createTempApp('tbox-d6-');
1009
+ invalidateContractsResolver();
1010
+ const store = joinStore(app.appDir, 'contracts');
1011
+ writeFakeContractsPackage(store, { version: '0.9.0', slots: ['mall.info'] });
1012
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
1013
+ writeAppFile(
1014
+ app.appDir,
1015
+ '.tbox/app.json',
1016
+ JSON.stringify({
1017
+ templateVersion: 'test',
1018
+ npmModules: [{ id: 'module-mall', package: '@tbox.cn/app-module-mall', version: '0.1.0', mode: 'sdk' }],
1019
+ }),
1020
+ );
1021
+ writeAppFile(
1022
+ app.appDir,
1023
+ 'packages/module-mall/tbox.module.json',
1024
+ JSON.stringify({
1025
+ schemaVersion: 1,
1026
+ id: 'module-mall',
1027
+ version: '0.1.0',
1028
+ kind: 'business',
1029
+ declare: { owns: [{ service: 'mall.info' }], consumes: [] },
1030
+ dependencies: { modules: [] },
1031
+ env: [],
1032
+ }),
1033
+ );
1034
+ const tk = createAppToolkit(app.appDir);
1035
+ const batch = await tk.loadServiceResolutions();
1036
+ expect(batch.resolutions['mall.info']?.module).toBe('module-mall');
1037
+ expect(batch.resolutions['mall.info']?.module).not.toBe('@tbox.cn/app-module-mall');
1038
+ app.dispose();
1039
+ });
1040
+
1041
+ it('D7 v6.1 归属键集全轴联动:owned ∖ 词汇 fail-visible(求值可见 + 静态 200 + 双键直击 + PUT 放行)', async () => {
1042
+ const app = createTempApp('tbox-d7-');
1043
+ invalidateContractsResolver();
1044
+ const store = joinStore(app.appDir, 'contracts');
1045
+ writeFakeContractsPackage(store, { version: '0.9.0' }); // 词汇空——ghost.owned 纯归属形态
1046
+ linkContracts(app.appDir, store, { subdir: 'apps/server' });
1047
+ writeAppFile(
1048
+ app.appDir,
1049
+ 'packages/module-x/tbox.module.json',
1050
+ JSON.stringify({
1051
+ schemaVersion: 1,
1052
+ id: 'module-x',
1053
+ version: '0.1.0',
1054
+ kind: 'business',
1055
+ declare: { owns: [{ service: 'ghost.owned' }], consumes: [] },
1056
+ dependencies: { modules: [] },
1057
+ env: [],
1058
+ }),
1059
+ );
1060
+ const tk = createAppToolkit(app.appDir);
1061
+ // 求值轴可见(resolutionKeys += 归属):module 归因 + 未配置态
1062
+ const batch = await tk.loadServiceResolutions();
1063
+ expect(batch.resolutions['ghost.owned']).toMatchObject({ module: 'module-x', status: 'service-not-configured' });
1064
+ // 静态单体可达(isKnownService += 归属——修复前 404 死锁)
1065
+ const svc = await tk.loadService('ghost.owned');
1066
+ expect(svc.service).toBe('ghost.owned');
1067
+ // 双键寻址直击(resolveServiceKey += 归属)
1068
+ const detail = await tk.loadServiceResolution('ghost.owned');
1069
+ expect(detail.resolution.module).toBe('module-x');
1070
+ // 写闸放行(P9 判据 += 归属——诊断指引的编辑不再被拒)
1071
+ const put = await tk.writeServiceIntegration('ghost.owned', { provider: 'mock' });
1072
+ expect(put.written).toBe(true);
1073
+ app.dispose();
1074
+ });
1075
+
1076
+ it('D6 偏斜态分级(P1 标记门 v7):v5 contracts 假件 + v6 toolkit → 写端点 503 保留;读求值轴降级骨架(503 退役)', async () => {
853
1077
  const app = createTempApp('tbox-d6-');
854
1078
  invalidateContractsResolver();
855
1079
  const store = joinStore(app.appDir, 'contracts');
@@ -859,15 +1083,21 @@ describe('v6 所有权族(D34 多域钉死退役 → ownedServices/binding 单
859
1083
  schemaVersion: 1, id: 'module-parking', version: '0.1.0', kind: 'business',
860
1084
  declare: { owns: [{ service: 'parking.query' }] },
861
1085
  }));
1086
+ writeAppFile(app.appDir, 'config/integrations.json', JSON.stringify({ provider: 'mock', services: { 'parking.query': {} } }));
862
1087
  const tk = createAppToolkit(app.appDir);
863
- // 写路径门控(求值轴同此门——assertContractsUsable 单点)
1088
+ // 写路径门控保留(assertContractsUsable——policy at edge 收窄至写/迁移面)
864
1089
  await expect(tk.writeModuleIntegration('module-parking', { provider: 'wanda' })).rejects.toMatchObject({
865
1090
  code: 'CONTRACTS_NOT_RESOLVED',
866
1091
  httpStatus: 503,
867
1092
  message: expect.stringContaining('OWNERSHIP_BINDING_V6'),
868
1093
  });
869
- await expect(tk.loadServiceResolutions()).rejects.toMatchObject({ code: 'CONTRACTS_NOT_RESOLVED', httpStatus: 503 });
870
- // 静态端点照常(永不 503——门控 policy at edge 只拦求值/写/migrate)
1094
+ // 读求值轴降级(503 退役):statusMessage 与写门控同源(N1 标记分支单源)
1095
+ const degraded = await tk.loadServiceResolutions();
1096
+ expect(degraded.contracts.strictValidation).toBe(true);
1097
+ const entry = degraded.resolutions['parking.query'];
1098
+ expect(entry.status).toBe('evaluation-unavailable');
1099
+ expect(entry.statusMessage).toMatch(/OWNERSHIP_BINDING_V6 标记缺席/);
1100
+ // 静态端点照常(永不 503——门控 policy at edge 只拦写/迁移)
871
1101
  const mod = await tk.loadModule('module-parking');
872
1102
  expect(mod.ownedServices).toEqual(['parking.query']);
873
1103
  app.dispose();
@@ -921,15 +1151,20 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
921
1151
  h2.app.dispose();
922
1152
  });
923
1153
 
924
- it('I3 degrade:<0.9 null 永不 503;形态异常包(严格面在场求值面缺席)→ null(双条件)', async () => {
1154
+ it('I3 degrade:内嵌轴同步降级(v7 P14 (b)——D31 null 分支退役):三条件不满足 骨架非 null', async () => {
925
1155
  const low = harnessWithContracts({ version: '0.8.2', withStrict: false });
926
- expect((await low.tk.loadApp({ include: ['serviceResolutions'] })).serviceResolutions).toBeNull();
1156
+ const lowInc = await low.tk.loadApp({ include: ['serviceResolutions'] });
1157
+ expect(lowInc.serviceResolutions).not.toBeNull();
1158
+ const lowEntries = Object.values(lowInc.serviceResolutions!.resolutions);
1159
+ expect(lowEntries.length).toBeGreaterThan(0);
1160
+ expect(lowEntries.every((e) => e.status === 'evaluation-unavailable')).toBe(true);
927
1161
  expect((await low.tk.loadApp({ include: ['integrationSchemas'] })).integrationSchemas).toEqual({ provider: 'mock', services: {} }); // 静态轴富化零 contracts
928
1162
  low.app.dispose();
1163
+ // 畸形包(严格面在场求值面缺席)→ 内嵌轴同样降级(503/双 null 全退役)
929
1164
  const malformed = harnessWithContracts({ missingEvaluationExports: true });
930
- expect((await malformed.tk.loadApp({ include: ['serviceResolutions'] })).serviceResolutions).toBeNull();
931
- // 对照:求值轴端点同 fixture → 503(M3 已钉——门控与内嵌两机制不共用断言)
932
- await expect(malformed.tk.loadServiceResolutions()).rejects.toMatchObject({ httpStatus: 503 });
1165
+ const malformedInc = await malformed.tk.loadApp({ include: ['serviceResolutions'] });
1166
+ expect(malformedInc.serviceResolutions).not.toBeNull();
1167
+ expect(Object.values(malformedInc.serviceResolutions!.resolutions)[0]?.status).toBe('evaluation-unavailable');
933
1168
  malformed.app.dispose();
934
1169
  });
935
1170