trackly-cli 0.6.2 → 0.7.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 CHANGED
@@ -99,7 +99,9 @@ trackly agent setup --client codex # or claude / both
99
99
  trackly agent doctor
100
100
  ```
101
101
 
102
- The skill uses the profile and default resume in your Trackly account, asks only missing questions, fills user-approved saved jobs, and always stops before Submit. Greenhouse is fully supported; Ashby and Lever are best effort.
102
+ The skill uses the profile and default resume in your Trackly account, asks only missing questions, fills user-approved saved jobs, and always stops before Submit. Support is fetched from Trackly at the start of every run: Greenhouse is full, Ashby and Lever are best effort, and 27 additional named ATS/provider classes use constrained guided mode. Employer-hosted unknown forms run only when Trackly binds them to a verified company domain. LinkedIn-hosted forms and unverified origins remain manual-only; a separately stored external application URL is evaluated under its own ATS and origin policy.
103
+
104
+ Guided mode is deliberately fail-closed. The agent stops on credential entry, OTP/email verification, CAPTCHA/human verification, an unexpected employer or origin, a submit-only transition, or any field whose committed state cannot be observed. `trackly agent doctor` checks the local skill, MCP registration, protocol compatibility, declared browser/computer-use configuration, profile completeness, and default-resume metadata. Live semantic browser capability and the exact resume bytes are verified at the start of a real run.
103
105
 
104
106
  #### Claude Code one-liner
105
107
 
package/bin/trackly CHANGED
@@ -969,7 +969,8 @@ async function cmdAgent(args) {
969
969
  console.log(color('bold', 'Trackly Apply doctor'));
970
970
  console.log(`Overall: ${color(report.ok ? 'green' : 'yellow', report.ok ? 'ready' : 'needs attention')}`);
971
971
  console.log(`Authenticated: ${report.authenticated ? 'yes' : 'no'}`);
972
- console.log(`Protocol compatible: ${report.compatible ? 'yes' : 'no'}`);
972
+ console.log(`Protocol: ${report.protocol?.version || 'unavailable'}; compatible=${report.compatible ? 'yes' : 'no'}`);
973
+ console.log(`Semantic browser + computer use: ${report.browserControl.semanticReady ? 'ready' : 'not verified'}`);
973
974
  for (const client of report.clients) {
974
975
  console.log(`${client.client}: executable=${client.executable ? 'yes' : 'no'}, skill=${client.installed ? 'yes' : 'no'}, MCP=${client.mcpRegistered ? 'yes' : 'no'}`);
975
976
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "2.0.0",
2
+ "contractVersion": "3.0.0",
3
3
  "tools": {
4
4
  "trackly_get_apply_queue": "{limit:z.number().int().min(1).max(100).optional()}",
5
5
  "trackly_get_application_profile": "{includeSensitive:z.boolean().optional(),provider:z.string().max(100).optional(),companyId:z.string().max(100).optional()}",
@@ -7,9 +7,9 @@
7
7
  "trackly_update_application_profile": "{expectedRevision:z.number().int().min(1),source:z.enum(['web','ios','macos','codex','claude','mcp']).optional(),changes:z.array(z.discriminatedUnion('scope',[z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('global'),questionLabel:z.string().max(1000).optional()}),z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('provider'),scopeValue:z.string().min(1).max(200),questionLabel:z.string().max(1000).optional()}),z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('company'),scopeValue:z.string().min(1).max(200),questionLabel:z.string().max(1000).optional()})])).max(100).optional(),education:z.array(z.object({school:z.string().min(1).max(500),degree:z.string().max(500).nullable().optional(),fieldOfStudy:z.string().max(500).nullable().optional(),gpa:z.string().max(50).nullable().optional(),startDate:z.string().max(50).nullable().optional(),endDate:z.string().max(50).nullable().optional()})).max(20).optional(),confirmProfile:z.boolean().optional(),sensitiveStorageConsent:z.boolean().optional()}",
8
8
  "trackly_start_apply_run": "{jobId:z.number().int().min(1),clientName:z.string().max(100).optional()}",
9
9
  "trackly_get_apply_protocol": "{}",
10
- "trackly_report_apply_observation": "{runId:z.number().int().min(1).optional(),provider:z.string().min(1).max(100),fieldLabel:z.string().min(1).max(1000),observationType:z.string().min(1).max(100),resolutionCode:z.string().max(100).optional(),metadata:z.object({controlType:z.string().max(100).optional(),required:z.boolean().optional(),errorCode:z.string().max(100).optional(),committed:z.boolean().optional()}).optional()}",
10
+ "trackly_report_apply_observation": "{runId:z.number().int().min(1),provider:z.string().regex(SAFE_OBSERVATION_CODE),fieldLabel:z.string().min(1).max(1000),observationType:z.string().regex(SAFE_OBSERVATION_CODE),resolutionCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),metadata:z.object({controlType:z.string().regex(SAFE_OBSERVATION_CODE).optional(),required:z.boolean().optional(),errorCode:z.string().regex(SAFE_OBSERVATION_CODE).optional(),committed:z.boolean(),scenarioCode:z.enum(APPLY_SCENARIO_CODES),browserSurface:z.enum(APPLY_BROWSER_SURFACES),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/).optional(),resumedAfterHandoff:z.boolean().optional()})}",
11
11
  "trackly_record_application_outcome": "{runId:z.number().int().min(1),outcome:z.enum(['review_ready','submitted','failed','blocked']),confirmation:z.string().max(500).optional()}",
12
- "trackly_prepare_resume": "{runId:z.number().int().min(1)}",
12
+ "trackly_prepare_resume": "{runId:z.number().int().min(1),browserSurface:z.enum(APPLY_BROWSER_SURFACES),browserBindingHash:z.string().regex(/^[a-f0-9]{64}$/)}",
13
13
  "trackly_verify_prepared_resume": {
14
14
  "local": "{runId:z.number().int().min(1),confirmationId:z.string().min(1).max(200),exactLocalPath:z.string().min(1).max(4096),sha256:z.string().regex(/^[a-f0-9]{64}$/i),sizeBytes:z.number().int().min(1),expiresAt:z.string().datetime()}",
15
15
  "hosted": "{runId:z.number().int().min(1),confirmationId:z.string().min(1).max(200)}"
@@ -94,12 +94,12 @@ You can also pass `TRACKLY_API_KEY` as an environment variable for one-off runs.
94
94
  - **trackly_update_application_profile** — Save explicit answer states with optimistic concurrency and global/provider/company scope.
95
95
  - **trackly_start_apply_run** — Start a manual-submit browser run for a queued job.
96
96
  - **trackly_get_apply_protocol** — Get the current ATS support, browser integrity rules, and compatible skill version.
97
- - **trackly_report_apply_observation** — Report redacted ATS mechanics without answer values.
97
+ - **trackly_report_apply_observation** — Report redacted ATS mechanics and actual scenario coverage without answer values or page text.
98
98
  - **trackly_record_application_outcome** — Record review readiness or a confirmed manual submission.
99
99
  - **trackly_prepare_resume** — Local MCP only: materialize the default resume in a private, expiring mode-0600 cache and return filename, size, SHA-256, exact local path, and visual-confirmation metadata. Hosted MCP returns a manual/local-agent requirement.
100
100
  - **trackly_verify_prepared_resume** — Local MCP only: immediately before attachment, recompute the user-confirmed resume hash and size, validate the exact path/run/expiration, and lock the file read-only. Any mismatch requires a fresh preview and confirmation.
101
101
 
102
- Apply contract v2 intentionally gives this verifier different local and hosted schemas: local MCP receives the full proof needed to inspect the private file, while hosted MCP accepts only run and confirmation identifiers and returns the manual/local-agent requirement. Local paths and fingerprints are never sent to the hosted verifier.
102
+ Apply contract v3 intentionally gives this verifier different local and hosted schemas: local MCP receives the full proof needed to inspect the private file, while hosted MCP accepts only run and confirmation identifiers and returns the manual/local-agent requirement. Local paths and fingerprints are never sent to the hosted verifier. Version 3 also requires explicit committed-state evidence on every Apply observation.
103
103
 
104
104
  ### Maintenance behavior
105
105
 
package/lib/agent.js CHANGED
@@ -7,12 +7,13 @@ const path = require('node:path');
7
7
  const { spawnSync } = require('node:child_process');
8
8
  const { apiRequest, downloadFile, getConfigPaths, hasAuth, maintenanceOutput } = require('./client');
9
9
  const { version: PACKAGE_VERSION } = require('../package.json');
10
- const SKILL_VERSION = '2.1.0';
10
+ const SKILL_VERSION = '4.0.0';
11
11
  const CLI_USER_AGENT = `trackly-cli/${PACKAGE_VERSION}`;
12
12
  const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
13
13
 
14
14
  const SKILL_NAME = 'trackly-apply';
15
15
  const SKILL_MAJOR = Number(SKILL_VERSION.split('.')[0]);
16
+ const MIN_APPLY_PROTOCOL_VERSION = '3.0.0';
16
17
  const CACHE_TTL_MS = 2 * 60 * 60 * 1000;
17
18
  const MANAGED_FILE = '.trackly-managed.json';
18
19
  const RESUME_PROOF_FILE = '.trackly-resume-proof.json';
@@ -597,15 +598,23 @@ function validateResumeFile(download) {
597
598
  };
598
599
  }
599
600
 
600
- async function prepareResume(runId, userAgent = MCP_USER_AGENT) {
601
+ async function prepareResume(runId, browserSurface, browserBindingHash, userAgent = MCP_USER_AGENT) {
601
602
  if (!hasAuth()) throw new Error('Authenticate first with `trackly login` or `trackly config --api-key ...`.');
602
603
  const normalizedRunId = Number(runId);
603
604
  if (!Number.isInteger(normalizedRunId) || normalizedRunId < 1) {
604
605
  throw new Error('A valid Trackly Apply run ID is required before preparing a resume.');
605
606
  }
607
+ if (!['codex_in_app', 'chrome_extension', 'claude_in_chrome'].includes(browserSurface)) {
608
+ throw new Error('A supported semantic browser surface is required before preparing a resume.');
609
+ }
610
+ if (!/^[a-f0-9]{64}$/.test(String(browserBindingHash || ''))) {
611
+ throw new Error('A lowercase SHA-256 browser binding is required before preparing a resume.');
612
+ }
606
613
  cleanResumeCache();
607
614
  const download = await downloadFile(
608
- `/api/jobscout/application-profile/default-resume?runId=${normalizedRunId}`,
615
+ `/api/jobscout/application-profile/default-resume?runId=${normalizedRunId}`
616
+ + `&browserSurface=${encodeURIComponent(browserSurface)}`
617
+ + `&browserBindingHash=${browserBindingHash}`,
609
618
  userAgent,
610
619
  );
611
620
  try {
@@ -618,17 +627,41 @@ async function prepareResume(runId, userAgent = MCP_USER_AGENT) {
618
627
  }
619
628
  }
620
629
 
630
+ function protocolAtLeast(version, minimum = MIN_APPLY_PROTOCOL_VERSION) {
631
+ const parse = (value) => String(value || '').split('.').map((part) => Number(part));
632
+ const actual = parse(version);
633
+ const required = parse(minimum);
634
+ if (actual.length < 2 || actual.some((part) => !Number.isInteger(part) || part < 0)) return false;
635
+ for (let index = 0; index < Math.max(actual.length, required.length); index++) {
636
+ const left = actual[index] || 0;
637
+ const right = required[index] || 0;
638
+ if (left !== right) return left > right;
639
+ }
640
+ return true;
641
+ }
642
+
643
+ function liveBrowserReady(browserControl) {
644
+ return (browserControl?.codex === true && browserControl?.codexComputerUse === true)
645
+ || browserControl?.claude === true;
646
+ }
647
+
621
648
  async function doctorAgent() {
622
649
  const cache = cleanResumeCache();
650
+ const codexBrowser = fs.existsSync(path.join(os.homedir(), '.codex', 'plugins', 'cache', 'openai-bundled', 'chrome'));
651
+ const codexComputerUse = fs.existsSync(path.join(os.homedir(), '.codex', 'plugins', 'cache', 'openai-bundled', 'computer-use'));
652
+ const browserControl = {
653
+ codex: codexBrowser,
654
+ codexComputerUse,
655
+ claude: null,
656
+ };
623
657
  const report = {
624
658
  skillVersion: SKILL_VERSION,
625
659
  authenticated: hasAuth(),
626
660
  clients: ['codex', 'claude'].map(inspectClient),
627
661
  browserControl: {
628
- codex: fs.existsSync(path.join(os.homedir(), '.codex', 'plugins', 'cache', 'openai-bundled', 'chrome')),
629
- codexComputerUse: fs.existsSync(path.join(os.homedir(), '.codex', 'plugins', 'cache', 'openai-bundled', 'computer-use')),
630
- claude: null,
631
- note: 'Capability is client-managed; verify Chrome/browser and computer-use tools are enabled before a live run.',
662
+ ...browserControl,
663
+ semanticReady: liveBrowserReady(browserControl),
664
+ note: 'A live run requires both semantic browser control and computer use. Claude capability must be verified by the active client before the run starts.',
632
665
  },
633
666
  cache,
634
667
  protocol: null,
@@ -649,7 +682,8 @@ async function doctorAgent() {
649
682
  completeness: profile.profile?.completeness,
650
683
  hasDefaultResume: Boolean(profile.profile?.defaultResume),
651
684
  };
652
- report.compatible = Number(report.protocol.compatibleSkillMajor) === SKILL_MAJOR;
685
+ report.compatible = Number(report.protocol.compatibleSkillMajor) === SKILL_MAJOR
686
+ && protocolAtLeast(report.protocol.version, MIN_APPLY_PROTOCOL_VERSION);
653
687
  if (report.profile.hasDefaultResume) {
654
688
  report.resume = {
655
689
  available: true,
@@ -662,6 +696,7 @@ async function doctorAgent() {
662
696
  }
663
697
  report.ok = report.authenticated
664
698
  && report.compatible
699
+ && report.browserControl.semanticReady
665
700
  && report.clients.some((client) => client.executable && client.installed && client.mcpRegistered)
666
701
  && Boolean(report.profile?.hasDefaultResume)
667
702
  && report.resume.available
@@ -681,6 +716,7 @@ module.exports = {
681
716
  CLI_USER_AGENT,
682
717
  MCP_USER_AGENT,
683
718
  SKILL_MAJOR,
719
+ MIN_APPLY_PROTOCOL_VERSION,
684
720
  SKILL_NAME,
685
721
  bundledSkillDir,
686
722
  cacheDir,
@@ -689,8 +725,10 @@ module.exports = {
689
725
  clientSkillDir,
690
726
  doctorAgent,
691
727
  inspectClient,
728
+ liveBrowserReady,
692
729
  materializeResume,
693
730
  prepareResume,
731
+ protocolAtLeast,
694
732
  resumeConfirmation,
695
733
  resumeCacheName,
696
734
  resumeValidationStatus,
package/mcp/server.js CHANGED
@@ -11,6 +11,13 @@ const { version: PACKAGE_VERSION } = require('../package.json');
11
11
  const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
12
12
  const MCP_MAINTENANCE_ERROR_CODE = -32002;
13
13
  const AUTH_HINT = 'Run `trackly login` or set TRACKLY_API_KEY. Get a key at https://usetrackly.app (sign in → Settings → API Keys).';
14
+ const APPLY_BROWSER_SURFACES = ['codex_in_app', 'chrome_extension', 'claude_in_chrome'];
15
+ const APPLY_SCENARIO_CODES = [
16
+ 'browser_reclaim', 'resume_upload', 'resume_parser_recheck', 'semantic_boolean_commit',
17
+ 'custom_select_commit', 'multi_step_navigation', 'free_text_voice',
18
+ 'required_error_sweep', 'final_consent', 'handoff_reclaim',
19
+ ];
20
+ const SAFE_OBSERVATION_CODE = /^[a-z0-9][a-z0-9_:-]{0,99}$/;
14
21
 
15
22
  // Mirrors `granola-followup-app/src/services/region-classifier.ts:8` REGION_TAGS.
16
23
  // Keep in sync when the backend enum changes.
@@ -439,19 +446,23 @@ function createServer() {
439
446
 
440
447
  server.tool(
441
448
  'trackly_report_apply_observation',
442
- 'Report a redacted ATS mechanics observation. Never include answer values, addresses, contact data, OTPs, or free-form page content.',
449
+ 'Report a redacted ATS mechanics or scenario-coverage observation. Never include answer values, addresses, contact data, OTPs, or free-form page content.',
443
450
  {
444
- runId: z.number().int().min(1).optional(),
445
- provider: z.string().min(1).max(100),
451
+ runId: z.number().int().min(1),
452
+ provider: z.string().regex(SAFE_OBSERVATION_CODE),
446
453
  fieldLabel: z.string().min(1).max(1000),
447
- observationType: z.string().min(1).max(100),
448
- resolutionCode: z.string().max(100).optional(),
454
+ observationType: z.string().regex(SAFE_OBSERVATION_CODE),
455
+ resolutionCode: z.string().regex(SAFE_OBSERVATION_CODE).optional(),
449
456
  metadata: z.object({
450
- controlType: z.string().max(100).optional(),
457
+ controlType: z.string().regex(SAFE_OBSERVATION_CODE).optional(),
451
458
  required: z.boolean().optional(),
452
- errorCode: z.string().max(100).optional(),
453
- committed: z.boolean().optional(),
454
- }).optional(),
459
+ errorCode: z.string().regex(SAFE_OBSERVATION_CODE).optional(),
460
+ committed: z.boolean(),
461
+ scenarioCode: z.enum(APPLY_SCENARIO_CODES),
462
+ browserSurface: z.enum(APPLY_BROWSER_SURFACES),
463
+ browserBindingHash: z.string().regex(/^[a-f0-9]{64}$/).optional(),
464
+ resumedAfterHandoff: z.boolean().optional(),
465
+ }),
455
466
  },
456
467
  wrapTool(async (params) => apiRequest('POST', '/api/jobscout/apply/observations', params, false, false, MCP_USER_AGENT), 'Failed to report apply observation')
457
468
  );
@@ -470,8 +481,13 @@ function createServer() {
470
481
  server.tool(
471
482
  'trackly_prepare_resume',
472
483
  'Download the authenticated default resume into a mode-0600 temporary Trackly cache and return exact-file proof for user confirmation before browser upload.',
473
- { runId: z.number().int().min(1) },
474
- wrapTool(async ({ runId }) => prepareResume(runId), 'Failed to prepare default resume')
484
+ {
485
+ runId: z.number().int().min(1),
486
+ browserSurface: z.enum(APPLY_BROWSER_SURFACES),
487
+ browserBindingHash: z.string().regex(/^[a-f0-9]{64}$/),
488
+ },
489
+ wrapTool(async ({ runId, browserSurface, browserBindingHash }) =>
490
+ prepareResume(runId, browserSurface, browserBindingHash), 'Failed to prepare default resume')
475
491
  );
476
492
 
477
493
  server.tool(
@@ -496,7 +512,7 @@ function createServer() {
496
512
  role: 'user',
497
513
  content: {
498
514
  type: 'text',
499
- text: 'Fetch the Trackly Apply protocol and profile onboarding, resolve missing answers with me, and start the next approved queue run. Prepare the run-bound resume locally, show me its exact path, filename, size, SHA-256, run, and expiration, and obtain my explicit confirmation. Immediately before attachment, use the local verifier to validate the signed proof, recompute hash and size, check expiration, and lock the file read-only. Then fill the form, verify every required field, and stop before Submit. If maintenance interrupts the run, retain the run and browser context, wait for the advertised window, refetch protocol and profile state, and resume the existing agent_browser run. Never start a duplicate run, blindly retry a mutation, or click Submit.',
515
+ text: 'Fetch the Trackly Apply protocol, profile onboarding, profile, and approved queue. Resolve missing answers with me. Before starting anything, stop on every non-null executionBlocker and every manual_only item. Start only the selected approved queue item, require major(run.protocolVersion) === major(protocol.version), require protocol.compatibleSkillMajor === 4, preserve the stored version for a resumed run, and require its provider, atsCapability, required scenarios, and originPolicy to match the queue preflight. Reclaim semantic browser control, verify the exact job/run/tab binding, hash that value-free binding, and report the same-run browser_ready attestation with committed=true. Before entering private data, normalize every page, redirect, and data-receiving iframe URL; accept an exact authorized origin or hostname only when host === allowedDomain or host.endsWith("." + allowedDomain), never by substring or page text. For vendor-hosted ATS pages, execute the backend-owned originPolicy.tenantRule exactly after every redirect or data-receiving iframe change, including its extraction, exact-host-depth, locale, percent-decoding, normalization, and fail-closed semantics, then require the normalized result to equal originPolicy.verifiedAtsTenant; never invent or reinterpret a strategy token. Obey every capability stop condition. Determine whether the form has a semantically identified Resume or CV attachment control. Only when that specific control exists, prepare the run-bound resume locally with that browser surface and binding hash, show me its exact path, filename, size, SHA-256, run, and expiration, and obtain my explicit confirmation. Treat cover-letter, portfolio, transcript, and other supporting-document controls separately according to the profile and protocol; never upload a resume to them. Immediately before attaching the resume, use the local verifier to validate the signed proof, recompute hash and size, check expiration, and lock the file read-only. Fill the form through semantic controls, verify committed values and every required error, and report a same-run passed or corrected scenario_coverage observation with committed=true for every backend-required scenario except browser_reclaim, which is satisfied only by browser_ready with the binding hash. If a required scenario cannot pass, record blocked rather than review_ready. Stop before Submit. If maintenance interrupts the run, retain the run and browser context, wait for the advertised window, refetch protocol, queue, and profile state, and resume the existing agent_browser run. Never start a duplicate run, blindly retry a mutation, enter credentials or verification codes, evade human verification, or click Submit.',
500
516
  },
501
517
  }],
502
518
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.6.2",
3
+ "version": "0.7.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": {
package/server.json CHANGED
@@ -17,12 +17,12 @@
17
17
  ]
18
18
  }
19
19
  ],
20
- "version": "0.6.2",
20
+ "version": "0.7.0",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "trackly-cli",
25
- "version": "0.6.2",
25
+ "version": "0.7.0",
26
26
  "runtimeHint": "npx",
27
27
  "runtimeArguments": [
28
28
  {
@@ -10,7 +10,7 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
10
10
  ## Non-negotiable rules
11
11
 
12
12
  1. Stop before Submit. Never click a submit-application button, even when the user previously approved submission or asks for full automation. The user submits manually.
13
- 2. Treat a saved/check-later job as an execution instruction. Do not rescore, veto, or delay it based on fit. Surface only execution blockers such as a closed posting, unsupported ATS, missing answer, or verification challenge.
13
+ 2. Treat a saved/check-later job as an execution instruction. Do not rescore, veto, or delay it based on fit. Surface only execution blockers such as a closed posting, a protocol-declared manual-only ATS, missing answer, or verification challenge.
14
14
  3. Never invent immigration, authorization, EEO, education, compensation, consent, employment, referral, or communication answers.
15
15
  4. Never store OTPs, CAPTCHA answers, or human-verification codes. Do not evade anti-bot controls; pause for the user.
16
16
  5. Mark a job applied only after a real success page or the user explicitly confirms manual submission.
@@ -19,7 +19,7 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
19
19
 
20
20
  ## Start every run
21
21
 
22
- 1. Call `trackly_get_apply_protocol`. Reject an incompatible major skill version and report that `trackly agent setup` must update the skill.
22
+ 1. Call `trackly_get_apply_protocol`. Skill 4.0 requires protocol major 3 (version 3.0.0 or newer) and `compatibleSkillMajor: 4`. Reject an older or incompatible major and report that the backend must finish updating or `trackly agent setup` must update the skill. This breaking gate prevents a pre-guided-mode skill or run from being reused under the stronger cross-ATS contract.
23
23
  2. Call `trackly_get_profile_onboarding` or fetch both the profile schema and application profile. Ask only unknown or unconfirmed fields.
24
24
  3. Save answers with `trackly_update_application_profile`:
25
25
  - Use `answered`, `intentionally_blank`, `declined`, or `unknown` exactly.
@@ -27,20 +27,40 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
27
27
  - Save ATS-specific behavior by provider and employer-specific facts by company.
28
28
  - Ask which scope applies when it is unclear.
29
29
  - Require explicit encrypted-storage consent before saving restricted profile values.
30
- 4. Require the one-time profile confirmation, complete education entries, and a default resume before browser work.
30
+ - Use `employment.previously_worked_for_employer` only at company scope.
31
+ - Use `employment.has_close_relationship_at_employer` only at company scope.
32
+ - Use `location.requires_relocation_assistance` only at global scope.
33
+ - Keep `identity.pronouns` separate from `eeo.gender_identity`; save `eeo.gender_identity` only at global scope.
34
+ - Use `consent.future_opportunity_retention` only at company scope because one employer's optional retention choice is not consent for another employer.
35
+ - 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
+ 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.
31
37
  5. Call `trackly_get_apply_queue`. Select deterministically unless the user names a job. Do not replace the queue call with a fresh job search.
32
- 6. Call `trackly_start_apply_run` for the selected job.
33
- 7. Call `trackly_prepare_resume` with the active application run ID. If hosted MCP reports it unavailable, tell the user that local Trackly MCP or manual upload is required.
34
- 8. 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.
35
- 9. Before any upload, let the user inspect the exact prepared file returned by `trackly_prepare_resume`:
38
+ - Use the queue item's backend-owned `atsCapability`, `originPolicy`, and `executionBlocker`. Never infer a stronger support claim from the page design or the LLM's familiarity with a provider. Required scenarios are backend-enforced gates; possible scenarios are reported only when actually exercised.
39
+ - Stop before `trackly_start_apply_run` whenever `executionBlocker` is non-null. `standard` runs use the provider playbook. `guided` runs are allowed only while semantic browser state remains observable, `originPolicy.authorized` is true, and every published stop condition is obeyed. `manual_only` items stop without starting or mutating a run.
40
+ - If the user requests the next `N` jobs, freeze the deterministic ordered set of exactly `N` job IDs before starting any run. Do not replace, rescore, or expand that approved batch.
41
+ - 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.
42
+ 6. Call `trackly_start_apply_run` for the selected job or current fixed batch member. Reuse an active run returned by Trackly; never create a replacement run because browser control was interrupted.
43
+ - Require `run.protocolVersion` to share protocol major 3 with the fetched protocol. A resumed run may retain an older compatible 3.x patch/minor version; preserve that exact run and let the backend bind observations and resume proof to its stored version. Stop on a major mismatch. Compare `run.atsCapability` and `run.originPolicy` with the queue values. Stop and refetch the protocol and queue if support level, execution mode, provider, required scenarios, or authorized origin policy changed between preflight and run creation.
44
+ - 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.
45
+ 7. Pass the browser readiness gate before preparing resume bytes:
46
+ - Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
47
+ - Prove non-mutating capability: the surface can discover or reclaim every target tab, inspect the DOM, click and select semantic controls, determine whether a file input exists, and read committed field state. When an upload control exists, identify it semantically. Do not upload any file during readiness; a real upload happens only after exact-file confirmation and verification.
48
+ - 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.
49
+ - 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.
50
+ - After a handoff, context resume, or browser-control interruption, reclaim and re-verify every mapped tab before continuing.
51
+ - 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.
52
+ - 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.
53
+ 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.
54
+ 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.
55
+ 10. Before any upload, let the user inspect the exact prepared file returned by `trackly_prepare_resume`:
36
56
  - Prefer an inline visual preview. Otherwise open the exact local file in Quick Look or Preview.app.
37
57
  - 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.
38
- - Ask for explicit confirmation to use that resume. Bind confirmation to the exact SHA-256 and current application run; a different hash or run requires new confirmation.
58
+ - 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.
39
59
  - Always provide `confirmation.verification.exactLocalPath` so the user can independently verify the file. Never describe the prepared cache path as the original upload source.
40
60
  - If an original local source path is known from the current session, identify it separately. Do not store original device paths in Trackly.
41
61
  - 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.
42
62
  - If no exact preview method works, stop and ask the user to inspect the file manually.
43
- 10. 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.
63
+ 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.
44
64
 
45
65
  ## Resume after maintenance
46
66
 
@@ -54,35 +74,43 @@ If any REST or MCP tool returns canonical `maintenance_mode` (or the legacy `pla
54
74
 
