@mj-biz-apps/sales-core-entities-server 5.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.
Files changed (106) hide show
  1. package/LICENSE +183 -0
  2. package/dist/CloseDealOperation.d.ts +176 -0
  3. package/dist/CloseDealOperation.d.ts.map +1 -0
  4. package/dist/CloseDealOperation.js +1107 -0
  5. package/dist/CloseDealOperation.js.map +1 -0
  6. package/dist/CloseWonTaskService.d.ts +249 -0
  7. package/dist/CloseWonTaskService.d.ts.map +1 -0
  8. package/dist/CloseWonTaskService.js +460 -0
  9. package/dist/CloseWonTaskService.js.map +1 -0
  10. package/dist/DealEntityServer.d.ts +625 -0
  11. package/dist/DealEntityServer.d.ts.map +1 -0
  12. package/dist/DealEntityServer.js +1529 -0
  13. package/dist/DealEntityServer.js.map +1 -0
  14. package/dist/LiveContractsSeam.d.ts +212 -0
  15. package/dist/LiveContractsSeam.d.ts.map +1 -0
  16. package/dist/LiveContractsSeam.js +362 -0
  17. package/dist/LiveContractsSeam.js.map +1 -0
  18. package/dist/LiveOrdersSeam.d.ts +68 -0
  19. package/dist/LiveOrdersSeam.d.ts.map +1 -0
  20. package/dist/LiveOrdersSeam.js +80 -0
  21. package/dist/LiveOrdersSeam.js.map +1 -0
  22. package/dist/SaveDealOperation.d.ts +45 -0
  23. package/dist/SaveDealOperation.d.ts.map +1 -0
  24. package/dist/SaveDealOperation.js +268 -0
  25. package/dist/SaveDealOperation.js.map +1 -0
  26. package/dist/SequenceService.d.ts +39 -0
  27. package/dist/SequenceService.d.ts.map +1 -0
  28. package/dist/SequenceService.js +44 -0
  29. package/dist/SequenceService.js.map +1 -0
  30. package/dist/activities/ActivityIngestService.d.ts +111 -0
  31. package/dist/activities/ActivityIngestService.d.ts.map +1 -0
  32. package/dist/activities/ActivityIngestService.js +485 -0
  33. package/dist/activities/ActivityIngestService.js.map +1 -0
  34. package/dist/activities/ActivityReader.d.ts +68 -0
  35. package/dist/activities/ActivityReader.d.ts.map +1 -0
  36. package/dist/activities/ActivityReader.js +147 -0
  37. package/dist/activities/ActivityReader.js.map +1 -0
  38. package/dist/activities/ActivitySource.d.ts +142 -0
  39. package/dist/activities/ActivitySource.d.ts.map +1 -0
  40. package/dist/activities/ActivitySource.js +2 -0
  41. package/dist/activities/ActivitySource.js.map +1 -0
  42. package/dist/activities/ActivitySyncJob.d.ts +72 -0
  43. package/dist/activities/ActivitySyncJob.d.ts.map +1 -0
  44. package/dist/activities/ActivitySyncJob.js +146 -0
  45. package/dist/activities/ActivitySyncJob.js.map +1 -0
  46. package/dist/activities/ActivityWriterService.d.ts +119 -0
  47. package/dist/activities/ActivityWriterService.d.ts.map +1 -0
  48. package/dist/activities/ActivityWriterService.js +270 -0
  49. package/dist/activities/ActivityWriterService.js.map +1 -0
  50. package/dist/activities/DealMatcher.d.ts +58 -0
  51. package/dist/activities/DealMatcher.d.ts.map +1 -0
  52. package/dist/activities/DealMatcher.js +109 -0
  53. package/dist/activities/DealMatcher.js.map +1 -0
  54. package/dist/activities/FixtureActivitySource.d.ts +37 -0
  55. package/dist/activities/FixtureActivitySource.d.ts.map +1 -0
  56. package/dist/activities/FixtureActivitySource.js +58 -0
  57. package/dist/activities/FixtureActivitySource.js.map +1 -0
  58. package/dist/activities/GraphMessageMapper.d.ts +47 -0
  59. package/dist/activities/GraphMessageMapper.d.ts.map +1 -0
  60. package/dist/activities/GraphMessageMapper.js +141 -0
  61. package/dist/activities/GraphMessageMapper.js.map +1 -0
  62. package/dist/activities/ImportedGraphActivitySource.d.ts +16 -0
  63. package/dist/activities/ImportedGraphActivitySource.d.ts.map +1 -0
  64. package/dist/activities/ImportedGraphActivitySource.js +84 -0
  65. package/dist/activities/ImportedGraphActivitySource.js.map +1 -0
  66. package/dist/activities/MSGraphActivitySource.d.ts +67 -0
  67. package/dist/activities/MSGraphActivitySource.d.ts.map +1 -0
  68. package/dist/activities/MSGraphActivitySource.js +186 -0
  69. package/dist/activities/MSGraphActivitySource.js.map +1 -0
  70. package/dist/activities/MSGraphCalendarSource.d.ts +72 -0
  71. package/dist/activities/MSGraphCalendarSource.d.ts.map +1 -0
  72. package/dist/activities/MSGraphCalendarSource.js +225 -0
  73. package/dist/activities/MSGraphCalendarSource.js.map +1 -0
  74. package/dist/activities/RelevanceFilter.d.ts +101 -0
  75. package/dist/activities/RelevanceFilter.d.ts.map +1 -0
  76. package/dist/activities/RelevanceFilter.js +148 -0
  77. package/dist/activities/RelevanceFilter.js.map +1 -0
  78. package/dist/activities/activity-vocabulary.d.ts +103 -0
  79. package/dist/activities/activity-vocabulary.d.ts.map +1 -0
  80. package/dist/activities/activity-vocabulary.js +79 -0
  81. package/dist/activities/activity-vocabulary.js.map +1 -0
  82. package/dist/forecast/FixtureForecastSource.d.ts +22 -0
  83. package/dist/forecast/FixtureForecastSource.d.ts.map +1 -0
  84. package/dist/forecast/FixtureForecastSource.js +16 -0
  85. package/dist/forecast/FixtureForecastSource.js.map +1 -0
  86. package/dist/forecast/ForecastSnapshotJob.d.ts +78 -0
  87. package/dist/forecast/ForecastSnapshotJob.d.ts.map +1 -0
  88. package/dist/forecast/ForecastSnapshotJob.js +225 -0
  89. package/dist/forecast/ForecastSnapshotJob.js.map +1 -0
  90. package/dist/forecast/ForecastSource.d.ts +74 -0
  91. package/dist/forecast/ForecastSource.d.ts.map +1 -0
  92. package/dist/forecast/ForecastSource.js +25 -0
  93. package/dist/forecast/ForecastSource.js.map +1 -0
  94. package/dist/forecast/QueryForecastSource.d.ts +76 -0
  95. package/dist/forecast/QueryForecastSource.d.ts.map +1 -0
  96. package/dist/forecast/QueryForecastSource.js +164 -0
  97. package/dist/forecast/QueryForecastSource.js.map +1 -0
  98. package/dist/index.d.ts +117 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +125 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/orders-availability.d.ts +15 -0
  103. package/dist/orders-availability.d.ts.map +1 -0
  104. package/dist/orders-availability.js +47 -0
  105. package/dist/orders-availability.js.map +1 -0
  106. package/package.json +43 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @fileoverview The ingest run: source → filter → match → write, with the watermark and failure state.
