Preflight capability resolution reset by interleaved read-only calls before mutation #469

Closed
opened 2026-07-07 16:21:39 -05:00 by jcwalker3 · 2 comments
Owner

Problem

The mutation preflight guard requires gitea_resolve_task_capability to have been resolved immediately before a gated mutation. However, read-only calls issued after resolve_task_capability and before the mutation reset the resolved-capability state, causing the mutation to fail closed with a spurious preflight-order error even though capability was correctly resolved moments earlier.

Observed behavior

During closure of superseded PR #414 (reconciler close_pr):

  1. Called gitea_resolve_task_capability(close_pr)allowed_in_current_session: true.
  2. In the same batch, called read-only gitea_whoami, gitea_view_pr(414), gitea_view_pr(413), gitea_view_issue(400) (re-pin preconditions).
  3. Called gitea_edit_pr(414, state="closed")failed closed:
    Pre-flight order violation: Task capability (gitea_resolve_task_capability)
    has not been resolved (fail closed)
    
  4. Re-ran gitea_resolve_task_capability(close_pr) immediately before gitea_edit_pr, with no interleaved reads → close succeeded.

So the capability preflight flag (e.g. _preflight_capability_called) is cleared or invalidated by benign read-only tool calls (whoami, view_pr, view_issue) that occur between resolution and mutation.

Impact

  • Forces an awkward "resolve capability, then mutate with nothing in between" ordering.
  • Callers that legitimately re-pin live PR/issue state (a required drift-safety step) between capability resolution and mutation are penalized with a false preflight-order failure.
  • Encourages skipping the safety-oriented re-pin reads to avoid the reset — the opposite of desired behavior.

Expected

Read-only, non-mutating tool calls (whoami, view_pr, view_issue, list_*, get_runtime_context, etc.) must not reset a valid resolved-capability preflight state. Only a profile/identity change, a new resolve_task_capability for a different task, or an actual mutation should invalidate it.

Acceptance criteria

  1. After resolve_task_capability(<task>), interleaved read-only calls (whoami, view_pr, view_issue, list_prs, list_issues, get_runtime_context) do not clear the resolved-capability preflight for <task>.
  2. The gated mutation for <task> succeeds when capability was resolved earlier in the session and remains valid, even with intervening read-only calls.
  3. Preflight state is still invalidated on: profile/identity switch, resolving a different task's capability, or after a mutation completes.
  4. A genuinely missing capability resolution still fails closed with the clear preflight-order error.
  5. Tests cover: resolve → read-only calls → mutation (should pass); no-resolve → mutation (should fail closed); resolve task A → mutation task B (should fail closed).
  6. Distinguish "capability not resolved" from "capability resolved but read-only calls reset it" is no longer possible because the reset no longer happens.

Evidence (PR #414 closure)

  • First gitea_edit_pr(414, close) failed: preflight capability not resolved, despite a resolve_task_capability(close_pr) in the same turn.
  • Root difference: the failing attempt had whoami + view_pr + view_issue between resolve and mutation; the succeeding attempt had a bare resolve_task_capability(close_pr) immediately before the mutation.

Related

  • #468 (reconciler close_pr branch/worktree guard) — surfaced during the same #414 closure.
  • Preflight order guard (verify_preflight_purity / record_preflight_check).
## Problem The mutation preflight guard requires `gitea_resolve_task_capability` to have been resolved immediately before a gated mutation. However, **read-only calls issued after `resolve_task_capability` and before the mutation reset the resolved-capability state**, causing the mutation to fail closed with a spurious preflight-order error even though capability was correctly resolved moments earlier. ## Observed behavior During closure of superseded PR #414 (reconciler `close_pr`): 1. Called `gitea_resolve_task_capability(close_pr)` → `allowed_in_current_session: true`. 2. In the same batch, called read-only `gitea_whoami`, `gitea_view_pr(414)`, `gitea_view_pr(413)`, `gitea_view_issue(400)` (re-pin preconditions). 3. Called `gitea_edit_pr(414, state="closed")` → **failed closed**: ``` Pre-flight order violation: Task capability (gitea_resolve_task_capability) has not been resolved (fail closed) ``` 4. Re-ran `gitea_resolve_task_capability(close_pr)` **immediately** before `gitea_edit_pr`, with no interleaved reads → close succeeded. So the capability preflight flag (e.g. `_preflight_capability_called`) is cleared or invalidated by benign read-only tool calls (`whoami`, `view_pr`, `view_issue`) that occur between resolution and mutation. ## Impact - Forces an awkward "resolve capability, then mutate with nothing in between" ordering. - Callers that legitimately re-pin live PR/issue state (a required drift-safety step) between capability resolution and mutation are penalized with a false preflight-order failure. - Encourages skipping the safety-oriented re-pin reads to avoid the reset — the opposite of desired behavior. ## Expected Read-only, non-mutating tool calls (`whoami`, `view_pr`, `view_issue`, `list_*`, `get_runtime_context`, etc.) must **not** reset a valid resolved-capability preflight state. Only a profile/identity change, a new `resolve_task_capability` for a different task, or an actual mutation should invalidate it. ## Acceptance criteria 1. After `resolve_task_capability(<task>)`, interleaved read-only calls (`whoami`, `view_pr`, `view_issue`, `list_prs`, `list_issues`, `get_runtime_context`) do not clear the resolved-capability preflight for `<task>`. 2. The gated mutation for `<task>` succeeds when capability was resolved earlier in the session and remains valid, even with intervening read-only calls. 3. Preflight state is still invalidated on: profile/identity switch, resolving a different task's capability, or after a mutation completes. 4. A genuinely missing capability resolution still fails closed with the clear preflight-order error. 5. Tests cover: resolve → read-only calls → mutation (should pass); no-resolve → mutation (should fail closed); resolve task A → mutation task B (should fail closed). 6. Distinguish "capability not resolved" from "capability resolved but read-only calls reset it" is no longer possible because the reset no longer happens. ## Evidence (PR #414 closure) - First `gitea_edit_pr(414, close)` failed: preflight capability not resolved, despite a `resolve_task_capability(close_pr)` in the same turn. - Root difference: the failing attempt had `whoami` + `view_pr` + `view_issue` between resolve and mutation; the succeeding attempt had a bare `resolve_task_capability(close_pr)` immediately before the mutation. ## Related - #468 (reconciler close_pr branch/worktree guard) — surfaced during the same #414 closure. - Preflight order guard (`verify_preflight_purity` / `record_preflight_check`).
jcwalker3 added the status:in-progress label 2026-07-07 16:45:56 -05:00
Author
Owner

Issue claim heartbeat

<!-- gitea-issue-claim-heartbeat:v1 --> **Issue claim heartbeat** - kind: claim - issue: #469 - branch: fix/issue-469-preflight-read-survival - phase: claimed - profile: prgs-author - pr: none - blocker: none - next_action: create worktree and begin implementation
Author
Owner

Issue claim heartbeat

<!-- gitea-issue-claim-heartbeat:v1 --> **Issue claim heartbeat** - kind: claim - issue: #469 - branch: pending - phase: claimed - profile: prgs-author - pr: none - blocker: none - next_action: create worktree and begin implementation
sysadmin removed the status:in-progress label 2026-07-08 03:01:50 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#469