55
75
  ## Fill the form
56
76
 
57
- Read [references/ats-playbook.md](references/ats-playbook.md) for the detected ATS and [references/form-integrity.md](references/form-integrity.md) before interacting with fields.
77
+ Read [references/ats-playbook.md](references/ats-playbook.md) for the detected ATS, [references/form-integrity.md](references/form-integrity.md), and [references/scenario-coverage.md](references/scenario-coverage.md) before interacting with fields.
58
78
 
59
79
  Follow this order:
60
80
 
61
- 1. Open the application in the controlled browser context and confirm the employer, role, ATS host, and HTTPS URL.
81
+ 1. Open the application in the controlled browser context and confirm the employer, role, ATS host, and HTTPS URL. Before entering private data, parse and normalize the URL, then require the exact origin to equal an `originPolicy.authorizedOrigins` entry or the normalized hostname to satisfy `host === allowedDomain` or `host.endsWith("." + allowedDomain)` for an allowed ATS suffix or verified company domain. Never use substring, display-text, logo, or suffix-without-a-dot matching. For a vendor-hosted ATS, execute the backend-owned declarative `originPolicy.tenantRule` exactly, including its extraction, exact-host-depth, locale, percent-decoding, normalization, and fail-closed semantics, then require the normalized result to equal `originPolicy.verifiedAtsTenant`; never invent or reinterpret a strategy token. Revalidate both origin and tenant after every redirect and apply the same policy to every iframe that receives private data. Stop on any unmatched origin or tenant, malformed percent encoding, missing rule, or rule shape the client cannot execute exactly.
62
82
  2. Inspect the whole form and identify required fields, semantic controls, consent controls, document inputs, and multi-step sections.
