@crmforall/connector 0.1.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,314 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * 커넥터 런타임 설정.
4
+ * 위치: $CRMFORALL_CONFIG 또는 ~/.crmforall/connector.json (0600).
5
+ * 보안 원칙: DB 비밀번호·커넥터 시크릿은 이 파일과 환경변수로만 흐른다.
6
+ * AI 운전 계층(MCP 호출자)에게는 어떤 도구 출력에도 노출하지 않는다.
7
+ */
8
+ export declare const fieldMappingEntitySchema: z.ZodObject<{
9
+ table: z.ZodObject<{
10
+ schema: z.ZodString;
11
+ table: z.ZodString;
12
+ }, "strip", z.ZodTypeAny, {
13
+ table: string;
14
+ schema: string;
15
+ }, {
16
+ table: string;
17
+ schema: string;
18
+ }>;
19
+ /** 표준 필드명 → 운영 DB 컬럼명 */
20
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
21
+ }, "strip", z.ZodTypeAny, {
22
+ table: {
23
+ table: string;
24
+ schema: string;
25
+ };
26
+ fields: Record<string, string>;
27
+ }, {
28
+ table: {
29
+ table: string;
30
+ schema: string;
31
+ };
32
+ fields: Record<string, string>;
33
+ }>;
34
+ export type FieldMappingEntity = z.infer<typeof fieldMappingEntitySchema>;
35
+ export declare const mappingConfigSchema: z.ZodObject<{
36
+ version: z.ZodString;
37
+ customer: z.ZodObject<{
38
+ table: z.ZodObject<{
39
+ schema: z.ZodString;
40
+ table: z.ZodString;
41
+ }, "strip", z.ZodTypeAny, {
42
+ table: string;
43
+ schema: string;
44
+ }, {
45
+ table: string;
46
+ schema: string;
47
+ }>;
48
+ /** 표준 필드명 → 운영 DB 컬럼명 */
49
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
50
+ }, "strip", z.ZodTypeAny, {
51
+ table: {
52
+ table: string;
53
+ schema: string;
54
+ };
55
+ fields: Record<string, string>;
56
+ }, {
57
+ table: {
58
+ table: string;
59
+ schema: string;
60
+ };
61
+ fields: Record<string, string>;
62
+ }>;
63
+ reservation: z.ZodObject<{
64
+ table: z.ZodObject<{
65
+ schema: z.ZodString;
66
+ table: z.ZodString;
67
+ }, "strip", z.ZodTypeAny, {
68
+ table: string;
69
+ schema: string;
70
+ }, {
71
+ table: string;
72
+ schema: string;
73
+ }>;
74
+ /** 표준 필드명 → 운영 DB 컬럼명 */
75
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
76
+ }, "strip", z.ZodTypeAny, {
77
+ table: {
78
+ table: string;
79
+ schema: string;
80
+ };
81
+ fields: Record<string, string>;
82
+ }, {
83
+ table: {
84
+ table: string;
85
+ schema: string;
86
+ };
87
+ fields: Record<string, string>;
88
+ }>;
89
+ }, "strip", z.ZodTypeAny, {
90
+ version: string;
91
+ customer: {
92
+ table: {
93
+ table: string;
94
+ schema: string;
95
+ };
96
+ fields: Record<string, string>;
97
+ };
98
+ reservation: {
99
+ table: {
100
+ table: string;
101
+ schema: string;
102
+ };
103
+ fields: Record<string, string>;
104
+ };
105
+ }, {
106
+ version: string;
107
+ customer: {
108
+ table: {
109
+ table: string;
110
+ schema: string;
111
+ };
112
+ fields: Record<string, string>;
113
+ };
114
+ reservation: {
115
+ table: {
116
+ table: string;
117
+ schema: string;
118
+ };
119
+ fields: Record<string, string>;
120
+ };
121
+ }>;
122
+ export type MappingConfig = z.infer<typeof mappingConfigSchema>;
123
+ export declare const connectorConfigSchema: z.ZodObject<{
124
+ platformUrl: z.ZodString;
125
+ tenantId: z.ZodString;
126
+ connectionId: z.ZodString;
127
+ connectorId: z.ZodString;
128
+ /** 플랫폼 인증 시크릿 — 설치 토큰 교환으로 발급됨 */
129
+ connectorSecret: z.ZodString;
130
+ crmSchema: z.ZodDefault<z.ZodString>;
131
+ db: z.ZodObject<{
132
+ sourceType: z.ZodEnum<["postgresql", "mysql", "supabase", "cafe24"]>;
133
+ host: z.ZodString;
134
+ port: z.ZodNumber;
135
+ database: z.ZodString;
136
+ user: z.ZodString;
137
+ password: z.ZodString;
138
+ ssl: z.ZodDefault<z.ZodBoolean>;
139
+ }, "strip", z.ZodTypeAny, {
140
+ sourceType: "postgresql" | "mysql" | "supabase" | "cafe24";
141
+ host: string;
142
+ port: number;
143
+ database: string;
144
+ user: string;
145
+ password: string;
146
+ ssl: boolean;
147
+ }, {
148
+ sourceType: "postgresql" | "mysql" | "supabase" | "cafe24";
149
+ host: string;
150
+ port: number;
151
+ database: string;
152
+ user: string;
153
+ password: string;
154
+ ssl?: boolean | undefined;
155
+ }>;
156
+ /** 확정된 표준 필드 매핑 — validate_mapping 통과본만 저장 */
157
+ mapping: z.ZodDefault<z.ZodNullable<z.ZodObject<{
158
+ version: z.ZodString;
159
+ customer: z.ZodObject<{
160
+ table: z.ZodObject<{
161
+ schema: z.ZodString;
162
+ table: z.ZodString;
163
+ }, "strip", z.ZodTypeAny, {
164
+ table: string;
165
+ schema: string;
166
+ }, {
167
+ table: string;
168
+ schema: string;
169
+ }>;
170
+ /** 표준 필드명 → 운영 DB 컬럼명 */
171
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
172
+ }, "strip", z.ZodTypeAny, {
173
+ table: {
174
+ table: string;
175
+ schema: string;
176
+ };
177
+ fields: Record<string, string>;
178
+ }, {
179
+ table: {
180
+ table: string;
181
+ schema: string;
182
+ };
183
+ fields: Record<string, string>;
184
+ }>;
185
+ reservation: z.ZodObject<{
186
+ table: z.ZodObject<{
187
+ schema: z.ZodString;
188
+ table: z.ZodString;
189
+ }, "strip", z.ZodTypeAny, {
190
+ table: string;
191
+ schema: string;
192
+ }, {
193
+ table: string;
194
+ schema: string;
195
+ }>;
196
+ /** 표준 필드명 → 운영 DB 컬럼명 */
197
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
198
+ }, "strip", z.ZodTypeAny, {
199
+ table: {
200
+ table: string;
201
+ schema: string;
202
+ };
203
+ fields: Record<string, string>;
204
+ }, {
205
+ table: {
206
+ table: string;
207
+ schema: string;
208
+ };
209
+ fields: Record<string, string>;
210
+ }>;
211
+ }, "strip", z.ZodTypeAny, {
212
+ version: string;
213
+ customer: {
214
+ table: {
215
+ table: string;
216
+ schema: string;
217
+ };
218
+ fields: Record<string, string>;
219
+ };
220
+ reservation: {
221
+ table: {
222
+ table: string;
223
+ schema: string;
224
+ };
225
+ fields: Record<string, string>;
226
+ };
227
+ }, {
228
+ version: string;
229
+ customer: {
230
+ table: {
231
+ table: string;
232
+ schema: string;
233
+ };
234
+ fields: Record<string, string>;
235
+ };
236
+ reservation: {
237
+ table: {
238
+ table: string;
239
+ schema: string;
240
+ };
241
+ fields: Record<string, string>;
242
+ };
243
+ }>>>;
244
+ }, "strip", z.ZodTypeAny, {
245
+ platformUrl: string;
246
+ tenantId: string;
247
+ connectionId: string;
248
+ connectorId: string;
249
+ connectorSecret: string;
250
+ crmSchema: string;
251
+ db: {
252
+ sourceType: "postgresql" | "mysql" | "supabase" | "cafe24";
253
+ host: string;
254
+ port: number;
255
+ database: string;
256
+ user: string;
257
+ password: string;
258
+ ssl: boolean;
259
+ };
260
+ mapping: {
261
+ version: string;
262
+ customer: {
263
+ table: {
264
+ table: string;
265
+ schema: string;
266
+ };
267
+ fields: Record<string, string>;
268
+ };
269
+ reservation: {
270
+ table: {
271
+ table: string;
272
+ schema: string;
273
+ };
274
+ fields: Record<string, string>;
275
+ };
276
+ } | null;
277
+ }, {
278
+ platformUrl: string;
279
+ tenantId: string;
280
+ connectionId: string;
281
+ connectorId: string;
282
+ connectorSecret: string;
283
+ db: {
284
+ sourceType: "postgresql" | "mysql" | "supabase" | "cafe24";
285
+ host: string;
286
+ port: number;
287
+ database: string;
288
+ user: string;
289
+ password: string;
290
+ ssl?: boolean | undefined;
291
+ };
292
+ crmSchema?: string | undefined;
293
+ mapping?: {
294
+ version: string;
295
+ customer: {
296
+ table: {
297
+ table: string;
298
+ schema: string;
299
+ };
300
+ fields: Record<string, string>;
301
+ };
302
+ reservation: {
303
+ table: {
304
+ table: string;
305
+ schema: string;
306
+ };
307
+ fields: Record<string, string>;
308
+ };
309
+ } | null | undefined;
310
+ }>;
311
+ export type ConnectorConfig = z.infer<typeof connectorConfigSchema>;
312
+ export declare function defaultConfigPath(): string;
313
+ export declare function loadConfig(path?: string): ConnectorConfig | null;
314
+ export declare function saveConfig(config: ConnectorConfig, path?: string): void;
package/dist/config.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.connectorConfigSchema = exports.mappingConfigSchema = exports.fieldMappingEntitySchema = void 0;
4
+ exports.defaultConfigPath = defaultConfigPath;
5
+ exports.loadConfig = loadConfig;
6
+ exports.saveConfig = saveConfig;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ const zod_1 = require("zod");
11
+ /**
12
+ * 커넥터 런타임 설정.
13
+ * 위치: $CRMFORALL_CONFIG 또는 ~/.crmforall/connector.json (0600).
14
+ * 보안 원칙: DB 비밀번호·커넥터 시크릿은 이 파일과 환경변수로만 흐른다.
15
+ * AI 운전 계층(MCP 호출자)에게는 어떤 도구 출력에도 노출하지 않는다.
16
+ */
17
+ exports.fieldMappingEntitySchema = zod_1.z.object({
18
+ table: zod_1.z.object({
19
+ schema: zod_1.z.string().min(1),
20
+ table: zod_1.z.string().min(1),
21
+ }),
22
+ /** 표준 필드명 → 운영 DB 컬럼명 */
23
+ fields: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
24
+ });
25
+ exports.mappingConfigSchema = zod_1.z.object({
26
+ version: zod_1.z.string().min(1),
27
+ customer: exports.fieldMappingEntitySchema,
28
+ reservation: exports.fieldMappingEntitySchema,
29
+ });
30
+ exports.connectorConfigSchema = zod_1.z.object({
31
+ platformUrl: zod_1.z.string().url(),
32
+ tenantId: zod_1.z.string().min(1),
33
+ connectionId: zod_1.z.string().min(1),
34
+ connectorId: zod_1.z.string().min(1),
35
+ /** 플랫폼 인증 시크릿 — 설치 토큰 교환으로 발급됨 */
36
+ connectorSecret: zod_1.z.string().min(16),
37
+ crmSchema: zod_1.z
38
+ .string()
39
+ .regex(/^[a-z_][a-z0-9_]{0,62}$/)
40
+ .default("crmforall"),
41
+ db: zod_1.z.object({
42
+ sourceType: zod_1.z.enum(["postgresql", "mysql", "supabase", "cafe24"]),
43
+ host: zod_1.z.string().min(1),
44
+ port: zod_1.z.number().int().positive(),
45
+ database: zod_1.z.string().min(1),
46
+ user: zod_1.z.string().min(1),
47
+ password: zod_1.z.string().min(1),
48
+ ssl: zod_1.z.boolean().default(false),
49
+ }),
50
+ /** 확정된 표준 필드 매핑 — validate_mapping 통과본만 저장 */
51
+ mapping: exports.mappingConfigSchema.nullable().default(null),
52
+ });
53
+ function defaultConfigPath() {
54
+ return (process.env.CRMFORALL_CONFIG ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".crmforall", "connector.json"));
55
+ }
56
+ function loadConfig(path = defaultConfigPath()) {
57
+ if (!(0, node_fs_1.existsSync)(path))
58
+ return null;
59
+ const raw = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
60
+ // 환경변수가 파일보다 우선 (컨테이너/Secrets Manager 주입 경로)
61
+ if (process.env.CRMFORALL_DB_PASSWORD) {
62
+ raw.db = { ...raw.db, password: process.env.CRMFORALL_DB_PASSWORD };
63
+ }
64
+ if (process.env.CRMFORALL_CONNECTOR_SECRET) {
65
+ raw.connectorSecret = process.env.CRMFORALL_CONNECTOR_SECRET;
66
+ }
67
+ return exports.connectorConfigSchema.parse(raw);
68
+ }
69
+ function saveConfig(config, path = defaultConfigPath()) {
70
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true, mode: 0o700 });
71
+ (0, node_fs_1.writeFileSync)(path, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
72
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @crmforall/contracts 미러 (커넥터에 필요한 부분만).
3
+ * TODO: contracts 패키지 npm 발행 후 이 파일을 의존성으로 교체한다.
4
+ * 원본: crmforall-platform/packages/contracts/src/{mcp-tools,approval,errors,standard-fields}.ts
5
+ */
6
+ export declare const MCP_TOOL: {
7
+ readonly TEST_CONNECTION: "test_connection";
8
+ readonly INSPECT_SCHEMA: "inspect_schema";
9
+ readonly PROPOSE_FIELD_MAPPING: "propose_field_mapping";
10
+ readonly VALIDATE_MAPPING: "validate_mapping";
11
+ readonly PREVIEW_TARGETS: "preview_targets";
12
+ readonly CREATE_TARGET_SNAPSHOT: "create_target_snapshot";
13
+ readonly WRITE_CRM_EVENT: "write_crm_event";
14
+ readonly SEND_KAKAO_MESSAGE: "send_kakao_message";
15
+ readonly SYNC_SEND_RESULT: "sync_send_result";
16
+ readonly IMPORT_PROSPECTS: "import_prospects";
17
+ readonly REGISTER_ENTRY_POINT: "register_entry_point";
18
+ readonly TRACK_ENGAGEMENT: "track_engagement";
19
+ };
20
+ export type McpToolName = (typeof MCP_TOOL)[keyof typeof MCP_TOOL];
21
+ export declare const APPROVAL_SCOPE: {
22
+ readonly TARGET_SNAPSHOT_CREATE: "target_snapshot:create";
23
+ readonly CRM_EVENT_WRITE: "crm_event:write";
24
+ readonly KAKAO_SEND: "kakao:send";
25
+ readonly PROSPECT_IMPORT: "prospect:import";
26
+ readonly ENTRY_POINT_REGISTER: "entry_point:register";
27
+ };
28
+ export type ApprovalScope = (typeof APPROVAL_SCOPE)[keyof typeof APPROVAL_SCOPE];
29
+ /** 도구별 필요 승인 scope. null = 승인 불필요. */
30
+ export declare const TOOL_APPROVAL_REQUIREMENT: Record<McpToolName, ApprovalScope | null>;
31
+ /** 승인 토큰이 필요한 도구 — 토큰 없이 호출되면 즉시 거부한다. */
32
+ export declare const APPROVAL_REQUIRED_TOOLS: ReadonlySet<McpToolName>;
33
+ export declare const ERROR_CODE: {
34
+ readonly NET_UNREACHABLE: "E_NET_UNREACHABLE";
35
+ readonly AUTH_FAILED: "E_AUTH_FAILED";
36
+ readonly OVERPRIVILEGED: "E_OVERPRIVILEGED";
37
+ readonly SCHEMA_MISSING_FIELD: "E_SCHEMA_MISSING_FIELD";
38
+ readonly TOKEN_EXPIRED: "E_TOKEN_EXPIRED";
39
+ readonly TOKEN_REUSED: "E_TOKEN_REUSED";
40
+ readonly TOKEN_SCOPE_MISMATCH: "E_TOKEN_SCOPE_MISMATCH";
41
+ readonly SNAPSHOT_HASH_MISMATCH: "E_SNAPSHOT_HASH_MISMATCH";
42
+ readonly CONSENT_AMBIGUOUS: "E_CONSENT_AMBIGUOUS";
43
+ readonly CRM_SCHEMA_NOT_READY: "E_CRM_SCHEMA_NOT_READY";
44
+ readonly PLATFORM_UNREACHABLE: "E_PLATFORM_UNREACHABLE";
45
+ };
46
+ export type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];
47
+ export declare const ERROR_HINTS: Record<ErrorCode, string>;
48
+ /** 필수 표준 필드 — 전부 매핑돼야 조건 프로젝트 활성화 가능 (기존 수용 기준) */
49
+ export declare const REQUIRED_MAPPING_FIELDS: readonly ["customerKey", "phoneNumberHash", "marketingOptIn", "reservationId", "productId", "pickupCompletedAt"];
50
+ export type RequiredMappingField = (typeof REQUIRED_MAPPING_FIELDS)[number];
51
+ /** preview_targets 제외 사유 — 원문 전화번호 미노출, 사유별 건수만 */
52
+ export declare const EXCLUSION_REASON: {
53
+ readonly CHANNEL_BLOCKED: "channel_blocked";
54
+ readonly UNSUBSCRIBED: "unsubscribed";
55
+ readonly MARKETING_OPT_OUT: "marketing_opt_out";
56
+ readonly PHONE_MISSING: "phone_missing";
57
+ readonly DUPLICATE: "duplicate";
58
+ readonly RECENT_SEND_LIMIT: "recent_send_limit";
59
+ readonly CONSENT_AMBIGUOUS: "consent_ambiguous";
60
+ };
61
+ export type ExclusionReason = (typeof EXCLUSION_REASON)[keyof typeof EXCLUSION_REASON];
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXCLUSION_REASON = exports.REQUIRED_MAPPING_FIELDS = exports.ERROR_HINTS = exports.ERROR_CODE = exports.APPROVAL_REQUIRED_TOOLS = exports.TOOL_APPROVAL_REQUIREMENT = exports.APPROVAL_SCOPE = exports.MCP_TOOL = void 0;
4
+ /**
5
+ * @crmforall/contracts 미러 (커넥터에 필요한 부분만).
6
+ * TODO: contracts 패키지 npm 발행 후 이 파일을 의존성으로 교체한다.
7
+ * 원본: crmforall-platform/packages/contracts/src/{mcp-tools,approval,errors,standard-fields}.ts
8
+ */
9
+ exports.MCP_TOOL = {
10
+ TEST_CONNECTION: "test_connection",
11
+ INSPECT_SCHEMA: "inspect_schema",
12
+ PROPOSE_FIELD_MAPPING: "propose_field_mapping",
13
+ VALIDATE_MAPPING: "validate_mapping",
14
+ PREVIEW_TARGETS: "preview_targets",
15
+ CREATE_TARGET_SNAPSHOT: "create_target_snapshot",
16
+ WRITE_CRM_EVENT: "write_crm_event",
17
+ SEND_KAKAO_MESSAGE: "send_kakao_message",
18
+ SYNC_SEND_RESULT: "sync_send_result",
19
+ IMPORT_PROSPECTS: "import_prospects",
20
+ REGISTER_ENTRY_POINT: "register_entry_point",
21
+ TRACK_ENGAGEMENT: "track_engagement",
22
+ };
23
+ exports.APPROVAL_SCOPE = {
24
+ TARGET_SNAPSHOT_CREATE: "target_snapshot:create",
25
+ CRM_EVENT_WRITE: "crm_event:write",
26
+ KAKAO_SEND: "kakao:send",
27
+ PROSPECT_IMPORT: "prospect:import",
28
+ ENTRY_POINT_REGISTER: "entry_point:register",
29
+ };
30
+ /** 도구별 필요 승인 scope. null = 승인 불필요. */
31
+ exports.TOOL_APPROVAL_REQUIREMENT = {
32
+ [exports.MCP_TOOL.TEST_CONNECTION]: null,
33
+ [exports.MCP_TOOL.INSPECT_SCHEMA]: null,
34
+ [exports.MCP_TOOL.PROPOSE_FIELD_MAPPING]: null,
35
+ [exports.MCP_TOOL.VALIDATE_MAPPING]: null,
36
+ [exports.MCP_TOOL.PREVIEW_TARGETS]: null,
37
+ [exports.MCP_TOOL.CREATE_TARGET_SNAPSHOT]: exports.APPROVAL_SCOPE.TARGET_SNAPSHOT_CREATE,
38
+ [exports.MCP_TOOL.WRITE_CRM_EVENT]: exports.APPROVAL_SCOPE.CRM_EVENT_WRITE,
39
+ [exports.MCP_TOOL.SEND_KAKAO_MESSAGE]: exports.APPROVAL_SCOPE.KAKAO_SEND,
40
+ [exports.MCP_TOOL.SYNC_SEND_RESULT]: null,
41
+ [exports.MCP_TOOL.IMPORT_PROSPECTS]: exports.APPROVAL_SCOPE.PROSPECT_IMPORT,
42
+ [exports.MCP_TOOL.REGISTER_ENTRY_POINT]: exports.APPROVAL_SCOPE.ENTRY_POINT_REGISTER,
43
+ [exports.MCP_TOOL.TRACK_ENGAGEMENT]: null,
44
+ };
45
+ /** 승인 토큰이 필요한 도구 — 토큰 없이 호출되면 즉시 거부한다. */
46
+ exports.APPROVAL_REQUIRED_TOOLS = new Set(Object.entries(exports.TOOL_APPROVAL_REQUIREMENT)
47
+ .filter(([, scope]) => scope !== null)
48
+ .map(([name]) => name));
49
+ exports.ERROR_CODE = {
50
+ NET_UNREACHABLE: "E_NET_UNREACHABLE",
51
+ AUTH_FAILED: "E_AUTH_FAILED",
52
+ OVERPRIVILEGED: "E_OVERPRIVILEGED",
53
+ SCHEMA_MISSING_FIELD: "E_SCHEMA_MISSING_FIELD",
54
+ TOKEN_EXPIRED: "E_TOKEN_EXPIRED",
55
+ TOKEN_REUSED: "E_TOKEN_REUSED",
56
+ TOKEN_SCOPE_MISMATCH: "E_TOKEN_SCOPE_MISMATCH",
57
+ SNAPSHOT_HASH_MISMATCH: "E_SNAPSHOT_HASH_MISMATCH",
58
+ CONSENT_AMBIGUOUS: "E_CONSENT_AMBIGUOUS",
59
+ CRM_SCHEMA_NOT_READY: "E_CRM_SCHEMA_NOT_READY",
60
+ PLATFORM_UNREACHABLE: "E_PLATFORM_UNREACHABLE",
61
+ };
62
+ exports.ERROR_HINTS = {
63
+ [exports.ERROR_CODE.NET_UNREACHABLE]: "DB 호스트에 도달할 수 없습니다. 방화벽/보안그룹에서 DB 포트의 아웃바운드 허용 여부를 확인하세요.",
64
+ [exports.ERROR_CODE.AUTH_FAILED]: "인증에 실패했습니다. 자격증명과 계정 잠금 여부를 확인하세요.",
65
+ [exports.ERROR_CODE.OVERPRIVILEGED]: "과권한 계정이 감지되었습니다. 출력된 권한 축소 SQL을 실행한 뒤 connect doctor를 다시 실행하세요. 과권한 상태에서는 활성화가 차단됩니다.",
66
+ [exports.ERROR_CODE.SCHEMA_MISSING_FIELD]: "필수 표준 필드가 매핑되지 않았습니다. propose_field_mapping을 실행하세요.",
67
+ [exports.ERROR_CODE.TOKEN_EXPIRED]: "토큰이 만료되었습니다. 웹 콘솔에서 재발급 받으세요.",
68
+ [exports.ERROR_CODE.TOKEN_REUSED]: "이미 사용된 승인 토큰입니다. 새 승인을 요청하세요.",
69
+ [exports.ERROR_CODE.TOKEN_SCOPE_MISMATCH]: "승인 토큰의 scope가 요청 작업과 일치하지 않습니다.",
70
+ [exports.ERROR_CODE.SNAPSHOT_HASH_MISMATCH]: "승인된 대상 스냅샷과 실행 대상이 다릅니다. 대상을 다시 산출하고 재승인 받으세요.",
71
+ [exports.ERROR_CODE.CONSENT_AMBIGUOUS]: "광고성 수신동의 또는 채널 상태가 불명확한 대상이 포함되어 발송이 차단되었습니다(fail-closed). 해당 대상은 제외 사유에 표시됩니다.",
72
+ [exports.ERROR_CODE.CRM_SCHEMA_NOT_READY]: "CRM 전용 스키마가 없거나 마이그레이션이 필요합니다. connect init 또는 connect upgrade를 실행하세요.",
73
+ [exports.ERROR_CODE.PLATFORM_UNREACHABLE]: "crmforall 플랫폼에 연결할 수 없습니다. 네트워크와 설치 토큰 상태를 확인하세요.",
74
+ };
75
+ /** 필수 표준 필드 — 전부 매핑돼야 조건 프로젝트 활성화 가능 (기존 수용 기준) */
76
+ exports.REQUIRED_MAPPING_FIELDS = [
77
+ "customerKey",
78
+ "phoneNumberHash",
79
+ "marketingOptIn",
80
+ "reservationId",
81
+ "productId",
82
+ "pickupCompletedAt",
83
+ ];
84
+ /** preview_targets 제외 사유 — 원문 전화번호 미노출, 사유별 건수만 */
85
+ exports.EXCLUSION_REASON = {
86
+ CHANNEL_BLOCKED: "channel_blocked",
87
+ UNSUBSCRIBED: "unsubscribed",
88
+ MARKETING_OPT_OUT: "marketing_opt_out",
89
+ PHONE_MISSING: "phone_missing",
90
+ DUPLICATE: "duplicate",
91
+ RECENT_SEND_LIMIT: "recent_send_limit",
92
+ CONSENT_AMBIGUOUS: "consent_ambiguous",
93
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * DB 어댑터 인터페이스 — PostgreSQL/MySQL/Supabase/카페24가 모두 이 계약을 구현한다.
3
+ * 근거: 마스터플랜 04_MCP_Connector §지원 데이터 소스, §Connection Readiness Gate.
4
+ */
5
+ export interface ColumnInfo {
6
+ schema: string;
7
+ table: string;
8
+ column: string;
9
+ dataType: string;
10
+ }
11
+ /** Readiness 검사 항목별 결과 */
12
+ export interface ReadinessCheck {
13
+ name: "connect" | "operational_select" | "crm_schema_exists" | "crm_schema_write" | "source_table_write_blocked" | "ddl_limited";
14
+ passed: boolean;
15
+ detail: string;
16
+ /** 과권한 감지 시 권한 축소 SQL 제안 (DBA 전달용) */
17
+ remediationSql?: string;
18
+ }
19
+ export interface ReadinessReport {
20
+ ready: boolean;
21
+ /** 과권한 감지 — true면 활성화 차단 (fail-closed) */
22
+ overprivileged: boolean;
23
+ checks: ReadinessCheck[];
24
+ }
25
+ /**
26
+ * SQL 직접 실행 능력 — 대상 산출(읽기)과 CRM 스키마 쓰기의 기반.
27
+ * selectRows는 읽기 전용 쿼리만, execute는 CRM 전용 스키마 쓰기만 받는다.
28
+ * (운영 원천 테이블 쓰기는 Readiness Gate에서 권한 자체를 차단한다)
29
+ */
30
+ export interface SqlExecutor {
31
+ selectRows(sql: string, params?: unknown[]): Promise<Record<string, unknown>[]>;
32
+ execute(sql: string, params?: unknown[]): Promise<{
33
+ rowCount: number;
34
+ }>;
35
+ }
36
+ export interface DbAdapter extends SqlExecutor {
37
+ readonly sourceType: "postgresql" | "mysql" | "supabase" | "cafe24";
38
+ /** 연결 시도 — 자격증명은 어댑터 생성 시 시크릿 저장소에서 주입된다 */
39
+ connect(): Promise<void>;
40
+ disconnect(): Promise<void>;
41
+ /**
42
+ * Connection Readiness Gate 일괄 검사.
43
+ * 핵심: 운영 원천 테이블에 쓰기 권한이 있으면 overprivileged=true로
44
+ * 활성화를 차단해야 한다 (기존 SPEC 계승).
45
+ */
46
+ checkReadiness(options: {
47
+ crmSchema: string;
48
+ }): Promise<ReadinessReport>;
49
+ /** 스키마 탐색 — 컬럼명 중심, 데이터 미노출 (inspect_schema 도구의 기반) */
50
+ listColumns(options: {
51
+ schemaAllowlist?: string[];
52
+ }): Promise<ColumnInfo[]>;
53
+ /** CRM 전용 스키마 생성/마이그레이션 (멱등) — 설치 단계에만 호출 */
54
+ ensureCrmSchema(options: {
55
+ crmSchema: string;
56
+ }): Promise<void>;
57
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * DB 어댑터 인터페이스 — PostgreSQL/MySQL/Supabase/카페24가 모두 이 계약을 구현한다.
4
+ * 근거: 마스터플랜 04_MCP_Connector §지원 데이터 소스, §Connection Readiness Gate.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import type { SqlExecutor } from "./adapter";
2
+ /**
3
+ * CRM 전용 스키마 DDL — 멱등 (CREATE TABLE IF NOT EXISTS).
4
+ * 근거: 01_데이터_모델 §고객사 측 CRM 스키마 (기존 9테이블 중 MVP 사용분).
5
+ * 구조는 고객사 DB든 관리형 테넌트 스키마든 동일해야 한다 (내보내기 보장의 기반).
6
+ * 호출 시점: 설치 단계(connect init)에만 — DDL 권한은 설치 시에만 허용된다.
7
+ */
8
+ export declare function validateSchemaName(crmSchema: string): void;
9
+ export declare function migrateCrmSchema(db: SqlExecutor, crmSchema: string): Promise<void>;