trackly-cli 0.7.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.7.3",
3
+ "version": "0.9.0",
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:hosted-contract": "node scripts/verify-hosted-contract.js"
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"
@@ -57,11 +62,14 @@
57
62
  "url": "https://github.com/trackly-app/trackly-cli/issues"
58
63
  },
59
64
  "dependencies": {
60
- "@modelcontextprotocol/sdk": "^1.27.1",
65
+ "@modelcontextprotocol/sdk": "1.30.0",
61
66
  "hono": "^4.12.30",
62
67
  "zod": "^3.23.0"
63
68
  },
64
69
  "engines": {
65
- "node": ">=18"
70
+ "node": ">=20"
71
+ },
72
+ "overrides": {
73
+ "@hono/node-server": "2.0.12"
66
74
  }
67
75
  }
@@ -0,0 +1,165 @@
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
+ 0,
74
+ 'No npm audit exceptions are permitted',
75
+ );
76
+ }
77
+
78
+ function validateAuditReport(report, policy) {
79
+ if (
80
+ report
81
+ && typeof report === 'object'
82
+ && (
83
+ (report.error && typeof report.error === 'object')
84
+ || typeof report.message === 'string'
85
+ )
86
+ ) {
87
+ const detail = [
88
+ report.message,
89
+ report.error?.summary,
90
+ report.error?.detail,
91
+ ].find((value) => typeof value === 'string' && value.trim());
92
+ throw new Error(
93
+ `npm audit did not return an advisory report${detail ? `: ${detail.trim()}` : ''}`,
94
+ );
95
+ }
96
+ assert.equal(report?.auditReportVersion, 2, 'Unsupported npm audit report version');
97
+ validatePolicy(policy);
98
+
99
+ if (cleanAuditResult(report)) {
100
+ return {
101
+ allowedAdvisories: [],
102
+ propagatedVulnerabilities: [],
103
+ };
104
+ }
105
+ throw new Error(
106
+ 'npm audit reported a vulnerability; Trackly CLI permits no audit exceptions.',
107
+ );
108
+ }
109
+
110
+ function loadPolicy(policyPath = DEFAULT_POLICY_PATH) {
111
+ return JSON.parse(fs.readFileSync(policyPath, 'utf8'));
112
+ }
113
+
114
+ function runAudit() {
115
+ const result = spawnSync(NPM, ['audit', '--json', '--audit-level=moderate'], {
116
+ cwd: path.join(__dirname, '..'),
117
+ encoding: 'utf8',
118
+ maxBuffer: 10 * 1024 * 1024,
119
+ });
120
+
121
+ if (result.error) {
122
+ throw result.error;
123
+ }
124
+ if (!result.stdout.trim()) {
125
+ throw new Error(`npm audit returned no JSON. stderr: ${result.stderr.trim()}`);
126
+ }
127
+
128
+ let report;
129
+ try {
130
+ report = JSON.parse(result.stdout);
131
+ } catch (error) {
132
+ throw new Error(
133
+ `npm audit returned invalid JSON. stderr: ${result.stderr.trim()}`,
134
+ { cause: error },
135
+ );
136
+ }
137
+
138
+ return validateAuditReport(report, loadPolicy());
139
+ }
140
+
141
+ if (require.main === module) {
142
+ try {
143
+ const result = runAudit();
144
+ if (result.allowedAdvisories.length === 0) {
145
+ console.log('npm audit is clean; no temporary exceptions were used.');
146
+ } else {
147
+ console.log(
148
+ `npm audit matches the single temporary exception: ${result.allowedAdvisories.join(', ')}`,
149
+ );
150
+ }
151
+ } catch (error) {
152
+ console.error(error.message);
153
+ if (error.cause?.message) {
154
+ console.error(error.cause.message);
155
+ }
156
+ process.exitCode = 1;
157
+ }
158
+ }
159
+
160
+ module.exports = {
161
+ loadPolicy,
162
+ normalizedAuditShape,
163
+ runAudit,
164
+ validateAuditReport,
165
+ };
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "exceptions": []
4
+ }
package/server.json CHANGED
@@ -17,12 +17,12 @@
17
17
  ]
18
18
  }
19
19
  ],
