@agent-pattern-labs/leads-rig 0.1.3

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 (87) hide show
  1. package/.claude/agents/general-free.md +39 -0
  2. package/.claude/agents/general-paid.md +20 -0
  3. package/.claude/agents/glm-minimal.md +9 -0
  4. package/.claude/iso-route.resolved.json +21 -0
  5. package/.claude/settings.json +3 -0
  6. package/.codex/config.toml +24 -0
  7. package/.cursor/iso-route.md +17 -0
  8. package/.cursor/mcp.json +19 -0
  9. package/.cursor/rules/agent-general-free.mdc +38 -0
  10. package/.cursor/rules/agent-general-paid.mdc +19 -0
  11. package/.cursor/rules/agent-glm-minimal.mdc +8 -0
  12. package/.cursor/rules/main.mdc +61 -0
  13. package/.mcp.json +19 -0
  14. package/.opencode/agents/general-free.md +48 -0
  15. package/.opencode/agents/general-paid.md +29 -0
  16. package/.opencode/agents/glm-minimal.md +19 -0
  17. package/.opencode/instructions.md +3 -0
  18. package/.opencode/skills/lead-harness.md +56 -0
  19. package/.opencode/skills/public-leads.md +56 -0
  20. package/.pi/prompts/lead-harness.md +54 -0
  21. package/.pi/prompts/public-leads.md +54 -0
  22. package/.pi/skills/general-free/SKILL.md +38 -0
  23. package/.pi/skills/general-paid/SKILL.md +19 -0
  24. package/.pi/skills/glm-minimal/SKILL.md +8 -0
  25. package/AGENTS.md +56 -0
  26. package/CLAUDE.md +56 -0
  27. package/LICENSE +21 -0
  28. package/README.md +61 -0
  29. package/batch/README.md +37 -0
  30. package/batch/batch-prompt.md +19 -0
  31. package/batch/batch-runner.sh +18 -0
  32. package/bin/create-leads-harness.mjs +178 -0
  33. package/bin/lead-harness.mjs +201 -0
  34. package/bin/sync.mjs +150 -0
  35. package/config/profile.example.yml +34 -0
  36. package/docs/ARCHITECTURE.md +85 -0
  37. package/docs/CONSTRUCTION.md +40 -0
  38. package/docs/README.md +5 -0
  39. package/docs/SETUP.md +89 -0
  40. package/examples/README.md +9 -0
  41. package/examples/sample-leads.json +57 -0
  42. package/iso/agents/general-free.md +50 -0
  43. package/iso/agents/general-paid.md +31 -0
  44. package/iso/agents/glm-minimal.md +21 -0
  45. package/iso/commands/lead-harness.md +59 -0
  46. package/iso/commands/public-leads.md +59 -0
  47. package/iso/config.json +11 -0
  48. package/iso/instructions.md +56 -0
  49. package/iso/instructions.opencode.md +3 -0
  50. package/iso/mcp.json +16 -0
  51. package/lib/leadharness-crawler.mjs +911 -0
  52. package/lib/leadharness-ingest.mjs +157 -0
  53. package/lib/leadharness-leads.mjs +574 -0
  54. package/models.yaml +32 -0
  55. package/modes/_shared.md +50 -0
  56. package/modes/batch.md +34 -0
  57. package/modes/crawl.md +25 -0
  58. package/modes/ingest.md +33 -0
  59. package/modes/pipeline.md +27 -0
  60. package/modes/reference-local-helpers.md +13 -0
  61. package/modes/review.md +15 -0
  62. package/modes/setup.md +25 -0
  63. package/opencode.json +43 -0
  64. package/package.json +186 -0
  65. package/scripts/batch-orchestrator.mjs +558 -0
  66. package/scripts/crawl.mjs +129 -0
  67. package/scripts/ingest.mjs +48 -0
  68. package/scripts/manifest.mjs +71 -0
  69. package/scripts/pipeline.mjs +118 -0
  70. package/scripts/validate-leads.mjs +69 -0
  71. package/templates/canon.json +26 -0
  72. package/templates/capabilities.json +23 -0
  73. package/templates/context.json +20 -0
  74. package/templates/contracts.json +72 -0
  75. package/templates/facts.json +13 -0
  76. package/templates/index.json +13 -0
  77. package/templates/lead-schema.json +143 -0
  78. package/templates/lineage.json +10 -0
  79. package/templates/migrations.json +4 -0
  80. package/templates/postflight.json +11 -0
  81. package/templates/preflight.json +19 -0
  82. package/templates/prioritize.json +10 -0
  83. package/templates/redact.json +15 -0
  84. package/templates/score.json +18 -0
  85. package/templates/states.yml +25 -0
  86. package/templates/timeline.json +10 -0
  87. package/verify-pipeline.mjs +123 -0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { ingestArtifact } from '../lib/leadharness-ingest.mjs';
