thumbgate 1.28.4 → 1.29.2
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/.claude/commands/dashboard.md +11 -1
- package/.claude/commands/thumbgate-dashboard.md +23 -8
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +18 -10
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +66 -3
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +88 -2
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +8 -8
- package/bin/postinstall.js +4 -13
- package/commands/dashboard.md +11 -1
- package/commands/thumbgate-dashboard.md +23 -8
- package/config/agent-outcome-monitor-thresholds.json +63 -0
- package/config/evals/agent-outcomes-baseline.json +17 -0
- package/config/evals/agent-outcomes-golden.json +412 -0
- package/config/evals/prompt-eval-baseline.json +23 -0
- package/config/github-about.json +5 -4
- package/config/post-deploy-marketing-pages.json +6 -6
- package/config/schemas/task-outcome-receipt.schema.json +296 -0
- package/docs/integrations/grafana/README.md +109 -0
- package/docs/integrations/grafana/thumbgate-revenue-evidence-dashboard.json +1930 -0
- package/openapi/openapi.yaml +475 -5
- package/package.json +75 -22
- package/public/agent-manager.html +10 -11
- package/public/agents-cost-savings.html +2 -2
- package/public/assets/brand/thumbgate-logo-transparent.svg +6 -11
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +11 -10
- package/public/assets/brand/thumbgate-mark.svg +10 -11
- package/public/blog/inside-your-boundary.html +114 -0
- package/public/blog/process-over-outcome-gates.html +119 -0
- package/public/blog.html +296 -402
- package/public/brand/thumbgate-mark.svg +5 -9
- package/public/codex-enterprise.html +2 -2
- package/public/compare.html +12 -3
- package/public/diagnostic.html +79 -29
- package/public/guide.html +4 -4
- package/public/index.html +1090 -2098
- package/public/install.html +3 -3
- package/public/js/buyer-intent.js +33 -18
- package/public/numbers.html +2 -2
- package/public/pricing.html +268 -408
- package/public/pro.html +4 -4
- package/scripts/agent-outcome-eval.js +130 -0
- package/scripts/agent-outcome-monitor.js +261 -0
- package/scripts/agent-reasoning-traces.js +8 -9
- package/scripts/async-job-runner.js +107 -13
- package/scripts/billing.js +456 -126
- package/scripts/buyer-paths.js +102 -0
- package/scripts/cli-feedback.js +2 -2
- package/scripts/commercial-offer.js +18 -10
- package/scripts/durability/step.js +121 -12
- package/scripts/external-customer-audit.js +881 -0
- package/scripts/feedback-loop.js +26 -0
- package/scripts/gates-engine.js +554 -19
- package/scripts/grafana-revenue-evidence.js +856 -0
- package/scripts/human-escalation.js +265 -0
- package/scripts/hybrid-feedback-context.js +93 -50
- package/scripts/jsonl-window.js +89 -0
- package/scripts/judge-reward-function.js +30 -18
- package/scripts/lesson-embedding-index.js +3 -7
- package/scripts/meta-agent-loop.js +20 -2
- package/scripts/observability-env.js +139 -0
- package/scripts/observability-setup.js +55 -0
- package/scripts/plausible-domain-config.js +4 -0
- package/scripts/prompt-eval.js +81 -4
- package/scripts/provider-live-evidence.js +1290 -0
- package/scripts/provider-payment-reconciler.js +442 -0
- package/scripts/provider-revenue-evidence.js +249 -0
- package/scripts/rate-limiter.js +1 -5
- package/scripts/revenue-action-eligibility.js +414 -0
- package/scripts/revenue-evidence-remediation.js +694 -0
- package/scripts/revenue-offer-system.js +709 -0
- package/scripts/sales-pipeline.js +1117 -0
- package/scripts/schedule-manager.js +249 -0
- package/scripts/seo-gsd.js +8 -4
- package/scripts/stripe-credentials.js +37 -0
- package/scripts/stripe-revenue-catalog-audit.js +363 -0
- package/scripts/stripe-revenue-catalog.js +164 -0
- package/scripts/task-outcomes.js +425 -0
- package/scripts/telemetry-analytics.js +23 -3
- package/scripts/tool-contract-validator.js +287 -59
- package/scripts/tool-registry.js +143 -0
- package/scripts/vector-store.js +83 -7
- package/scripts/workflow-intake-queue.js +483 -0
- package/src/api/server.js +647 -118
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const TARGET_HOURLY_GROSS_DOLLARS = 1000;
|
|
5
|
+
const TARGET_DAILY_GROSS_DOLLARS = TARGET_HOURLY_GROSS_DOLLARS * 24;
|
|
6
|
+
const TARGET_ANNUAL_GROSS_DOLLARS = TARGET_DAILY_GROSS_DOLLARS * 365;
|
|
7
|
+
const TARGET_MONTHLY_GROSS_DOLLARS = TARGET_ANNUAL_GROSS_DOLLARS / 12;
|
|
8
|
+
const INTAKE_WARM_WINDOW_MS = 14 * 24 * 60 * 60 * 1000;
|
|
9
|
+
const INTAKE_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
const OFFER_CATALOG = Object.freeze({
|
|
12
|
+
workflow_hardening_diagnostic: Object.freeze({
|
|
13
|
+
status: 'live_public',
|
|
14
|
+
buyer: 'An accountable owner with a repeated AI-agent workflow failure.',
|
|
15
|
+
outcome: 'A packet defining blocks, warnings, and approvals.',
|
|
16
|
+
deliverables: Object.freeze([
|
|
17
|
+
'One workflow and failure map.',
|
|
18
|
+
'One block, warn, and human-review matrix.',
|
|
19
|
+
'One verification checklist.',
|
|
20
|
+
'One prioritized implementation recommendation.',
|
|
21
|
+
]),
|
|
22
|
+
buyerEffort: 'A short intake, 60-minute review, and agreed non-secret evidence.',
|
|
23
|
+
timeToValue: 'Within two business days after review and receipt of agreed materials.',
|
|
24
|
+
priceCents: 49900,
|
|
25
|
+
billing: 'one_time',
|
|
26
|
+
publicCheckout: true,
|
|
27
|
+
nextStep: '/diagnostic',
|
|
28
|
+
proofToCountRevenue: 'Provider-confirmed payment for the diagnostic.',
|
|
29
|
+
boundaries: Object.freeze([
|
|
30
|
+
'No implementation.',
|
|
31
|
+
'No legal or compliance certification.',
|
|
32
|
+
'No savings or incident-prevention guarantee.',
|
|
33
|
+
]),
|
|
34
|
+
}),
|
|
35
|
+
workflow_hardening_sprint: Object.freeze({
|
|
36
|
+
status: 'live_public_scope_first',
|
|
37
|
+
buyer: 'A diagnostic-qualified workflow owner ready to implement the first gate set.',
|
|
38
|
+
outcome: 'One agreed workflow has local gates and reviewable proof.',
|
|
39
|
+
deliverables: Object.freeze([
|
|
40
|
+
'Scoped local gate implementation for one workflow.',
|
|
41
|
+
'Local regression and proof artifacts.',
|
|
42
|
+
'Approval and rollback runbook.',
|
|
43
|
+
'Review handoff.',
|
|
44
|
+
]),
|
|
45
|
+
buyerEffort: 'Provide an owner, non-secret examples, scoped repository access, and review decisions.',
|
|
46
|
+
timeToValue: 'Fixed in the signed scope before payment.',
|
|
47
|
+
priceCents: 150000,
|
|
48
|
+
billing: 'one_time',
|
|
49
|
+
publicCheckout: false,
|
|
50
|
+
nextStep: '/go/sprint',
|
|
51
|
+
proofToCountRevenue: 'Provider-confirmed payment for the scoped sprint.',
|
|
52
|
+
boundaries: Object.freeze([
|
|
53
|
+
'One workflow only.',
|
|
54
|
+
'No ongoing monitoring or on-call support.',
|
|
55
|
+
'No uncontracted hosted-team capability.',
|
|
56
|
+
]),
|
|
57
|
+
}),
|
|
58
|
+
pro: Object.freeze({
|
|
59
|
+
status: 'live_public',
|
|
60
|
+
buyer: 'One operator who has already proved value in a local workflow.',
|
|
61
|
+
outcome: 'Higher limits, personal recall, local dashboard visibility, and exports.',
|
|
62
|
+
deliverables: Object.freeze([
|
|
63
|
+
'Personal lesson recall and search.',
|
|
64
|
+
'Personal local dashboard.',
|
|
65
|
+
'Managed adapter maintenance.',
|
|
66
|
+
'DPO and advanced exports.',
|
|
67
|
+
]),
|
|
68
|
+
buyerEffort: 'Install and activate the individual local runtime.',
|
|
69
|
+
timeToValue: 'After provider-confirmed checkout and local activation.',
|
|
70
|
+
priceCents: 1900,
|
|
71
|
+
annualPriceCents: 14900,
|
|
72
|
+
billing: 'monthly_or_annual',
|
|
73
|
+
publicCheckout: true,
|
|
74
|
+
nextStep: '/checkout/pro',
|
|
75
|
+
proofToCountRevenue: 'An active provider subscription tied to a ThumbGate Pro product.',
|
|
76
|
+
boundaries: Object.freeze([
|
|
77
|
+
'Individual operator only.',
|
|
78
|
+
'No hosted team sync or hosted org dashboard.',
|
|
79
|
+
]),
|
|
80
|
+
}),
|
|
81
|
+
workflow_reliability_operations: Object.freeze({
|
|
82
|
+
status: 'qualified_proposal_only',
|
|
83
|
+
buyer: 'A proof-backed sprint owner who needs the same production workflow re-verified as it changes.',
|
|
84
|
+
outcome: 'One existing governed workflow stays reviewable as its rules, tooling, and failure evidence change.',
|
|
85
|
+
deliverables: Object.freeze([
|
|
86
|
+
'One 45-minute monthly evidence review.',
|
|
87
|
+
'Up to two small gate or regression updates inside the same workflow.',
|
|
88
|
+
'One incident or near-miss review.',
|
|
89
|
+
'One refreshed approval, rollback, and proof packet.',
|
|
90
|
+
]),
|
|
91
|
+
buyerEffort: 'Provide sanitized evidence, name a decision owner, and attend the review.',
|
|
92
|
+
timeToValue: 'First monthly review date is fixed in the signed scope.',
|
|
93
|
+
priceCents: 300000,
|
|
94
|
+
billing: 'monthly',
|
|
95
|
+
publicCheckout: false,
|
|
96
|
+
nextStep: '/#workflow-sprint-intake',
|
|
97
|
+
proofToCountRevenue: 'Signed recurring scope plus an active provider subscription or paid recurring invoice.',
|
|
98
|
+
boundaries: Object.freeze([
|
|
99
|
+
'One existing workflow only.',
|
|
100
|
+
'No new integration, 24/7 monitoring, incident-response SLA, or compliance certification.',
|
|
101
|
+
'No hosted team sync or hosted org dashboard unless separately built, contracted, and verified.',
|
|
102
|
+
]),
|
|
103
|
+
}),
|
|
104
|
+
enterprise_governance_pilot: Object.freeze({
|
|
105
|
+
status: 'qualified_proposal_only',
|
|
106
|
+
buyer: 'A team with two or three consequential workflows, an owner, budget authority, and a 30-day decision window.',
|
|
107
|
+
outcome: 'Up to three local workflows receive explicit approval boundaries, rollback paths, and reviewable proof.',
|
|
108
|
+
deliverables: Object.freeze([
|
|
109
|
+
'Cross-workflow risk and owner map for up to three workflows.',
|
|
110
|
+
'Local gate implementation and regression proof for the signed scope.',
|
|
111
|
+
'Approval, rollback, and evidence ownership runbooks.',
|
|
112
|
+
'Final review and expansion recommendation.',
|
|
113
|
+
]),
|
|
114
|
+
buyerEffort: 'Provide owners, non-secret evidence, in-scope repositories, and timely decisions.',
|
|
115
|
+
timeToValue: 'Thirty-day delivery window begins after signed scope, payment, and receipt of agreed inputs.',
|
|
116
|
+
priceCents: 1500000,
|
|
117
|
+
billing: 'one_time',
|
|
118
|
+
publicCheckout: false,
|
|
119
|
+
nextStep: '/#workflow-sprint-intake',
|
|
120
|
+
proofToCountRevenue: 'Signed pilot scope plus provider-confirmed payment; delivered work and customer outcome remain separate proof states.',
|
|
121
|
+
boundaries: Object.freeze([
|
|
122
|
+
'Maximum three local workflows.',
|
|
123
|
+
'No promise of hosted team sync, hosted org dashboard, SSO, SIEM, data residency, or certification.',
|
|
124
|
+
'Any unavailable shared capability requires a separate build decision and contract before it can be sold as delivered.',
|
|
125
|
+
]),
|
|
126
|
+
}),
|
|
127
|
+
enterprise_reliability_operations: Object.freeze({
|
|
128
|
+
status: 'qualified_proposal_only',
|
|
129
|
+
buyer: 'A completed pilot owner needing evidence review for up to three governed workflows.',
|
|
130
|
+
outcome: 'The signed pilot workflows stay reviewable while their tools, policies, and failure evidence change.',
|
|
131
|
+
deliverables: Object.freeze([
|
|
132
|
+
'One monthly portfolio evidence review.',
|
|
133
|
+
'Up to six small gate or regression updates across the same three workflows.',
|
|
134
|
+
'Up to two incident or near-miss reviews.',
|
|
135
|
+
'One monthly portfolio proof packet and rollout decision log.',
|
|
136
|
+
]),
|
|
137
|
+
buyerEffort: 'Maintain named owners, provide sanitized evidence, and attend the portfolio review.',
|
|
138
|
+
timeToValue: 'First monthly review date is fixed in the signed scope.',
|
|
139
|
+
priceCents: 1000000,
|
|
140
|
+
billing: 'monthly',
|
|
141
|
+
publicCheckout: false,
|
|
142
|
+
nextStep: '/#workflow-sprint-intake',
|
|
143
|
+
proofToCountRevenue: 'Signed recurring scope plus an active provider subscription or paid recurring invoice.',
|
|
144
|
+
boundaries: Object.freeze([
|
|
145
|
+
'Maximum three existing pilot workflows.',
|
|
146
|
+
'No 24/7 monitoring, incident-response SLA, compliance certification, or unlimited changes.',
|
|
147
|
+
'No hosted team feature may be claimed unless separately built, contracted, and verified.',
|
|
148
|
+
]),
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
function normalizeText(value, maxLength = 1000) {
|
|
153
|
+
if (value === undefined || value === null) return null;
|
|
154
|
+
const text = String(value).trim();
|
|
155
|
+
return text ? text.slice(0, maxLength) : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function normalizeInlineText(value, maxLength = 240) {
|
|
159
|
+
const text = normalizeText(value, maxLength);
|
|
160
|
+
return text ? text.replace(/[\r\n\t]+/g, ' ').replace(/\s{2,}/g, ' ').trim() : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isValidContactEmail(value) {
|
|
164
|
+
const email = normalizeText(value, 320);
|
|
165
|
+
return Boolean(email && /^[^\s@]{1,64}@[^\s@]{1,255}\.[^\s@]{1,63}$/.test(email));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function positiveInteger(value, fallback = 0) {
|
|
169
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
170
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function calculateTargetMath() {
|
|
174
|
+
const unitsPerMonth = {};
|
|
175
|
+
for (const [offerId, offer] of Object.entries(OFFER_CATALOG)) {
|
|
176
|
+
unitsPerMonth[offerId] = Math.ceil((TARGET_MONTHLY_GROSS_DOLLARS * 100) / offer.priceCents);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
targetHourlyGrossDollars: TARGET_HOURLY_GROSS_DOLLARS,
|
|
180
|
+
targetDailyGrossDollars: TARGET_DAILY_GROSS_DOLLARS,
|
|
181
|
+
targetMonthlyGrossDollars: TARGET_MONTHLY_GROSS_DOLLARS,
|
|
182
|
+
targetAnnualGrossDollars: TARGET_ANNUAL_GROSS_DOLLARS,
|
|
183
|
+
unitsPerMonth,
|
|
184
|
+
disclaimer: 'Arithmetic requirements only. This is not a forecast and does not claim achieved revenue or delivery capacity.',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function missingCoreQualification(input = {}) {
|
|
189
|
+
const missing = [];
|
|
190
|
+
if (!normalizeText(input.workflow, 240)) missing.push('workflow');
|
|
191
|
+
if (!normalizeText(input.owner, 160)) missing.push('owner');
|
|
192
|
+
if (!normalizeText(input.repeatedFailure, 1000)) missing.push('repeatedFailure');
|
|
193
|
+
return missing;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function qualifyRevenueOffer(input = {}) {
|
|
197
|
+
if (input.requiresBuyerPaymentToAccessLead === true || input.requiresRevenueShare === true) {
|
|
198
|
+
return {
|
|
199
|
+
decision: 'discarded_paid_requirement',
|
|
200
|
+
offerId: null,
|
|
201
|
+
missing: [],
|
|
202
|
+
reason: 'The opportunity requires the seller to pay or accept a revenue-share obligation.',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const missing = missingCoreQualification(input);
|
|
207
|
+
if (missing.length) {
|
|
208
|
+
return {
|
|
209
|
+
decision: 'needs_diagnostic_intake',
|
|
210
|
+
offerId: 'workflow_hardening_diagnostic',
|
|
211
|
+
missing,
|
|
212
|
+
reason: 'The workflow, accountable owner, and repeated failure are not all explicit yet.',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (input.requiresUnavailableHostedFeature === true) {
|
|
217
|
+
return {
|
|
218
|
+
decision: 'not_fit_unavailable_capability',
|
|
219
|
+
offerId: null,
|
|
220
|
+
missing: [],
|
|
221
|
+
reason: 'The requested hosted or shared capability is not generally available and cannot be sold as delivered.',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const workflowCount = positiveInteger(input.workflowCount, 1);
|
|
226
|
+
const authorityConfirmed = input.authorityConfirmed === true;
|
|
227
|
+
const budgetCents = positiveInteger(input.budgetCents, 0);
|
|
228
|
+
const urgencyDays = positiveInteger(input.urgencyDays, 9999);
|
|
229
|
+
const proofBackedSprint = input.proofBackedSprint === true;
|
|
230
|
+
const completedEnterprisePilot = input.completedEnterprisePilot === true;
|
|
231
|
+
|
|
232
|
+
if (
|
|
233
|
+
completedEnterprisePilot
|
|
234
|
+
&& workflowCount <= 3
|
|
235
|
+
&& authorityConfirmed
|
|
236
|
+
&& urgencyDays <= 30
|
|
237
|
+
&& budgetCents >= OFFER_CATALOG.enterprise_reliability_operations.priceCents
|
|
238
|
+
) {
|
|
239
|
+
return {
|
|
240
|
+
decision: 'qualify_for_signed_proposal',
|
|
241
|
+
offerId: 'enterprise_reliability_operations',
|
|
242
|
+
missing: [],
|
|
243
|
+
reason: 'Completed pilot proof, bounded workflow count, authority, timing, and recurring budget are explicit.',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
proofBackedSprint
|
|
249
|
+
&& workflowCount >= 2
|
|
250
|
+
&& workflowCount <= 3
|
|
251
|
+
&& authorityConfirmed
|
|
252
|
+
&& urgencyDays <= 30
|
|
253
|
+
&& budgetCents >= OFFER_CATALOG.enterprise_governance_pilot.priceCents
|
|
254
|
+
) {
|
|
255
|
+
return {
|
|
256
|
+
decision: 'qualify_for_signed_proposal',
|
|
257
|
+
offerId: 'enterprise_governance_pilot',
|
|
258
|
+
missing: [],
|
|
259
|
+
reason: 'Proof, scope, authority, timing, and pilot budget are explicit for up to three workflows.',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (
|
|
264
|
+
proofBackedSprint
|
|
265
|
+
&& workflowCount === 1
|
|
266
|
+
&& authorityConfirmed
|
|
267
|
+
&& urgencyDays <= 30
|
|
268
|
+
&& budgetCents >= OFFER_CATALOG.workflow_reliability_operations.priceCents
|
|
269
|
+
) {
|
|
270
|
+
return {
|
|
271
|
+
decision: 'qualify_for_signed_proposal',
|
|
272
|
+
offerId: 'workflow_reliability_operations',
|
|
273
|
+
missing: [],
|
|
274
|
+
reason: 'The existing proof-backed workflow has an owner, authority, timing, and recurring budget.',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (input.readyToImplement === true) {
|
|
279
|
+
return {
|
|
280
|
+
decision: 'scope_sprint',
|
|
281
|
+
offerId: 'workflow_hardening_sprint',
|
|
282
|
+
missing: [],
|
|
283
|
+
reason: 'One explicit workflow is ready for a fixed implementation scope.',
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
decision: 'start_diagnostic',
|
|
289
|
+
offerId: 'workflow_hardening_diagnostic',
|
|
290
|
+
missing: [],
|
|
291
|
+
reason: 'The failure is explicit, but implementation or expansion fit is not yet proved.',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hasText(value) {
|
|
296
|
+
return Boolean(normalizeText(value));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function formatUsdCents(value) {
|
|
300
|
+
const cents = Number(value);
|
|
301
|
+
return Number.isInteger(cents) && cents >= 0
|
|
302
|
+
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(cents / 100)
|
|
303
|
+
: null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildApprovalToken(value, fallback = 'LEAD') {
|
|
307
|
+
const token = String(value || '').trim().toUpperCase().replace(/[^A-Z0-9]+/g, '_');
|
|
308
|
+
return token.replace(/^_+|_+$/g, '').slice(0, 80) || fallback;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function resolvePublicOfferUrl(nextStep, publicOrigin = 'https://thumbgate.ai') {
|
|
312
|
+
if (!nextStep || !String(nextStep).startsWith('/')) return null;
|
|
313
|
+
try {
|
|
314
|
+
const origin = new URL(publicOrigin);
|
|
315
|
+
if (!['http:', 'https:'].includes(origin.protocol)) return null;
|
|
316
|
+
return new URL(nextStep, origin).toString();
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function buildIntakeChronology(submittedAt, now = new Date().toISOString()) {
|
|
323
|
+
const submitted = new Date(String(submittedAt || ''));
|
|
324
|
+
const current = new Date(String(now || ''));
|
|
325
|
+
if (Number.isNaN(submitted.getTime()) || Number.isNaN(current.getTime())) {
|
|
326
|
+
return {
|
|
327
|
+
valid: false,
|
|
328
|
+
freshness: 'invalid',
|
|
329
|
+
ageHours: null,
|
|
330
|
+
reason: 'The intake timestamp is missing or invalid.',
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const ageMs = current.getTime() - submitted.getTime();
|
|
335
|
+
if (ageMs < -INTAKE_FUTURE_SKEW_MS) {
|
|
336
|
+
return {
|
|
337
|
+
valid: false,
|
|
338
|
+
freshness: 'future_invalid',
|
|
339
|
+
ageHours: Number((ageMs / (60 * 60 * 1000)).toFixed(2)),
|
|
340
|
+
reason: 'The intake timestamp is more than five minutes in the future.',
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
valid: true,
|
|
346
|
+
freshness: ageMs <= 24 * 60 * 60 * 1000
|
|
347
|
+
? 'same_day'
|
|
348
|
+
: ageMs <= INTAKE_WARM_WINDOW_MS ? 'warm' : 'stale',
|
|
349
|
+
ageHours: Number((Math.max(0, ageMs) / (60 * 60 * 1000)).toFixed(2)),
|
|
350
|
+
reason: ageMs > INTAKE_WARM_WINDOW_MS
|
|
351
|
+
? 'The intake is older than the 14-day warm-signal window and needs current-intent verification.'
|
|
352
|
+
: null,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function buildIntakeQualificationCard(lead = {}, {
|
|
357
|
+
now = new Date().toISOString(),
|
|
358
|
+
qualificationReviewVerified = false,
|
|
359
|
+
} = {}) {
|
|
360
|
+
const contact = lead.contact && typeof lead.contact === 'object' ? lead.contact : {};
|
|
361
|
+
const qualification = lead.qualification && typeof lead.qualification === 'object'
|
|
362
|
+
? lead.qualification
|
|
363
|
+
: {};
|
|
364
|
+
const review = lead.qualificationReview && typeof lead.qualificationReview === 'object'
|
|
365
|
+
? lead.qualificationReview
|
|
366
|
+
: {};
|
|
367
|
+
const proofArtifacts = Array.isArray(lead.proof?.artifacts) ? lead.proof.artifacts : [];
|
|
368
|
+
const chronology = buildIntakeChronology(lead.submittedAt, now);
|
|
369
|
+
const reviewVerified = qualificationReviewVerified === true;
|
|
370
|
+
const diagnosticPath = lead.offer === 'workflow_hardening_diagnostic' ||
|
|
371
|
+
(reviewVerified && review.recommendedOfferId === 'workflow_hardening_diagnostic');
|
|
372
|
+
const knownFacts = {
|
|
373
|
+
contactEmailProvided: hasText(contact.email),
|
|
374
|
+
contactEmailValid: isValidContactEmail(contact.email),
|
|
375
|
+
workflowProvided: hasText(qualification.workflow),
|
|
376
|
+
ownerProvided: hasText(qualification.owner) || (reviewVerified && hasText(review.decisionAuthority)),
|
|
377
|
+
repeatedFailureProvided: hasText(qualification.blocker) ||
|
|
378
|
+
(reviewVerified && hasText(review.severityAndFrequency)),
|
|
379
|
+
runtimeProvided: hasText(qualification.runtime),
|
|
380
|
+
urgencyProvided: hasText(qualification.urgency),
|
|
381
|
+
proofArtifactCount: proofArtifacts.length,
|
|
382
|
+
qualificationEvidenceCount: reviewVerified && Array.isArray(review.evidenceReferences)
|
|
383
|
+
? review.evidenceReferences.length
|
|
384
|
+
: 0,
|
|
385
|
+
operatorQualified: ['qualified', 'named_pilot', 'proof_backed_run', 'paid_team'].includes(lead.status)
|
|
386
|
+
&& reviewVerified,
|
|
387
|
+
sellerAccessCostStatus: 'proceed_zero_cost',
|
|
388
|
+
};
|
|
389
|
+
const coreMissing = [];
|
|
390
|
+
if (!knownFacts.contactEmailProvided) coreMissing.push('contactEmail');
|
|
391
|
+
if (!knownFacts.workflowProvided) coreMissing.push('workflow');
|
|
392
|
+
if (!knownFacts.ownerProvided) coreMissing.push('owner');
|
|
393
|
+
if (!knownFacts.repeatedFailureProvided) coreMissing.push('repeatedFailure');
|
|
394
|
+
if (!diagnosticPath && !knownFacts.runtimeProvided) coreMissing.push('runtime');
|
|
395
|
+
|
|
396
|
+
const qualificationUnknowns = [...coreMissing];
|
|
397
|
+
if (!knownFacts.urgencyProvided && (!reviewVerified || !hasText(review.urgencyAndTrigger))) qualificationUnknowns.push('urgencyAndTrigger');
|
|
398
|
+
if (!reviewVerified || !hasText(review.measurableImpact)) qualificationUnknowns.push('measurableCurrentImpact');
|
|
399
|
+
if (!reviewVerified || !hasText(review.decisionAuthority)) qualificationUnknowns.push('decisionAuthority');
|
|
400
|
+
if (!reviewVerified || !hasText(review.budgetMechanism)) qualificationUnknowns.push('budgetMechanism');
|
|
401
|
+
if (!reviewVerified || review.priceUnderstandingConfirmed !== true) qualificationUnknowns.push('priceUnderstanding');
|
|
402
|
+
if (!reviewVerified || !hasText(review.proofRequired)) qualificationUnknowns.push('proofRequiredToDecide');
|
|
403
|
+
|
|
404
|
+
const score = Math.min(100,
|
|
405
|
+
(knownFacts.contactEmailProvided ? 10 : 0)
|
|
406
|
+
+ (knownFacts.workflowProvided ? 20 : 0)
|
|
407
|
+
+ (knownFacts.ownerProvided ? 15 : 0)
|
|
408
|
+
+ (knownFacts.repeatedFailureProvided ? 20 : 0)
|
|
409
|
+
+ (knownFacts.runtimeProvided ? 10 : 0)
|
|
410
|
+
+ (knownFacts.urgencyProvided ? 10 : 0)
|
|
411
|
+
+ (knownFacts.operatorQualified ? 10 : 0)
|
|
412
|
+
+ (knownFacts.proofArtifactCount > 0 || knownFacts.qualificationEvidenceCount > 0 ? 5 : 0));
|
|
413
|
+
|
|
414
|
+
const offerDecision = qualifyRevenueOffer({
|
|
415
|
+
workflow: qualification.workflow,
|
|
416
|
+
owner: qualification.owner || (reviewVerified ? review.decisionAuthority : null),
|
|
417
|
+
repeatedFailure: qualification.blocker || (reviewVerified ? review.severityAndFrequency : null),
|
|
418
|
+
});
|
|
419
|
+
const requestedOfferId = OFFER_CATALOG[lead.offer] ? lead.offer : null;
|
|
420
|
+
const reviewedOfferId = reviewVerified && OFFER_CATALOG[review.recommendedOfferId]
|
|
421
|
+
? review.recommendedOfferId
|
|
422
|
+
: null;
|
|
423
|
+
const recommendedOfferId = reviewedOfferId || (
|
|
424
|
+
requestedOfferId === 'workflow_hardening_sprint' && coreMissing.length === 0
|
|
425
|
+
? requestedOfferId
|
|
426
|
+
: offerDecision.offerId
|
|
427
|
+
);
|
|
428
|
+
const recommendedOffer = recommendedOfferId ? OFFER_CATALOG[recommendedOfferId] : null;
|
|
429
|
+
const disqualifiers = [];
|
|
430
|
+
let route = reviewVerified && ['diagnostic', 'close'].includes(review.route)
|
|
431
|
+
? review.route
|
|
432
|
+
: 'diagnostic';
|
|
433
|
+
let recommendedNextStep = 'Review the intake evidence and prepare only the few questions needed to remove material uncertainty.';
|
|
434
|
+
|
|
435
|
+
if (!chronology.valid) {
|
|
436
|
+
route = 'nurture';
|
|
437
|
+
disqualifiers.push('invalid_intake_chronology');
|
|
438
|
+
recommendedNextStep = 'Repair or verify the intake timestamp before treating it as current buyer intent.';
|
|
439
|
+
} else if (chronology.freshness === 'stale') {
|
|
440
|
+
route = 'nurture';
|
|
441
|
+
recommendedNextStep = 'Verify current intent with a separately reviewed, low-pressure reactivation draft before offering checkout.';
|
|
442
|
+
} else if (!knownFacts.contactEmailProvided) {
|
|
443
|
+
route = 'disqualify';
|
|
444
|
+
disqualifiers.push('no_contact_path');
|
|
445
|
+
recommendedNextStep = 'Hold the record because there is no verified contact path.';
|
|
446
|
+
} else if (!knownFacts.contactEmailValid) {
|
|
447
|
+
route = 'disqualify';
|
|
448
|
+
disqualifiers.push('invalid_contact_path');
|
|
449
|
+
recommendedNextStep = 'Hold the record because the contact path is invalid.';
|
|
450
|
+
} else if (coreMissing.length > 0) {
|
|
451
|
+
route = 'nurture';
|
|
452
|
+
recommendedNextStep = `Ask for the missing qualification evidence: ${coreMissing.join(', ')}.`;
|
|
453
|
+
} else if (lead.status === 'qualified' && recommendedOfferId === 'workflow_hardening_diagnostic') {
|
|
454
|
+
recommendedNextStep = 'Prepare a transparent $499 diagnostic offer and exact draft for action-time approval; do not send automatically.';
|
|
455
|
+
} else if (lead.status === 'qualified' && recommendedOfferId === 'workflow_hardening_sprint') {
|
|
456
|
+
recommendedNextStep = 'Prepare the fixed one-workflow sprint scope questions; send no checkout until scope is accepted.';
|
|
457
|
+
} else {
|
|
458
|
+
recommendedNextStep = 'Review the evidence, ask at most three material questions, and advance to qualified only when the operator verifies fit.';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const questionCatalog = {
|
|
462
|
+
contactEmail: 'What verified contact path can be used for this buyer?',
|
|
463
|
+
workflow: 'Which exact workflow should be reviewed?',
|
|
464
|
+
owner: 'Who is accountable for this workflow and its approval decision?',
|
|
465
|
+
repeatedFailure: 'What repeated failure or rollout blocker is occurring?',
|
|
466
|
+
runtime: 'Which agent or runtime executes the workflow?',
|
|
467
|
+
urgencyAndTrigger: 'What changed now, and when must a decision be made?',
|
|
468
|
+
measurableCurrentImpact: 'What measurable impact does the repeated failure create today?',
|
|
469
|
+
decisionAuthority: 'Who would approve the scope if there is a fit?',
|
|
470
|
+
budgetMechanism: 'If there is a fit, is there an approved budget path for a fixed-scope engagement?',
|
|
471
|
+
priceUnderstanding: 'Does the buyer understand the fixed price and scope boundaries?',
|
|
472
|
+
proofRequiredToDecide: 'What proof does the buyer need before deciding?',
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
const freshnessWeight = chronology.freshness === 'same_day'
|
|
476
|
+
? 15
|
|
477
|
+
: chronology.freshness === 'warm' ? 5 : chronology.freshness === 'stale' ? -20 : -30;
|
|
478
|
+
const priorityScore = Math.max(0, Math.min(120, score + freshnessWeight));
|
|
479
|
+
const checkoutEligible = chronology.valid && chronology.freshness !== 'stale'
|
|
480
|
+
&& coreMissing.length === 0
|
|
481
|
+
&& knownFacts.operatorQualified
|
|
482
|
+
&& review.priceUnderstandingConfirmed === true
|
|
483
|
+
&& recommendedOffer?.publicCheckout === true;
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
status: 'evidence_based_operator_recommendation_not_buyer_intent_or_revenue',
|
|
487
|
+
leadId: normalizeText(lead.leadId, 160),
|
|
488
|
+
lifecycleStatus: normalizeText(lead.status, 64) || 'new',
|
|
489
|
+
chronology,
|
|
490
|
+
knownFacts,
|
|
491
|
+
unknowns: qualificationUnknowns,
|
|
492
|
+
fitScore: score,
|
|
493
|
+
fitBand: score >= 80 ? 'strong_evidence_for_review' : score >= 55 ? 'partial_evidence' : 'incomplete_evidence',
|
|
494
|
+
priorityScore,
|
|
495
|
+
priorityBand: priorityScore >= 100 ? 'review_now' : priorityScore >= 70 ? 'review_next' : 'hold_or_nurture',
|
|
496
|
+
route,
|
|
497
|
+
offerDecision: offerDecision.decision,
|
|
498
|
+
requestedOfferId,
|
|
499
|
+
recommendedOffer: recommendedOffer ? {
|
|
500
|
+
offerId: recommendedOfferId,
|
|
501
|
+
status: recommendedOffer.status,
|
|
502
|
+
priceCents: recommendedOffer.priceCents,
|
|
503
|
+
billing: recommendedOffer.billing,
|
|
504
|
+
nextStep: recommendedOffer.nextStep,
|
|
505
|
+
proofToCountRevenue: recommendedOffer.proofToCountRevenue,
|
|
506
|
+
} : null,
|
|
507
|
+
evidence: {
|
|
508
|
+
leadId: normalizeText(lead.leadId, 160),
|
|
509
|
+
submittedAt: normalizeText(lead.submittedAt, 64),
|
|
510
|
+
updatedAt: normalizeText(lead.updatedAt, 64),
|
|
511
|
+
lifecycleStatus: normalizeText(lead.status, 64) || 'new',
|
|
512
|
+
qualificationReviewEvidenceCount: knownFacts.qualificationEvidenceCount,
|
|
513
|
+
qualificationReviewVerified: reviewVerified,
|
|
514
|
+
},
|
|
515
|
+
disqualifiers,
|
|
516
|
+
questionsToAsk: qualificationUnknowns.slice(0, 3).map((key) => questionCatalog[key]),
|
|
517
|
+
recommendedNextStep,
|
|
518
|
+
approvalRequiredBeforeExternalAction: true,
|
|
519
|
+
approvalPhrase: null,
|
|
520
|
+
checkoutEligible,
|
|
521
|
+
checkoutPath: checkoutEligible ? recommendedOffer.nextStep : null,
|
|
522
|
+
externalActionAuthorized: false,
|
|
523
|
+
revenueRecognized: false,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function buildIntakeDiscoveryPacket(lead = {}, {
|
|
528
|
+
now = new Date().toISOString(),
|
|
529
|
+
qualificationReviewVerified = false,
|
|
530
|
+
} = {}) {
|
|
531
|
+
const card = buildIntakeQualificationCard(lead, { now, qualificationReviewVerified });
|
|
532
|
+
const contactEmail = normalizeText(lead.contact?.email, 320);
|
|
533
|
+
const questions = card.questionsToAsk.filter(hasText).slice(0, 3);
|
|
534
|
+
const blockers = [];
|
|
535
|
+
|
|
536
|
+
if (card.lifecycleStatus !== 'new') blockers.push('lifecycle_not_new');
|
|
537
|
+
if (!card.chronology.valid || card.chronology.freshness === 'stale') blockers.push('intake_not_current');
|
|
538
|
+
if (!card.knownFacts.contactEmailProvided) blockers.push('contact_path_missing');
|
|
539
|
+
else if (!card.knownFacts.contactEmailValid) blockers.push('contact_path_invalid');
|
|
540
|
+
if (questions.length === 0) blockers.push('no_material_questions');
|
|
541
|
+
if (card.route === 'disqualify' || card.disqualifiers.length > 0) blockers.push('disqualified');
|
|
542
|
+
if (card.knownFacts.sellerAccessCostStatus !== 'proceed_zero_cost') blockers.push('zero_spend_unverified');
|
|
543
|
+
|
|
544
|
+
const approvalReady = blockers.length === 0;
|
|
545
|
+
const workflow = normalizeInlineText(lead.qualification?.workflow, 160) || 'workflow';
|
|
546
|
+
const failure = normalizeInlineText(lead.qualification?.blocker, 500);
|
|
547
|
+
const questionList = questions.map((question, index) => `${index + 1}. ${question}`).join('\n');
|
|
548
|
+
const body = approvalReady ? [
|
|
549
|
+
`Thanks for sharing the ${workflow} workflow.`,
|
|
550
|
+
...(failure ? [`You reported this repeated issue: ${failure}`] : []),
|
|
551
|
+
'Before I recommend any next step, I want to make sure it fits. Could you clarify:',
|
|
552
|
+
questionList,
|
|
553
|
+
'If this is no longer active, reply "not now" and I will not follow up.',
|
|
554
|
+
'No payment or commitment is requested in this message.',
|
|
555
|
+
].join('\n\n') : null;
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
type: 'discovery_questions',
|
|
559
|
+
status: approvalReady ? 'approval_ready_not_authorized' : 'hold_not_approval_ready',
|
|
560
|
+
leadId: card.leadId,
|
|
561
|
+
destination: approvalReady ? {
|
|
562
|
+
channel: 'email',
|
|
563
|
+
address: contactEmail.toLowerCase(),
|
|
564
|
+
} : null,
|
|
565
|
+
draft: approvalReady ? {
|
|
566
|
+
subject: `Quick clarification on your ${workflow} intake`,
|
|
567
|
+
body,
|
|
568
|
+
} : null,
|
|
569
|
+
approvalPhrase: approvalReady
|
|
570
|
+
? `APPROVE SEND THUMBGATE DISCOVERY QUESTIONS TO ${buildApprovalToken(card.leadId)}`
|
|
571
|
+
: null,
|
|
572
|
+
blockers: [...new Set(blockers)],
|
|
573
|
+
evidence: {
|
|
574
|
+
chronology: card.chronology,
|
|
575
|
+
questionCount: questions.length,
|
|
576
|
+
sellerAccessCostStatus: card.knownFacts.sellerAccessCostStatus,
|
|
577
|
+
},
|
|
578
|
+
verificationPlan: [
|
|
579
|
+
'Record a platform or mail send receipt before marking contacted.',
|
|
580
|
+
'Record a buyer reply separately from the send receipt.',
|
|
581
|
+
'Check for an existing send receipt before any retry or resend.',
|
|
582
|
+
'Do not offer checkout or recognize revenue until later evidence gates pass.',
|
|
583
|
+
],
|
|
584
|
+
externalActionAuthorized: false,
|
|
585
|
+
revenueRecognized: false,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function buildIntakeClosePacket(lead = {}, {
|
|
590
|
+
now = new Date().toISOString(),
|
|
591
|
+
qualificationReviewVerified = false,
|
|
592
|
+
publicOrigin = 'https://thumbgate.ai',
|
|
593
|
+
} = {}) {
|
|
594
|
+
const review = lead.qualificationReview && typeof lead.qualificationReview === 'object'
|
|
595
|
+
? lead.qualificationReview
|
|
596
|
+
: {};
|
|
597
|
+
const card = buildIntakeQualificationCard(lead, { now, qualificationReviewVerified });
|
|
598
|
+
const offerId = card.recommendedOffer?.offerId || null;
|
|
599
|
+
const offer = offerId ? OFFER_CATALOG[offerId] : null;
|
|
600
|
+
const blockers = [];
|
|
601
|
+
if (qualificationReviewVerified !== true) blockers.push('qualification_review_unverified');
|
|
602
|
+
if (!card.knownFacts.operatorQualified) blockers.push('lifecycle_not_evidence_qualified');
|
|
603
|
+
if (!card.chronology.valid || card.chronology.freshness === 'stale') blockers.push('intake_not_current');
|
|
604
|
+
if (!card.knownFacts.contactEmailProvided) blockers.push('contact_path_missing');
|
|
605
|
+
else if (!card.knownFacts.contactEmailValid) blockers.push('contact_path_invalid');
|
|
606
|
+
if (card.unknowns.length > 0) blockers.push('material_unknowns_remaining');
|
|
607
|
+
if (review.priceUnderstandingConfirmed !== true) blockers.push('price_understanding_unconfirmed');
|
|
608
|
+
if (!offer) blockers.push('offer_unavailable');
|
|
609
|
+
if (!['diagnostic', 'close'].includes(card.route)) blockers.push('route_not_closeable');
|
|
610
|
+
|
|
611
|
+
const approvalReady = blockers.length === 0;
|
|
612
|
+
const workflow = normalizeText(lead.qualification?.workflow, 240) || 'the reviewed workflow';
|
|
613
|
+
const failure = normalizeText(
|
|
614
|
+
lead.qualification?.blocker || review.severityAndFrequency,
|
|
615
|
+
1000,
|
|
616
|
+
) || 'the repeated failure documented in the review';
|
|
617
|
+
const price = offer ? formatUsdCents(offer.priceCents) : null;
|
|
618
|
+
const checkoutUrl = offer?.publicCheckout
|
|
619
|
+
? resolvePublicOfferUrl(offer.nextStep, publicOrigin)
|
|
620
|
+
: null;
|
|
621
|
+
if (offer?.publicCheckout && !checkoutUrl) blockers.push('checkout_url_unavailable');
|
|
622
|
+
|
|
623
|
+
const finalApprovalReady = approvalReady && blockers.length === 0;
|
|
624
|
+
const scopeFirst = offer ? offer.publicCheckout !== true : true;
|
|
625
|
+
const deliverables = offer ? offer.deliverables.map((item) => `- ${item}`).join('\n') : '';
|
|
626
|
+
const boundaries = offer ? offer.boundaries.map((item) => `- ${item}`).join('\n') : '';
|
|
627
|
+
const nextStepCopy = scopeFirst
|
|
628
|
+
? `Before any payment request, please confirm the exact workflow, decision owner, and proof needed. If the scope is aligned, reply "scope review"; no payment link or invoice will be sent before written scope acceptance.`
|
|
629
|
+
: `If you want to proceed, review the scope and secure checkout here: ${checkoutUrl}\n\nYou can reply with a scope question or say no thanks; no follow-up is required.`;
|
|
630
|
+
const subject = offer ? `${offerId === 'workflow_hardening_diagnostic' ? 'Workflow Hardening Diagnostic' : 'ThumbGate scope'} — ${workflow}` : null;
|
|
631
|
+
const body = offer ? [
|
|
632
|
+
`Thanks for sharing the ${workflow} workflow. The reviewed issue is: ${failure}`,
|
|
633
|
+
`The bounded next step is ${price} ${offerId.replaceAll('_', ' ')} (${offer.billing}).`,
|
|
634
|
+
'Included:',
|
|
635
|
+
deliverables,
|
|
636
|
+
'Boundaries:',
|
|
637
|
+
boundaries,
|
|
638
|
+
nextStepCopy,
|
|
639
|
+
].join('\n\n') : null;
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
status: finalApprovalReady ? 'approval_ready_not_authorized' : 'hold_not_approval_ready',
|
|
643
|
+
leadId: card.leadId,
|
|
644
|
+
destination: finalApprovalReady ? {
|
|
645
|
+
channel: 'email',
|
|
646
|
+
address: normalizeText(lead.contact?.email, 320),
|
|
647
|
+
} : null,
|
|
648
|
+
offer: offer ? {
|
|
649
|
+
offerId,
|
|
650
|
+
priceCents: offer.priceCents,
|
|
651
|
+
price,
|
|
652
|
+
billing: offer.billing,
|
|
653
|
+
publicCheckout: offer.publicCheckout,
|
|
654
|
+
checkoutUrl,
|
|
655
|
+
scopeFirst,
|
|
656
|
+
} : null,
|
|
657
|
+
draft: finalApprovalReady ? { subject, body } : null,
|
|
658
|
+
approvalPhrase: finalApprovalReady
|
|
659
|
+
? `APPROVE SEND THUMBGATE ${buildApprovalToken(offerId, 'OFFER')} OFFER TO ${buildApprovalToken(card.leadId)}`
|
|
660
|
+
: null,
|
|
661
|
+
blockers: [...new Set(blockers)],
|
|
662
|
+
evidence: {
|
|
663
|
+
chronology: card.chronology,
|
|
664
|
+
qualificationReviewVerified: qualificationReviewVerified === true,
|
|
665
|
+
qualificationEvidenceCount: card.knownFacts.qualificationEvidenceCount,
|
|
666
|
+
reviewedAt: normalizeText(review.reviewedAt, 64),
|
|
667
|
+
reviewedBy: normalizeText(review.reviewedBy, 160),
|
|
668
|
+
},
|
|
669
|
+
verificationPlan: [
|
|
670
|
+
'Record a platform or mail send receipt before marking contacted.',
|
|
671
|
+
'Record a buyer reply or written scope acceptance separately from the send.',
|
|
672
|
+
...(scopeFirst ? ['Record written scope acceptance before sending any payment request.'] : []),
|
|
673
|
+
'Treat checkout started as intent, not revenue.',
|
|
674
|
+
'Mark paid only after provider-confirmed payment is reconciled to this lead and offer.',
|
|
675
|
+
],
|
|
676
|
+
externalActionAuthorized: false,
|
|
677
|
+
revenueRecognized: false,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function buildRevenueOfferSystem() {
|
|
682
|
+
return {
|
|
683
|
+
status: 'operating_model_not_traction_proof',
|
|
684
|
+
offers: OFFER_CATALOG,
|
|
685
|
+
targetMath: calculateTargetMath(),
|
|
686
|
+
proofRule: 'Only provider-confirmed payments and currently active provider subscriptions count as revenue. Proposals, checkouts, and delivery are separate states.',
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (require.main === module) { // NOSONAR
|
|
691
|
+
process.stdout.write(`${JSON.stringify(buildRevenueOfferSystem(), null, 2)}\n`);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
module.exports = {
|
|
695
|
+
OFFER_CATALOG,
|
|
696
|
+
INTAKE_FUTURE_SKEW_MS,
|
|
697
|
+
INTAKE_WARM_WINDOW_MS,
|
|
698
|
+
TARGET_ANNUAL_GROSS_DOLLARS,
|
|
699
|
+
TARGET_DAILY_GROSS_DOLLARS,
|
|
700
|
+
TARGET_HOURLY_GROSS_DOLLARS,
|
|
701
|
+
TARGET_MONTHLY_GROSS_DOLLARS,
|
|
702
|
+
buildIntakeChronology,
|
|
703
|
+
buildIntakeClosePacket,
|
|
704
|
+
buildIntakeDiscoveryPacket,
|
|
705
|
+
buildIntakeQualificationCard,
|
|
706
|
+
buildRevenueOfferSystem,
|
|
707
|
+
calculateTargetMath,
|
|
708
|
+
qualifyRevenueOffer,
|
|
709
|
+
};
|