job-application-agent 3.4.2 → 3.6.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.
- package/README.md +23 -0
- package/installer/src/cli.mjs +8 -1
- package/installer/src/installer.mjs +8 -0
- package/job-application-agent/SKILL.md +32 -2
- package/job-application-agent/capabilities.json +3 -1
- package/job-application-agent/references/ACCOUNTING.md +104 -0
- package/job-application-agent/references/AUTONOMY.md +2 -0
- package/job-application-agent/references/CLOUD_STATE.md +2 -0
- package/job-application-agent/references/FREE_AI.md +39 -0
- package/job-application-agent/references/OUTREACH.md +210 -0
- package/job-application-agent/references/RUNS.md +35 -0
- package/job-application-agent/references/SCHEMAS.md +12 -2
- package/job-application-agent/references/agent-box/README.md +77 -0
- package/job-application-agent/references/agent-box/novnc.service.example +16 -0
- package/job-application-agent/scripts/application-accounting.mjs +246 -0
- package/job-application-agent/scripts/ats/answer-inject.mjs +203 -0
- package/job-application-agent/scripts/ats/submit-adapters.mjs +194 -0
- package/job-application-agent/scripts/attention-questions.mjs +111 -0
- package/job-application-agent/scripts/attention-resume-submit.mjs +450 -0
- package/job-application-agent/scripts/attention-runner-poll.mjs +328 -0
- package/job-application-agent/scripts/captcha-vendor.mjs +328 -0
- package/job-application-agent/scripts/cloud-state-client.mjs +62 -8
- package/job-application-agent/scripts/job-application.mjs +243 -27
- package/job-application-agent/scripts/novnc-display-guard.mjs +300 -0
- package/job-application-agent/scripts/outreach-cli.mjs +95 -0
- package/job-application-agent/scripts/outreach-domain.mjs +287 -0
- package/job-application-agent/scripts/outreach-store.mjs +72 -0
- package/job-application-agent/scripts/session-binding.mjs +474 -0
- package/job-application-agent/scripts/version.mjs +1 -1
- package/job-application-agent/tests/accounting-cli.test.mjs +86 -0
- package/job-application-agent/tests/accounting-cloud-client.test.mjs +183 -0
- package/job-application-agent/tests/accounting-retry-cli.test.mjs +96 -0
- package/job-application-agent/tests/accounting-source-race.test.mjs +109 -0
- package/job-application-agent/tests/answer-inject-captcha.test.mjs +169 -0
- package/job-application-agent/tests/application-accounting.test.mjs +315 -0
- package/job-application-agent/tests/attention-resume-submit.test.mjs +119 -0
- package/job-application-agent/tests/attention-runner-poll.test.mjs +135 -0
- package/job-application-agent/tests/fixtures/outreach.mjs +22 -0
- package/job-application-agent/tests/job-application.test.mjs +5 -0
- package/job-application-agent/tests/novnc-display-guard.test.mjs +50 -0
- package/job-application-agent/tests/outreach-cli.test.mjs +73 -0
- package/job-application-agent/tests/outreach.test.mjs +178 -0
- package/job-application-agent/tests/privacy-audit.test.mjs +2 -0
- package/job-application-agent/tests/review-cadence.test.mjs +66 -0
- package/job-application-agent/tests/session-binding.test.mjs +102 -0
- package/job-application-agent/tests/skill-contract.test.mjs +25 -0
- package/job-application-agent/tests/workflow-state.test.mjs +30 -3
- package/package.json +6 -3
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { CloudStateClient, readCachedCloudProfile, saveCloudConfig } from '../scripts/cloud-state-client.mjs';
|
|
8
|
+
import { deliveryProjection, discoveryProjection, validateDelivery, validateLead } from '../scripts/application-accounting.mjs';
|
|
9
|
+
import worker, { sha256Hex } from '../../state-worker/src/worker.mjs';
|
|
10
|
+
import { createBackup, restoreBackup } from '../../state-worker/src/backup.mjs';
|
|
11
|
+
import { createMemoryD1, hasNodeSqlite } from '../../state-worker/tests/d1-mock.mjs';
|
|
12
|
+
import { createMemoryR2 } from '../../state-worker/tests/r2-mock.mjs';
|
|
13
|
+
|
|
14
|
+
const TOKEN = 'accounting-cloud-client-synthetic-token-long-enough';
|
|
15
|
+
const sqliteTest = hasNodeSqlite ? test : test.skip;
|
|
16
|
+
const now = '2026-09-13T10:00:00.000Z';
|
|
17
|
+
const application = { id: 'app', company: 'Example', role: 'Engineer', source: 'email', url: 'https://example.com/jobs/app', status: 'submitted', submittedAt: '2026-09-12T10:00:00.000Z' };
|
|
18
|
+
const failure = validateDelivery({ version: 1, id: 'failure-event', applicationId: 'app', attemptId: 'initial:app', type: 'delivery-failed', occurredAt: now, evidenceType: 'final-delivery-failure', evidence: 'Synthetic final failure matched to the original application email.' });
|
|
19
|
+
const retry = validateDelivery({ version: 1, id: 'retry-event', applicationId: 'app', attemptId: 'recovery-attempt', type: 'retry-confirmed', channel: 'browser', url: 'https://example.com/jobs/app', channelVerifiedAt: now, approval: 'STANDING AUTHORIZATION', evidenceType: 'browser-confirmation', evidence: 'Synthetic visible ATS success.', occurredAt: '2026-09-13T11:00:00.000Z' });
|
|
20
|
+
const qualifiedLead = validateLead({ version: 1, id: 'lead-event', type: 'lead-reviewed', roundId: 'round-1', sourceId: 'direct', url: application.url, company: application.company, role: application.role, disposition: 'qualified', observedAt: now, evidence: 'Synthetic active employer posting matches requirements.' });
|
|
21
|
+
|
|
22
|
+
async function setup(t) {
|
|
23
|
+
const root = await mkdtemp(join(tmpdir(), 'job-agent-accounting-cloud-'));
|
|
24
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
25
|
+
const stateDir = join(root, 'state');
|
|
26
|
+
const configPath = join(root, 'cloud', 'config.json');
|
|
27
|
+
await mkdir(stateDir, { recursive: true });
|
|
28
|
+
const schema = await readFile(new URL('../../state-worker/migrations/0001_private_state.sql', import.meta.url), 'utf8');
|
|
29
|
+
const DB = createMemoryD1(schema);
|
|
30
|
+
await DB.prepare('INSERT INTO clients (client_id, name, token_hash, created_at) VALUES (?, ?, ?, ?)')
|
|
31
|
+
.bind('client-test', 'Synthetic Accounting Client', sha256Hex(TOKEN), now).run();
|
|
32
|
+
const bindings = { DB, STATE: createMemoryR2(), STATE_TOKEN: 'legacy', LEGACY_WRITES_DISABLED: '1' };
|
|
33
|
+
const fetchImpl = (input, init) => worker.fetch(new Request(input, init), bindings);
|
|
34
|
+
await saveCloudConfig({ version: 2, url: 'https://state.example.com', token: TOKEN, clientId: 'client-test' }, { configPath });
|
|
35
|
+
const client = new CloudStateClient({ stateDir, configPath, fetchImpl });
|
|
36
|
+
return { root, stateDir, configPath, schema, bindings, fetchImpl, client };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function writeRows(stateDir, stream, rows) {
|
|
40
|
+
await writeFile(join(stateDir, `${stream}.ndjson`), `${rows.map(row => JSON.stringify(row)).join('\n')}\n`, { mode: 0o600 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function readRows(stateDir, stream) {
|
|
44
|
+
return (await readFile(join(stateDir, `${stream}.ndjson`), 'utf8')).split('\n').filter(Boolean).map(JSON.parse);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
sqliteTest('local and cloud accounting projections agree after repeated bidirectional reconciliation', async t => {
|
|
48
|
+
const ctx = await setup(t);
|
|
49
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
50
|
+
await writeRows(ctx.stateDir, 'delivery', [failure]);
|
|
51
|
+
await writeRows(ctx.stateDir, 'discovery', [qualifiedLead]);
|
|
52
|
+
const originalProjection = deliveryProjection([application], [failure]);
|
|
53
|
+
assert.equal(originalProjection.effectiveSubmissionCount, 0);
|
|
54
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
55
|
+
const correction = validateDelivery({ version: 1, id: 'correction-event', applicationId: 'app', attemptId: 'initial:app', type: 'correction', supersedes: failure.id, status: 'receipt-confirmed', occurredAt: '2026-09-14T10:00:00.000Z', evidenceType: 'employer-acknowledgement', evidence: 'Synthetic employer acknowledgement corrects the matched failure.' });
|
|
56
|
+
await ctx.client.appendRecord('delivery', correction);
|
|
57
|
+
const downloaded = await ctx.client.reconcile({ dryRun: false });
|
|
58
|
+
assert.equal(downloaded.streams.delivery.cloudOnly, 1);
|
|
59
|
+
const repeated = await ctx.client.reconcile({ dryRun: false });
|
|
60
|
+
assert.equal(repeated.imported, 0);
|
|
61
|
+
assert.equal(repeated.downloaded, 0);
|
|
62
|
+
const localApplications = await readRows(ctx.stateDir, 'applications');
|
|
63
|
+
const localDelivery = await readRows(ctx.stateDir, 'delivery');
|
|
64
|
+
const cloudApplications = (await ctx.client.listStream('applications')).map(row => row.value);
|
|
65
|
+
const cloudDelivery = (await ctx.client.listStream('delivery')).map(row => row.value);
|
|
66
|
+
assert.equal(cloudDelivery.length, 2);
|
|
67
|
+
assert.deepEqual(deliveryProjection(localApplications, localDelivery), deliveryProjection(cloudApplications, cloudDelivery));
|
|
68
|
+
assert.equal(deliveryProjection(localApplications, localDelivery).effectiveSubmissionCount, 1);
|
|
69
|
+
const localDiscovery = await readRows(ctx.stateDir, 'discovery');
|
|
70
|
+
const cloudDiscovery = (await ctx.client.listStream('discovery')).map(row => row.value);
|
|
71
|
+
assert.deepEqual(discoveryProjection(localDiscovery, { roundId: 'round-1' }), discoveryProjection(cloudDiscovery, { roundId: 'round-1' }));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
sqliteTest('default reconciliation migrates failed-email recovery history and exports one application with two delivery records', async t => {
|
|
75
|
+
const ctx = await setup(t);
|
|
76
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
77
|
+
await writeRows(ctx.stateDir, 'delivery', [failure, retry]);
|
|
78
|
+
const result = await ctx.client.reconcile({ dryRun: false });
|
|
79
|
+
assert.equal(result.imported, 3);
|
|
80
|
+
const again = await ctx.client.reconcile({ dryRun: false });
|
|
81
|
+
assert.equal(again.imported, 0);
|
|
82
|
+
const exported = await ctx.client.exportTo(join(ctx.root, 'private-export.json'));
|
|
83
|
+
const archive = JSON.parse(await readFile(exported.path, 'utf8'));
|
|
84
|
+
assert.equal(archive.streams.applications.length, 1);
|
|
85
|
+
assert.equal(archive.streams.delivery.length, 2);
|
|
86
|
+
assert.ok(archive.streams.delivery.every(row => row.provenance === 'local-reconcile'));
|
|
87
|
+
assert.deepEqual(archive.streams.delivery.map(row => row.value), [failure, retry]);
|
|
88
|
+
assert.equal(deliveryProjection(archive.streams.applications.map(row => row.value), archive.streams.delivery.map(row => row.value)).effectiveSubmissionCount, 1);
|
|
89
|
+
if (process.platform !== 'win32') assert.equal((await stat(exported.path)).mode & 0o777, 0o600);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
sqliteTest('private backup restores delivery attempts and projection without duplicating a second restore', async t => {
|
|
93
|
+
const ctx = await setup(t);
|
|
94
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
95
|
+
await writeRows(ctx.stateDir, 'delivery', [failure, retry]);
|
|
96
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
97
|
+
const archive = await createBackup(ctx.bindings.DB, now);
|
|
98
|
+
const restored = createMemoryD1(ctx.schema);
|
|
99
|
+
assert.equal((await restoreBackup(restored, archive)).records, 3);
|
|
100
|
+
assert.equal((await restoreBackup(restored, archive)).records, 0);
|
|
101
|
+
const values = async stream => (await restored.prepare('SELECT payload_json FROM records WHERE stream = ? ORDER BY sequence').bind(stream).all()).results.map(row => JSON.parse(row.payload_json));
|
|
102
|
+
const restoredApplications = await values('applications');
|
|
103
|
+
const restoredDelivery = await values('delivery');
|
|
104
|
+
assert.deepEqual(restoredDelivery, [failure, retry]);
|
|
105
|
+
assert.deepEqual(deliveryProjection(restoredApplications, restoredDelivery), deliveryProjection([application], [failure, retry]));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
sqliteTest('an older backend is rejected before any local-only accounting data uploads', async t => {
|
|
109
|
+
const ctx = await setup(t);
|
|
110
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
111
|
+
await writeRows(ctx.stateDir, 'delivery', [failure]);
|
|
112
|
+
const mutations = [];
|
|
113
|
+
const older = new CloudStateClient({ stateDir: ctx.stateDir, configPath: ctx.configPath, fetchImpl: async (input, init) => {
|
|
114
|
+
if (new URL(input).pathname === '/v2/status') return Response.json({ apiVersion: 2, backend: 'cloudflare-d1-r2', capabilities: [] });
|
|
115
|
+
if (init.method && init.method !== 'GET') mutations.push({ input, method: init.method });
|
|
116
|
+
return ctx.fetchImpl(input, init);
|
|
117
|
+
} });
|
|
118
|
+
await assert.rejects(() => older.reconcile({ dryRun: false }), /backend upgrade required.*application-accounting-v1/i);
|
|
119
|
+
assert.equal(mutations.length, 0);
|
|
120
|
+
assert.equal((await ctx.bindings.DB.prepare('SELECT COUNT(*) AS count FROM records').first()).count, 0);
|
|
121
|
+
assert.deepEqual(await readRows(ctx.stateDir, 'delivery'), [failure]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
sqliteTest('cached accounting permits offline research and durable observation queues while blocking transmission intents', async t => {
|
|
125
|
+
const ctx = await setup(t);
|
|
126
|
+
await ctx.client.putDocument('profile', { name: 'Synthetic Candidate', roleFamilies: ['engineering'] }, 0);
|
|
127
|
+
await ctx.client.refreshProfileCache();
|
|
128
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
129
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
130
|
+
const offline = new CloudStateClient({ stateDir: ctx.stateDir, configPath: ctx.configPath, fetchImpl: async () => { throw new Error('synthetic network outage'); } });
|
|
131
|
+
await offline.requireAccounting();
|
|
132
|
+
assert.equal((await readCachedCloudProfile(ctx.stateDir)).name, 'Synthetic Candidate');
|
|
133
|
+
assert.deepEqual(await readRows(ctx.stateDir, 'applications'), [application]);
|
|
134
|
+
const queued = await offline.appendRecord('delivery', failure, { queueOnFailure: true });
|
|
135
|
+
assert.equal(queued.queued, true);
|
|
136
|
+
const restarted = new CloudStateClient({ stateDir: ctx.stateDir, configPath: ctx.configPath, fetchImpl: offline.fetchImpl });
|
|
137
|
+
const pending = await restarted.pendingWrites();
|
|
138
|
+
assert.equal(pending.length, 1);
|
|
139
|
+
assert.deepEqual(pending[0].payload.value, failure);
|
|
140
|
+
assert.equal(pending[0].stream, 'delivery');
|
|
141
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(ctx.stateDir, 'cloud-pending.ndjson'))).mode & 0o777, 0o600);
|
|
142
|
+
await assert.rejects(() => restarted.createIntent({ applicationId: 'another-app', canonicalUrl: 'https://example.com/jobs/another', leaseId: 'expired-lease' }), /cloud state unavailable/i);
|
|
143
|
+
assert.equal((await ctx.bindings.DB.prepare('SELECT COUNT(*) AS count FROM application_intents').first()).count, 0);
|
|
144
|
+
await ctx.client.reconcile({dryRun:false});
|
|
145
|
+
assert.equal((await ctx.client.pendingWrites()).length,0);
|
|
146
|
+
assert.equal((await readRows(ctx.stateDir,'delivery')).length,1);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
sqliteTest('a delivery replay followed by repeated synchronization preserves one logical evidence event', async t => {
|
|
150
|
+
const ctx = await setup(t);
|
|
151
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
152
|
+
await writeRows(ctx.stateDir, 'delivery', [failure]);
|
|
153
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
154
|
+
await ctx.client.appendRecord('delivery', failure, { idempotencyKey: 'different-network-replay-id' });
|
|
155
|
+
const sync = await ctx.client.reconcile({ dryRun: false });
|
|
156
|
+
const repeated = await ctx.client.reconcile({ dryRun: false });
|
|
157
|
+
assert.equal(sync.imported + sync.downloaded + repeated.imported + repeated.downloaded, 0);
|
|
158
|
+
assert.equal((await ctx.client.listStream('delivery')).length, 1);
|
|
159
|
+
assert.equal((await readRows(ctx.stateDir, 'delivery')).length, 1);
|
|
160
|
+
assert.equal(deliveryProjection([application], await readRows(ctx.stateDir, 'delivery')).failedDeliveryCount, 1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
sqliteTest('cross-client JSON field ordering does not duplicate the same delivery event during synchronization', async t => {
|
|
164
|
+
const ctx = await setup(t);
|
|
165
|
+
await writeRows(ctx.stateDir, 'applications', [application]);
|
|
166
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
167
|
+
await writeRows(ctx.stateDir, 'delivery', [failure]);
|
|
168
|
+
const reorderedFailure = Object.fromEntries(Object.entries(failure).reverse());
|
|
169
|
+
await ctx.client.appendRecord('delivery', reorderedFailure);
|
|
170
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
171
|
+
await ctx.client.reconcile({ dryRun: false });
|
|
172
|
+
assert.equal((await ctx.client.listStream('delivery')).length, 1);
|
|
173
|
+
assert.equal((await readRows(ctx.stateDir, 'delivery')).length, 1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
sqliteTest('unfamiliar versioned discovery records survive synchronization unchanged', async t => {
|
|
177
|
+
const ctx = await setup(t);
|
|
178
|
+
const legacy = {version:7,type:'legacy-discovery-report',id:'legacy',roundId:'old',results:{reviewed:12}};
|
|
179
|
+
await writeRows(ctx.stateDir,'discovery',[legacy]);
|
|
180
|
+
await ctx.client.reconcile({dryRun:false});
|
|
181
|
+
assert.deepEqual((await ctx.client.listStream('discovery'))[0].value,legacy);
|
|
182
|
+
assert.deepEqual(discoveryProjection(await readRows(ctx.stateDir,'discovery')).leads,[]);
|
|
183
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import test from 'node:test';
|
|
9
|
+
|
|
10
|
+
import { validateDelivery } from '../scripts/application-accounting.mjs';
|
|
11
|
+
|
|
12
|
+
const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
|
|
13
|
+
|
|
14
|
+
test('CLI records a transmitted cloud retry when late receipt evidence invalidates its original preparation eligibility', async t => {
|
|
15
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'accounting-retry-cli-'));
|
|
16
|
+
t.after(() => rm(stateDir, { recursive: true, force: true }));
|
|
17
|
+
const token = 'synthetic-accounting-retry-cli-token-long-enough';
|
|
18
|
+
const intentId = 'prepared-recovery-intent';
|
|
19
|
+
const leaseId = 'synthetic-live-lease';
|
|
20
|
+
const occurredAt = '2026-09-14T10:00:00.000Z';
|
|
21
|
+
const application = { id: 'app', company: 'Example', role: 'Engineer', url: 'https://jobs.example.com/app', source: 'email', status: 'submitted', submittedAt: '2026-09-12T10:00:00.000Z' };
|
|
22
|
+
const failure = validateDelivery({ version: 1, id: 'failure-event', applicationId: 'app', attemptId: 'initial:app', type: 'delivery-failed', occurredAt: '2026-09-13T10:00:00.000Z', evidenceType: 'final-delivery-failure', evidence: 'Synthetic final failure matched to original email.' });
|
|
23
|
+
const receipt = validateDelivery({ version: 1, id: 'late-receipt', applicationId: 'app', attemptId: 'initial:app', type: 'receipt-confirmed', occurredAt, evidenceType: 'employer-acknowledgement', evidence: 'Synthetic employer acknowledgement arrived after recovery was transmitted.' });
|
|
24
|
+
const retry = validateDelivery({ version: 1, id: 'retry-event', applicationId: 'app', attemptId: intentId, type: 'retry-confirmed', channel: 'browser', url: application.url, channelVerifiedAt: occurredAt, approval: 'STANDING AUTHORIZATION', evidenceType: 'browser-confirmation', evidence: 'Synthetic visible ATS success for prepared recovery.', occurredAt });
|
|
25
|
+
const streams = { applications: [application], delivery: [failure, receipt] };
|
|
26
|
+
const confirmations = [];
|
|
27
|
+
const unexpected = [];
|
|
28
|
+
const server = createServer(async (request, response) => {
|
|
29
|
+
const url = new URL(request.url, 'http://127.0.0.1');
|
|
30
|
+
response.setHeader('content-type', 'application/json');
|
|
31
|
+
if (request.headers.authorization !== `Bearer ${token}`) {
|
|
32
|
+
response.writeHead(401).end(JSON.stringify({ error: 'Synthetic authentication required' }));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (request.method === 'GET' && url.pathname === '/v2/status') {
|
|
36
|
+
response.end(JSON.stringify({ apiVersion: 2, backend: 'synthetic-accounting-backend', capabilities: ['application-accounting-v1'], documents: [], streams: [], files: [] }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (request.method === 'GET' && url.pathname.startsWith('/v2/streams/')) {
|
|
40
|
+
const stream = url.pathname.split('/').at(-1);
|
|
41
|
+
const records = (streams[stream] ?? []).map((value, index) => ({ sequence: index + 1, recordKey: value.id, idempotencyKey: `accounting:${value.id}`, value }));
|
|
42
|
+
const after = Number(url.searchParams.get('after') ?? 0);
|
|
43
|
+
const page = records.filter(record => record.sequence > after);
|
|
44
|
+
response.end(JSON.stringify({ stream, records: page, nextCursor: page.at(-1)?.sequence ?? after }));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (request.method === 'GET' && url.pathname.startsWith('/v2/documents/')) {
|
|
48
|
+
response.writeHead(404).end(JSON.stringify({ error: 'Synthetic document not present' }));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (request.method === 'POST' && url.pathname === `/v2/intents/${intentId}/confirm`) {
|
|
52
|
+
let raw = '';
|
|
53
|
+
for await (const chunk of request) raw += chunk;
|
|
54
|
+
const body = JSON.parse(raw);
|
|
55
|
+
confirmations.push(body);
|
|
56
|
+
if (body.leaseId !== leaseId || body.delivery?.attemptId !== intentId) {
|
|
57
|
+
response.writeHead(409).end(JSON.stringify({ error: 'Wrong prepared intent or lease' }));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
streams.delivery.push(body.delivery);
|
|
61
|
+
response.end(JSON.stringify({ confirmed: true, intentId }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
unexpected.push(`${request.method} ${url.pathname}`);
|
|
65
|
+
response.writeHead(500).end(JSON.stringify({ error: 'Unexpected synthetic backend request' }));
|
|
66
|
+
});
|
|
67
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
68
|
+
t.after(() => new Promise(resolve => server.close(resolve)));
|
|
69
|
+
const backendUrl = `http://127.0.0.1:${server.address().port}`;
|
|
70
|
+
const preload = `const realFetch = globalThis.fetch; globalThis.fetch = (input, init) => { const url = new URL(typeof input === 'string' ? input : input.url); if (url.origin !== 'https://state.example.com') throw new Error('Unexpected non-fixture network request'); return realFetch(process.env.ACCOUNTING_TEST_BACKEND_URL + url.pathname + url.search, init); };`;
|
|
71
|
+
const configPath = join(stateDir, 'cloud-config.json');
|
|
72
|
+
await writeFile(configPath, JSON.stringify({ version: 2, url: 'https://state.example.com', token, clientId: 'synthetic-client' }), { mode: 0o600 });
|
|
73
|
+
await writeFile(join(stateDir, 'telemetry.json'), JSON.stringify({ version: 1, enabled: false, disclosed: true, graceConsumed: true, installationEventPending: false }));
|
|
74
|
+
for (const [stream, rows] of Object.entries(streams)) {
|
|
75
|
+
await writeFile(join(stateDir, `${stream}.ndjson`), `${rows.map(row => JSON.stringify(row)).join('\n')}\n`, { mode: 0o600 });
|
|
76
|
+
}
|
|
77
|
+
const env = { ...process.env, ACCOUNTING_TEST_BACKEND_URL: backendUrl, JOB_APPLICATION_AGENT_STATE_DIR: stateDir, JOB_APPLICATION_AGENT_CLOUD_CONFIG: configPath, JOB_APPLICATION_AGENT_SOURCE_COMMUNITY_URL: 'http://127.0.0.1:9' };
|
|
78
|
+
const result = await new Promise((resolve, reject) => {
|
|
79
|
+
const child = spawn(process.execPath, ['--import', `data:text/javascript,${encodeURIComponent(preload)}`, script, 'ledger', 'retry', '--stdin'], { env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
80
|
+
let stdout = '';
|
|
81
|
+
let stderr = '';
|
|
82
|
+
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
83
|
+
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
84
|
+
child.on('error', reject);
|
|
85
|
+
child.on('close', code => resolve({ code, stdout, stderr }));
|
|
86
|
+
child.stdin.end(JSON.stringify({ ...retry, cloudIntentId: intentId, cloudLeaseId: leaseId }));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
assert.deepEqual(unexpected, []);
|
|
90
|
+
assert.equal(result.code, 0, result.stderr);
|
|
91
|
+
assert.equal(confirmations.length, 1);
|
|
92
|
+
assert.deepEqual(confirmations[0], { delivery: retry, leaseId });
|
|
93
|
+
assert.equal(JSON.parse(result.stdout).recorded, true);
|
|
94
|
+
const localEvents = (await readFile(join(stateDir, 'delivery.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
95
|
+
assert.equal(localEvents.filter(event => event.type === 'retry-confirmed' && event.attemptId === intentId).length, 1);
|
|
96
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
import test from 'node:test';
|
|
8
|
+
|
|
9
|
+
const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
|
|
10
|
+
// Constant preload: pause immediately before the source command acquires its
|
|
11
|
+
// rounds lock, allowing a real lead command to commit first without timing sleeps.
|
|
12
|
+
const lockBarrierPreload = `
|
|
13
|
+
import fs from 'node:fs/promises';
|
|
14
|
+
import { basename } from 'node:path';
|
|
15
|
+
import { syncBuiltinESMExports } from 'node:module';
|
|
16
|
+
const originalOpen = fs.open;
|
|
17
|
+
let intercepted = false;
|
|
18
|
+
fs.open = async function(path, ...args) {
|
|
19
|
+
if (!intercepted && basename(String(path)) === '.rounds.lock') {
|
|
20
|
+
intercepted = true;
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
process.once('message', message => {
|
|
23
|
+
if (message !== 'release-lock-attempt') return reject(new Error('Unexpected barrier message'));
|
|
24
|
+
process.disconnect();
|
|
25
|
+
resolve();
|
|
26
|
+
});
|
|
27
|
+
process.send('before-rounds-lock');
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return originalOpen.call(fs, path, ...args);
|
|
31
|
+
};
|
|
32
|
+
syncBuiltinESMExports();
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
function launch(env, args, input, preloadPath) {
|
|
36
|
+
const runtime = preloadPath ? ['--import', pathToFileURL(preloadPath).href] : [];
|
|
37
|
+
const child = spawn(process.execPath, [...runtime, script, ...args], {
|
|
38
|
+
env, stdio: preloadPath ? ['pipe', 'pipe', 'pipe', 'ipc'] : ['pipe', 'pipe', 'pipe'],
|
|
39
|
+
});
|
|
40
|
+
const completed = new Promise((resolve, reject) => {
|
|
41
|
+
let stdout = '';
|
|
42
|
+
let stderr = '';
|
|
43
|
+
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
44
|
+
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
45
|
+
child.once('error', reject);
|
|
46
|
+
child.once('close', code => resolve({ code, stdout, stderr }));
|
|
47
|
+
});
|
|
48
|
+
child.stdin.end(JSON.stringify(input));
|
|
49
|
+
return { child, completed };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const explicitStaleCounts of [false, true]) {
|
|
53
|
+
test(explicitStaleCounts
|
|
54
|
+
? 'source coverage rejects stale count assertions after a lead commits before lock acquisition'
|
|
55
|
+
: 'source coverage derives counts from the lead committed before lock acquisition', { timeout: 15000 }, async t => {
|
|
56
|
+
const dir = await mkdtemp(join(tmpdir(), 'accounting-source-race-'));
|
|
57
|
+
t.after(() => rm(dir, { recursive: true, force: true }));
|
|
58
|
+
const preloadPath = join(dir, 'rounds-lock-barrier.mjs');
|
|
59
|
+
await writeFile(preloadPath, lockBarrierPreload);
|
|
60
|
+
await writeFile(join(dir, 'telemetry.json'), JSON.stringify({ enabled: false, disclosed: true }));
|
|
61
|
+
const env = {
|
|
62
|
+
...process.env,
|
|
63
|
+
JOB_APPLICATION_AGENT_STATE_DIR: dir,
|
|
64
|
+
JOB_APPLICATION_AGENT_CLOUD_CONFIG: join(dir, 'absent-cloud-config.json'),
|
|
65
|
+
JOB_APPLICATION_AGENT_SOURCE_COMMUNITY_URL: 'http://127.0.0.1:9',
|
|
66
|
+
};
|
|
67
|
+
const started = await launch(env, ['round', 'start', '--stdin'], { requestedCount: 1 }).completed;
|
|
68
|
+
assert.equal(started.code, 0, started.stderr);
|
|
69
|
+
const { roundId } = JSON.parse(started.stdout);
|
|
70
|
+
const sourceId = 'indeed';
|
|
71
|
+
const source = launch(env, ['round', 'source', '--stdin'], {
|
|
72
|
+
roundId, sourceId, status: 'searched', evidence: 'Synthetic source search.',
|
|
73
|
+
...(explicitStaleCounts ? { reviewedCount: 0, qualifiedCount: 0 } : {}),
|
|
74
|
+
}, preloadPath);
|
|
75
|
+
t.after(() => { if (source.child.exitCode === null) source.child.kill(); });
|
|
76
|
+
await Promise.race([
|
|
77
|
+
new Promise((resolve, reject) => {
|
|
78
|
+
source.child.once('message', message => {
|
|
79
|
+
if (message === 'before-rounds-lock') resolve();
|
|
80
|
+
else reject(new Error('Source sent an unexpected barrier message.'));
|
|
81
|
+
});
|
|
82
|
+
}),
|
|
83
|
+
source.completed.then(result => { throw new Error(`Source exited before the lock barrier: ${result.stderr}`); }),
|
|
84
|
+
]);
|
|
85
|
+
const lead = await launch(env, ['round', 'lead', '--stdin'], {
|
|
86
|
+
id: 'qualified-lead', roundId, sourceId, company: 'Synthetic Example', role: 'Engineer',
|
|
87
|
+
url: 'https://example.test/jobs/123', disposition: 'qualified',
|
|
88
|
+
observedAt: '2026-09-14T10:00:00.000Z', evidence: 'Synthetic qualifying employer requisition.',
|
|
89
|
+
}).completed;
|
|
90
|
+
assert.equal(lead.code, 0, lead.stderr);
|
|
91
|
+
source.child.send('release-lock-attempt');
|
|
92
|
+
const result = await source.completed;
|
|
93
|
+
const stored = (await readFile(join(dir, 'rounds.ndjson'), 'utf8')).split('\n').filter(Boolean).map(JSON.parse);
|
|
94
|
+
const coverage = stored.filter(event => event.type === 'source-checked');
|
|
95
|
+
if (explicitStaleCounts) {
|
|
96
|
+
assert.notEqual(result.code, 0, 'Stale zero-count assertions must not be accepted after one qualified lead commits.');
|
|
97
|
+
assert.match(result.stderr, /count assertions do not match recorded leads/i);
|
|
98
|
+
assert.equal(coverage.length, 0);
|
|
99
|
+
} else {
|
|
100
|
+
assert.equal(result.code, 0, result.stderr);
|
|
101
|
+
const event = JSON.parse(result.stdout);
|
|
102
|
+
assert.equal(event.reviewedCount, 1);
|
|
103
|
+
assert.equal(event.qualifiedCount, 1);
|
|
104
|
+
assert.equal(coverage.length, 1);
|
|
105
|
+
assert.equal(coverage[0].reviewedCount, 1);
|
|
106
|
+
assert.equal(coverage[0].qualifiedCount, 1);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
buildAnswerInjectPlan,
|
|
6
|
+
labelSimilarity,
|
|
7
|
+
mapAnswersToFields,
|
|
8
|
+
shouldInjectAnswersBeforeSubmit,
|
|
9
|
+
} from "../scripts/ats/answer-inject.mjs";
|
|
10
|
+
import {
|
|
11
|
+
detectAiAssistanceDiscouraged,
|
|
12
|
+
extractNarrativeQuestionsFromText,
|
|
13
|
+
normalizeAttentionQuestions,
|
|
14
|
+
} from "../scripts/attention-questions.mjs";
|
|
15
|
+
import {
|
|
16
|
+
checkCaptchaSpendCap,
|
|
17
|
+
detectChallenge,
|
|
18
|
+
resolveCaptchaVendorConfig,
|
|
19
|
+
tryCaptchaVendorAssist,
|
|
20
|
+
} from "../scripts/captcha-vendor.mjs";
|
|
21
|
+
import {
|
|
22
|
+
RESUME_EXIT,
|
|
23
|
+
decideResumeSubmit,
|
|
24
|
+
} from "../scripts/attention-resume-submit.mjs";
|
|
25
|
+
|
|
26
|
+
const binding = {
|
|
27
|
+
version: 1,
|
|
28
|
+
attentionId: "attention-1",
|
|
29
|
+
jobUrl: "https://jobs.ashbyhq.com/livekit/application",
|
|
30
|
+
browserProfilePath: "/tmp/jaa-chrome",
|
|
31
|
+
display: ":99",
|
|
32
|
+
vncPort: 5900,
|
|
33
|
+
createdAt: "2026-09-16T00:00:00.000Z",
|
|
34
|
+
questions: [
|
|
35
|
+
{ id: "why", prompt: "Why do you want to work at LiveKit?", kind: "why-us", required: true },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
test("mapAnswersToFields matches by id and label", () => {
|
|
40
|
+
const mapped = mapAnswersToFields(
|
|
41
|
+
[
|
|
42
|
+
{ questionId: "why", text: "Because realtime infra.", source: "typed", prompt: "Why LiveKit?" },
|
|
43
|
+
{ questionId: "proud", text: "Shipped agents.", source: "bank", prompt: "Proud project?" },
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
{ id: "why", label: "Why LiveKit?", selector: "textarea#why" },
|
|
47
|
+
{ id: "other", label: "Project you are proud of", selector: "textarea#proud" },
|
|
48
|
+
],
|
|
49
|
+
);
|
|
50
|
+
assert.equal(mapped.mappings.length, 2);
|
|
51
|
+
assert.equal(mapped.mappings[0].matchedBy, "question_id");
|
|
52
|
+
assert.ok(mapped.mappings[1].score >= 0.25);
|
|
53
|
+
assert.ok(labelSimilarity("why this role", "why this company") > 0.2);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("buildAnswerInjectPlan prefers ashby hint", () => {
|
|
57
|
+
const plan = buildAnswerInjectPlan({
|
|
58
|
+
pageUrl: "https://jobs.ashbyhq.com/x/application",
|
|
59
|
+
answers: [{ questionId: "why", text: "Hello", source: "typed", prompt: "Why us?" }],
|
|
60
|
+
});
|
|
61
|
+
assert.equal(plan.adapterHint, "ashby");
|
|
62
|
+
assert.equal(plan.fills.length, 1);
|
|
63
|
+
assert.equal(plan.fills[0].action, "fill_textarea");
|
|
64
|
+
assert.equal(shouldInjectAnswersBeforeSubmit({ answers: plan.fills }).inject, true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("pause packaging extracts questions and AI policy", () => {
|
|
68
|
+
const text = "Why do you want to work at LiveKit? Do not use AI assistance.";
|
|
69
|
+
assert.equal(detectAiAssistanceDiscouraged(text), true);
|
|
70
|
+
const extracted = extractNarrativeQuestionsFromText(text);
|
|
71
|
+
assert.ok(extracted.length >= 1);
|
|
72
|
+
assert.equal(normalizeAttentionQuestions(extracted)[0].kind, "why-us");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("decideResumeSubmit injects answers before submit", () => {
|
|
76
|
+
const decision = decideResumeSubmit({
|
|
77
|
+
binding,
|
|
78
|
+
answers: [{ questionId: "why", text: "I want to build realtime systems.", source: "typed" }],
|
|
79
|
+
snapshot: {
|
|
80
|
+
pageUrl: "https://jobs.ashbyhq.com/livekit/application",
|
|
81
|
+
submitEnabled: true,
|
|
82
|
+
leaseHeld: true,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
assert.equal(decision.action, "inject_answers");
|
|
86
|
+
assert.equal(decision.exitCode, RESUME_EXIT.INJECT_ANSWERS);
|
|
87
|
+
assert.equal(decision.injectPlan.fills[0].text.includes("realtime"), true);
|
|
88
|
+
|
|
89
|
+
const after = decideResumeSubmit({
|
|
90
|
+
binding,
|
|
91
|
+
answers: [{ questionId: "why", text: "I want to build realtime systems.", source: "typed" }],
|
|
92
|
+
snapshot: {
|
|
93
|
+
pageUrl: "https://jobs.ashbyhq.com/livekit/application",
|
|
94
|
+
submitEnabled: true,
|
|
95
|
+
leaseHeld: true,
|
|
96
|
+
answersInjected: true,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
assert.equal(after.action, "ready_to_submit");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("captcha vendor off → no network and fail closed", async () => {
|
|
103
|
+
let network = 0;
|
|
104
|
+
const fetchImpl = async () => {
|
|
105
|
+
network += 1;
|
|
106
|
+
return new Response("{}");
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const off = await tryCaptchaVendorAssist({
|
|
110
|
+
env: { CAPTCHA_VENDOR: "off" },
|
|
111
|
+
fetchImpl,
|
|
112
|
+
snapshot: {
|
|
113
|
+
pageUrl: "https://jobs.ashbyhq.com/x/application",
|
|
114
|
+
pageHtml: '<div class="g-recaptcha" data-sitekey="abc"></div>',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
assert.equal(off.reason, "vendor_off");
|
|
118
|
+
assert.equal(off.fallback, "complete-captcha");
|
|
119
|
+
assert.equal(network, 0);
|
|
120
|
+
|
|
121
|
+
const missingKey = await tryCaptchaVendorAssist({
|
|
122
|
+
env: { CAPTCHA_VENDOR: "capsolver", CAPTCHA_BUYER_OPT_IN: "on" },
|
|
123
|
+
fetchImpl,
|
|
124
|
+
snapshot: {
|
|
125
|
+
pageUrl: "https://jobs.ashbyhq.com/x/application",
|
|
126
|
+
pageHtml: '<div class="g-recaptcha" data-sitekey="abc"></div>',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
assert.equal(missingKey.reason, "api_key_missing");
|
|
130
|
+
assert.equal(network, 0);
|
|
131
|
+
|
|
132
|
+
const unsupported = await tryCaptchaVendorAssist({
|
|
133
|
+
env: {
|
|
134
|
+
CAPTCHA_VENDOR: "capsolver",
|
|
135
|
+
CAPTCHA_VENDOR_API_KEY: "test-key",
|
|
136
|
+
CAPTCHA_BUYER_OPT_IN: "on",
|
|
137
|
+
},
|
|
138
|
+
fetchImpl,
|
|
139
|
+
snapshot: {
|
|
140
|
+
pageUrl: "https://jobs.ashbyhq.com/x/application",
|
|
141
|
+
pageHtml: "<div>hCaptcha challenge</div>",
|
|
142
|
+
challengeType: "hcaptcha",
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
assert.equal(unsupported.reason, "unsupported_type");
|
|
146
|
+
assert.equal(network, 0);
|
|
147
|
+
assert.equal(detectChallenge({ pageHtml: "cf-turnstile" }).type, "turnstile");
|
|
148
|
+
assert.equal(resolveCaptchaVendorConfig({}).vendor, "off");
|
|
149
|
+
assert.equal(checkCaptchaSpendCap({ spendMonthUsd: 5, spendCapUsd: 5, additionalUsd: 0.01 }).ok, false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("still_blocked captcha surfaces vendor_off assist note", () => {
|
|
153
|
+
const decision = decideResumeSubmit({
|
|
154
|
+
binding,
|
|
155
|
+
captchaAssist: {
|
|
156
|
+
reason: "vendor_off",
|
|
157
|
+
fallback: "complete-captcha",
|
|
158
|
+
networkCalled: false,
|
|
159
|
+
},
|
|
160
|
+
snapshot: {
|
|
161
|
+
pageUrl: "https://jobs.ashbyhq.com/livekit/application",
|
|
162
|
+
pageText: "Please complete the reCAPTCHA",
|
|
163
|
+
leaseHeld: true,
|
|
164
|
+
answersInjected: true,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
assert.equal(decision.action, "still_blocked");
|
|
168
|
+
assert.equal(decision.captchaAssist.reason, "vendor_off");
|
|
169
|
+
});
|