Rank local campaign source units before extraction - #226
lumenstech wants to merge 53 commits into
Conversation
release: promote main
…es--release chore: release release
release: promote main
release: promote main
release: promote main
release: promote main
release: promote main
release: promote main
release: promote main
release: v1.8.1
release: v1.8.2
release: v1.9.0
release: v1.10.0
release: v1.11.0
release: v1.12.0
release: v1.13.0
release: v1.14.0
release: v1.15.0
release: v1.15.1
release: v1.15.2
release: v1.15.3
|
Someone is attempting to deploy a commit to the Comp AI - PoC Team on Vercel. A member of the Team first needs to authorize it. |
|
Retargeted this onto
Nothing is wrong with your branch. If the diff now shows commits that are already on git fetch origin main
git rebase origin/main
git push --force-with-lease |
There was a problem hiding this comment.
40 issues found across 34 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/api/src/ingest/ingest.service.ts">
<violation number="1" location="apps/api/src/ingest/ingest.service.ts:114">
P1: When the same source tuple is ingested for another project, this conflict handler moves the existing signal to the second business unit. Scope the uniqueness and conflict handling by business unit, or reject cross-project duplicates instead of overwriting ownership.</violation>
<violation number="2" location="apps/api/src/ingest/ingest.service.ts:139">
P2: A malformed `signal_score` or legacy `fit_score` makes the inbox query fail because these text values are cast directly to `float`. Validate the value with a numeric-safe expression before casting.</violation>
<violation number="3" location="apps/api/src/ingest/ingest.service.ts:181">
P2: When a source record has no `observedAt`, the inbox mapper throws on `.toISOString()`. Either require the column or return a nullable timestamp consistently in the query result and output contract.</violation>
</file>
<file name="apps/agent/agent/cli/local-campaign-worker.ts">
<violation number="1" location="apps/agent/agent/cli/local-campaign-worker.ts:23">
P1: When an optional flag is omitted, `argument()` returns the command name instead of `undefined`. `run`, `list`, and `export` then fail before doing their work. Guard the fallback with `index >= 0`.</violation>
<violation number="2" location="apps/agent/agent/cli/local-campaign-worker.ts:56">
P1: The CLI overrides bypass `localCampaignSchema`, so callers can exceed source, timeout, and Ollama-call caps or turn a positive value into zero. Parse the merged campaign with `localCampaignSchema` before calling `runLocalCampaign`.</violation>
</file>
<file name="apps/agent/agent/lib/local-lead-extraction.ts">
<violation number="1" location="apps/agent/agent/lib/local-lead-extraction.ts:105">
P1: When Ollama returns a fabricated lead plus a matching fabricated excerpt, validation accepts it and stages it because the excerpt is never checked against `text`. Pass source text into validation and require `evidence_excerpt` to occur in that source before accepting the lead.</violation>
<violation number="2" location="apps/agent/agent/lib/local-lead-extraction.ts:154">
P1: When a public seed URL redirects or DNS-rebinds to a private address, `fetchImpl` can read that internal endpoint after the one-time hostname check. Validate every destination and pin the validated address, or reject redirects and use a pinned client.</violation>
<violation number="3" location="apps/agent/agent/lib/local-lead-extraction.ts:181">
P2: When a server sends headers and stalls the body, `reader.read()` can wait indefinitely because the fetch timeout was cleared before body consumption. Keep the abort timer active through the body or time-limit each read.</violation>
</file>
<file name="packages/db/prisma/schema.prisma">
<violation number="1" location="packages/db/prisma/schema.prisma:1700">
P1: An opportunity, source, or person link can combine records from different business units. Add composite same-unit foreign keys or database checks for every canonical relationship.</violation>
<violation number="2" location="packages/db/prisma/schema.prisma:1782">
P2: A queued campaign member can reference a deleted or nonexistent target. Add optional canonical company and person relations, then enforce that each target belongs to the campaign’s business unit.</violation>
<violation number="3" location="packages/db/prisma/schema.prisma:1790">
P1: This constraint permits duplicate recipients and members with no recipient. Require one target and add null-safe partial unique indexes for company and person members.</violation>
<violation number="4" location="packages/db/prisma/schema.prisma:1829">
P2: `AgentRunContext.agentRunId` accepts orphan run IDs even though `AgentRun` exists in this schema. Add an `AgentRun` relation, inverse field, and explicit delete policy.</violation>
</file>
<file name="apps/app/app/(app)/[slug]/intelligence/intelligence-inbox.tsx">
<violation number="1" location="apps/app/app/(app)/[slug]/intelligence/intelligence-inbox.tsx:69">
P2: When more than 100 signals exist, this inbox silently hides every signal after the first 100 because it has no paging or load-more path. Reps cannot review the remaining signals or projects from this screen; add pagination or an explicit load-more control.</violation>
<violation number="2" location="apps/app/app/(app)/[slug]/intelligence/intelligence-inbox.tsx:216">
P1: When a rep opens signal B after editing signal A, `SignalReviewSheet` retains both `score` and `ownerId` because it is not keyed or reset by `sourceRecordId`. Saving B can submit A’s scoring components or assign A’s owner to B. Reset these fields when the row changes, or key the sheet by the selected source record.</violation>
<violation number="3" location="apps/app/app/(app)/[slug]/intelligence/intelligence-inbox.tsx:219">
P2: After resolve or qualify succeeds, `SignalReviewSheet` keeps the old `selected` snapshot because `onChanged` only refetches `signals`. The sheet then uses stale `row.mapped` and `row.signalScore`, so promotion remains disabled or unresolved status remains visible. Replace `selected` with the refetched row, or close the sheet when it disappears.</violation>
<violation number="4" location="apps/app/app/(app)/[slug]/intelligence/intelligence-inbox.tsx:347">
P1: `row.sourceUrl` is rendered directly as an anchor, while the ingest contract accepts a generic URL rather than restricting schemes to HTTP(S). A crafted `javascript:` source URL executes in the CRM origin when a user clicks Open source; allow only `http:` and `https:` URLs before rendering.</violation>
</file>
<file name="apps/api/src/ingest/signal-qualification.service.ts">
<violation number="1" location="apps/api/src/ingest/signal-qualification.service.ts:108">
P2: Validate the resolved amount before either write. Negative source estimates currently reach canonical and visible deal amounts, while oversized `amountUsd` values bypass `dealCreateInput` because `promote` invokes `DealsService.create` directly.</violation>
<violation number="2" location="apps/api/src/ingest/signal-qualification.service.ts:113">
P1: Concurrent `promote` calls can both observe no mapping here, create duplicate canonical opportunities and deals, and then let the final upsert retain only one mapping. Serialize promotion per source or make opportunity, deal, and mapping creation one idempotent transaction.</violation>
<violation number="3" location="apps/api/src/ingest/signal-qualification.service.ts:131">
P1: When `createDeal` is requested but the score, company link, or owner validation fails, this insert commits before the later checks and leaves an orphan canonical opportunity even though `promote` returns 400. Validate all promotion prerequisites first and wrap the canonical, deal, and mapping writes in one transaction.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-source-units.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-source-units.ts:82">
P1: Every valid anchor becomes a source unit, including ordinary navigation and footer links. Restrict link extraction to company/profile links with meaningful context, or extract the enclosing card or row, before ranking and sending candidates to Ollama.</violation>
<violation number="2" location="apps/agent/agent/lib/local-campaign-source-units.ts:209">
P2: The dedupe key requires the website, company text, and content hash to all match, so changed descriptions create duplicate Ollama calls for the same exhibitor. Treat matching website or company keys as duplicates, using the content hash only when neither identifier exists.</violation>
<violation number="3" location="apps/agent/agent/lib/local-campaign-source-units.ts:271">
P2: Exhibitor rows without a legal suffix, contact, or URL are dropped before ranking, even when they contain booth and product evidence. Include those company-context signals in this gate so names such as `Eaton` reach extraction.</violation>
</file>
<file name="packages/db/prisma/migrations/20260902184536_sequencenow_crm_foundation/migration.sql">
<violation number="1" location="packages/db/prisma/migrations/20260902184536_sequencenow_crm_foundation/migration.sql:11">
P1: On a fresh database, every ingest request fails because the migration creates no business-unit records and the repository seed does not add one. Seed the supported project keys or provide the required provisioning path before enabling this endpoint.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-cache.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-cache.ts:23">
P2: Every cache lookup scans the entire append-only `pages.jsonl`. Larger campaigns therefore incur quadratic I/O and can exhaust memory; use an indexed or bounded cache.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-runner.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-runner.ts:396">
P2: When fetch, extraction, or staging runs past `perSeedTimeoutMs`, the runner only checked the deadline beforehand and still accepts the result. Enforce the deadline across each awaited operation and stop the seed when it expires.</violation>
<violation number="2" location="apps/agent/agent/lib/local-campaign-runner.ts:409">
P2: When the extractor repairs a schema-invalid response, it makes a second Ollama request, but this counter advances only once. Count both attempts before enforcing per-seed and global call caps.</violation>
<violation number="3" location="apps/agent/agent/lib/local-campaign-runner.ts:454">
P2: When `required_fields` contains a typo or unsupported field, this lookup returns `undefined`, which is not `null`, so the lead is marked `new`. Validate required field names or treat unknown fields as missing.</violation>
</file>
<file name="apps/api/src/ingest/ingest.module.ts">
<violation number="1" location="apps/api/src/ingest/ingest.module.ts:9">
P1: The ingest procedures cannot resolve their `AuthMiddleware` through this module because `IngestModule` does not import `TrpcModule`. Import `TrpcModule` here, as every other router-bearing feature module does, so the authenticated ingest routes are registered and executable.</violation>
</file>
<file name="apps/agent/agent/lib/local-ollama.ts">
<violation number="1" location="apps/agent/agent/lib/local-ollama.ts:62">
P2: When Ollama sends headers but stalls the response body, this call waits indefinitely because the timeout is cleared before `response.json()` runs. Keep the same deadline active through body consumption and abort or race `response.json()` so the per-call timeout bounds the full request.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-scoring.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-scoring.ts:21">
P2: `project_fit_reason` is model-authored and not checked against source text, but this line lets it supply keywords and exclusions. A model can therefore raise a fit score or trigger an exclusion without source evidence; score only source-grounded fields or validate this field against the excerpt.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-extraction.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-extraction.ts:89">
P2: When a model returns the wrong `source_url` or a scalar value missing from `evidence_excerpt`, `extractLocalCampaignLead` drops the candidate without using its repair attempt. Include these validation failures in `needsSchemaRepair` so the repair prompt can correct the response.</violation>
</file>
<file name="apps/agent/agent/lib/local-lead-staging.ts">
<violation number="1" location="apps/agent/agent/lib/local-lead-staging.ts:17">
P2: When `var/local-leads` contains a symlink, this lexical check accepts it and `appendFile` follows it, allowing `--staging-path` to write outside the staging root. Resolve and validate the real parent path, or open the file without following symlinks.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-staging.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-staging.ts:26">
P2: `URL.pathname` is percent-encoded and uses URL-style Windows paths. Repositories with spaces or Windows checkouts resolve `AGENT_ROOT` incorrectly; use `fileURLToPath(import.meta.url)`.</violation>
<violation number="2" location="apps/agent/agent/lib/local-campaign-staging.ts:44">
P2: On Windows, `resolved` contains backslashes, so this check allows `var/local-leads/.git/config`. Use a separator-independent Git path check.</violation>
<violation number="3" location="apps/agent/agent/lib/local-campaign-staging.ts:69">
P2: The `domain:` key in dedupeKeys makes every lead from the same page domain a duplicate of every other lead from that domain. `runLocalCampaign` sets `source_domain` to the seed *page* domain (`local-campaign-runner.ts`: `source_domain: domain`, derived from `new URL(url).hostname`), and `dedupeAgainst` treats any single shared key as a match. On a multi-exhibitor directory page, the first staged lead makes every subsequent same-page lead a duplicate, so only one company survives per page despite the ranked-unit design (`max_ollama_calls_per_seed` up to 5). Remove the `domain:` key (the `hash:`, `company:`, `email:` and `phone:` keys already cover source content and identity), or derive it from the company website host instead of the source page. Verified by simulation: lead B (Beta Fixtures, same page domain) is flagged duplicate against lead A (Acme Displays).</violation>
<violation number="4" location="apps/agent/agent/lib/local-campaign-staging.ts:71">
P2: `normalizeText` strips email punctuation, so `a.b@example.com` and `a-b@example.com` produce the same duplicate key. Lowercase and trim email addresses without removing punctuation.</violation>
</file>
<file name="apps/agent/agent/cli/local-lead-worker.ts">
<violation number="1" location="apps/agent/agent/cli/local-lead-worker.ts:21">
P2: When an option has no value, `valueAfter` consumes the next flag or returns `undefined`, so malformed commands can extract flag text or silently use fallback values. Reject missing and empty option values before extraction and staging.</violation>
</file>
<file name="apps/agent/agent/lib/local-campaign-lead-schema.ts">
<violation number="1" location="apps/agent/agent/lib/local-campaign-lead-schema.ts:6">
P2: When source evidence contains a bare domain such as `acme.com`, this validation rejects the extraction before staging, and the repair prompt does not require a scheme. Accept non-empty website values or normalize bare domains before validating them.</violation>
</file>
<file name="apps/agent/test/local-lead-staging.spec.ts">
<violation number="1" location="apps/agent/test/local-lead-staging.spec.ts:54">
P2: The test does not await this asynchronous assertion. Await the matcher so a regression that accepts `.env` fails this test reliably.</violation>
</file>
<file name="apps/agent/campaigns/partwall-2027.json">
<violation number="1" location="apps/agent/campaigns/partwall-2027.json:11">
P2: When this campaign is run without `--seed-file`, it silently processes zero seeds because `seed_urls` is empty. Populate `seed_urls` with the campaign's source URLs or provide a checked-in/manual-list input and make that invocation explicit.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| CURRENT_TIMESTAMP | ||
| ) | ||
| ON CONFLICT ("sourceSystem", "sourceType", "sourceId") DO UPDATE SET | ||
| "businessUnitId" = EXCLUDED."businessUnitId", |
There was a problem hiding this comment.
P1: When the same source tuple is ingested for another project, this conflict handler moves the existing signal to the second business unit. Scope the uniqueness and conflict handling by business unit, or reject cross-project duplicates instead of overwriting ownership.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/ingest/ingest.service.ts, line 114:
<comment>When the same source tuple is ingested for another project, this conflict handler moves the existing signal to the second business unit. Scope the uniqueness and conflict handling by business unit, or reject cross-project duplicates instead of overwriting ownership.</comment>
<file context>
@@ -0,0 +1,415 @@
+ CURRENT_TIMESTAMP
+ )
+ ON CONFLICT ("sourceSystem", "sourceType", "sourceId") DO UPDATE SET
+ "businessUnitId" = EXCLUDED."businessUnitId",
+ "sourceUrl" = EXCLUDED."sourceUrl",
+ "observedAt" = EXCLUDED."observedAt",
</file context>
| return ( | ||
| args | ||
| .find((value) => value.startsWith(`${flag}=`)) | ||
| ?.slice(flag.length + 1) ?? args[index + 1] |
There was a problem hiding this comment.
P1: When an optional flag is omitted, argument() returns the command name instead of undefined. run, list, and export then fail before doing their work. Guard the fallback with index >= 0.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/cli/local-campaign-worker.ts, line 23:
<comment>When an optional flag is omitted, `argument()` returns the command name instead of `undefined`. `run`, `list`, and `export` then fail before doing their work. Guard the fallback with `index >= 0`.</comment>
<file context>
@@ -0,0 +1,126 @@
+ return (
+ args
+ .find((value) => value.startsWith(`${flag}=`))
+ ?.slice(flag.length + 1) ?? args[index + 1]
+ );
+}
</file context>
| ?.slice(flag.length + 1) ?? args[index + 1] | |
| ?.slice(flag.length + 1) ?? (index >= 0 ? args[index + 1] : undefined) |
| let response: Response; | ||
| try { | ||
| response = await Promise.race([ | ||
| fetchImpl(url, { |
There was a problem hiding this comment.
P1: When a public seed URL redirects or DNS-rebinds to a private address, fetchImpl can read that internal endpoint after the one-time hostname check. Validate every destination and pin the validated address, or reject redirects and use a pinned client.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/local-lead-extraction.ts, line 154:
<comment>When a public seed URL redirects or DNS-rebinds to a private address, `fetchImpl` can read that internal endpoint after the one-time hostname check. Validate every destination and pin the validated address, or reject redirects and use a pinned client.</comment>
<file context>
@@ -0,0 +1,301 @@
+ let response: Response;
+ try {
+ response = await Promise.race([
+ fetchImpl(url, {
+ signal: controller.signal,
+ headers: { accept: "text/html,text/plain;q=0.9" },
</file context>
| businessUnitId String | ||
| businessUnit BusinessUnit @relation(fields: [businessUnitId], references: [id], onDelete: Restrict) | ||
| companyId String? | ||
| company CanonicalCompany? @relation(fields: [companyId], references: [id], onDelete: SetNull) |
There was a problem hiding this comment.
P1: An opportunity, source, or person link can combine records from different business units. Add composite same-unit foreign keys or database checks for every canonical relationship.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/prisma/schema.prisma, line 1700:
<comment>An opportunity, source, or person link can combine records from different business units. Add composite same-unit foreign keys or database checks for every canonical relationship.</comment>
<file context>
@@ -1583,3 +1592,249 @@ model Apikey {
+ businessUnitId String
+ businessUnit BusinessUnit @relation(fields: [businessUnitId], references: [id], onDelete: Restrict)
+ companyId String?
+ company CanonicalCompany? @relation(fields: [companyId], references: [id], onDelete: SetNull)
+ pipelineId String?
+ pipeline Pipeline? @relation(fields: [pipelineId], references: [id], onDelete: SetNull)
</file context>
| </table> | ||
| </div> | ||
|
|
||
| <SignalReviewSheet |
There was a problem hiding this comment.
P1: When a rep opens signal B after editing signal A, SignalReviewSheet retains both score and ownerId because it is not keyed or reset by sourceRecordId. Saving B can submit A’s scoring components or assign A’s owner to B. Reset these fields when the row changes, or key the sheet by the selected source record.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/app/(app)/[slug]/intelligence/intelligence-inbox.tsx, line 216:
<comment>When a rep opens signal B after editing signal A, `SignalReviewSheet` retains both `score` and `ownerId` because it is not keyed or reset by `sourceRecordId`. Saving B can submit A’s scoring components or assign A’s owner to B. Reset these fields when the row changes, or key the sheet by the selected source record.</comment>
<file context>
@@ -0,0 +1,650 @@
+ </table>
+ </div>
+
+ <SignalReviewSheet
+ row={selected}
+ onClose={() => setSelected(null)}
</file context>
| <SignalReviewSheet | |
| <SignalReviewSheet key={selected?.sourceRecordId ?? "empty"} |
| ) { | ||
| throw new Error("Campaign path must stay under var/local-leads."); | ||
| } | ||
| if (basename(resolved).startsWith(".env") || resolved.includes("/.git/")) { |
There was a problem hiding this comment.
P2: On Windows, resolved contains backslashes, so this check allows var/local-leads/.git/config. Use a separator-independent Git path check.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/local-campaign-staging.ts, line 44:
<comment>On Windows, `resolved` contains backslashes, so this check allows `var/local-leads/.git/config`. Use a separator-independent Git path check.</comment>
<file context>
@@ -0,0 +1,118 @@
+ ) {
+ throw new Error("Campaign path must stay under var/local-leads.");
+ }
+ if (basename(resolved).startsWith(".env") || resolved.includes("/.git/")) {
+ throw new Error("Campaign path cannot target configuration or Git files.");
+ }
</file context>
| return [ | ||
| `domain:${normalizeDomain(lead.source_domain)}`, | ||
| lead.company_name ? `company:${normalizeText(lead.company_name)}` : null, | ||
| lead.email ? `email:${normalizeText(lead.email)}` : null, |
There was a problem hiding this comment.
P2: normalizeText strips email punctuation, so a.b@example.com and a-b@example.com produce the same duplicate key. Lowercase and trim email addresses without removing punctuation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/local-campaign-staging.ts, line 71:
<comment>`normalizeText` strips email punctuation, so `a.b@example.com` and `a-b@example.com` produce the same duplicate key. Lowercase and trim email addresses without removing punctuation.</comment>
<file context>
@@ -0,0 +1,118 @@
+ return [
+ `domain:${normalizeDomain(lead.source_domain)}`,
+ lead.company_name ? `company:${normalizeText(lead.company_name)}` : null,
+ lead.email ? `email:${normalizeText(lead.email)}` : null,
+ lead.phone ? `phone:${normalizePhone(lead.phone)}` : null,
+ `hash:${lead.content_hash}`,
</file context>
| lead.email ? `email:${normalizeText(lead.email)}` : null, | |
| lead.email ? `email:${lead.email.trim().toLocaleLowerCase()}` : null, |
| export const localCampaignLeadSchema = z | ||
| .object({ | ||
| company_name: z.string().trim().min(1).nullable(), | ||
| website: z.string().trim().url().nullable(), |
There was a problem hiding this comment.
P2: When source evidence contains a bare domain such as acme.com, this validation rejects the extraction before staging, and the repair prompt does not require a scheme. Accept non-empty website values or normalize bare domains before validating them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/local-campaign-lead-schema.ts, line 6:
<comment>When source evidence contains a bare domain such as `acme.com`, this validation rejects the extraction before staging, and the repair prompt does not require a scheme. Accept non-empty website values or normalize bare domains before validating them.</comment>
<file context>
@@ -0,0 +1,29 @@
+export const localCampaignLeadSchema = z
+ .object({
+ company_name: z.string().trim().min(1).nullable(),
+ website: z.string().trim().url().nullable(),
+ contact_name: z.string().trim().min(1).nullable(),
+ role: z.string().trim().min(1).nullable(),
</file context>
| website: z.string().trim().url().nullable(), | |
| website: z.string().trim().min(1).nullable(), |
| "https://example.com/acme", | ||
| { generate: async () => ({ text: response, latencyMs: 5 }) }, | ||
| ); | ||
| expect(stageLocalLead(lead, ".env")).rejects.toThrow("var/local-leads"); |
There was a problem hiding this comment.
P2: The test does not await this asynchronous assertion. Await the matcher so a regression that accepts .env fails this test reliably.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/test/local-lead-staging.spec.ts, line 54:
<comment>The test does not await this asynchronous assertion. Await the matcher so a regression that accepts `.env` fails this test reliably.</comment>
<file context>
@@ -0,0 +1,56 @@
+ "https://example.com/acme",
+ { generate: async () => ({ text: response, latencyMs: 5 }) },
+ );
+ expect(stageLocalLead(lead, ".env")).rejects.toThrow("var/local-leads");
+ });
+});
</file context>
| expect(stageLocalLead(lead, ".env")).rejects.toThrow("var/local-leads"); | |
| await expect(stageLocalLead(lead, ".env")).rejects.toThrow("var/local-leads"); |
| "end": "2027-06-30" | ||
| }, | ||
| "source_types": ["seed_url", "manual_list"], | ||
| "seed_urls": [], |
There was a problem hiding this comment.
P2: When this campaign is run without --seed-file, it silently processes zero seeds because seed_urls is empty. Populate seed_urls with the campaign's source URLs or provide a checked-in/manual-list input and make that invocation explicit.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/campaigns/partwall-2027.json, line 11:
<comment>When this campaign is run without `--seed-file`, it silently processes zero seeds because `seed_urls` is empty. Populate `seed_urls` with the campaign's source URLs or provide a checked-in/manual-list input and make that invocation explicit.</comment>
<file context>
@@ -0,0 +1,29 @@
+ "end": "2027-06-30"
+ },
+ "source_types": ["seed_url", "manual_list"],
+ "seed_urls": [],
+ "keywords": [
+ "exhibitor",
</file context>
Summary
Pilot Result
Compared with the prior pilot:
Staged examples included:
Safety
Tests
Known Follow-ups
Summary by cubic
Ranks local campaign source units before Ollama extraction so the runner spends model calls on the most promising exhibitor rows and stages fewer, higher-quality leads. Previously source units went to the model without ranking; now each is scored, dedupe still runs before model calls, and staged leads include the rank score and ranking reasons.
apps/agent/var/local-leads, and it never writes CRM or Neon data.business_unit,canonical_company, and optionalbusinessUnitIdon company, deal, and agent task.Written for commit 9948864. Summary will update on new commits.