Skip to content

Add shared multi-harness debug attachment core - #251

Open
kumanday wants to merge 2 commits into
developfrom
fix/coe-455-debug-attachment-core
Open

kumanday wants to merge 2 commits into
developfrom
fix/coe-455-debug-attachment-core

Conversation

@kumanday

@kumanday kumanday commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

Add the harness-neutral attachment core for host-owned ACP debug sessions.

Changes

  • Validate exact issue workspace, manifests, persisted ACP route/profile, run,
    attempt, opaque session identity, and current terminal or parent envelope.
  • Revalidate the current terminal checkout or parent execution root before
    exposing an attachment.
  • Return live attachments through the retained SessionHandle owner seam only
    after comparing the complete owner identity.
  • Distinguish capability-gated restoration, fresh-context reset, transcript
    inspection, and unavailable context without starting a replacement session.
  • Document the binding and validation boundary and add regression coverage.

Evidence

Test output

cargo test-system-duckdb --lib attachment::tests -- --nocapture
2 passed

cargo test-system-duckdb --test debug -- --nocapture
4 passed

cargo test-system-duckdb --test acp_session_host -- --nocapture
37 passed, 3 ignored

cargo clippy --no-default-features --features duckdb-prebuilt --all-targets -- -D warnings
Finished successfully

cargo test-system-duckdb
1480 unit tests passed; all integration suites passed; documented live/authenticated suites remained ignored

Verification

The pre-change debug suite passed its existing OpenHands and Codex cases but
had no shared ACP attachment resolver. The resolver now rejects parent/store
paths, stale route/profile and envelope bindings, cross-owner snapshots, and
owner-loss races while preserving the existing native debug routes.

Checklist

  • Tests pass (cargo test-system-duckdb)
  • Code is formatted (cargo fmt --all)
  • Clippy is clean (cargo clippy with system DuckDB and all targets)
  • Documentation updated (if behavior changed)
  • AGENTS.md updated (if repo knowledge changed)
  • Evidence section filled out (for substantive changes)

Type of change

  • Bug fix
  • New feature
  • Performance improvement
  • Refactoring
  • Documentation
  • Other

Breaking changes

None. Existing OpenHands and Codex debug routes remain unchanged.

Related issues

  • Linear: COE-455

Additional notes

The repository's clippy-system-duckdb alias currently forwards its baked-in
--all-targets -- -D warnings incorrectly under this Cargo toolchain.
Validation used the equivalent direct command with the same system DuckDB
environment.

Summary:
- validate exact workspace, runtime envelope, profile, and session bindings
- attach through the retained ACP owner and expose transcript/unavailable modes
- document the owner seam and add exact-path/no-replacement regression tests

Rationale:
- IDE and terminal debug consumers need one harness-neutral binding boundary
- owner loss must not silently launch a competing ACP session

Tests:
- cargo fmt --all
- cargo check-system-duckdb
- cargo clippy with system DuckDB and all targets
- cargo test-system-duckdb
@kumanday kumanday added the symphony OpenSymphony work label Oct 8, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T06:56:11.432112Z 198d74f Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 98d8e9427a

ℹ️ 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 on lines +131 to +135
.runtime_envelope
.as_ref()
.ok_or(AttachmentError::BindingMismatch(
"runtime envelope is missing",
))?;

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 Accept parent runtime envelopes for ACP attachments

When the issue is a parent/multi-checkout execution, the production backend creates no TerminalRuntimeEnvelope because the parent workspace has no checkout generation, and persists the binding in run.parent_runtime_envelope instead. This unconditional access therefore rejects every valid parent ACP session as runtime envelope is missing, preventing parent issues from using the new debug attachment core. Validate either the terminal or parent envelope with the appropriate identity checks.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 198d74f: added parent runtime-envelope validation and current parent execution-root verification.