63
- 3. Only after the exact-hash visual confirmation and a successful immediate pre-attach `trackly_verify_prepared_resume` check, upload the prepared resume before autofill when parsing may overwrite contact fields. Do not change the file between verification and attachment. Verify that the filename chip exactly matches the prepared resume’s user-facing filename and contains no internal cache identifier. Stop and replace the attachment if it does not.
83
+ 3. When an attachment control exists, only after the exact-hash visual confirmation and a successful immediate pre-attach `trackly_verify_prepared_resume` check, upload the prepared resume before autofill when parsing may overwrite contact fields. Do not change the file between verification and attachment. Verify that the filename chip exactly matches the prepared resume’s user-facing filename and contains no internal cache identifier. Stop and replace the attachment if it does not. When no attachment control exists, skip resume preparation and upload without treating that absence as an error.
64
84
  4. Fill typed fields from the resolved Trackly profile. Clear parser-filled data when the canonical state is intentionally blank.
65
- 5. Use real UI clicks for React/native selects, radios, and checkboxes. After every selection, verify the committed value and disappearance of the required-field error.
85
+ 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.
66
86
  6. Recheck email and phone through both browser DOM state and macOS accessibility state. Require exact values and reject duplicate/concatenated values.
67
87
  7. Complete known optional fields, education, links, relocation, and source answers. Do not silently omit canonical answers.
