outcome-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +261 -0
  2. package/package.json +95 -0
  3. package/src/agents/README.md +139 -0
  4. package/src/agents/adapters/anthropic.adapter.ts +166 -0
  5. package/src/agents/adapters/dalle.adapter.ts +145 -0
  6. package/src/agents/adapters/gemini.adapter.ts +134 -0
  7. package/src/agents/adapters/imagen.adapter.ts +106 -0
  8. package/src/agents/adapters/nano-banana.adapter.ts +129 -0
  9. package/src/agents/adapters/openai.adapter.ts +165 -0
  10. package/src/agents/adapters/veo.adapter.ts +130 -0
  11. package/src/agents/agent.schema.property.test.ts +379 -0
  12. package/src/agents/agent.schema.test.ts +148 -0
  13. package/src/agents/agent.schema.ts +263 -0
  14. package/src/agents/index.ts +60 -0
  15. package/src/agents/registered-agent.schema.ts +356 -0
  16. package/src/agents/registry.ts +97 -0
  17. package/src/agents/tournament-configs.property.test.ts +266 -0
  18. package/src/cli/README.md +145 -0
  19. package/src/cli/commands/define.ts +79 -0
  20. package/src/cli/commands/list.ts +46 -0
  21. package/src/cli/commands/logs.ts +83 -0
  22. package/src/cli/commands/run.ts +416 -0
  23. package/src/cli/commands/verify.ts +110 -0
  24. package/src/cli/index.ts +81 -0
  25. package/src/config/README.md +128 -0
  26. package/src/config/env.ts +262 -0
  27. package/src/config/index.ts +19 -0
  28. package/src/eval/README.md +318 -0
  29. package/src/eval/ai-judge.test.ts +435 -0
  30. package/src/eval/ai-judge.ts +368 -0
  31. package/src/eval/code-validators.ts +414 -0
  32. package/src/eval/evaluateOutcome.property.test.ts +1174 -0
  33. package/src/eval/evaluateOutcome.ts +591 -0
  34. package/src/eval/immigration-validators.ts +122 -0
  35. package/src/eval/index.ts +90 -0
  36. package/src/eval/judge-cache.ts +402 -0
  37. package/src/eval/tournament-validators.property.test.ts +439 -0
  38. package/src/eval/validators.property.test.ts +1118 -0
  39. package/src/eval/validators.ts +1199 -0
  40. package/src/eval/weighted-scorer.ts +285 -0
  41. package/src/index.ts +17 -0
  42. package/src/league/README.md +188 -0
  43. package/src/league/health-check.ts +353 -0
  44. package/src/league/index.ts +93 -0
  45. package/src/league/killAgent.ts +151 -0
  46. package/src/league/league.test.ts +1151 -0
  47. package/src/league/runLeague.ts +843 -0
  48. package/src/league/scoreAgent.ts +175 -0
  49. package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
  50. package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
  51. package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
  52. package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
  53. package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
  54. package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
  55. package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
  56. package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
  57. package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
  58. package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
  59. package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
  60. package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
  61. package/src/modules/omnibridge/api/.gitkeep +1 -0
  62. package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
  63. package/src/modules/omnibridge/auth/.gitkeep +1 -0
  64. package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
  65. package/src/modules/omnibridge/auth/session-vault.ts +577 -0
  66. package/src/modules/omnibridge/core/.gitkeep +1 -0
  67. package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
  68. package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
  69. package/src/modules/omnibridge/core/types.ts +610 -0
  70. package/src/modules/omnibridge/execution/.gitkeep +1 -0
  71. package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
  72. package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
  73. package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
  74. package/src/modules/omnibridge/index.ts +212 -0
  75. package/src/modules/omnibridge/omnibridge.ts +510 -0
  76. package/src/modules/omnibridge/verification/.gitkeep +1 -0
  77. package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
  78. package/src/outcomes/README.md +75 -0
  79. package/src/outcomes/acquire-pilot-customer.ts +297 -0
  80. package/src/outcomes/code-delivery-outcomes.ts +89 -0
  81. package/src/outcomes/code-outcomes.ts +256 -0
  82. package/src/outcomes/code_review_battle.test.ts +135 -0
  83. package/src/outcomes/code_review_battle.ts +135 -0
  84. package/src/outcomes/cold_email_battle.ts +97 -0
  85. package/src/outcomes/content_creation_battle.ts +160 -0
  86. package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
  87. package/src/outcomes/index.ts +107 -0
  88. package/src/outcomes/lead_gen_battle.test.ts +113 -0
  89. package/src/outcomes/lead_gen_battle.ts +99 -0
  90. package/src/outcomes/outcome.schema.property.test.ts +229 -0
  91. package/src/outcomes/outcome.schema.ts +187 -0
  92. package/src/outcomes/qualified_sales_interest.ts +118 -0
  93. package/src/outcomes/swarm_planner.property.test.ts +370 -0
  94. package/src/outcomes/swarm_planner.ts +96 -0
  95. package/src/outcomes/web_extraction.ts +234 -0
  96. package/src/runtime/README.md +220 -0
  97. package/src/runtime/agentRunner.test.ts +341 -0
  98. package/src/runtime/agentRunner.ts +746 -0
  99. package/src/runtime/claudeAdapter.ts +232 -0
  100. package/src/runtime/costTracker.ts +123 -0
  101. package/src/runtime/index.ts +34 -0
  102. package/src/runtime/modelAdapter.property.test.ts +305 -0
  103. package/src/runtime/modelAdapter.ts +144 -0
  104. package/src/runtime/openaiAdapter.ts +235 -0
  105. package/src/utils/README.md +122 -0
  106. package/src/utils/command-runner.ts +134 -0
  107. package/src/utils/cost-guard.ts +379 -0
  108. package/src/utils/errors.test.ts +290 -0
  109. package/src/utils/errors.ts +442 -0
  110. package/src/utils/index.ts +37 -0
  111. package/src/utils/logger.test.ts +361 -0
  112. package/src/utils/logger.ts +419 -0
  113. package/src/utils/output-parsers.ts +216 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Property-based tests for Outcome Schema Validation
