fullstackgtm 0.10.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.
- package/CHANGELOG.md +381 -0
- package/INSTALL_FOR_AGENTS.md +87 -0
- package/LICENSE +202 -0
- package/README.md +230 -0
- package/dist/audit.d.ts +7 -0
- package/dist/audit.js +202 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +6 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +915 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.js +85 -0
- package/dist/connector.d.ts +30 -0
- package/dist/connector.js +94 -0
- package/dist/connectors/hubspot.d.ts +20 -0
- package/dist/connectors/hubspot.js +409 -0
- package/dist/connectors/hubspotAuth.d.ts +42 -0
- package/dist/connectors/hubspotAuth.js +189 -0
- package/dist/connectors/salesforce.d.ts +26 -0
- package/dist/connectors/salesforce.js +318 -0
- package/dist/connectors/salesforceAuth.d.ts +44 -0
- package/dist/connectors/salesforceAuth.js +120 -0
- package/dist/connectors/stripe.d.ts +27 -0
- package/dist/connectors/stripe.js +176 -0
- package/dist/credentials.d.ts +75 -0
- package/dist/credentials.js +197 -0
- package/dist/demo.d.ts +20 -0
- package/dist/demo.js +169 -0
- package/dist/diff.d.ts +46 -0
- package/dist/diff.js +107 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.js +109 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +17 -0
- package/dist/mappings.d.ts +8 -0
- package/dist/mappings.js +123 -0
- package/dist/mcp-bin.d.ts +2 -0
- package/dist/mcp-bin.js +33 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +140 -0
- package/dist/merge.d.ts +48 -0
- package/dist/merge.js +145 -0
- package/dist/planStore.d.ts +31 -0
- package/dist/planStore.js +116 -0
- package/dist/rules.d.ts +24 -0
- package/dist/rules.js +512 -0
- package/dist/sampleData.d.ts +2 -0
- package/dist/sampleData.js +115 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.js +8 -0
- package/docs/api.md +72 -0
- package/docs/roadmap-to-1.0.md +121 -0
- package/llms.txt +25 -0
- package/package.json +76 -0
- package/src/audit.ts +242 -0
- package/src/bin.ts +7 -0
- package/src/cli.ts +1042 -0
- package/src/config.ts +113 -0
- package/src/connector.ts +140 -0
- package/src/connectors/hubspot.ts +528 -0
- package/src/connectors/hubspotAuth.ts +246 -0
- package/src/connectors/salesforce.ts +420 -0
- package/src/connectors/salesforceAuth.ts +167 -0
- package/src/connectors/stripe.ts +215 -0
- package/src/credentials.ts +282 -0
- package/src/demo.ts +200 -0
- package/src/diff.ts +158 -0
- package/src/format.ts +162 -0
- package/src/index.ts +129 -0
- package/src/mappings.ts +157 -0
- package/src/mcp-bin.ts +32 -0
- package/src/mcp.ts +185 -0
- package/src/merge.ts +235 -0
- package/src/planStore.ts +155 -0
- package/src/rules.ts +539 -0
- package/src/sampleData.ts +117 -0
- package/src/types.ts +372 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { CanonicalGtmSnapshot } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export const sampleSnapshot: CanonicalGtmSnapshot = {
|
|
4
|
+
generatedAt: "2026-05-03T12:00:00.000Z",
|
|
5
|
+
provider: "mock",
|
|
6
|
+
users: [
|
|
7
|
+
{
|
|
8
|
+
id: "user_ada",
|
|
9
|
+
provider: "mock",
|
|
10
|
+
crmId: "005-ada",
|
|
11
|
+
name: "Ada Lovelace",
|
|
12
|
+
email: "ada@example.com",
|
|
13
|
+
title: "Account Executive",
|
|
14
|
+
active: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
accounts: [
|
|
18
|
+
{
|
|
19
|
+
id: "acct_acme",
|
|
20
|
+
provider: "mock",
|
|
21
|
+
crmId: "001-acme",
|
|
22
|
+
name: "Acme Corp",
|
|
23
|
+
domain: "acme.example",
|
|
24
|
+
ownerId: "user_ada",
|
|
25
|
+
lastActivityAt: "2026-04-28",
|
|
26
|
+
lastSyncAt: "2026-05-03",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "acct_orphan",
|
|
30
|
+
provider: "mock",
|
|
31
|
+
crmId: "001-orphan",
|
|
32
|
+
name: "Orphan Industries",
|
|
33
|
+
domain: "orphan.example",
|
|
34
|
+
lastSyncAt: "2026-05-03",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
contacts: [
|
|
38
|
+
{
|
|
39
|
+
id: "contact_acme_cfo",
|
|
40
|
+
provider: "mock",
|
|
41
|
+
crmId: "003-acme-cfo",
|
|
42
|
+
accountId: "acct_acme",
|
|
43
|
+
firstName: "Grace",
|
|
44
|
+
lastName: "Hopper",
|
|
45
|
+
email: "grace@acme.example",
|
|
46
|
+
title: "CFO",
|
|
47
|
+
ownerId: "user_ada",
|
|
48
|
+
lastActivityAt: "2026-04-28",
|
|
49
|
+
lastSyncAt: "2026-05-03",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
deals: [
|
|
53
|
+
{
|
|
54
|
+
id: "deal_missing_owner",
|
|
55
|
+
provider: "mock",
|
|
56
|
+
crmId: "006-missing-owner",
|
|
57
|
+
accountId: "acct_acme",
|
|
58
|
+
name: "Acme Expansion",
|
|
59
|
+
amount: 12500000,
|
|
60
|
+
currency: "USD",
|
|
61
|
+
stage: "Proposal",
|
|
62
|
+
closeDate: "2026-06-30",
|
|
63
|
+
forecastCategory: "best_case",
|
|
64
|
+
probability: 0.6,
|
|
65
|
+
isClosed: false,
|
|
66
|
+
lastActivityAt: "2026-04-15",
|
|
67
|
+
lastSyncAt: "2026-05-03",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "deal_past_close",
|
|
71
|
+
provider: "mock",
|
|
72
|
+
crmId: "006-past-close",
|
|
73
|
+
accountId: "acct_acme",
|
|
74
|
+
ownerId: "user_ada",
|
|
75
|
+
name: "Acme New Division",
|
|
76
|
+
amount: 22000000,
|
|
77
|
+
currency: "USD",
|
|
78
|
+
stage: "Discovery",
|
|
79
|
+
closeDate: "2026-04-01",
|
|
80
|
+
forecastCategory: "pipeline",
|
|
81
|
+
probability: 0.3,
|
|
82
|
+
isClosed: false,
|
|
83
|
+
lastActivityAt: "2026-04-29",
|
|
84
|
+
lastSyncAt: "2026-05-03",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "deal_stale",
|
|
88
|
+
provider: "mock",
|
|
89
|
+
crmId: "006-stale",
|
|
90
|
+
accountId: "acct_acme",
|
|
91
|
+
ownerId: "user_ada",
|
|
92
|
+
name: "Acme Services",
|
|
93
|
+
amount: 4500000,
|
|
94
|
+
currency: "USD",
|
|
95
|
+
stage: "Demo",
|
|
96
|
+
closeDate: "2026-09-30",
|
|
97
|
+
forecastCategory: "pipeline",
|
|
98
|
+
probability: 0.2,
|
|
99
|
+
isClosed: false,
|
|
100
|
+
lastActivityAt: "2026-02-01",
|
|
101
|
+
lastSyncAt: "2026-05-03",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
activities: [
|
|
105
|
+
{
|
|
106
|
+
id: "act_acme_meeting",
|
|
107
|
+
provider: "mock",
|
|
108
|
+
crmId: "00T-acme",
|
|
109
|
+
accountId: "acct_acme",
|
|
110
|
+
dealId: "deal_past_close",
|
|
111
|
+
ownerId: "user_ada",
|
|
112
|
+
type: "meeting",
|
|
113
|
+
occurredAt: "2026-04-29",
|
|
114
|
+
subject: "Discovery follow-up",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical GTM data model.
|
|
3
|
+
*
|
|
4
|
+
* Amounts are expressed in major currency units (e.g. dollars), matching what
|
|
5
|
+
* CRM providers return natively. Adapters that store minor units (cents) must
|
|
6
|
+
* convert at their own boundary.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type CrmProvider =
|
|
10
|
+
| "salesforce"
|
|
11
|
+
| "hubspot"
|
|
12
|
+
| "mock"
|
|
13
|
+
| "unknown"
|
|
14
|
+
// Any other provider id (marketing automation, billing, call intelligence, …).
|
|
15
|
+
| (string & {});
|
|
16
|
+
|
|
17
|
+
export type RiskLevel = "low" | "medium" | "high";
|
|
18
|
+
|
|
19
|
+
export type ApprovalStatus =
|
|
20
|
+
| "draft"
|
|
21
|
+
| "needs_approval"
|
|
22
|
+
| "approved"
|
|
23
|
+
| "rejected"
|
|
24
|
+
| "applied";
|
|
25
|
+
|
|
26
|
+
export type GtmObjectType = "account" | "contact" | "deal" | "user" | "activity";
|
|
27
|
+
|
|
28
|
+
export type GtmEvidenceSourceSystem =
|
|
29
|
+
| "salesforce"
|
|
30
|
+
| "hubspot"
|
|
31
|
+
| "gong"
|
|
32
|
+
| "chorus"
|
|
33
|
+
| "fathom"
|
|
34
|
+
| "manual"
|
|
35
|
+
| "csv"
|
|
36
|
+
| "mock"
|
|
37
|
+
| "unknown";
|
|
38
|
+
|
|
39
|
+
export type PatchOperationType =
|
|
40
|
+
| "set_field"
|
|
41
|
+
| "clear_field"
|
|
42
|
+
| "link_record"
|
|
43
|
+
| "archive_record"
|
|
44
|
+
| "create_task";
|
|
45
|
+
|
|
46
|
+
export type AuditFindingSeverity = "info" | "warning" | "critical";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One claim that a canonical record exists in an external system. A record
|
|
50
|
+
* merged from several providers carries one identity per provider.
|
|
51
|
+
*/
|
|
52
|
+
export type ProviderIdentity = {
|
|
53
|
+
provider: CrmProvider;
|
|
54
|
+
externalId: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type PipelineFindingType =
|
|
58
|
+
| "call_next_step_not_reflected_in_crm"
|
|
59
|
+
| "deal_missing_next_step"
|
|
60
|
+
| "deal_stale_activity"
|
|
61
|
+
| "deal_past_close_date"
|
|
62
|
+
| "deal_missing_owner"
|
|
63
|
+
| "deal_missing_account";
|
|
64
|
+
|
|
65
|
+
export type PipelineFindingStatus =
|
|
66
|
+
| "open"
|
|
67
|
+
| "planned"
|
|
68
|
+
| "approved"
|
|
69
|
+
| "applied"
|
|
70
|
+
| "verified"
|
|
71
|
+
| "dismissed"
|
|
72
|
+
| "stale";
|
|
73
|
+
|
|
74
|
+
export type SourceFreshness = {
|
|
75
|
+
state: "fresh" | "stale" | "unknown";
|
|
76
|
+
checkedAt: string;
|
|
77
|
+
sourceUpdatedAt?: string;
|
|
78
|
+
ageDays?: number;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type GtmEvidence = {
|
|
82
|
+
id: string;
|
|
83
|
+
sourceSystem: GtmEvidenceSourceSystem;
|
|
84
|
+
sourceObjectType: string;
|
|
85
|
+
sourceObjectId: string;
|
|
86
|
+
objectType?: GtmObjectType;
|
|
87
|
+
objectId?: string;
|
|
88
|
+
title?: string;
|
|
89
|
+
text: string;
|
|
90
|
+
observedAt?: string;
|
|
91
|
+
capturedAt?: string;
|
|
92
|
+
freshness?: SourceFreshness;
|
|
93
|
+
metadata?: Record<string, unknown>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type PipelineFinding = {
|
|
97
|
+
id: string;
|
|
98
|
+
type: PipelineFindingType;
|
|
99
|
+
objectType: GtmObjectType;
|
|
100
|
+
objectId: string;
|
|
101
|
+
severity: AuditFindingSeverity;
|
|
102
|
+
status: PipelineFindingStatus;
|
|
103
|
+
title: string;
|
|
104
|
+
summary: string;
|
|
105
|
+
recommendation: string;
|
|
106
|
+
evidenceIds: string[];
|
|
107
|
+
currentCrmValue?: unknown;
|
|
108
|
+
proposedValue?: unknown;
|
|
109
|
+
freshness: SourceFreshness;
|
|
110
|
+
patchEligibility: {
|
|
111
|
+
eligible: boolean;
|
|
112
|
+
operation?: PatchOperationType | "none";
|
|
113
|
+
field?: string;
|
|
114
|
+
reason: string;
|
|
115
|
+
approvalRequired: boolean;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type PatchVerification = {
|
|
120
|
+
status: "not_started" | "pending" | "verified" | "failed" | "skipped";
|
|
121
|
+
checkedAt?: string;
|
|
122
|
+
sourceSystem?: GtmEvidenceSourceSystem;
|
|
123
|
+
expectedValue?: unknown;
|
|
124
|
+
observedValue?: unknown;
|
|
125
|
+
providerResult?: unknown;
|
|
126
|
+
auditText?: string;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type CanonicalUser = {
|
|
130
|
+
id: string;
|
|
131
|
+
provider?: CrmProvider;
|
|
132
|
+
crmId?: string;
|
|
133
|
+
identities?: ProviderIdentity[];
|
|
134
|
+
name: string;
|
|
135
|
+
email?: string;
|
|
136
|
+
title?: string;
|
|
137
|
+
active?: boolean;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type CanonicalAccount = {
|
|
141
|
+
id: string;
|
|
142
|
+
provider?: CrmProvider;
|
|
143
|
+
crmId?: string;
|
|
144
|
+
identities?: ProviderIdentity[];
|
|
145
|
+
name: string;
|
|
146
|
+
domain?: string;
|
|
147
|
+
industry?: string;
|
|
148
|
+
ownerId?: string;
|
|
149
|
+
employeeCount?: number;
|
|
150
|
+
annualRevenue?: number;
|
|
151
|
+
lastActivityAt?: string;
|
|
152
|
+
lastSyncAt?: string;
|
|
153
|
+
/** Provider-native payload escape hatch. Never read by audit rules. */
|
|
154
|
+
raw?: unknown;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export type CanonicalContact = {
|
|
158
|
+
id: string;
|
|
159
|
+
provider?: CrmProvider;
|
|
160
|
+
crmId?: string;
|
|
161
|
+
identities?: ProviderIdentity[];
|
|
162
|
+
accountId?: string;
|
|
163
|
+
firstName?: string;
|
|
164
|
+
lastName?: string;
|
|
165
|
+
email?: string;
|
|
166
|
+
phone?: string;
|
|
167
|
+
title?: string;
|
|
168
|
+
ownerId?: string;
|
|
169
|
+
lastActivityAt?: string;
|
|
170
|
+
lastSyncAt?: string;
|
|
171
|
+
/** Provider-native payload escape hatch. Never read by audit rules. */
|
|
172
|
+
raw?: unknown;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type CanonicalDeal = {
|
|
176
|
+
id: string;
|
|
177
|
+
provider?: CrmProvider;
|
|
178
|
+
crmId?: string;
|
|
179
|
+
identities?: ProviderIdentity[];
|
|
180
|
+
accountId?: string;
|
|
181
|
+
ownerId?: string;
|
|
182
|
+
name: string;
|
|
183
|
+
amount?: number;
|
|
184
|
+
currency?: string;
|
|
185
|
+
stage?: string;
|
|
186
|
+
closeDate?: string;
|
|
187
|
+
/** Provider-native deal type string (e.g. HubSpot `dealtype`), unmapped. */
|
|
188
|
+
dealType?: string;
|
|
189
|
+
forecastCategory?: string;
|
|
190
|
+
nextStep?: string;
|
|
191
|
+
probability?: number;
|
|
192
|
+
isClosed?: boolean;
|
|
193
|
+
isWon?: boolean;
|
|
194
|
+
lastActivityAt?: string;
|
|
195
|
+
lastSyncAt?: string;
|
|
196
|
+
/** Provider-native payload escape hatch. Never read by audit rules. */
|
|
197
|
+
raw?: unknown;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export type CanonicalActivity = {
|
|
201
|
+
id: string;
|
|
202
|
+
provider?: CrmProvider;
|
|
203
|
+
crmId?: string;
|
|
204
|
+
identities?: ProviderIdentity[];
|
|
205
|
+
accountId?: string;
|
|
206
|
+
contactId?: string;
|
|
207
|
+
dealId?: string;
|
|
208
|
+
ownerId?: string;
|
|
209
|
+
type: "call" | "email" | "meeting" | "note" | "task" | "other";
|
|
210
|
+
occurredAt: string;
|
|
211
|
+
subject?: string;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export type CanonicalGtmSnapshot = {
|
|
215
|
+
generatedAt: string;
|
|
216
|
+
provider: CrmProvider;
|
|
217
|
+
users: CanonicalUser[];
|
|
218
|
+
accounts: CanonicalAccount[];
|
|
219
|
+
contacts: CanonicalContact[];
|
|
220
|
+
deals: CanonicalDeal[];
|
|
221
|
+
activities: CanonicalActivity[];
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export type GtmPolicy = {
|
|
225
|
+
staleDealDays: number;
|
|
226
|
+
requireDealOwner: boolean;
|
|
227
|
+
requireAccountForDeal: boolean;
|
|
228
|
+
today: string;
|
|
229
|
+
/** Flag open deals without an amount (default true). */
|
|
230
|
+
requireDealAmount?: boolean;
|
|
231
|
+
/** Window ahead of today that counts as "closing soon" (default 14 days). */
|
|
232
|
+
closingSoonDays?: number;
|
|
233
|
+
/** Idle days that make a closing-soon deal a finding (default 7). */
|
|
234
|
+
closingSoonIdleDays?: number;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export type AuditFinding = {
|
|
238
|
+
id: string;
|
|
239
|
+
objectType: GtmObjectType;
|
|
240
|
+
objectId: string;
|
|
241
|
+
ruleId: string;
|
|
242
|
+
title: string;
|
|
243
|
+
severity: AuditFindingSeverity;
|
|
244
|
+
summary: string;
|
|
245
|
+
recommendation: string;
|
|
246
|
+
type?: PipelineFindingType;
|
|
247
|
+
evidenceIds?: string[];
|
|
248
|
+
currentCrmValue?: unknown;
|
|
249
|
+
proposedValue?: unknown;
|
|
250
|
+
freshness?: SourceFreshness;
|
|
251
|
+
patchEligible?: boolean;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export type PatchOperation = {
|
|
255
|
+
id: string;
|
|
256
|
+
objectType: GtmObjectType;
|
|
257
|
+
objectId: string;
|
|
258
|
+
operation: PatchOperationType;
|
|
259
|
+
field?: string;
|
|
260
|
+
beforeValue?: unknown;
|
|
261
|
+
afterValue?: unknown;
|
|
262
|
+
reason: string;
|
|
263
|
+
riskLevel: RiskLevel;
|
|
264
|
+
approvalRequired: boolean;
|
|
265
|
+
sourceRuleOrPolicy?: string;
|
|
266
|
+
rollback?: string;
|
|
267
|
+
evidenceIds?: string[];
|
|
268
|
+
findingIds?: string[];
|
|
269
|
+
verification?: PatchVerification;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* A patch plan is always a dry-run proposal. Applying a plan never mutates
|
|
274
|
+
* the plan itself; the outcome is recorded separately as a `PatchPlanRun`.
|
|
275
|
+
*/
|
|
276
|
+
export type PatchPlan = {
|
|
277
|
+
id: string;
|
|
278
|
+
title: string;
|
|
279
|
+
createdAt: string;
|
|
280
|
+
status: ApprovalStatus;
|
|
281
|
+
dryRun: true;
|
|
282
|
+
summary: string;
|
|
283
|
+
findings: AuditFinding[];
|
|
284
|
+
pipelineFindings?: PipelineFinding[];
|
|
285
|
+
evidence?: GtmEvidence[];
|
|
286
|
+
operations: PatchOperation[];
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// ── Audit rule engine ──────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
/** Pre-computed lookups shared by all rules so each rule stays O(n). */
|
|
292
|
+
export type GtmSnapshotIndex = {
|
|
293
|
+
usersById: Map<string, CanonicalUser>;
|
|
294
|
+
accountsById: Map<string, CanonicalAccount>;
|
|
295
|
+
contactsByAccountId: Map<string, CanonicalContact[]>;
|
|
296
|
+
dealsByAccountId: Map<string, CanonicalDeal[]>;
|
|
297
|
+
activitiesByDealId: Map<string, CanonicalActivity[]>;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export type GtmRuleContext = {
|
|
301
|
+
snapshot: CanonicalGtmSnapshot;
|
|
302
|
+
policy: GtmPolicy;
|
|
303
|
+
index: GtmSnapshotIndex;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export type GtmRuleResult = {
|
|
307
|
+
findings: AuditFinding[];
|
|
308
|
+
operations: PatchOperation[];
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* A deterministic audit rule. Rules read the snapshot through the context and
|
|
313
|
+
* return findings plus dry-run patch operations; they never perform writes.
|
|
314
|
+
*/
|
|
315
|
+
export type GtmAuditRule = {
|
|
316
|
+
id: string;
|
|
317
|
+
title: string;
|
|
318
|
+
description: string;
|
|
319
|
+
/** Grouping for docs and discovery, e.g. "hygiene", "forecast", "coverage". */
|
|
320
|
+
category?: string;
|
|
321
|
+
evaluate: (context: GtmRuleContext) => GtmRuleResult;
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// ── Connectors and patch application ──────────────────────────
|
|
325
|
+
|
|
326
|
+
export type PatchOperationResult = {
|
|
327
|
+
operationId: string;
|
|
328
|
+
/** `conflict`: the provider value drifted since the plan was proposed; nothing was written. */
|
|
329
|
+
status: "applied" | "failed" | "skipped" | "conflict";
|
|
330
|
+
detail?: string;
|
|
331
|
+
providerData?: unknown;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
export type PatchPlanRunStatus = "applied" | "partial" | "failed" | "rejected";
|
|
335
|
+
|
|
336
|
+
/** The record of one attempt to apply an approved patch plan. */
|
|
337
|
+
export type PatchPlanRun = {
|
|
338
|
+
planId: string;
|
|
339
|
+
provider: CrmProvider;
|
|
340
|
+
startedAt: string;
|
|
341
|
+
finishedAt: string;
|
|
342
|
+
status: PatchPlanRunStatus;
|
|
343
|
+
results: PatchOperationResult[];
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The provider contract. Reads produce a canonical snapshot; writes accept a
|
|
348
|
+
* single patch operation and report the outcome. Connectors without
|
|
349
|
+
* `applyOperation` are read-only.
|
|
350
|
+
*/
|
|
351
|
+
export type GtmConnector = {
|
|
352
|
+
provider: CrmProvider;
|
|
353
|
+
fetchSnapshot: () => Promise<CanonicalGtmSnapshot>;
|
|
354
|
+
applyOperation?: (operation: PatchOperation) => Promise<PatchOperationResult>;
|
|
355
|
+
/**
|
|
356
|
+
* Read the live value of one canonical field, used for compare-and-set:
|
|
357
|
+
* apply orchestration refuses to write over values that drifted since the
|
|
358
|
+
* plan was proposed.
|
|
359
|
+
*/
|
|
360
|
+
readField?: (
|
|
361
|
+
objectType: GtmObjectType,
|
|
362
|
+
objectId: string,
|
|
363
|
+
field: string,
|
|
364
|
+
) => Promise<unknown>;
|
|
365
|
+
/**
|
|
366
|
+
* Records modified since the given ISO timestamp, as a partial snapshot —
|
|
367
|
+
* the incremental alternative to `fetchSnapshot` for frequent syncs.
|
|
368
|
+
* Provider change feeds may omit cross-record associations; consumers
|
|
369
|
+
* merging deltas must preserve associations from the last full snapshot.
|
|
370
|
+
*/
|
|
371
|
+
fetchChanges?: (sinceIso: string) => Promise<CanonicalGtmSnapshot>;
|
|
372
|
+
};
|