88
+ - 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.
68
89
  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.
69
- 9. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner.
90
+ 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.
91
+ 10. Run the full integrity gate, including the final consent checkbox, every visible error, all steps, and any correction banner.
70
92
 
71
- When the user corrects an answer, immediately save the appropriate scope with `trackly_update_application_profile` and report only a redacted mechanics observation through `trackly_report_apply_observation`. Never promote one user’s value into a global default.
93
+ When the user corrects an answer, immediately save the appropriate scope with `trackly_update_application_profile` and report only a redacted mechanics observation through `trackly_report_apply_observation`. Never promote one user’s value into a global default. For `generic_web_form`, never save provider-scoped answers; use company scope for form-specific answers.
94
+
95
+ For every run, track only scenarios actually exercised. 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. Every required scenario must have the corresponding same-run committed evidence. If any required scenario is missing, uncommitted, or blocked, record the run as `blocked` instead of `review_ready`. Never include answer values or page text. Include the actual scenario coverage in the final handoff; do not claim unobserved coverage.
72
96
 
73
97
  ## Review handoff
74
98
 
75
- Call `trackly_record_application_outcome` with `review_ready`, then provide the review block defined in [references/review-handoff.md](references/review-handoff.md). Keep the browser on the final review state and stop.
99
+ Call `trackly_record_application_outcome` with `review_ready`, then provide the review block defined in [references/review-handoff.md](references/review-handoff.md). For a single run, keep the browser on the final review state and stop. For a frozen batch, preserve the current review-ready tab and continue the same lifecycle for the next mapped batch member; stop only after every frozen member is review-ready, then provide one review block per run. Never submit any member. End with job ID -> run ID -> browser tab -> ATS -> status plus the actual scenario coverage for each run.
76
100
 