3
+ *
4
+ * Everything here is behind `IActivitySource`, so this whole file is exercised by the fixture source and
5
+ * needs no mailbox. Order is not negotiable and is enforced by the shape of the method:
6
+ *
7
+ * 1. `Fetch` — the source hands over normalized items
8
+ * 2. `Apply` — the relevance filter discards anything involving no known contact
9
+ * 3. `MatchOpenDeals` — resolve which deal, if any
10
+ * 4. `LogActivity` — write, idempotently
11
+ * 5. watermark — advance `LastSyncAt` only if the run got that far
12
+ *
13
+ * Step 2 is the privacy boundary. No model call may be inserted before it; see `RelevanceFilter`.
14
+ *
15
+ * @module @mj-biz-apps/sales-core-entities-server
16
+ */
17
+ import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
18
+ import { ActivityWriterService } from './ActivityWriterService.js';
19
+ import { DealMatcher } from './DealMatcher.js';
20
+ import { RelevanceFilter } from './RelevanceFilter.js';
21
+ import type { IActivitySource } from './ActivitySource.js';
22
+ export interface IngestRunResult {
23
+ Success: boolean;
24
+ Fetched: number;
25
+ /** Passed the relevance filter. */
26
+ Relevant: number;
27
+ /** Discarded because no participant was a known contact. Not a failure — the filter working. */
28
+ Irrelevant: number;
29
+ Written: number;
30
+ /** Matched an existing `(SourceSystem, ExternalID)`; nothing written. The idempotency count. */
31
+ Duplicates: number;
32
+ /** Relevant, but matched no open deal. Filed against the parties only. */
33
+ Unattributed: number;
34
+ /**
35
+ * Items this run FAILED on, as opposed to discarded.
36
+ *
37
+ * The distinction is the whole point and the code did not make it. A discarded item has been SEEN
38
+ * — the filter looked at it and said no — so the watermark may pass it. A FAILED item has not been
39
+ * seen to a conclusion, and because `GetMessages` has no date filter (D-17) anything the watermark
40
+ * passes can never be re-fetched. Advancing over a failure loses the item permanently.
41
+ */
42
+ Failed: number;
43
+ WatermarkAdvancedTo: Date | null;
44
+ Issues: string[];
45
+ }
46
+ export declare class ActivityIngestService {
47
+ private readonly writer;
48
+ private readonly filter;
49
+ private readonly matcher;
50
+ /**
51
+ * The collaborators are injectable, defaulting to the real ones.
52
+ *
53
+ * ── WHY, AND IT IS NOT FOR TIDINESS ──
54
+ *
55
+ * A mutation pass found that nothing exercised the LOOKUP-FAILURE path: reverting
56
+ * `RelevanceFilter`'s `failed: true` to `false` killed no check, because the only failure any check
57
+ * could force was a failed WRITE. So the branch that holds the watermark when a contact-method read
58
+ * blips was untested — the exact branch that stops a transient error discarding a batch of real
59
+ * mail permanently.
60
+ *
61
+ * A read failure cannot be provoked from outside without breaking the database for everything else.
62
+ * Injecting a filter that reports one is the only way to reach it, and it costs three fields.
63
+ */
64
+ constructor(writer?: ActivityWriterService, filter?: RelevanceFilter, matcher?: DealMatcher);
65
+ /**
66
+ * Runs one sync for one connection.
67
+ *
68
+ * ── WHY FAILURE LANDS ON THE CONNECTION ROW AND NOT IN A LOG ──
69
+ *
70
+ * `ActivitySyncConnection` has `Status` (Active/Paused/Error/Disabled) and `LastError` because a sync
71
+ * that has been failing for a week is an operational fact somebody has to see. A log line is seen by
72
+ * whoever happens to be tailing it. So a failed run sets `Status = 'Error'` with the reason, and a
73
+ * successful one clears both — which means the row answers "is this working" without anyone reading
74
+ * anything else.
75
+ */
76
+ RunSync(connectionID: string, source: IActivitySource, provider: IMetadataProvider, contextUser: UserInfo, limit?: number): Promise<IngestRunResult>;
77
+ /**
78
+ * Writes one relevant item — once per matched deal, or once against the parties when none matched.
79
+ *
80
+ * ── THE UNATTRIBUTED CASE IS THE POINT, NOT THE FALLBACK ──
81
+ *
82
+ * S-US10's third criterion is that items link to account and contact "even where no deal match
83
+ * exists". The writer needs a `DealID`, so an unattributed item cannot go through the same path —
84
+ * instead the parties are recorded as UNRESOLVED IDENTITIES on the deal-less side. That keeps the
85
+ * evidence without inventing a stub Person, which the common migration forbids.
86
+ */
87
+ private fileItem;
88
+ /**
89
+ * Whether the stored rules allow this item.
90
+ *
91
+ * `Sequence` order, LAST MATCH WINS, default include. That is the ordinary firewall convention and
92
+ * the one a reader of `Sequence` + `Action` would expect — but nothing in the schema states it, so it
93
+ * is a choice recorded here rather than a fact read off the table. See D-21.
94
+ *
95
+ * `Filter` is NOT evaluated. It is a free-text column with no stated grammar, and inventing one here
96
+ * would mean every future consumer had to match an undocumented dialect.
97
+ */
98
+ private rulesAllow;
99
+ private loadConnection;
100
+ private loadRules;
101
+ /** The key in `Settings` holding the calendar watermark. */
102
+ private static readonly CALENDAR_WATERMARK;
103
+ /** The watermark for one surface. Messages use the column; the calendar uses `Settings`. */
104
+ private watermarkFor;
105
+ private advanceWatermark;
106
+ private recordFailure;
107
+ /** A clean run clears the error, so `Status` reflects the LAST run rather than the worst one. */
108
+ private clearFailure;
109
+ private connectionEntity;
110
+ }
111
+ //# sourceMappingURL=ActivityIngestService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActivityIngestService.d.ts","sourceRoot":"","sources":["../../src/activities/ActivityIngestService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAqB,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhG,OAAO,EAAE,qBAAqB,EAAmC,MAAM,4BAA4B,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAyB,MAAM,sBAAsB,CAAC;AAE9E,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,qBAAqB,CAAC;AAG3E,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,EAAE,IAAI,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAyBD,qBAAa,qBAAqB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IAEtC;;;;;;;;;;;;;OAaG;gBAEC,MAAM,GAAE,qBAAmD,EAC3D,MAAM,GAAE,eAAuC,EAC/C,OAAO,GAAE,WAA+B;IAO5C;;;;;;;;;;OAUG;IACU,OAAO,CAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,QAAQ,EACrB,KAAK,SAAM,GACZ,OAAO,CAAC,eAAe,CAAC;IAsL3B;;;;;;;;;OASG;YACW,QAAQ;IA4HtB;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;YAoBJ,cAAc;YAad,SAAS;IAavB,4DAA4D;IAC5D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAwB;IAElE,4FAA4F;IAC5F,OAAO,CAAC,YAAY;YAiBN,gBAAgB;YAoChB,aAAa;IAiB3B,iGAAiG;YACnF,YAAY;YAmBZ,gBAAgB;CAWjC"}
@@ -0,0 +1,485 @@
1
+ /**
2
+ * @fileoverview The ingest run: source → filter → match → write, with the watermark and failure state.
3
+ *
4
+ * Everything here is behind `IActivitySource`, so this whole file is exercised by the fixture source and
5
+ * needs no mailbox. Order is not negotiable and is enforced by the shape of the method:
6
+ *
7
+ * 1. `Fetch` — the source hands over normalized items
8
+ * 2. `Apply` — the relevance filter discards anything involving no known contact
9
+ * 3. `MatchOpenDeals` — resolve which deal, if any
10
+ * 4. `LogActivity` — write, idempotently
11
+ * 5. watermark — advance `LastSyncAt` only if the run got that far
12
+ *
13
+ * Step 2 is the privacy boundary. No model call may be inserted before it; see `RelevanceFilter`.
14
+ *
15
+ * @module @mj-biz-apps/sales-core-entities-server
16
+ */
17
+ import { LogError, RunView } from '@memberjunction/core';
18
+ import { ActivityWriterService, escapeSql } from './ActivityWriterService.js';
19
+ import { DealMatcher } from './DealMatcher.js';
20
+ import { RelevanceFilter } from './RelevanceFilter.js';
21
+ import { E_ACTIVITY_SYNC_CONNECTION, E_ACTIVITY_SYNC_RULE } from '@mj-biz-apps/sales-entities';
22
+ export class ActivityIngestService {
23
+ /**
24
+ * The collaborators are injectable, defaulting to the real ones.
25
+ *
26
+ * ── WHY, AND IT IS NOT FOR TIDINESS ──
27
+ *
28
+ * A mutation pass found that nothing exercised the LOOKUP-FAILURE path: reverting
29
+ * `RelevanceFilter`'s `failed: true` to `false` killed no check, because the only failure any check
30
+ * could force was a failed WRITE. So the branch that holds the watermark when a contact-method read
31
+ * blips was untested — the exact branch that stops a transient error discarding a batch of real
32
+ * mail permanently.
33
+ *
34
+ * A read failure cannot be provoked from outside without breaking the database for everything else.
35
+ * Injecting a filter that reports one is the only way to reach it, and it costs three fields.
36
+ */
37
+ constructor(writer = new ActivityWriterService(), filter = new RelevanceFilter(), matcher = new DealMatcher()) {
38
+ this.writer = writer;
39
+ this.filter = filter;
40
+ this.matcher = matcher;
41
+ }
42
+ /**
43
+ * Runs one sync for one connection.
44
+ *
45
+ * ── WHY FAILURE LANDS ON THE CONNECTION ROW AND NOT IN A LOG ──
46
+ *
47
+ * `ActivitySyncConnection` has `Status` (Active/Paused/Error/Disabled) and `LastError` because a sync
48
+ * that has been failing for a week is an operational fact somebody has to see. A log line is seen by
49
+ * whoever happens to be tailing it. So a failed run sets `Status = 'Error'` with the reason, and a
50
+ * successful one clears both — which means the row answers "is this working" without anyone reading
51
+ * anything else.
52
+ */
53
+ async RunSync(connectionID, source, provider, contextUser, limit = 100) {
54
+ const result = {
55
+ Success: false,
56
+ Fetched: 0,
57
+ Relevant: 0,
58
+ Irrelevant: 0,
59
+ Written: 0,
60
+ Duplicates: 0,
61
+ Unattributed: 0,
62
+ Failed: 0,
63
+ WatermarkAdvancedTo: null,
64
+ Issues: [],
65
+ };
66
+ const connection = await this.loadConnection(connectionID, contextUser);
67
+ if (!connection) {
68
+ result.Issues.push(`No ActivitySyncConnection with ID '${connectionID}' exists.`);
69
+ return result;
70
+ }
71
+ /**
72
+ * ONLY `Active` RUNS. Paused, Error and Disabled all mean "do not read this mailbox", and the
73
+ * distinction between them is for a human — an operator paused it, a run failed, an admin turned
74
+ * it off. Treating any of them as runnable would make pausing advisory.
75
+ */
76
+ /*
77
+ * WHY THIS IS EXEMPT FROM THE VOCABULARY GATE, which correctly flagged it.
78
+ *
79
+ * `ActivitySyncConnection.Status` is a CHECK-constrained OPERATIONAL state in another app's
80
+ * schema, not domain vocabulary: there is no companion type table and no behaviour flag to read
81
+ * instead -- the column IS the state, and `recordFailure` below writes it. Renaming a value is a
82
+ * migration in bizapps-common, not somebody editing a lookup row, so the hazard the gate guards
83
+ * against -- a rename silently changing behaviour -- cannot arise here.
84
+ */
85
+ /**
86
+ * `Error` IS RETRIED. `Paused` and `Disabled` are not.
87
+ *
88
+ * This gate used to refuse anything but `Active`, and `clearFailure` sat inside the try block
89
+ * the gate protected — so one Graph throttle latched the connection off permanently. Nothing
90
+ * would retry it, and the job then reported SUCCESS with NO_CONNECTIONS when the only mailbox
91
+ * had been disabled by a transient error. Recovering meant a manual UPDATE on another app's
92
+ * table.
93
+ *
94
+ * The distinction the gate was missing: `Paused` and `Disabled` are decisions a PERSON made and
95
+ * must be honoured, while `Error` is a record of the last run and says nothing about whether
96
+ * the next one will work. So `Error` retries, and a clean run clears it.
97
+ */
98
+ const runnable = connection.Status === 'Active' || connection.Status === 'Error'; // vocabulary-grep-allow: operational state, not vocabulary
99
+ if (!runnable) {
100
+ result.Issues.push(`Connection '${connectionID}' is ${connection.Status}, not Active, so no sync was attempted.`);
101
+ return result;
102
+ }
103
+ if (connection.Provider !== source.ProviderName) {
104
+ result.Issues.push(`Connection provider '${connection.Provider}' does not match the source '${source.ProviderName}'.`);
105
+ return result;
106
+ }
107
+ if (!connection.Mailbox) {
108
+ result.Issues.push(`Connection '${connectionID}' names no mailbox.`);
109
+ await this.recordFailure(connectionID, 'No mailbox is configured.', provider, contextUser);
110
+ return result;
111
+ }
112
+ try {
113
+ /**
114
+ * ONE WATERMARK PER SURFACE, and this is a correctness requirement rather than tidiness.
115
+ *
116
+ * A connection has two sources -- messages and calendar -- and they advance independently. A
117
+ * shared `LastSyncAt` would take the max of both, so a meeting older than the newest email
118
+ * would be considered already-seen and SKIPPED FOREVER: the watermark moved past it without
119
+ * the calendar source ever having been asked. Nothing would error and no row would be
120
+ * missing from anything anyone counts.
121
+ *
122
+ * `LastSyncAt` stays the MESSAGE watermark, so nothing about existing behaviour changes, and
123
+ * the calendar's lives in `Settings` -- an existing nullable JSON column on the connection.
124
+ * That avoids a migration in another app's schema for a field only this app reads.
125
+ */
126
+ const since = this.watermarkFor(connection, source.Kind);
127
+ const batch = await source.Fetch({ Mailbox: connection.Mailbox, Since: since, Limit: limit });
128
+ result.Fetched = batch.Items.length;
129
+ result.Issues.push(...batch.Issues);
130
+ const rules = await this.loadRules(connectionID, contextUser);
131
+ const allowed = batch.Items.filter((item) => this.rulesAllow(item, rules));
132
+ if (allowed.length !== batch.Items.length) {
133
+ result.Issues.push(`${batch.Items.length - allowed.length} item(s) were excluded by ActivitySyncRule rows.`);
134
+ }
135
+ // ── THE PRIVACY BOUNDARY. Nothing above this line has looked at content for meaning. ──
136
+ const filtered = await this.filter.Apply(allowed, contextUser);
137
+ if (filtered.LookupFailed) {
138
+ /**
139
+ * A FAILED LOOKUP IS NOT AN IRRELEVANT BATCH. Counting these as failures is what holds
140
+ * the watermark: without it a database blip discarded the whole batch as personal mail
141
+ * and moved past it, and D-17 means it could never be re-fetched.
142
+ */
143
+ result.Failed += allowed.length;
144
+ result.Issues.push('The contact-method lookup failed, so nothing could be judged relevant. The watermark '
145
+ + 'was held and these items will be re-read.');
146
+ return result;
147
+ }
148
+ const relevant = filtered.Verdicts.filter((v) => v.IsRelevant);
149
+ result.Relevant = relevant.length;
150
+ result.Irrelevant = filtered.Verdicts.length - relevant.length;
151
+ for (const verdict of relevant) {
152
+ await this.fileItem(verdict, connection, source, provider, contextUser, result);
153
+ }
154
+ /**
155
+ * THE WATERMARK ADVANCES ON A SUCCESSFUL RUN ONLY, and to the newest item the SOURCE reported
156
+ * rather than the newest one written. An item the filter discarded has still been seen, and
157
+ * not advancing past it would re-read and re-discard it forever.
158
+ */
159
+ /**
160
+ * THE WATERMARK ONLY MOVES IF NOTHING FAILED.
161
+ *
162
+ * Two ways a failure used to slip past it. `fileItem` records a failed write as an Issue
163
+ * and continues, and `LogActivity` returns `Success: false` rather than throwing — so the
164
+ * loop completed and `result.Success` was set true regardless. And `RelevanceFilter.lookup`
165
+ * and `DealMatcher` both return empty on a READ failure, which made a transient database
166
+ * blip look exactly like a batch of irrelevant mail.
167
+ *
168
+ * Either way the watermark advanced over items that were never written, and D-17 means they
169
+ * can never be re-fetched. Holding the watermark costs one re-read on the next run, which
170
+ * dedupe absorbs; advancing over a failure costs the item.
171
+ */
172
+ if (batch.HighWatermark && result.Failed === 0) {
173
+ await this.advanceWatermark(connectionID, batch.HighWatermark, source.Kind, provider, contextUser, result);
174
+ }
175
+ if (result.Failed > 0) {
176
+ result.Issues.push(`${result.Failed} item(s) failed rather than being discarded, so the watermark was `
177
+ + 'held. They will be re-read next run and dedupe will absorb anything that did land.');
178
+ }
179
+ /**
180
+ * A run that failed on individual items is NOT a success. It used to report one, which is
181
+ * what let the connection look healthy while losing mail.
182
+ */
183
+ result.Success = result.Failed === 0;
184
+ /**
185
+ * ── AND THE FAILURE ROW IS ONLY CLEARED ON AN ACTUAL SUCCESS ────────────────────
186
+ *
187
+ * `clearFailure` ran BEFORE `Success` was computed, so it ran unconditionally: a run where
188
+ * EVERY write failed still reset the connection to `Active` with `LastError` blanked. The
189
+ * fix above made the RESULT honest and left the ROW lying, which is the half a human
190
+ * actually looks at.
191
+ *
192
+ * This file's own header says that row exists so a week of failure is visible. Clearing it
193
+ * on a failed run is the precise inverse — each failure erases the evidence of the last, so
194
+ * a connection failing every night forever looks healthy every morning.
195
+ */
196
+ if (result.Success) {
197
+ await this.clearFailure(connectionID, provider, contextUser);
198
+ }
199
+ return result;
200
+ }
201
+ catch (err) {
202
+ LogError(`ActivityIngestService.RunSync failed for connection ${connectionID}: ${err}`);
203
+ result.Issues.push(`The sync failed: ${String(err)}`);
204
+ await this.recordFailure(connectionID, String(err), provider, contextUser);
205
+ return result;
206
+ }
207
+ }
208
+ /**
209
+ * Writes one relevant item — once per matched deal, or once against the parties when none matched.
210
+ *
211
+ * ── THE UNATTRIBUTED CASE IS THE POINT, NOT THE FALLBACK ──
212
+ *
213
+ * S-US10's third criterion is that items link to account and contact "even where no deal match
214
+ * exists". The writer needs a `DealID`, so an unattributed item cannot go through the same path —
215
+ * instead the parties are recorded as UNRESOLVED IDENTITIES on the deal-less side. That keeps the
216
+ * evidence without inventing a stub Person, which the common migration forbids.
217
+ */
218
+ async fileItem(verdict, connection, source, provider, contextUser, result) {
219
+ const item = verdict.Item;
220
+ const matched = await this.matcher.MatchOpenDeals(verdict.Matches, contextUser);
221
+ if (matched.ReadFailed) {
222
+ /**
223
+ * The deal read failed, so this item has NOT been considered. Reporting it as unattributed
224
+ * would claim it was looked at and matched nothing.
225
+ */
226
+ result.Failed++;
227
+ result.Issues.push(`"${truncate(item.Subject)}" could not be matched to a deal — the read failed, so it was `
228
+ + 'neither filed nor discarded.');
229
+ return;
230
+ }
231
+ const deals = matched.Matches;
232
+ if (deals.length === 0) {
233
+ result.Unattributed++;
234
+ result.Issues.push(`"${truncate(item.Subject)}" involves a known contact but matches no open deal, so it was `
235
+ + 'not filed. Its participants are known; a later matcher can attribute it.');
236
+ return;
237
+ }
238
+ for (const match of deals) {
239
+ const written = await this.writer.LogActivity({
240
+ DealID: match.DealID,
241
+ TypeCode: item.TypeCode,
242
+ Title: item.Subject,
243
+ Description: item.Body,
244
+ StartedAt: item.StartedAt,
245
+ EndedAt: item.EndedAt,
246
+ Location: item.Location,
247
+ Direction: item.Direction,
248
+ /**
249
+ * D-25, DECIDED: a cancelled meeting is `Cancelled`, not `Completed`.
250
+ *
251
+ * A meeting that did not happen is not a completed activity, and anyone reading a
252
+ * timeline would be misled by one that says it was. `CK_Activity_Status` already
253
+ * allows `'Cancelled'`, so this needs no new vocabulary -- the value was there and
254
+ * the earlier version simply was not using it.
255
+ *
256
+ * `Outcome` is deliberately left NULL. The nearest seeded outcome is `NoShow`, and
257
+ * that is a DIFFERENT fact: a no-show is a meeting that went ahead and somebody
258
+ * failed to attend. Reusing it for a meeting called off in advance would put a
259
+ * false claim in a column reports read.
260
+ */
261
+ Status: item.Cancelled ? 'Cancelled' : 'Completed',
262
+ /**
263
+ * `Integration`, never `Manual` — and only when the source actually contacted a
264
+ * remote. A fixture run writes `System`, so a database can never be read as holding
265
+ * real ingested mail when it holds test data.
266
+ */
267
+ Source: source.IsLive ? 'Integration' : 'System',
268
+ SourceSystem: source.ProviderName,
269
+ ExternalID: item.ExternalID,
270
+ ExternalThreadID: item.ExternalThreadID,
271
+ ActivitySyncConnectionID: connection.ID,
272
+ Details: safeStringify(item.Raw),
273
+ /**
274
+ * The deal's own parties are NOT added. Attaching the deal's contact to a message they
275
+ * were not on would be a fabricated fact — the participants below are the ones who
276
+ * were actually on it.
277
+ */
278
+ IncludeDealParties: false,
279
+ Parties: verdict.Matches.flatMap((known) => {
280
+ const role = roleFor(item, known.Address);
281
+ const parties = [];
282
+ if (known.PersonID) {
283
+ parties.push({ Kind: 'Person', RecordID: known.PersonID, Role: role });
284
+ }
285
+ if (known.OrganizationID) {
286
+ parties.push({
287
+ Kind: 'Organization',
288
+ RecordID: known.OrganizationID,
289
+ Role: 'Participant',
290
+ });
291
+ }
292
+ return parties;
293
+ }),
294
+ /** Everyone on the message we do not know, kept as identities for a later matcher. */
295
+ UnresolvedParties: verdict.Unmatched.map((address) => ({
296
+ Kind: 'Email',
297
+ Value: address,
298
+ Role: roleFor(item, address),
299
+ })),
300
+ }, provider, contextUser);
301
+ if (written.AlreadyPresent) {
302
+ result.Duplicates++;
303
+ }
304
+ else if (written.Success) {
305
+ result.Written++;
306
+ }
307
+ else {
308
+ /**
309
+ * COUNTED AS A FAILURE, not just reported. This is one of the two paths that used to
310
+ * let the watermark advance over an item that was never written.
311
+ */
312
+ result.Failed++;
313
+ result.Issues.push(`"${truncate(item.Subject)}" could not be filed: ${written.Issues.join(' | ')}`);
314
+ }
315
+ /**
316
+ * ONE ROW, MANY DEALS — so only the FIRST deal writes it. `(SourceSystem, ExternalID)` is
317
+ * unique, so a second deal would hit the idempotency short-circuit and count as a duplicate
318
+ * rather than gaining a second `Regarding` link. Linking one activity to several deals needs a
319
+ * writer that appends links to an existing row, which is a real gap: see D-20.
320
+ */
321
+ break;
322
+ }
323
+ }
324
+ /**
325
+ * Whether the stored rules allow this item.
326
+ *
327
+ * `Sequence` order, LAST MATCH WINS, default include. That is the ordinary firewall convention and
328
+ * the one a reader of `Sequence` + `Action` would expect — but nothing in the schema states it, so it
329
+ * is a choice recorded here rather than a fact read off the table. See D-21.
330
+ *
331
+ * `Filter` is NOT evaluated. It is a free-text column with no stated grammar, and inventing one here
332
+ * would mean every future consumer had to match an undocumented dialect.
333
+ */
334
+ rulesAllow(item, rules) {
335
+ let allowed = true;
336
+ for (const rule of rules) {
337
+ if (!rule.IsEnabled) {
338
+ continue;
339
+ }
340
+ if (rule.Direction && rule.Direction !== item.Direction) {
341
+ continue;
342
+ }
343
+ if (rule.DateFrom && item.StartedAt < new Date(rule.DateFrom)) {
344
+ continue;
345
+ }
346
+ if (rule.DateTo && item.StartedAt > new Date(rule.DateTo)) {
347
+ continue;
348
+ }
349
+ allowed = rule.Action === 'Include';
350
+ }
351
+ return allowed;
352
+ }
353
+ async loadConnection(id, contextUser) {
354
+ const r = await new RunView().RunView({
355
+ EntityName: E_ACTIVITY_SYNC_CONNECTION,
356
+ ExtraFilter: `ID = '${escapeSql(id)}'`,
357
+ ResultType: 'simple',
358
+ Fields: ['ID', 'Provider', 'Status', 'Mailbox', 'LastSyncAt', 'Settings'],
359
+ }, contextUser);
360
+ return r.Success ? ((r.Results ?? [])[0] ?? null) : null;
361
+ }
362
+ async loadRules(connectionID, contextUser) {
363
+ const r = await new RunView().RunView({
364
+ EntityName: E_ACTIVITY_SYNC_RULE,
365
+ ExtraFilter: `ActivitySyncConnectionID = '${escapeSql(connectionID)}'`,
366
+ OrderBy: 'Sequence ASC',
367
+ ResultType: 'simple',
368
+ }, contextUser);
369
+ return r.Success ? (r.Results ?? []) : [];
370
+ }
371
+ /** The key in `Settings` holding the calendar watermark. */
372
+ static { this.CALENDAR_WATERMARK = 'CalendarLastSyncAt'; }
373
+ /** The watermark for one surface. Messages use the column; the calendar uses `Settings`. */
374
+ watermarkFor(connection, kind) {
375
+ if (kind === 'Message') {
376
+ return connection.LastSyncAt ? new Date(connection.LastSyncAt) : null;
377
+ }
378
+ const stored = readSettings(connection.Settings)[ActivityIngestService.CALENDAR_WATERMARK];
379
+ if (typeof stored !== 'string' || !stored) {
380
+ return null;
381
+ }
382
+ const parsed = new Date(stored);
383
+ /**
384
+ * AN UNPARSEABLE WATERMARK IS TREATED AS ABSENT, not as an error. Hand-edited JSON is the likely
385
+ * cause, and the recovery -- re-read the window and let dedupe absorb it -- costs one extra fetch.
386
+ * Failing the run instead would leave the calendar permanently stuck on a typo.
387
+ */
388
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
389
+ }
390
+ async advanceWatermark(connectionID, to, kind, provider, contextUser, result) {
391
+ const row = await this.connectionEntity(connectionID, provider, contextUser);
392
+ if (!row) {
393
+ result.Issues.push('The watermark could not be advanced — the connection row would not load.');
394
+ return;
395
+ }
396
+ if (kind === 'Message') {
397
+ row.LastSyncAt = to;
398
+ }
399
+ else {
400
+ /**
401
+ * MERGED, NOT OVERWRITTEN. `Settings` is a shared JSON bag on another app's row -- anything
402
+ * else keeping state there must survive this write, and replacing the object would delete it
403
+ * silently.
404
+ */
405
+ const settings = readSettings(row.Settings);
406
+ settings[ActivityIngestService.CALENDAR_WATERMARK] = to.toISOString();
407
+ row.Settings = JSON.stringify(settings);
408
+ }
409
+ if (await row.Save()) {
410
+ result.WatermarkAdvancedTo = to;
411
+ }
412
+ else {
413
+ result.Issues.push(`The watermark could not be advanced: ${row.LatestResult?.CompleteMessage ?? 'unknown error'}. `
414
+ + 'The next run will re-read this window; dedupe will absorb it.');
415
+ }
416
+ }
417
+ async recordFailure(connectionID, reason, provider, contextUser) {
418
+ const row = await this.connectionEntity(connectionID, provider, contextUser);
419
+ if (!row) {
420
+ return;
421
+ }
422
+ row.Status = 'Error';
423
+ row.LastError = reason.slice(0, 4000);
424
+ if (!(await row.Save())) {
425
+ LogError(`Could not record the sync failure on connection ${connectionID}.`);
426
+ }
427
+ }
428
+ /** A clean run clears the error, so `Status` reflects the LAST run rather than the worst one. */
429
+ async clearFailure(connectionID, provider, contextUser) {
430
+ const row = await this.connectionEntity(connectionID, provider, contextUser);
431
+ /**
432
+ * Clears on either signal, not just `LastError`. A connection left `Error` with a null message —
433
+ * possible if a previous version failed before writing one — would otherwise stay `Error`
434
+ * forever even though every run since has succeeded.
435
+ */
436
+ if (!row || (!row.LastError && row.Status !== 'Error')) { // vocabulary-grep-allow: operational state, not vocabulary
437
+ return;
438
+ }
439
+ row.LastError = null;
440
+ row.Status = 'Active';
441
+ await row.Save();
442
+ }
443
+ async connectionEntity(id, provider, contextUser) {
444
+ const row = await provider.GetEntityObject(E_ACTIVITY_SYNC_CONNECTION, contextUser);
445
+ return (await row.Load(id)) ? row : null;
446
+ }
447
+ }
448
+ /**
449
+ * `ActivitySyncConnection.Settings` parsed, or an empty bag.
450
+ *
451
+ * Never throws: the column is free-form JSON on a row this app does not own, so unparseable content is
452
+ * somebody else's problem to fix and not a reason to fail a sync.
453
+ */
454
+ function readSettings(raw) {
455
+ if (!raw) {
456
+ return {};
457
+ }
458
+ try {
459
+ const parsed = JSON.parse(raw);
460
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
461
+ ? parsed
462
+ : {};
463
+ }
464
+ catch {
465
+ return {};
466
+ }
467
+ }
468
+ /** The role an address earned on this item, defaulting to `Participant` when it is not listed. */
469
+ function roleFor(item, address) {
470
+ const match = item.Participants.find((p) => p.Address.trim().toLowerCase() === address.trim().toLowerCase());
471
+ return match ? match.Role : 'Participant';
472
+ }
473
+ /** `Details` is nvarchar(max); a payload that will not stringify must not take the whole run down. */
474
+ function safeStringify(value) {
475
+ try {
476
+ return JSON.stringify(value);
477
+ }
478
+ catch {
479
+ return null;
480
+ }
481
+ }
482
+ function truncate(value, max = 60) {
483
+ return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
484
+ }
485
+ //# sourceMappingURL=ActivityIngestService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActivityIngestService.js","sourceRoot":"","sources":["../../src/activities/ActivityIngestService.ts"],"names":[],"mappings":"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"}