trackly-cli 0.16.0 → 0.17.1
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/bin/trackly +2 -2
- package/contracts/trackly-apply-contract-history.json +9 -0
- package/contracts/trackly-apply-tools.json +16 -3
- package/lib/agent.js +1 -1
- package/lib/formatters.js +106 -2
- package/mcp/apply-tools.js +94 -22
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/trackly-apply/SKILL.md +18 -9
- package/skills/trackly-apply/references/access-probe.md +68 -0
- package/skills/trackly-apply/references/answer-resolution.md +25 -0
- package/skills/trackly-apply/references/application-writing.md +20 -2
- package/skills/trackly-apply/references/browser-upload.md +26 -8
- package/skills/trackly-apply/references/form-integrity.md +22 -0
- package/skills/trackly-apply/references/operational-checkpoints.md +319 -0
- package/skills/trackly-apply/references/performance-telemetry.md +61 -0
- package/skills/trackly-apply/references/review-handoff.md +21 -0
- package/skills/trackly-apply/scripts/validate-phase-checkpoint.js +1068 -0
package/bin/trackly
CHANGED
|
@@ -15,7 +15,7 @@ const {
|
|
|
15
15
|
removeCredentials,
|
|
16
16
|
saveConfig,
|
|
17
17
|
} = require('../lib/client');
|
|
18
|
-
const { color, isJSON, outputJobs, outputCompanies, outputStats, outputContacts, outputReferralCampaign, outputNetworkBrief, outputCompanyBrief, outputCompanyWorkspace, outputError } = require('../lib/formatters');
|
|
18
|
+
const { color, isJSON, jobDateDetailLines, outputJobs, outputCompanies, outputStats, outputContacts, outputReferralCampaign, outputNetworkBrief, outputCompanyBrief, outputCompanyWorkspace, outputError } = require('../lib/formatters');
|
|
19
19
|
const http = require('http');
|
|
20
20
|
const crypto = require('crypto');
|
|
21
21
|
const { spawnSync } = require('child_process');
|
|
@@ -665,7 +665,7 @@ async function cmdJob(args) {
|
|
|
665
665
|
console.log(`Company: ${color('blue', job.companyName || (job.company && job.company.name) || '')}`);
|
|
666
666
|
if (job.location) console.log(`Location: ${job.location}`);
|
|
667
667
|
if (job.jobUrl) console.log(`URL: ${color('dim', job.jobUrl)}`);
|
|
668
|
-
|
|
668
|
+
for (const line of jobDateDetailLines(job)) console.log(line);
|
|
669
669
|
// Field presence, not truthiness: a returned null/'unknown' status is a real
|
|
670
670
|
// "Not mentioned" answer from the backend. Only an older backend that omits
|
|
671
671
|
// the key entirely should render no Sponsorship line at all.
|
|
@@ -23,5 +23,14 @@
|
|
|
23
23
|
},
|
|
24
24
|
"3.7.3": {
|
|
25
25
|
"contractSha256": "d37e7ec0a17d3dba399f12dd7281c51536ca92bbb21125ca09288d1678f34c14"
|
|
26
|
+
},
|
|
27
|
+
"3.7.4": {
|
|
28
|
+
"contractSha256": "b9da194f5d2a02afda339b271a2c5ed8e78b538b3d95ba8df54b14408bf49d8f"
|
|
29
|
+
},
|
|
30
|
+
"3.7.5": {
|
|
31
|
+
"contractSha256": "13997fd0496035bfa2db1d8ba9b52ff326f880e8bea3231a3c9a7fec26ddc1a7"
|
|
32
|
+
},
|
|
33
|
+
"3.7.6": {
|
|
34
|
+
"contractSha256": "259f4c0b52739956fced3936e36792b27960dcad820659994f3e9107453a5844"
|
|
26
35
|
}
|
|
27
36
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"contractVersion": "3.7.
|
|
2
|
+
"contractVersion": "3.7.6",
|
|
3
3
|
"constants": {
|
|
4
4
|
"applyExecutionMaxTarget": 20,
|
|
5
5
|
"applyScenarioCodes": ["browser_reclaim", "resume_upload", "resume_parser_recheck", "semantic_boolean_commit", "custom_select_commit", "multi_step_navigation", "free_text_voice", "required_error_sweep", "final_consent", "handoff_reclaim", "critical_contact_integrity", "manual_submit_boundary", "job_identity_match"],
|
|
6
6
|
"applyBrowserSurfaces": ["codex_in_app", "chrome_extension", "claude_in_chrome"],
|
|
7
7
|
"applyCheckpointActionCodes": ["answer/unknown", "auth/sign_in", "auth/account_creation", "auth/otp", "captcha/before_form", "captcha/at_submit", "artifact/upload_required", "legal/decision_required", "consent/decision_required", "review/manual_submit", "trust/origin_mismatch", "observability/unverifiable_state"],
|
|
8
|
+
"applyCheckpointContinuationByAction": {"answer/unknown": true, "auth/sign_in": true, "auth/account_creation": true, "auth/otp": true, "captcha/before_form": true, "captcha/at_submit": false, "artifact/upload_required": true, "legal/decision_required": true, "consent/decision_required": true, "review/manual_submit": false, "trust/origin_mismatch": false, "observability/unverifiable_state": false},
|
|
9
|
+
"applyCheckpointLifecycleByAction": {"answer/unknown": "needs_input", "auth/sign_in": "needs_input", "auth/account_creation": "needs_input", "auth/otp": "needs_input", "captcha/before_form": "needs_input", "captcha/at_submit": "review_ready", "artifact/upload_required": "needs_input", "legal/decision_required": "needs_input", "consent/decision_required": "needs_input", "review/manual_submit": "review_ready", "trust/origin_mismatch": "blocked", "observability/unverifiable_state": "blocked"},
|
|
10
|
+
"applyCheckpointQuestionPacketByAction": {"answer/unknown": true, "auth/sign_in": false, "auth/account_creation": false, "auth/otp": false, "captcha/before_form": false, "captcha/at_submit": false, "artifact/upload_required": false, "legal/decision_required": true, "consent/decision_required": true, "review/manual_submit": false, "trust/origin_mismatch": false, "observability/unverifiable_state": false},
|
|
8
11
|
"applyAccessClassifications": ["accessible", "authentication_required", "account_creation_required", "otp_required", "captcha_before_form", "captcha_at_submit", "manual_only", "unknown_unobservable"],
|
|
9
12
|
"applyExecutionDispositionSources": ["live_probe"],
|
|
10
13
|
"applyProbeCleanupPreferences": ["never", "submitted_only", "submitted_and_probe_blockers"],
|
|
11
14
|
"applyExecutionStopReasonCodes": ["user_requested", "target_changed", "session_ended", "execution_restarted", "operator_stop"],
|
|
12
|
-
"applyBatchConflictCodes": ["state_changed", "lease_unavailable", "idempotency_key_reused", "fixed_batch_not_found", "execution_child_batch", "fixed_batch_not_active", "fixed_batch_revision_changed", "submission_in_progress", "attestation_dependencies_changed", "legacy_fixed_batch_active", "dedicated_recovery_required"],
|
|
15
|
+
"applyBatchConflictCodes": ["state_changed", "lease_unavailable", "idempotency_key_reused", "fixed_batch_not_found", "execution_child_batch", "fixed_batch_not_active", "fixed_batch_revision_changed", "submission_in_progress", "attestation_dependencies_changed", "legacy_fixed_batch_active", "dedicated_recovery_required", "recovery_migration_not_ready"],
|
|
13
16
|
"fixedApplyBatchCancelReasonCodes": ["user_requested_restart"],
|
|
14
17
|
"applyCheckpointPacketPhases": ["first_pass", "delta"],
|
|
15
18
|
"applySurfaceBindingReasons": ["initial_binding", "recovery_binding"],
|
|
@@ -24,10 +27,20 @@
|
|
|
24
27
|
"applyUploadFailureCodes": ["upload_capability_unavailable", "upload_stage_missing", "upload_stage_out_of_order", "upload_stage_duplicate", "control_ambiguous", "chooser_timeout", "chooser_open_without_commit", "file_missing", "file_expired", "set_files_failed", "filename_uncommitted", "unexpected_file_navigation", "parser_unsettled", "parser_field_regression"]
|
|
25
28
|
},
|
|
26
29
|
"toolInputInvariants": {
|
|
30
|
+
"trackly_checkpoint_apply_batch": {
|
|
31
|
+
"questionAndNonQuestionActionsMutuallyExclusiveForNewCheckpoints": true,
|
|
32
|
+
"exactReplayOfPreviouslyAcceptedPayloadPreserved": true,
|
|
33
|
+
"preFormAuthenticationWallsRejectedAfterExactReplayLookup": true
|
|
34
|
+
},
|
|
27
35
|
"trackly_get_apply_execution_snapshot": {
|
|
28
36
|
"officeProjectionMemberIdsMustExistInMemberIds": true
|
|
29
37
|
}
|
|
30
38
|
},
|
|
39
|
+
"schemaDigests": {
|
|
40
|
+
"applyCheckpointActionVariant": "db6b69924b791b3e50c4c7464519fa1a786c7b39ed86c3cc9482e18c211553d2",
|
|
41
|
+
"applyCheckpointActionSchema": "99baf25b27ae4bd41183bf47f8e1eb7522e1d471156f6090a784362d5b248e95",
|
|
42
|
+
"applyCheckpointSchema": "101c5d7606cb902e89e677773fa45fbbdc9b38de99b452d641806bac90004274"
|
|
43
|
+
},
|
|
31
44
|
"tools": {
|
|
32
45
|
"trackly_get_apply_queue": "{limit:z.number().int().min(1).max(100).optional(),cursor:z.string().min(1).max(2048).optional()}",
|
|
33
46
|
"trackly_get_application_profile": "{includeSensitive:z.boolean().optional(),provider:z.string().max(100).optional(),companyId:z.string().max(100).optional(),jurisdiction:iso3166Alpha2Schema.optional(),office:officeScopeSchema.optional()}",
|
|
@@ -51,7 +64,7 @@
|
|
|
51
64
|
"trackly_get_active_apply_batch": "{limit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),cursor:z.string().min(1).max(2048).optional(),actionLimit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),actionCursor:z.string().min(1).max(2048).optional()}",
|
|
52
65
|
"trackly_get_apply_batch": "{batchId:z.number().int().min(1),limit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),cursor:z.string().min(1).max(2048).optional(),actionLimit:z.number().int().min(1).max(APPLY_BATCH_MAX_MEMBERS).optional(),actionCursor:z.string().min(1).max(2048).optional()}",
|
|
53
66
|
"trackly_claim_apply_batch": "{batchId:z.number().int().min(1),expectedRevision:z.number().int().min(1),leaseOwner:z.string().min(1).max(1024),leaseToken:z.string().min(1).max(1024),leaseDurationMs:z.number().int().min(15000).max(300000)}",
|
|
54
|
-
"trackly_checkpoint_apply_batch": "{batchId:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),checkpoints:z.array(
|
|
67
|
+
"trackly_checkpoint_apply_batch": "{batchId:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),checkpoints:z.array(applyCheckpointSchema).min(1).max(APPLY_BATCH_MAX_CHECKPOINTS_PER_REQUEST)}",
|
|
55
68
|
"trackly_bind_apply_surface": "{batchId:z.number().int().min(1),memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(0),leaseToken:z.string().min(1).max(1024),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/),browserSurface:z.enum(APPLY_BROWSER_SURFACES),adapterCode:z.string().regex(SAFE_OBSERVATION_CODE),bindingReason:z.enum(APPLY_SURFACE_BINDING_REASONS),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
56
69
|
"trackly_record_apply_surface_evidence": "{batchId:z.number().int().min(1),memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/),browserSurface:z.enum(APPLY_BROWSER_SURFACES),adapterCode:z.string().regex(SAFE_OBSERVATION_CODE),ownershipState:z.enum(APPLY_SURFACE_OWNERSHIP_STATES),completeInventory:z.boolean(),evidenceType:z.enum(APPLY_SURFACE_EVIDENCE_TYPES),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
|
57
70
|
"trackly_record_apply_submission_evidence": "{batchId:z.number().int().min(1),memberId:z.number().int().min(1),runId:z.number().int().min(1),expectedMemberVersion:z.number().int().min(1),expectedInspectionEpoch:z.number().int().min(1),leaseToken:z.string().min(1).max(1024),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/),evidenceType:z.enum(APPLY_SUBMISSION_EVIDENCE_TYPES),evidenceSource:z.enum(APPLY_SUBMISSION_EVIDENCE_SOURCES),evidenceFingerprint:z.string().regex(/^[a-f0-9]{64}$/),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
|
package/lib/agent.js
CHANGED
|
@@ -8,7 +8,7 @@ const { spawnSync } = require('node:child_process');
|
|
|
8
8
|
const { apiRequest, downloadFile, getConfigPaths, hasAuth, maintenanceOutput } = require('./client');
|
|
9
9
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
10
10
|
const { contractVersion: MCP_CONTRACT_VERSION } = require('../contracts/trackly-apply-tools.json');
|
|
11
|
-
const SKILL_VERSION = '4.
|
|
11
|
+
const SKILL_VERSION = '4.7.1';
|
|
12
12
|
const CLI_USER_AGENT = `trackly-cli/${PACKAGE_VERSION}`;
|
|
13
13
|
const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
|
|
14
14
|
|
package/lib/formatters.js
CHANGED
|
@@ -44,6 +44,107 @@ function formatFundingLine(company) {
|
|
|
44
44
|
return parts.length > 0 ? parts.join(' | ') : null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function validCalendarDay(value) {
|
|
48
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
|
|
49
|
+
const parsed = new Date(`${value}T00:00:00.000Z`);
|
|
50
|
+
return Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value
|
|
51
|
+
? null
|
|
52
|
+
: value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isoCalendarDay(value) {
|
|
56
|
+
if (typeof value !== 'string' || value.trim() === '') return null;
|
|
57
|
+
const normalized = value.trim();
|
|
58
|
+
const sourceDay = validCalendarDay(normalized.slice(0, 10));
|
|
59
|
+
if (!sourceDay) return null;
|
|
60
|
+
const parsed = new Date(normalized);
|
|
61
|
+
return Number.isNaN(parsed.getTime()) ? null : sourceDay;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sourceCalendarDay(job, kind) {
|
|
65
|
+
const meta = kind === 'posted'
|
|
66
|
+
? job && job.sourceDateMeta && job.sourceDateMeta.original
|
|
67
|
+
: kind === 'reposted'
|
|
68
|
+
? job && job.sourceDateMeta && job.sourceDateMeta.repost
|
|
69
|
+
: null;
|
|
70
|
+
return meta && meta.precision === 'day'
|
|
71
|
+
? validCalendarDay(meta.calendarDay)
|
|
72
|
+
: null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveJobDateBundle(job = {}) {
|
|
76
|
+
const projectedKind = ['posted', 'reposted', 'found'].includes(job.displayDateKind)
|
|
77
|
+
? job.displayDateKind
|
|
78
|
+
: null;
|
|
79
|
+
const projectedDay = projectedKind
|
|
80
|
+
? sourceCalendarDay(job, projectedKind) || isoCalendarDay(job.displayDate)
|
|
81
|
+
: null;
|
|
82
|
+
const originalDay = sourceCalendarDay(job, 'posted') || isoCalendarDay(job.originalPostedAt);
|
|
83
|
+
const repostDay = sourceCalendarDay(job, 'reposted') || isoCalendarDay(job.repostedAt)
|
|
84
|
+
|| (projectedKind === 'reposted' ? projectedDay : null);
|
|
85
|
+
const legacyPostedDay = isoCalendarDay(job.postedAt || job.posted_at);
|
|
86
|
+
const foundDay = isoCalendarDay(job.firstSeenAt || job.first_seen_at);
|
|
87
|
+
const presentation = projectedKind && projectedDay
|
|
88
|
+
? {
|
|
89
|
+
label: { posted: 'Posted', reposted: 'Reposted', found: 'Found' }[projectedKind],
|
|
90
|
+
value: projectedDay,
|
|
91
|
+
}
|
|
92
|
+
: legacyPostedDay
|
|
93
|
+
? { label: 'Posted', value: legacyPostedDay }
|
|
94
|
+
: { label: 'Found', value: foundDay || 'unknown' };
|
|
95
|
+
return {
|
|
96
|
+
projectedKind,
|
|
97
|
+
projectedDay,
|
|
98
|
+
originalDay,
|
|
99
|
+
repostDay,
|
|
100
|
+
legacyPostedDay,
|
|
101
|
+
foundDay,
|
|
102
|
+
presentation,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function jobDatePresentation(job = {}) {
|
|
107
|
+
return resolveJobDateBundle(job).presentation;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function jobDateDetailLines(job = {}) {
|
|
111
|
+
const lines = [];
|
|
112
|
+
const {
|
|
113
|
+
projectedKind,
|
|
114
|
+
projectedDay,
|
|
115
|
+
originalDay,
|
|
116
|
+
repostDay,
|
|
117
|
+
legacyPostedDay,
|
|
118
|
+
foundDay,
|
|
119
|
+
presentation,
|
|
120
|
+
} = resolveJobDateBundle(job);
|
|
121
|
+
if (originalDay) lines.push(`Posted: ${originalDay}`);
|
|
122
|
+
else if (repostDay && legacyPostedDay) lines.push(`Posted: ${legacyPostedDay}`);
|
|
123
|
+
else if (repostDay) lines.push('Original posting date unavailable');
|
|
124
|
+
else if (projectedKind === 'posted' && projectedDay) lines.push(`Posted: ${projectedDay}`);
|
|
125
|
+
else if ((!projectedKind || !projectedDay) && legacyPostedDay) {
|
|
126
|
+
lines.push(`Posted: ${legacyPostedDay}`);
|
|
127
|
+
}
|
|
128
|
+
if (foundDay) lines.push(`Found: ${foundDay}`);
|
|
129
|
+
else if (lines.length === 0 && !originalDay && !repostDay) {
|
|
130
|
+
if (presentation.value !== 'unknown') {
|
|
131
|
+
lines.push(`${presentation.label}: ${presentation.value}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (repostDay) lines.push(`Reposted: ${repostDay}`);
|
|
135
|
+
const requirement = sourceRequirement(job);
|
|
136
|
+
if (requirement) lines.push(`Req: ${requirement}`);
|
|
137
|
+
return lines;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function sourceRequirement(job = {}) {
|
|
141
|
+
const reference = job.sourceReference;
|
|
142
|
+
return reference && reference.label === 'Req' && reference.disambiguatesTitle === true
|
|
143
|
+
&& typeof reference.value === 'string' && reference.value.trim()
|
|
144
|
+
? reference.value.trim()
|
|
145
|
+
: null;
|
|
146
|
+
}
|
|
147
|
+
|
|
47
148
|
function outputJobs(jobs) {
|
|
48
149
|
if (isJSON()) return console.log(JSON.stringify(jobs, null, 2));
|
|
49
150
|
if (!jobs || jobs.length === 0) {
|
|
@@ -57,7 +158,10 @@ function outputJobs(jobs) {
|
|
|
57
158
|
console.log(` ${color('blue', companyName)} | ${location}`);
|
|
58
159
|
const fundingLine = formatFundingLine(job.company);
|
|
59
160
|
if (fundingLine) console.log(` ${color('magenta', fundingLine)}`);
|
|
60
|
-
|
|
161
|
+
const date = jobDatePresentation(job);
|
|
162
|
+
const requirement = sourceRequirement(job);
|
|
163
|
+
const requirementText = requirement ? ` | Req: ${color('dim', requirement)}` : '';
|
|
164
|
+
console.log(` ID: ${color('dim', job.id)} | ${date.label}: ${color('dim', date.value)}${requirementText}`);
|
|
61
165
|
if (job.jobUrl) console.log(` ${color('dim', job.jobUrl)}`);
|
|
62
166
|
console.log();
|
|
63
167
|
}
|
|
@@ -330,4 +434,4 @@ function outputError(msg) {
|
|
|
330
434
|
console.error(color('red', `Error: ${msg}`, process.stderr));
|
|
331
435
|
}
|
|
332
436
|
|
|
333
|
-
module.exports = { color, shouldColor, isJSON, outputJobs, outputCompanies, outputStats, outputContacts, outputReferralCampaign, outputNetworkBrief, outputCompanyBrief, outputCompanyWorkspace, outputError };
|
|
437
|
+
module.exports = { color, shouldColor, isJSON, jobDatePresentation, jobDateDetailLines, outputJobs, outputCompanies, outputStats, outputContacts, outputReferralCampaign, outputNetworkBrief, outputCompanyBrief, outputCompanyWorkspace, outputError };
|
package/mcp/apply-tools.js
CHANGED
|
@@ -18,7 +18,6 @@ const APPLY_BROWSER_SURFACES = APPLY_CONTRACT.constants.applyBrowserSurfaces;
|
|
|
18
18
|
const APPLY_EXECUTION_ACCESS_CLASSIFICATIONS = APPLY_CONTRACT.constants.applyAccessClassifications;
|
|
19
19
|
const APPLY_EXECUTION_DISPOSITION_SOURCES = APPLY_CONTRACT.constants.applyExecutionDispositionSources;
|
|
20
20
|
const APPLY_SCENARIO_CODES = APPLY_CONTRACT.constants.applyScenarioCodes;
|
|
21
|
-
const APPLY_CHECKPOINT_ACTION_CODES = APPLY_CONTRACT.constants.applyCheckpointActionCodes;
|
|
22
21
|
const APPLY_EXECUTION_MAX_TARGET = APPLY_CONTRACT.constants.applyExecutionMaxTarget;
|
|
23
22
|
const APPLY_EXECUTION_STOP_REASON_CODES = APPLY_CONTRACT.constants.applyExecutionStopReasonCodes;
|
|
24
23
|
const FIXED_APPLY_BATCH_CANCEL_REASON_CODES = APPLY_CONTRACT.constants.fixedApplyBatchCancelReasonCodes;
|
|
@@ -35,8 +34,96 @@ const APPLY_BATCH_MAX_CHECKPOINTS_PER_REQUEST = 20;
|
|
|
35
34
|
const APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT = 25;
|
|
36
35
|
const APPLY_BATCH_MAX_BULK_MUTATIONS = 20;
|
|
37
36
|
|
|
37
|
+
const APPLY_CHECKPOINT_CONTINUATION_BY_ACTION = APPLY_CONTRACT.constants
|
|
38
|
+
.applyCheckpointContinuationByAction;
|
|
39
|
+
const APPLY_CHECKPOINT_LIFECYCLE_BY_ACTION = APPLY_CONTRACT.constants
|
|
40
|
+
.applyCheckpointLifecycleByAction;
|
|
41
|
+
const APPLY_CHECKPOINT_QUESTION_PACKET_BY_ACTION = APPLY_CONTRACT.constants
|
|
42
|
+
.applyCheckpointQuestionPacketByAction;
|
|
43
|
+
const APPLY_CHECKPOINT_ACTION_CODES = APPLY_CONTRACT.constants.applyCheckpointActionCodes;
|
|
44
|
+
const applyCheckpointActionVariant = (actionCode) => z.object({
|
|
45
|
+
actionCode: z.literal(actionCode),
|
|
46
|
+
continuationAllowed: z.literal(APPLY_CHECKPOINT_CONTINUATION_BY_ACTION[actionCode]),
|
|
47
|
+
fieldFingerprint: APPLY_CHECKPOINT_QUESTION_PACKET_BY_ACTION[actionCode]
|
|
48
|
+
? z.string().regex(/^[a-f0-9]{64}$/)
|
|
49
|
+
: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
50
|
+
});
|
|
51
|
+
const applyCheckpointActionSchema = z.discriminatedUnion(
|
|
52
|
+
'actionCode',
|
|
53
|
+
APPLY_CHECKPOINT_ACTION_CODES.map(applyCheckpointActionVariant),
|
|
54
|
+
);
|
|
55
|
+
|
|
38
56
|
const SAFE_OBSERVATION_CODE = /^[a-z0-9][a-z0-9_:-]{0,99}$/;
|
|
39
57
|
const SAFE_IDEMPOTENCY_KEY = /^[\x20-\x7e]+$/;
|
|
58
|
+
const applyCheckpointSchema = z.object({
|
|
59
|
+
memberId: z.number().int().min(1),
|
|
60
|
+
runId: z.number().int().min(1),
|
|
61
|
+
expectedMemberVersion: z.number().int().min(1),
|
|
62
|
+
expectedInspectionEpoch: z.number().int().min(0),
|
|
63
|
+
inspectionEpoch: z.number().int().min(0),
|
|
64
|
+
packetPhase: z.enum(APPLY_CHECKPOINT_PACKET_PHASES).optional(),
|
|
65
|
+
knownFieldsCommitted: z.boolean(),
|
|
66
|
+
resolvedActionIds: z.array(z.string().regex(/^[1-9][0-9]*$/))
|
|
67
|
+
.max(APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT).optional(),
|
|
68
|
+
idempotencyKey: z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY),
|
|
69
|
+
actions: z.array(applyCheckpointActionSchema)
|
|
70
|
+
.min(1).max(APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT),
|
|
71
|
+
}).superRefine((checkpoint, context) => {
|
|
72
|
+
if (checkpoint.inspectionEpoch !== checkpoint.expectedInspectionEpoch) {
|
|
73
|
+
context.addIssue({
|
|
74
|
+
code: z.ZodIssueCode.custom,
|
|
75
|
+
path: ['inspectionEpoch'],
|
|
76
|
+
message: 'inspectionEpoch must equal expectedInspectionEpoch',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (checkpoint.resolvedActionIds
|
|
80
|
+
&& new Set(checkpoint.resolvedActionIds).size !== checkpoint.resolvedActionIds.length) {
|
|
81
|
+
context.addIssue({
|
|
82
|
+
code: z.ZodIssueCode.custom,
|
|
83
|
+
path: ['resolvedActionIds'],
|
|
84
|
+
message: 'resolvedActionIds must be unique',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const actionCodes = checkpoint.actions.map(({ actionCode }) => actionCode);
|
|
88
|
+
if (new Set(actionCodes.map((code) => APPLY_CHECKPOINT_LIFECYCLE_BY_ACTION[code])).size !== 1) {
|
|
89
|
+
context.addIssue({
|
|
90
|
+
code: z.ZodIssueCode.custom,
|
|
91
|
+
path: ['actions'],
|
|
92
|
+
message: 'Actions in one inspection checkpoint must share one lifecycle',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const hasQuestions = actionCodes.some((code) => APPLY_CHECKPOINT_QUESTION_PACKET_BY_ACTION[code]);
|
|
96
|
+
if (hasQuestions && checkpoint.packetPhase === undefined) {
|
|
97
|
+
context.addIssue({
|
|
98
|
+
code: z.ZodIssueCode.custom,
|
|
99
|
+
path: ['packetPhase'],
|
|
100
|
+
message: 'Question checkpoints require a packet phase',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (hasQuestions && !checkpoint.knownFieldsCommitted) {
|
|
104
|
+
context.addIssue({
|
|
105
|
+
code: z.ZodIssueCode.custom,
|
|
106
|
+
path: ['knownFieldsCommitted'],
|
|
107
|
+
message: 'Question checkpoints require committed known fields',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (!hasQuestions && checkpoint.packetPhase !== undefined) {
|
|
111
|
+
context.addIssue({
|
|
112
|
+
code: z.ZodIssueCode.custom,
|
|
113
|
+
path: ['packetPhase'],
|
|
114
|
+
message: 'packetPhase is only valid for grouped questions',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const reviewReady = actionCodes.includes('captcha/at_submit')
|
|
118
|
+
|| actionCodes.includes('review/manual_submit');
|
|
119
|
+
if (reviewReady && !checkpoint.knownFieldsCommitted) {
|
|
120
|
+
context.addIssue({
|
|
121
|
+
code: z.ZodIssueCode.custom,
|
|
122
|
+
path: ['knownFieldsCommitted'],
|
|
123
|
+
message: 'Review checkpoints require all known fields to be committed',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
40
127
|
const iso3166Alpha2Schema = z.string().regex(/^[A-Za-z]{2}$/).refine(isIso3166Alpha2, {
|
|
41
128
|
message: 'Expected an ISO 3166-1 alpha-2 country code',
|
|
42
129
|
});
|
|
@@ -263,7 +350,7 @@ const startApplyRunSchema = z.object({
|
|
|
263
350
|
}
|
|
264
351
|
});
|
|
265
352
|
|
|
266
|
-
const APPLY_RELIABILITY_PROMPT = 'Protocol 3.6 / skill 4.
|
|
353
|
+
const APPLY_RELIABILITY_PROMPT = 'Protocol 3.6 / skill 4.7.1 reliability gate: recover active work first. Retain the latest explicit target as hard, prove genuine applicant fields before counting access, obtain approval for the exact accessible jobs before form mutation, fill all deterministic fields before one true-gap question packet, and validate each value-free phase checkpoint. Every checkpoint action must use its canonical continuationAllowed value; review/manual_submit, captcha/at_submit, trust/origin_mismatch, and observability/unverifiable_state require false. Fail closed on a rejected checkpoint. After full local context loss, list recoverable executions, show only stable job identity, obtain explicit confirmation of the exact candidate set, and call exact-member recovery without substitutions. Treat recovered tab presence, form state, and mutation authority as three separate facts; reacquire a fresh browser binding and inspection epoch before mutation. Before resolving broad submission statements, list active review handoffs for the execution; use only an explicit receipt or the sole returned active receipt, classify every member as detected, user_confirmed, unresolved, or contradictory, and claim that exact handoff before writing outcomes. Use provider-specific positive success evidence; an unchanged URL or title is never negative evidence. Validate an exact expected browser keep set locally before finalization. For resume uploads, negotiate the browser surface capabilities, identify the semantic control, arm the chooser before clicking, attach the immediately verified file, prove the user-facing filename committed, and recheck parser-modified fields. Use compact snapshots and server-provided mutability. Preserve user-edited and unknown non-empty fields. Never reopen parked work without explicit user resumption. Never click Submit.';
|
|
267
354
|
|
|
268
355
|
function registerApplyTools(
|
|
269
356
|
server,
|
|
@@ -773,25 +860,10 @@ function registerApplyTools(
|
|
|
773
860
|
{
|
|
774
861
|
batchId: z.number().int().min(1),
|
|
775
862
|
leaseToken: z.string().min(1).max(1024),
|
|
776
|
-
checkpoints: z.array(
|
|
777
|
-
|
|
778
|
-
runId: z.number().int().min(1),
|
|
779
|
-
expectedMemberVersion: z.number().int().min(1),
|
|
780
|
-
expectedInspectionEpoch: z.number().int().min(0),
|
|
781
|
-
inspectionEpoch: z.number().int().min(0),
|
|
782
|
-
packetPhase: z.enum(APPLY_CHECKPOINT_PACKET_PHASES).optional(),
|
|
783
|
-
knownFieldsCommitted: z.boolean(),
|
|
784
|
-
resolvedActionIds: z.array(z.string().regex(/^[1-9][0-9]*$/))
|
|
785
|
-
.max(APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT).optional(),
|
|
786
|
-
idempotencyKey: z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY),
|
|
787
|
-
actions: z.array(z.object({
|
|
788
|
-
actionCode: z.enum(APPLY_CHECKPOINT_ACTION_CODES),
|
|
789
|
-
continuationAllowed: z.boolean(),
|
|
790
|
-
fieldFingerprint: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
791
|
-
})).min(1).max(APPLY_BATCH_MAX_ACTIONS_PER_CHECKPOINT),
|
|
792
|
-
})).min(1).max(APPLY_BATCH_MAX_CHECKPOINTS_PER_REQUEST),
|
|
863
|
+
checkpoints: z.array(applyCheckpointSchema)
|
|
864
|
+
.min(1).max(APPLY_BATCH_MAX_CHECKPOINTS_PER_REQUEST),
|
|
793
865
|
},
|
|
794
|
-
wrapTool(async ({ batchId, ...body }) =>
|
|
866
|
+
wrapTool(async ({ batchId, ...body }) => applyApiRequest(
|
|
795
867
|
'POST',
|
|
796
868
|
`/api/jobscout/apply/batches/${batchId}/checkpoints`,
|
|
797
869
|
body,
|
|
@@ -1199,7 +1271,7 @@ function registerApplyTools(
|
|
|
1199
1271
|
content: { type: 'text', text: APPLY_RELIABILITY_PROMPT },
|
|
1200
1272
|
}, {
|
|
1201
1273
|
role: 'user',
|
|
1202
|
-
content: { type: 'text', text: 'Before generating questions or filling controls, run the skill 4.
|
|
1274
|
+
content: { type: 'text', text: 'Before generating questions or filling controls, run the skill 4.7.1 deterministic answer resolver. Classify every visible answer as exact_profile, safe_derivation, supported_draft, missing_fact, live_consent, or forbidden_inference; validate the control type; fill only the first three; and ask only currently visible unresolved needs. Treat the current profile revision as reusable authority, never transcript, screenshot, parser, autocomplete, or cached values. Accessible-first is a hard scheduler invariant: park known authentication, account-creation, OTP, and pre-form-CAPTCHA work without starting a draft while an accessible candidate remains.' },
|
|
1203
1275
|
}, {
|
|
1204
1276
|
role: 'user',
|
|
1205
1277
|
content: { type: 'text', text: 'Only active=true identifies resumable execution work. Active=false and preserved=true is terminal read-only reconciliation evidence.' },
|
|
@@ -1207,7 +1279,7 @@ function registerApplyTools(
|
|
|
1207
1279
|
role: 'user',
|
|
1208
1280
|
content: {
|
|
1209
1281
|
type: 'text',
|
|
1210
|
-
text: 'Protocol 3.6.0 reliability gate for new work: require MCP contract 3.7.
|
|
1282
|
+
text: 'Protocol 3.6.0 reliability gate for new work: require MCP contract 3.7.6 and skill 4.7.1. After complete local context loss, list bounded recovery candidates, obtain explicit confirmation of the exact set, and recover only that set. Treat tab recovery, form-state recovery, and mutation authority as independent. List active handoff receipts for the execution before resolving grouped submission statements; use the named receipt or the sole returned active receipt, classify every member, and claim that receipt before recording outcomes. Validate tab keep sets and resume upload stages locally. Never send raw browser values or click Submit.',
|
|
1211
1283
|
},
|
|
1212
1284
|
}, {
|
|
1213
1285
|
role: 'user',
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "trackly-cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.17.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"mcpName": "io.github.trackly-app/trackly",
|
|
5
5
|
"description": "AI job search CLI + hosted MCP server with OAuth. 170K+ jobs, 3,800+ companies, 40+ ATS. Works with Claude, ChatGPT, Cursor, Windsurf, Codex via hosted streamable-http or local stdio.",
|
|
6
6
|
"bin": {
|
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.17.1",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.17.1",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|
|
@@ -20,15 +20,22 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
|
|
|
20
20
|
|
|
21
21
|
## Start every run
|
|
22
22
|
|
|
23
|
-
Before selecting or
|
|
23
|
+
Before selecting, probing, or mutating work, read
|
|
24
|
+
[references/operational-checkpoints.md](references/operational-checkpoints.md),
|
|
25
|
+
[references/access-probe.md](references/access-probe.md),
|
|
24
26
|
[references/batch-orchestration.md](references/batch-orchestration.md) and
|
|
25
|
-
[references/browser-lifecycle.md](references/browser-lifecycle.md).
|
|
27
|
+
[references/browser-lifecycle.md](references/browser-lifecycle.md). Read
|
|
28
|
+
[references/performance-telemetry.md](references/performance-telemetry.md) before
|
|
29
|
+
reusing a form schema, grouping templates, or emitting Apply telemetry. Before generating an employer-form question packet, also read and run
|
|
26
30
|
[references/answer-resolution.md](references/answer-resolution.md). These
|
|
27
|
-
references define the
|
|
28
|
-
and missing-surface recovery
|
|
31
|
+
references define the phase checkpoints, access state machine, server-frozen
|
|
32
|
+
batch, grouped-question, tab-reconciliation, and missing-surface recovery
|
|
33
|
+
contract; they are mandatory even for a one-job batch. Validate each local
|
|
34
|
+
value-free phase receipt with `scripts/validate-phase-checkpoint.js` before
|
|
35
|
+
claiming that phase complete.
|
|
29
36
|
Read [references/legal-clarifications.md](references/legal-clarifications.md) when a form asks for a legal, regulatory, consent, or employer-relationship decision.
|
|
30
37
|
|
|
31
|
-
1. Call `trackly_get_apply_protocol`. Skill 4.
|
|
38
|
+
1. Call `trackly_get_apply_protocol`. Skill 4.7.1 requires protocol 3.6.0 or newer for new reliability work. Protocol 3.2 remains valid only for an already-active explicit legacy single run; an already-active explicit 3.2 single run may finish through its legacy path. Preserve the protocol's documented recovery paths for already-active older work. Require `compatibleSkillMajor: 4` and `compatibleSkillMinimumVersion` no newer than this installed skill. Reject an older or incompatible version for new work and report that the backend must finish updating or `trackly agent setup` must update the skill. Protocol 3.2 added exact-origin trust for jobs Trackly ingested directly from employer careers sources; do not recreate the retired ownership-timestamp gate in the client.
|
|
32
39
|
2. Call `trackly_get_profile_onboarding` or fetch both the profile schema and application profile. When present, render `schema.screens` in ascending `order` as one grouped question packet per screen. Within a screen, preserve category `order` and then field `order`; use each field's user-facing `rationale` when the user asks why it is needed. Ask only unknown or confirmation-needed fields and honor `consistencyRules` before submitting profile changes, resolving contradictions with the user instead of guessing. When `schema.screens` is absent on a legacy backend, fall back to the existing category-based onboarding behavior. Treat `completeness.percent` as required onboarding readiness only. Use `coverage.missingReusableKeys` to explain reusable optional gaps, while `coverage.contextualKeys` are intentionally asked only on the relevant employer form. Do not claim that 100% required completeness answers every possible application question.
|
|
33
40
|
3. Save answers with `trackly_update_application_profile`:
|
|
34
41
|
- Use `answered`, `intentionally_blank`, `declined`, or `unknown` exactly.
|
|
@@ -73,7 +80,7 @@ Read [references/legal-clarifications.md](references/legal-clarifications.md) wh
|
|
|
73
80
|
- For each fixed batch member, preserve an explicit job ID -> application run ID -> browser tab mapping. Complete the full start -> conditional resume preparation/confirmation/verification when an upload control exists -> form completion -> `review_ready` lifecycle for every member. A review-ready run does not block the next member. Never submit any of them.
|
|
74
81
|
6. When the selected job or current fixed batch member has no `runId`, call `trackly_start_apply_run` with the complete `batchId`, `memberId`, `expectedMemberVersion`, `expectedInspectionEpoch`, and `leaseToken` returned by Trackly. This is the sanctioned idempotent lookup for a recovered member whose `runId` is absent; call it with the exact recovered binding and Trackly will reuse the bound active run. When the member already has a `runId`, do not call `trackly_start_apply_run` in this or any subsequent start step: reuse that exact run and bind or recover its browser surface through `trackly_bind_apply_surface` as required. Never create a replacement run because browser control was interrupted.
|
|
75
82
|
- If a bound start returns a transport failure, a non-access HTTP 5xx response, or an error explicitly marked `retryable: true`, preserve the frozen member and browser state. Refetch the active batch, renew its lease, and retry the same complete binding once. Classify the retry response independently with these same rules: route canonical `maintenance_mode` or legacy `planned_maintenance` directly through **Resume after maintenance**; surface controlled-access/request errors marked `retryable: false` and every other HTTP 4xx response unchanged; only a second transport failure, non-access HTTP 5xx response, or explicitly retryable error becomes `backend_run_start_unavailable`. Route maintenance on either attempt without consuming or relabeling the retry, and never relabel a permanent retry response as an outage. For `backend_run_start_unavailable`, continue other members and report a Trackly control-plane failure. Do not call `trackly_checkpoint_apply_batch` for this condition because start failure has not produced the required run ID. The unchanged frozen member is the durable resume point. Never switch that frozen member to an unbound legacy run or blame the employer form.
|
|
76
|
-
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new reliability execution member requires 3.6.0 or newer; already-active older work may use only the recovery path published by its fetched protocol. Never continue a pre-evidence 3.0.x run under skill 4.
|
|
83
|
+
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new reliability execution member requires 3.6.0 or newer; already-active older work may use only the recovery path published by its fetched protocol. Never continue a pre-evidence 3.0.x run under skill 4.7.1. Preserve that run instead of starting a replacement, record it `blocked` with a value-free protocol-upgrade reason when possible, and tell the user the saved job can be retried only after the stale run is cleared through Trackly's supported lifecycle. Stop and refetch the protocol and active execution or batch if support level, execution mode, provider, required scenarios, authorized origin policy, member version, or inspection epoch changes after run creation.
|
|
77
84
|
- In guided mode, inspect the page before preparing any resume bytes. Confirm the employer, role, HTTPS origin, reachable review path, semantic controls, whether an attachment control exists, and absence of a credential, verification, CAPTCHA, or submit-only wall. A missing file input is not itself a blocker; skip the resume path when the application has no attachment control. Any other failed precondition is an execution blocker, not permission to improvise.
|
|
78
85
|
7. Pass the browser readiness gate before preparing resume bytes:
|
|
79
86
|
- Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
|
|
@@ -118,13 +125,15 @@ Follow this order:
|
|
|
118
125
|
5. Use real UI clicks for React/native selects, radios, and checkboxes. Resolve boolean values by their exact semantic label (`true` to Yes, `false` to No), never by option order, index, proximity, or a stale prior selection. After every selection, compare the committed value with the canonical Trackly value. If the field is required or had a validation error before selection, verify that the required-field error disappeared. An optional control with no validation error passes when its committed value is correct. Treat any value mismatch or applicable stale error as a failed field and correct it before continuing.
|
|
119
126
|
6. Recheck email and phone through both browser DOM state and macOS accessibility state. Require exact values and reject duplicate/concatenated values.
|
|
120
127
|
7. Complete every visible field whose canonical answer is known, including optional fields, previous-employer/title history, education rows and dates, links, relocation, communication preferences, and opportunity source. Before review, compare the live field inventory with the resolved Trackly profile and prove that no known visible answer was silently omitted. Group only genuinely unknown contextual fields into the question packet.
|
|
128
|
+
- Before asking, query the typed scopes in [references/answer-resolution.md](references/answer-resolution.md) from narrowest to broadest and retain only value-free counts and fingerprints. Semantic similarity may find a canonical intent candidate but never supplies an answer.
|
|
129
|
+
- Account for every visible control with exactly one typed disposition and validate the `fill` receipt. Reconcile all canonical education records and position-level employment records, then audit every resume stage separately. Aggregate counts or a successful upload call alone are not completeness proof.
|
|
121
130
|
- Enter education and employment-history rows in reverse chronological order, with the most recent or current record first.
|
|
122
131
|
- Keep employment status, current company, and most recent employer distinct. An intentionally blank current company remains blank and never implies an employment status; read employment status independently from its canonical profile field or ask once. The blank also does not erase prior employment. For “most recent employer/title,” use `employment.most_recent_company` and `employment.most_recent_title` only after the fetched profile schema exposes those exact keys. If an exposed key is unknown, ask once and sync the confirmed value globally through `trackly_update_application_profile` instead of inserting the current-company blank or inventing an employer. If either key is absent from the fetched schema, do not PATCH it; preserve the answer locally for the current form and report that the backend schema must update.
|
|
123
132
|
- Use the canonical English school name from Trackly when the ATS offers that exact committed option. If a closed ATS selector does not offer it, select only a verified catalog option for the same institution; never leave free-typed text as though it were committed.
|
|
124
133
|
- Treat partial dates as unknown at the missing precision. If Trackly has only a year but the ATS requires a month, ask once and sync the complete date before selecting either control. Never accept an ATS-selected current/default month or infer an education month.
|
|
125
134
|
8. Use the canonical `consent.background_check_if_advanced` field only when the form explicitly asks for consent to a background check if the candidate advances. If it is unknown, ask before selecting it and save the answer at the user's chosen scope. Never infer it from privacy, demographic, recruiting-data, general application, criminal-record, or professional-reference consent. Treat the latter two as separate unknown consent questions unless the current profile schema supplies their own canonical fields.
|
|
126
|
-
9. For a free-text application response, read [references/application-writing.md](references/application-writing.md). Draft from supported profile and role facts,
|
|
127
|
-
10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner.
|
|
135
|
+
9. For a free-text application response, read [references/application-writing.md](references/application-writing.md). Draft from supported profile and role facts, then run Humanizer automatically when available. Treat its output as a new revision: rebuild the complete local claim-reference packet against that exact final text, set `claimsComplete: true` only after confirming that the packet covers the whole revision, and then call `trackly_lint_application_text`. Apply the self-contained gate whether or not Humanizer is available, and do not block the application merely because Humanizer is unavailable. Do not enter the answer until the exact final revision passes deterministic lint and every claim has a supported evidence reference. Omitting claim metadata is a blocking lint failure, including for drafts the agent believes contain no claims.
|
|
136
|
+
10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner. Reinventory after conditional reveals and require the final form-inventory fingerprint to cover the exact current surface.
|
|
128
137
|
|
|
129
138
|
If browser startup or file work reports `ENOSPC`, `EACCES`, quota, or another I/O error, call `trackly_diagnose_local_path` on the exact implicated path before explaining the cause. Never generalize one path failure to the whole disk without matching measured evidence.
|
|
130
139
|
|
|
@@ -148,7 +157,7 @@ the sole active receipt returned by Trackly; if multiple receipts remain, ask
|
|
|
148
157
|
the user to identify the intended group. Never guess a receipt from tabs or
|
|
149
158
|
chat history.
|
|
150
159
|
|
|
151
|
-
After every conditional field is settled and all other review gates pass, bulk checkpoint every ready member with `review/manual_submit`, the current unchanged inspection epoch in both epoch fields, and any `resolvedActionIds`.
|
|
160
|
+
After every conditional field is settled and all other review gates pass, bulk checkpoint every ready member with `review/manual_submit`, literal `continuationAllowed: false`, the current unchanged inspection epoch in both epoch fields, and any `resolvedActionIds`. Preserve the accepted checkpoint response and use its returned member versions and epochs; this checkpoint resolves the listed human actions and moves the durable member lifecycle to `review_ready` without invalidating current-epoch browser evidence or an unchanged resume-content approval. On `Apply checkpoint action fields are invalid` or another schema rejection, refetch once and fail closed unless the current public contract proves an exact compatible payload. Never retry a malformed payload or claim durable readiness. Do not wait for `needs_input` members: whenever at least one member is durably `review_ready`, ask for one explicit final truthfulness confirmation for the exact complete subset that is currently `review_ready`. If any form in that certified subset used a resume attachment, certify with `resumeDependency: approved` and the exact approved resume identity. If no form in the subset exposes a resume control, certify with `resumeDependency: not_applicable` and omit the resume ID and hash; do not invent an upload requirement. After that confirmation is recorded, validate the value-free review receipt against the preserved accepted checkpoint response and current visibility proof; include the resolved-action count and fingerprint, never the raw IDs. Then call `trackly_record_application_outcomes` once with every member in that certified subset using its exact current `runId`, `batchId`, `memberId`, `inspectionEpoch`, and `leaseToken`; every item must use the literal `outcome: review_ready`. Verify every recorded run returns `awaiting_manual_submit` before showing the review handoff. A member conflict does not invalidate successful siblings: preserve its tab, refresh that member once, and surface the returned stable conflict code instead of blindly replaying a different transition. A member that becomes ready later requires a fresh certification for the then-current complete `review_ready` subset; never reuse an older certification for it. Use singular `trackly_record_application_outcome` only for a legacy single run or an isolated post-review transition. Use the normal review block in [references/review-handoff.md](references/review-handoff.md) only after documented visibility proof for the exact review tab succeeds. Always validate a `handoff` receipt that reports employer, Trackly, and browser state separately. If visibility is unverified, preserve the tab, set `reviewReadyClaimed: false`, use the separate visibility-unverified block, and do not tell the user to submit until the tab is reclaimed and visibly proven. For a single run, keep the browser on the final review state and stop. For a frozen batch, preserve every review-ready tab, hand off each certified review-ready subset without waiting on unrelated human actions, and continue the same lifecycle for the remaining mapped members. Members with unresolved actions stay frozen and resumable. Never submit any member. End with job ID -> run ID -> browser tab -> ATS -> status plus the actual scenario coverage for each run.
|
|
152
161
|
|
|
153
162
|
After the user submits manually:
|
|
154
163
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Access-probe state machine
|
|
2
|
+
|
|
3
|
+
Use this reference for every candidate whose current access state is not already
|
|
4
|
+
durably terminal. A provider hint is scheduling input, never sufficient proof.
|
|
5
|
+
|
|
6
|
+
## States
|
|
7
|
+
|
|
8
|
+
- `requisition_loaded`: the exact stored HTTPS requisition loaded and its
|
|
9
|
+
employer, role, requisition, and complete origin policy passed, including a
|
|
10
|
+
tenant only when the policy requires one.
|
|
11
|
+
- `apply_entry_found`: a genuine application-entry control exists.
|
|
12
|
+
- `intermediate_apply_shell`: a modal, chooser, overview, marketing shell,
|
|
13
|
+
guest landing page, resume-choice screen, or routing page. Accessibility is
|
|
14
|
+
still unknown.
|
|
15
|
+
- `applicant_fields_reached`: genuine applicant controls are visible,
|
|
16
|
+
semantically editable, and reachable without credentials.
|
|
17
|
+
- `authentication_required`: an existing-account login blocks applicant fields.
|
|
18
|
+
- `account_creation_required`: a new account blocks applicant fields.
|
|
19
|
+
- `otp_required`: email or SMS verification blocks applicant fields.
|
|
20
|
+
- `captcha_before_form`: human verification blocks applicant fields.
|
|
21
|
+
- `captcha_at_submit`: applicant fields are usable, but the user must solve a
|
|
22
|
+
final challenge at Submit.
|
|
23
|
+
- `inactive`: the exact posting is removed, closed, or no longer accepts
|
|
24
|
+
applications.
|
|
25
|
+
- `manual_only`: provider policy forbids automated form mutation.
|
|
26
|
+
- `unknown_unobservable`: the adapter cannot prove committed semantic controls.
|
|
27
|
+
|
|
28
|
+
## Transition contract
|
|
29
|
+
|
|
30
|
+
1. Begin from only the exact backend-stored requisition URL.
|
|
31
|
+
2. Validate HTTPS, employer, role, requisition, and the complete authoritative
|
|
32
|
+
origin policy. For a vendor-hosted ATS, this includes the supplied tenant
|
|
33
|
+
rule and exact verified tenant. For `trackly_employer_source_exact_origin`,
|
|
34
|
+
validate only the exact listed origin; do not invent or require an ATS tenant.
|
|
35
|
+
3. Follow ordinary non-sensitive application-entry controls while remaining in
|
|
36
|
+
`intermediate_apply_shell`.
|
|
37
|
+
4. Stop only at genuine applicant controls or one typed blocker. An Apply button,
|
|
38
|
+
modal, guest shell, overview, or readable job page is not access proof.
|
|
39
|
+
5. Never enter private data, credentials, create an account, solve OTP or
|
|
40
|
+
CAPTCHA, or change an applicant control during the probe.
|
|
41
|
+
6. Count a job as accessible only after `applicant_fields_reached` is durably
|
|
42
|
+
proven and the active contract's `accessible` disposition is durably
|
|
43
|
+
recorded. Use `accessible`, not the local state name, in the access checkpoint.
|
|
44
|
+
`captcha_at_submit` may continue through review because the user owns Submit;
|
|
45
|
+
every other access wall is parked without consuming capacity.
|
|
46
|
+
7. Revalidate origin, any policy-required tenant, and exact requisition after
|
|
47
|
+
every redirect.
|
|
48
|
+
8. Keep `inactive` as the exact local page observation, but do not claim that
|
|
49
|
+
Trackly stored a disposition its active contract does not expose. Under MCP
|
|
50
|
+
contract 3.7.3, persist and checkpoint the conservative non-counting
|
|
51
|
+
`unknown_unobservable` disposition with `applicantControlsObserved: false`,
|
|
52
|
+
and report the local inactive reason separately without raw page text. Use a
|
|
53
|
+
future exact `inactive` disposition only when the fetched contract exposes
|
|
54
|
+
it. Preserve a typed control-plane conflict rather than inventing a
|
|
55
|
+
replacement run.
|
|
56
|
+
|
|
57
|
+
## Provider evidence
|
|
58
|
+
|
|
59
|
+
| Provider or pattern | Insufficient | Accessible | Blocker |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| Greenhouse | Job description or Apply button | Editable name/contact or equivalent applicant controls on the exact tenant and requisition | Login or challenge before fields |
|
|
62
|
+
| Ashby | Overview tab | Application tab with editable applicant controls | Account or challenge before controls |
|
|
63
|
+
| Workday | Start Application modal or Apply Manually choice | Actual applicant-data page without a credential wall | Create Account, Sign In, OTP, or pre-form challenge means `account_creation_required`, `authentication_required`, `otp_required`, or `captcha_before_form` |
|
|
64
|
+
| Adobe or Microsoft style | Readable guest landing page or application shell | Editable applicant controls before login | Login before fields means `authentication_required` |
|
|
65
|
+
| Amazon | Job description and Apply button | Editable applicant fields before Amazon Jobs authentication | Amazon sign-in means `authentication_required` |
|
|
66
|
+
|
|
67
|
+
When browser and accessibility surfaces disagree, use
|
|
68
|
+
`unknown_unobservable`. Never upgrade uncertainty to accessibility.
|