3
+ *
4
+ * **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
5
+ * **Validates: Requirements 1.1, 1.2**
6
+ *
7
+ * Property 1: Outcome Schema Validation
8
+ * *For any* outcome object, validation SHALL accept the object if and only if
9
+ * it contains all required fields (name, payoutAmount, maxAttempts, timeLimitMs,
10
+ * successCriteria, failureReasons) with correct types.
11
+ */
12
+
13
+ import { describe, test, expect } from 'vitest';
14
+ import * as fc from 'fast-check';
15
+ import { validateOutcome, type Outcome, type SuccessCriterion } from './outcome.schema.js';
16
+
17
+ /**
18
+ * Arbitrary for generating non-empty strings (not just whitespace)
19
+ * The validator requires strings that are non-empty after trimming
20
+ */
21
+ const nonEmptyStringArb: fc.Arbitrary<string> = fc
22
+ .tuple(fc.string({ minLength: 1 }), fc.string())
23
+ .map(([prefix, suffix]) => {
24
+ // Ensure at least one non-whitespace character
25
+ const nonWhitespace = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
26
+ const char = nonWhitespace[Math.abs(prefix.charCodeAt(0) || 0) % nonWhitespace.length];
27
+ return prefix.replace(/^\s*/, '') + char + suffix;
28
+ })
29
+ .filter((s) => s.trim().length > 0);
30
+
31
+ /**
32
+ * Arbitrary for generating valid SuccessCriterion objects
33
+ */
34
+ const validSuccessCriterionArb: fc.Arbitrary<SuccessCriterion> = fc.record({
35
+ name: nonEmptyStringArb,
36
+ validator: nonEmptyStringArb,
37
+ params: fc.dictionary(fc.string(), fc.jsonValue()),
38
+ });
39
+
40
+ /**
41
+ * Arbitrary for generating valid Outcome objects
42
+ */
43
+ const validOutcomeArb: fc.Arbitrary<Outcome> = fc.record({
44
+ name: nonEmptyStringArb,
45
+ description: fc.string(),
46
+ payoutAmount: fc.double({ min: 0.01, max: 1_000_000, noNaN: true }),
47
+ maxAttempts: fc.integer({ min: 1, max: 1000 }),
48
+ timeLimitMs: fc.integer({ min: 1, max: 86_400_000 }), // up to 24 hours
49
+ successCriteria: fc.array(validSuccessCriterionArb, { minLength: 1, maxLength: 10 }),
50
+ failureReasons: fc.array(fc.string(), { minLength: 0, maxLength: 10 }),
51
+ });
52
+
53
+ describe('Outcome Schema Validation - Property Tests', () => {
54
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
55
+ test('valid outcomes are accepted', () => {
56
+ fc.assert(
57
+ fc.property(validOutcomeArb, (outcome) => {
58
+ const result = validateOutcome(outcome);
59
+ expect(result.valid).toBe(true);
60
+ expect(result.errors).toHaveLength(0);
61
+ }),
62
+ { numRuns: 100 }
63
+ );
64
+ });
65
+
66
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
67
+ test('outcomes missing required fields are rejected', () => {
68
+ const requiredFields = [
69
+ 'name',
70
+ 'description',
71
+ 'payoutAmount',
72
+ 'maxAttempts',
73
+ 'timeLimitMs',
74
+ 'successCriteria',
75
+ 'failureReasons',
76
+ ] as const;
77
+
78
+ fc.assert(
79
+ fc.property(
80
+ validOutcomeArb,
81
+ fc.constantFrom(...requiredFields),
82
+ (outcome, fieldToRemove) => {
83
+ // Create a copy and remove one required field
84
+ const incompleteOutcome = { ...outcome };
85
+ delete (incompleteOutcome as Record<string, unknown>)[fieldToRemove];
86
+
87
+ const result = validateOutcome(incompleteOutcome);
88
+ expect(result.valid).toBe(false);
89
+ expect(result.errors.length).toBeGreaterThan(0);
90
+ }
91
+ ),
92
+ { numRuns: 100 }
93
+ );
94
+ });
95
+
96
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
97
+ test('outcomes with wrong field types are rejected', () => {
98
+ const wrongTypeValues = [null, undefined, [], 'string', 123, true, {}];
99
+
100
+ fc.assert(
101
+ fc.property(
102
+ validOutcomeArb,
103
+ fc.constantFrom('name', 'description', 'payoutAmount', 'maxAttempts', 'timeLimitMs'),
104
+ fc.constantFrom(...wrongTypeValues),
105
+ (outcome, field, wrongValue) => {
106
+ // Skip if the wrong value happens to be the correct type
107
+ const expectedTypes: Record<string, string> = {
108
+ name: 'string',
109
+ description: 'string',
110
+ payoutAmount: 'number',
111
+ maxAttempts: 'number',
112
+ timeLimitMs: 'number',
113
+ };
114
+
115
+ if (typeof wrongValue === expectedTypes[field]) {
116
+ return; // Skip this case
117
+ }
118
+
119
+ const invalidOutcome = { ...outcome, [field]: wrongValue };
120
+ const result = validateOutcome(invalidOutcome);
121
+ expect(result.valid).toBe(false);
122
+ }
123
+ ),
124
+ { numRuns: 100 }
125
+ );
126
+ });
127
+
128
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
129
+ test('outcomes with invalid payoutAmount are rejected', () => {
130
+ fc.assert(
131
+ fc.property(
132
+ validOutcomeArb,
133
+ fc.constantFrom(0, -1, -100, -0.01),
134
+ (outcome, invalidPayout) => {
135
+ const invalidOutcome = { ...outcome, payoutAmount: invalidPayout };
136
+ const result = validateOutcome(invalidOutcome);
137
+ expect(result.valid).toBe(false);
138
+ expect(result.errors.some((e) => e.includes('payoutAmount'))).toBe(true);
139
+ }
140
+ ),
141
+ { numRuns: 100 }
142
+ );
143
+ });
144
+
145
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
146
+ test('outcomes with invalid maxAttempts are rejected', () => {
147
+ fc.assert(
148
+ fc.property(
149
+ validOutcomeArb,
150
+ fc.constantFrom(0, -1, -100, 1.5, 2.7),
151
+ (outcome, invalidAttempts) => {
152
+ const invalidOutcome = { ...outcome, maxAttempts: invalidAttempts };
153
+ const result = validateOutcome(invalidOutcome);
154
+ expect(result.valid).toBe(false);
155
+ expect(result.errors.some((e) => e.includes('maxAttempts'))).toBe(true);
156
+ }
157
+ ),
158
+ { numRuns: 100 }
159
+ );
160
+ });
161
+
162
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
163
+ test('outcomes with invalid timeLimitMs are rejected', () => {
164
+ fc.assert(
165
+ fc.property(
166
+ validOutcomeArb,
167
+ fc.constantFrom(0, -1, -1000, 1.5, 2.7),
168
+ (outcome, invalidTimeLimit) => {
169
+ const invalidOutcome = { ...outcome, timeLimitMs: invalidTimeLimit };
170
+ const result = validateOutcome(invalidOutcome);
171
+ expect(result.valid).toBe(false);
172
+ expect(result.errors.some((e) => e.includes('timeLimitMs'))).toBe(true);
173
+ }
174
+ ),
175
+ { numRuns: 100 }
176
+ );
177
+ });
178
+
179
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
180
+ test('outcomes with empty successCriteria are rejected', () => {
181
+ fc.assert(
182
+ fc.property(validOutcomeArb, (outcome) => {
183
+ const invalidOutcome = { ...outcome, successCriteria: [] };
184
+ const result = validateOutcome(invalidOutcome);
185
+ expect(result.valid).toBe(false);
186
+ expect(result.errors.some((e) => e.includes('successCriteria'))).toBe(true);
187
+ }),
188
+ { numRuns: 100 }
189
+ );
190
+ });
191
+
192
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
193
+ test('outcomes with empty name are rejected', () => {
194
+ fc.assert(
195
+ fc.property(validOutcomeArb, fc.constantFrom('', ' ', '\t', '\n'), (outcome, emptyName) => {
196
+ const invalidOutcome = { ...outcome, name: emptyName };
197
+ const result = validateOutcome(invalidOutcome);
198
+ expect(result.valid).toBe(false);
199
+ expect(result.errors.some((e) => e.includes('name'))).toBe(true);
200
+ }),
201
+ { numRuns: 100 }
202
+ );
203
+ });
204
+
205
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
206
+ test('non-object values are rejected', () => {
207
+ fc.assert(
208
+ fc.property(fc.constantFrom(null, undefined, 'string', 123, true, []), (invalidValue) => {
209
+ const result = validateOutcome(invalidValue);
210
+ expect(result.valid).toBe(false);
211
+ expect(result.errors.length).toBeGreaterThan(0);
212
+ }),
213
+ { numRuns: 100 }
214
+ );
215
+ });
216
+
217
+ // **Feature: earnd-bounty-engine, Property 1: Outcome Schema Validation**
218
+ test('validation is deterministic - same input produces same output', () => {
219
+ fc.assert(
220
+ fc.property(validOutcomeArb, (outcome) => {
221
+ const result1 = validateOutcome(outcome);
222
+ const result2 = validateOutcome(outcome);
223
+ expect(result1.valid).toBe(result2.valid);
224
+ expect(result1.errors).toEqual(result2.errors);
225
+ }),
226
+ { numRuns: 100 }
227
+ );
228
+ });
229
+ });
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Outcome Schema - Type definitions and validation for business outcomes
3
+ *
4
+ * An Outcome is a declarative, deterministic definition of a business goal
5
+ * including success criteria, payout amount, and constraints.
6
+ *
7
+ * @module outcomes/outcome.schema
8
+ */
9
+
10
+ /**
11
+ * Represents a single success criterion that must be met for an outcome.
12
+ */
13
+ export interface SuccessCriterion {
14
+ /** Unique name identifying this criterion */
15
+ name: string;
16
+ /** Reference to the validator function name */
17
+ validator: string;
18
+ /** Parameters passed to the validator function */
19
+ params: Record<string, unknown>;
20
+ }
21
+
22
+ /**
23
+ * Represents a complete business outcome definition.
24
+ * Outcomes are deterministic and verifiable.
25
+ */
26
+ export interface Outcome {
27
+ /** Unique name identifying this outcome */
28
+ name: string;
29
+ /** Human-readable description of the outcome */
30
+ description: string;
31
+ /** Amount paid upon successful completion (in dollars) */
32
+ payoutAmount: number;
33
+ /** Maximum number of attempts allowed per agent */
34
+ maxAttempts: number;
35
+ /** Time limit for achieving the outcome (in milliseconds) */
36
+ timeLimitMs: number;
37
+ /** List of criteria that must all pass for success */
38
+ successCriteria: SuccessCriterion[];
39
+ /** Predefined failure reasons for structured error reporting */
40
+ failureReasons: string[];
41
+ }
42
+
43
+ /**
44
+ * Result of validating an outcome or its fields.
45
+ */
46
+ export interface ValidationResult {
47
+ /** Whether validation passed */
48
+ valid: boolean;
49
+ /** Error messages if validation failed */
50
+ errors: string[];
51
+ }
52
+
53
+ /**
54
+ * Validates a SuccessCriterion object.
55
+ *
56
+ * @param criterion - The criterion to validate
57
+ * @returns ValidationResult with any errors found
58
+ */
59
+ function validateSuccessCriterion(criterion: unknown): ValidationResult {
60
+ const errors: string[] = [];
61
+
62
+ if (typeof criterion !== 'object' || criterion === null) {
63
+ return { valid: false, errors: ['Success criterion must be an object'] };
64
+ }
65
+
66
+ const c = criterion as Record<string, unknown>;
67
+
68
+ if (typeof c.name !== 'string' || c.name.trim() === '') {
69
+ errors.push('Success criterion must have a non-empty string "name"');
70
+ }
71
+
72
+ if (typeof c.validator !== 'string' || c.validator.trim() === '') {
73
+ errors.push('Success criterion must have a non-empty string "validator"');
74
+ }
75
+
76
+ if (typeof c.params !== 'object' || c.params === null || Array.isArray(c.params)) {
77
+ errors.push('Success criterion must have an object "params"');
78
+ }
79
+
80
+ return { valid: errors.length === 0, errors };
81
+ }
82
+
83
+ /**
84
+ * Validates an Outcome object against the schema.
85
+ *
86
+ * Ensures all required fields are present with correct types:
87
+ * - name: non-empty string
88
+ * - description: string
89
+ * - payoutAmount: positive number
90
+ * - maxAttempts: positive integer
91
+ * - timeLimitMs: positive integer
92
+ * - successCriteria: non-empty array of valid SuccessCriterion
93
+ * - failureReasons: array of strings
94
+ *
95
+ * @param outcome - The outcome object to validate
96
+ * @returns ValidationResult indicating if the outcome is valid
97
+ *
98
+ * @example
99
+ * const result = validateOutcome(myOutcome);
100
+ * if (!result.valid) {
101
+ * console.error('Invalid outcome:', result.errors);
102
+ * }
103
+ */
104
+ export function validateOutcome(outcome: unknown): ValidationResult {
105
+ const errors: string[] = [];
106
+
107
+ // Check if outcome is an object
108
+ if (typeof outcome !== 'object' || outcome === null) {
109
+ return { valid: false, errors: ['Outcome must be an object'] };
110
+ }
111
+
112
+ const o = outcome as Record<string, unknown>;
113
+
114
+ // Validate name
115
+ if (typeof o.name !== 'string') {
116
+ errors.push('Outcome must have a string "name"');
117
+ } else if (o.name.trim() === '') {
118
+ errors.push('Outcome "name" must not be empty');
119
+ }
120
+
121
+ // Validate description
122
+ if (typeof o.description !== 'string') {
123
+ errors.push('Outcome must have a string "description"');
124
+ }
125
+
126
+ // Validate payoutAmount
127
+ if (typeof o.payoutAmount !== 'number') {
128
+ errors.push('Outcome must have a number "payoutAmount"');
129
+ } else if (o.payoutAmount <= 0) {
130
+ errors.push('Outcome "payoutAmount" must be positive');
131
+ } else if (!Number.isFinite(o.payoutAmount)) {
132
+ errors.push('Outcome "payoutAmount" must be a finite number');
133
+ }
134
+
135
+ // Validate maxAttempts
136
+ if (typeof o.maxAttempts !== 'number') {
137
+ errors.push('Outcome must have a number "maxAttempts"');
138
+ } else if (!Number.isInteger(o.maxAttempts) || o.maxAttempts <= 0) {
139
+ errors.push('Outcome "maxAttempts" must be a positive integer');
140
+ }
141
+
142
+ // Validate timeLimitMs
143
+ if (typeof o.timeLimitMs !== 'number') {
144
+ errors.push('Outcome must have a number "timeLimitMs"');
145
+ } else if (!Number.isInteger(o.timeLimitMs) || o.timeLimitMs <= 0) {
146
+ errors.push('Outcome "timeLimitMs" must be a positive integer');
147
+ }
148
+
149
+ // Validate successCriteria
150
+ if (!Array.isArray(o.successCriteria)) {
151
+ errors.push('Outcome must have an array "successCriteria"');
152
+ } else if (o.successCriteria.length === 0) {
153
+ errors.push('Outcome "successCriteria" must not be empty');
154
+ } else {
155
+ o.successCriteria.forEach((criterion, index) => {
156
+ const criterionResult = validateSuccessCriterion(criterion);
157
+ if (!criterionResult.valid) {
158
+ criterionResult.errors.forEach((err) => {
159
+ errors.push(`successCriteria[${index}]: ${err}`);
160
+ });
161
+ }
162
+ });
163
+ }
164
+
165
+ // Validate failureReasons
166
+ if (!Array.isArray(o.failureReasons)) {
167
+ errors.push('Outcome must have an array "failureReasons"');
168
+ } else {
169
+ o.failureReasons.forEach((reason, index) => {
170
+ if (typeof reason !== 'string') {
171
+ errors.push(`failureReasons[${index}] must be a string`);
172
+ }
173
+ });
174
+ }
175
+
176
+ return { valid: errors.length === 0, errors };
177
+ }
178
+
179
+ /**
180
+ * Type guard to check if an unknown value is a valid Outcome.
181
+ *
182
+ * @param value - The value to check
183
+ * @returns True if the value is a valid Outcome
184
+ */
185
+ export function isOutcome(value: unknown): value is Outcome {
186
+ return validateOutcome(value).valid;
187
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Qualified Sales Interest Outcome
3
+ *
4
+ * Demo outcome for demonstrating the Earnd Bounty Engine to investors.
5
+ * An agent achieves this outcome by generating a qualified sales lead
6
+ * that meets all 5 success criteria.
7
+ *
8
+ * @module outcomes/qualified_sales_interest
9
+ */
10
+
11
+ import type { Outcome } from './outcome.schema.js';
12
+
13
+ /**
14
+ * Payout amount for successfully qualifying a sales interest.
15
+ * Set to $250 as per Requirements 8.6.
16
+ */
17
+ export const PAYOUT_AMOUNT = 250;
18
+
19
+ /**
20
+ * Maximum number of attempts an agent can make to achieve this outcome.
21
+ */
22
+ export const MAX_ATTEMPTS = 5;
23
+
24
+ /**
25
+ * Time limit for achieving the outcome (5 minutes in milliseconds).
26
+ */
27
+ export const TIME_LIMIT_MS = 300000;
28
+
29
+ /**
30
+ * Keywords that indicate buying intent.
31
+ * Message must contain at least one of these keywords.
32
+ */
33
+ export const BUYING_INTENT_KEYWORDS = ['pricing', 'demo', 'next steps'];
34
+
35
+ /**
36
+ * Minimum company size required for qualification.
37
+ */
38
+ export const MIN_COMPANY_SIZE = 50;
39
+
40
+ /**
41
+ * Roles that are excluded from qualification.
42
+ */
43
+ export const EXCLUDED_ROLES = ['intern', 'student'];
44
+
45
+ /**
46
+ * Minimum number of words required in the message.
47
+ */
48
+ export const MIN_MESSAGE_WORDS = 20;
49
+
50
+ /**
51
+ * Qualified Sales Interest Outcome Definition
52
+ *
53
+ * Success requires meeting ALL 5 criteria:
54
+ * 1. Message contains buying intent keywords (pricing, demo, next steps)
55
+ * 2. Company size >= 50 employees
56
+ * 3. Role is not intern or student
57
+ * 4. Message length >= 20 words
58
+ * 5. Email is syntactically valid
59
+ *
60
+ * @see Requirements 8.1, 8.2, 8.3, 8.4, 8.5, 8.6
61
+ */
62
+ export const qualifiedSalesInterest: Outcome = {
63
+ name: 'qualified_sales_interest',
64
+ description:
65
+ 'Generate a qualified sales lead by engaging with a prospect who demonstrates buying intent, works at a company of sufficient size, holds a decision-making role, and provides valid contact information.',
66
+ payoutAmount: PAYOUT_AMOUNT,
67
+ maxAttempts: MAX_ATTEMPTS,
68
+ timeLimitMs: TIME_LIMIT_MS,
69
+ successCriteria: [
70
+ {
71
+ // Requirement 8.1: Verify message contains buying intent keywords
72
+ name: 'buying_intent',
73
+ validator: 'validateBuyingIntent',
74
+ params: {
75
+ keywords: BUYING_INTENT_KEYWORDS,
76
+ },
77
+ },
78
+ {
79
+ // Requirement 8.2: Verify company size >= 50
80
+ name: 'company_size',
81
+ validator: 'validateCompanySize',
82
+ params: {
83
+ minimum: MIN_COMPANY_SIZE,
84
+ },
85
+ },
86
+ {
87
+ // Requirement 8.3: Verify role is not intern or student
88
+ name: 'valid_role',
89
+ validator: 'validateRole',
90
+ params: {
91
+ excludedRoles: EXCLUDED_ROLES,
92
+ },
93
+ },
94
+ {
95
+ // Requirement 8.4: Verify message length >= 20 words
96
+ name: 'message_length',
97
+ validator: 'validateMessageLength',
98
+ params: {
99
+ minWords: MIN_MESSAGE_WORDS,
100
+ },
101
+ },
102
+ {
103
+ // Requirement 8.5: Verify email is syntactically valid
104
+ name: 'valid_email',
105
+ validator: 'validateEmail',
106
+ params: {},
107
+ },
108
+ ],
109
+ failureReasons: [
110
+ 'No buying intent detected - message must contain: pricing, demo, or next steps',
111
+ 'Company too small - must have at least 50 employees',
112
+ 'Invalid role - interns and students are not qualified leads',
113
+ 'Message too short - must be at least 20 words',
114
+ 'Invalid email format',
115
+ ],
116
+ };
117
+
118
+ export default qualifiedSalesInterest;