77
101
  After the user submits manually:
78
102
 
79
103
  - If a success page is visible, record `submitted` with a short non-sensitive confirmation signal.
80
104
  - If the user explicitly confirms submission, record `submitted` with `user_confirmed`.
81
105
  - If neither exists, do not move the job to applied.
106
+ - 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.
82
107
 
83
108
  ## Support boundary
84
109
 
85
- - Greenhouse: fully supported.
86
- - Ashby and Lever: best effort; stop if the integrity gate cannot observe committed state.
87
- - Other ATS platforms: stop and explain that the current protocol does not support them.
110
+ - Treat the current protocol's `atsCapabilities` as authoritative on every run. Do not hardcode or remember a provider's level from an earlier session.
111
+ - `full` means the deterministic Trackly fixtures and live beta cover the advertised mechanics. It never permits submission or bypassing an integrity failure.
112
+ - `best_effort` means use the named playbook and stop if browser and accessibility state disagree.
113
+ - `guided` means the provider or employer-hosted page may be completed only through observable semantic controls. Obey every published stop condition; stop on login credentials, OTP/email verification, CAPTCHA/human verification, unexpected origin/employer, submit-only navigation, or unobservable committed state.
114
+ - `blocked` / `manual_only` means do not start or mutate an application. LinkedIn-hosted applications are manual-only. If Trackly already stores a separate external application URL, evaluate that stored URL as its own ATS/origin; do not request or invent a URL override that the run cannot bind.
115
+ - Unknown employer forms use the protocol's `unknownAtsFallback` only when the queue supplies an authorized, verified company-domain origin policy. An unmatched or unverified HTTPS page is manual-only, not guided, and is never promoted to `full` by the agent.
88
116
  - Launch support: Codex and Claude Code on macOS with browser control and computer use available.
