job-application-agent 3.1.1 → 3.2.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.
@@ -2,8 +2,10 @@ import { chmod, mkdir, readFile, readdir, rename, writeFile } from 'node:fs/prom
2
2
  import { join } from 'node:path';
3
3
 
4
4
  import { createTelemetryEnvelope, jobIdentity, validateEvent } from './telemetry-schema.mjs';
5
+ import { SKILL_VERSION } from './version.mjs';
6
+
7
+ export { SKILL_VERSION };
5
8
 
6
- export const SKILL_VERSION = '1.2.1';
7
9
  export const DEFAULT_TELEMETRY_ENDPOINT = process.env.JOB_APPLICATION_AGENT_TELEMETRY_URL ?? 'https://job-application-agent-telemetry.varora1406.workers.dev';
8
10
  export const TELEMETRY_NOTICE = 'Anonymous usage analytics are enabled by default. They include structured job and workflow metrics, but never your identity, resume, prompts, form answers, browser data, or candidate profile. Run `telemetry disable` to opt out or `telemetry preview` to inspect an event.\n';
9
11
 
@@ -0,0 +1 @@
1
+ export const SKILL_VERSION = '3.2.0';
@@ -4,6 +4,7 @@ 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 { buildReview, commandCategory, durationBucket, migrateProfile, profileStatus, scoreJob, telemetryJobAssessed, validateLedgerEntry, validateProfile, validateSubmissionTelemetry } from '../scripts/job-application.mjs';
9
10
 
