Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
ef9e2ab
fix(plugin): fence stale Deep publication and preserve replay
mldangelo-oai Sep 12, 2026
1179155
Merge remote-tracking branch 'origin/main' into mdangelo/codex/phase1…
mldangelo-oai Sep 16, 2026
0e623ac
test(plugin): preserve legacy Deep publication replay
mldangelo-oai Sep 16, 2026
fed7140
Preserve Deep parent drafts and reducer publication order
mldangelo-oai Sep 16, 2026
4173b55
fix(deep): recover accepted publication response loss
mldangelo-oai Sep 16, 2026
4a5983f
Read reducer sequence from its worker directory
mldangelo-oai Sep 16, 2026
f9aae33
Merge reducer directory ordering prerequisite
mldangelo-oai Sep 16, 2026
2cb3645
Keep accepted publication successful when receipt I/O fails
mldangelo-oai Sep 16, 2026
91515fa
Distinguish Windows receipt storage errors from path validation
mldangelo-oai Sep 16, 2026
774b4d5
Emulate Windows temporary-file deletion on handle close
mldangelo-oai Sep 16, 2026
685fc98
Merge main and reconcile Deep Scan publication fencing
mldangelo-oai Oct 6, 2026
30f7bc5
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
1152827
Merge updated parent and preserve publication recovery
mldangelo-oai Oct 6, 2026
e021cf4
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
17a9944
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
591b1e1
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
3935943
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
bc06ffc
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
8787aaa
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
bdd3733
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
832a3c6
Merge updated base branch into PR branch
mldangelo-oai Oct 6, 2026
8d70ac6
Merge current main into publication replay branch
mldangelo-oai Oct 6, 2026
5e89f16
fix(deep): preserve terminal aggregates during completion replay
mldangelo-oai Oct 6, 2026
ff7bd08
Merge current main into publication response recovery branch
mldangelo-oai Oct 6, 2026
afd844d
Merge current publication replay prerequisite
mldangelo-oai Oct 6, 2026
dabab96
fix(deep): preserve accepted publication results during recovery
mldangelo-oai Oct 6, 2026
02a7c39
fix(deep): retain Windows receipt probe I/O errors
mldangelo-oai Oct 6, 2026
5a0998c
Merge main into accepted-publication recovery
mldangelo-oai Oct 6, 2026
e97ea58
Merge commit 'a7b3618f4c5e63aa70f4defa4e72bbe8e0c4cdb1' into HEAD
mldangelo-oai Oct 6, 2026
2d6aaee
Merge commit 'refs/codex/pr-repair/907/current-main' into HEAD
mldangelo-oai Oct 6, 2026
d68f027
Merge current main into publication recovery
mldangelo-oai Oct 6, 2026
d7a0591
Preserve partial Deep setup drafts before orchestration
mldangelo-oai Oct 6, 2026
c4850d0
Merge stack parent into publication recovery
mldangelo-oai Oct 6, 2026
8636afc
Merge current main into publication recovery
mldangelo-oai Oct 6, 2026
151946f
Merge commit 'refs/codex/pr-repair/916/main-f894' into HEAD
mldangelo-oai Oct 6, 2026
c07d281
Merge current stack parent into publication recovery
mldangelo-oai Oct 6, 2026
59d36f5
Merge commit 'refs/codex/pr-repair/916/current-main' into HEAD
mldangelo-oai Oct 7, 2026
dfabb2d
Merge commit 'da446104d4328435e7b305c79c4f26f80e771ccf' into HEAD
mldangelo-oai Oct 7, 2026
5dceb5e
fix(deep-scan): retain terminal legacy publication during replay
mldangelo-oai Oct 7, 2026
1665efe
test(deep-scan): retain actual SQLite replay crash boundaries
mldangelo-oai Oct 7, 2026
1656243
Merge main into Deep Scan publication updates
mldangelo-oai Oct 7, 2026
1cf7c2a
fix(deep): preserve selected legacy publications during completion re…
mldangelo-oai Oct 7, 2026
cf3f24d
fix(deep): preserve legacy progress and selected-result replay
mldangelo-oai Oct 7, 2026
7da9822
Merge main into Deep Scan publication updates
mldangelo-oai Oct 7, 2026
0118c1a
style(deep): format selected publication check
mldangelo-oai Oct 7, 2026
51397ff
Merge main into Deep publication fix
mldangelo-oai Oct 7, 2026
40246ad
Merge main into Deep publication fix
mldangelo-oai Oct 7, 2026
f1ba502
Merge main into Deep publication update
mldangelo-oai Oct 7, 2026
e215264
fix(deep): retain parent publication after legacy lease claims
mldangelo-oai Oct 7, 2026
d2a1775
Merge main into Deep publication fixes
mldangelo-oai Oct 7, 2026
38f5710
Merge main into Deep publication fixes
mldangelo-oai Oct 7, 2026
1a14d67
Merge main into Deep publication fixes
mldangelo-oai Oct 7, 2026
be75948
Merge main into Deep Scan publication fix
mldangelo-oai Oct 7, 2026
0e30641
Merge main into Deep Scan publication fix
mldangelo-oai Oct 7, 2026
6eacf7d
Merge main into Deep Scan publication fix
mldangelo-oai Oct 8, 2026
a51c643
Merge main into Deep Scan publication fix
mldangelo-oai Oct 8, 2026
21eb50f
fix(deep): align publication with recovered reducer selection
mldangelo-oai Oct 8, 2026
d46c854
Merge main into Deep Scan publication fix
mldangelo-oai Oct 8, 2026
26ff705
Merge main into Deep Scan publication fixes
mldangelo-oai Oct 8, 2026
6591067
Merge main into Deep Scan publication fixes
mldangelo-oai Oct 8, 2026
39f06e6
test(workbench): track the current migration history
mldangelo-oai Oct 8, 2026
f7fcedd
Merge main into Deep Scan publication fixes
mldangelo-oai Oct 8, 2026
2ff4fe4
Merge main into Deep Scan publication fixes
mldangelo-oai Oct 8, 2026
bc7a120
fix(deep): retain progress after publication selection
mldangelo-oai Oct 8, 2026
0b97a93
Merge main into Deep publication changes
mldangelo-oai Oct 8, 2026
e605e4e
Merge main into Deep publication changes
mldangelo-oai Oct 8, 2026
f5b5f07
Merge main into Deep publication changes
mldangelo-oai Oct 8, 2026
473f0ab
Merge main into Deep publication changes
mldangelo-oai Oct 8, 2026
fb7e9f9
Merge main into Deep publication changes
mldangelo-oai Oct 8, 2026
f8ef501
Merge main into Deep Scan publication fix
mldangelo-oai Oct 8, 2026
95b9884
Merge main into Deep publication fix
mldangelo-oai Oct 9, 2026
c17a4a5
Merge main into Deep publication fix
mldangelo-oai Oct 9, 2026
8829d13
Merge main into Deep publication fix
mldangelo-oai Oct 9, 2026
5de8cfd
Merge main into Deep publication fix
mldangelo-oai Oct 9, 2026
78d37a2
Merge main into Deep publication fix
mldangelo-oai Oct 9, 2026
3a09174
Merge main into Deep publication fix
mldangelo-oai Oct 9, 2026
af584a1
Merge main and preserve Deep Scan publication metadata
mldangelo-oai Oct 10, 2026
517cbe9
Merge current stack parent into publication recovery
mldangelo-oai Oct 10, 2026
9f9f33b
Merge current main into publication recovery
mldangelo-oai Oct 10, 2026
812c72a
style(deep-scan): format accepted publication comparison
mldangelo-oai Oct 10, 2026
71cf387
Merge current stack parent into publication recovery
mldangelo-oai Oct 10, 2026
ea5d3b5
chore: merge main and resolve integration conflicts
mldangelo-oai Oct 11, 2026
5dbb147
Merge current parent and main into publication recovery
mldangelo-oai Oct 12, 2026
95f1160
fix(publication): keep late Windows path failures fatal
mldangelo-oai Oct 12, 2026
5d798c5
Merge main into Deep Scan publication recovery
codex Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion plugins/codex-security/mcp-app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,7 @@ export function createCodexSecurityServer(): McpServer {
log: logDeepScanEvent,
handoffClaimToken,
threadId,
onComplete: async (draft, signal) => {
onComplete: async (draft, signal, publication) => {
const context = await createScanArtifactContext(
begun.scanId,
runWorkbench,
Expand All @@ -1206,6 +1206,7 @@ export function createCodexSecurityServer(): McpServer {
},
runWorkbench,
signal,
publication,
);
},
onStopped: async (run) => {
Expand Down
45 changes: 42 additions & 3 deletions plugins/codex-security/mcp-app/src/artifact-scan-draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { JsonObject } from "./types.js";
import { isRecord as isObject } from "./record.js";
import { createHash, randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
import { dirname, join, sep } from "node:path";
import { basename, dirname, join, sep } from "node:path";
import { isDeepStrictEqual } from "node:util";
import type * as z from "zod/v4";
import commonSchema from "../../schemas/definitions/artifact-common.schema.json";
Expand Down Expand Up @@ -57,6 +57,12 @@ interface PreparedScanDraft {
coverage: JsonObject;
}

/** Host-selected Deep aggregate, separate from model-authored draft fields. */
export interface DeepScanPublication {
coordinatorGeneration?: number;
resultPath: string | null;
}

interface SavedScanDraft {
input: ScanDraftInput;
modifiedMs: number;
Expand Down Expand Up @@ -207,6 +213,7 @@ export async function recordCodexSecurityScanDraftViaWorkbench(
input: ScanDraftInput,
runWorkbench: RunArtifactWorkbench,
signal?: AbortSignal,
publication?: DeepScanPublication,
): Promise<ScanDraftResult> {
return recordCodexSecurityScanDraft(
context,
Expand All @@ -222,11 +229,18 @@ export async function recordCodexSecurityScanDraftViaWorkbench(
["drafts", `${randomUUID()}.json`],
"staged scan draft",
);
const acceptanceName = `${basename(draftPath, ".json")}.accepted.json`;
try {
const { handoffClaimToken: _claim, ...snapshot } = checkpoint;
const stagedDraft = {
...draft,
...(publication === undefined
? {}
: { deepScanPublication: publication }),
};
await Promise.all([
replaceArtifactJson(checkpointPath, snapshot),
replaceArtifactJson(draftPath, draft),
replaceArtifactJson(draftPath, stagedDraft),
]);
const arguments_ = [
"write-scan-draft",
Expand All @@ -243,14 +257,38 @@ export async function recordCodexSecurityScanDraftViaWorkbench(
if (context.handoffClaimToken) {
arguments_.push("--claim-token", context.handoffClaimToken);
}
try {
const publish = async () => {
const result = await runWorkbench(arguments_);
return Array.isArray(result?.warnings)
? result.warnings.filter(
(warning): warning is string => typeof warning === "string",
)
: undefined;
};
try {
return await publish();
} catch (error) {
if (context.mode === "deep" && publication !== undefined) {
const accepted = await readArtifactJsonObject(
context,
["drafts", acceptanceName],
"accepted scan draft",
).catch(() => undefined);
if (
isDeepStrictEqual(
accepted,
JSON.parse(
JSON.stringify({
status: "draft_written",
input: { ...stagedDraft, checkpoint: snapshot },
}),
),
)
) {
signal?.throwIfAborted();
return await publish();
Comment thread
mldangelo-oai marked this conversation as resolved.
Outdated
Comment thread
mldangelo-oai marked this conversation as resolved.
Outdated
}
}
if (!workbenchScanDraftConflict(error)) throw error;
throw Object.assign(
new Error(
Expand All @@ -263,6 +301,7 @@ export async function recordCodexSecurityScanDraftViaWorkbench(
await Promise.all([
fs.rm(checkpointPath, { force: true }),
fs.rm(draftPath, { force: true }),
fs.rm(join(dirname(draftPath), acceptanceName), { force: true }),
Comment thread
mldangelo-oai marked this conversation as resolved.
Outdated
]);
}
},
Expand Down
19 changes: 16 additions & 3 deletions plugins/codex-security/mcp-app/src/deep-scan/coordinator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference lib="es2023.array" />
import { randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
import { join } from "node:path";
import { basename, dirname, join } from "node:path";
import { setTimeout as delay } from "node:timers/promises";
import {
createDeepScanArtifacts,
Expand All @@ -14,6 +14,7 @@ import {
} from "./artifact-validation.js";
import {
scanDraftInputSchema,
type DeepScanPublication,
type ScanDraftInput,
} from "../artifact-scan-draft.js";
import type { DeepScanArtifacts } from "./artifacts.js";
Expand Down Expand Up @@ -56,6 +57,7 @@ interface SchedulerResult {
reason: DeepScanTerminalReason;
omittedWorkerIds: string[];
result?: DeepReductionInput;
resultPath?: string;
}

export interface CoordinatorOptions {
Expand All @@ -72,7 +74,11 @@ export interface CoordinatorOptions {
threadId?: string;
heartbeatIntervalMs?: number;
observeReplacement?: (run: DeepScanRunState) => Promise<DeepScanRunState>;
onComplete?: (draft: ScanDraftInput, signal: AbortSignal) => Promise<void>;
onComplete?: (
draft: ScanDraftInput,
signal: AbortSignal,
publication: DeepScanPublication,
) => Promise<void>;
onStopped?: (run: DeepScanRunState) => Promise<void>;
}

Expand Down Expand Up @@ -318,6 +324,10 @@ export class DeepScanCoordinator {
await this.options.onComplete?.(
draft,
this.publicationAbortController.signal,
{
coordinatorGeneration: this.state.coordinatorGeneration,
resultPath: schedulerResult.resultPath ?? null,
},
);
if (this.canceled || this.externallyFailed) return;
this.state = await this.finishWithReplay(schedulerResult);
Expand Down Expand Up @@ -924,6 +934,7 @@ export class DeepScanCoordinator {
reason: stopReason,
omittedWorkerIds: [...new Set(omittedWorkerIds)],
result: latestResult,
resultPath: previousReducerResultPath,
};
}

Expand Down Expand Up @@ -1111,7 +1122,9 @@ function workerLabelSequence(
worker: PersistedDeepScanWorker,
kind: "discovery" | "dedup",
): number {
const match = worker.promptPath.match(new RegExp(`${kind}-(\\d+)`));
const match = basename(dirname(worker.promptPath)).match(
new RegExp(`${kind}-(\\d+)`),
);
return match ? Number(match[1]) : 0;
}

Expand Down
34 changes: 34 additions & 0 deletions plugins/codex-security/mcp-app/tests/clock_workbench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Control the workbench clock while retaining its real commands and database writes."""

import errno
import os
import runpy
import sys
from pathlib import Path

source = Path(sys.argv[1])
sys.argv = [str(source), *sys.argv[2:]]
sys.path.insert(0, str(source.parent))
api = runpy.run_path(str(source), run_name="test_workbench")
instant = os.environ["TEST_WORKBENCH_NOW"]
api["main"].__globals__["now"] = lambda: instant
api["_WORKBENCH_DB_CONTEXT"].now = lambda: instant
if os.environ.get("TEST_WORKBENCH_RECEIPT_IO_FAILURE"):
if os.name == "nt":
from finalize_scan_contract import _windows_scan_local_files

backend = _windows_scan_local_files()
original_replace = backend._rename_handle
else:
original_replace = os.replace

def replace(source, destination, *args, **kwargs):
if str(destination).endswith(".accepted.json"):
raise OSError(errno.ENOSPC, "Synthetic receipt I/O failure", destination)
return original_replace(source, destination, *args, **kwargs)

if os.name == "nt":
backend._rename_handle = replace
else:
os.replace = replace
api["main"]()
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,10 @@ try {
"obsolete.json",
);
await writeFile(obsoleteCheckpointPath, "{malformed obsolete checkpoint\n");
const deepPublication = {
coordinatorGeneration: 3,
resultPath: path.join(deepParentRoot, "workers", "reducer", "result.json"),
};
const deepWorkbenchWrites = mock.fn(async (arguments_: string[]) => {
assert.deepEqual(arguments_.slice(0, 3), [
"write-scan-draft",
Expand All @@ -851,6 +855,8 @@ try {
arguments_[arguments_.indexOf("--checkpoint-path") + 1];
const staged = await readJson(draftPath);
const stagedCheckpoint = await readJson(checkpointPath);
assert.deepEqual(staged.deepScanPublication, deepPublication);
assert.equal(stagedCheckpoint.deepScanPublication, undefined);
assert.deepEqual(staged.findings, acceptedDeepFindings);
assert.deepEqual(staged.coverage, acceptedDeepCoverage);
assert.deepEqual(stagedCheckpoint.findings, acceptedDeepDraft.findings);
Expand All @@ -860,6 +866,8 @@ try {
deepParentContext,
acceptedDeepDraft,
deepWorkbenchWrites,
undefined,
deepPublication,
);
assert.equal(
deepWorkbenchWrites.mock.callCount(),
Expand Down
18 changes: 16 additions & 2 deletions plugins/codex-security/mcp-app/tests/test_deep_scan_coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3446,6 +3446,8 @@ async function testSaturationIgnoresDiscoveryCancellationWriteFailure() {

async function testPublicationUsesAcceptedReducerSnapshot() {
const { fixture, store } = await coordinatorFixture();
fixture.run.coordinatorGeneration = 3;
store.run.coordinatorGeneration = 3;
const commitDedup = store.commitDedup.bind(store);
store.commitDedup = async (commit) => {
const accepted = await commitDedup(commit);
Expand All @@ -3462,12 +3464,19 @@ async function testPublicationUsesAcceptedReducerSnapshot() {
return structuredClone(store.run);
};
const completed: ScanDraftInput[] = [];
const published: {
coordinatorGeneration?: number;
resultPath: string | null;
}[] = [];
const terminal = await runCoordinator(
fixture,
store,
new FakeExecutor({ discoveryCandidateId: "accepted-finding" }),
{
onComplete: async (draft) => void completed.push(structuredClone(draft)),
onComplete: async (draft, _signal, publication) => {
completed.push(structuredClone(draft));
published.push(publication);
},
},
);
assert.equal(terminal?.status, "succeeded", terminal?.error);
Expand All @@ -3477,8 +3486,13 @@ async function testPublicationUsesAcceptedReducerSnapshot() {
"accepted-finding",
);
assert.equal(completed[0].coverage.completeness, "complete");
const reducer = [...store.workers.values()].find(
(worker) => worker.kind === "dedup",
);
assert.deepEqual(published, [
{ coordinatorGeneration: 3, resultPath: reducer!.resultManifestPath },
]);
}

async function testResumeRequiresHistoricalWorkerPrompt(
status: "failed" | "canceled",
) {
Expand Down
Loading
Loading