@tbox.cn/app-toolkit 0.6.0 → 0.7.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.
@@ -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': {} } }));
745
+ const tk = createAppToolkit(app.appDir);
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(/未安装/);
751
+ app.dispose();
752
+ });
753
+
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
+ }));
707
768
  const tk = createAppToolkit(app.appDir);
708
- await expect(tk.loadServiceResolutions()).rejects.toThrow(/构建产物导入失败/);
709
- await expect(tk.loadServiceResolutions()).rejects.not.toThrow(/未安装/);
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 = demand owners 首(与求值轴同源);required = OR 聚合
774
+ expect(entry.module).toBe('@app/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');
710
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();
711
801
  });
712
802
 
713
- it('M2 <0.9 message 对齐 api.md 信封(需 ≥0.9.0——resolver note 文案)', async () => {
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,7 @@ 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('D6 偏斜态分级(P1 标记门 v7):v5 contracts 假件 + v6 toolkit → 写端点 503 保留;读求值轴降级骨架(503 退役)', async () => {
853
951
  const app = createTempApp('tbox-d6-');
854
952
  invalidateContractsResolver();
855
953
  const store = joinStore(app.appDir, 'contracts');
@@ -859,15 +957,21 @@ describe('v6 所有权族(D34 多域钉死退役 → ownedServices/binding 单
859
957
  schemaVersion: 1, id: 'module-parking', version: '0.1.0', kind: 'business',
860
958
  declare: { owns: [{ service: 'parking.query' }] },
861
959
  }));
960
+ writeAppFile(app.appDir, 'config/integrations.json', JSON.stringify({ provider: 'mock', services: { 'parking.query': {} } }));
862
961
  const tk = createAppToolkit(app.appDir);
863
- // 写路径门控(求值轴同此门——assertContractsUsable 单点)
962
+ // 写路径门控保留(assertContractsUsable——policy at edge 收窄至写/迁移面)
864
963
  await expect(tk.writeModuleIntegration('module-parking', { provider: 'wanda' })).rejects.toMatchObject({
865
964
  code: 'CONTRACTS_NOT_RESOLVED',
866
965
  httpStatus: 503,
867
966
  message: expect.stringContaining('OWNERSHIP_BINDING_V6'),
868
967
  });
869
- await expect(tk.loadServiceResolutions()).rejects.toMatchObject({ code: 'CONTRACTS_NOT_RESOLVED', httpStatus: 503 });
870
- // 静态端点照常(永不 503——门控 policy at edge 只拦求值/写/migrate)
968
+ // 读求值轴降级(503 退役):statusMessage 与写门控同源(N1 标记分支单源)
969
+ const degraded = await tk.loadServiceResolutions();
970
+ expect(degraded.contracts.strictValidation).toBe(true);
971
+ const entry = degraded.resolutions['parking.query'];
972
+ expect(entry.status).toBe('evaluation-unavailable');
973
+ expect(entry.statusMessage).toMatch(/OWNERSHIP_BINDING_V6 标记缺席/);
974
+ // 静态端点照常(永不 503——门控 policy at edge 只拦写/迁移)
871
975
  const mod = await tk.loadModule('module-parking');
872
976
  expect(mod.ownedServices).toEqual(['parking.query']);
873
977
  app.dispose();
@@ -921,15 +1025,20 @@ describe('v4.4 include 协议 + Entry.layer + 层级 schema(F4/F5)', () => {
921
1025
  h2.app.dispose();
922
1026
  });
923
1027
 
924
- it('I3 degrade:<0.9 null 永不 503;形态异常包(严格面在场求值面缺席)→ null(双条件)', async () => {
1028
+ it('I3 degrade:内嵌轴同步降级(v7 P14 (b)——D31 null 分支退役):三条件不满足 骨架非 null', async () => {
925
1029
  const low = harnessWithContracts({ version: '0.8.2', withStrict: false });
926
- expect((await low.tk.loadApp({ include: ['serviceResolutions'] })).serviceResolutions).toBeNull();
1030
+ const lowInc = await low.tk.loadApp({ include: ['serviceResolutions'] });
1031
+ expect(lowInc.serviceResolutions).not.toBeNull();
1032
+ const lowEntries = Object.values(lowInc.serviceResolutions!.resolutions);
1033
+ expect(lowEntries.length).toBeGreaterThan(0);
1034
+ expect(lowEntries.every((e) => e.status === 'evaluation-unavailable')).toBe(true);
927
1035
  expect((await low.tk.loadApp({ include: ['integrationSchemas'] })).integrationSchemas).toEqual({ provider: 'mock', services: {} }); // 静态轴富化零 contracts
928
1036
  low.app.dispose();
1037
+ // 畸形包(严格面在场求值面缺席)→ 内嵌轴同样降级(503/双 null 全退役)
929
1038
  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 });
1039
+ const malformedInc = await malformed.tk.loadApp({ include: ['serviceResolutions'] });
1040
+ expect(malformedInc.serviceResolutions).not.toBeNull();
1041
+ expect(Object.values(malformedInc.serviceResolutions!.resolutions)[0]?.status).toBe('evaluation-unavailable');
933
1042
  malformed.app.dispose();
934
1043
  });
935
1044