Comment on lines +168 to +172
Ok(owner) => {
let snapshot = owner
.inspect()
.await
.map_err(|error| AttachmentError::BindingMismatch(host_error_detail(error)))?;

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 Verify the looked-up owner matches the validated binding

When stale or copied conversation metadata names another live issue's owner_id with the same generation, SessionHost::lookup returns that issue's handle because it filters only by owner ID and generation. The inspected snapshot is not compared with the validated workspace/profile/run/session target, so the resolver marks the wrong owner as live and exposes its events and control operations, including attachment and retirement. Compare the returned snapshot's complete binding with the target before returning the handle.

AGENTS.md reference: AGENTS.md:L32-L35

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 198d74f: compare the live owner snapshot against the complete persisted ACP identity before exposing it.

Comment on lines +136 to +140
validate_binding(
&issue,
&run,
&conversation,
state.identity.clone(),

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 the envelope against the current checkout

When the issue, run, and conversation manifests are mutually consistent but stale after a checkout generation, target commit, policy, or instruction change, this validation accepts them without checking the actual managed checkout. The existing native debug path calls WorkspaceManager::verify_runtime_envelope_for_retry, which checks the handle generation and current checkout provenance; this resolver should perform the equivalent verification before exposing an owner, otherwise it can attach to a session bound to obsolete workspace contents.

AGENTS.md reference: AGENTS.md:L32-L35

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 198d74f: revalidate terminal checkouts and parent execution roots against the current runtime envelope.

Comment on lines +144 to +145
if let Some(requested) = request.profile_id.as_deref()
&& requested != state.identity.profile_id

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 Validate the ACP state against the persisted run route

When profile_id is omitted, as the request documentation permits, the resolver trusts the conversation state's profile and never checks run.acp_route or even that state.harness is acp. A stale conversation and envelope can therefore remain self-consistent while the current run selected another profile, causing attachment to the prior profile and credential/tool scope instead of rejecting the mismatch. Require the persisted route's harness and profile to match the ACP state before using it.

AGENTS.md reference: AGENTS.md:L165-L169

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 198d74f: require the persisted ACP route and profile to match the conversation state.

Comment on lines +38 to +42
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttachmentMode {
Live,
TranscriptInspection,
Unavailable,

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 Represent restoration and fresh-reset attachment states

After owner loss, the debugging specification requires consumers to distinguish capability-gated restoration and fresh-context reset from transcript inspection, but this enum cannot represent either state and inspect_recorded_session collapses every record with a session ID to TranscriptOnly. A persisted session whose initialization advertises load/resume is therefore reported as transcript-only, while callers lose the actionable restoration status needed to recover it safely. Preserve the negotiated capability result and expose distinct restoration/reset modes without starting a replacement session.

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

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 198d74f: preserve and expose capability-gated restoration and fresh-context reset modes separately from transcript inspection.

Comment on lines +169 to +172
let snapshot = owner
.inspect()
.await
.map_err(|error| AttachmentError::BindingMismatch(host_error_detail(error)))?;

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 Reconcile owner loss after the lookup race

If the retained owner exits after lookup returns its handle but before inspect completes—for example when the idle-retention timer fires or the process terminates—inspect returns HostError::Unavailable, which is converted here into a binding mismatch. This normal owner-loss race should follow the same recorded-session fallback as an unavailable lookup so callers receive TranscriptInspection or Unavailable rather than a false metadata-corruption error.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 198d74f: treat owner-inspection loss as the recorded-session fallback path.

Summary:
- Validate terminal and parent runtime envelopes against current workspace state.
- Check persisted ACP route/profile and complete live-owner identity.
- Preserve restoration, reset, transcript, and unavailable attachment modes.
- Document the expanded attachment validation and regression coverage.

Rationale:
- Prevent stale or cross-issue owners from being exposed through debug attach.
- Keep parent sessions attachable without launching competing runtimes.
- Preserve capability-gated recovery outcomes after owner loss.

Tests:
- cargo test-system-duckdb --lib attachment::tests -- --nocapture
- cargo test-system-duckdb --test acp_session_host -- --nocapture
- cargo test-system-duckdb --test debug -- --nocapture
- cargo clippy --no-default-features --features duckdb-prebuilt --all-targets -- -D warnings
- cargo test-system-duckdb (1480 passed on rerun)
@kumanday

kumanday commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 198d74fbb3

ℹ️ 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".

session_id,
envelope,
)?;
manager.verify_runtime_envelope(workspace, envelope).await?;

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 Permit worker changes when validating an attachment

When attaching after the active ACP agent has modified its checkout, this calls strict verify_runtime_envelope, which invokes Git verification with worker changes disallowed; consequently a normal dirty worktree is rejected before its retained owner can be attached. The existing native debug path deliberately uses verify_runtime_envelope_for_retry, which still validates the generation and envelope while allowing those expected edits, and the ACP resolver needs the same behavior.

Useful? React with 👍 / 👎.

Comment on lines +219 to +221
let snapshot = super::inspect_recorded_session(manager, workspace)
.await
.map_err(|error| AttachmentError::BindingMismatch(host_error_detail(error)))?;

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 Revalidate the recorded snapshot after owner loss

If the scheduler advances the workspace to another run between the initial manifest validation and an unavailable owner lookup, inspect_recorded_session reloads the newer conversation manifest and this function returns that snapshot alongside the target built from the older run. Because the reloaded snapshot is never compared with target, callers can receive restoration or transcript status for a different profile, run, attempt, generation, owner, or native session; validate the fallback snapshot against the previously verified identity before returning it.

Useful? React with 👍 / 👎.

Comment on lines +325 to +326
if route.harness_kind != "acp"
|| route.harness_profile.as_deref() != Some(state.identity.profile_id.as_str())

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 Reject non-ACP persisted states on terminal workspaces

When a terminal conversation contains stale or inconsistent ACP metadata and its owner is unavailable, validate_route verifies only the run route and profile, so a state.harness value other than acp reaches the recorded-session fallback and is returned as a valid ACP attachment target. Fresh evidence after the prior fix is that this revised condition still omits state.harness, while only the parent and live-owner paths perform that check; require the persisted state harness to match the ACP route before recovery data is exposed.

AGENTS.md reference: AGENTS.md:L165-L169

Useful? React with 👍 / 👎.

Comment on lines +225 to +231
match snapshot.state.recovery {
AcpRecovery::RestoredLoad | AcpRecovery::RestoredResume => {
AttachmentMode::CapabilityGatedRestoration {
recovery: snapshot.state.recovery,
}
}
AcpRecovery::Fresh => AttachmentMode::FreshContextReset,

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 Withhold restoration while execution remains uncertain

When the owner is lost during a prompt, the persisted state can retain RestoredLoad or RestoredResume while its status is Submitted/Uncertain or its process is still recorded as running, but this match advertises CapabilityGatedRestoration solely from the recovery enum. A consumer following that actionable mode could attempt recovery while the prior submission or process may still be active; classify restoration/reset as unavailable or uncertain until the status and process prove quiescence.

AGENTS.md reference: AGENTS.md:L165-L169

Useful? React with 👍 / 👎.

pub target: AttachmentTarget,
pub mode: AttachmentMode,
pub snapshot: SessionSnapshot,
pub owner: Option<SessionHandle>,

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 Expose a read-only handle until control is granted

For every live resolution, this public field exposes the full SessionHandle, whose public methods include prompt, operation, and SessionControl::Retire; those commands require only the generation and do not require an orchestrator-owned writer lease. A terminal or IDE consumer can therefore start work or retire the retained owner immediately after attachment, bypassing the scheduler hold and transfer protocol that is supposed to authorize execution; return an observation-only facade and expose mutation only after orchestrator control is granted.

AGENTS.md reference: AGENTS.md:L23-L27

Useful? React with 👍 / 👎.

Comment on lines +283 to +285
|| conversation.conversation_id != session_id.unwrap_or_default()
|| envelope.conversation_binding.as_deref() != session_id
|| envelope.acp_session.as_ref() != Some(&identity)

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 Require the terminal conversation envelope to match

When the terminal conversation manifest contains a stale or partially updated runtime_envelope, this validation ignores it and accepts the run envelope as long as the nested ACP state happens to match. The parent path explicitly requires its conversation envelope to equal the run envelope, and the existing native debug verifier likewise requires both copies to agree; add the equivalent terminal comparison so inconsistent persisted metadata is rejected rather than attaching through only one side of the durable binding.

Useful? React with 👍 / 👎.

Comment on lines +141 to +147
} else if state.session_id.is_some()
&& !matches!(
state.recovery,
AcpRecovery::RestoredLoad | AcpRecovery::RestoredResume
)
{
state.recovery = AcpRecovery::TranscriptOnly;

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 Derive restoration from the negotiated capabilities

When a persistence-capable session is created normally for the first time, its recovery value remains Fresh even though the stored initialization advertises load or resume; after that owner exits, this branch overwrites it with TranscriptOnly because it preserves restoration only when the previous owner was itself restored. The fresh evidence after the prior claimed fix is that the new condition never inspects the persisted initialization capabilities, so the common first owner loss still cannot expose capability-gated restoration.

Useful? React with 👍 / 👎.

Comment on lines +668 to +672
Debug attachment core regression tests also cover parent/store/nested-path
rejection, exact workspace binding, terminal and parent envelope verification,
persisted route/profile mismatches, complete owner identity comparison, the
no-runtime-binding failure path, owner-loss races, and the distinct
restoration/reset/transcript/unavailable modes. The host control-plane tests

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 Add the attachment regressions claimed by the test guide

This paragraph says the core tests cover envelope verification, route/profile mismatches, owner identity, owner-loss races, and every attachment mode, but the entire new attachment module contains only two tests: ordinary parent/nested-path rejection and the missing-run path. No other test invokes resolve_debug_attachment, so the claimed coverage does not exist and the substantive binding/state-machine behavior is left unverified; add the listed regressions or correct the evidence rather than documenting them as present.

AGENTS.md reference: AGENTS.md:L421-L427

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

symphony OpenSymphony work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant