trackly-cli 0.14.2 → 0.15.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.
@@ -17,5 +17,11 @@
17
17
  },
18
18
  "3.7.1": {
19
19
  "contractSha256": "0eb582fcac72804347c5da53d214a67908f657f600536db7b42167350bb3f3ab"
20
+ },
21
+ "3.7.2": {
22
+ "contractSha256": "058dc698ff1b0722f48b9f244773c0f09286046540609c31f001fe8604b06093"
23
+ },
24
+ "3.7.3": {
25
+ "contractSha256": "d37e7ec0a17d3dba399f12dd7281c51536ca92bbb21125ca09288d1678f34c14"
20
26
  }
21
27
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "3.7.1",
2
+ "contractVersion": "3.7.3",
3
3
  "constants": {
4
4
  "applyExecutionMaxTarget": 20,
5
5
  "applyScenarioCodes": ["browser_reclaim", "resume_upload", "resume_parser_recheck", "semantic_boolean_commit", "custom_select_commit", "multi_step_navigation", "free_text_voice", "required_error_sweep", "final_consent", "handoff_reclaim", "critical_contact_integrity", "manual_submit_boundary", "job_identity_match"],
@@ -23,11 +23,16 @@
23
23
  "applyUploadStages": ["semantic_control_discovered", "file_chooser_armed", "file_chooser_opened", "set_files_succeeded", "user_facing_filename_committed", "parser_fields_rechecked"],
24
24
  "applyUploadFailureCodes": ["upload_capability_unavailable", "upload_stage_missing", "upload_stage_out_of_order", "upload_stage_duplicate", "control_ambiguous", "chooser_timeout", "chooser_open_without_commit", "file_missing", "file_expired", "set_files_failed", "filename_uncommitted", "unexpected_file_navigation", "parser_unsettled", "parser_field_regression"]
25
25
  },