@@ -0,0 +1,36 @@
1
+ # Application writing integrity
2
+
3
+ Use this reference for free-text questions such as "Why this company?", motivation, or experience summaries.
4
+
5
+ ## Calibrate once
6
+
7
+ - Read `writing.voice_sample` and `writing.style_instructions` from the resolved Trackly profile.
8
+ - Offer both optional fields during profile setup, but never block an application run when they are unknown. If a free-text question appears before the user has set them, ask once before drafting and synchronize the answer. Let the user decline a voice sample, choose intentionally blank style instructions, or continue with the plain default style for the current run.
9
+ - Treat the sample and preferences as private user data. Never copy them into the public skill, logs, observations, or another user's defaults.
10
+ - A separate humanizer or writing skill may be used when available, but it is optional. This gate remains authoritative and self-contained.
11
+
12
+ ## Draft from evidence
13
+
14
+ 1. Answer the exact question using only canonical profile facts, resume evidence, and specific facts visible in the job posting.
15
+ 2. Lead with the concrete overlap between the user's experience and the role. Avoid generic company praise or unsupported enthusiasm.
16
+ 3. Prefer one or two specific proofs over a broad inventory of strengths. Never invent an achievement, employer, skill, or motivation.
17
+ 4. Keep the response proportionate to the form. Short questions should receive short answers.
18
+
19
+ ## Match the user's voice
20
+
21
+ - Match the sample's register, sentence length, paragraph breaks, first-person usage, punctuation, and level of informality.
22
+ - Honor explicit style instructions over generic defaults.
23
+ - Preserve readable quirks and personality. Do not polish every sentence into the same formal register.
24
+ - When no sample is available, default to plain first-person language, short paragraphs, and concrete evidence.
25
+
26
+ ## Anti-slop gate
27
+
28
+ Before entering the response:
29
+
30
+ 1. Remove generic praise, inflated claims, vague transitions, boilerplate conclusions, and chatbot phrases.
31
+ 2. Rewrite `not just X, but Y`, ornamental rule-of-three lists, and dangling `-ing` clauses unless the user's sample clearly uses them naturally.
32
+ 3. Use no em dash by default. Use one only when the user's sample or saved instructions show that punctuation is part of their voice.
33
+ 4. Vary sentence length and structure. Avoid a sequence of equally sized, equally formal sentences.
34
+ 5. Prefer active verbs, concrete nouns, real numbers, and named examples already supported by the profile.
35
+ 6. Read the answer aloud. If it sounds like a press release, generic cover letter, or assistant response, rewrite it.
36
+ 7. When a voice sample exists, compare the final response with it for rhythm and register. When the sample was declined or remains unknown for the current run, use the saved style instructions or plain default instead. In every case, confirm each factual claim again.
@@ -21,8 +21,31 @@
21
21
  - Best effort: stop when a required or semantic control cannot pass the integrity gate.