4
+ import { parseArgs, relativeProjectPath } from '../lib/leadharness-leads.mjs';
5
+
6
+ const USAGE = `public-leads ingest -- submit validated leads to an ingest API
7
+
8
+ Usage:
9
+ public-leads ingest --input <file> [--api <base-url>] [--ingest-path </path>]
10
+ [--operator-email <email>] [--operator-email-header <header>]
11
+ [--auth-header <header>] [--auth-scheme <scheme>]
12
+ [--token <token>] [--token-env ADMIN_API_TOKEN]
13
+ [--target-project /path/to/cold-agent-leads]
14
+ [--job-id <id>] [--out data/ingest-response.json] [--dry-run]
15
+
16
+ Defaults are read from config/profile.yml when present:
17
+ api.base_url, api.ingest_path, api.operator_email, api.operator_email_header,
18
+ api.auth_header, api.auth_scheme, api.auth_token_env, api.target_project
19
+
20
+ Compatibility fallbacks are still accepted:
21
+ --admin-email, api.admin_email, api.admin_token_env, $PUBLIC_LEADS_API_TOKEN
22
+ `;
23
+
24
+ const opts = parseArgs(process.argv.slice(2));
25
+
26
+ if (opts.help || !opts.input) {
27
+ console.log(USAGE);
28
+ process.exit(opts.help ? 0 : 2);
29
+ }
30
+
31
+ try {
32
+ const outputPath = opts.out || 'data/ingest-response.json';
33
+ const output = await ingestArtifact(opts.input, {
34
+ ...opts,
35
+ out: outputPath,
36
+ });
37
+
38
+ if (opts.dryRun) {
39
+ console.log(`dry run: wrote ${relativeProjectPath(outputPath)} (${output.payload.leads.length} leads)`);
40
+ process.exit(0);
41
+ }
42
+
43
+ const jobId = output.response?.job?.id || opts.jobId || '';
44
+ console.log(`ingested ${output.leadCount} leads${jobId ? ` as job ${jobId}` : ''}; wrote ${relativeProjectPath(outputPath)}`);
45
+ } catch (error) {
46
+ console.error(error instanceof Error ? error.message : String(error));
47
+ process.exit(1);
48
+ }
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from 'fs';
4
+ import {
5
+ createManifestRecord,
6
+ parseArgs,
7
+ readJson,
8
+ readLeadArtifact,
9
+ relativeProjectPath,
10
+ resolveProjectPath,
11
+ validatePayload,
12
+ writeJson,
13
+ } from '../lib/leadharness-leads.mjs';
14
+
15
+ const USAGE = `public-leads manifest -- build/update the lead artifact manifest
16
+
17
+ Usage:
18
+ public-leads manifest --input <file> [--manifest data/lead-manifest.json] [--json]
19
+ `;
20
+
21
+ const opts = parseArgs(process.argv.slice(2));
22
+
23
+ if (opts.help || !opts.input) {
24
+ console.log(USAGE);
25
+ process.exit(opts.help ? 0 : 2);
26
+ }
27
+
28
+ try {
29
+ const manifestPath = resolveProjectPath(opts.manifest || 'data/lead-manifest.json');
30
+ const payload = readLeadArtifact(opts.input);
31
+ const validation = validatePayload(payload);
32
+ const record = createManifestRecord({
33
+ inputPath: opts.input,
34
+ payload,
35
+ validation,
36
+ });
37
+ const manifest = existsSync(manifestPath)
38
+ ? readJson(manifestPath)
39
+ : { version: 1, generatedAt: new Date().toISOString(), batches: [] };
40
+
41
+ manifest.version = 1;
42
+ manifest.generatedAt = new Date().toISOString();
43
+ manifest.batches = Array.isArray(manifest.batches) ? manifest.batches : [];
44
+
45
+ const existingIndex = manifest.batches.findIndex((item) => item.input === record.input);
46
+ if (existingIndex === -1) manifest.batches.push(record);
47
+ else manifest.batches[existingIndex] = record;
48
+
49
+ manifest.summary = {
50
+ batchCount: manifest.batches.length,
51
+ readyForIngest: manifest.batches.filter((item) => item.readyForIngest).length,
52
+ leadCount: manifest.batches.reduce((sum, item) => sum + item.leadCount, 0),
53
+ errorCount: manifest.batches.reduce((sum, item) => sum + item.validation.errors, 0),
54
+ warningCount: manifest.batches.reduce((sum, item) => sum + item.validation.warnings, 0),
55
+ };
56
+
57
+ writeJson(manifestPath, manifest);
58
+
59
+ if (opts.json) {
60
+ console.log(JSON.stringify({ manifest: relativeProjectPath(manifestPath), record, summary: manifest.summary }, null, 2));
61
+ } else {
62
+ console.log(`manifest: ${relativeProjectPath(manifestPath)}`);
63
+ console.log(`batch: ${record.id} leads=${record.leadCount} ready=${record.readyForIngest}`);
64
+ console.log(`summary: batches=${manifest.summary.batchCount} ready=${manifest.summary.readyForIngest} leads=${manifest.summary.leadCount}`);
65
+ }
66
+
67
+ process.exit(validation.ok ? 0 : 1);
68
+ } catch (error) {
69
+ console.error(error instanceof Error ? error.message : String(error));
70
+ process.exit(1);
71
+ }
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from 'fs';
4
+ import { crawlDomains } from '../lib/leadharness-crawler.mjs';
5
+ import { ingestPayload } from '../lib/leadharness-ingest.mjs';
6
+ import {
7
+ createManifestRecord,
8
+ loadProfileConfig,
9
+ parseArgs,
10
+ readJson,
11
+ relativeProjectPath,
12
+ resolveProjectPath,
13
+ validatePayload,
14
+ writeJson,
15
+ } from '../lib/leadharness-leads.mjs';
16
+ import { readRequestedDomains } from './crawl.mjs';
17
+
18
+ const USAGE = `public-leads pipeline -- crawl, validate, manifest, and optionally ingest leads
19
+
20
+ Usage:
21
+ public-leads pipeline [--domain example.com | --domains example.com,example.org]
22
+ [--input data/domains.tsv] [--out data/lead-results.json]
23
+ [--manifest data/lead-manifest.json]
24
+ [--max-pages 10] [--min-confidence 30]
25
+ [--ingest | --upload] [--dry-run]
26
+ [--target-project /path/to/cold-agent-leads]
27
+
28
+ Examples:
29
+ public-leads pipeline --domains example.com,example.org
30
+ public-leads pipeline --input data/domains.tsv --ingest --target-project /path/to/cold-agent-leads
31
+ `;
32
+
33
+ const opts = parseArgs(process.argv.slice(2));
34
+
35
+ if (opts.help) {
36
+ console.log(USAGE);
37
+ process.exit(0);
38
+ }
39
+
40
+ try {
41
+ const profile = loadProfileConfig();
42
+ const domains = readRequestedDomains(opts);
43
+ const out = opts.out || 'data/lead-results.json';
44
+ const manifestPath = opts.manifest || 'data/lead-manifest.json';
45
+ const ingestOut = opts.ingestOut || opts.ingestResponse || 'data/ingest-response.json';
46
+
47
+ const payload = await crawlDomains(domains, {
48
+ maxPages: opts.maxPages || profile.maxPages,
49
+ minConfidence: opts.minConfidence || profile.minConfidence,
50
+ userAgent: opts.userAgent || profile.userAgent,
51
+ includeBlocked: Boolean(opts.includeBlocked),
52
+ delayMs: opts.delayMs,
53
+ timeoutMs: opts.timeoutMs,
54
+ jobId: opts.jobId,
55
+ });
56
+ writeJson(resolveProjectPath(out), payload);
57
+
58
+ const validation = validatePayload(payload, { allowEmpty: Boolean(opts.allowEmpty) });
59
+ if (!validation.ok) {
60
+ for (const item of validation.issues.filter((issue) => issue.severity === 'error')) {
61
+ console.error(`error: ${item.path}: ${item.code}: ${item.message}`);
62
+ }
63
+ process.exit(1);
64
+ }
65
+
66
+ const manifest = upsertManifest(manifestPath, out, payload, validation);
67
+ console.log(`pipeline: wrote ${relativeProjectPath(resolveProjectPath(out))}`);
68
+ console.log(`manifest: ${relativeProjectPath(resolveProjectPath(manifestPath))}`);
69
+ console.log(`domains=${validation.summary.domainCount} leads=${validation.summary.leadCount} results=${validation.summary.resultCount} errors=${payload.errors.length}`);
70
+
71
+ if (opts.ingest || opts.upload) {
72
+ const ingestResult = await ingestPayload(payload, {
73
+ ...opts,
74
+ input: out,
75
+ out: ingestOut,
76
+ dryRun: Boolean(opts.dryRun),
77
+ });
78
+ if (opts.dryRun) {
79
+ console.log(`dry-run ingest: wrote ${relativeProjectPath(resolveProjectPath(ingestOut))}`);
80
+ } else {
81
+ const jobId = ingestResult.response?.job?.id || payload.jobId || '';
82
+ console.log(`ingested ${ingestResult.leadCount} leads${jobId ? ` as job ${jobId}` : ''}; wrote ${relativeProjectPath(resolveProjectPath(ingestOut))}`);
83
+ }
84
+ } else {
85
+ console.log('next: public-leads ingest --input ' + out);
86
+ }
87
+
88
+ if (opts.json) {
89
+ console.log(JSON.stringify({ artifact: out, manifest: manifestPath, summary: manifest.summary }, null, 2));
90
+ }
91
+ } catch (error) {
92
+ console.error(error instanceof Error ? error.message : String(error));
93
+ process.exit(1);
94
+ }
95
+
96
+ function upsertManifest(manifestPath, inputPath, payload, validation) {
97
+ const abs = resolveProjectPath(manifestPath);
98
+ const record = createManifestRecord({ inputPath, payload, validation });
99
+ const manifest = existsSync(abs)
100
+ ? readJson(abs)
101
+ : { version: 1, generatedAt: new Date().toISOString(), batches: [] };
102
+
103
+ manifest.version = 1;
104
+ manifest.generatedAt = new Date().toISOString();
105
+ manifest.batches = Array.isArray(manifest.batches) ? manifest.batches : [];
106
+ const index = manifest.batches.findIndex((item) => item.input === record.input);
107
+ if (index === -1) manifest.batches.push(record);
108
+ else manifest.batches[index] = record;
109
+ manifest.summary = {
110
+ batchCount: manifest.batches.length,
111
+ readyForIngest: manifest.batches.filter((item) => item.readyForIngest).length,
112
+ leadCount: manifest.batches.reduce((sum, item) => sum + item.leadCount, 0),
113
+ errorCount: manifest.batches.reduce((sum, item) => sum + item.validation.errors, 0),
114
+ warningCount: manifest.batches.reduce((sum, item) => sum + item.validation.warnings, 0),
115
+ };
116
+ writeJson(abs, manifest);
117
+ return manifest;
118
+ }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ parseArgs,
5
+ readLeadArtifact,
6
+ relativeProjectPath,
7
+ validatePayload,
8
+ writeJson,
9
+ } from '../lib/leadharness-leads.mjs';
10
+
11
+ const USAGE = `public-leads validate -- validate lead artifacts
12
+
13
+ Usage:
14
+ public-leads validate --input <file> [--json] [--out <file>] [--allow-empty]
15
+
16
+ Accepted input:
17
+ - JSON object with { jobId?, domains?, leads, results?, errors? }
18
+ - JSON array of lead records
19
+ - JSONL lead records
20
+ `;
21
+
22
+ const opts = parseArgs(process.argv.slice(2));
23
+
24
+ if (opts.help || !opts.input) {
25
+ console.log(USAGE);
26
+ process.exit(opts.help ? 0 : 2);
27
+ }
28
+
29
+ try {
30
+ const payload = readLeadArtifact(opts.input);
31
+ const result = validatePayload(payload, { allowEmpty: Boolean(opts.allowEmpty) });
32
+ const report = {
33
+ input: opts.input,
34
+ ok: result.ok,
35
+ summary: result.summary,
36
+ errors: result.errors,
37
+ warnings: result.warnings,
38
+ issues: result.issues,
39
+ payload,
40
+ };
41
+
42
+ if (opts.out) {
43
+ writeJson(opts.out, report);
44
+ }
45
+
46
+ if (opts.json) {
47
+ console.log(JSON.stringify(report, null, 2));
48
+ } else {
49
+ printReport(report);
50
+ }
51
+
52
+ process.exit(result.ok ? 0 : 1);
53
+ } catch (error) {
54
+ console.error(error instanceof Error ? error.message : String(error));
55
+ process.exit(1);
56
+ }
57
+
58
+ function printReport(report) {
59
+ const rel = relativeProjectPath(report.input);
60
+ console.log(`${report.ok ? 'OK' : 'ERROR'} ${rel}`);
61
+ console.log(`domains=${report.summary.domainCount} leads=${report.summary.leadCount} results=${report.summary.resultCount} avgConfidence=${report.summary.averageConfidence}`);
62
+ if (Object.keys(report.summary.byType).length > 0) {
63
+ console.log(`types=${Object.entries(report.summary.byType).map(([type, count]) => `${type}:${count}`).join(', ')}`);
64
+ }
65
+ for (const item of report.issues) {
66
+ const prefix = item.severity === 'error' ? 'error' : 'warn';
67
+ console.log(`${prefix}: ${item.path}: ${item.code}: ${item.message}`);
68
+ }
69
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "version": 1,
3
+ "profiles": [
4
+ {
5
+ "name": "publicleads",
6
+ "entities": {
7
+ "domain": {
8
+ "case": "lower",
9
+ "stripPrefixes": ["http://", "https://", "www."],
10
+ "stripSuffixes": ["/"],
11
+ "collapseWhitespace": true
12
+ },
13
+ "company": {
14
+ "case": "lower",
15
+ "collapseWhitespace": true,
16
+ "stripPunctuation": true,
17
+ "stripSuffixes": [" inc", " llc", " ltd", " co", " corp", " corporation"]
18
+ },
19
+ "lead": {
20
+ "case": "lower",
21
+ "join": ["domain", "email", "contactName", "title"]
22
+ }
23
+ }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "version": 1,
3
+ "roles": {
4
+ "general-free": {
5
+ "description": "Procedural crawl, extract, validate, and ingest helper.",
6
+ "tools": ["geometra", "filesystem", "bash"],
7
+ "filesystem": ["read", "write"],
8
+ "network": ["public-web", "lead-ingest-api"]
9
+ },
10
+ "general-paid": {
11
+ "description": "Quality-sensitive lead fit and evidence review helper.",
12
+ "tools": ["geometra", "filesystem", "bash"],
13
+ "filesystem": ["read", "write"],
14
+ "network": ["public-web"]
15
+ },
16
+ "glm-minimal": {
17
+ "description": "Narrow JSON transform helper.",
18
+ "tools": [],
19
+ "filesystem": [],
20
+ "network": []
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": 1,
3
+ "bundles": [
4
+ {
5
+ "name": "crawl",
6
+ "budget": 24000,
7
+ "files": ["modes/_shared.md", "modes/crawl.md", "templates/lead-schema.json"]
8
+ },
9
+ {
10
+ "name": "pipeline",
11
+ "budget": 26000,
12
+ "files": ["modes/_shared.md", "modes/pipeline.md", "templates/lead-schema.json", "data/pipeline.md"]
13
+ },
14
+ {
15
+ "name": "ingest",
16
+ "budget": 18000,
17
+ "files": ["modes/_shared.md", "modes/ingest.md", "templates/lead-schema.json", "config/profile.yml"]
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "contracts": [
3
+ {
4
+ "name": "publicleads.lead",
5
+ "version": "1.0.0",
6
+ "description": "Source-backed lead record produced by the agentic harness.",
7
+ "fields": [
8
+ { "name": "id", "type": "string" },
9
+ { "name": "company", "type": "string" },
10
+ { "name": "domain", "type": "string", "required": true },
11
+ { "name": "websiteUrl", "type": "url" },
12
+ { "name": "contactName", "type": "string" },
13
+ { "name": "title", "type": "string" },
14
+ { "name": "email", "type": "string" },
15
+ {
16
+ "name": "emailType",
17
+ "type": "enum",
18
+ "required": true,
19
+ "values": ["person", "role", "blocked", "contact_path", "unknown"]
20
+ },
21
+ { "name": "sourceUrl", "type": "url", "required": true },
22
+ { "name": "sourceLabel", "type": "string" },
23
+ { "name": "evidence", "type": "string", "required": true },
24
+ { "name": "extractionMethod", "type": "string", "required": true },
25
+ {
26
+ "name": "verificationStatus",
27
+ "type": "enum",
28
+ "required": true,
29
+ "values": ["verified", "mx_verified", "unverified", "not_applicable", "blocked", "unknown"]
30
+ },
31
+ { "name": "confidence", "type": "integer", "required": true, "min": 0, "max": 100 },
32
+ { "name": "warnings", "type": "json" },
33
+ { "name": "phone", "type": "string" },
34
+ { "name": "socialUrls", "type": "json" },
35
+ { "name": "contactUrls", "type": "json" },
36
+ { "name": "foundAt", "type": "datetime" }
37
+ ],
38
+ "formats": {
39
+ "tsv": {
40
+ "style": "delimited",
41
+ "delimiter": "tab",
42
+ "fields": [
43
+ "company",
44
+ "domain",
45
+ "contactName",
46
+ "title",
47
+ "email",
48
+ "emailType",
49
+ "verificationStatus",
50
+ "confidence",
51
+ "sourceUrl",
52
+ "evidence"
53
+ ]
54
+ }
55
+ }
56
+ },
57
+ {
58
+ "name": "publicleads.ingest-outcome",
59
+ "version": "1.0.0",
60
+ "description": "Structured outcome from submitting an artifact to an ingest API.",
61
+ "fields": [
62
+ { "name": "status", "type": "enum", "required": true, "values": ["INGESTED", "INGEST FAILED", "DRY RUN"] },
63
+ { "name": "input", "type": "string", "required": true },
64
+ { "name": "jobId", "type": "string" },
65
+ { "name": "leadCount", "type": "integer", "required": true },
66
+ { "name": "api", "type": "url" },
67
+ { "name": "responsePath", "type": "string" },
68
+ { "name": "reason", "type": "string" }
69
+ ]
70
+ }
71
+ ]
72
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 1,
3
+ "facts": [
4
+ {
5
+ "name": "lead",
6
+ "sources": ["data/lead-results*.json", "batch/lead-results*.json"]
7
+ },
8
+ {
9
+ "name": "ingest",
10
+ "sources": ["data/ingest-response.json", "batch/ingest-response*.json"]
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 1,
3
+ "sources": [
4
+ {
5
+ "name": "lead-artifacts",
6
+ "globs": ["data/lead-results*.json", "batch/lead-results*.json", "batch/ingest-payload*.json"]
7
+ },
8
+ {
9
+ "name": "lead-manifest",
10
+ "globs": ["data/lead-manifest.json"]
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,143 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "urn:public-leads-harness:templates:lead-schema",
4
+ "title": "Lead ingest payload",
5
+ "description": "Portable payload for lead review or ingest APIs.",
6
+ "type": "object",
7
+ "additionalProperties": true,
8
+ "required": ["leads"],
9
+ "properties": {
10
+ "jobId": {
11
+ "type": "string",
12
+ "description": "Optional stable crawl job ID. The app generates one when omitted."
13
+ },
14
+ "domains": {
15
+ "type": "array",
16
+ "items": { "type": "string" },
17
+ "default": []
18
+ },
19
+ "leads": {
20
+ "type": "array",
21
+ "items": { "$ref": "#/$defs/lead" }
22
+ },
23
+ "results": {
24
+ "type": "array",
25
+ "items": { "$ref": "#/$defs/result" },
26
+ "default": []
27
+ },
28
+ "errors": {
29
+ "type": "array",
30
+ "items": { "type": "string" },
31
+ "default": []
32
+ }
33
+ },
34
+ "$defs": {
35
+ "lead": {
36
+ "type": "object",
37
+ "additionalProperties": true,
38
+ "required": [
39
+ "domain",
40
+ "emailType",
41
+ "sourceUrl",
42
+ "evidence",
43
+ "extractionMethod",
44
+ "verificationStatus",
45
+ "confidence"
46
+ ],
47
+ "properties": {
48
+ "id": { "type": "string" },
49
+ "company": { "type": "string" },
50
+ "domain": { "type": "string" },
51
+ "websiteUrl": { "type": "string" },
52
+ "contactName": { "type": "string" },
53
+ "title": { "type": "string" },
54
+ "email": { "type": "string" },
55
+ "emailType": {
56
+ "type": "string",
57
+ "enum": ["person", "role", "blocked", "contact_path", "unknown"]
58
+ },
59
+ "sourceUrl": { "type": "string" },
60
+ "sourceLabel": { "type": "string" },
61
+ "evidence": { "type": "string" },
62
+ "extractionMethod": { "type": "string" },
63
+ "verificationStatus": {
64
+ "type": "string",
65
+ "enum": ["verified", "mx_verified", "unverified", "not_applicable", "blocked", "unknown"]
66
+ },
67
+ "confidence": {
68
+ "type": "integer",
69
+ "minimum": 0,
70
+ "maximum": 100
71
+ },
72
+ "warnings": {
73
+ "type": "array",
74
+ "items": { "type": "string" },
75
+ "default": []
76
+ },
77
+ "phone": {
78
+ "type": "string",
79
+ "description": "Optional normalized phone number found near the public contact evidence."
80
+ },
81
+ "socialUrls": {
82
+ "type": "array",
83
+ "items": { "type": "string" },
84
+ "default": [],
85
+ "description": "Optional public social/profile URLs found on the same source page."
86
+ },
87
+ "contactUrls": {
88
+ "type": "array",
89
+ "items": { "type": "string" },
90
+ "default": [],
91
+ "description": "Optional public scheduling or contact URLs found on the same source page."
92
+ },
93
+ "foundAt": {
94
+ "type": "string",
95
+ "format": "date-time"
96
+ }
97
+ }
98
+ },
99
+ "result": {
100
+ "type": "object",
101
+ "additionalProperties": true,
102
+ "required": ["domain"],
103
+ "properties": {
104
+ "domain": { "type": "string" },
105
+ "websiteUrl": { "type": "string" },
106
+ "leads": {
107
+ "type": "array",
108
+ "items": { "$ref": "#/$defs/lead" },
109
+ "default": []
110
+ },
111
+ "pages": {
112
+ "type": "array",
113
+ "items": { "$ref": "#/$defs/pageVisit" },
114
+ "default": []
115
+ },
116
+ "warnings": {
117
+ "type": "array",
118
+ "items": { "type": "string" },
119
+ "default": []
120
+ },
121
+ "completedAt": {
122
+ "type": "string",
123
+ "format": "date-time"
124
+ }
125
+ }
126
+ },
127
+ "pageVisit": {
128
+ "type": "object",
129
+ "additionalProperties": true,
130
+ "required": ["url"],
131
+ "properties": {
132
+ "url": { "type": "string" },
133
+ "title": { "type": "string" },
134
+ "statusCode": { "type": "integer" },
135
+ "emailsFound": { "type": "integer" },
136
+ "phonesFound": { "type": "integer" },
137
+ "socialUrlsFound": { "type": "integer" },
138
+ "contactUrlsFound": { "type": "integer" },
139
+ "error": { "type": "string" }
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 1,
3
+ "artifacts": [
4
+ {
5
+ "kind": "ingest-payload",
6
+ "inputs": ["lead-results"],
7
+ "outputs": ["ingest-response"]
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "migrations": []
4
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "workflows": [
4
+ {
5
+ "name": "publicleads.ingest",
6
+ "terminalStatuses": ["INGESTED", "INGEST FAILED", "DRY RUN"],
7
+ "requiredArtifacts": ["input", "responsePath"],
8
+ "successStatuses": ["INGESTED", "DRY RUN"]
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "version": 1,
3
+ "workflows": [
4
+ {
5
+ "name": "publicleads.crawl",
6
+ "maxParallel": 2,
7
+ "requiredSourceFields": ["domain"],
8
+ "blockGates": ["duplicate", "robotsBlocked", "privateSource"],
9
+ "warnGates": ["lowSignal", "noCompany"]
10
+ },
11
+ {
12
+ "name": "publicleads.ingest",
13
+ "maxParallel": 1,
14
+ "requiredSourceFields": ["input", "leadCount", "validationPath"],
15
+ "blockGates": ["validationErrors", "missingToken", "missingOperatorEmail"],
16
+ "warnGates": ["validationWarnings"]
17
+ }
18
+ ]
19
+ }