Skip to content

feat(deep): share execution and persist recoverable scan results - #907

Open
mldangelo-oai wants to merge 179 commits into
mainfrom
mdangelo/codex/deep-scan-integration
Open

mldangelo-oai wants to merge 179 commits into
mainfrom
mdangelo/codex/deep-scan-integration

Conversation

@mldangelo-oai

@mldangelo-oai mldangelo-oai commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Managed Standard and Deep scans share execution and audit handling. Durable accepted results let replacement workers and publishers recover the same inputs, unresolved coverage, execution settings and result conversation. Native Deep scans also finish publishing when their last observing request disconnects. SDK Deep scans account for the complete owner turn and enforce its budget before publishing.

Changes

  • Share session construction, stream handling and audit admission while preserving scheduling, retry ownership, custom-validation restrictions and intentional Standard/Deep permission differences.
  • Persist authoritative checkpoint heads, accepted results, attempt/session ownership, original discovery context and deadline, ordered merge inputs and finalization selection. Fence publication to the current coordinator and replay committed persistence operations safely.
  • Preserve rejected checkpoints, distinct required fixes, evidence, unresolved coverage and cancellation causes. Interrupted audits remain distinct from empty successful audits. Cost limits publish partial results without further scan model work.
  • Preserve optional finding identities during cost-limited publication. Generated sibling identities receive distinct instances without changing authored identities or discarding repairs. Accepted artifact bytes and the selected result hash remain unchanged across publication and recovery.
  • Keep native publication with the existing coordinator. Cancellation reaches its parent completion process; local rejoiners retain the owner result, remote observers cannot run a competing finalizer, and a failed owner can recover through its existing lease.
  • Defer both automatic and explicit MCP completion for SDK Deep scans until the SDK accounts for the completed turn and enforces its budget. Preserve strict crossing, equality, result conversation and post-scan follow-ups.
  • Validate sealed artifacts during completed replay without another finalizer, projection rewrite or completion-time change. Corrupted or missing accepted artifacts still fail.
  • Preserve public result fields, raw responses, continuation and saved-result access. Attribute replacement sessions and descendants; missing usage remains distinct from zero.
  • Recover complete copied rollouts and stream plain saved-log JSON through the existing output writer, preserving filtering, repeated events, formatting, backpressure and error behavior.
  • Keep custom-validation compatibility and physically standalone plugin packaging. Public commands, flags, environment variables and defaults remain unchanged.
  • Refresh two missing Windows test-duration estimates from completed test files in the linked CI runs. The existing sharder separates the two expensive suites; test membership, shard counts and timeouts are unchanged.

Testing

Current source: ca53a81d673ffd944115bfe3598d4f5efbfee463, tree 7b8b098b98e02cc6bb4829b6e0fde97d1990c386. Independent source and QA commits resolve to this exact entire tree.

Validation Actual result
Five required portable checks Ruff lint/format, SDK build:ci, source compatibility and all 9 checker tests pass.
Full MCP 80 pass, no failures or skips.
Full Python 1,380 pass, 8 skip, 109 subtests pass; declared test extras and umask 0022.
Full SDK Seed 12345 and default order (actual seed 1457328025) each pass 3,148 tests with 50 skips; types and formatting pass.
Installed SDK and physically standalone plugin Both pass 14 completion modes plus the original cancellation/restart lifecycle. Standalone has no sibling SDK, ancestor node_modules or symlinks. Ordinary package SHA256 d8c9df4115c39862c9b87978d7221814161e5b5b17a8a6b9f075ed62e02555f8; 515 package members and 142 plugin files verified.
SDK usage and post-scan API Normal, equality, strict crossing and explicit MCP crossing pass through the installed public API, shipped MCP and SQLite. Known synthetic usage is 203 input/3 output tokens; normal/equality retain follow-up, strict crossings skip it. loadContract matches returned artifacts.
Deterministic parent comparisons Parent 652298cd reproduces cancellation during finalization, clipped owner usage/budget and competing late completion. The repair passes each process-gated comparison. Parent rejection of corrupted/removed sealed artifacts is retained; the intermediate replay regression is reproduced and fixed.
Recovery invariants Lost-response replay keeps the original completion timestamp, selected bytes and worker starts. Failed-owner recovery reuses the existing lease; heartbeat writes remain independent of blocked database writes.

These are Linux checks using Node 24.13, Python 3.12.13 and stock Git 2.43. They do not establish current-head Windows/macOS behavior. Hosted checks for this head remain pending. Preceding hosted verification and its source/package identity remain separate.

Earlier failures remain recorded: an intermediate replay-integrity regression, obsolete prompt assertion, custom Git configuration behavior, missing test extras, inherited umask 0002 and a missing-build-dependency default-order attempt. Final passes use the declared dependencies and preserve existing path and credential protections. No original failure is counted as a pass.

