trackly-cli 0.7.2 → 0.8.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/README.md +30 -5
- package/bin/trackly +224 -21
- package/contracts/trackly-apply-tools.json +26 -7
- package/docs/trackly-tools.md +26 -2
- package/lib/agent.js +197 -11
- package/lib/client.js +86 -2
- package/mcp/apply-tools.js +666 -0
- package/mcp/server.js +124 -210
- package/npm-shrinkwrap.json +1177 -0
- package/package.json +7 -2
- package/scripts/verify-audit-exceptions.js +190 -0
- package/security/audit-exceptions.json +70 -0
- package/server.json +2 -2
- package/skills/trackly-apply/SKILL.md +23 -16
- package/skills/trackly-apply/agents/openai.yaml +1 -1
- package/skills/trackly-apply/references/batch-orchestration.md +187 -0
- package/skills/trackly-apply/references/browser-lifecycle.md +93 -0
- package/skills/trackly-apply/references/review-handoff.md +53 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"mcpName": "io.github.trackly-app/trackly",
|
|
5
5
|
"description": "AI job search CLI + hosted MCP server with OAuth. 128K+ jobs, 1,900+ companies, 40+ ATS. Works with Claude, ChatGPT, Cursor, Windsurf, Codex via hosted streamable-http or local stdio.",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node --test",
|
|
11
|
-
"test:
|
|
11
|
+
"test:contract-fixture": "node --test test/apply-contract.test.js",
|
|
12
|
+
"test:hosted-contract": "node scripts/verify-hosted-contract.js",
|
|
13
|
+
"security:audit": "node scripts/verify-audit-exceptions.js"
|
|
12
14
|
},
|
|
13
15
|
"files": [
|
|
14
16
|
"bin/",
|
|
@@ -17,6 +19,9 @@
|
|
|
17
19
|
"skills/",
|
|
18
20
|
"contracts/",
|
|
19
21
|
"docs/",
|
|
22
|
+
"scripts/verify-audit-exceptions.js",
|
|
23
|
+
"security/audit-exceptions.json",
|
|
24
|
+
"npm-shrinkwrap.json",
|
|
20
25
|
"server.json",
|
|
21
26
|
"README.md",
|
|
22
27
|
"LICENSE"
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_POLICY_PATH = path.join(
|
|
10
|
+
__dirname,
|
|
11
|
+
'..',
|
|
12
|
+
'security',
|
|
13
|
+
'audit-exceptions.json',
|
|
14
|
+
);
|
|
15
|
+
const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
16
|
+
|
|
17
|
+
function selectAdvisoryFields(advisory) {
|
|
18
|
+
return {
|
|
19
|
+
source: advisory.source,
|
|
20
|
+
name: advisory.name,
|
|
21
|
+
dependency: advisory.dependency,
|
|
22
|
+
title: advisory.title,
|
|
23
|
+
url: advisory.url,
|
|
24
|
+
severity: advisory.severity,
|
|
25
|
+
cwe: advisory.cwe,
|
|
26
|
+
cvss: advisory.cvss,
|
|
27
|
+
range: advisory.range,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function selectVulnerabilityFields(vulnerability) {
|
|
32
|
+
return {
|
|
33
|
+
name: vulnerability.name,
|
|
34
|
+
severity: vulnerability.severity,
|
|
35
|
+
isDirect: vulnerability.isDirect,
|
|
36
|
+
via: vulnerability.via.map((entry) => (
|
|
37
|
+
typeof entry === 'string' ? entry : selectAdvisoryFields(entry)
|
|
38
|
+
)),
|
|
39
|
+
effects: vulnerability.effects,
|
|
40
|
+
range: vulnerability.range,
|
|
41
|
+
nodes: vulnerability.nodes,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizedAuditShape(report) {
|
|
46
|
+
const vulnerabilities = {};
|
|
47
|
+
for (const packageName of Object.keys(report.vulnerabilities || {}).sort()) {
|
|
48
|
+
vulnerabilities[packageName] = selectVulnerabilityFields(
|
|
49
|
+
report.vulnerabilities[packageName],
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
vulnerabilityCounts: report.metadata?.vulnerabilities,
|
|
55
|
+
vulnerabilities,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function cleanAuditResult(report) {
|
|
60
|
+
const counts = report.metadata?.vulnerabilities;
|
|
61
|
+
const vulnerabilities = report.vulnerabilities || {};
|
|
62
|
+
const zeroCounts = counts
|
|
63
|
+
&& ['info', 'low', 'moderate', 'high', 'critical', 'total']
|
|
64
|
+
.every((key) => counts[key] === 0);
|
|
65
|
+
return zeroCounts && Object.keys(vulnerabilities).length === 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validatePolicy(policy) {
|
|
69
|
+
assert.equal(policy?.schemaVersion, 1);
|
|
70
|
+
assert.ok(Array.isArray(policy.exceptions));
|
|
71
|
+
assert.equal(
|
|
72
|
+
policy.exceptions.length,
|
|
73
|
+
1,
|
|
74
|
+
'Exactly one temporary npm audit exception is permitted',
|
|
75
|
+
);
|
|
76
|
+
const exception = policy.exceptions[0];
|
|
77
|
+
assert.equal(exception.advisory, 'GHSA-frvp-7c67-39w9');
|
|
78
|
+
assert.equal(exception.package, '@hono/node-server');
|
|
79
|
+
assert.equal(exception.dependencyOf, '@modelcontextprotocol/sdk');
|
|
80
|
+
assert.equal(exception.severity, 'moderate');
|
|
81
|
+
assert.equal(exception.vulnerableRange, '<2.0.5');
|
|
82
|
+
assert.equal(
|
|
83
|
+
exception.upstream,
|
|
84
|
+
'https://github.com/modelcontextprotocol/typescript-sdk/issues/2531',
|
|
85
|
+
);
|
|
86
|
+
assert.ok(exception.expectedAuditShape);
|
|
87
|
+
return exception;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function validateAuditReport(report, policy) {
|
|
91
|
+
if (
|
|
92
|
+
report
|
|
93
|
+
&& typeof report === 'object'
|
|
94
|
+
&& (
|
|
95
|
+
(report.error && typeof report.error === 'object')
|
|
96
|
+
|| typeof report.message === 'string'
|
|
97
|
+
)
|
|
98
|
+
) {
|
|
99
|
+
const detail = [
|
|
100
|
+
report.message,
|
|
101
|
+
report.error?.summary,
|
|
102
|
+
report.error?.detail,
|
|
103
|
+
].find((value) => typeof value === 'string' && value.trim());
|
|
104
|
+
throw new Error(
|
|
105
|
+
`npm audit did not return an advisory report${detail ? `: ${detail.trim()}` : ''}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
assert.equal(report?.auditReportVersion, 2, 'Unsupported npm audit report version');
|
|
109
|
+
const exception = validatePolicy(policy);
|
|
110
|
+
|
|
111
|
+
if (cleanAuditResult(report)) {
|
|
112
|
+
return {
|
|
113
|
+
allowedAdvisories: [],
|
|
114
|
+
propagatedVulnerabilities: [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
assert.deepEqual(normalizedAuditShape(report), exception.expectedAuditShape);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const wrapped = new Error(
|
|
122
|
+
'npm audit report does not match the temporary exception. '
|
|
123
|
+
+ 'Review every new advisory, severity, affected range, and dependency-path change.',
|
|
124
|
+
);
|
|
125
|
+
wrapped.cause = error;
|
|
126
|
+
throw wrapped;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
allowedAdvisories: [exception.advisory],
|
|
131
|
+
propagatedVulnerabilities: Object.keys(report.vulnerabilities).sort(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function loadPolicy(policyPath = DEFAULT_POLICY_PATH) {
|
|
136
|
+
return JSON.parse(fs.readFileSync(policyPath, 'utf8'));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function runAudit() {
|
|
140
|
+
const result = spawnSync(NPM, ['audit', '--json', '--audit-level=moderate'], {
|
|
141
|
+
cwd: path.join(__dirname, '..'),
|
|
142
|
+
encoding: 'utf8',
|
|
143
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (result.error) {
|
|
147
|
+
throw result.error;
|
|
148
|
+
}
|
|
149
|
+
if (!result.stdout.trim()) {
|
|
150
|
+
throw new Error(`npm audit returned no JSON. stderr: ${result.stderr.trim()}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let report;
|
|
154
|
+
try {
|
|
155
|
+
report = JSON.parse(result.stdout);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`npm audit returned invalid JSON. stderr: ${result.stderr.trim()}`,
|
|
159
|
+
{ cause: error },
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return validateAuditReport(report, loadPolicy());
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (require.main === module) {
|
|
167
|
+
try {
|
|
168
|
+
const result = runAudit();
|
|
169
|
+
if (result.allowedAdvisories.length === 0) {
|
|
170
|
+
console.log('npm audit is clean; no temporary exceptions were used.');
|
|
171
|
+
} else {
|
|
172
|
+
console.log(
|
|
173
|
+
`npm audit matches the single temporary exception: ${result.allowedAdvisories.join(', ')}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error(error.message);
|
|
178
|
+
if (error.cause?.message) {
|
|
179
|
+
console.error(error.cause.message);
|
|
180
|
+
}
|
|
181
|
+
process.exitCode = 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
loadPolicy,
|
|
187
|
+
normalizedAuditShape,
|
|
188
|
+
runAudit,
|
|
189
|
+
validateAuditReport,
|
|
190
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"exceptions": [
|
|
4
|
+
{
|
|
5
|
+
"advisory": "GHSA-frvp-7c67-39w9",
|
|
6
|
+
"source": 1124006,
|
|
7
|
+
"package": "@hono/node-server",
|
|
8
|
+
"dependencyOf": "@modelcontextprotocol/sdk",
|
|
9
|
+
"severity": "moderate",
|
|
10
|
+
"vulnerableRange": "<2.0.5",
|
|
11
|
+
"upstream": "https://github.com/modelcontextprotocol/typescript-sdk/issues/2531",
|
|
12
|
+
"rationale": "Trackly starts the MCP SDK only through its stdio transport. It does not import @hono/node-server, Streamable HTTP, or serveStatic, so the Windows static-file path traversal is unreachable. Automated stdio isolation and packed-consumer tests enforce that boundary until the SDK removes or upgrades this dependency.",
|
|
13
|
+
"expectedAuditShape": {
|
|
14
|
+
"vulnerabilityCounts": {
|
|
15
|
+
"info": 0,
|
|
16
|
+
"low": 0,
|
|
17
|
+
"moderate": 2,
|
|
18
|
+
"high": 0,
|
|
19
|
+
"critical": 0,
|
|
20
|
+
"total": 2
|
|
21
|
+
},
|
|
22
|
+
"vulnerabilities": {
|
|
23
|
+
"@hono/node-server": {
|
|
24
|
+
"name": "@hono/node-server",
|
|
25
|
+
"severity": "moderate",
|
|
26
|
+
"isDirect": false,
|
|
27
|
+
"via": [
|
|
28
|
+
{
|
|
29
|
+
"source": 1124006,
|
|
30
|
+
"name": "@hono/node-server",
|
|
31
|
+
"dependency": "@hono/node-server",
|
|
32
|
+
"title": "Node.js Adapter for Hono: Path traversal in `serve-static` on Windows via encoded backslash (`%5C`)",
|
|
33
|
+
"url": "https://github.com/advisories/GHSA-frvp-7c67-39w9",
|
|
34
|
+
"severity": "moderate",
|
|
35
|
+
"cwe": [
|
|
36
|
+
"CWE-22"
|
|
37
|
+
],
|
|
38
|
+
"cvss": {
|
|
39
|
+
"score": 5.9,
|
|
40
|
+
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N"
|
|
41
|
+
},
|
|
42
|
+
"range": "<2.0.5"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"effects": [
|
|
46
|
+
"@modelcontextprotocol/sdk"
|
|
47
|
+
],
|
|
48
|
+
"range": "<2.0.5",
|
|
49
|
+
"nodes": [
|
|
50
|
+
"node_modules/@hono/node-server"
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"@modelcontextprotocol/sdk": {
|
|
54
|
+
"name": "@modelcontextprotocol/sdk",
|
|
55
|
+
"severity": "moderate",
|
|
56
|
+
"isDirect": true,
|
|
57
|
+
"via": [
|
|
58
|
+
"@hono/node-server"
|
|
59
|
+
],
|
|
60
|
+
"effects": [],
|
|
61
|
+
"range": ">=1.25.0",
|
|
62
|
+
"nodes": [
|
|
63
|
+
"node_modules/@modelcontextprotocol/sdk"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.8.1",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.8.1",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: trackly-apply
|
|
3
|
-
description: Fill user-approved jobs from a Trackly apply/check-later queue through a supported ATS in a real browser, using the user’s profile and resume from Trackly, then stop for manual review and submission. Use for requests such as “apply to my queue,” “fill the next application,” “Trackly apply
|
|
3
|
+
description: Fill the next user-approved jobs from a Trackly apply/check-later queue through a supported ATS in a real browser, using the user’s profile and resume from Trackly, then stop for manual review and submission. Use for requests such as “apply to my queue,” “fill the next application,” or “Trackly apply” in Codex or Claude Code on macOS.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Trackly Apply
|
|
@@ -19,7 +19,13 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
|
|
|
19
19
|
|
|
20
20
|
## Start every run
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Before selecting or resuming work, read
|
|
23
|
+
[references/batch-orchestration.md](references/batch-orchestration.md) and
|
|
24
|
+
[references/browser-lifecycle.md](references/browser-lifecycle.md). Those
|
|
25
|
+
references define the server-frozen batch, grouped-question, tab-reconciliation,
|
|
26
|
+
and missing-surface recovery contract; they are mandatory even for a one-job batch.
|
|
27
|
+
|
|
28
|
+
1. Call `trackly_get_apply_protocol`. Skill 4.2.1 requires protocol major 3 and protocol 3.3.1 or newer for frozen-batch execution; protocol 3.2 remains valid for the explicit legacy single-run workflow. Require `compatibleSkillMajor: 4` and `compatibleSkillMinimumVersion` no newer than this installed skill. Reject an older or incompatible version 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.
|
|
23
29
|
2. Call `trackly_get_profile_onboarding` or fetch both the profile schema and application profile. 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.
|
|
24
30
|
3. Save answers with `trackly_update_application_profile`:
|
|
25
31
|
- Use `answered`, `intentionally_blank`, `declined`, or `unknown` exactly.
|
|
@@ -34,14 +40,14 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
|
|
|
34
40
|
- Use `consent.future_opportunity_retention` only at company scope because one employer's optional retention choice is not consent for another employer.
|
|
35
41
|
- Treat an accuracy or truthfulness certification as a live per-run attestation. Never save that attestation to the reusable profile; ask and verify it on every application run.
|
|
36
42
|
4. Require the one-time profile confirmation, complete education entries, and default-resume metadata before browser work. Do not prepare or upload the resume when the form has no attachment control.
|
|
37
|
-
5.
|
|
38
|
-
- Use the
|
|
39
|
-
- Stop
|
|
43
|
+
5. For every protocol 3.3.1 request, call `trackly_get_active_apply_batch` before creating anything. When it returns a batch, page and resume that exact batch, including every non-null existing member `runId`; never replace it because chat context, a tab, or the local ledger was lost. Only when it returns no active batch may you call `trackly_create_apply_batch` once with the requested count and a fresh idempotency key. Then page only that batch with `trackly_get_apply_batch` and claim it with `trackly_claim_apply_batch`. The returned server membership and order are authoritative. Do not call `trackly_get_apply_queue` first to construct a client-side candidate list; reserve that tool for previewing the queue or resuming a legacy protocol 3.2 single run.
|
|
44
|
+
- Use the frozen member's `staticExclusionCode` as the pre-run blocker. Never infer a stronger support claim from the page design or the LLM's familiarity with a provider. For a member without a static exclusion, call `trackly_start_apply_run`; its returned backend-owned `atsCapability`, `originPolicy`, `executionBlocker`, and required scenarios are authoritative for browser work.
|
|
45
|
+
- Stop after `trackly_start_apply_run` whenever its `executionBlocker` is non-null. `standard` runs use the provider playbook. `guided` runs are allowed only while semantic browser state remains observable, `originPolicy.authorized` is true, and every published stop condition is obeyed. `manual_only` items stop without browser or private-data mutation.
|
|
40
46
|
- When `originPolicy.verification` is `trackly_employer_source_exact_origin`, Trackly has authorized the exact stored HTTPS origin from its direct employer-careers ingestion. Do not demand a separate ownership timestamp, ATS tenant, or company-domain suffix. This authorization never extends to another origin, hostname suffix, redirect, or iframe. The queue/run will require `job_identity_match`; pass it only after the browser visibly confirms the frozen company, role, and available requisition identity. Revalidate that identity after every navigation or redirect and before entering any additional private data.
|
|
41
|
-
- If the user requests the next `N` jobs, freeze
|
|
47
|
+
- If the user requests the next `N` jobs, freeze up to `N` available saved jobs before starting any run. Preserve inactive, insecure, or protocol-declared manual-only members with their static exclusion reason; do not replenish, replace, rescore, or expand the batch. If fewer than `N` saved jobs exist, report the frozen count rather than searching for substitutes.
|
|
42
48
|
- 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.
|
|
43
|
-
6. Call `trackly_start_apply_run` for the selected job or current fixed batch member
|
|
44
|
-
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. Never continue a pre-evidence 3.0.x run under skill 4.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.
|
|
49
|
+
6. Call `trackly_start_apply_run` for the selected job or current fixed batch member with the complete `batchId`, `memberId`, `expectedMemberVersion`, `expectedInspectionEpoch`, and `leaseToken` returned by Trackly. This call is also the sanctioned idempotent lookup when a recovered member has no `runId`; call it with the exact recovered binding and Trackly will reuse the bound active run. Never create a replacement run because browser control was interrupted.
|
|
50
|
+
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new batch member requires 3.3.1 or newer; an explicit 3.2 single run may start or finish through its legacy path. Never continue a pre-evidence 3.0.x run under skill 4.2.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 batch if support level, execution mode, provider, required scenarios, authorized origin policy, member version, or inspection epoch changes after run creation.
|
|
45
51
|
- 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.
|
|
46
52
|
7. Pass the browser readiness gate before preparing resume bytes:
|
|
47
53
|
- Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
|
|
@@ -49,25 +55,25 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
|
|
|
49
55
|
- Bind each tab to the exact employer, role, ATS, requisition URL, job ID, and run ID. A window position or ephemeral tab number alone is not identity.
|
|
50
56
|
- Build a value-free browser binding from those normalized keys plus the semantic browser surface and stable controller tab identity. Compute its lowercase SHA-256; never send the raw URL, title, employer, role, or tab text as observation metadata.
|
|
51
57
|
- After a handoff, context resume, or browser-control interruption, reclaim and re-verify every mapped tab before continuing.
|
|
52
|
-
- Report `observationType: browser_ready` for the current run with `scenarioCode: browser_reclaim`, the allowed `browserSurface`, `committed: true`, and that `browserBindingHash`. Do not call `trackly_prepare_resume` until this same-run attestation succeeds. Accessibility may provide an independent verification signal, but coordinate-only clicking is forbidden for form completion.
|
|
58
|
+
- Report `observationType: browser_ready` for the current run with the exact current `batchId`, `memberId`, and `inspectionEpoch`, plus `scenarioCode: browser_reclaim`, the allowed `browserSurface`, `committed: true`, and that `browserBindingHash`. Do not call `trackly_prepare_resume` until this same-run attestation succeeds. Accessibility may provide an independent verification signal, but coordinate-only clicking is forbidden for form completion.
|
|
53
59
|
- If the semantic browser bridge is unavailable, preserve every existing run and tab mapping, record the blocker when possible, and stop before any upload or form mutation.
|
|
54
60
|
8. If and only if the application offers or requires a resume attachment, call `trackly_prepare_resume` with that exact application run ID, browser surface, and browser binding hash. If hosted MCP reports it unavailable, tell the user that local Trackly MCP or manual upload is required. If no attachment control exists, skip steps 8–11 and do not report `resume_upload` as exercised.
|
|
55
61
|
9. Preserve the user’s filename returned by `trackly_prepare_resume`. Internal cache identifiers belong only in private parent directories and must never appear in the employer-facing upload filename.
|
|
56
62
|
10. Before any upload, let the user inspect the exact prepared file returned by `trackly_prepare_resume`:
|
|
57
63
|
- Prefer an inline visual preview. Otherwise open the exact local file in Quick Look or Preview.app.
|
|
58
|
-
- Show a compact proof block with source (`Trackly default resume`), exact local path, user-facing filename, file size, SHA-256 fingerprint, application run, and expiration.
|
|
64
|
+
- Show a compact local proof block with source (`Trackly default resume`), exact local path, user-facing filename, file size, SHA-256 fingerprint, application run, and expiration. The exact path is local-only. The SHA-256 may be sent only to Trackly's authenticated resume approval, prepared-resume verification, and truth-certification tools; never send either value in observations, logs, application answers, analytics, or employer form fields.
|
|
59
65
|
- Ask for explicit confirmation to use that resume. Bind confirmation to the exact SHA-256 and current application run. For an explicitly approved batch of `N` runs, the user may authorize the same confirmed SHA-256 only for the frozen job/run/tab set; still show and verify each member's exact path, size, hash, run ID, and expiration. Stop if any hash differs, a run is missing, or a run falls outside the frozen batch. Outside that batch, a different hash or run requires new confirmation.
|
|
60
66
|
- Always provide `confirmation.verification.exactLocalPath` so the user can independently verify the file. Never describe the prepared cache path as the original upload source.
|
|
61
67
|
- If an original local source path is known from the current session, identify it separately. Do not store original device paths in Trackly.
|
|
62
68
|
- A generic profile page is not proof of the prepared file. Use an app or web deep link only when the current protocol supplies an authenticated exact-resume viewer tied to the same SHA-256.
|
|
63
69
|
- If no exact preview method works, stop and ask the user to inspect the file manually.
|
|
64
|
-
11. After the user confirms and immediately before attachment, call local `trackly_verify_prepared_resume` with the confirmed run ID, confirmation ID, exact path, SHA-256, size, and expiration. Continue only when it returns `verified: true` for exactly those values. The verifier validates the signed prepare-issued proof, recomputes the file hash, and locks it read-only. If it is unavailable, expired, missing, or mismatched, stop; prepare and visually confirm a fresh copy or require manual upload. Never send a local path or fingerprint to the hosted verifier.
|
|
70
|
+
11. After the user confirms and immediately before attachment, call local `trackly_verify_prepared_resume` with the confirmed run ID, resume ID, confirmation ID, exact path, SHA-256, size, and expiration. Continue only when it returns `verified: true` for exactly those values. The verifier validates the signed prepare-issued proof, recomputes the file hash, and locks it read-only. If it is unavailable, expired, missing, or mismatched, stop; prepare and visually confirm a fresh copy or require manual upload. Never send a local path or fingerprint to the hosted verifier.
|
|
65
71
|
|
|
66
72
|
## Resume after maintenance
|
|
67
73
|
|
|
68
74
|
If any REST or MCP tool returns canonical `maintenance_mode` (or the legacy `planned_maintenance` compatibility alias):
|
|
69
75
|
|
|
70
|
-
1. Retain the current `agent_browser` run ID, selected job, and browser context. Do not call `trackly_start_apply_run`
|
|
76
|
+
1. Retain the current `agent_browser` run ID, selected job, and browser context. Do not blindly call `trackly_start_apply_run` while maintenance is active. After recovery, call it only when the active frozen member omits `runId`, using the exact current member/version/epoch/lease binding so Trackly performs the sanctioned idempotent lookup.
|
|
71
77
|
2. Stop issuing mutations and wait for the advertised retry window or estimated return time. Do not loop or blindly retry.
|
|
72
78
|
3. After maintenance clears, refetch `trackly_get_apply_protocol` and the application profile/onboarding state before taking another action.
|
|
73
79
|
4. Resume the existing run from the observable browser state, re-verify fields that may have rerendered, and continue toward manual review.
|
|
@@ -91,7 +97,7 @@ Follow this order:
|
|
|
91
97
|
9. For a free-text application response, read [references/application-writing.md](references/application-writing.md). Calibrate from the user's Trackly writing fields, use only supported profile and role facts, and run the built-in voice and anti-slop gate before entering the response. Do not require a separate writing or humanizer skill.
|
|
92
98
|
10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner.
|
|
93
99
|
|
|
94
|
-
When the user corrects an answer, immediately save the appropriate scope with `trackly_update_application_profile` and report only a redacted mechanics observation through `trackly_report_apply_observation
|
|
100
|
+
When the user corrects an answer, immediately save the appropriate scope with `trackly_update_application_profile` and report only a redacted mechanics observation. For a frozen batch, collect current-epoch evidence locally and send it through `trackly_report_apply_observations` in one bounded bulk call; use `trackly_report_apply_observation` only for a legacy single run or an isolated follow-up. For every protocol 3.3 observation, include the exact current `batchId`, `memberId`, and `inspectionEpoch`; stale-epoch evidence must fail closed and be recreated only after reclaiming the current surface. Never promote one user’s value into a global default. For `generic_web_form`, never save provider-scoped answers; use company scope for form-specific answers.
|
|
95
101
|
|
|
96
102
|
For every run, track only scenarios actually exercised, except the two universal review proofs below. Attest `browser_reclaim` once with the same-run `observationType: browser_ready`, exact binding hash, browser surface, and `metadata.committed: true`; do not send a duplicate `scenario_coverage` row for it. Before `review_ready`, report every other exercised scenario with `observationType: scenario_coverage`, the stable scenario code, browser surface, `metadata.committed: true` for `passed` or `corrected`, and whether the tab was resumed after handoff.
|
|
97
103
|
|
|
@@ -105,12 +111,13 @@ Every required scenario and both universal evidence scenarios must have correspo
|
|
|
105
111
|
|
|
106
112
|
## Review handoff
|
|
107
113
|
|
|
108
|
-
|
|
114
|
+
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`. Use the 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. 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. Then call `trackly_record_application_outcomes` once with every member in that certified subset using its exact current `runId`, `batchId`, `memberId`, `inspectionEpoch`, and `leaseToken`; refresh only members returned as conflicts. 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. Provide the review block defined in [references/review-handoff.md](references/review-handoff.md). 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.
|
|
109
115
|
|
|
110
116
|
After the user submits manually:
|
|
111
117
|
|
|
112
|
-
- If a success page is visible, record `submitted` with
|
|
113
|
-
- If the user explicitly confirms submission, record `submitted` with `
|
|
118
|
+
- If a success page is visible, reclaim the current batch lease if needed, first record current-epoch `confirmation_detected` evidence with source `success_page` through `trackly_record_apply_submission_evidence`, then record `submitted` with the exact current batch/member/epoch/lease binding and confirmation `success_page`.
|
|
119
|
+
- If the user explicitly confirms submission, reclaim the current batch lease if needed, first record current-epoch `confirmation_detected` evidence with source `user_confirmation` through `trackly_record_apply_submission_evidence`, then record `submitted` with the exact current batch/member/epoch/lease binding and confirmation `user_confirmation`.
|
|
120
|
+
- A provider receipt is optional orthogonal evidence. Record it as `provider_receipt_detected`; never use `provider_receipt` as the outcome confirmation or as a substitute for a success page or explicit user confirmation.
|
|
114
121
|
- If neither exists, do not move the job to applied.
|
|
115
122
|
- Treat a contradictory ATS response such as “already applied” as provisional until the exact requisition URL settles. Do not click Submit again. Preserve the page, confirm the job/requisition identifier is unchanged, and re-read the final route state after the UI and network activity settle. A later explicit success state on that same requisition overrides the provisional error and must be recorded as `submitted`; otherwise record the run as blocked without marking the job applied.
|
|
116
123
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Trackly Apply"
|
|
3
3
|
short_description: "Fill queued job applications for manual review"
|
|
4
|
-
default_prompt: "Use $trackly-apply
|
|
4
|
+
default_prompt: "Use $trackly-apply. Resume my active frozen batch, or create a one-job batch from my next approved Trackly job. Keep the browser visible, verify every committed field, and stop before Submit."
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Batch orchestration
|
|
2
|
+
|
|
3
|
+
Use the backend batch contract for every new protocol 3.3 run, including a
|
|
4
|
+
one-job request. The batch is an execution ledger, not a fit-ranking exercise.
|
|
5
|
+
Protocol 3.2 is retained for the explicit legacy single-run workflow.
|
|
6
|
+
|
|
7
|
+
## Freeze before browser work
|
|
8
|
+
|
|
9
|
+
Create one server-frozen batch before opening or mutating application forms.
|
|
10
|
+
Use the backend order exactly: active jobs first, then `savedAt` descending,
|
|
11
|
+
then job ID ascending. Never replace, rescore, or expand frozen membership
|
|
12
|
+
because a form is difficult or another job looks easier. A job saved after the
|
|
13
|
+
snapshot belongs only to a future batch.
|
|
14
|
+
|
|
15
|
+
Static planning keeps an inactive posting, insecure URL, or protocol-declared
|
|
16
|
+
manual-only job in frozen membership with a non-executable exclusion reason.
|
|
17
|
+
Never replenish or replace it with another queued job. Credentials, CAPTCHA
|
|
18
|
+
placement, attachment controls, semantic observability, and review reachability
|
|
19
|
+
are browser findings; they do not change membership. If the user removes or
|
|
20
|
+
discards a frozen member, stop before its next private-data mutation.
|
|
21
|
+
|
|
22
|
+
Read large batches through opaque continuation tokens. Do not recreate ordering
|
|
23
|
+
or pagination in the prompt.
|
|
24
|
+
|
|
25
|
+
## Backend tool sequence
|
|
26
|
+
|
|
27
|
+
For every protocol 3.3.1 request:
|
|
28
|
+
|
|
29
|
+
1. call `trackly_get_active_apply_batch` before any create call;
|
|
30
|
+
2. resume the returned active batch, or, only when none exists, call
|
|
31
|
+
`trackly_create_apply_batch` once with the requested size and a fresh
|
|
32
|
+
idempotency key;
|
|
33
|
+
3. page only that batch with `trackly_get_apply_batch`;
|
|
34
|
+
4. acquire or renew ownership with `trackly_claim_apply_batch`;
|
|
35
|
+
5. start or reuse each run with the complete batch/member/lease binding; and
|
|
36
|
+
6. send browser findings in groups of at most 20 through
|
|
37
|
+
`trackly_checkpoint_apply_batch`.
|
|
38
|
+
|
|
39
|
+
Do not recreate a batch after maintenance. Refetch the existing batch, reclaim
|
|
40
|
+
its lease with the latest revision, and resume its existing run bindings.
|
|
41
|
+
|
|
42
|
+
### Request budget
|
|
43
|
+
|
|
44
|
+
For a new 20-member batch, keep recovery, planning, initial binding, evidence,
|
|
45
|
+
two bulk checkpoints, resume approval, truth certification, outcomes, and the
|
|
46
|
+
final batch refresh within 52 non-resume MCP/HTTP requests: one active-batch
|
|
47
|
+
lookup, one create, one page, one claim, 20 run starts, 20 surface bindings, one
|
|
48
|
+
`trackly_report_apply_observations` call before resume preparation, a second
|
|
49
|
+
bulk observation call for final scenario coverage, two bulk checkpoints, one
|
|
50
|
+
resume approval, one truth certification, one
|
|
51
|
+
`trackly_record_application_outcomes` call, and one final refresh. An existing
|
|
52
|
+
active batch omits the create call. Do not replace bulk observations,
|
|
53
|
+
checkpoints, or outcomes with per-member requests. Resume download and exact
|
|
54
|
+
local verification are excluded from this count.
|
|
55
|
+
|
|
56
|
+
## Ownership and replay safety
|
|
57
|
+
|
|
58
|
+
Acquire the renewable lease before browser mutation. Each mutation supplies
|
|
59
|
+
only the guards exposed by its tool schema: lease token when the schema exposes
|
|
60
|
+
it, optimistic version when the schema exposes it, inspection epoch when the
|
|
61
|
+
schema exposes it, and idempotency key when the schema exposes it. Never invent
|
|
62
|
+
or attach an uncontracted guard.
|
|
63
|
+
|
|
64
|
+
Renew before expiry and stop mutation if ownership is lost. For operations
|
|
65
|
+
documented as idempotent, a same-key, same-payload replay returns the original
|
|
66
|
+
result. A same key with a different payload is a conflict (`409`), never
|
|
67
|
+
permission to retry under a new run. Do not blindly retry a mutation without an
|
|
68
|
+
idempotency key after an ambiguous response; recover through the documented
|
|
69
|
+
read or lookup operation first. Reclaiming a browser tab increments the
|
|
70
|
+
inspection epoch; earlier-epoch review, attachment, certification, or close
|
|
71
|
+
evidence cannot satisfy the current gate.
|
|
72
|
+
|
|
73
|
+
## First pass
|
|
74
|
+
|
|
75
|
+
Process every frozen member even when another needs user input:
|
|
76
|
+
|
|
77
|
+
1. Create or reuse its bound application run.
|
|
78
|
+
2. Reclaim or open its exact stored requisition URL and validate origin and job
|
|
79
|
+
identity.
|
|
80
|
+
3. Inventory the accessible form.
|
|
81
|
+
4. Fill and verify every known field before checkpointing an unknown.
|
|
82
|
+
5. Record typed human actions and continue to the next frozen member.
|
|
83
|
+
|
|
84
|
+
Recoverable actions keep the run in `needs_input`. Group the first interruption
|
|
85
|
+
into one grouped first-pass packet organized by company, role, run, and action
|
|
86
|
+
type. After answers reveal conditional fields, send a delta packet containing
|
|
87
|
+
only newly revealed items.
|
|
88
|
+
|
|
89
|
+
Action records contain only value-free type, stage, blocking scope,
|
|
90
|
+
continuation flags, status, and redacted fingerprints. Credentials, OTPs,
|
|
91
|
+
CAPTCHA answers, raw question text, and private answer values never enter
|
|
92
|
+
Trackly observations.
|
|
93
|
+
|
|
94
|
+
Each checkpoint includes the expected member version, unchanged current
|
|
95
|
+
inspection epoch in both epoch fields, lease token, one to 25 typed actions, the shared
|
|
96
|
+
known-fields-committed flag, optional packet phase, and its own idempotency key.
|
|
97
|
+
Each action carries its continuation flag and optional redacted field
|
|
98
|
+
fingerprint. All actions in one checkpoint share one member lifecycle. The
|
|
99
|
+
member version advances once, but only a browser bind or reclaim may advance the
|
|
100
|
+
inspection epoch. Never add raw labels, options, answers, or page text to this
|
|
101
|
+
packet.
|
|
102
|
+
|
|
103
|
+
Use `packetPhase: first_pass` while inventorying the frozen set. Use
|
|
104
|
+
`packetPhase: delta` only for a conditional question or action that became
|
|
105
|
+
visible after the first grouped packet. A per-member conflict does not cancel
|
|
106
|
+
successful siblings; refresh only the conflicted member before retrying it.
|
|
107
|
+
When the user has completed or answered a prior human action, include its exact
|
|
108
|
+
server-returned ID in `resolvedActionIds` on the next checkpoint for that same
|
|
109
|
+
member and current inspection epoch. Never infer, fabricate, or resolve an
|
|
110
|
+
action from another member; an unknown or stale action ID must conflict.
|
|
111
|
+
|
|
112
|
+
## Challenge placement
|
|
113
|
+
|
|
114
|
+
An access CAPTCHA or verification wall that hides the semantic form must be
|
|
115
|
+
recorded before private data is entered. Create a human action and continue the
|
|
116
|
+
batch.
|
|
117
|
+
|
|
118
|
+
A submit-time CAPTCHA beside an otherwise complete final form stays
|
|
119
|
+
`review_ready` with an `at_submit` human action because the user owns Submit.
|
|
120
|
+
Never solve, store, or bypass the challenge. Reserve terminal `blocked` for an
|
|
121
|
+
unrecoverable trust or observability failure, not for an answer the user can
|
|
122
|
+
provide.
|
|
123
|
+
|
|
124
|
+
## Resume approval and truth
|
|
125
|
+
|
|
126
|
+
Resume approval is early and content-bound. It covers only immutable batch/run
|
|
127
|
+
membership, default-resume identity, exact content hash, user-facing filename,
|
|
128
|
+
size, profile revision, and expiry. Each upload still needs its own immediate
|
|
129
|
+
per-run local path proof for the exact bytes.
|
|
130
|
+
|
|
131
|
+
Exact local paths are user-visible local proof only and never leave the
|
|
132
|
+
machine. Content hashes may be sent only to authenticated Trackly resume
|
|
133
|
+
approval, prepared-resume verification, and truth-certification endpoints.
|
|
134
|
+
Never send either value to observations, application answers, analytics, logs,
|
|
135
|
+
or employer form fields.
|
|
136
|
+
|
|
137
|
+
Prepare the resume for every run that exposes a real Resume or CV control. Show
|
|
138
|
+
one consolidated proof with every run/path plus the shared resume identity,
|
|
139
|
+
filename, size, and SHA-256. After explicit user approval, call
|
|
140
|
+
`trackly_approve_apply_batch_resume` for the complete current run set. Reuse that
|
|
141
|
+
content approval only while every returned immutable dependency remains exact.
|
|
142
|
+
Ordinary checkpoints may advance member versions without invalidating approval
|
|
143
|
+
for unchanged resume bytes and run membership. Immediately before each
|
|
144
|
+
attachment, call `trackly_verify_prepared_resume` with that run's resume ID and
|
|
145
|
+
signed local proof.
|
|
146
|
+
|
|
147
|
+
Truth certification is late. Ask only after final answers and any conditional
|
|
148
|
+
wording are known. Bind it to the run set, answer snapshot, wording
|
|
149
|
+
fingerprints, profile revision, inspection epochs, and expiry. When at least
|
|
150
|
+
one form used a resume attachment, use `resumeDependency: approved` and bind
|
|
151
|
+
the exact approved resume identity. When no form in the batch exposes a resume
|
|
152
|
+
control, use `resumeDependency: not_applicable` with no resume ID or hash. It
|
|
153
|
+
is ephemeral evidence and never a reusable profile answer.
|
|
154
|
+
|
|
155
|
+
After all conditional questions and certification wording are visible, show one
|
|
156
|
+
final truthfulness prompt. Only after explicit user confirmation, compute the
|
|
157
|
+
value-free answer-snapshot and wording fingerprints and call
|
|
158
|
+
`trackly_certify_apply_batch_truth` for the exact complete subset that is
|
|
159
|
+
currently `review_ready`. A `needs_input` member does not block certification
|
|
160
|
+
or handoff of ready siblings. Never certify an arbitrary subset. When another
|
|
161
|
+
member becomes ready later, obtain a fresh certification for the then-current
|
|
162
|
+
complete `review_ready` subset. Never place the certification or its wording in
|
|
163
|
+
the application profile.
|
|
164
|
+
|
|
165
|
+
A membership, profile revision, resume identity or hash, answer snapshot,
|
|
166
|
+
certification wording, or affected inspection epoch change invalidates the
|
|
167
|
+
affected approval or attestation. An ordinary member-version checkpoint does
|
|
168
|
+
not invalidate an unchanged resume-content approval. Recompute and ask again
|
|
169
|
+
only for the invalidated scope.
|
|
170
|
+
|
|
171
|
+
## Finish
|
|
172
|
+
|
|
173
|
+
Bring every accessible member to `review_ready`, preserving each browser tab
|
|
174
|
+
for manual submission. Certify, record, and hand off the exact current
|
|
175
|
+
`review_ready` subset without waiting for unrelated members. Members with
|
|
176
|
+
actions remain frozen and resumable, and require a fresh certification after
|
|
177
|
+
they later become ready. Report one compact table mapping job ID, run ID,
|
|
178
|
+
browser surface/tab label, ATS, state, actions, and evidence status. Raw tab
|
|
179
|
+
identifiers stay local.
|
|
180
|
+
|
|
181
|
+
Never submit a member. After manual submission, mark Applied only from an
|
|
182
|
+
observable success page or explicit user confirmation. Record the submit
|
|
183
|
+
request, success page or `user_confirmation`, and any provider receipt with
|
|
184
|
+
`trackly_record_apply_submission_evidence`; store only the redacted fingerprint
|
|
185
|
+
and typed source. Then reconcile tab closure separately using the
|
|
186
|
+
browser-lifecycle gate. Submission, receipt, and closure never substitute for
|
|
187
|
+
one another.
|