26
+ "toolInputInvariants": {
27
+ "trackly_get_apply_execution_snapshot": {
28
+ "officeProjectionMemberIdsMustExistInMemberIds": true
29
+ }
30
+ },
26
31
  "tools": {
27
32
  "trackly_get_apply_queue": "{limit:z.number().int().min(1).max(100).optional(),cursor:z.string().min(1).max(2048).optional()}",
28
- "trackly_get_application_profile": "{includeSensitive:z.boolean().optional(),provider:z.string().max(100).optional(),companyId:z.string().max(100).optional(),jurisdiction:iso3166Alpha2Schema.optional()}",
33
+ "trackly_get_application_profile": "{includeSensitive:z.boolean().optional(),provider:z.string().max(100).optional(),companyId:z.string().max(100).optional(),jurisdiction:iso3166Alpha2Schema.optional(),office:officeScopeSchema.optional()}",
29
34
  "trackly_get_profile_onboarding": "{}",
30
- "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()}),z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('jurisdiction'),scopeValue:iso3166Alpha2Schema,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(),sensitiveRevocationConfirmToken:z.string().regex(/^[a-f0-9]{64}$/).optional()}",
35
+ "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()}),z.object({key:z.string().min(1).max(200),state:z.enum(['unknown','answered','intentionally_blank','declined']),value:z.any().optional(),scope:z.literal('jurisdiction'),scopeValue:iso3166Alpha2Schema,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('office'),scopeValue:officeScopeSchema,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(),sensitiveRevocationConfirmToken:z.string().regex(/^[a-f0-9]{64}$/).optional()}",
31
36
  "trackly_start_apply_execution": "{mode:z.literal('complete_next_n_accessible'),target:z.number().int().min(1).max(APPLY_EXECUTION_MAX_TARGET),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
32
37
  "trackly_get_active_apply_execution": "{}",
33
38
  "trackly_get_apply_execution": "{executionId:z.number().int().min(1)}",
@@ -35,7 +40,7 @@
35
40
  "trackly_recover_exact_apply_members": "{sourceExecutionId:z.number().int().min(1),sourceSnapshotHash:z.string().regex(/^[a-f0-9]{64}$/),candidateIds:z.array(z.number().int().min(1)).min(1).max(APPLY_EXECUTION_MAX_TARGET).refine((values)=>newSet(values).size===values.length,{message:'candidateIdsmustbeunique'}),explicitExactSetConfirmation:z.literal(true),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
36
41
  "trackly_list_apply_review_handoffs": "{executionId:z.number().int().min(1)}",
37
42
  "trackly_claim_apply_review_handoff": "{handoffId:z.number().int().min(1),members:z.array(z.object({memberId:z.number().int().min(1),classification:z.enum(APPLY_HANDOFF_RECONCILIATION_CLASSIFICATION_CODES)}).strict()).min(1).max(APPLY_EXECUTION_MAX_TARGET).refine((values)=>newSet(values.map(({memberId})=>memberId)).size===values.length,{message:'membersmustcontainuniquememberIdvalues'}),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
38
- "trackly_get_apply_execution_snapshot": "{executionId:z.number().int().min(1),memberIds:z.array(z.number().int().min(1)).min(1).max(APPLY_EXECUTION_MAX_TARGET),profileKeys:z.array(z.string().min(1).max(200)).max(100).optional(),browserSurface:z.enum(APPLY_BROWSER_SURFACES)}",
43
+ "trackly_get_apply_execution_snapshot": "{executionId:z.number().int().min(1),memberIds:z.array(z.number().int().min(1)).min(1).max(APPLY_EXECUTION_MAX_TARGET).refine((values)=>newSet(values).size===values.length,{message:'memberIdsmustbeunique'}),profileKeys:z.array(z.string().min(1).max(200)).max(100).refine((values)=>newSet(values).size===values.length,{message:'profileKeysmustbeunique'}).optional(),officeProjections:z.array(z.object({memberId:z.number().int().min(1),office:officeScopeSchema,profileKeys:z.array(z.string().min(1).max(200)).min(1).max(100).refine((values)=>newSet(values).size===values.length,{message:'officeprofileKeysmustbeunique'})}).strict()).max(APPLY_EXECUTION_MAX_TARGET).refine((values)=>newSet(values.map(({memberId})=>memberId)).size===values.length,{message:'officeProjectionsmustcontainuniquememberIdvalues'}).optional(),browserSurface:z.enum(APPLY_BROWSER_SURFACES)}",
39
44
  "trackly_resume_parked_apply_member": "{executionId:z.number().int().min(1),memberId:z.number().int().min(1),expectedRevision:z.number().int().min(1),browserSurface:z.enum(APPLY_BROWSER_SURFACES),explicitUserResume:z.literal(true),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
40
45
  "trackly_approve_apply_execution_resume": "{executionId:z.number().int().min(1),expectedRevision:z.number().int().min(1),originalSnapshotHash:z.string().regex(/^[a-f0-9]{64}$/),profileRevision:z.number().int().min(1),resumeId:z.number().int().min(1),resumeSha256:z.string().regex(/^[a-f0-9]{64}$/),resumeFilename:z.string().min(1).max(255),resumeSizeBytes:z.number().int().min(1),expiresAt:z.string().datetime(),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
41
46
  "trackly_advance_apply_execution": "{executionId:z.number().int().min(1),expectedRevision:z.number().int().min(1),browserSurface:z.enum(APPLY_BROWSER_SURFACES),idempotencyKey:z.string().min(16).max(200).regex(SAFE_IDEMPOTENCY_KEY)}",
package/lib/agent.js CHANGED
@@ -8,7 +8,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
10
  const { contractVersion: MCP_CONTRACT_VERSION } = require('../contracts/trackly-apply-tools.json');
11
- const SKILL_VERSION = '4.5.0';
11
+ const SKILL_VERSION = '4.6.0';
12
12
  const CLI_USER_AGENT = `trackly-cli/${PACKAGE_VERSION}`;
13
13
  const MCP_USER_AGENT = `trackly-mcp/${PACKAGE_VERSION}`;
14
14
 
@@ -40,6 +40,7 @@ const SAFE_IDEMPOTENCY_KEY = /^[\x20-\x7e]+$/;
40
40
  const iso3166Alpha2Schema = z.string().regex(/^[A-Za-z]{2}$/).refine(isIso3166Alpha2, {
41
41
  message: 'Expected an ISO 3166-1 alpha-2 country code',
42
42
  });
43
+ const officeScopeSchema = z.string().regex(/^[1-9]\d*:[A-Za-z0-9][A-Za-z0-9._-]{0,150}$/);
43
44
  const opaqueTabIdSchema = z.union([
44
45
  z.number().int().safe(),
45
46
  z.string().min(1).max(512).refine((value) => value.trim().length > 0, {
@@ -262,7 +263,7 @@ const startApplyRunSchema = z.object({
262
263
  }
263
264
  });
264
265
 
265
- const APPLY_RELIABILITY_PROMPT = 'Protocol 3.6 / skill 4.5.0 reliability gate: recover active work first. After full local context loss, list recoverable executions, show only stable job identity, obtain explicit confirmation of the exact candidate set, and call exact-member recovery without substitutions. Treat recovered tab presence, form state, and mutation authority as three separate facts; reacquire a fresh browser binding and inspection epoch before mutation. Before resolving broad submission statements, list active review handoffs for the execution; use only an explicit receipt or the sole returned active receipt, classify every member as detected, user_confirmed, unresolved, or contradictory, and claim that exact handoff before writing outcomes. Use provider-specific positive success evidence; an unchanged URL or title is never negative evidence. Validate an exact expected browser keep set locally before finalization. For resume uploads, negotiate the browser surface capabilities, identify the semantic control, arm the chooser before clicking, attach the immediately verified file, prove the user-facing filename committed, and recheck parser-modified fields. Use compact snapshots and server-provided mutability. Preserve user-edited and unknown non-empty fields. Never reopen parked work without explicit user resumption. Never click Submit.';
266
+ const APPLY_RELIABILITY_PROMPT = 'Protocol 3.6 / skill 4.6.0 reliability gate: recover active work first. After full local context loss, list recoverable executions, show only stable job identity, obtain explicit confirmation of the exact candidate set, and call exact-member recovery without substitutions. Treat recovered tab presence, form state, and mutation authority as three separate facts; reacquire a fresh browser binding and inspection epoch before mutation. Before resolving broad submission statements, list active review handoffs for the execution; use only an explicit receipt or the sole returned active receipt, classify every member as detected, user_confirmed, unresolved, or contradictory, and claim that exact handoff before writing outcomes. Use provider-specific positive success evidence; an unchanged URL or title is never negative evidence. Validate an exact expected browser keep set locally before finalization. For resume uploads, negotiate the browser surface capabilities, identify the semantic control, arm the chooser before clicking, attach the immediately verified file, prove the user-facing filename committed, and recheck parser-modified fields. Use compact snapshots and server-provided mutability. Preserve user-edited and unknown non-empty fields. Never reopen parked work without explicit user resumption. Never click Submit.';
266
267
 
267
268
  function registerApplyTools(
268
269
  server,
@@ -311,13 +312,19 @@ function registerApplyTools(
311
312
  provider: z.string().max(100).optional(),
312
313
  companyId: z.string().max(100).optional(),
313
314
  jurisdiction: iso3166Alpha2Schema.optional(),
315
+ office: officeScopeSchema.optional(),
314
316
  },
315
- wrapTool(async ({ includeSensitive, provider, companyId, jurisdiction }) => {
317
+ wrapTool(async ({ includeSensitive, provider, companyId, jurisdiction, office }) => {
318
+ const normalizedCompanyId = companyId?.trim();
319
+ if (office && (!normalizedCompanyId || !office.startsWith(`${normalizedCompanyId}:`))) {
320
+ throw new Error('Office scope must match the requested companyId');
321
+ }
316
322
  const qs = new URLSearchParams();
317
323
  if (includeSensitive) qs.set('includeSensitive', 'true');
318
324
  if (provider) qs.set('provider', provider);
319
- if (companyId) qs.set('companyId', companyId);
325
+ if (normalizedCompanyId) qs.set('companyId', normalizedCompanyId);
320
326
  if (jurisdiction) qs.set('jurisdiction', jurisdiction);
327
+ if (office) qs.set('office', office);
321
328
  return applyApiRequest('GET', `/api/jobscout/application-profile?${qs.toString()}`, null, false, false, MCP_USER_AGENT);
322
329
  }, 'Failed to fetch application profile')
323
330
  );
@@ -361,6 +368,11 @@ function registerApplyTools(
361
368
  value: z.any().optional(), scope: z.literal('jurisdiction'), scopeValue: iso3166Alpha2Schema,
362
369
  questionLabel: z.string().max(1000).optional(),
363
370
  }),
371
+ z.object({
372
+ key: z.string().min(1).max(200), state: z.enum(['unknown', 'answered', 'intentionally_blank', 'declined']),
373
+ value: z.any().optional(), scope: z.literal('office'), scopeValue: officeScopeSchema,
374
+ questionLabel: z.string().max(1000).optional(),
375
+ }),
364
376
  ])).max(100).optional(),
365
377
  education: z.array(z.object({
366
378
  school: z.string().min(1).max(500),
@@ -535,13 +547,36 @@ function registerApplyTools(
535
547
  'Fetch a compact, bounded projection for one Apply execution. Request only the current members and profile keys needed for the visible form. The response owns mutability, allowed operations, milestones, lease timing, and progress.',
536
548
  {
537
549
  executionId: z.number().int().min(1),
538
- memberIds: z.array(z.number().int().min(1)).min(1).max(APPLY_EXECUTION_MAX_TARGET),
539
- profileKeys: z.array(z.string().min(1).max(200)).max(100).optional(),
550
+ memberIds: z.array(z.number().int().min(1)).min(1).max(APPLY_EXECUTION_MAX_TARGET)
551
+ .refine((values) => new Set(values).size === values.length, {
552
+ message: 'memberIds must be unique',
553
+ }),
554
+ profileKeys: z.array(z.string().min(1).max(200)).max(100)
555
+ .refine((values) => new Set(values).size === values.length, {
556
+ message: 'profileKeys must be unique',
557
+ }).optional(),
558
+ officeProjections: z.array(z.object({
559
+ memberId: z.number().int().min(1),
560
+ office: officeScopeSchema,
561
+ profileKeys: z.array(z.string().min(1).max(200)).min(1).max(100)
562
+ .refine((values) => new Set(values).size === values.length, {
563
+ message: 'office profileKeys must be unique',
564
+ }),
565
+ }).strict()).max(APPLY_EXECUTION_MAX_TARGET)
566
+ .refine((values) => new Set(values.map(({ memberId }) => memberId)).size === values.length, {
567
+ message: 'officeProjections must contain unique memberId values',
568
+ }).optional(),
540
569
  browserSurface: z.enum(APPLY_BROWSER_SURFACES),
541
570
  },
542
- wrapTool(async ({ executionId, ...body }) => applyControlRequest(
543
- 'POST', `/api/jobscout/apply/executions/${executionId}/snapshot`, body,
544
- ), 'Failed to fetch compact apply execution snapshot')
571
+ wrapTool(async ({ executionId, ...body }) => {
572
+ const memberIds = new Set(body.memberIds);
573
+ if (body.officeProjections?.some(({ memberId }) => !memberIds.has(memberId))) {
574
+ throw new Error('Every office projection memberId must exist in memberIds.');
575
+ }
576
+ return applyControlRequest(
577
+ 'POST', `/api/jobscout/apply/executions/${executionId}/snapshot`, body,
578
+ );
579
+ }, 'Failed to fetch compact apply execution snapshot')
545
580
  );
546
581
 
547
582
  server.tool(
@@ -1162,6 +1197,9 @@ function registerApplyTools(
1162
1197
  messages: [{
1163
1198
  role: 'user',
1164
1199
  content: { type: 'text', text: APPLY_RELIABILITY_PROMPT },
1200
+ }, {
1201
+ role: 'user',
1202
+ content: { type: 'text', text: 'Before generating questions or filling controls, run the skill 4.6 deterministic answer resolver. Classify every visible answer as exact_profile, safe_derivation, supported_draft, missing_fact, live_consent, or forbidden_inference; validate the control type; fill only the first three; and ask only currently visible unresolved needs. Treat the current profile revision as reusable authority, never transcript, screenshot, parser, autocomplete, or cached values. Accessible-first is a hard scheduler invariant: park known authentication, account-creation, OTP, and pre-form-CAPTCHA work without starting a draft while an accessible candidate remains.' },
1165
1203
  }, {
1166
1204
  role: 'user',
1167
1205
  content: { type: 'text', text: 'Only active=true identifies resumable execution work. Active=false and preserved=true is terminal read-only reconciliation evidence.' },
@@ -1169,7 +1207,7 @@ function registerApplyTools(
1169
1207
  role: 'user',
1170
1208
  content: {
1171
1209
  type: 'text',
1172
- text: 'Protocol 3.6.0 reliability gate for new work: require MCP contract 3.7.1 and skill 4.5.0. After complete local context loss, list bounded recovery candidates, obtain explicit confirmation of the exact set, and recover only that set. Treat tab recovery, form-state recovery, and mutation authority as independent. List active handoff receipts for the execution before resolving grouped submission statements; use the named receipt or the sole returned active receipt, classify every member, and claim that receipt before recording outcomes. Validate tab keep sets and resume upload stages locally. Never send raw browser values or click Submit.',
1210
+ text: 'Protocol 3.6.0 reliability gate for new work: require MCP contract 3.7.3 and skill 4.6.0. After complete local context loss, list bounded recovery candidates, obtain explicit confirmation of the exact set, and recover only that set. Treat tab recovery, form-state recovery, and mutation authority as independent. List active handoff receipts for the execution before resolving grouped submission statements; use the named receipt or the sole returned active receipt, classify every member, and claim that receipt before recording outcomes. Validate tab keep sets and resume upload stages locally. Never send raw browser values or click Submit.',
1173
1211
  },
1174
1212
  }, {
1175
1213
  role: 'user',
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.14.2",
3
+ "version": "0.15.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "trackly-cli",
9
- "version": "0.14.2",
9
+ "version": "0.15.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@modelcontextprotocol/sdk": "1.30.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackly-cli",
3
- "version": "0.14.2",
3
+ "version": "0.15.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": {
package/server.json CHANGED
@@ -17,12 +17,12 @@
17
17
  ]
18
18
  }
19
19
  ],
20
- "version": "0.14.2",
20
+ "version": "0.15.0",
21
21
  "packages": [
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "trackly-cli",
25
- "version": "0.14.2",
25
+ "version": "0.15.0",
26
26
  "runtimeHint": "npx",
27
27
  "runtimeArguments": [
28
28
  {
@@ -22,12 +22,13 @@ Use Trackly as the source of truth for profile answers, documents, queue decisio
22
22
 
23
23
  Before selecting or resuming work, read
24
24
  [references/batch-orchestration.md](references/batch-orchestration.md) and
25
- [references/browser-lifecycle.md](references/browser-lifecycle.md). Those
25
+ [references/browser-lifecycle.md](references/browser-lifecycle.md). Before generating an employer-form question packet, also read and run
26
+ [references/answer-resolution.md](references/answer-resolution.md). These
26
27
  references define the server-frozen batch, grouped-question, tab-reconciliation,
27
28
  and missing-surface recovery contract; they are mandatory even for a one-job batch.
28
29
  Read [references/legal-clarifications.md](references/legal-clarifications.md) when a form asks for a legal, regulatory, consent, or employer-relationship decision.
29
30
 
30
- 1. Call `trackly_get_apply_protocol`. Skill 4.5.0 requires protocol 3.6.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.
31
+ 1. Call `trackly_get_apply_protocol`. Skill 4.6.0 requires protocol 3.6.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.
31
32
  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.
32
33
  3. Save answers with `trackly_update_application_profile`:
33
34
  - Use `answered`, `intentionally_blank`, `declined`, or `unknown` exactly.
@@ -48,6 +49,7 @@ Read [references/legal-clarifications.md](references/legal-clarifications.md) wh
48
49
  - 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, and source-execution identity; do not identify candidates from chat, tab order, search results, or page copy. A missing or mismatched Trackly job record blocks confirmation. 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.
49
50
  - 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.
50
51
  - 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.
52
+ - 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.
51
53
  - Recover every entry in `execution.unresolvedWaves` in ascending `waveOrder`; `currentWave` identifies only the latest scheduling wave, not the complete recovery set.
52
54
  - Immediately consume the start response's authoritative `progress` and `nextAction`; do not issue a speculative advance first.
53
55
  - 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.
@@ -71,7 +73,7 @@ Read [references/legal-clarifications.md](references/legal-clarifications.md) wh
71
73
  - 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.
72
74
  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.
73
75
  - 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.
74
- - 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.6.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.5.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.
76
+ - 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.6.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.6.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.
75
77
  - 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.
76
78
  7. Pass the browser readiness gate before preparing resume bytes:
77
79
  - Use semantic browser control through Codex in-app browser controls, Chrome MCP/extension browser control, or Claude in Chrome.
@@ -112,7 +114,7 @@ Follow this order:
112
114
  1. Open the application in the controlled browser context and confirm the employer, role, ATS host, and HTTPS URL. Before entering private data, require the visible company and role to match the run binding and, when the stored URL exposes one, require the requisition identifier to match. When `job_identity_match` is required, immediately report its value-free committed `scenario_coverage` proof after this check passes; never include company, role, URL, requisition, or page text in the observation. On exact-origin fallback, revalidate the frozen company, role, and available requisition identity after every navigation or redirect and before entering any additional 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. When `verification` is `trackly_employer_source_exact_origin`, accept only the exact listed origin; never convert it into a hostname suffix or carry it across a redirect or iframe origin change. For every other vendor-hosted ATS policy, require both `originPolicy.tenantRule` and `originPolicy.verifiedAtsTenant` to be non-null; stop before private data entry if either is missing. Execute the backend-owned declarative tenant rule 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 required rule, or rule shape the client cannot execute exactly.
113
115
  2. Inspect the whole form and identify required fields, semantic controls, consent controls, document inputs, and multi-step sections.
114
116
  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.
115
- 4. Fill typed fields from the resolved Trackly profile. Clear parser-filled data when the canonical state is intentionally blank. Before writing any non-empty control, apply the local field-ownership gate from [references/form-integrity.md](references/form-integrity.md). Preserve `user_edited` and `unknown_external_change` values byte-for-byte unless the user explicitly requests a rewrite; never let resume parsing, rerendering, recovery, or the final sweep overwrite them.
117
+ 4. Run the deterministic resolver from [references/answer-resolution.md](references/answer-resolution.md) against the visible controls, then fill typed fields from the resolved Trackly profile. Every answer must be classified as `exact_profile`, `safe_derivation`, `supported_draft`, `missing_fact`, `live_consent`, or `forbidden_inference` before use. Clear parser-filled data when the canonical state is intentionally blank. Before writing any non-empty control, apply the local field-ownership gate from [references/form-integrity.md](references/form-integrity.md). Preserve `user_edited` and `unknown_external_change` values byte-for-byte unless the user explicitly requests a rewrite; never let resume parsing, rerendering, recovery, or the final sweep overwrite them.
116
118
  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.
117
119
  6. Recheck email and phone through both browser DOM state and macOS accessibility state. Require exact values and reject duplicate/concatenated values.
118
120
  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.
@@ -8,14 +8,20 @@ questions and piecemeal profile writes.
8
8
 
9
9
  1. Collect all newly supplied answers from the current question packet.
10
10
  2. Fetch the current profile schema and the smallest relevant profile
11
- projection once. Include `provider`, `company`, and `jurisdiction` when that
12
- context is known and required. Corporate-family reuse is unavailable until
11
+ projection once. Include `provider`, `company`, `jurisdiction`, and `office`
12
+ when that context is known and required. An office scope value is the exact
13
+ backend company ID plus a stable office identity, for example
14
+ `42:waltham-ma`; never derive it from a display name alone. Corporate-family reuse is unavailable until
13
15
  Trackly has an authoritative company-family registry; never derive a family
14
16
  identity from names, logos, page text, parent-company knowledge, or search.
15
17
  3. Map each answer to an exposed canonical key and permitted scope. Never call
16
18
  a field missing merely because it was absent from the compact execution
17
19
  snapshot; contextual fields require a targeted profile fetch.
18
- 4. Classify every answer as exactly one of:
20
+ 4. Validate the expected input type before mapping or writing. “Whole number”
21
+ requires a number, and “Yes or No” requires a boolean. A mismatch remains
22
+ unresolved and returns to the grouped packet; never coerce a boolean into
23
+ years, a proficiency label into a language level, or vice versa.
24
+ 5. Classify every answer as exactly one of:
19
25
  - `saved`: a canonical field exists and this run changed it;
20
26
  - `already_matched`: the canonical field already contains the same state and
21
27
  value at the resolved scope;
@@ -23,9 +29,13 @@ questions and piecemeal profile writes.
23
29
  live schema audit;
24
30
  - `run_only_contextual`: the answer is intentionally per-run, such as the
25
31
  final truthfulness certification, and must not enter the reusable profile.
26
- 5. Ask scope only when the user's wording and the schema do not determine it.
32
+ 6. Ask scope only when the user's wording and the schema do not determine it.
27
33
  “Always” means global only for fields whose schema permits global scope.
28
34
 
35
+ Compound a reusable gap only when a visible form requires it. Defer optional
36
+ profile gaps that no current visible application needs; do not turn the full
37
+ schema into an onboarding questionnaire.
38
+
29
39
  ## One write and one verification
30
40
 
31
41
  - Send all `saved` changes in one `trackly_update_application_profile` call
@@ -65,6 +75,10 @@ claim that Trackly learned it.
65
75
 
66
76
  - Country-specific work authorization:
67
77
  `authorization.legally_authorized_by_country` at `jurisdiction` scope.
78
+ - Employer-office commute willingness and cadence:
79
+ `location.commute_willing`, `location.commute_days_per_week`, and
80
+ `location.commute_commitment` at exact `office` scope. Never reuse an answer
81
+ for another office of the same company.
68
82
  - Prior interview with the employer:
69
83
  `employment.previously_interviewed_at_employer` at `company` scope.
70
84
  - Employment, contracting, consulting, temporary work, or similar engagement
@@ -83,6 +97,18 @@ claim that Trackly learned it.
83
97
  the backend can prove a match. A write receipt or refetch verifies only the
84
98
  company scope and `questionFingerprint`; a redacted, unknown, or absent
85
99
  reusable value is expected and must not block truth certification or review.
100
+ - Named applicant privacy-notice routing:
101
+ `consent.named_applicant_privacy_notice_acknowledgement_policy` is a global
102
+ routing policy, not consent to unseen text. `acknowledge_named_notice_only`
103
+ permits only a visible, unbundled named applicant notice; `ask` remains a
104
+ live question. Marketing, retention, arbitration, background-check,
105
+ recording, AI, and data-sharing choices stay separate.
106
+ - Interview recording or transcription consent and workplace-policy
107
+ acknowledgement: use `consent.interview_recording` and
108
+ `consent.workplace_policy_acknowledgement` only at exact company scope with
109
+ the current policy question or version as `questionLabel`. Both are
110
+ `run_only_contextual`: retain the fingerprint for audit, ask again, and never
111
+ require a reusable returned value during the verification refetch.
86
112
  - Consumer hardware, IoT, or retail experience level and supporting summary:
87
113
  the two global `employment.consumer_hardware_iot_retail_experience_*` fields.
88
114
  - Residential city and EEO sexual orientation remain their existing canonical
@@ -0,0 +1,123 @@
1
+ # Deterministic answer resolution
2
+
3
+ Run this resolver after reading the current application-profile revision and
4
+ inventorying a visible employer form, but before generating that form's
5
+ question packet or writing an answer. Profile onboarding that happens before
6
+ an employer form exists uses the profile schema's field metadata instead.
7
+ This resolver reduces questions without weakening truthfulness.
8
+
9
+ ## Resolution pipeline
10
+
11
+ For each visible question:
12
+
13
+ 1. Normalize its label, help text, expected input type, choices, employer scope,
14
+ jurisdiction, and whether it is required.
15
+ 2. Map it to one exact current schema key. Do not map by a single keyword.
16
+ 3. Resolve one authority class:
17
+ - `exact_profile`: an answered canonical key with matching scope and type.
18
+ - `safe_derivation`: a deterministic derivation allowed below.
19
+ - `supported_draft`: free text whose every factual claim has evidence.
20
+ - `missing_fact`: no authoritative value exists; ask at point of use.
21
+ - `live_consent`: a notice, acknowledgement, certification, or choice that
22
+ must be made against the current form.
23
+ - `forbidden_inference`: a legal, identity, relationship, or specialized
24
+ claim that cannot be inferred.
25
+ 4. Validate the expected input type before translating or committing a value.
26
+ 5. Fill only the first three classes. Put only visible `missing_fact`,
27
+ `live_consent`, and user-resolvable `forbidden_inference` items in one
28
+ grouped question packet with expected type and scope.
29
+
30
+ Keep this value-free local provenance for each resolved control: profile
31
+ revision, schema key, scope, authority class, derivation rule when any,
32
+ expected type, and committed-control receipt. Never send the answer value in
33
+ an observation.
34
+
35
+ ## Source precedence
36
+
37
+ The current profile revision is the only reusable answer authority. A
38
+ transcript or conversation, screenshot, parser output, browser autocomplete,
39
+ or cached value is never authority. Resume parsing is a suggestion only: it may
40
+ help identify a schema key, but the parser never becomes authority and never
41
+ overwrites an answered profile value or a user-edited control.
42
+
43
+ Contact details and addresses must resolve from exact current profile keys.
44
+ Verify their live committed state even when the browser initially displays a
45
+ masked or formatted value.
46
+
47
+ For an ordinary application name, use the canonical first and last name and
48
+ reconcile parser-created casing. A government-ID full legal name is different:
49
+ use it only when the form explicitly asks for a government-ID or legal-document
50
+ name and the dedicated restricted profile field is explicitly answered. Never
51
+ derive it from ordinary name fields.
52
+
53
+ Employment status outranks legacy employer columns. When status is
54
+ `not_employed`, current company and current title are non-authoritative even if
55
+ legacy values remain populated. For a current-employer control, commit boolean
56
+ No, an exact “Not currently employed” option, or an intentionally blank optional
57
+ text control according to the visible control semantics. Ask only when a
58
+ required control offers no truthful non-employed representation. Answer
59
+ prior-employer questions from
60
+ `employment.most_recent_company` and `employment.most_recent_title`, and leave current-employer
61
+ questions unresolved unless the schema supplies an applicable authoritative
62
+ answer. `student`, `other`, or unknown status fails closed when applicability
63
+ is ambiguous.
64
+
65
+ ## Safe experience derivation
66
+
67
+ Read the wording semantically:
68
+
69
+ - A minimum qualification/profile range asks whether experience meets the
70
+ lower threshold. A profile value of 7 years answers “2–4 years?” as Yes when
71
+ the choices mean “at least 2 years” or a qualification bracket.
72
+ - An exact bounded band asks whether the value lies inside both bounds. With 7
73
+ years, “exactly between 2 and 4 years?” is No.
74
+ - An explicit disqualifying maximum is a ceiling. With 7 years, “no more than 4 years?” is No.
75
+
76
+ Never infer specialized experience from total product-management tenure.
77
+ Financial-infrastructure years, growth-focused software PM years, enterprise
78
+ SaaS PM years, health-tech experience, and named platform/tool experience need
79
+ their own schema keys or an explicit user answer.
80
+
81
+ Type must match the control. Returning Yes for “financial-infrastructure
82
+ years” is a type mismatch; ask for a whole number. Returning C2 for SQL
83
+ proficiency is a type mismatch when the field expects a proficiency enum.
84
+ English C2 is valid when the English field expects a CEFR level. Never coerce a
85
+ type mismatch into a plausible-looking answer.
86
+
87
+ ## Employer relationships and legal boundaries
88
+
89
+ A direct prior-employment No may be derived only under the complete-history
90
+ rule in the main skill. It never proves no contractor, affiliate, subsidiary,
91
+ acquisition, family, friendship, referral, or other relationship. Those need
92
+ an exact matching scoped field or a user answer.
93
+
94
+ Region authorization is separate from country authorization. A region may be
95
+ answered Yes only when it is explicitly present in
96
+ `authorization.legally_authorized_regions`; absence is unknown, never No, and a
97
+ region answer must not be stored under a country jurisdiction.
98
+
99
+ Commute willingness and days-per-week commitments are employer-office facts,
100
+ not company-wide facts. Resolve them only from an exact `office` scope whose
101
+ identity combines the backend company ID and the specific office; another
102
+ office at the same employer remains unknown.
103
+
104
+ A named applicant privacy notice may be acknowledged only as the current
105
+ notice policy allows and with its current fingerprint. Marketing, data
106
+ retention, arbitration, background check, and interview recording are separate
107
+ choices. Onsite willingness does not prove that a workplace policy was read or
108
+ accepted. Truthfulness certification is always per-run and never reusable.
109
+
110
+ ## Supported writing
111
+
112
+ For a useful optional motivation or experience prompt, `supported_draft` means
113
+ fill it when every factual claim is supported. Build from canonical facts and
114
+ evidence claims, state explicit gaps instead of inventing bridge facts, match
115
+ the saved voice rules, lint, commit, and reread. The user's final truth review
116
+ still governs the complete review-ready subset.
117
+
118
+ Ask the user only for a genuinely new fact, subjective choice, live consent,
119
+ or unsupported claim that the visible application requires. A visible employer
120
+ accuracy checkbox is a live consent resolved before the review-ready
121
+ checkpoint; the separate Trackly truth certification is requested only
122
+ afterward for the exact durable review-ready subset. Defer reusable
123
+ gaps that no current visible form needs.
@@ -40,4 +40,11 @@ Before entering the response:
40
40
 
41
41
  When `writing.optional_question_policy` permits it, answer a strategically useful optional motivation, experience, product, or role-overlap question when every fact is supported. Optional does not mean skip. Leave demographic, consent, legal, compensation, and employer-relationship questions unanswered when their canonical value is unknown. Group those unknowns into the consolidated question packet.
42
42
 
43
+ A supported strategically useful optional prompt must not be left blank. Draft
44
+ it from canonical evidence claims, expose any explicit gaps instead of
45
+ inventing bridge facts, match the user's voice, pass deterministic lint, enter
46
+ the committed text, and reread it from the live control. Ask only when a new
47
+ fact or subjective choice is actually required; the final truth review remains
48
+ the user's approval boundary.
49
+
43
50
  Before entering any answer, supply value-free claim fingerprints and evidence-reference codes to the local linter. It returns only a draft hash, length, policy, and violation codes. It never echoes or sends the draft to Trackly.
@@ -116,6 +116,16 @@ With protocol 3.5 or newer and the compact-snapshot capability enabled, prefer `
116
116
 
117
117
  Every compact snapshot request must contain a non-empty list of current member IDs. Never use an empty member projection as shorthand for all members.
118
118
 
119
+ For an office-scoped question visible on one member, add an `officeProjections`
120
+ entry containing only that member ID, its canonical `companyId:office-identity`,
121
+ and the exact office-only keys needed by the form. Read the answer only from the
122
+ matching `memberOfficeProfiles` entry. Never copy it into the shared profile
123
+ projection or reuse it for a sibling member, another office, or another
124
+ employer. Recovery does not weaken this binding: Trackly must prove the
125
+ candidate's frozen company identity through its source lineage. A legacy
126
+ candidate without that immutable identity stays unknown and must be confirmed
127
+ from the visible form/user rather than inferred from current job metadata.
128
+
119
129
  The execution freezes one original recent-first queue snapshot and ordering
120
130
  version. Newly saved jobs wait for the next execution. Every continuation is a
121
131
  new immutable child batch linked in wave order; never append, replace, reorder,
@@ -300,6 +310,22 @@ evidence cannot satisfy the current gate.
300
310
 
301
311
  ## First pass
302
312
 
313
+ Accessible-first is a hard scheduler invariant. While any accessible candidate remains, a known authentication-gated, account-creation, OTP, or pre-form
314
+ CAPTCHA candidate must not be opened. When access is unknown, perform only a
315
+ minimal non-mutating probe. If a gate appears, never start a draft or enter
316
+ private data: record the typed disposition, park it, and continue accessible
317
+ work. The backend disposition must release its reservation or capacity and
318
+ increment the authoritative `authParked` funnel count before replacement
319
+ scheduling. If that durable transition conflicts, preserve the surface and
320
+ surface the conflict; never invent released capacity locally.
321
+
322
+ An authoritative already-parked member is not first-pass browser work and
323
+ needs no duplicate disposition. A server access hint is not authoritative: if
324
+ accessible candidates remain, defer that hinted member; after accessible work
325
+ is exhausted, perform the minimal live probe required by the public
326
+ disposition contract. “Process every frozen member” below means every currently
327
+ actionable, non-parked member subject to this deferral rule.
328
+
303
329
  Process every frozen member even when another needs user input:
304
330
 
305
331
  1. Create or reuse its bound application run.
@@ -60,8 +60,11 @@ agent-written fingerprint.
60
60
 
61
61
  ## Contact-field integrity
62
62
 
63
- - Compare email and phone to the exact Trackly values after resume parsing and autofill.
64
- - Read both DOM input state and macOS accessibility state.
63
+ - Resolve email, phone, name, and address from exact canonical keys in the current profile revision. Transcript or conversation text, screenshots, parser output, autocomplete, and cached values are never reusable authority.
64
+ - Compare email and phone to the exact Trackly values after resume parsing and autofill. Reconcile parser-altered ordinary name casing to canonical first/last-name casing; never use that rule for a government-ID name.
65
+ - Read both DOM input state and macOS accessibility state. A static HTML `value` attribute is not truth or proof of the live committed control state; require rendered live state, framework/native state, or accessibility state after events settle.
66
+ - For a masked phone control, compare its normalized live digits and country code to the canonical phone rather than requiring identical formatting. Prove an implicit country code from live control metadata, a committed country selector, or framework/native state; ten visible digits alone do not prove it.
67
+ - For address autocomplete, choose the exact offered option and prove that option committed through the control's semantic state; displayed typed text alone is insufficient.
65
68
  - Reject duplicate or concatenated values, autofill overlays, placeholder-only text, and values present only in a custom wrapper.
66
69
  - Recheck when navigation, resume parsing, or a correction banner rerenders the form.
67
70
  - Before review, inventory every email, phone, country-code, and required contact control. Pass `critical_contact_integrity` only when all present canonical values are exact and no required contact field was omitted. A form with no contact control passes only after the whole-form inventory confirms none exists or is required.