job-application-agent 3.1.0 → 3.1.2
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 +88 -128
- package/job-application-agent/SKILL.md +28 -19
- package/job-application-agent/references/ANALYTICS.md +4 -0
- package/job-application-agent/references/RUNS.md +2 -1
- package/job-application-agent/references/SCHEMAS.md +8 -3
- package/job-application-agent/references/SOURCES.json +156 -0
- package/job-application-agent/references/SOURCES.md +66 -0
- package/job-application-agent/scripts/job-application.mjs +208 -23
- package/job-application-agent/scripts/source-community-client.mjs +254 -0
- package/job-application-agent/scripts/source-community-schema.mjs +180 -0
- package/job-application-agent/tests/job-application.test.mjs +11 -9
- package/job-application-agent/tests/privacy-audit.test.mjs +16 -0
- package/job-application-agent/tests/source-community-client.test.mjs +245 -0
- package/job-application-agent/tests/source-community-schema.test.mjs +166 -0
- package/job-application-agent/tests/telemetry-client.test.mjs +1 -1
- package/job-application-agent/tests/workflow-state.test.mjs +394 -7
- package/package.json +7 -3
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { execFileSync, spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import test from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
import { sourcesSync } from '../scripts/job-application.mjs';
|
|
10
|
+
|
|
11
|
+
const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
|
|
9
12
|
|
|
10
13
|
async function fixture(t, label) {
|
|
11
14
|
const directory = await mkdtemp(join(tmpdir(), `public-job-agent-${label}-`));
|
|
@@ -17,7 +20,7 @@ async function fixture(t, label) {
|
|
|
17
20
|
graceConsumed: true,
|
|
18
21
|
installationEventPending: false,
|
|
19
22
|
}));
|
|
20
|
-
return { directory, env: { ...process.env, JOB_APPLICATION_AGENT_STATE_DIR: directory } };
|
|
23
|
+
return { directory, env: { ...process.env, JOB_APPLICATION_AGENT_STATE_DIR: directory, JOB_APPLICATION_AGENT_SOURCE_COMMUNITY_URL: 'http://127.0.0.1:9' } };
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
function cli(env, args, input) {
|
|
@@ -36,6 +39,18 @@ function cliFailure(env, args, input) {
|
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function cliConcurrent(env, args, input) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const child = spawn(process.execPath, [script, ...args], { env });
|
|
45
|
+
let stdout = '';
|
|
46
|
+
let stderr = '';
|
|
47
|
+
child.stdout.setEncoding('utf8').on('data', (chunk) => { stdout += chunk; });
|
|
48
|
+
child.stderr.setEncoding('utf8').on('data', (chunk) => { stderr += chunk; });
|
|
49
|
+
child.on('close', (status) => resolve({ status, stdout, stderr }));
|
|
50
|
+
child.stdin.end(JSON.stringify(input));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
function submission(index, roundId, overrides = {}) {
|
|
40
55
|
return {
|
|
41
56
|
id: `round-role-${index}`,
|
|
@@ -69,7 +84,7 @@ test('persists a scoped autonomy grant and revokes future routine transmissions'
|
|
|
69
84
|
const status = cli(env, ['autonomy', 'status']);
|
|
70
85
|
assert.equal(status.enabled, true);
|
|
71
86
|
assert.equal(status.mode, 'routine-auto');
|
|
72
|
-
assert.equal((await stat(join(directory, 'autonomy.json'))).mode & 0o777, 0o600);
|
|
87
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'autonomy.json'))).mode & 0o777, 0o600);
|
|
73
88
|
|
|
74
89
|
const preview = cli(env, ['autonomy', 'preview']);
|
|
75
90
|
assert.equal(preview.maySubmitRoutineApplications, true);
|
|
@@ -112,7 +127,379 @@ test('counts only unique confirmed ledger submissions for an explicit round', as
|
|
|
112
127
|
const roundEvents = (await readFile(join(directory, 'rounds.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
113
128
|
assert.equal(roundEvents.filter((event) => event.type === 'submission-confirmed').length, 30);
|
|
114
129
|
assert.equal(roundEvents.length, 32);
|
|
115
|
-
assert.equal((await stat(join(directory, 'rounds.ndjson'))).mode & 0o777, 0o600);
|
|
130
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'rounds.ndjson'))).mode & 0o777, 0o600);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('ships a filterable global discovery source catalog and tracks source attribution', async (t) => {
|
|
134
|
+
const { directory, env } = await fixture(t, 'source-catalog');
|
|
135
|
+
const catalog = cli(env, ['sources', 'list']);
|
|
136
|
+
const yc = catalog.sources.find((source) => source.id === 'yc-work-at-a-startup');
|
|
137
|
+
|
|
138
|
+
assert.ok(yc);
|
|
139
|
+
assert.equal(yc.kind, 'startup-network');
|
|
140
|
+
assert.equal(yc.requiresSession, true);
|
|
141
|
+
assert.equal(yc.verification, 'direct-employer-or-ats');
|
|
142
|
+
|
|
143
|
+
const filtered = cli(env, ['sources', 'list', '--stdin'], {
|
|
144
|
+
regions: ['global'],
|
|
145
|
+
roleFamilies: ['engineering'],
|
|
146
|
+
});
|
|
147
|
+
assert.ok(filtered.sources.some((source) => source.id === 'yc-work-at-a-startup'));
|
|
148
|
+
|
|
149
|
+
cli(env, ['ledger', 'add', '--stdin'], {
|
|
150
|
+
id: 'catalog-sourced-role',
|
|
151
|
+
company: 'Catalog Example',
|
|
152
|
+
role: 'Staff Product Engineer',
|
|
153
|
+
url: 'https://jobs.catalog.example/staff-product-engineer',
|
|
154
|
+
source: 'company',
|
|
155
|
+
discoverySource: 'yc',
|
|
156
|
+
discoverySourceId: 'yc-work-at-a-startup',
|
|
157
|
+
applicationChannel: 'company',
|
|
158
|
+
score: 90,
|
|
159
|
+
status: 'submitted',
|
|
160
|
+
submittedAt: new Date().toISOString(),
|
|
161
|
+
approval: 'STANDING AUTHORIZATION',
|
|
162
|
+
answers: {},
|
|
163
|
+
});
|
|
164
|
+
const [stored] = (await readFile(join(directory, 'applications.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
165
|
+
assert.equal(stored.discoverySourceId, 'yc-work-at-a-startup');
|
|
166
|
+
|
|
167
|
+
const invalidSource = cliFailure(env, ['ledger', 'add', '--stdin'], {
|
|
168
|
+
id: 'catalog-mistyped-source-role',
|
|
169
|
+
company: 'Catalog Example Two',
|
|
170
|
+
role: 'Senior Product Engineer',
|
|
171
|
+
url: 'https://jobs.catalog.example/senior-product-engineer',
|
|
172
|
+
source: 'company',
|
|
173
|
+
discoverySource: 'job-board',
|
|
174
|
+
discoverySourceId: 'employable-al',
|
|
175
|
+
applicationChannel: 'company',
|
|
176
|
+
score: 85,
|
|
177
|
+
status: 'submitted',
|
|
178
|
+
submittedAt: new Date().toISOString(),
|
|
179
|
+
approval: 'STANDING AUTHORIZATION',
|
|
180
|
+
answers: {},
|
|
181
|
+
});
|
|
182
|
+
assert.equal(invalidSource.status, 1);
|
|
183
|
+
assert.match(invalidSource.stderr, /packaged source catalog/i);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('queues sanitized repeatable source suggestions locally for public-registry review', async (t) => {
|
|
187
|
+
const { directory, env } = await fixture(t, 'source-suggestions');
|
|
188
|
+
const suggestion = cli(env, ['sources', 'suggest', '--stdin'], {
|
|
189
|
+
name: 'Example Engineering Board',
|
|
190
|
+
baseUrl: 'https://jobs.example.org/openings/engineering?term=software&ref=candidate@example.com&token=private#apply',
|
|
191
|
+
kind: 'job-board',
|
|
192
|
+
regions: ['global'],
|
|
193
|
+
roleFamilies: ['engineering'],
|
|
194
|
+
requiresSession: false,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
assert.equal(suggestion.queued, true);
|
|
198
|
+
assert.equal(suggestion.suggestion.baseUrl, 'https://jobs.example.org/openings/engineering');
|
|
199
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'source-suggestions.ndjson'))).mode & 0o777, 0o600);
|
|
200
|
+
|
|
201
|
+
const pending = cli(env, ['sources', 'pending']);
|
|
202
|
+
assert.equal(pending.scope, 'local-unsent');
|
|
203
|
+
assert.equal(pending.count, 1);
|
|
204
|
+
assert.equal(pending.suggestions[0].name, 'Example Engineering Board');
|
|
205
|
+
|
|
206
|
+
const rejected = cliFailure(env, ['sources', 'suggest', '--stdin'], {
|
|
207
|
+
name: 'Personal referral',
|
|
208
|
+
baseUrl: 'https://linkedin.com/in/some-person',
|
|
209
|
+
kind: 'user-supplied',
|
|
210
|
+
regions: ['global'],
|
|
211
|
+
roleFamilies: ['engineering'],
|
|
212
|
+
requiresSession: true,
|
|
213
|
+
});
|
|
214
|
+
assert.equal(rejected.status, 1);
|
|
215
|
+
assert.match(rejected.stderr, /profile or personal URL/i);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('exposes independent opt-out controls for default community source sharing', async (t) => {
|
|
219
|
+
const { directory, env } = await fixture(t, 'source-sharing-controls');
|
|
220
|
+
const initial = cli(env, ['sources', 'sharing', 'status']);
|
|
221
|
+
assert.equal(initial.enabled, true);
|
|
222
|
+
|
|
223
|
+
const disabled = cli(env, ['sources', 'sharing', 'disable']);
|
|
224
|
+
assert.equal(disabled.enabled, false);
|
|
225
|
+
assert.equal(disabled.hasInstallationId, false);
|
|
226
|
+
|
|
227
|
+
const suggestion = cli(env, ['sources', 'suggest', '--stdin'], {
|
|
228
|
+
name: 'Example Engineering Board',
|
|
229
|
+
baseUrl: 'https://jobs.example.org/openings/engineering',
|
|
230
|
+
kind: 'job-board',
|
|
231
|
+
regions: ['global'],
|
|
232
|
+
roleFamilies: ['engineering'],
|
|
233
|
+
requiresSession: false,
|
|
234
|
+
});
|
|
235
|
+
assert.equal(suggestion.queued, true);
|
|
236
|
+
assert.deepEqual(suggestion.community, { shared: false, reason: 'disabled' });
|
|
237
|
+
assert.equal(cli(env, ['sources', 'pending']).count, 1);
|
|
238
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'source-sharing.json'))).mode & 0o777, 0o600);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('source sync counts only the contribution actually attempted before an unavailable relay stops the queue', async (t) => {
|
|
242
|
+
const { directory, env } = await fixture(t, 'source-sync-attempts');
|
|
243
|
+
const previous = process.env.JOB_APPLICATION_AGENT_STATE_DIR;
|
|
244
|
+
process.env.JOB_APPLICATION_AGENT_STATE_DIR = directory;
|
|
245
|
+
t.after(() => {
|
|
246
|
+
if (previous == null) delete process.env.JOB_APPLICATION_AGENT_STATE_DIR;
|
|
247
|
+
else process.env.JOB_APPLICATION_AGENT_STATE_DIR = previous;
|
|
248
|
+
});
|
|
249
|
+
const suggestion = (id, baseUrl) => ({
|
|
250
|
+
type: 'suggested', id, name: `Source ${id}`, baseUrl, kind: 'job-board',
|
|
251
|
+
regions: ['global'], roleFamilies: ['engineering'], requiresSession: false,
|
|
252
|
+
createdAt: new Date().toISOString(),
|
|
253
|
+
});
|
|
254
|
+
await writeFile(join(directory, 'source-suggestions.ndjson'), [
|
|
255
|
+
JSON.stringify(suggestion('source-suggestion-one', 'https://one.example.com/openings')),
|
|
256
|
+
JSON.stringify(suggestion('source-suggestion-two', 'https://two.example.com/openings')),
|
|
257
|
+
].join('\n').concat('\n'));
|
|
258
|
+
let calls = 0;
|
|
259
|
+
|
|
260
|
+
const result = await sourcesSync({
|
|
261
|
+
contribute: async () => {
|
|
262
|
+
calls += 1;
|
|
263
|
+
return { shared: false, reason: 'unavailable' };
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
assert.equal(calls, 1);
|
|
268
|
+
assert.deepEqual(result, { attempted: 1, shared: 0, remaining: 2 });
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('blocks a different role at the same company during the 15-day cooldown', async (t) => {
|
|
272
|
+
const { env } = await fixture(t, 'company-cooldown-enforced');
|
|
273
|
+
const recent = new Date(Date.now() - (5 * 86_400_000)).toISOString();
|
|
274
|
+
const now = new Date().toISOString();
|
|
275
|
+
cli(env, ['ledger', 'add', '--stdin'], submission(100, undefined, {
|
|
276
|
+
id: 'example-backend-role',
|
|
277
|
+
company: 'Example',
|
|
278
|
+
role: 'Senior Backend Engineer',
|
|
279
|
+
url: 'https://jobs.example.com/backend-100',
|
|
280
|
+
employerJobId: 'example:backend-100',
|
|
281
|
+
submittedAt: recent,
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
const check = cli(env, ['ledger', 'check', '--stdin'], {
|
|
285
|
+
id: 'example-product-role',
|
|
286
|
+
company: 'Example',
|
|
287
|
+
role: 'Staff Product Engineer',
|
|
288
|
+
url: 'https://jobs.example.com/product-101',
|
|
289
|
+
employerJobId: 'example:product-101',
|
|
290
|
+
});
|
|
291
|
+
assert.equal(check.companyReapply.decision, 'cooldown-active');
|
|
292
|
+
assert.equal(check.companyReapply.eligible, false);
|
|
293
|
+
|
|
294
|
+
const blocked = cliFailure(env, ['ledger', 'add', '--stdin'], submission(101, undefined, {
|
|
295
|
+
id: 'example-product-role',
|
|
296
|
+
company: 'Example',
|
|
297
|
+
role: 'Staff Product Engineer',
|
|
298
|
+
url: 'https://jobs.example.com/product-101',
|
|
299
|
+
employerJobId: 'example:product-101',
|
|
300
|
+
submittedAt: now,
|
|
301
|
+
}));
|
|
302
|
+
|
|
303
|
+
assert.equal(blocked.status, 1);
|
|
304
|
+
assert.match(blocked.stderr, /reapplication cooldown is active/i);
|
|
305
|
+
assert.match(blocked.stderr, /CANDIDATE APPROVED EARLY REAPPLICATION/);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('records bounded evidence for an explicitly approved early reapplication', async (t) => {
|
|
309
|
+
const { directory, env } = await fixture(t, 'company-cooldown-override');
|
|
310
|
+
const recent = new Date(Date.now() - (5 * 86_400_000)).toISOString();
|
|
311
|
+
const now = new Date().toISOString();
|
|
312
|
+
cli(env, ['ledger', 'add', '--stdin'], submission(110, undefined, {
|
|
313
|
+
id: 'override-backend-role',
|
|
314
|
+
company: 'Override Co',
|
|
315
|
+
role: 'Senior Backend Engineer',
|
|
316
|
+
url: 'https://jobs.example.com/override-backend-110',
|
|
317
|
+
submittedAt: recent,
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
const result = cli(env, ['ledger', 'add', '--stdin'], {
|
|
321
|
+
...submission(111, undefined, {
|
|
322
|
+
id: 'override-product-role',
|
|
323
|
+
company: 'Override Co',
|
|
324
|
+
role: 'Staff Product Engineer',
|
|
325
|
+
url: 'https://jobs.example.com/override-product-111',
|
|
326
|
+
submittedAt: now,
|
|
327
|
+
}),
|
|
328
|
+
companyReapplyOverride: 'CANDIDATE APPROVED EARLY REAPPLICATION',
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
assert.equal(result.recorded, 'override-product-role');
|
|
332
|
+
const stored = (await readFile(join(directory, 'applications.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
333
|
+
assert.equal(stored[1].reapplicationApproval, 'candidate-explicit');
|
|
334
|
+
assert.equal(JSON.stringify(stored).includes('CANDIDATE APPROVED EARLY REAPPLICATION'), false);
|
|
335
|
+
assert.equal('companyReapplyOverride' in stored[1], false);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('requires explicit approval when the latest company application has a follow-up', async (t) => {
|
|
339
|
+
const { directory, env } = await fixture(t, 'company-follow-up-override');
|
|
340
|
+
const old = new Date(Date.now() - (30 * 86_400_000)).toISOString();
|
|
341
|
+
const followUp = new Date(Date.now() - (20 * 86_400_000)).toISOString();
|
|
342
|
+
const now = new Date().toISOString();
|
|
343
|
+
cli(env, ['ledger', 'add', '--stdin'], submission(120, undefined, {
|
|
344
|
+
id: 'follow-up-backend-role',
|
|
345
|
+
company: 'Follow Up Co',
|
|
346
|
+
role: 'Senior Backend Engineer',
|
|
347
|
+
url: 'https://jobs.example.com/follow-up-backend-120',
|
|
348
|
+
submittedAt: old,
|
|
349
|
+
}));
|
|
350
|
+
cli(env, ['ledger', 'outcome', '--stdin'], {
|
|
351
|
+
id: 'follow-up-backend-role',
|
|
352
|
+
status: 'rejected',
|
|
353
|
+
occurredAt: followUp,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const candidate = submission(121, undefined, {
|
|
357
|
+
id: 'follow-up-product-role',
|
|
358
|
+
company: 'Follow Up Co',
|
|
359
|
+
role: 'Staff Product Engineer',
|
|
360
|
+
url: 'https://jobs.example.com/follow-up-product-121',
|
|
361
|
+
submittedAt: now,
|
|
362
|
+
});
|
|
363
|
+
const blocked = cliFailure(env, ['ledger', 'add', '--stdin'], candidate);
|
|
364
|
+
assert.equal(blocked.status, 1);
|
|
365
|
+
assert.match(blocked.stderr, /recorded follow-up/i);
|
|
366
|
+
|
|
367
|
+
const result = cli(env, ['ledger', 'add', '--stdin'], {
|
|
368
|
+
...candidate,
|
|
369
|
+
companyReapplyOverride: 'CANDIDATE APPROVED EARLY REAPPLICATION',
|
|
370
|
+
});
|
|
371
|
+
assert.equal(result.recorded, 'follow-up-product-role');
|
|
372
|
+
const stored = (await readFile(join(directory, 'applications.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
373
|
+
assert.equal(stored[1].reapplicationApproval, 'candidate-explicit');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('allows a genuinely different role after 15 full quiet days', async (t) => {
|
|
377
|
+
const { env } = await fixture(t, 'company-cooldown-complete');
|
|
378
|
+
const old = new Date(Date.now() - (16 * 86_400_000)).toISOString();
|
|
379
|
+
cli(env, ['ledger', 'add', '--stdin'], submission(130, undefined, {
|
|
380
|
+
id: 'quiet-backend-role',
|
|
381
|
+
company: 'Quiet Co',
|
|
382
|
+
role: 'Senior Backend Engineer',
|
|
383
|
+
url: 'https://jobs.example.com/quiet-backend-130',
|
|
384
|
+
submittedAt: old,
|
|
385
|
+
}));
|
|
386
|
+
|
|
387
|
+
const check = cli(env, ['ledger', 'check', '--stdin'], {
|
|
388
|
+
id: 'quiet-product-role',
|
|
389
|
+
company: 'Quiet Co',
|
|
390
|
+
role: 'Staff Product Engineer',
|
|
391
|
+
url: 'https://jobs.example.com/quiet-product-131',
|
|
392
|
+
employerJobId: 'example:quiet-product-131',
|
|
393
|
+
});
|
|
394
|
+
assert.equal(check.companyReapply.decision, 'eligible-after-cooldown');
|
|
395
|
+
assert.equal(check.companyReapply.eligible, true);
|
|
396
|
+
|
|
397
|
+
const result = cli(env, ['ledger', 'add', '--stdin'], submission(131, undefined, {
|
|
398
|
+
id: 'quiet-product-role',
|
|
399
|
+
company: 'Quiet Co',
|
|
400
|
+
role: 'Staff Product Engineer',
|
|
401
|
+
url: 'https://jobs.example.com/quiet-product-131',
|
|
402
|
+
employerJobId: 'example:quiet-product-131',
|
|
403
|
+
submittedAt: new Date().toISOString(),
|
|
404
|
+
}));
|
|
405
|
+
assert.equal(result.recorded, 'quiet-product-role');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test('requires the requisition override for same-role aliases', async (t) => {
|
|
409
|
+
const { directory, env } = await fixture(t, 'same-role-alias');
|
|
410
|
+
const recent = new Date(Date.now() - (2 * 86_400_000)).toISOString();
|
|
411
|
+
cli(env, ['ledger', 'add', '--stdin'], submission(140, undefined, {
|
|
412
|
+
id: 'alias-backend-original',
|
|
413
|
+
company: 'Alias Co',
|
|
414
|
+
role: 'Senior Backend Developer',
|
|
415
|
+
url: 'https://jobs.example.com/alias-backend-140',
|
|
416
|
+
employerJobId: 'alias:140',
|
|
417
|
+
submittedAt: recent,
|
|
418
|
+
}));
|
|
419
|
+
const candidate = submission(141, undefined, {
|
|
420
|
+
id: 'alias-backend-new',
|
|
421
|
+
company: 'Alias Co',
|
|
422
|
+
role: 'Sr Backend Engineer',
|
|
423
|
+
url: 'https://jobs.example.com/alias-backend-141',
|
|
424
|
+
employerJobId: 'alias:141',
|
|
425
|
+
submittedAt: new Date().toISOString(),
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const blocked = cliFailure(env, ['ledger', 'add', '--stdin'], candidate);
|
|
429
|
+
assert.equal(blocked.status, 1);
|
|
430
|
+
assert.match(blocked.stderr, /NEW REQUISITION CONFIRMED/);
|
|
431
|
+
const result = cli(env, ['ledger', 'add', '--stdin'], {
|
|
432
|
+
...candidate,
|
|
433
|
+
duplicateOverride: 'NEW REQUISITION CONFIRMED',
|
|
434
|
+
});
|
|
435
|
+
assert.equal(result.recorded, 'alias-backend-new');
|
|
436
|
+
const stored = (await readFile(join(directory, 'applications.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
437
|
+
assert.equal('reapplicationApproval' in stored[1], false);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('never permits hard duplicates even when both override phrases are supplied', async (t) => {
|
|
441
|
+
const { env } = await fixture(t, 'hard-duplicates');
|
|
442
|
+
const original = submission(150, undefined, {
|
|
443
|
+
id: 'hard-original',
|
|
444
|
+
company: 'Hard Duplicate Co',
|
|
445
|
+
role: 'Senior Backend Engineer',
|
|
446
|
+
url: 'https://jobs.example.com/hard-original',
|
|
447
|
+
employerJobId: 'hard:150',
|
|
448
|
+
submittedAt: new Date(Date.now() - (30 * 86_400_000)).toISOString(),
|
|
449
|
+
});
|
|
450
|
+
cli(env, ['ledger', 'add', '--stdin'], original);
|
|
451
|
+
const overrides = {
|
|
452
|
+
duplicateOverride: 'NEW REQUISITION CONFIRMED',
|
|
453
|
+
companyReapplyOverride: 'CANDIDATE APPROVED EARLY REAPPLICATION',
|
|
454
|
+
submittedAt: new Date().toISOString(),
|
|
455
|
+
};
|
|
456
|
+
const candidates = [
|
|
457
|
+
{ ...submission(151, undefined), ...overrides, id: original.id, company: original.company },
|
|
458
|
+
{ ...submission(152, undefined), ...overrides, company: original.company, url: original.url },
|
|
459
|
+
{ ...submission(153, undefined), ...overrides, company: original.company, employerJobId: original.employerJobId },
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
const messages = [/matching ledger ID/, /matching canonical URL/, /matching employer job ID or requisition/];
|
|
463
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
464
|
+
const blocked = cliFailure(env, ['ledger', 'add', '--stdin'], candidate);
|
|
465
|
+
assert.equal(blocked.status, 1);
|
|
466
|
+
assert.match(blocked.stderr, /Hard duplicate blocked/);
|
|
467
|
+
assert.match(blocked.stderr, messages[index]);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test('serializes concurrent company writes so the cooldown cannot be bypassed', async (t) => {
|
|
472
|
+
const { directory, env } = await fixture(t, 'concurrent-company-cooldown');
|
|
473
|
+
cli(env, ['ledger', 'add', '--stdin'], submission(160, undefined, {
|
|
474
|
+
id: 'concurrent-backend-role',
|
|
475
|
+
company: 'Concurrent Co',
|
|
476
|
+
role: 'Senior Backend Engineer',
|
|
477
|
+
url: 'https://jobs.example.com/concurrent-backend-160',
|
|
478
|
+
submittedAt: new Date(Date.now() - (16 * 86_400_000)).toISOString(),
|
|
479
|
+
}));
|
|
480
|
+
const first = submission(161, undefined, {
|
|
481
|
+
id: 'concurrent-product-role',
|
|
482
|
+
company: 'Concurrent Co',
|
|
483
|
+
role: 'Staff Product Engineer',
|
|
484
|
+
url: 'https://jobs.example.com/concurrent-product-161',
|
|
485
|
+
submittedAt: new Date().toISOString(),
|
|
486
|
+
});
|
|
487
|
+
const second = submission(162, undefined, {
|
|
488
|
+
id: 'concurrent-data-role',
|
|
489
|
+
company: 'Concurrent Co',
|
|
490
|
+
role: 'Principal Data Engineer',
|
|
491
|
+
url: 'https://jobs.example.com/concurrent-data-162',
|
|
492
|
+
submittedAt: new Date().toISOString(),
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const results = await Promise.all([
|
|
496
|
+
cliConcurrent(env, ['ledger', 'add', '--stdin'], first),
|
|
497
|
+
cliConcurrent(env, ['ledger', 'add', '--stdin'], second),
|
|
498
|
+
]);
|
|
499
|
+
assert.deepEqual(results.map((result) => result.status).sort(), [0, 1]);
|
|
500
|
+
assert.match(results.find((result) => result.status === 1).stderr, /reapplication cooldown is active/i);
|
|
501
|
+
const stored = (await readFile(join(directory, 'applications.ndjson'), 'utf8')).trim().split('\n').map(JSON.parse);
|
|
502
|
+
assert.equal(stored.length, 2);
|
|
116
503
|
});
|
|
117
504
|
|
|
118
505
|
test('does not complete a round from blocked or partially filled applications', async (t) => {
|
|
@@ -156,7 +543,7 @@ test('replays and prioritizes an owner-only attention queue', async (t) => {
|
|
|
156
543
|
|
|
157
544
|
const before = cli(env, ['attention', 'list']);
|
|
158
545
|
assert.deepEqual(before.items.map((item) => item.id), [captcha.id, judgment.id]);
|
|
159
|
-
assert.equal((await stat(join(directory, 'attention.ndjson'))).mode & 0o777, 0o600);
|
|
546
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'attention.ndjson'))).mode & 0o777, 0o600);
|
|
160
547
|
|
|
161
548
|
cli(env, ['attention', 'resolve', '--stdin'], { id: captcha.id });
|
|
162
549
|
const after = cli(env, ['attention', 'list']);
|
|
@@ -185,7 +572,7 @@ test('qualifies only reproducible general-purpose friction for a public PR', asy
|
|
|
185
572
|
const listed = cli(env, ['friction', 'list']);
|
|
186
573
|
assert.equal(listed.items.length, 2);
|
|
187
574
|
assert.deepEqual(listed.prCandidates.map((item) => item.id), [general.id]);
|
|
188
|
-
assert.equal((await stat(join(directory, 'friction.ndjson'))).mode & 0o777, 0o600);
|
|
575
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'friction.ndjson'))).mode & 0o777, 0o600);
|
|
189
576
|
assert.equal((await readFile(join(directory, 'friction.ndjson'), 'utf8')).includes('candidate@example.com'), false);
|
|
190
577
|
});
|
|
191
578
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-application-agent",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "A privacy-first Agent Skill and CLI for evidence-based job discovery, application completion, and outcome tracking.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,13 +34,17 @@
|
|
|
34
34
|
"provenance": true
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "node --test
|
|
37
|
+
"test": "node --test",
|
|
38
|
+
"check:native-artifacts": "node scripts/ci/validate-native-artifacts.mjs",
|
|
38
39
|
"privacy-audit": "node --test job-application-agent/tests/privacy-audit.test.mjs",
|
|
39
40
|
"smoke:package": "node scripts/smoke-package.mjs",
|
|
40
|
-
"check": "node --check job-application-agent/scripts/job-application.mjs && node --check job-application-agent/scripts/secret-store.mjs && node --check job-application-agent/scripts/telemetry-client.mjs && node --check job-application-agent/scripts/telemetry-schema.mjs && node --check telemetry-worker/src/worker.mjs && node --check telemetry-worker/src/public-stats.mjs && node --check telemetry-worker/public/dashboard.js && node --check installer/src/cli.mjs && node --check installer/src/installer.mjs && node --check installer/src/runner.mjs && node --check installer/src/scheduler.mjs && node --check bin/job-application-agent.mjs",
|
|
41
|
+
"check": "node --check job-application-agent/scripts/job-application.mjs && node --check job-application-agent/scripts/secret-store.mjs && node --check job-application-agent/scripts/source-community-client.mjs && node --check job-application-agent/scripts/source-community-schema.mjs && node --check job-application-agent/scripts/telemetry-client.mjs && node --check job-application-agent/scripts/telemetry-schema.mjs && node --check telemetry-worker/src/worker.mjs && node --check telemetry-worker/src/public-stats.mjs && node --check telemetry-worker/public/dashboard.js && node --check installer/src/cli.mjs && node --check installer/src/installer.mjs && node --check installer/src/runner.mjs && node --check installer/src/scheduler.mjs && node --check scripts/ci/classify-paths.mjs && node --check scripts/ci/validate-native-artifacts.mjs && node --check bin/job-application-agent.mjs",
|
|
41
42
|
"prepack": "npm run check && npm test && npm run privacy-audit"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"yaml": "2.9.0"
|
|
45
49
|
}
|
|
46
50
|
}
|