trackly-cli 0.17.0 → 0.18.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 +7 -4
- package/bin/trackly +2 -2
- package/contracts/trackly-apply-contract-history.json +15 -0
- package/contracts/trackly-apply-tools.json +46 -4
- package/docs/solutions/integration-issues/backend-owned-sensitive-answer-deletion-guard.md +88 -0
- package/docs/trackly-tools.md +10 -1
- package/lib/agent.js +2 -2
- package/lib/formatters.js +106 -2
- package/mcp/apply-tools.js +577 -39
- package/mcp/server.js +96 -11
- package/npm-shrinkwrap.json +22 -21
- package/package.json +6 -4
- package/server.json +2 -2
- package/skills/trackly-apply/SKILL.md +9 -6
- package/skills/trackly-apply/references/access-probe.md +5 -0
- package/skills/trackly-apply/references/answer-resolution.md +25 -0
- package/skills/trackly-apply/references/batch-orchestration.md +58 -3
- package/skills/trackly-apply/references/browser-upload.md +26 -8
- package/skills/trackly-apply/references/form-integrity.md +22 -0
- package/skills/trackly-apply/references/operational-checkpoints.md +141 -10
- package/skills/trackly-apply/references/review-handoff.md +21 -0
- package/skills/trackly-apply/scripts/validate-phase-checkpoint.js +670 -23
package/mcp/server.js
CHANGED
|
@@ -6,10 +6,12 @@ const { McpError } = require('@modelcontextprotocol/sdk/types.js');
|
|
|
6
6
|
const { z } = require('zod');
|
|
7
7
|
const { apiRequest, createTracklyAccessError, hasAuth, maintenanceOutput } = require('../lib/client');
|
|
8
8
|
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
9
|
+
const APPLY_CONTRACT = require('../contracts/trackly-apply-tools.json');
|
|
9
10
|
const { registerApplyTools } = require('./apply-tools');
|
|
10
11
|
const { configureMcpAnalytics, shutdownMcpAnalytics } = require('./analytics');
|
|
11
12
|
|
|
12
13
|
const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
|
|
14
|
+
const MCP_SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
13
15
|
const MCP_MAINTENANCE_ERROR_CODE = -32002;
|
|
14
16
|
const MCP_ACCESS_ERROR_CODE = -32003;
|
|
15
17
|
const MCP_AUTH_ERROR_CODE = -32004;
|
|
@@ -82,6 +84,11 @@ const SORT_VALUES = ['newest', 'match'];
|
|
|
82
84
|
// stage value, NOT the legacy action name. Hoisted to module scope so it's not
|
|
83
85
|
// rebuilt on every tool invocation.
|
|
84
86
|
const ACTION_TO_STAGE = { applied: 'applied', saved: 'backlog', dismissed: 'discarded' };
|
|
87
|
+
const APPLY_BATCH_CONFLICT_CODES = new Set(
|
|
88
|
+
Array.isArray(APPLY_CONTRACT.constants?.applyBatchConflictCodes)
|
|
89
|
+
? APPLY_CONTRACT.constants.applyBatchConflictCodes.filter((code) => typeof code === 'string')
|
|
90
|
+
: [],
|
|
91
|
+
);
|
|
85
92
|
|
|
86
93
|
function createErrorResult(error, fallbackMessage, extra = {}) {
|
|
87
94
|
const normalizedMaintenance = maintenanceOutput(error);
|
|
@@ -110,6 +117,17 @@ function createErrorResult(error, fallbackMessage, extra = {}) {
|
|
|
110
117
|
payload.status = error.status;
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
// Apply batch conflicts are a versioned, bounded error surface. Preserve a
|
|
121
|
+
// code only when the HTTP envelope and code both match the published
|
|
122
|
+
// contract; never reflect arbitrary backend error properties into MCP data.
|
|
123
|
+
if (
|
|
124
|
+
error?.status === 409
|
|
125
|
+
&& error?.error === 'apply_batch_conflict'
|
|
126
|
+
&& APPLY_BATCH_CONFLICT_CODES.has(error?.conflictCode)
|
|
127
|
+
) {
|
|
128
|
+
payload.conflictCode = error.conflictCode;
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
if (
|
|
114
132
|
error?.code === 'experience_filter_v2_unavailable'
|
|
115
133
|
|| error?.code === 'invalid_preference_revision'
|
|
@@ -597,31 +615,98 @@ async function startMcpServer(options = {}) {
|
|
|
597
615
|
|
|
598
616
|
function installMcpSignalHandlers(server, options = {}) {
|
|
599
617
|
const signalTarget = options.signalTarget || process;
|
|
618
|
+
const input = options.input || process.stdin;
|
|
619
|
+
const output = options.output || process.stdout;
|
|
600
620
|
const exit = options.exit || ((code) => process.exit(code));
|
|
601
621
|
const shutdownAnalytics = options.shutdownAnalytics || shutdownMcpAnalytics;
|
|
622
|
+
const shutdownTimeoutMs = options.shutdownTimeoutMs ?? MCP_SHUTDOWN_TIMEOUT_MS;
|
|
623
|
+
const closeServer = options.closeServer || (() => server.close());
|
|
624
|
+
const reportInputError = options.reportInputError || ((error) => {
|
|
625
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
626
|
+
process.stderr.write(`MCP stdin error: ${message}\n`);
|
|
627
|
+
});
|
|
628
|
+
const reportOutputError = options.reportOutputError || ((error) => {
|
|
629
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
630
|
+
process.stderr.write(`MCP stdout error: ${message}\n`);
|
|
631
|
+
});
|
|
602
632
|
let terminating = false;
|
|
603
633
|
const handlers = new Map();
|
|
634
|
+
let inputEndHandler;
|
|
635
|
+
let inputCloseHandler;
|
|
636
|
+
let inputErrorHandler;
|
|
637
|
+
let outputErrorHandler;
|
|
604
638
|
const cleanup = () => {
|
|
605
639
|
for (const [signal, handler] of handlers) {
|
|
606
640
|
signalTarget.removeListener(signal, handler);
|
|
607
641
|
}
|
|
608
642
|
handlers.clear();
|
|
643
|
+
if (inputEndHandler) input.removeListener('end', inputEndHandler);
|
|
644
|
+
if (inputCloseHandler) input.removeListener('close', inputCloseHandler);
|
|
645
|
+
if (inputErrorHandler) input.removeListener('error', inputErrorHandler);
|
|
646
|
+
if (outputErrorHandler) output.removeListener('error', outputErrorHandler);
|
|
647
|
+
};
|
|
648
|
+
const terminate = (exitCode) => {
|
|
649
|
+
if (terminating) return;
|
|
650
|
+
terminating = true;
|
|
651
|
+
let shutdownTimer;
|
|
652
|
+
const shutdown = Promise.resolve()
|
|
653
|
+
.then(() => closeServer())
|
|
654
|
+
.catch(() => {})
|
|
655
|
+
.then(() => shutdownAnalytics(server))
|
|
656
|
+
.catch(() => {});
|
|
657
|
+
const deadline = new Promise((resolve) => {
|
|
658
|
+
shutdownTimer = setTimeout(resolve, shutdownTimeoutMs);
|
|
659
|
+
});
|
|
660
|
+
void Promise.race([shutdown, deadline])
|
|
661
|
+
.finally(() => {
|
|
662
|
+
clearTimeout(shutdownTimer);
|
|
663
|
+
// Keep signal and pipe handlers installed until teardown settles so a
|
|
664
|
+
// repeated disconnect cannot restore Node's default early termination.
|
|
665
|
+
cleanup();
|
|
666
|
+
exit(exitCode);
|
|
667
|
+
});
|
|
609
668
|
};
|
|
610
669
|
for (const [signal, exitCode] of [['SIGINT', 130], ['SIGTERM', 143]]) {
|
|
611
|
-
const handler = () =>
|
|
612
|
-
if (terminating) return;
|
|
613
|
-
terminating = true;
|
|
614
|
-
void Promise.resolve()
|
|
615
|
-
.then(() => shutdownAnalytics(server))
|
|
616
|
-
.catch(() => {})
|
|
617
|
-
.finally(() => {
|
|
618
|
-
cleanup();
|
|
619
|
-
exit(exitCode);
|
|
620
|
-
});
|
|
621
|
-
};
|
|
670
|
+
const handler = () => terminate(exitCode);
|
|
622
671
|
handlers.set(signal, handler);
|
|
623
672
|
signalTarget.on(signal, handler);
|
|
624
673
|
}
|
|
674
|
+
|
|
675
|
+
// A stdio client can disappear without sending a signal. Close the MCP
|
|
676
|
+
// transport explicitly so its onclose hooks run instead of relying on the
|
|
677
|
+
// Node event loop to happen to become empty.
|
|
678
|
+
inputEndHandler = () => terminate(0);
|
|
679
|
+
inputCloseHandler = () => terminate(0);
|
|
680
|
+
inputErrorHandler = (error) => {
|
|
681
|
+
reportInputError(error);
|
|
682
|
+
terminate(1);
|
|
683
|
+
};
|
|
684
|
+
input.on('end', inputEndHandler);
|
|
685
|
+
input.on('close', inputCloseHandler);
|
|
686
|
+
input.on('error', inputErrorHandler);
|
|
687
|
+
|
|
688
|
+
// A response racing with client teardown can reach a closed stdout pipe.
|
|
689
|
+
// Treat only EPIPE as a normal disconnect; preserve fail-fast behavior for
|
|
690
|
+
// every unrelated stream failure.
|
|
691
|
+
outputErrorHandler = (error) => {
|
|
692
|
+
if (error?.code === 'EPIPE') {
|
|
693
|
+
terminate(0);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
reportOutputError(error);
|
|
697
|
+
terminate(1);
|
|
698
|
+
};
|
|
699
|
+
output.on('error', outputErrorHandler);
|
|
700
|
+
|
|
701
|
+
// A pipe may close while startMcpServer is awaiting transport connection,
|
|
702
|
+
// before these process-level handlers can be installed.
|
|
703
|
+
if (input.errored) {
|
|
704
|
+
inputErrorHandler(input.errored);
|
|
705
|
+
} else if (output.errored) {
|
|
706
|
+
outputErrorHandler(output.errored);
|
|
707
|
+
} else if (input.readableEnded || input.destroyed || output.writableEnded || output.destroyed) {
|
|
708
|
+
terminate(0);
|
|
709
|
+
}
|
|
625
710
|
return cleanup;
|
|
626
711
|
}
|
|
627
712
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "trackly-cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.18.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
13
13
|
"@posthog/mcp": "0.11.0",
|
|
14
|
-
"fast-uri": "3.1.
|
|
14
|
+
"fast-uri": "3.1.7",
|
|
15
15
|
"hono": "4.13.0",
|
|
16
16
|
"ip-address": "10.4.0",
|
|
17
17
|
"posthog-node": "5.48.1",
|
|
@@ -440,9 +440,9 @@
|
|
|
440
440
|
}
|
|
441
441
|
},
|
|
442
442
|
"node_modules/es-object-atoms": {
|
|
443
|
-
"version": "1.1.
|
|
444
|
-
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.
|
|
445
|
-
"integrity": "sha512-
|
|
443
|
+
"version": "1.1.2",
|
|
444
|
+
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
|
445
|
+
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
|
446
446
|
"license": "MIT",
|
|
447
447
|
"dependencies": {
|
|
448
448
|
"es-errors": "^1.3.0"
|
|
@@ -555,9 +555,9 @@
|
|
|
555
555
|
"license": "MIT"
|
|
556
556
|
},
|
|
557
557
|
"node_modules/fast-uri": {
|
|
558
|
-
"version": "3.1.
|
|
559
|
-
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.
|
|
560
|
-
"integrity": "sha512-
|
|
558
|
+
"version": "3.1.7",
|
|
559
|
+
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
|
560
|
+
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
|
|
561
561
|
"funding": [
|
|
562
562
|
{
|
|
563
563
|
"type": "github",
|
|
@@ -680,9 +680,9 @@
|
|
|
680
680
|
}
|
|
681
681
|
},
|
|
682
682
|
"node_modules/hasown": {
|
|
683
|
-
"version": "2.0.
|
|
684
|
-
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.
|
|
685
|
-
"integrity": "sha512-
|
|
683
|
+
"version": "2.0.4",
|
|
684
|
+
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
|
685
|
+
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
|
686
686
|
"license": "MIT",
|
|
687
687
|
"dependencies": {
|
|
688
688
|
"function-bind": "^1.1.2"
|
|
@@ -976,12 +976,13 @@
|
|
|
976
976
|
}
|
|
977
977
|
},
|
|
978
978
|
"node_modules/qs": {
|
|
979
|
-
"version": "6.
|
|
980
|
-
"resolved": "https://registry.npmjs.org/qs/-/qs-6.
|
|
981
|
-
"integrity": "sha512-
|
|
979
|
+
"version": "6.16.0",
|
|
980
|
+
"resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
|
|
981
|
+
"integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
|
|
982
982
|
"license": "BSD-3-Clause",
|
|
983
983
|
"dependencies": {
|
|
984
|
-
"
|
|
984
|
+
"es-define-property": "^1.0.1",
|
|
985
|
+
"side-channel": "^1.1.1"
|
|
985
986
|
},
|
|
986
987
|
"engines": {
|
|
987
988
|
"node": ">=0.6"
|
|
@@ -1118,14 +1119,14 @@
|
|
|
1118
1119
|
}
|
|
1119
1120
|
},
|
|
1120
1121
|
"node_modules/side-channel": {
|
|
1121
|
-
"version": "1.1.
|
|
1122
|
-
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.
|
|
1123
|
-
"integrity": "sha512-
|
|
1122
|
+
"version": "1.1.1",
|
|
1123
|
+
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
|
1124
|
+
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
|
1124
1125
|
"license": "MIT",
|
|
1125
1126
|
"dependencies": {
|
|
1126
1127
|
"es-errors": "^1.3.0",
|
|
1127
|
-
"object-inspect": "^1.13.
|
|
1128
|
-
"side-channel-list": "^1.0.
|
|
1128
|
+
"object-inspect": "^1.13.4",
|
|
1129
|
+
"side-channel-list": "^1.0.1",
|
|
1129
1130
|
"side-channel-map": "^1.0.1",
|
|
1130
1131
|
"side-channel-weakmap": "^1.0.2"
|
|
1131
1132
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"mcpName": "io.github.trackly-app/trackly",
|
|
5
5
|
"description": "AI job search CLI + hosted MCP server with OAuth. 170K+ jobs, 3,800+ companies, 40+ ATS. Works with Claude, ChatGPT, Cursor, Windsurf, Codex via hosted streamable-http or local stdio.",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"test": "node --test",
|
|
11
11
|
"test:contract-fixture": "node --test test/apply-contract.test.js",
|
|
12
12
|
"test:hosted-contract": "node scripts/verify-hosted-contract.js",
|
|
13
|
+
"test:review-auth-contract": "node scripts/verify-review-auth-contract.js",
|
|
13
14
|
"security:audit": "node scripts/verify-audit-exceptions.js"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
"dependencies": {
|
|
65
66
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
66
67
|
"@posthog/mcp": "0.11.0",
|
|
67
|
-
"fast-uri": "3.1.
|
|
68
|
+
"fast-uri": "3.1.7",
|
|
68
69
|
"hono": "4.13.0",
|
|
69
70
|
"ip-address": "10.4.0",
|
|
70
71
|
"posthog-node": "5.48.1",
|
|
@@ -75,8 +76,9 @@
|
|
|
75
76
|
},
|
|
76
77
|
"overrides": {
|
|
77
78
|
"@hono/node-server": "2.0.12",
|
|
78
|
-
"fast-uri": "3.1.
|
|
79
|
-
"ip-address": "10.4.0"
|
|
79
|
+
"fast-uri": "3.1.7",
|
|
80
|
+
"ip-address": "10.4.0",
|
|
81
|
+
"qs": "6.16.0"
|
|
80
82
|
},
|
|
81
83
|
"devDependencies": {
|
|
82
84
|
"@babel/parser": "7.29.8",
|
package/server.json
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
]
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.18.0",
|
|
21
21
|
"packages": [
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "trackly-cli",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.18.0",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"runtimeArguments": [
|
|
28
28
|
{
|
|
@@ -35,7 +35,7 @@ value-free phase receipt with `scripts/validate-phase-checkpoint.js` before
|
|
|
35
35
|
claiming that phase complete.
|
|
36
36
|
Read [references/legal-clarifications.md](references/legal-clarifications.md) when a form asks for a legal, regulatory, consent, or employer-relationship decision.
|
|
37
37
|
|
|
38
|
-
1. Call `trackly_get_apply_protocol`. Skill 4.
|
|
38
|
+
1. Call `trackly_get_apply_protocol`. Skill 4.8.0 requires protocol 3.7.0 or newer for new reliability work. Protocol 3.2 remains valid only for an already-active explicit legacy single run; an already-active explicit 3.2 single run may finish through its legacy path. Preserve the protocol's documented recovery paths for already-active older work. Require `compatibleSkillMajor: 4` and `compatibleSkillMinimumVersion` no newer than this installed skill. Reject an older or incompatible version for new work and report that the backend must finish updating or `trackly agent setup` must update the skill. Protocol 3.2 added exact-origin trust for jobs Trackly ingested directly from employer careers sources; do not recreate the retired ownership-timestamp gate in the client.
|
|
39
39
|
2. Call `trackly_get_profile_onboarding` or fetch both the profile schema and application profile. When present, render `schema.screens` in ascending `order` as one grouped question packet per screen. Within a screen, preserve category `order` and then field `order`; use each field's user-facing `rationale` when the user asks why it is needed. Ask only unknown or confirmation-needed fields and honor `consistencyRules` before submitting profile changes, resolving contradictions with the user instead of guessing. When `schema.screens` is absent on a legacy backend, fall back to the existing category-based onboarding behavior. Treat `completeness.percent` as required onboarding readiness only. Use `coverage.missingReusableKeys` to explain reusable optional gaps, while `coverage.contextualKeys` are intentionally asked only on the relevant employer form. Do not claim that 100% required completeness answers every possible application question.
|
|
40
40
|
3. Save answers with `trackly_update_application_profile`:
|
|
41
41
|
- Use `answered`, `intentionally_blank`, `declined`, or `unknown` exactly.
|
|
@@ -53,13 +53,14 @@ Read [references/legal-clarifications.md](references/legal-clarifications.md) wh
|
|
|
53
53
|
- Infer a direct prior-employer answer of No only when the profile explicitly marks history complete, the target and backend-confirmed aliases are absent, and the question is limited to direct employment. Ask for subsidiary, affiliate, acquisition, contractor, or otherwise ambiguous relationships.
|
|
54
54
|
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.
|
|
55
55
|
5. For every protocol 3.4 or newer request, call `trackly_get_active_apply_execution` before legacy recovery. Resume execution work only when the response says `active: true`. A response with `preserved: true` and `active: false` exposes a stopped, closed, or otherwise terminal execution only for read-only reconciliation. Never fetch its compact snapshot, continue its wave, mutate its members, or interpret historical `currentlyFilling` or reservation counts as current work; require `progress.nextAction: none`. Only with protocol 3.5 or newer and an enabled compact-snapshot capability may an active recovery or new execution call `trackly_get_apply_execution_snapshot`, using only the current member IDs, profile keys needed by the visible forms, and actual browser surface. An already-active protocol 3.4 execution is read-only legacy recovery: use only its published get or stop operations, never call the 3.5-only snapshot, resume, approval, advance, or disposition tools, and never mutate its browser forms. For protocol 3.5 or newer, treat snapshot `mutable` and `allowedOperations` values as authoritative. Never mutate or reopen authentication, account-creation, OTP, pre-form-CAPTCHA, or manual-only members. Only an explicit user request may call `trackly_resume_parked_apply_member`; the returned member still requires a fresh non-mutating probe before private data or form mutation. Resume unresolved waves in ascending order only for `active: true` and obey the server `nextAction` and funnel. Never reconstruct progress or choose replacements locally. Treat backend `achievementCount`, target-capped `completed`, `targetReached`, and `nextAction` as authoritative. `durablyReviewReady` and `submitted` remain current operator projections and must never be added together by the client to reconstruct cumulative completion. Let the backend own reservations, capacity, attempted-job deduplication, immutable waves, and advancement. Continue until `targetReached`, `queueExhausted`, a blocking `nextAction`, or the user stops.
|
|
56
|
-
- After full local context loss with no active execution, call `trackly_list_recoverable_apply_executions`. For every distinct returned `jobId`, call `trackly_get_job`, bind that Trackly-owned job record to the returned `candidateId`, and show the user its company, role, requisition identity when available,
|
|
56
|
+
- After full local context loss with no active execution, call `trackly_list_recoverable_apply_executions`. For every distinct returned `jobId`, call `trackly_get_job`, bind that Trackly-owned job record to the returned `candidateId`, and show the user its company, role, requisition identity when available, source-execution identity, and `accessKnowledge` scheduling reason when present; do not identify candidates from chat, tab order, search results, or page copy. A missing or mismatched Trackly job record blocks confirmation. An active personal deferment on any confirmed candidate blocks recovery until that deferment is cleared. Require explicit confirmation of one exact candidate set, then call `trackly_recover_exact_apply_members` with that source execution, snapshot hash, and only those candidate IDs. Verify `assertedCandidateIds`, `eligibleCandidateIds`, and the eligibility candidate IDs each contain exactly the confirmed set with no duplicates. Never add substitutes, newly saved jobs, or merely similar roles. Curated or historical walls stay in the exact recovered set as parked members and still require explicit resume plus a fresh probe.
|
|
57
57
|
- Exact recovery restores server lineage only. Treat tab restored, form state restored, and mutation authority restored as three independent receipts. Bind every recovered surface with a fresh recovery binding and inspection epoch, revalidate the full form, and preserve every user-edited or unknown non-empty value before any write. Earlier-epoch browser, field, upload, integrity, and submission evidence cannot authorize the recovered form.
|
|
58
58
|
- Every compact snapshot request must contain a non-empty list of only the current member IDs. Never request an empty or inferred all-members projection.
|
|
59
59
|
- When a visible form asks an office-scoped profile question and the exact employer office is known, request that field only through the matching member's `officeProjections` entry using the canonical `companyId:office-identity` scope. Consume only the returned `memberOfficeProfiles` entry for that same member and exact office; never move an office answer into the global profile map or reuse it for another member, employer, or office. A recovered member may reuse the answer only when Trackly proves its frozen company identity. If a legacy snapshot cannot prove that identity, preserve the form and treat the answer as unknown instead of refilling from chat or current mutable job metadata.
|
|
60
60
|
- Recover every entry in `execution.unresolvedWaves` in ascending `waveOrder`; `currentWave` identifies only the latest scheduling wave, not the complete recovery set.
|
|
61
61
|
- Immediately consume the start response's authoritative `progress` and `nextAction`; do not issue a speculative advance first.
|
|
62
|
-
- When that authoritative `nextAction` requests another wave, call `trackly_advance_apply_execution` with the actual current `browserSurface`, latest execution revision, and a fresh idempotency key.
|
|
62
|
+
- When that authoritative `nextAction` requests another wave, call `trackly_advance_apply_execution` with the actual current `browserSurface`, latest execution revision, and a fresh idempotency key. Pause on every returned immutable `proposedWave`, including ordinary OPEN or neutral proposals. Before asking for approval, show each server-frozen member in `memberPosition` order with its job ID, company, title, provider, requisition URL, and value-free scheduling reason; require the single projected `proposedWave` identity to match the corresponding `accessProposal.members[]` identity exactly. Missing, noncontiguous, or mismatched identity blocks approval; never substitute current search results, chat, browser state, or a later job lookup for the frozen proposal. Obtain the user's explicit approval of that exact ordered set, then call `trackly_advance_apply_execution` again with the unchanged `accessProposal.members[].jobId` order, its server-provided `accessProposal.approvalHash`, returned revision, same browser surface, and a fresh idempotency key. After `createdWave: true`, never reapprove that wave; ask only for a later `nextAction: access_review` where no wave was created. Never substitute a synthetic hash or infer approval from an earlier request to apply generally. Never describe a proposed job as accessible until the current live probe proves applicant fields. `freshLiveProbeRequired` remains true for every classification; curated OPEN never changes `allowedOperations` to `fill_form`.
|
|
63
|
+
- Progress may include `availableCandidateCount` and `deferredCandidateCount`. When `nextAction` is `access_review`, display the bounded deferred proposal, do not open a browser, and do not report the queue as exhausted. Offer probe, clear-deferment, stop, or expiry. Probe exact deferred job IDs only with the server-provided hash-bound `accessReviewApproval` after any active personal deferment for those jobs is cleared. List deferments with `trackly_list_apply_access_deferments`, create one with `trackly_defer_apply_access` from `jobId` and scope `job`, `company`, or `provider`, and clear one with `trackly_clear_apply_access_deferment` using a discovered deferment id. Provider scope is the approved global access-policy boundary: the backend derives the provider from the stable job anchor and blocks that provider across companies, proposal approval, recovery, and replay until explicitly cleared. Persist a provider exclusion only after the current authenticated user explicitly requests that policy; never infer a Workday, Eightfold, or other provider preference from this shared skill. Never submit a URL, provider name, or free-text identity.
|
|
63
64
|
- Recover an existing execution even when `batchOrchestration.accessibleExecution.enabled` is false. When disabled and an execution is active, recover it read-only; never start, advance, or record dispositions. Use only get or stop operations.
|
|
64
65
|
- If the active execution target differs from the user's requested target, explain the mismatch and require explicit confirmation. Only then stop it with reason `target_changed`, verify the exact execution reached `stopped` or `closed` from the stop response or `trackly_get_apply_execution`, and refetch until the active-execution response says `active: false`. A terminal execution appears with `preserved: true` only when reconciliation evidence remains, so its absence is not a blocker after the exact execution is terminal. Start the replacement target without continuing the stopped wave.
|
|
65
66
|
- When the user asks to stop, call `trackly_stop_apply_execution` with reason `user_requested`, verify the exact execution reached `stopped` or `closed` from the stop response or `trackly_get_apply_execution`, refetch until `active: false`, and never continue its unresolved waves. If the response also says `preserved: true`, keep that record read-only for reconciliation; do not require preservation when no unresolved evidence remains.
|
|
@@ -80,7 +81,7 @@ Read [references/legal-clarifications.md](references/legal-clarifications.md) wh
|
|
|
80
81
|
- 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.
|
|
81
82
|
6. When the selected job or current fixed batch member has no `runId`, call `trackly_start_apply_run` with the complete `batchId`, `memberId`, `expectedMemberVersion`, `expectedInspectionEpoch`, and `leaseToken` returned by Trackly. This is the sanctioned idempotent lookup for a recovered member whose `runId` is absent; call it with the exact recovered binding and Trackly will reuse the bound active run. When the member already has a `runId`, do not call `trackly_start_apply_run` in this or any subsequent start step: reuse that exact run and bind or recover its browser surface through `trackly_bind_apply_surface` as required. Never create a replacement run because browser control was interrupted.
|
|
82
83
|
- 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.
|
|
83
|
-
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new reliability execution member requires 3.
|
|
84
|
+
- Require `run.protocolVersion` to be 3.1.0 or newer and to share protocol major 3 with the fetched protocol. A new reliability execution member requires 3.7.0 or newer; already-active older work may use only the recovery path published by its fetched protocol. Never continue a pre-evidence 3.0.x run under skill 4.8.0. Preserve that run instead of starting a replacement, record it `blocked` with a value-free protocol-upgrade reason when possible, and tell the user the saved job can be retried only after the stale run is cleared through Trackly's supported lifecycle. Stop and refetch the protocol and active execution or batch if support level, execution mode, provider, required scenarios, authorized origin policy, member version, or inspection epoch changes after run creation.
|
|
84
85
|
- 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.
|
|
85
86
|
7. Pass the browser readiness gate before preparing resume bytes:
|
|
86
87
|
- Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
|
|
@@ -125,13 +126,15 @@ Follow this order:
|
|
|
125
126
|
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.
|
|
126
127
|
6. Recheck email and phone through both browser DOM state and macOS accessibility state. Require exact values and reject duplicate/concatenated values.
|
|
127
128
|
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.
|
|
129
|
+
- Before asking, query the typed scopes in [references/answer-resolution.md](references/answer-resolution.md) from narrowest to broadest and retain only value-free counts and fingerprints. Semantic similarity may find a canonical intent candidate but never supplies an answer.
|
|
130
|
+
- Account for every visible control with exactly one typed disposition and validate the `fill` receipt. Reconcile all canonical education records and position-level employment records, then audit every resume stage separately. Aggregate counts or a successful upload call alone are not completeness proof.
|
|
128
131
|
- Enter education and employment-history rows in reverse chronological order, with the most recent or current record first.
|
|
129
132
|
- Keep employment status, current company, and most recent employer distinct. An intentionally blank current company remains blank and never implies an employment status; read employment status independently from its canonical profile field or ask once. The blank also does not erase prior employment. For “most recent employer/title,” use `employment.most_recent_company` and `employment.most_recent_title` only after the fetched profile schema exposes those exact keys. If an exposed key is unknown, ask once and sync the confirmed value globally through `trackly_update_application_profile` instead of inserting the current-company blank or inventing an employer. If either key is absent from the fetched schema, do not PATCH it; preserve the answer locally for the current form and report that the backend schema must update.
|
|
130
133
|
- Use the canonical English school name from Trackly when the ATS offers that exact committed option. If a closed ATS selector does not offer it, select only a verified catalog option for the same institution; never leave free-typed text as though it were committed.
|
|
131
134
|
- 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.
|
|
132
135
|
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.
|
|
133
136
|
9. For a free-text application response, read [references/application-writing.md](references/application-writing.md). Draft from supported profile and role facts, then run Humanizer automatically when available. Treat its output as a new revision: rebuild the complete local claim-reference packet against that exact final text, set `claimsComplete: true` only after confirming that the packet covers the whole revision, and then call `trackly_lint_application_text`. Apply the self-contained gate whether or not Humanizer is available, and do not block the application merely because Humanizer is unavailable. Do not enter the answer until the exact final revision passes deterministic lint and every claim has a supported evidence reference. Omitting claim metadata is a blocking lint failure, including for drafts the agent believes contain no claims.
|
|
134
|
-
10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner.
|
|
137
|
+
10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner. Reinventory after conditional reveals and require the final form-inventory fingerprint to cover the exact current surface.
|
|
135
138
|
|
|
136
139
|
If browser startup or file work reports `ENOSPC`, `EACCES`, quota, or another I/O error, call `trackly_diagnose_local_path` on the exact implicated path before explaining the cause. Never generalize one path failure to the whole disk without matching measured evidence.
|
|
137
140
|
|
|
@@ -155,7 +158,7 @@ the sole active receipt returned by Trackly; if multiple receipts remain, ask
|
|
|
155
158
|
the user to identify the intended group. Never guess a receipt from tabs or
|
|
156
159
|
chat history.
|
|
157
160
|
|
|
158
|
-
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`.
|
|
161
|
+
After every conditional field is settled and all other review gates pass, bulk checkpoint every ready member with `review/manual_submit`, literal `continuationAllowed: false`, the current unchanged inspection epoch in both epoch fields, and any `resolvedActionIds`. Preserve the accepted checkpoint response and use its returned member versions and epochs; this checkpoint resolves the listed human actions and moves the durable member lifecycle to `review_ready` without invalidating current-epoch browser evidence or an unchanged resume-content approval. On `Apply checkpoint action fields are invalid` or another schema rejection, refetch once and fail closed unless the current public contract proves an exact compatible payload. Never retry a malformed payload or claim durable readiness. Do not wait for `needs_input` members: whenever at least one member is durably `review_ready`, ask for one explicit final truthfulness confirmation for the exact complete subset that is currently `review_ready`. If any form in that certified subset used a resume attachment, certify with `resumeDependency: approved` and the exact approved resume identity. If no form in the subset exposes a resume control, certify with `resumeDependency: not_applicable` and omit the resume ID and hash; do not invent an upload requirement. After that confirmation is recorded, validate the value-free review receipt against the preserved accepted checkpoint response and current visibility proof; include the resolved-action count and fingerprint, never the raw IDs. Then call `trackly_record_application_outcomes` once with every member in that certified subset using its exact current `runId`, `batchId`, `memberId`, `inspectionEpoch`, and `leaseToken`; every item must use the literal `outcome: review_ready`. Verify every recorded run returns `awaiting_manual_submit` before showing the review handoff. A member conflict does not invalidate successful siblings: preserve its tab, refresh that member once, and surface the returned stable conflict code instead of blindly replaying a different transition. A member that becomes ready later requires a fresh certification for the then-current complete `review_ready` subset; never reuse an older certification for it. Use singular `trackly_record_application_outcome` only for a legacy single run or an isolated post-review transition. Use the normal review block in [references/review-handoff.md](references/review-handoff.md) only after documented visibility proof for the exact review tab succeeds. Always validate a `handoff` receipt that reports employer, Trackly, and browser state separately. If visibility is unverified, preserve the tab, set `reviewReadyClaimed: false`, use the separate visibility-unverified block, and do not tell the user to submit until the tab is reclaimed and visibly proven. For a single run, keep the browser on the final review state and stop. For a frozen batch, preserve every review-ready tab, hand off each certified review-ready subset without waiting on unrelated human actions, and continue the same lifecycle for the remaining mapped members. Members with unresolved actions stay frozen and resumable. Never submit any member. End with job ID -> run ID -> browser tab -> ATS -> status plus the actual scenario coverage for each run.
|
|
159
162
|
|
|
160
163
|
After the user submits manually:
|
|
161
164
|
|
|
@@ -66,3 +66,8 @@ durably terminal. A provider hint is scheduling input, never sufficient proof.
|
|
|
66
66
|
|
|
67
67
|
When browser and accessibility surfaces disagree, use
|
|
68
68
|
`unknown_unobservable`. Never upgrade uncertainty to accessibility.
|
|
69
|
+
|
|
70
|
+
Keep the audit UI's four labels (`OPEN`, `ACCOUNT WALL`, `VARIES`, `UNKNOWN`)
|
|
71
|
+
separate from these detailed live classifications. Curated `OPEN` and recent
|
|
72
|
+
accessibility can schedule a probe; they never authorize `fill_form`.
|
|
73
|
+
`freshLiveProbeRequired` remains true for every proposed member.
|
|
@@ -32,6 +32,23 @@ revision, schema key, scope, authority class, derivation rule when any,
|
|
|
32
32
|
expected type, and committed-control receipt. Never send the answer value in
|
|
33
33
|
an observation.
|
|
34
34
|
|
|
35
|
+
## Typed answer-memory lookup
|
|
36
|
+
|
|
37
|
+
Before asking, query every applicable scope in this narrow-to-broad order:
|
|
38
|
+
run-only live choice, exact question and version, exact office, jurisdiction,
|
|
39
|
+
company, provider, then global. A candidate is reusable only when its canonical
|
|
40
|
+
intent, type, sensitivity, question version when applicable, and permitted
|
|
41
|
+
scope all match. The first compatible narrower answer wins; an answer never
|
|
42
|
+
flows to a broader scope without explicit user confirmation.
|
|
43
|
+
|
|
44
|
+
Question-ontology aliases, semantic retrieval, and form-schema history may
|
|
45
|
+
identify candidate canonical intents, but they are not answer authority. Exact
|
|
46
|
+
typed profile or contextual state must still resolve the value. Fail closed to
|
|
47
|
+
`missing_fact`, `live_consent`, or `forbidden_inference` when intent, type,
|
|
48
|
+
scope, version, or confidence is ambiguous. Record only per-scope counts and a
|
|
49
|
+
lowercase SHA-256 over value-free lookup inputs and decisions; never hash or
|
|
50
|
+
persist labels, answers, page text, or private values in the checkpoint.
|
|
51
|
+
|
|
35
52
|
## Source precedence
|
|
36
53
|
|
|
37
54
|
The current profile revision is the only reusable answer authority. A
|
|
@@ -40,6 +57,14 @@ or cached value is never authority. Resume parsing is a suggestion only: it may
|
|
|
40
57
|
help identify a schema key, but the parser never becomes authority and never
|
|
41
58
|
overwrites an answered profile value or a user-edited control.
|
|
42
59
|
|
|
60
|
+
For a frozen batch, distinguish its frozen profile revision (the audit and
|
|
61
|
+
certification baseline) from the current bounded snapshot projection used to
|
|
62
|
+
resolve visible answers. A later scoped answer may legitimately appear in the
|
|
63
|
+
current projection while the frozen baseline number remains unchanged. Do not
|
|
64
|
+
treat that difference as corruption, do not silently substitute the stale
|
|
65
|
+
baseline value, and do not mutate until the current work receipt authorizes the
|
|
66
|
+
projection and certification behavior.
|
|
67
|
+
|
|
43
68
|
Contact details and addresses must resolve from exact current profile keys.
|
|
44
69
|
Verify their live committed state even when the browser initially displays a
|
|
45
70
|
masked or formatted value.
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
Protocol 3.4 adds a server-owned Apply execution above immutable child batches.
|
|
4
4
|
Protocol 3.6 adds exact-member recovery after local context loss and scoped
|
|
5
5
|
review-handoff reconciliation.
|
|
6
|
+
Protocol 3.7 adds access-aware scheduling: curated ATS defaults, user
|
|
7
|
+
deferments, and live-probe observations produce frozen `accessKnowledge` on
|
|
8
|
+
every proposed-wave member, batch member, compact snapshot member, and
|
|
9
|
+
recovery candidate. Historical knowledge can schedule work but can never
|
|
10
|
+
authorize form filling.
|
|
11
|
+
|
|
6
12
|
After context loss, rediscover active handoff receipts with
|
|
7
13
|
`trackly_list_apply_review_handoffs`; never reconstruct or guess a handoff ID
|
|
8
14
|
from chat history, tab order, or application similarity.
|
|
@@ -58,10 +64,57 @@ draft, question, review, submission, or closure obligation after a newer
|
|
|
58
64
|
replacement wave was created. Use `response.execution.currentWave.batchId`
|
|
59
65
|
only as the latest scheduling identity when following `response.progress.nextAction`; it is not
|
|
60
66
|
the complete recovery set. For an advance response that creates a wave, use
|
|
61
|
-
its top-level `response.batchId
|
|
67
|
+
its top-level `response.batchId` and the immutable `proposedWave` with frozen
|
|
68
|
+
`accessKnowledge` receipts. Never guess across those response shapes. If
|
|
62
69
|
the applicable field is null or `unresolvedWaves` is empty, follow
|
|
63
70
|
`response.progress.nextAction` rather than guessing a prior batch.
|
|
64
71
|
|
|
72
|
+
## Access-aware scheduling
|
|
73
|
+
|
|
74
|
+
Every proposed-wave member, execution-owned batch member, compact snapshot
|
|
75
|
+
member, and recovery candidate includes `accessKnowledge`. Use it only to
|
|
76
|
+
schedule. A fresh non-mutating live probe is still required before private
|
|
77
|
+
data or form mutation.
|
|
78
|
+
|
|
79
|
+
Rank `OPEN`, then neutral `VARIES`/`UNKNOWN`, then fresh `ACCOUNT WALL`. Never
|
|
80
|
+
select an active user deferment automatically. Workday starts as `ACCOUNT WALL`,
|
|
81
|
+
is authentication-gated for this workflow, and must not be opened for a browser
|
|
82
|
+
probe while Greenhouse or other `OPEN`/neutral alternatives exist. Eightfold
|
|
83
|
+
starts as `ACCOUNT WALL`, is independently authentication-gated, and must not be
|
|
84
|
+
opened for a browser probe under the same condition. Static safety exclusions
|
|
85
|
+
always override `OPEN`.
|
|
86
|
+
An active job, company, or provider deferment is an unconditional no-browser
|
|
87
|
+
rule, regardless of whether another accessible candidate remains.
|
|
88
|
+
|
|
89
|
+
Read `progress.availableCandidateCount`, `progress.deferredCandidateCount`, and
|
|
90
|
+
`nextAction`. When `nextAction` is `access_review`, return the bounded deferred
|
|
91
|
+
proposal, open nothing, and do not report the queue as exhausted. Probe those
|
|
92
|
+
exact job IDs only with hash-bound `accessReviewApproval` after clearing any
|
|
93
|
+
active personal deferment. List, defer, or clear deferments only through
|
|
94
|
+
`trackly_list_apply_access_deferments`, `trackly_defer_apply_access`, and
|
|
95
|
+
`trackly_clear_apply_access_deferment` using a Trackly `jobId`; never submit a
|
|
96
|
+
URL, provider name, or raw chat. Scope `provider` is the approved global
|
|
97
|
+
user-policy boundary: the server derives that identity and blocks matching
|
|
98
|
+
candidates across companies, proposal approval, recovery, and replay. Create a
|
|
99
|
+
provider deferment only after the current authenticated user explicitly requests
|
|
100
|
+
that persistent provider policy; never infer it from this shared skill.
|
|
101
|
+
|
|
102
|
+
Pause after every `proposedWave`. Same-key advance replay returns the same
|
|
103
|
+
member IDs, order, and rationale. Browser probing requires explicit wave
|
|
104
|
+
approval, including when every proposed job is OPEN or neutral. Show every
|
|
105
|
+
server-frozen member in exact `memberPosition` order with its job ID, company,
|
|
106
|
+
title, provider, requisition URL, and a short value-free reason such as
|
|
107
|
+
“Greenhouse default: OPEN, audited 6 days ago; fresh probe still required.”
|
|
108
|
+
Require each simple `proposedWave` identity to equal the corresponding rich
|
|
109
|
+
`accessProposal.members[]` identity. Missing, noncontiguous, or mismatched
|
|
110
|
+
identity blocks approval; never substitute chat, browser state, search results,
|
|
111
|
+
or a later mutable job lookup. Never describe a proposed job as accessible until the current
|
|
112
|
+
probe proves it. Bind approval to the unchanged
|
|
113
|
+
ordered `accessProposal.members[].jobId` values and the exact server-provided
|
|
114
|
+
`accessProposal.approvalHash`; never invent a hash or reuse a general request
|
|
115
|
+
to apply as approval of a specific proposed wave.
|
|
116
|
+
`freshLiveProbeRequired` remains true for every classification.
|
|
117
|
+
|
|
65
118
|
## Exact recovery after local context loss
|
|
66
119
|
|
|
67
120
|
When no active execution exists but unfinished lineage may still be retained,
|
|
@@ -70,7 +123,8 @@ and candidates are the complete bounded recovery menu. Resolve every distinct
|
|
|
70
123
|
returned `jobId` through `trackly_get_job`, bind that Trackly-owned record to
|
|
71
124
|
its `candidateId`, and present its company, role, requisition identity when
|
|
72
125
|
available, and source execution to the user. A missing or mismatched Trackly
|
|
73
|
-
job record blocks confirmation.
|
|
126
|
+
job record blocks confirmation. An active personal deferment on any confirmed
|
|
127
|
+
candidate blocks recovery until that deferment is cleared. Require explicit confirmation of the exact
|
|
74
128
|
candidate set. Never infer the set from browser tabs, conversation memory,
|
|
75
129
|
search results, page copy, employer similarity, or queue position.
|
|
76
130
|
|
|
@@ -201,7 +255,8 @@ For an execution request:
|
|
|
201
255
|
5. bring accessible members through the ordinary batch integrity and review
|
|
202
256
|
gates; and
|
|
203
257
|
6. refetch progress, then call `trackly_advance_apply_execution` only when the
|
|
204
|
-
current wave has no unclassified member.
|
|
258
|
+
current wave has no unclassified member. Pause on the returned
|
|
259
|
+
`proposedWave`. Follow `nextAction: access_review` without opening a browser.
|
|
205
260
|
|
|
206
261
|
For an explicit fixed inspection request or legacy recovery:
|
|
207
262
|
|
|
@@ -6,11 +6,24 @@ other.
|
|
|
6
6
|
|
|
7
7
|
## Capability gate
|
|
8
8
|
|
|
9
|
-
Before
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Before an automated upload, require the active browser adapter to advertise all
|
|
10
|
+
five capabilities: semantic control discovery, chooser arming, file attachment,
|
|
11
|
+
committed-filename inspection, and parser-field recheck. If only chooser arming
|
|
12
|
+
or file attachment is unavailable, fail closed before opening the chooser and
|
|
13
|
+
hand the upload to the user only when semantic control discovery,
|
|
14
|
+
committed-filename inspection, and parser-field recheck remain available. If
|
|
15
|
+
any of those three verification capabilities is unavailable, stop the fill as
|
|
16
|
+
unverified; do not route it into `manual_unbound`, claim a passed audit, or
|
|
17
|
+
continue to review. Never navigate to a `file://` URL or paste a local path into
|
|
18
|
+
an ordinary text field.
|
|
19
|
+
|
|
20
|
+
When the user performs that handoff upload, keep it explicitly manual,
|
|
21
|
+
browser-local, and unbound. Record `resumeAudit.mode: manual_unbound`,
|
|
22
|
+
`approval: passed`, `preAttachVerification: not_applicable`, and
|
|
23
|
+
`attachmentCommit: user_confirmed` only for this genuine manual path; do not use
|
|
24
|
+
those values for an agent-performed upload or a failed verifier. Before
|
|
25
|
+
continuing, require `passed` filename verification and parser recheck, then
|
|
26
|
+
prove during the `passed` final sweep that the attachment and filename persist.
|
|
14
27
|
|
|
15
28
|
Perform these stages in exact order:
|
|
16
29
|
|
|
@@ -19,14 +32,19 @@ Perform these stages in exact order:
|
|
|
19
32
|
3. prove the chooser opened;
|
|
20
33
|
4. run `trackly_verify_prepared_resume`, then immediately attach that verified
|
|
21
34
|
file through the adapter's documented file-setting primitive;
|
|
22
|
-
5. verify the employer-facing filename committed;
|
|
35
|
+
5. verify the employer-facing filename committed;
|
|
23
36
|
6. recheck every contact, employment, education, and other field that resume
|
|
24
|
-
parsing may have changed
|
|
37
|
+
parsing may have changed; and
|
|
38
|
+
7. during the final sweep, prove the attachment is still committed and the
|
|
39
|
+
visible filename remains the approved employer-facing filename.
|
|
25
40
|
|
|
26
41
|
Pass only the value-free stage outcomes to
|
|
27
42
|
`trackly_validate_apply_resume_upload`. Do not pass a filename, local path,
|
|
28
43
|
resume hash, form value, URL, page text, or tab identifier. Claim attachment
|
|
29
|
-
success only when it returns `safeToClaimAttachment: true`.
|
|
44
|
+
success only when it returns `safeToClaimAttachment: true`. Record the six
|
|
45
|
+
audited outcomes separately: approval, pre-attach verification, attachment
|
|
46
|
+
commit, filename verification, parser recheck, and final-sweep persistence. A
|
|
47
|
+
single upload-success boolean cannot replace this chain.
|
|
30
48
|
|
|
31
49
|
Stable failure codes include capability unavailable, ambiguous control,
|
|
32
50
|
chooser timeout, chooser opened without a commit, missing or expired file,
|
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Run this gate after each semantic selection and again across the entire form before review.
|
|
4
4
|
|
|
5
|
+
## Whole-form accounting
|
|
6
|
+
|
|
7
|
+
Create one accounting row for every visible control across every reachable
|
|
8
|
+
step, including optional, repeated, conditional, consent, and attachment
|
|
9
|
+
controls. Each row has one semantic fingerprint and exactly one disposition:
|
|
10
|
+
`filled_exact_profile`, `filled_safe_derivation`, `filled_supported_draft`,
|
|
11
|
+
`preserved_user_edit`, `missing_fact`, `live_consent`,
|
|
12
|
+
`authentication_blocker`, `unobservable_commit`, `unsupported_control`, or
|
|
13
|
+
`not_applicable`. The inventory count must equal committed plus typed
|
|
14
|
+
exceptions, and known omissions must be zero. Reinventory after navigation,
|
|
15
|
+
conditional reveal, resume parsing, validation, and rerender; an earlier
|
|
16
|
+
inventory cannot prove a later form complete.
|
|
17
|
+
|
|
18
|
+
Reconcile education and employment separately against all canonical eligible
|
|
19
|
+
records. Education entries and position-level employment records must be
|
|
20
|
+
accounted for in reverse chronological order. Multiple promotions at one
|
|
21
|
+
employer remain separate positions. Preserve user-owned rows, use only the date
|
|
22
|
+
precision actually stored, and ask when a form requires missing precision;
|
|
23
|
+
never accept a default month or invent promotion, start, or end precision. A
|
|
24
|
+
flattened employer summary or a “most recent roles” subset fails reconciliation.
|
|
25
|
+
Keep only value-free counts and fingerprints in the checkpoint.
|
|
26
|
+
|
|
5
27
|
## Local field ownership
|
|
6
28
|
|
|
7
29
|
Maintain a private local field ledger keyed by execution, run, inspection
|