22
22
  - A Lever confirmation/thank-you page is a valid submission signal.
23
23
 
24
- ## Unsupported or verification state
24
+ ## Guided enterprise ATS
25
25
 
26
- - Do not improvise on unsupported ATS platforms.
27
- - Pause for OTP, email verification, CAPTCHA, or other human verification. Never read or store the code as a profile answer.
26
+ This group includes Workday, Oracle Recruiting/Taleo, SAP SuccessFactors, iCIMS, Phenom, Eightfold, Jibe, and Avature when the current protocol marks them `guided`.
27
+
28
+ - Expect account or login steps, multi-page state, custom comboboxes, employer-hosted shells, embedded frames, and resume parsing.
29
+ - Before uploading, confirm there is a semantic path from the current page to review that does not require the agent to enter credentials, solve verification, or cross to an unexpected origin.
30
+ - Reclaim and revalidate the exact requisition after every page transition. Recheck prior-step values when the page exposes them.
31
+ - Stop immediately when committed state is hidden, the only next control submits the application, or an authentication/verification wall appears.
32
+
33
+ ## Guided mid-market ATS
34
+
35
+ This group includes SmartRecruiters, Workable, BambooHR, Recruitee, Jobvite, Teamtailor, Comeet, Rippling, Gem, JazzHR, Breezy HR, Freshteam, Personio, Pinpoint, Zoho Recruit, Gusto, HiBob, Paylocity, and ADP when the current protocol marks them `guided`. YC Work at a Startup is currently `manual_only` because its shared job URL does not expose a company tenant that Trackly can verify.
36
+
37
+ - Expect a mixture of native controls, custom widgets, employer subdomains, embedded frames, shared profile data, and optional resume parsing.
38
+ - Match the live HTTPS origin, employer, role, and requisition before entering private data.
39
+ - Use the same committed-state and required-error gate as Greenhouse. Familiar-looking controls do not lower the evidence requirement.
40
+
41
+ ## Unknown employer-hosted form
42
+
43
+ - Use only when the protocol resolves the run to `generic_web_form` in guided mode and supplies `originPolicy.authorized: true` with `verification: verified_employer_domain`.
44
+ - Inventory every step, origin transition, control type, required error, file input, and review/submit boundary before any upload.
45
+ - Never claim provider support from visual similarity. Stop if any critical control cannot be observed semantically.
46
+
47
+ ## Manual-only or verification state
48
+
49
+ - Do not automate LinkedIn-hosted applications. If Trackly already stores a separate external application URL, evaluate that stored URL under its own backend-issued ATS and origin policy; otherwise let the user complete the LinkedIn form manually.
50
+ - Pause for login credentials, OTP, email verification, CAPTCHA, or other human verification. Never read or store credentials or a verification code as a profile answer.
28
51
  - Report closed/inactive postings as blockers; do not dismiss the saved job unless the user requests it.
@@ -2,13 +2,32 @@
2
2
 
3
3
  Run this gate after each semantic selection and again across the entire form before review.
4
4
 
5
+ ## Browser-control continuity
6
+
7
+ - If the semantic browser bridge becomes unavailable, stop before the next form mutation. Coordinate-only computer use is not a safe substitute for semantic field control.
8
+ - Preserve the current run and job mapping. After control returns, reclaim and re-verify the tab against the exact employer, role, ATS, requisition URL, job ID, and run ID.
9
+ - Reinspect the DOM and committed control state after every handoff, context resume, navigation, or rerender. Do not assume a stale snapshot or tab handle still identifies the same application.
10
+ - Prepare resume bytes only after semantic tab discovery, inspection, selection, committed-state capability, and a semantic determination that an attachment control exists. A form without a file input skips the resume path; it is not blocked. Do not perform a readiness upload.
11
+
12
+ ## Page and website-type inventory
13
+
14
+ - Before entering private data on a guided or unknown form, enumerate the current HTTPS origin, every visible step, any iframe origins, all file inputs, and the control that advances toward review. Require every data-receiving origin to match the backend-supplied run `originPolicy`; page text, logos, and employer names are not origin proof.
15
+ - Distinguish `Next`, `Continue`, `Save`, `Review`, and `Submit` by accessible name and observed behavior. If the only way to reveal the next state is a submit-like mutation, stop for manual review.
16
+ - Treat navigation to a different origin as a new trust boundary. Reconfirm employer, role, and requisition, then parse and normalize the redirected URL. An exact origin may equal an `authorizedOrigins` entry; hostname policies match only when `host === allowedDomain` or `host.endsWith("." + allowedDomain)`. Never accept substring, display-text, or `notexample.com` / `example.com.evil.test` lookalikes. On a vendor-hosted ATS, the shared origin is insufficient: execute the backend-owned declarative `originPolicy.tenantRule` exactly after every redirect or data-receiving iframe change, including extraction, exact host depth, optional locale, percent decoding, normalization, and every fail-closed condition. Compare the normalized result to `originPolicy.verifiedAtsTenant`. Never guess the meaning of an opaque strategy token. Unmatched redirects, tenants, malformed encoding, missing or unexecutable rules, and iframe origins are manual-only.
17
+ - Treat an authentication wall as a user handoff. Never enter, retrieve, store, or infer credentials.
18
+
5
19
  ## Semantic controls
