@liuhange/dsh-data-asset-registration-helper 3.0.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.
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { checkDisallowedScenarios } from '../disallowedScenarioChecker.js';
3
+ import { matchAgency } from '../agencyMatcher.js';
4
+ const disallowedScenarios = [
5
+ { id: 'DIS_001', label: '危害国家安全', keywords: ['国家安全', '机密', '涉密'] },
6
+ { id: 'DIS_002', label: '来源违反法律法规', keywords: ['非法', '窃取', '违规采集'] },
7
+ { id: 'DIS_003', label: '权属纠纷未解决', keywords: ['纠纷', '争议', '诉讼'] },
8
+ { id: 'DIS_004', label: '隐瞒真实情况', keywords: ['隐瞒', '虚假', '欺诈'] },
9
+ ];
10
+ describe('disallowedScenarioChecker', () => {
11
+ it('正常描述→CAN_REGISTER', () => {
12
+ const result = checkDisallowedScenarios('企业内部经营数据', disallowedScenarios, false);
13
+ expect(result.conclusion).toBe('CAN_REGISTER');
14
+ expect(result.failedItems).toHaveLength(0);
15
+ });
16
+ it('含涉密关键词→CANNOT_REGISTER', () => {
17
+ const result = checkDisallowedScenarios('涉及国家机密数据', disallowedScenarios, false);
18
+ expect(result.conclusion).toBe('CANNOT_REGISTER');
19
+ expect(result.disallowedScenariosHit[0].id).toBe('DIS_001');
20
+ });
21
+ it('权属纠纷→CANNOT_REGISTER', () => {
22
+ const result = checkDisallowedScenarios('正常数据', disallowedScenarios, true);
23
+ expect(result.conclusion).toBe('CANNOT_REGISTER');
24
+ expect(result.failedItems.some(f => f.includes('权属纠纷'))).toBe(true);
25
+ });
26
+ it('多个禁止场景同时命中', () => {
27
+ const result = checkDisallowedScenarios('非法窃取且虚假隐瞒', disallowedScenarios, false);
28
+ expect(result.conclusion).toBe('CANNOT_REGISTER');
29
+ expect(result.disallowedScenariosHit.length).toBeGreaterThanOrEqual(2);
30
+ });
31
+ });
32
+ describe('agencyMatcher', () => {
33
+ const agencyConfig = {
34
+ mode: 'local',
35
+ httpEndpoints: {
36
+ beijing: { url: 'https://example.com/bj', apiKey: '${KEY_BJ}' },
37
+ shanghai: { url: 'https://example.com/sh', apiKey: '${KEY_SH}' },
38
+ shenzhen: { url: 'https://example.com/sz', apiKey: '${KEY_SZ}' },
39
+ },
40
+ };
41
+ it('指定region精确匹配', () => {
42
+ const result = matchAgency('金融', 'shanghai', agencyConfig);
43
+ expect(result.matched).toBe(true);
44
+ expect(result.recommendedAgency).toBe('shanghai');
45
+ expect(result.url).toBe('https://example.com/sh');
46
+ });
47
+ it('未指定region时从dataType推断', () => {
48
+ const result = matchAgency('金融', undefined, agencyConfig);
49
+ expect(result.recommendedAgency).toBe('shanghai');
50
+ });
51
+ it('科技数据推断为深圳', () => {
52
+ const result = matchAgency('科技数据', undefined, agencyConfig);
53
+ expect(result.recommendedAgency).toBe('shenzhen');
54
+ });
55
+ it('未知region回退到第一个endpoint', () => {
56
+ const result = matchAgency('未知', 'mars', agencyConfig);
57
+ expect(result.matched).toBe(false);
58
+ expect(result.recommendedAgency).toBe('beijing');
59
+ });
60
+ });
61
+ //# sourceMappingURL=registrationHelper.test.js.map
@@ -0,0 +1,36 @@
1
+ export function matchAgency(dataType, region, config) {
2
+ const regionKey = region ?? inferRegionFromDataType(dataType);
3
+ const endpoint = config.httpEndpoints[regionKey];
4
+ if (endpoint) {
5
+ return {
6
+ recommendedAgency: regionKey,
7
+ url: endpoint.url,
8
+ matched: true,
9
+ };
10
+ }
11
+ const firstKey = Object.keys(config.httpEndpoints)[0];
12
+ if (firstKey) {
13
+ const fallback = config.httpEndpoints[firstKey];
14
+ if (fallback) {
15
+ return {
16
+ recommendedAgency: firstKey,
17
+ url: fallback.url,
18
+ matched: false,
19
+ };
20
+ }
21
+ }
22
+ return {
23
+ recommendedAgency: 'unknown',
24
+ url: '',
25
+ matched: false,
26
+ };
27
+ }
28
+ function inferRegionFromDataType(dataType) {
29
+ const typeLower = dataType.toLowerCase();
30
+ if (typeLower.includes('金融') || typeLower.includes('finance'))
31
+ return 'shanghai';
32
+ if (typeLower.includes('科技') || typeLower.includes('tech'))
33
+ return 'shenzhen';
34
+ return 'beijing';
35
+ }
36
+ //# sourceMappingURL=agencyMatcher.js.map
@@ -0,0 +1,20 @@
1
+ export function checkDisallowedScenarios(assetDescription, disallowedScenarios, hasDispute) {
2
+ const failedItems = [];
3
+ const hitScenarios = [];
4
+ for (const scenario of disallowedScenarios) {
5
+ const matched = scenario.keywords.some(kw => assetDescription.includes(kw));
6
+ if (matched) {
7
+ hitScenarios.push(scenario);
8
+ failedItems.push(`${scenario.id}: ${scenario.label}(关键词: ${scenario.keywords.join(', ')})`);
9
+ }
10
+ }
11
+ if (hasDispute) {
12
+ failedItems.push('权属纠纷未解决: ownershipConfirmation.hasDispute = true');
13
+ }
14
+ return {
15
+ conclusion: failedItems.length === 0 ? 'CAN_REGISTER' : 'CANNOT_REGISTER',
16
+ failedItems,
17
+ disallowedScenariosHit: hitScenarios,
18
+ };
19
+ }
20
+ //# sourceMappingURL=disallowedScenarioChecker.js.map
package/lib/index.js ADDED
@@ -0,0 +1,76 @@
1
+ import { checkDisallowedScenarios } from './disallowedScenarioChecker.js';
2
+ import { matchAgency } from './agencyMatcher.js';
3
+ import { generateRegistrationReport } from './registrationReportGenerator.js';
4
+ import { validateRegistrationArgs } from './invariant.js';
5
+ export const name = '@liuhange/dsh-data-asset-registration-helper';
6
+ export const inject = ['tools'];
7
+ export function apply(ctx) {
8
+ ctx.tools.register({
9
+ name: 'registration_helper',
10
+ description: '数据资产登记助手:禁止场景检查+权属纠纷检查+登记机构匹配+登记流程指引,兼容已有registration-precheck/generate-registration-docs/match-registration-agency',
11
+ parameters: {
12
+ type: 'object',
13
+ properties: {
14
+ assetName: { type: 'string', description: '数据资产名称' },
15
+ assetDescription: { type: 'string', description: '数据资产描述(用于禁止场景检测)' },
16
+ dataType: { type: 'string', description: '数据类型(金融/医疗/交通等)' },
17
+ region: { type: 'string', description: '意向登记地区(beijing/shanghai/shenzhen,可选)' },
18
+ ownershipConfirmation: {
19
+ type: 'object',
20
+ description: '权属确认信息',
21
+ properties: {
22
+ hasDispute: { type: 'boolean', description: '是否存在权属纠纷' },
23
+ confirmedAt: { type: 'string', description: '确认时间' },
24
+ holder: { type: 'string', description: '持有方' },
25
+ processor: { type: 'string', description: '加工方' },
26
+ operator: { type: 'string', description: '运营方' },
27
+ },
28
+ },
29
+ orchestrationResult: { type: 'string', description: '体检全流程产物JSON' },
30
+ },
31
+ required: ['assetName', 'assetDescription', 'dataType', 'ownershipConfirmation', 'orchestrationResult'],
32
+ },
33
+ async execute(args) {
34
+ try {
35
+ const validated = validateRegistrationArgs(args);
36
+ const { readFileSync } = await import('node:fs');
37
+ let rulesConfig;
38
+ try {
39
+ rulesConfig = JSON.parse(readFileSync(process.env.BUSINESS_RULES_PATH || 'config/business-rules.json', 'utf-8'));
40
+ }
41
+ catch {
42
+ return JSON.stringify({ error: 'REGISTRATION_RULES_MISSING', message: '无法加载business-rules.json' });
43
+ }
44
+ const registrationConfig = rulesConfig.registration;
45
+ if (!registrationConfig) {
46
+ return JSON.stringify({ error: 'REGISTRATION_RULES_MISSING', message: 'registration配置段缺失' });
47
+ }
48
+ let policyDocuments;
49
+ try {
50
+ const policyConfig = JSON.parse(readFileSync(process.env.POLICY_REFS_PATH || 'config/policy-references.json', 'utf-8'));
51
+ const stage = policyConfig.policyReferences?.find((s) => s.stage === 'PRECHECK');
52
+ policyDocuments = stage?.documents ?? [];
53
+ }
54
+ catch {
55
+ policyDocuments = [];
56
+ }
57
+ const disallowedScenarios = registrationConfig.rules?.disallowedScenarios ?? [];
58
+ const precheck = checkDisallowedScenarios(validated.assetDescription, disallowedScenarios, validated.ownershipConfirmation.hasDispute);
59
+ let agencyMatch = null;
60
+ if (precheck.conclusion === 'CAN_REGISTER') {
61
+ agencyMatch = matchAgency(validated.dataType, validated.region, registrationConfig.agencies);
62
+ }
63
+ const report = generateRegistrationReport(validated.assetName, precheck, agencyMatch, policyDocuments);
64
+ return JSON.stringify(report, null, 2);
65
+ }
66
+ catch (e) {
67
+ const err = e;
68
+ return JSON.stringify({
69
+ error: err.message.split(':')[0] ?? 'UNKNOWN_ERROR',
70
+ message: err.message,
71
+ });
72
+ }
73
+ },
74
+ });
75
+ }
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,44 @@
1
+ export function invariant(condition, message) {
2
+ if (!condition) {
3
+ throw new Error(`INVARIANT_VIOLATION: ${message}`);
4
+ }
5
+ }
6
+ export function validateRegistrationArgs(args) {
7
+ if (!args || typeof args !== 'object') {
8
+ throw new Error('INVALID_ARGS: args must be an object');
9
+ }
10
+ if (!args.assetName || typeof args.assetName !== 'string') {
11
+ throw new Error('INVALID_ARGS: assetName is required and must be a string');
12
+ }
13
+ if (typeof args.assetDescription !== 'string') {
14
+ throw new Error('INVALID_ARGS: assetDescription is required and must be a string');
15
+ }
16
+ if (!args.dataType || typeof args.dataType !== 'string') {
17
+ throw new Error('INVALID_ARGS: dataType is required and must be a string');
18
+ }
19
+ const oc = args.ownershipConfirmation;
20
+ if (!oc || typeof oc !== 'object') {
21
+ throw new Error('INVALID_ARGS: ownershipConfirmation is required');
22
+ }
23
+ if (typeof oc.hasDispute !== 'boolean') {
24
+ throw new Error('INVALID_ARGS: ownershipConfirmation.hasDispute must be boolean');
25
+ }
26
+ if (typeof oc.confirmedAt !== 'string' || typeof oc.holder !== 'string' || typeof oc.processor !== 'string' || typeof oc.operator !== 'string') {
27
+ throw new Error('INVALID_ARGS: ownershipConfirmation needs confirmedAt/holder/processor/operator as strings');
28
+ }
29
+ if (!args.orchestrationResult || typeof args.orchestrationResult !== 'string') {
30
+ throw new Error('INVALID_ARGS: orchestrationResult is required and must be a string (JSON)');
31
+ }
32
+ const result = {
33
+ assetName: args.assetName,
34
+ assetDescription: args.assetDescription,
35
+ dataType: args.dataType,
36
+ ownershipConfirmation: oc,
37
+ orchestrationResult: args.orchestrationResult,
38
+ };
39
+ if (typeof args.region === 'string') {
40
+ result.region = args.region;
41
+ }
42
+ return result;
43
+ }
44
+ //# sourceMappingURL=invariant.js.map
@@ -0,0 +1,30 @@
1
+ export function generateRegistrationReport(assetName, precheck, agencyMatch, policyDocuments) {
2
+ const nextSteps = [];
3
+ if (precheck.conclusion === 'CANNOT_REGISTER') {
4
+ nextSteps.push('暂缓登记申请,需先解决以下问题:');
5
+ for (const item of precheck.failedItems) {
6
+ nextSteps.push(` - ${item}`);
7
+ }
8
+ }
9
+ else {
10
+ nextSteps.push('预检通过,可进入登记申请流程');
11
+ if (agencyMatch && agencyMatch.matched) {
12
+ nextSteps.push(`向${agencyMatch.recommendedAgency}数据交易所提交申请: ${agencyMatch.url}`);
13
+ }
14
+ else if (agencyMatch) {
15
+ nextSteps.push(`建议向${agencyMatch.recommendedAgency}数据交易所咨询: ${agencyMatch.url}`);
16
+ }
17
+ nextSteps.push('准备材料: 数据描述、来源合法性声明、产权归属说明');
18
+ nextSteps.push('通过generate_registration_docs工具生成申请材料');
19
+ nextSteps.push('提交至国家数据产权登记系统');
20
+ }
21
+ return {
22
+ assetName,
23
+ precheck,
24
+ agencyMatch,
25
+ nextSteps,
26
+ policyReferences: policyDocuments,
27
+ timestamp: new Date().toISOString(),
28
+ };
29
+ }
30
+ //# sourceMappingURL=registrationReportGenerator.js.map
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@liuhange/dsh-data-asset-registration-helper",
3
+ "description": "Registration helper plugin: disallowed scenario check + ownership dispute check + agency matching + registration workflow guidance",
4
+ "version": "3.0.0",
5
+ "publishConfig": { "access": "public" },
6
+ "repository": { "type": "git", "url": "git+https://github.com/liuhange789/data-asset-inspector.git", "directory": "packages/data-asset/data-asset-registration-helper" },
7
+ "type": "module",
8
+ "main": "lib/index.js",
9
+ "types": "lib/types/index.d.ts",
10
+ "exports": {
11
+ ".": { "types": "./lib/types/index.d.ts", "default": "./lib/index.js" },
12
+ "./src/*": "./src/*",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": ["lib/**/*.js", "lib/types/**/*.d.ts"],
16
+ "license": "MIT",
17
+ "scripts": {
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "vitest run",
20
+ "build": "tsc"
21
+ },
22
+ "peerDependencies": {
23
+ "@deepseek-ai/cordis": "^4.0.0",
24
+ "@deepseek-ai/dsh-tools": "0.0.1-rc.1",
25
+ "@liuhange/dsh-data-asset-shared": "^3.0.0"
26
+ },
27
+ "dsh": { "bundle": { "patch": true } }
28
+ }