Skip to content

Commit 4b3c1e8

Browse files
committed
chore(release): v2.14.55
1 parent 585f8ec commit 4b3c1e8

4 files changed

Lines changed: 286 additions & 49 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "job-forge",
3-
"version": "2.14.54",
3+
"version": "2.14.55",
44
"description": "AI-powered job search pipeline for Codex and OpenCode",
55
"type": "module",
66
"bin": {

scripts/apply-queue.mjs

Lines changed: 154 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -758,32 +758,40 @@ export async function authorizeSafeRetry({ projectDir, job, priorAttempt, retryO
758758
}
759759
const outcomePath = join(runDir, 'outcome.json');
760760
const eventsPath = join(runDir, 'events.jsonl');
761-
if (!existsSync(outcomePath) || !existsSync(eventsPath)) {
762-
throw new Error(`${job.id} prior attempt lacks outcome and MCP trace evidence`);
763-
}
764-
const outcome = parseJson(await readFile(outcomePath, 'utf8'), outcomePath);
765-
for (const [label, expected, actual] of [
766-
['candidate', priorAttempt.candidateId, outcome.candidateId],
767-
['company', job.company, outcome.company],
768-
['role', job.role, outcome.role],
769-
['URL', job.url, outcome.url],
770-
]) {
771-
if (expected !== actual) throw new Error(`${job.id} prior outcome ${label} does not match`);
772-
}
773-
if (outcome.submissionAttempted !== false
774-
|| outcome.submitActionCount !== 0
775-
|| outcome.otpConfirmationActions !== 0
776-
|| !['not-submitted', 'not-applicable'].includes(outcome.confirmation?.state)
777-
|| outcome.confirmation?.source !== 'none') {
778-
throw new Error(`${job.id} prior attempt cannot prove a pre-submit terminal outcome`);
779-
}
780-
if (!Number.isInteger(outcome.runActionsTransactions)
781-
|| outcome.runActionsTransactions < 0
782-
|| outcome.runActionsTransactions > 1
783-
|| !Number.isInteger(outcome.runActionsContinuations)
784-
|| outcome.runActionsContinuations < 0
785-
|| outcome.runActionsContinuations > 1) {
786-
throw new Error(`${job.id} prior outcome has invalid run-actions counts`);
761+
const guardPath = join(runDir, 'tool-guard-state.json');
762+
const hasOutcome = existsSync(outcomePath);
763+
const hasGuard = existsSync(guardPath);
764+
if (!existsSync(eventsPath)) {
765+
throw new Error(`${job.id} prior attempt lacks MCP trace evidence`);
766+
}
767+
if (!hasOutcome && !hasGuard) {
768+
throw new Error(`${job.id} prior attempt lacks outcome and guarded startup evidence`);
769+
}
770+
const outcome = hasOutcome ? parseJson(await readFile(outcomePath, 'utf8'), outcomePath) : null;
771+
if (outcome) {
772+
for (const [label, expected, actual] of [
773+
['candidate', priorAttempt.candidateId, outcome.candidateId],
774+
['company', job.company, outcome.company],
775+
['role', job.role, outcome.role],
776+
['URL', job.url, outcome.url],
777+
]) {
778+
if (expected !== actual) throw new Error(`${job.id} prior outcome ${label} does not match`);
779+
}
780+
if (outcome.submissionAttempted !== false
781+
|| outcome.submitActionCount !== 0
782+
|| outcome.otpConfirmationActions !== 0
783+
|| !['not-submitted', 'not-applicable'].includes(outcome.confirmation?.state)
784+
|| outcome.confirmation?.source !== 'none') {
785+
throw new Error(`${job.id} prior attempt cannot prove a pre-submit terminal outcome`);
786+
}
787+
if (!Number.isInteger(outcome.runActionsTransactions)
788+
|| outcome.runActionsTransactions < 0
789+
|| outcome.runActionsTransactions > 1
790+
|| !Number.isInteger(outcome.runActionsContinuations)
791+
|| outcome.runActionsContinuations < 0
792+
|| outcome.runActionsContinuations > 1) {
793+
throw new Error(`${job.id} prior outcome has invalid run-actions counts`);
794+
}
787795
}
788796

789797
const readOnlyOrLifecycleTools = new Set([
@@ -803,8 +811,6 @@ export async function authorizeSafeRetry({ projectDir, job, priorAttempt, retryO
803811
'geometra_wait_for_resume_parse',
804812
'geometra_workflow_state',
805813
]);
806-
const guardPath = join(runDir, 'tool-guard-state.json');
807-
const hasGuard = existsSync(guardPath);
808814
const mutationCalls = new Map();
809815
const opaqueCommandExecutions = new Set();
810816
const safeGmailTools = new Set(['get_message', 'gmail_get_message', 'gmail_list_messages', 'list_messages']);
@@ -850,6 +856,7 @@ export async function authorizeSafeRetry({ projectDir, job, priorAttempt, retryO
850856
const mutations = [...mutationCalls.values()];
851857

852858
let guardTerminalState = 'legacy-zero-mutation';
859+
let outcomeLessRuntimeEvidence = null;
853860
if (hasGuard) {
854861
if (opaqueCommandExecutions.size > 0) {
855862
throw new Error(`${job.id} guarded prior trace contains opaque command execution`);
@@ -867,9 +874,11 @@ export async function authorizeSafeRetry({ projectDir, job, priorAttempt, retryO
867874
}
868875
const guardProvesPreSubmit = guard.runActionsTerminalState === 'proven-pre-submit'
869876
&& guard.submitBoundaryObserved === false
877+
&& guard.otpFills === 0
870878
&& guard.otpConfirmationClicks === 0;
871879
const guardProvesNoTransaction = guard.runActionsCalls === 0
872880
&& guard.submitBoundaryObserved === false
881+
&& guard.otpFills === 0
873882
&& guard.otpConfirmationClicks === 0;
874883
if (!guardProvesPreSubmit && !guardProvesNoTransaction) {
875884
throw new Error(`${job.id} prior guard cannot prove execution stopped before Submit`);
@@ -879,25 +888,44 @@ export async function authorizeSafeRetry({ projectDir, job, priorAttempt, retryO
879888
|| (!guardProvesPreSubmit && mutations.length > 0)) {
880889
throw new Error(`${job.id} prior MCP trace contains an unproven mutation`);
881890
}
882-
const guardedTransactions = guard.runActionsCalls > 0 ? 1 : 0;
883-
const guardedContinuations = Math.max(0, guard.runActionsCalls - 1);
884-
const guardedSessions = guard.connectSucceeded ? 1 : 0;
885-
if (outcome.runActionsTransactions !== guardedTransactions
886-
|| outcome.runActionsContinuations !== guardedContinuations
887-
|| outcome.sessionsOpened !== guardedSessions
888-
|| (guardedSessions === 1 && outcome.sessionId !== guard.ownedSessionId)
889-
|| (guardedSessions === 0 && outcome.sessionId !== null)
890-
|| outcome.ownSessionDisconnected !== guard.ownedSessionDisconnected) {
891-
throw new Error(`${job.id} prior outcome does not match its guarded execution ledger`);
892-
}
893-
if (guardedTransactions > 0 && outcome.confirmation?.state !== 'not-submitted') {
894-
throw new Error(`${job.id} guarded pre-submit transaction requires not-submitted confirmation state`);
895-
}
896-
if (guardedTransactions === 0 && !['not-submitted', 'not-applicable'].includes(outcome.confirmation?.state)) {
897-
throw new Error(`${job.id} guarded zero-transaction outcome has an invalid confirmation state`);
891+
if (outcome) {
892+
const guardedTransactions = guard.runActionsCalls > 0 ? 1 : 0;
893+
const guardedContinuations = Math.max(0, guard.runActionsCalls - 1);
894+
const guardedSessions = guard.connectSucceeded ? 1 : 0;
895+
if (outcome.runActionsTransactions !== guardedTransactions
896+
|| outcome.runActionsContinuations !== guardedContinuations
897+
|| outcome.sessionsOpened !== guardedSessions
898+
|| (guardedSessions === 1 && outcome.sessionId !== guard.ownedSessionId)
899+
|| (guardedSessions === 0 && outcome.sessionId !== null)
900+
|| outcome.ownSessionDisconnected !== guard.ownedSessionDisconnected) {
901+
throw new Error(`${job.id} prior outcome does not match its guarded execution ledger`);
902+
}
903+
if (guardedTransactions > 0 && outcome.confirmation?.state !== 'not-submitted') {
904+
throw new Error(`${job.id} guarded pre-submit transaction requires not-submitted confirmation state`);
905+
}
906+
if (guardedTransactions === 0 && !['not-submitted', 'not-applicable'].includes(outcome.confirmation?.state)) {
907+
throw new Error(`${job.id} guarded zero-transaction outcome has an invalid confirmation state`);
908+
}
909+
} else {
910+
if (!guardProvesNoTransaction) {
911+
throw new Error(`${job.id} outcome-less guarded retry requires a zero-transaction attempt`);
912+
}
913+
outcomeLessRuntimeEvidence = await validateOutcomeLessRetryRuntime({
914+
projectDir,
915+
runDir,
916+
guardPath,
917+
eventsPath,
918+
guard,
919+
priorAttempt,
920+
job,
921+
});
922+
guardTerminalState = 'guarded-zero-transaction';
898923
}
899-
guardTerminalState = guard.runActionsTerminalState;
924+
if (outcome) guardTerminalState = guard.runActionsTerminalState;
900925
} else {
926+
if (!outcome) {
927+
throw new Error(`${job.id} legacy prior attempt cannot retry without a structured outcome`);
928+
}
901929
if (outcome.sessionsOpened !== 0
902930
|| outcome.sessionId !== null
903931
|| outcome.ownSessionDisconnected !== false
@@ -922,15 +950,95 @@ export async function authorizeSafeRetry({ projectDir, job, priorAttempt, retryO
922950
priorAttemptId: retryOfAttemptId,
923951
evidence: {
924952
runDir: relativeProjectPath(projectDir, runDir),
925-
outcome: relativeProjectPath(projectDir, outcomePath),
953+
outcome: hasOutcome ? relativeProjectPath(projectDir, outcomePath) : null,
926954
events: relativeProjectPath(projectDir, eventsPath),
955+
guard: hasGuard ? relativeProjectPath(projectDir, guardPath) : null,
956+
workerRuntime: outcomeLessRuntimeEvidence
957+
? relativeProjectPath(projectDir, outcomeLessRuntimeEvidence.runtimePath)
958+
: null,
959+
legacyStartupFailure: outcomeLessRuntimeEvidence?.legacyStartupFailure || false,
960+
startupStderr: outcomeLessRuntimeEvidence?.stderrPath
961+
? relativeProjectPath(projectDir, outcomeLessRuntimeEvidence.stderrPath)
962+
: null,
927963
guardTerminalState,
928964
mutationStarts: mutations,
929965
opaqueCommandExecutions: opaqueCommandExecutions.size,
930966
},
931967
};
932968
}
933969

970+
async function validateOutcomeLessRetryRuntime({
971+
projectDir,
972+
runDir,
973+
guardPath,
974+
eventsPath,
975+
guard,
976+
priorAttempt,
977+
job,
978+
}) {
979+
const runtimePath = join(runDir, 'worker-runtime.json');
980+
if (!existsSync(runtimePath)) {
981+
throw new Error(`${job.id} outcome-less guarded retry lacks worker runtime evidence`);
982+
}
983+
const runtime = parseJson(await readFile(runtimePath, 'utf8'), runtimePath);
984+
const expectedGuardPath = relativeProjectPath(projectDir, guardPath);
985+
const allowedServers = Array.isArray(runtime.allowedMcpServers)
986+
? [...new Set(runtime.allowedMcpServers)].sort()
987+
: [];
988+
const expectedServers = [...ALLOWED_APPLY_MCP_SERVERS].sort();
989+
const guardsMatch = [
990+
runtime.packagedPreToolUseGuard,
991+
runtime.packagedPostToolUseGuard,
992+
runtime.packagedPermissionRequestGuard,
993+
].every((path) => path === expectedGuardPath);
994+
if (runtime.permissionProfile !== 'jobforge_apply'
995+
|| runtime.approvalPolicy !== 'never (Codex exec invariant)'
996+
|| runtime.approvalsReviewer !== 'user'
997+
|| runtime.shellToolEnabled !== false
998+
|| runtime.ignoreUserConfig !== true
999+
|| runtime.ignoreExecPolicyRules !== true
1000+
|| runtime.hookTrustBypassScopedToPackagedGuard !== true
1001+
|| runtime.alternateBrowserToolsDisabled !== true
1002+
|| runtime.ephemeral !== true
1003+
|| !guardsMatch
1004+
|| allowedServers.length !== expectedServers.length
1005+
|| allowedServers.some((server, index) => server !== expectedServers[index])) {
1006+
throw new Error(`${job.id} outcome-less guarded retry lacks exact worker confinement evidence`);
1007+
}
1008+
if (runtime.ignoreProjectConfig === true) {
1009+
return { runtimePath, legacyStartupFailure: false, stderrPath: null };
1010+
}
1011+
if (runtime.ignoreProjectConfig !== undefined) {
1012+
throw new Error(`${job.id} outcome-less guarded retry lacks project-config isolation evidence`);
1013+
}
1014+
1015+
const expectedGuard = {
1016+
...initialGuardState(guard.authoritativeUrl, guard.projectRoot, {
1017+
company: guard.authoritativeCompany,
1018+
role: guard.authoritativeRole,
1019+
candidateEmailHash: guard.candidateEmailHash,
1020+
configuredProxyHash: guard.configuredProxyHash,
1021+
allowedUploadPaths: guard.allowedUploadPaths,
1022+
}),
1023+
projectRoot: guard.projectRoot,
1024+
};
1025+
const stderrPath = join(runDir, 'stderr.log');
1026+
const expectedReason = `worker exited ${priorAttempt.workerExitCode}; worker produced no structured outcome`;
1027+
if (!Number.isInteger(priorAttempt.workerExitCode)
1028+
|| priorAttempt.workerExitCode === 0
1029+
|| priorAttempt.reason !== expectedReason
1030+
|| (await readFile(eventsPath, 'utf8')).trim() !== ''
1031+
|| JSON.stringify(guard) !== JSON.stringify(expectedGuard)
1032+
|| !existsSync(stderrPath)) {
1033+
throw new Error(`${job.id} legacy outcome-less retry lacks pristine pre-startup failure evidence`);
1034+
}
1035+
const stderr = await readFile(stderrPath, 'utf8');
1036+
if (!/^Error loading config\.toml: invalid transport\r?\nin `mcp_servers\.[^`\r\n]+`\r?\n?$/.test(stderr)) {
1037+
throw new Error(`${job.id} legacy outcome-less retry is not an exact pre-startup config failure`);
1038+
}
1039+
return { runtimePath, legacyStartupFailure: true, stderrPath };
1040+
}
1041+
9341042
function parseMarkdownRows(text) {
9351043
const rows = [];
9361044
for (const line of text.split('\n')) {

0 commit comments

Comments
 (0)