Lifecycle and usage tests use deterministic model substitutes with real workbench, SQLite, accepted artifacts, publication, restart and saved-result access. Full-target natural and matched scans have complete, partial and failed outcomes. Independent quality, cost and latency comparisons remain in progress; whole billing is unavailable. No production equivalence or comparative quality parity is claimed.

Risk and rollout

This PR contains new writers and remains unmerged. Release compatible reader #919 first. It can finish supported selected results, but cannot resume arbitrary v2 discovery/reduction; unsupported state remains intact. Rollback must understand already-written state.

Sparse reader migrations 45–48 followed by writer migration 44 are tested. Open #816 and #829 both allocate 42 to different SQL; their eventual composition still needs reconciliation while preserving published migration numbers. #839's helper port also needs composition against the current entrypoint. No unused migration reservation is added here.

An unowned remote observer retains its aggregate-ready response and can return before the owner's public seal. Completion retains existing claim, continuation and locking authority; no new generation argument is added to complete-scan. POSIX fake-executable stdio tests skip on Windows; hosted platform coverage is reported separately.

The publication fixes preserve distinct repairs and honest partial output; they do not restore findings to earlier measured empty reports or establish comparative quality parity. Checkpoint persistence is separate from terminal artifact acceptance. A file removed after acceptance can still fail publication. Plain log streaming retains existing limits for individual oversized records and aggregate output controls. Credential, unsafe-path, scan-integrity and explicit permission protections remain enforced. No optional scheduler or provenance-format experiment is adopted.

Public disclosure review

  • No customer, partner, prospect, or user identities, data, or identifying details are included.
  • No credentials, personal data, private source, scan findings, or nonpublic links or tickets are included.
  • I reviewed the branch name, title, description, commits, changes, comments, logs, screenshots, attachments, and links for public disclosure.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0fa83d8de8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sdk/typescript/src/scan-logs.ts Outdated
Comment on lines +184 to +185
for (const path of sessionPaths.get(threadId)!.slice(1)) {
if (await extendsSessionLog(path, session.path)) session.path = path;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare duplicate rollouts after applying scan attribution

When the first-home copy contains the shared scan prefix followed by a later unrelated owner turn, while the recorded-home copy contains that prefix followed by the missing scan-turn events, this raw comparison treats the copies as divergent and retains the truncated first copy; the turn filter runs only afterward, so it removes the unrelated suffix without recovering the recorded scan suffix. Fresh evidence in the exact head is the new extendsSessionLog check, which explicitly rejects every non-prefix duplicate even when attribution makes the recorded copy more complete for this scan; compare or merge the attributed event occurrences instead.

AGENTS.md reference: AGENTS.md:L40-L41

Useful? React with 👍 / 👎.

@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

The saved-log follow-up is now in 4c0039a. Duplicate selection and output use the same existing replay and scan-attribution filter. The reproduced unrelated-owner-tail case now retains the recorded scan suffix, including repeated and non-usage events. Equal, shorter and genuinely divergent attributed streams still keep traversal priority.

The 159 affected tests pass. Independent installed readers and the public CLI preserve all ten expected events versus seven on the parent; stream cleanup/error controls pass. Required full SDK suites and this head's hosted platform checks remain pending and are identified separately in the description. Please review the current complete change.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 4c0039aa15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review exact current head 652298cd96678113f593b0be47f00995aa1958d1, including the complete shared execution, recovery, publication and accounting changes.

Since the previous review, budget-limited publication now assigns missing finding identities without colliding with authored identities, and empty partial reports describe unresolved work accurately. Native Deep completion now belongs to the owning coordinator after selected publication; local observers wait for that finalizer, and completion failures reach active observers. Please review interruption, ownership and result-acceptance behavior, including the new real stdio lifecycle cases.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 652298cd96

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

finish: (input) => this.options.store.finish(input),
});
if (this.canceled || this.externallyFailed) return;
await this.options.onFinalized?.(cloneState(this.state));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop parent finalization when cancellation starts

When cancel_codex_security_scan arrives after selected publication has set the Deep run to succeeded but while this callback is awaiting complete-scan, cancellation aborts the publication controllers but cannot interrupt this finalizer. Moreover, cancelAfterPersistence() deliberately postpones the actual cancel-scan command until the coordinator reaches finally, so complete-scan can seal the parent before cancellation is persisted, leaving an explicitly canceled scan reported as complete. Make this finalization cancellation-aware or persist cancellation before allowing parent completion to commit.

AGENTS.md reference: AGENTS.md:L42-L43

Useful? React with 👍 / 👎.