6
20
 
7
21
  - Open React comboboxes and click the exact list option. Do not inject a value or rely on displayed input text.
8
22
  - Click native select options, radio labels, and checkboxes through the UI.
9
- - After selection, verify the selected/checked state from the control, dispatch-completed page state, and visible label.
10
- - Verify that the control’s required error is absent. A visible value beside “This field is required” is a failed field.
23
+ - Map booleans semantically: canonical `true` must commit the visible Yes-equivalent and canonical `false` the visible No-equivalent. Never choose a boolean option by index, DOM order, keyboard offset, proximity, or previous control state.
24
+ - After selection, verify the selected/checked state from the control, dispatch-completed page state, and visible label. Compare the normalized committed value to the canonical Trackly value; presence alone is insufficient.
25
+ - If the committed value is the semantic opposite of the canonical value, stop the sweep, correct the field through a fresh real UI selection, and report a redacted integrity observation.
26
+ - For a required control or a control that had a validation error before selection, verify that the error is absent afterward. A visible value beside “This field is required” is a failed field. An optional control that never had a validation error passes when its committed value is correct.
11
27
  - If a stale error remains, reopen and reselect the control. Use non-submitting validation only when the ATS provides it.
28
+ - For custom comboboxes, verify the selected option through at least one committed semantic signal (`aria-selected`, hidden form value, selected-value chip, or a framework state reflected in the control) plus the visible label. Typed or displayed text alone is not a committed selection.
29
+ - For native selects, verify the selected option's `value` and visible label after the `change` event settles.
30
+ - For radios and checkboxes, verify the input's checked state and its exact associated label. A decorative checkmark is not sufficient.
12
31
 
13
32
  ## Contact-field integrity
14
33
 
@@ -27,3 +46,10 @@ Run this gate after each semantic selection and again across the entire form bef
27
46
  6. Confirm the Submit button is present but do not click it.
28
47
 
29
48
  If any field’s committed state cannot be observed, fail the gate and explain which field needs manual review.
49
+
50
+ ## Submission-state reconciliation
51
+
52
+ - Never retry Submit after a contradictory or duplicate-application response.
53
+ - Treat “already applied” and similar banners as provisional until the current ATS route finishes settling.
54
+ - Confirm the employer, role, and exact requisition identifier are unchanged, then re-read the final state from that same URL.
55
+ - An explicit success state on the exact requisition is authoritative and may be recorded as submitted. Without success or explicit user confirmation, record blocked and leave the job out of Applied.
@@ -19,6 +19,7 @@ Education:
19
19
  EEO / consent choices:
20
20
  Custom answers:
21
21
  Integrity sweep: pass/fail
22
+ Actual scenario coverage:
22
23
  Items requiring your attention:
23
24
 
24
25
  Please review the live form and click Submit manually.
@@ -0,0 +1,36 @@
1
+ # Actual scenario coverage
2
+
3
+ Record only the mechanics exercised on the current application run. Coverage is evidence for improving the reusable harness, not a place for profile values, answers, page text, contact data, or employer-confidential content.
4
+
5
+ ## Observation shape
6
+
7
+ For each exercised scenario, call `trackly_report_apply_observation` with:
8
+
9
+ - `runId`: the exact Trackly Apply run.
10
+ - `provider`: the normalized ATS.
11
+ - `fieldLabel`: a generic mechanics label such as `Run scenario coverage`; never copy private page text.
12
+ - `observationType`: `scenario_coverage`.
13
+ - `resolutionCode`: `passed` or `corrected`. A blocked scenario is not scenario-coverage evidence; record the Apply run as `blocked` and optionally report a redacted `integrity_failure` observation.
14
+ - `metadata.scenarioCode`: one stable code from the list below.
15
+ - `metadata.browserSurface`: the semantic browser surface used, such as `codex_in_app`, `chrome_extension`, or `claude_in_chrome`.
16
+ - `metadata.committed`: `true` only after the scenario's actual committed state is observed. Every `passed` or `corrected` scenario requires `true`.
17
+ - `metadata.resumedAfterHandoff`: whether that run's tab had to be reclaimed after a handoff or browser-control interruption.
18
+
19
+ `browser_reclaim` is the exception to the observation shape above: attest it once with `observationType: browser_ready`, `metadata.scenarioCode: browser_reclaim`, `metadata.browserSurface`, `metadata.browserBindingHash`, and `metadata.committed: true`. Do not send a duplicate `scenario_coverage` row for browser reclaim.
20
+
21
+ ## Stable scenario codes
22
+
23
+ - `browser_reclaim`: exact job/run/tab binding was discovered or reclaimed and verified.
24
+ - `resume_upload`: the verified exact resume was attached and the employer-facing filename matched.
25
+ - `resume_parser_recheck`: contact or profile fields were rechecked after resume parsing or autofill.
26
+ - `semantic_boolean_commit`: a Yes/No control was selected and verified by semantic value.
27
+ - `custom_select_commit`: a React or native select committed the intended canonical value.
28
+ - `multi_step_navigation`: the form crossed one or more steps and earlier state was reverified.
29
+ - `free_text_voice`: a free-text answer passed the saved-voice and factual-integrity gate.
30
+ - `required_error_sweep`: every required field and visible error surface was checked.
31
+ - `final_consent`: the final certification, acknowledgement, or consent control was verified.
32
+ - `handoff_reclaim`: the browser tab was reclaimed after a context or control handoff.
33
+
34
+ ## Final reporting
35
+
36
+ List scenario codes separately for every run. Do not report a code because the ATS commonly has that behavior; report it only when it actually occurred and was observed on that run. A visible value is not proof of committed state. When committed state cannot be verified, omit passed/corrected coverage for that scenario and record the run as blocked.