@@ -68,7 +69,7 @@ test('validates a candidate-defined target profile', () => {
68
69
  test('returns the canonical resume path for direct browser uploads', async (t) => {
69
70
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-resume-path-'));
70
71
  t.after(() => rm(directory, { recursive: true, force: true }));
71
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
72
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
72
73
  const resume = join(directory, 'resume.pdf');
73
74
  await writeFile(join(directory, 'telemetry.json'), JSON.stringify({ version: 1, enabled: false, disclosed: true, graceConsumed: true, installationEventPending: false }));
74
75
  await writeFile(resume, '%PDF-1.7\ncanonical resume fixture');
@@ -107,6 +108,7 @@ test('scores a matching role from candidate preferences', () => {
107
108
  assert.equal(result.autoEligible, true);
108
109
  assert.equal(result.mustHaveCoverage, 83);
109
110
  assert.ok(result.score >= 80);
111
+ assert.equal(scoreJob({ ...matchingJob, discoverySourceId: 'community-abcdef1234567890' }, target).decision, 'review');
110
112
  });
111
113
 
112
114
  test('applies posting, eligibility, work-mode, seniority and evidence gates before auto-submit', () => {
@@ -238,7 +240,7 @@ test('requires review at each ten confirmed submissions', () => {
238
240
  test('deduplicates ledger entries by normalized URL', async (t) => {
239
241
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-'));
240
242
  t.after(() => rm(directory, { recursive: true, force: true }));
241
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
243
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
242
244
  const entry = {
243
245
  id: 'example-role-1', company: 'Example', role: 'Senior Product Engineer', url: 'https://jobs.example.com/123?utm_source=x', source: 'company', score: 80, status: 'submitted', submittedAt: '2026-01-15T10:00:00Z', approval: 'STANDING AUTHORIZATION', answers: {},
244
246
  telemetry: { durationBucket: '5-15m', fieldsFilled: 14, shortAnswerCount: 2, resumeUploaded: true },
@@ -253,13 +255,13 @@ test('deduplicates ledger entries by normalized URL', async (t) => {
253
255
  }));
254
256
  assert.equal(relabelled.duplicate, true);
255
257
  assert.equal((await readFile(join(directory, 'applications.ndjson'), 'utf8')).includes('telemetry'), false);
256
- assert.equal((await stat(join(directory, 'applications.ndjson'))).mode & 0o777, 0o600);
258
+ if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'applications.ndjson'))).mode & 0o777, 0o600);
257
259
  });
258
260
 
259
261
  test('warns on same-company role matches and serializes concurrent duplicate submissions', async (t) => {
260
262
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-dedup-'));
261
263
  t.after(() => rm(directory, { recursive: true, force: true }));
262
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
264
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
263
265
  await writeFile(join(directory, 'telemetry.json'), JSON.stringify({ version: 1, enabled: false, disclosed: true, graceConsumed: true, installationEventPending: false }));
264
266
  const env = { ...process.env, JOB_APPLICATION_AGENT_STATE_DIR: directory };
265
267
  const base = {
@@ -283,7 +285,7 @@ test('warns on same-company role matches and serializes concurrent duplicate sub
283
285
  test('records structured outcomes idempotently without duplicate rows', async (t) => {
284
286
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-outcomes-'));
285
287
  t.after(() => rm(directory, { recursive: true, force: true }));
286
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
288
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
287
289
  await writeFile(join(directory, 'telemetry.json'), JSON.stringify({ version: 1, enabled: false, disclosed: true, graceConsumed: true, installationEventPending: false }));
288
290
  await writeFile(join(directory, 'applications.ndjson'), `${JSON.stringify({
289
291
  id: 'example-role-1', company: 'Example', role: 'Senior Product Engineer', url: 'https://jobs.example.com/123', source: 'company', score: 88,
@@ -306,7 +308,7 @@ test('records structured outcomes idempotently without duplicate rows', async (t
306
308
  test('records bounded interview quality and failure-point enrichment idempotently', async (t) => {
307
309
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-interview-quality-'));
308
310
  t.after(() => rm(directory, { recursive: true, force: true }));
309
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
311
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
310
312
  await writeFile(join(directory, 'telemetry.json'), JSON.stringify({ version: 1, enabled: false, disclosed: true, graceConsumed: true, installationEventPending: false }));
311
313
  await writeFile(join(directory, 'applications.ndjson'), `${JSON.stringify({
312
314
  id: 'example-role-1', company: 'Example', role: 'Staff Product Engineer', url: 'https://jobs.example.com/123', source: 'company', score: 91,
@@ -391,7 +393,7 @@ test('preserves interview quality after a later final outcome', () => {
391
393
  test('acknowledges a generated review only through the explicit CLI command', async (t) => {
392
394
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-review-'));
393
395
  t.after(() => rm(directory, { recursive: true, force: true }));
394
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
396
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
395
397
  await writeFile(join(directory, 'telemetry.json'), JSON.stringify({ version: 1, enabled: false, disclosed: true, graceConsumed: true, installationEventPending: false }));
396
398
  const entries = Array.from({ length: 10 }, (_, index) => ({
397
399
  id: `role-${index}`, company: `Company ${index}`, role: 'Senior Engineer', url: `https://jobs.example.com/${index}`,
@@ -414,6 +416,7 @@ test('maps commands and durations to bounded telemetry categories', () => {
414
416
  assert.equal(commandCategory(['ledger', 'add', '--stdin']), 'apply');
415
417
  assert.equal(commandCategory(['ledger', 'outcome', '--stdin']), 'outcome');
416
418
  assert.equal(commandCategory(['score', '--stdin']), 'assess');
419
+ assert.equal(commandCategory(['sources', 'list']), 'search');
417
420
  assert.equal(durationBucket(700), 'under-1s');
418
421
  assert.equal(durationBucket(70_000), '1-2m');
419
422
  assert.equal(durationBucket(2_000_000), '15m-plus');
@@ -436,11 +439,11 @@ test('builds a structured assessment event without description or candidate prof
436
439
  test('telemetry CLI controls are private and reset removes anonymous credentials', async (t) => {
437
440
  const directory = await mkdtemp(join(tmpdir(), 'public-job-agent-telemetry-'));
438
441
  t.after(() => rm(directory, { recursive: true, force: true }));
439
- const script = new URL('../scripts/job-application.mjs', import.meta.url).pathname;
442
+ const script = fileURLToPath(new URL('../scripts/job-application.mjs', import.meta.url));
440
443
  const env = { ...process.env, JOB_APPLICATION_AGENT_STATE_DIR: directory, JOB_APPLICATION_AGENT_TELEMETRY_URL: 'https://relay.invalid' };
441
444
  const disabled = JSON.parse(execFileSync(process.execPath, [script, 'telemetry', 'disable'], { env, encoding: 'utf8' }));
442
445
  assert.equal(disabled.enabled, false);
443
446
  const reset = JSON.parse(execFileSync(process.execPath, [script, 'telemetry', 'reset'], { env, encoding: 'utf8' }));
444
447
  assert.equal(reset.hasInstallationId, false);
445
- assert.equal((await stat(join(directory, 'telemetry.json'))).mode & 0o777, 0o600);
448
+ if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'telemetry.json'))).mode & 0o777, 0o600);
446
449
  });
@@ -3,6 +3,7 @@ import test from 'node:test';
3
3
 
4
4
  import { prepareTelemetryInput } from '../scripts/telemetry-client.mjs';
5
5
  import { validateEvent } from '../scripts/telemetry-schema.mjs';
6
+ import { createCommunityJobContributionEnvelope, normalizeCommunityJob, normalizeCommunitySource } from '../scripts/source-community-schema.mjs';
6
7
 
7
8
  const forbiddenProperties = [
8
9
  'name', 'email', 'phone', 'exactAddress', 'linkedin', 'github', 'portfolio', 'candidateLocation',
@@ -40,3 +41,41 @@ test('privacy audit strips the full query and fragment before job URL hashing',
40
41
  assert.equal(serialized.includes('secret'), false);
41
42
  assert.equal(serialized.includes('jobs.example.com/role/123'), false);
42
43
  });
44
+
45
+ test('privacy audit strips identity-bearing source parameters and rejects personal source metadata', () => {
46
+ const source = normalizeCommunitySource({
47
+ name: 'Example Engineering Board',
48
+ baseUrl: 'https://jobs.example.org/openings/engineering?email=candidate@example.com&token=secret#private',
49
+ kind: 'job-board',
50
+ regions: ['global'],
51
+ roleFamilies: ['engineering'],
52
+ requiresSession: false,
53
+ });
54
+ assert.equal(source.baseUrl, 'https://jobs.example.org/openings/engineering');
55
+ assert.equal(JSON.stringify(source).includes('candidate@example.com'), false);
56
+ assert.throws(() => normalizeCommunitySource({ ...source, name: 'candidate@example.com' }), /identity/i);
57
+ assert.throws(() => normalizeCommunitySource({ ...source, baseUrl: 'https://linkedin.com/in/candidate' }), /profile or personal/i);
58
+ });
59
+
60
+ test('privacy audit permits only sanitized public fields in community jobs', () => {
61
+ const job = normalizeCommunityJob({
62
+ url: 'https://jobs.ashbyhq.com/example/12345678-1234-4123-8123-123456789abc?email=candidate@example.com&token=secret#private',
63
+ company: 'Example',
64
+ role: 'Senior Product Engineer',
65
+ applicationChannel: 'ashby',
66
+ discoverySource: 'job-board',
67
+ });
68
+ const envelope = createCommunityJobContributionEnvelope({
69
+ installationId: '11111111-1111-4111-8111-111111111111',
70
+ token: 'anonymous-relay-token',
71
+ skillVersion: '3.2.0',
72
+ job,
73
+ });
74
+ const serialized = JSON.stringify(envelope.job);
75
+ assert.equal(serialized.includes('candidate@example.com'), false);
76
+ assert.equal(serialized.includes('secret'), false);
77
+ assert.deepEqual(Object.keys(envelope.job).sort(), ['applicationChannel', 'company', 'discoverySource', 'providerUrl', 'role', 'url']);
78
+ assert.throws(() => normalizeCommunityJob({ ...job, answers: { private: true } }), /unknown/i);
79
+ assert.throws(() => normalizeCommunityJob({ ...job, company: 'candidate@example.com' }), /identity/i);
80
+ assert.throws(() => normalizeCommunityJob({ ...job, url: 'https://linkedin.com/in/candidate' }), /personal/i);
81
+ });
@@ -0,0 +1,351 @@
1
+ import assert from 'node:assert/strict';
2
+ import { 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 { SourceCommunityClient } from '../scripts/source-community-client.mjs';
8
+
9
+ const source = {
10
+ name: 'Example Engineering Board',
11
+ baseUrl: 'https://jobs.example.org/openings/engineering?ref=candidate@example.com#openings',
12
+ kind: 'job-board',
13
+ regions: ['global', 'remote'],
14
+ roleFamilies: ['engineering'],
15
+ requiresSession: false,
16
+ };
17
+
18
+ const job = {
19
+ url: 'https://jobs.ashbyhq.com/example/12345678-1234-4123-8123-123456789abc?ref=candidate@example.com#apply',
20
+ company: 'Example',
21
+ role: 'Senior Product Engineer',
22
+ applicationChannel: 'ashby',
23
+ discoverySource: 'job-board',
24
+ };
25
+
26
+ function relay() {
27
+ const requests = [];
28
+ const community = [{
29
+ sourceId: 'community-abcdef1234567890',
30
+ name: 'Example Engineering Board',
31
+ baseUrl: 'https://jobs.example.org/openings/engineering',
32
+ kind: 'job-board',
33
+ regions: ['global', 'remote'],
34
+ roleFamilies: ['engineering'],
35
+ requiresSession: false,
36
+ registryStatus: 'community-reviewed',
37
+ contributionCount: 2,
38
+ }];
39
+ const jobs = [{
40
+ jobId: 'community-job-abcdef1234567890',
41
+ url: 'https://jobs.ashbyhq.com/example/12345678-1234-4123-8123-123456789abc',
42
+ company: 'Example',
43
+ role: 'Senior Product Engineer',
44
+ applicationChannel: 'ashby',
45
+ discoverySource: 'job-board',
46
+ providerUrl: 'https://jobs.ashbyhq.com/example',
47
+ firstSeenAt: '2026-08-24T00:00:00.000Z',
48
+ lastSeenAt: '2026-08-24T00:00:00.000Z',
49
+ contributionCount: 2,
50
+ }];
51
+ const fetch = async (url, options = {}) => {
52
+ requests.push({ url, options, body: options.body ? JSON.parse(options.body) : null });
53
+ if (url.endsWith('/v1/install')) return Response.json({ installationId: '11111111-1111-4111-8111-111111111111', token: 'source-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
54
+ if (url.endsWith('/v1/sources') && options.method === 'POST') return Response.json({ accepted: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 }, { status: 202 });
55
+ if (url.endsWith('/v1/jobs') && options.method === 'POST') return Response.json({ accepted: true, jobId: 'community-job-abcdef1234567890', publicationStatus: 'pending', contributionCount: 2 }, { status: 202 });
56
+ if (url.includes('/v1/jobs')) return Response.json({ version: 1, jobs, nextCursor: 'next-page' });
57
+ return Response.json({ version: 1, sources: community });
58
+ };
59
+ return { fetch, requests };
60
+ }
61
+
62
+ test('source sharing is enabled by default, disclosed, sanitized, and sent immediately', async (t) => {
63
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-default-'));
64
+ t.after(() => rm(directory, { recursive: true, force: true }));
65
+ const network = relay();
66
+ let notice = '';
67
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: (value) => { notice += value; } });
68
+
69
+ const result = await client.contribute(source);
70
+
71
+ assert.deepEqual(result, { shared: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 });
72
+ assert.match(notice, /community sharing is enabled by default/i);
73
+ assert.equal(network.requests.length, 2);
74
+ assert.equal(network.requests[1].url, 'https://relay.example.com/v1/sources');
75
+ assert.equal(network.requests[1].body.source.baseUrl, 'https://jobs.example.org/openings/engineering');
76
+ assert.equal(JSON.stringify(network.requests[1].body).includes('candidate@example.com'), false);
77
+ const stored = JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8'));
78
+ assert.equal(stored.enabled, true);
79
+ assert.equal(stored.disclosed, true);
80
+ assert.equal(stored.jobSharingDisclosed, true);
81
+ if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'source-sharing.json'))).mode & 0o777, 0o600);
82
+ });
83
+
84
+ test('existing source-sharing users receive one command to opt out before job backfill', async (t) => {
85
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-upgrade-disclosure-'));
86
+ t.after(() => rm(directory, { recursive: true, force: true }));
87
+ await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
88
+ version: 1,
89
+ enabled: true,
90
+ disclosed: true,
91
+ installationId: '11111111-1111-4111-8111-111111111111',
92
+ token: 'source-token',
93
+ tokenExpiresAt: '2099-01-01T00:00:00.000Z',
94
+ }));
95
+ const network = relay();
96
+ let notice = '';
97
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: (value) => { notice += value; } });
98
+
99
+ assert.deepEqual(await client.contributeJob(job), { shared: false, reason: 'grace' });
100
+ assert.match(notice, /confirmed public job links/i);
101
+ assert.equal(JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8')).jobSharingDisclosed, true);
102
+ assert.equal(network.requests.length, 0);
103
+ notice = '';
104
+ assert.equal((await client.contributeJob(job)).shared, true);
105
+ assert.equal(notice, '');
106
+ });
107
+
108
+ test('an interrupted job-sharing disclosure still preserves the grace command', async (t) => {
109
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-job-grace-recovery-'));
110
+ t.after(() => rm(directory, { recursive: true, force: true }));
111
+ await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
112
+ version: 1,
113
+ enabled: true,
114
+ disclosed: true,
115
+ jobSharingDisclosed: true,
116
+ jobSharingGraceConsumed: false,
117
+ installationId: null,
118
+ token: null,
119
+ tokenExpiresAt: null,
120
+ }));
121
+ const network = relay();
122
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
123
+
124
+ assert.deepEqual(await client.contributeJob(job), { shared: false, reason: 'grace' });
125
+ assert.equal(network.requests.length, 0);
126
+ assert.equal((await client.contributeJob(job)).shared, true);
127
+ });
128
+
129
+ test('historical applications without a source config receive the disclosure grace command', async (t) => {
130
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-historical-no-config-'));
131
+ t.after(() => rm(directory, { recursive: true, force: true }));
132
+ await writeFile(join(directory, 'applications.ndjson'), '{"historical":true}\n');
133
+ const network = relay();
134
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
135
+
136
+ assert.deepEqual(await client.contributeJob(job), { shared: false, reason: 'grace' });
137
+ assert.equal(network.requests.length, 0);
138
+ });
139
+
140
+ test('confirmed jobs are sanitized, shared anonymously, and public listings are validated', async (t) => {
141
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-job-'));
142
+ t.after(() => rm(directory, { recursive: true, force: true }));
143
+ const network = relay();
144
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
145
+
146
+ const contributed = await client.contributeJob(job);
147
+ assert.deepEqual(contributed, { shared: true, jobId: 'community-job-abcdef1234567890', publicationStatus: 'pending', contributionCount: 2 });
148
+ const posted = network.requests.find((request) => request.url.endsWith('/v1/jobs') && request.options.method === 'POST');
149
+ assert.equal(posted.body.job.url, 'https://jobs.ashbyhq.com/example/12345678-1234-4123-8123-123456789abc');
150
+ assert.equal(posted.body.job.providerUrl, 'https://jobs.ashbyhq.com/example');
151
+ assert.equal(JSON.stringify(posted.body).includes('candidate@example.com'), false);
152
+
153
+ const listed = await client.listJobs({ limit: 25, cursor: 'current-page' });
154
+ assert.equal(listed.jobs[0].jobId, 'community-job-abcdef1234567890');
155
+ assert.equal(listed.jobs[0].contributionCount, 2);
156
+ assert.equal(listed.nextCursor, 'next-page');
157
+ assert.equal(network.requests.at(-1).url, 'https://relay.example.com/v1/jobs?limit=25&cursor=current-page');
158
+ const requestsBeforeInvalidInput = network.requests.length;
159
+ await assert.rejects(() => client.listJobs({ limit: 0 }), /between 1 and 100/i);
160
+ assert.equal(network.requests.length, requestsBeforeInvalidInput);
161
+ });
162
+
163
+ test('community source listing validates the public response before reuse', async (t) => {
164
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-list-'));
165
+ t.after(() => rm(directory, { recursive: true, force: true }));
166
+ const network = relay();
167
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
168
+ const [listed] = await client.list();
169
+ assert.equal(listed.sourceId, 'community-abcdef1234567890');
170
+ assert.equal(listed.baseUrl, 'https://jobs.example.org/openings/engineering');
171
+ assert.equal(listed.contributionCount, 2);
172
+ assert.equal(listed.registryStatus, 'community-reviewed');
173
+ });
174
+
175
+ test('source sharing can be disabled independently and never blocks local collection', async (t) => {
176
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-disabled-'));
177
+ t.after(() => rm(directory, { recursive: true, force: true }));
178
+ const network = relay();
179
+ let notice = '';
180
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: (value) => { notice += value; } });
181
+
182
+ assert.equal((await client.configure('disable')).enabled, false);
183
+ assert.deepEqual(await client.contribute(source), { shared: false, reason: 'disabled' });
184
+ assert.deepEqual(await client.contributeJob(job), { shared: false, reason: 'disabled' });
185
+ assert.equal(network.requests.length, 0);
186
+ assert.equal((await client.configure('enable')).enabled, true);
187
+ assert.match(notice, /confirmed public job links/i);
188
+ assert.equal((await client.contribute(source)).shared, true);
189
+ });
190
+
191
+ test('source sharing rejects personal and one-off job URLs before network transmission', async (t) => {
192
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-reject-'));
193
+ t.after(() => rm(directory, { recursive: true, force: true }));
194
+ const network = relay();
195
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
196
+
197
+ await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://linkedin.com/in/some-person' }), /profile or personal/i);
198
+ await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://jobs.example.org/jobs/123456' }), /repeatable discovery surface/i);
199
+ await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://jobs.example.org/candidate@example.com/openings' }), /identity-like content/i);
200
+ await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://127.0.0.1/jobs' }), /public internet hostname/i);
201
+ await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://careers.internal.local/jobs' }), /public internet hostname/i);
202
+ assert.equal(network.requests.length, 0);
203
+ });
204
+
205
+ test('network failures are best effort and return an unavailable result', async (t) => {
206
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-offline-'));
207
+ t.after(() => rm(directory, { recursive: true, force: true }));
208
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: async () => { throw new Error('offline'); }, stderr: () => {} });
209
+ assert.deepEqual(await client.contribute(source), { shared: false, reason: 'unavailable' });
210
+ });
211
+
212
+ test('a failed contribution does not suppress a healthy community registry read', async (t) => {
213
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-independent-read-'));
214
+ t.after(() => rm(directory, { recursive: true, force: true }));
215
+ const network = relay();
216
+ const fetch = async (url, options = {}) => {
217
+ if (url.endsWith('/v1/sources') && options.method === 'POST') return Response.json({ error: 'rate_limited' }, { status: 429 });
218
+ return network.fetch(url, options);
219
+ };
220
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
221
+
222
+ assert.deepEqual(await client.contribute(source), { shared: false, reason: 'unavailable' });
223
+ const [listed] = await client.list();
224
+ assert.equal(listed.sourceId, 'community-abcdef1234567890');
225
+ });
226
+
227
+ test('concurrent opt-out is preserved and rechecked before source transmission', async (t) => {
228
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-opt-out-race-'));
229
+ t.after(() => rm(directory, { recursive: true, force: true }));
230
+ await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
231
+ version: 1,
232
+ enabled: true,
233
+ disclosed: true,
234
+ installationId: null,
235
+ token: null,
236
+ tokenExpiresAt: null,
237
+ }));
238
+ let releaseInstall;
239
+ let installStarted;
240
+ const installGate = new Promise((resolve) => { releaseInstall = resolve; });
241
+ const installObserved = new Promise((resolve) => { installStarted = resolve; });
242
+ const sourcePosts = [];
243
+ const fetch = async (url, options = {}) => {
244
+ if (url.endsWith('/v1/install')) {
245
+ installStarted();
246
+ await installGate;
247
+ return Response.json({ installationId: '11111111-1111-4111-8111-111111111111', token: 'source-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
248
+ }
249
+ sourcePosts.push({ url, options });
250
+ return Response.json({ accepted: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 }, { status: 202 });
251
+ };
252
+ const contributor = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
253
+ const settings = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
254
+
255
+ const contribution = contributor.contribute(source);
256
+ await installObserved;
257
+ assert.equal((await settings.configure('disable')).enabled, false);
258
+ releaseInstall();
259
+
260
+ assert.deepEqual(await contribution, { shared: false, reason: 'disabled' });
261
+ assert.equal(sourcePosts.length, 0);
262
+ assert.equal(JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8')).enabled, false);
263
+ });
264
+
265
+ test('concurrent reset is not undone by an in-flight credential refresh', async (t) => {
266
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-reset-race-'));
267
+ t.after(() => rm(directory, { recursive: true, force: true }));
268
+ await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
269
+ version: 1,
270
+ enabled: true,
271
+ disclosed: true,
272
+ installationId: null,
273
+ token: null,
274
+ tokenExpiresAt: null,
275
+ }));
276
+ let releaseInstall;
277
+ let installStarted;
278
+ const installGate = new Promise((resolve) => { releaseInstall = resolve; });
279
+ const installObserved = new Promise((resolve) => { installStarted = resolve; });
280
+ const fetch = async (url) => {
281
+ if (!url.endsWith('/v1/install')) throw new Error('source contribution must remain disabled');
282
+ installStarted();
283
+ await installGate;
284
+ return Response.json({ installationId: '11111111-1111-4111-8111-111111111111', token: 'source-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
285
+ };
286
+ const contributor = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
287
+ const settings = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
288
+
289
+ const contribution = contributor.contribute(source);
290
+ await installObserved;
291
+ assert.deepEqual(await settings.configure('reset'), {
292
+ enabled: false,
293
+ disclosed: true,
294
+ jobSharingDisclosed: false,
295
+ hasInstallationId: false,
296
+ endpoint: 'https://relay.example.com',
297
+ schemaVersion: 1,
298
+ });
299
+ releaseInstall();
300
+
301
+ assert.deepEqual(await contribution, { shared: false, reason: 'disabled' });
302
+ const stored = JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8'));
303
+ assert.equal(stored.enabled, false);
304
+ assert.equal(stored.installationId, null);
305
+ assert.equal(stored.token, null);
306
+ assert.equal(stored.tokenExpiresAt, null);
307
+ });
308
+
309
+ test('recovers a source-sharing lock whose owner process no longer exists', async (t) => {
310
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-stale-lock-'));
311
+ t.after(() => rm(directory, { recursive: true, force: true }));
312
+ await writeFile(join(directory, '.source-sharing.lock'), '99999999\n');
313
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: async () => { throw new Error('network must not be used'); }, stderr: () => {} });
314
+
315
+ assert.equal((await client.configure('disable')).enabled, false);
316
+ await assert.rejects(() => stat(join(directory, '.source-sharing.lock')), { code: 'ENOENT' });
317
+ });
318
+
319
+ test('invalid stored relay credentials are replaced once and persisted', async (t) => {
320
+ const directory = await mkdtemp(join(tmpdir(), 'source-community-credential-recovery-'));
321
+ t.after(() => rm(directory, { recursive: true, force: true }));
322
+ const oldInstallationId = '11111111-1111-4111-8111-111111111111';
323
+ const newInstallationId = '22222222-2222-4222-8222-222222222222';
324
+ await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
325
+ version: 1,
326
+ enabled: true,
327
+ disclosed: true,
328
+ installationId: oldInstallationId,
329
+ token: 'invalid-old-token',
330
+ tokenExpiresAt: '2099-01-01T00:00:00.000Z',
331
+ }));
332
+ const requests = [];
333
+ const fetch = async (url, options = {}) => {
334
+ const body = options.body ? JSON.parse(options.body) : null;
335
+ requests.push({ url, body });
336
+ if (url.endsWith('/v1/sources') && body.installationId === oldInstallationId) return Response.json({ error: 'invalid_token' }, { status: 401 });
337
+ if (url.endsWith('/v1/install') && body.installationId === oldInstallationId) return Response.json({ error: 'invalid_token' }, { status: 401 });
338
+ if (url.endsWith('/v1/install')) return Response.json({ installationId: newInstallationId, token: 'new-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
339
+ return Response.json({ accepted: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 }, { status: 202 });
340
+ };
341
+ const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
342
+
343
+ const result = await client.contribute(source);
344
+
345
+ assert.equal(result.shared, true);
346
+ assert.ok(requests.some((request) => request.url.endsWith('/v1/install') && request.body.installationId === oldInstallationId));
347
+ assert.ok(requests.some((request) => request.url.endsWith('/v1/install') && Object.keys(request.body).length === 0));
348
+ const stored = JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8'));
349
+ assert.equal(stored.installationId, newInstallationId);
350
+ assert.equal(stored.token, 'new-token');
351
+ });