// gong/transcript-ingest.ts
const calls = await gong.calls.list({ after })
const parts = await Promise.all(
calls.map((c) => segmentSpeakers(c.id)),
)
✗ gong/transcript-ingest.test.ts (3 failed)
TypeError: Cannot read properties of undefined
→ GET /v2/calls/:id/transcript 410 Gone
→ endpoint sunset 2026-06-01, migrate to v3
// gmail/thread-sync.ts
const { messages } = await gmail.users.threads
.get({ userId, id: threadId })
await linkToOpportunity(messages, dealId)
export async function backfill(since: Date) {
for await (const page of paginate(since)) {
await queue.push(page.records)
}
}
RateLimitError: 429 from /threads (60/min → 20/min)
// extract/requirements.ts
const out = await model.complete({
system: REQUIREMENT_PROMPT,
input: transcript.slice(0, MAX_TOKENS),
})
if (!out.requirements?.length) retry(attempt + 1)
// gong/transcript-ingest.ts
const calls = await gong.calls.list({ after })
const parts = await Promise.all(
calls.map((c) => segmentSpeakers(c.id)),
)
✗ gong/transcript-ingest.test.ts (3 failed)
TypeError: Cannot read properties of undefined
→ GET /v2/calls/:id/transcript 410 Gone
→ endpoint sunset 2026-06-01, migrate to v3
// gmail/thread-sync.ts
const { messages } = await gmail.users.threads
.get({ userId, id: threadId })
await linkToOpportunity(messages, dealId)
export async function backfill(since: Date) {
for await (const page of paginate(since)) {
await queue.push(page.records)
}
}
RateLimitError: 429 from /threads (60/min → 20/min)
// extract/requirements.ts
const out = await model.complete({
system: REQUIREMENT_PROMPT,
input: transcript.slice(0, MAX_TOKENS),
})
if (!out.requirements?.length) retry(attempt + 1)
// gong/transcript-ingest.ts
const calls = await gong.calls.list({ after })
const parts = await Promise.all(
calls.map((c) => segmentSpeakers(c.id)),
)
✗ gong/transcript-ingest.test.ts (3 failed)
TypeError: Cannot read properties of undefined
→ GET /v2/calls/:id/transcript 410 Gone
→ endpoint sunset 2026-06-01, migrate to v3
// gmail/thread-sync.ts
const { messages } = await gmail.users.threads
.get({ userId, id: threadId })
await linkToOpportunity(messages, dealId)
export async function backfill(since: Date) {
for await (const page of paginate(since)) {
await queue.push(page.records)
}
}
RateLimitError: 429 from /threads (60/min → 20/min)
// extract/requirements.ts
const out = await model.complete({
system: REQUIREMENT_PROMPT,
input: transcript.slice(0, MAX_TOKENS),
})
if (!out.requirements?.length) retry(attempt + 1)
// gong/transcript-ingest.ts
const calls = await gong.calls.list({ after })
const parts = await Promise.all(
calls.map((c) => segmentSpeakers(c.id)),
)
✗ gong/transcript-ingest.test.ts (3 failed)
TypeError: Cannot read properties of undefined
→ GET /v2/calls/:id/transcript 410 Gone
→ endpoint sunset 2026-06-01, migrate to v3
// gmail/thread-sync.ts
const { messages } = await gmail.users.threads
.get({ userId, id: threadId })
await linkToOpportunity(messages, dealId)
export async function backfill(since: Date) {
for await (const page of paginate(since)) {
await queue.push(page.records)
}
}
RateLimitError: 429 from /threads (60/min → 20/min)
// extract/requirements.ts
const out = await model.complete({
system: REQUIREMENT_PROMPT,
input: transcript.slice(0, MAX_TOKENS),
})
if (!out.requirements?.length) retry(attempt + 1)
// schema/opportunity.ts
export const Opportunity = z.object({
requirements: z.array(Requirement),
stakeholders: z.array(Stakeholder),
risks: z.array(Risk),
})
-- migrations/0042_stakeholder.sql
ALTER TABLE opportunities
DROP COLUMN primary_contact;
CREATE TABLE stakeholders (
opportunity_id uuid NOT NULL,
role text NOT NULL
);
✗ schema/migrate.test.ts (2 failed)
error: column "primary_contact" does not exist
→ 0042_stakeholder.sql:3 (dropped in 0041)
// salesforce/opportunity-sync.ts
const page = await sf.query(SOQL.openOpps)
for (const rec of page.records) {
await upsertOpportunity(toRecord(rec))
}
FIELD_INTEGRITY_EXCEPTION: unknown field
→ Opportunity.Renewal_Risk__c added by ops, unmapped
await reindex({ concurrency: 4, since: lastRun })
// schema/opportunity.ts
export const Opportunity = z.object({
requirements: z.array(Requirement),
stakeholders: z.array(Stakeholder),
risks: z.array(Risk),
})
-- migrations/0042_stakeholder.sql
ALTER TABLE opportunities
DROP COLUMN primary_contact;
CREATE TABLE stakeholders (
opportunity_id uuid NOT NULL,
role text NOT NULL
);
✗ schema/migrate.test.ts (2 failed)
error: column "primary_contact" does not exist
→ 0042_stakeholder.sql:3 (dropped in 0041)
// salesforce/opportunity-sync.ts
const page = await sf.query(SOQL.openOpps)
for (const rec of page.records) {
await upsertOpportunity(toRecord(rec))
}
FIELD_INTEGRITY_EXCEPTION: unknown field
→ Opportunity.Renewal_Risk__c added by ops, unmapped
await reindex({ concurrency: 4, since: lastRun })
// schema/opportunity.ts
export const Opportunity = z.object({
requirements: z.array(Requirement),
stakeholders: z.array(Stakeholder),
risks: z.array(Risk),
})
-- migrations/0042_stakeholder.sql
ALTER TABLE opportunities
DROP COLUMN primary_contact;
CREATE TABLE stakeholders (
opportunity_id uuid NOT NULL,
role text NOT NULL
);
✗ schema/migrate.test.ts (2 failed)
error: column "primary_contact" does not exist
→ 0042_stakeholder.sql:3 (dropped in 0041)
// salesforce/opportunity-sync.ts
const page = await sf.query(SOQL.openOpps)
for (const rec of page.records) {
await upsertOpportunity(toRecord(rec))
}
FIELD_INTEGRITY_EXCEPTION: unknown field
→ Opportunity.Renewal_Risk__c added by ops, unmapped
await reindex({ concurrency: 4, since: lastRun })
// schema/opportunity.ts
export const Opportunity = z.object({
requirements: z.array(Requirement),
stakeholders: z.array(Stakeholder),
risks: z.array(Risk),
})
-- migrations/0042_stakeholder.sql
ALTER TABLE opportunities
DROP COLUMN primary_contact;
CREATE TABLE stakeholders (
opportunity_id uuid NOT NULL,
role text NOT NULL
);
✗ schema/migrate.test.ts (2 failed)
error: column "primary_contact" does not exist
→ 0042_stakeholder.sql:3 (dropped in 0041)
// salesforce/opportunity-sync.ts
const page = await sf.query(SOQL.openOpps)
for (const rec of page.records) {
await upsertOpportunity(toRecord(rec))
}
FIELD_INTEGRITY_EXCEPTION: unknown field
→ Opportunity.Renewal_Risk__c added by ops, unmapped
await reindex({ concurrency: 4, since: lastRun })
// permissions/row-level.ts
export function visibleTo(user: User) {
return and(
inTerritory(user.territories),
notRestricted(user.role),
)
}
// slack/connect-webhook.ts
if (!verifySignature(req, SIGNING_SECRET)) {
return res.status(401).end()
}
await enqueue(channelEvent(req.body))
✗ slack/connect-webhook.test.ts (5 failed)
SlackApiError: missing_scope
→ needs channels:history, re-auth 214 workspaces
// queue/retry.ts
const delay = Math.min(
BASE * 2 ** attempt,
MAX_BACKOFF_MS,
)
// teams/graph-client.ts
const token = await credential.getToken(
SCOPES.chatReadAll,
)
AADSTS65001: admin consent required (again)
// permissions/row-level.ts
export function visibleTo(user: User) {
return and(
inTerritory(user.territories),
notRestricted(user.role),
)
}
// slack/connect-webhook.ts
if (!verifySignature(req, SIGNING_SECRET)) {
return res.status(401).end()
}
await enqueue(channelEvent(req.body))
✗ slack/connect-webhook.test.ts (5 failed)
SlackApiError: missing_scope
→ needs channels:history, re-auth 214 workspaces
// queue/retry.ts
const delay = Math.min(
BASE * 2 ** attempt,
MAX_BACKOFF_MS,
)
// teams/graph-client.ts
const token = await credential.getToken(
SCOPES.chatReadAll,
)
AADSTS65001: admin consent required (again)
// permissions/row-level.ts
export function visibleTo(user: User) {
return and(
inTerritory(user.territories),
notRestricted(user.role),
)
}
// slack/connect-webhook.ts
if (!verifySignature(req, SIGNING_SECRET)) {
return res.status(401).end()
}
await enqueue(channelEvent(req.body))
✗ slack/connect-webhook.test.ts (5 failed)
SlackApiError: missing_scope
→ needs channels:history, re-auth 214 workspaces
// queue/retry.ts
const delay = Math.min(
BASE * 2 ** attempt,
MAX_BACKOFF_MS,
)
// teams/graph-client.ts
const token = await credential.getToken(
SCOPES.chatReadAll,
)
AADSTS65001: admin consent required (again)
// permissions/row-level.ts
export function visibleTo(user: User) {
return and(
inTerritory(user.territories),
notRestricted(user.role),
)
}
// slack/connect-webhook.ts
if (!verifySignature(req, SIGNING_SECRET)) {
return res.status(401).end()
}
await enqueue(channelEvent(req.body))
✗ slack/connect-webhook.test.ts (5 failed)
SlackApiError: missing_scope
→ needs channels:history, re-auth 214 workspaces
// queue/retry.ts
const delay = Math.min(
BASE * 2 ** attempt,
MAX_BACKOFF_MS,
)
// teams/graph-client.ts
const token = await credential.getToken(
SCOPES.chatReadAll,
)
AADSTS65001: admin consent required (again)
Build vs. Buy Assessment
Go ahead. Try to build it.
Most teams get as far as a wrapper around a model and call it a context layer. The thing that actually moves revenue is harder: every call, thread, and shared room captured, structured at the opportunity level, permissioned to the record, and kept true while five vendors change their APIs underneath you. Fifteen questions. At the end we will tell you exactly how many engineer-months you are signing up for, and how far behind you’d still be.
- 15 questions
- 5 dimensions
- Your build bill at the end