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/README.md +18 -6
- package/bin/trackly +2 -0
- package/contracts/trackly-apply-tools.json +26 -7
- package/docs/trackly-tools.md +14 -2
- package/lib/agent.js +197 -11
- package/lib/client.js +40 -2
- package/mcp/apply-tools.js +666 -0
- package/mcp/server.js +5 -204
- package/npm-shrinkwrap.json +1177 -0
- package/package.json +12 -4
- package/scripts/verify-audit-exceptions.js +165 -0
- package/security/audit-exceptions.json +4 -0
- package/server.json +2 -2
- package/skills/trackly-apply/SKILL.md +26 -17
- package/skills/trackly-apply/agents/openai.yaml +1 -1
- package/skills/trackly-apply/references/ats-playbook.md +6 -0
- package/skills/trackly-apply/references/batch-orchestration.md +208 -0
- package/skills/trackly-apply/references/browser-lifecycle.md +101 -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.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:
|
|
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": "
|
|
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": ">=
|
|
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
|
+
};
|
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.9.0",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.
|
|
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
|
|
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.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.
|
|
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
|
-
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
113
|
-
- If the user explicitly confirms submission, record `submitted` with `
|
|
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
|
|
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.
|