20
- "version": "0.7.3",
20
+ "version": "0.9.0",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "trackly-cli",
25
- "version": "0.7.3",
25
+ "version": "0.9.0",
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,” or “prepare job 123 for review” in Codex or Claude Code on macOS.
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
- 1. Call `trackly_get_apply_protocol`. Skill 4.1 requires protocol major 3 (version 3.1.0 or newer) and `compatibleSkillMajor: 4`. Reject an older or incompatible version and report that the backend must finish updating or `trackly agent setup` must update the skill. This gate prevents a pre-evidence skill or run from being reused under the stronger cross-ATS contract. Protocol 3.2 adds exact-origin trust for jobs Trackly ingested directly from employer careers sources; do not recreate the retired ownership-timestamp gate in the client.
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.2 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,15 @@ 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. Call `trackly_get_apply_queue`. Select deterministically unless the user names a job. Do not replace the queue call with a fresh job search.
38
- - Use the queue item's backend-owned `atsCapability`, `originPolicy`, and `executionBlocker`. Never infer a stronger support claim from the page design or the LLM's familiarity with a provider. Required scenarios are backend-enforced gates; possible scenarios are reported only when actually exercised.
39
- - Stop before `trackly_start_apply_run` whenever `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 starting or mutating a run.
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 the deterministic ordered set of exactly `N` job IDs before starting any run. Do not replace, rescore, or expand that approved batch.
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. Reuse an active run returned by Trackly; never create a replacement run because browser control was interrupted.
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. Compare `run.atsCapability` and `run.originPolicy` with the queue values. Stop and refetch the protocol and queue if support level, execution mode, provider, required scenarios, or authorized origin policy changed between preflight and run creation.
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
+ - 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.
51
+ - 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.2. 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
52
  - 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
53
  7. Pass the browser readiness gate before preparing resume bytes:
47
54
  - Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
@@ -49,25 +56,25 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
49
56
  - 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
57
  - 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
58
  - 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.
59
+ - 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
60
  - 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
61
  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
62
  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
63
  10. Before any upload, let the user inspect the exact prepared file returned by `trackly_prepare_resume`:
57
64
  - 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.
65
+ - 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
66
  - 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
67
  - 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
68
  - If an original local source path is known from the current session, identify it separately. Do not store original device paths in Trackly.
62
69
  - 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
70
  - 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.
71
+ 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
72
 
66
73
  ## Resume after maintenance
67
74
 
68
75
  If any REST or MCP tool returns canonical `maintenance_mode` (or the legacy `planned_maintenance` compatibility alias):
69
76
 
70
- 1. Retain the current `agent_browser` run ID, selected job, and browser context. Do not call `trackly_start_apply_run` again.
77
+ 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
78
  2. Stop issuing mutations and wait for the advertised retry window or estimated return time. Do not loop or blindly retry.
72
79
  3. After maintenance clears, refetch `trackly_get_apply_protocol` and the application profile/onboarding state before taking another action.
73
80
  4. Resume the existing run from the observable browser state, re-verify fields that may have rerendered, and continue toward manual review.
@@ -85,13 +92,13 @@ Follow this order:
85
92
  4. Fill typed fields from the resolved Trackly profile. Clear parser-filled data when the canonical state is intentionally blank.
86
93
  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.
87
94
  6. Recheck email and phone through both browser DOM state and macOS accessibility state. Require exact values and reject duplicate/concatenated values.
88
- 7. Complete known optional fields, education, links, relocation, and source answers. Do not silently omit canonical answers.
95
+ 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.
89
96
  - 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.
90
97
  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.
91
98
  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
99
  10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner.
93
100
 
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`. 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.
101
+ 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
102
 
96
103
  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
104
 
@@ -105,12 +112,14 @@ Every required scenario and both universal evidence scenarios must have correspo
105
112
 
106
113
  ## Review handoff
107
114
 
108
- Call `trackly_record_application_outcome` with `review_ready`, then 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 the current review-ready tab and continue the same lifecycle for the next mapped batch member; stop only after every frozen member is review-ready, then provide one review block per run. Never submit any member. End with job ID -> run ID -> browser tab -> ATS -> status plus the actual scenario coverage for each run.
115
+ 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
116
 
110
117
  After the user submits manually:
111
118
 
112
- - If a success page is visible, record `submitted` with a short non-sensitive confirmation signal.
113
- - If the user explicitly confirms submission, record `submitted` with `user_confirmed`.
119
+ - 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`.
120
+ - 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`.
121
+ - Treat the outcome response as a durable commit gate. Refetch each submitted member and require both member lifecycle `submitted` and Trackly job state `applied_confirmed`. On conflict, preserve the success tab, refresh once, and replay only the documented idempotent evidence/outcome sequence. If reconciliation still fails, report a P0 reconciliation defect and leave the tab open; never claim Trackly moved the job.
122
+ - 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
123
  - If neither exists, do not move the job to applied.
115
124
  - 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
125
 
@@ -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 to fill my next approved Trackly job application and stop before Submit."
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."
@@ -4,6 +4,8 @@
4
4
 
5
5
  - Accept employer-hosted Greenhouse forms and `job-boards.greenhouse.io` / `boards.greenhouse.io` application hosts after matching the employer and role.
6
6
  - Expect resume parsing, React selects, correction banners, demographic sections, and final consent checkboxes.
7
+ - Use the visible file chooser or its semantic file input; never click a nearby decorative upload region by coordinates.
8
+ - For custom selects and checkbox groups, verify the selected label or checked element in the live DOM after the real click. A displayed answer with a remaining required error is uncommitted.
7
9
  - Treat red required-field text as authoritative even when a value is visible.
8
10
  - A Greenhouse “Thank you for applying” page is a valid submission signal.
9
11
 
@@ -11,6 +13,9 @@
11
13
 
12
14
  - Accept `jobs.ashbyhq.com` only after matching the employer and role.
13
15
  - Expect rerenders, masked contact inputs, and controls whose visible text can differ from committed state.
16
+ - Resolve binary button groups by the live selected/active state and semantic Yes/No label, never by button order or styling alone.
17
+ - Treat education as repeatable structured rows. After activating `+ Add Education`, require the row count to increase before filling the new row. If Playwright reports a click but the submit-typed control does not add a row, retry with the visible semantic DOM control; stop if the row remains absent.
18
+ - Select localized school suggestions by the exact intended institution and then verify the committed row value.
14
19
  - Best effort: stop when browser and accessibility state disagree.
15
20
  - An explicit application-success banner/page is a valid submission signal.
16
21
 
@@ -37,6 +42,7 @@ This group includes SmartRecruiters, Workable, BambooHR, Recruitee, Jobvite, Tea
37
42
  - Expect a mixture of native controls, custom widgets, employer subdomains, embedded frames, shared profile data, and optional resume parsing.
38
43
  - Match the live HTTPS origin, employer, role, and requisition before entering private data.
39
44
  - Use the same committed-state and required-error gate as Greenhouse. Familiar-looking controls do not lower the evidence requirement.
45
+ - On HiBob, treat document upload as a two-step commit when the UI requires choosing the file and then confirming the upload. Verify the final filename/attachment state; a selected local file alone is not an attached resume.
40
46
 
41
47
  ## Unknown employer-hosted form
42
48
 
@@ -0,0 +1,208 @@
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
+ When a bound run start returns a transport failure, a non-access HTTP 5xx
42
+ response, or an error explicitly marked `retryable: true`, refetch and renew,
43
+ then retry the same complete binding once. Classify the retry response
44
+ independently with the same rules. Route `maintenance_mode` or the legacy
45
+ `planned_maintenance` alias from either attempt through maintenance recovery.
46
+ Surface controlled-access/request errors marked `retryable: false` and every
47
+ other HTTP 4xx response unchanged, including when returned by the retry. Only a
48
+ second transport failure, non-access HTTP 5xx response, or explicitly
49
+ retryable error becomes `backend_run_start_unavailable` while siblings
50
+ continue. Do not checkpoint this condition: no run ID exists yet, and the
51
+ checkpoint contract requires one. The unchanged frozen member is the durable
52
+ resume point. Never detach that member into a legacy single run.
53
+
54
+ ### Request budget
55
+
56
+ For a failure-free new 20-member batch, keep planning, initial binding, evidence,
57
+ two bulk checkpoints, resume approval, truth certification, outcomes, and the
58
+ final batch refresh within 52 non-resume MCP/HTTP requests: one active-batch
59
+ lookup, one create, one page, one claim, 20 run starts, 20 surface bindings, one
60
+ `trackly_report_apply_observations` call before resume preparation, a second
61
+ bulk observation call for final scenario coverage, two bulk checkpoints, one
62
+ resume approval, one truth certification, one
63
+ `trackly_record_application_outcomes` call, and one final refresh. An existing
64
+ active batch omits the create call. Do not replace bulk observations,
65
+ checkpoints, or outcomes with per-member requests. Resume download and exact
66
+ local verification are excluded from this count. Each member that encounters
67
+ the one permitted retryable bound-start failure receives exactly three
68
+ additional recovery calls: refetch the active batch, renew its lease, and retry
69
+ the same binding. Therefore the bounded contingency budget is `52 + (3 * R)`,
70
+ where `R` is the number of affected members and cannot exceed 20. Maintenance
71
+ recovery is paced by the advertised window and is tracked separately.
72
+
73
+ ## Ownership and replay safety
74
+
75
+ Acquire the renewable lease before browser mutation. Each mutation supplies
76
+ only the guards exposed by its tool schema: lease token when the schema exposes
77
+ it, optimistic version when the schema exposes it, inspection epoch when the
78
+ schema exposes it, and idempotency key when the schema exposes it. Never invent
79
+ or attach an uncontracted guard.
80
+
81
+ Renew before expiry and stop mutation if ownership is lost. For operations
82
+ documented as idempotent, a same-key, same-payload replay returns the original
83
+ result. A same key with a different payload is a conflict (`409`), never
84
+ permission to retry under a new run. Do not blindly retry a mutation without an
85
+ idempotency key after an ambiguous response; recover through the documented
86
+ read or lookup operation first. Reclaiming a browser tab increments the
87
+ inspection epoch; earlier-epoch review, attachment, certification, or close
88
+ evidence cannot satisfy the current gate.
89
+
90
+ ## First pass
91
+
92
+ Process every frozen member even when another needs user input:
93
+
94
+ 1. Create or reuse its bound application run.
95
+ 2. Reclaim or open its exact stored requisition URL and validate origin and job
96
+ identity.
97
+ 3. Inventory the accessible form.
98
+ 4. Fill and verify every known field before checkpointing an unknown.
99
+ 5. Record typed human actions and continue to the next frozen member.
100
+
101
+ Recoverable actions keep the run in `needs_input`. Group the first interruption
102
+ into one grouped first-pass packet organized by company, role, run, and action
103
+ type. After answers reveal conditional fields, send a delta packet containing
104
+ only newly revealed items.
105
+
106
+ Action records contain only value-free type, stage, blocking scope,
107
+ continuation flags, status, and redacted fingerprints. Credentials, OTPs,
108
+ CAPTCHA answers, raw question text, and private answer values never enter
109
+ Trackly observations.
110
+
111
+ Each checkpoint includes the expected member version, unchanged current
112
+ inspection epoch in both epoch fields, lease token, one to 25 typed actions, the shared
113
+ known-fields-committed flag, optional packet phase, and its own idempotency key.
114
+ Each action carries its continuation flag and optional redacted field
115
+ fingerprint. All actions in one checkpoint share one member lifecycle. The
116
+ member version advances once, but only a browser bind or reclaim may advance the
117
+ inspection epoch. Never add raw labels, options, answers, or page text to this
118
+ packet.
119
+
120
+ Use `packetPhase: first_pass` while inventorying the frozen set. Use
121
+ `packetPhase: delta` only for a conditional question or action that became
122
+ visible after the first grouped packet. A per-member conflict does not cancel
123
+ successful siblings; refresh only the conflicted member before retrying it.
124
+ When the user has completed or answered a prior human action, include its exact
125
+ server-returned ID in `resolvedActionIds` on the next checkpoint for that same
126
+ member and current inspection epoch. Never infer, fabricate, or resolve an
127
+ action from another member; an unknown or stale action ID must conflict.
128
+
129
+ ## Challenge placement
130
+
131
+ An access CAPTCHA or verification wall that hides the semantic form must be
132
+ recorded before private data is entered. Create a human action and continue the
133
+ batch.
134
+
135
+ A submit-time CAPTCHA beside an otherwise complete final form stays
136
+ `review_ready` with an `at_submit` human action because the user owns Submit.
137
+ Never solve, store, or bypass the challenge. Reserve terminal `blocked` for an
138
+ unrecoverable trust or observability failure, not for an answer the user can
139
+ provide.
140
+
141
+ ## Resume approval and truth
142
+
143
+ Resume approval is early and content-bound. It covers only immutable batch/run
144
+ membership, default-resume identity, exact content hash, user-facing filename,
145
+ size, profile revision, and expiry. Each upload still needs its own immediate
146
+ per-run local path proof for the exact bytes.
147
+
148
+ Exact local paths are user-visible local proof only and never leave the
149
+ machine. Content hashes may be sent only to authenticated Trackly resume
150
+ approval, prepared-resume verification, and truth-certification endpoints.
151
+ Never send either value to observations, application answers, analytics, logs,
152
+ or employer form fields.
153
+
154
+ Prepare the resume for every run that exposes a real Resume or CV control. Show
155
+ one consolidated proof with every run/path plus the shared resume identity,
156
+ filename, size, and SHA-256. After explicit user approval, call
157
+ `trackly_approve_apply_batch_resume` for the complete current run set. Reuse that
158
+ content approval only while every returned immutable dependency remains exact.
159
+ Ordinary checkpoints may advance member versions without invalidating approval
160
+ for unchanged resume bytes and run membership. Immediately before each
161
+ attachment, call `trackly_verify_prepared_resume` with that run's resume ID and
162
+ signed local proof.
163
+
164
+ Truth certification is late. Ask only after final answers and any conditional
165
+ wording are known. Bind it to the run set, answer snapshot, wording
166
+ fingerprints, profile revision, inspection epochs, and expiry. When at least
167
+ one form used a resume attachment, use `resumeDependency: approved` and bind
168
+ the exact approved resume identity. When no form in the batch exposes a resume
169
+ control, use `resumeDependency: not_applicable` with no resume ID or hash. It
170
+ is ephemeral evidence and never a reusable profile answer.
171
+
172
+ After all conditional questions and certification wording are visible, show one
173
+ final truthfulness prompt. Only after explicit user confirmation, compute the
174
+ value-free answer-snapshot and wording fingerprints and call
175
+ `trackly_certify_apply_batch_truth` for the exact complete subset that is
176
+ currently `review_ready`. A `needs_input` member does not block certification
177
+ or handoff of ready siblings. Never certify an arbitrary subset. When another
178
+ member becomes ready later, obtain a fresh certification for the then-current
179
+ complete `review_ready` subset. Never place the certification or its wording in
180
+ the application profile.
181
+
182
+ A membership, profile revision, resume identity or hash, answer snapshot,
183
+ certification wording, or affected inspection epoch change invalidates the
184
+ affected approval or attestation. An ordinary member-version checkpoint does
185
+ not invalidate an unchanged resume-content approval. Recompute and ask again
186
+ only for the invalidated scope.
187
+
188
+ ## Finish
189
+
190
+ Bring every accessible member to `review_ready`, preserving each browser tab
191
+ for manual submission. Certify, record, and hand off the exact current
192
+ `review_ready` subset without waiting for unrelated members. Members with
193
+ actions remain frozen and resumable, and require a fresh certification after
194
+ they later become ready. Report one compact table mapping job ID, run ID,
195
+ browser surface/tab label, ATS, state, actions, and evidence status. Raw tab
196
+ identifiers stay local.
197
+
198
+ Never submit a member. After manual submission, mark Applied only from an
199
+ observable success page or explicit user confirmation. Record the submit
200
+ request, success page or `user_confirmation`, and any provider receipt with
201
+ `trackly_record_apply_submission_evidence`; store only the redacted fingerprint
202
+ and typed source. The outcome call is not complete until a fresh batch read
203
+ shows member lifecycle `submitted` and tracker state `applied_confirmed`.
204
+ Preserve the success tab during one documented idempotent conflict recovery;
205
+ if reconciliation remains unsuccessful, report the control-plane defect and
206
+ do not claim completion. Only then reconcile tab closure separately using the
207
+ browser-lifecycle gate. Submission, durable outcome, receipt, and closure never
208
+ substitute for one another.