Comment on lines +820 to +821
onFinalized: async (run) => {
try { await completeSelectedParent(run); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Defer sealing until the owner turn's usage is final

For an SDK Deep scan, this callback runs complete-scan from inside the MCP tool call, before the owning Codex turn emits its final turn.completed usage and before the SDK calls tracker.stop(). The workbench therefore seals the scan and fixes completedAt using only mid-turn rollout data; the later receipt suffix falls outside that attribution window and is represented as an unpriced remainder, so the SDK cannot reconcile a final cost or detect when that suffix crosses maxCostUsd before the scan is already complete. Delay parent completion until the owning turn's usage is final, or provide an authoritative post-completion accounting and budget-enforcement path.

AGENTS.md reference: AGENTS.md:L11-L11

Useful? React with 👍 / 👎.

Comment on lines +761 to +765
const immediate = deepScanTerminalResult(begun.run);
if (immediate) return { begun, immediate };
const completingLocally = begun.run.status === "succeeded"
&& begun.run.finalizationInput
&& deepScanCoordinators.get(begun.run.scanId);
if (immediate && !completingLocally) return { begun, immediate };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep late rejoiners behind the owning finalizer

When another MCP process rejoins after finish-deep-scan commits succeeded but before the owning process's complete-scan settles, its registry cannot see the owner's in-memory coordinator, so this branch treats the result as immediately terminal and the rejoiner subsequently runs completeSelectedParent itself. That creates two cross-process parent finalizers and can let the late rejoiner report success while the original active observers receive the owner's completion failure. Distinguish an actively owned finalization from an orphaned succeeded result before permitting recovery completion.

AGENTS.md reference: AGENTS.md:L42-L43

Useful? React with 👍 / 👎.

@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review exact current head ca53a81d673ffd944115bfe3598d4f5efbfee463, including cancellation during finalization, the owning coordinator’s completion and usage settlement, replay after lost persistence responses, and rejection of missing or corrupt sealed findings. Please review the complete shared execution, recovery, publication and accounting change.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca53a81d67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}): Promise<DeepScanRunState> {
const { run, artifacts, signal } = input;
const selection = selectedInput(run);
if (run.status === "succeeded") return run;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Revalidate selected input before sealing succeeded runs

When the Deep run has reached succeeded but its parent remains running, this early return skips both the selected-result digest check and republication. During the remainder of the SDK owner turn—or after process loss—scan-manifest.json, findings.json, or coverage.json can therefore be replaced with different schema-valid content, and the later complete-scan seals that content because parent readiness checks only the run status and manifest path, not its selected digest. Verify that the canonical draft still derives from finalizationInput, or republish the selected bytes, before accepting this state.

AGENTS.md reference: AGENTS.md:L42-L43

Useful? React with 👍 / 👎.

Comment on lines +819 to +823
if (sdkOwned) return;
try {
await runWorkbench([
"complete-scan", "--scan-id", run.scanId, "--thread-id", threadId,
...optionalArg("--claim-token", handoffClaimToken),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Settle native owner usage after its turn completes

For a native Deep scan (sdkOwned === false), this runs complete-scan from inside start_codex_security_deep_scan, so collect_scan_usage fixes its completion window before the owning turn emits the tool result and final response. Those later charged tokens are permanently omitted from the persisted scan cost, and the redundant completion call prompted afterward does not reconcile cost for already-complete scans. Fresh evidence in the exact head is the new if (sdkOwned) return branch, which defers settlement only for SDK scans while leaving native owners finalized mid-turn.

Useful? React with 👍 / 👎.

Comment on lines +760 to +762
const recoverableSelection = begun.run.status === "succeeded"
&& begun.run.finalizationInput && !sdkOwned;
if (immediate && !completingLocally && !recoverableSelection) return { begun, immediate, sdkOwned };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve cross-thread terminal-result recovery

When a headless Deep coordinator succeeds but its parent remains running and the original tool response is lost, terminal_deep_scan_for_target_snapshot deliberately lets a new thread recover that finished snapshot by calling the start tool with targetPath. This unconditional recoverableSelection now sends that cross-thread result into claimCoordinator, whose ownership check rejects the new thread, so the recovery path fails instead of returning the saved manifest for completion. Keep the immediate terminal return for snapshot reusers that do not own the original scan, while retaining lease-based finalization for actual owners.

Useful? React with 👍 / 👎.

Comment on lines +147 to +151
const saved = JSON.parse(await fs.readFile(path, "utf8"));
if (saved.version !== 1) {
throw new Error("This Deep Scan uses an unsupported execution settings version.");
}
settings = executionSettings(saved.settings);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Authenticate recovered execution settings before spawning

On coordinator takeover, this trusts execution-settings.json directly for codexPath and codexHome, even though the file lives in the scan output that the scan turn can write and no digest or database-bound copy authenticates it. If model-driven work alters this file before a resume, the recovered coordinator passes the substituted codexPath to the worker launcher and executes it as the current user; a substituted home is also traversed for sessions and credentials. Bind the snapshot bytes or security-sensitive fields to durable trusted state at creation and verify that binding before using them.

AGENTS.md reference: AGENTS.md:L6-L8

Useful? React with 👍 / 👎.

@mldangelo-oai
mldangelo-oai marked this pull request as ready for review September 16, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant