Skip to content

fix(ask): fence Ask settlement on the claim generation - #979

Draft
seonghobae wants to merge 27 commits into
codex/ask-timeout-attribution-20260907from
feat/ask-ownership-fenced-liveness-20260907
Draft

seonghobae wants to merge 27 commits into
codex/ask-timeout-attribution-20260907from
feat/ask-ownership-fenced-liveness-20260907

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Ask jobs could lose an already completed answer when a claim renewal committed just before completion: cancelling the renewal discarded its new generation, so compare-and-set settlement updated no row. A simultaneous renewal failure could also be ignored. Drain in-flight renewal before admitting success/failure, reject unconfirmed ownership, and preserve native cancellation of both tasks.

This PR also replaces the elapsed compute deadline with claim-generation fencing, retains nullable explicit transport timeout, and prevents reclaimed owners from settling.

Current stacked authority — 2026-09-19

  • parent fix(chat): preserve null timeouts and attribute worker expiry #974 exact 4341080f6027d869acb08896e41d761c3f3b8e77, now Draft after terminal CodeQL dispatch-verdict failure
  • current head 2dfd21110813f474d3068796d0733d96f28d6061
  • relation: ordinary two-parent non-force convergence; existing Ask product tree preserved while adopting the parent test-contract repair
  • state: open / Draft / mechanically mergeable
  • open descendants on this branch: none

#974's source repair remains the CodeQL-legible _build_ssl_context() boundary. Hosted Tests exposed one stale regression assertion that still required the predecessor module-level source spelling _SSL_CONTEXT.minimum_version = ssl.TLSVersion.TLSv1_2; parent 4341080f... repairs only that test contract. Parent Tests 35267030859, SAST 35267030789, and Security 35267030604 are GREEN.

Parent CodeQL 35267030868 has now executed and is terminal RED at the canonical dispatch-verdict settlement boundary, not at LineageWeave source analysis. Detect-languages is GREEN; actions/python/javascript compatibility jobs obtained hosted runners, read verdict=pending, and failed only at Release runner or enforce current-head CodeQL verdict; the dispatch job is GREEN. Fresh exact-head status still has no authenticated codeql-dispatch/* verdict. This specimen is handed to canonical .github#1929 comment 5733274869. Parent successful receipts do not transfer to this child, and the terminal parent CodeQL RED is not repaired by child ancestry.

This child does not independently implement HTTP/TLS or chat-normalization ownership. Convergence commit 2dfd2111... has first parent b27ef413... and second parent 4341080f...; the prior child product tree is preserved and only the repaired parent delta is adopted. Parent head/base did not move, so no descendant restack is required.

Fresh exact-head hosted checks and independent approval remain required after canonical CodeQL settlement. ADR 0371 stays Proposed; #980 owns ADR 0370 and #929 owns translation ADR 0362/migrations 0246–0248. No self-approval, gate weakening, force push, destructive rebase, blind rerun, deployment, release, or receipt inheritance is claimed.

Orphan recovery can flip a long-running job back to queued while
the original worker still holds the answer. Settlement now
compare-and-sets the claim's updated_at so the previous owner
cannot overwrite a reclaimed row.
@coderabbitai

coderabbitai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Codex added 2 commits September 7, 2026 21:45
Compare-and-set settlement now inspects PostgreSQL UPDATE 0 so a
reclaimed Ask job is left with the new owner instead of being
misread as a completed write.
Age-based orphan recovery could reclaim a job whose owner was still
computing. Renew updated_at on the recovery interval, abort when the
generation is lost, and keep the 600 s deadline until that heartbeat
is independently approved.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

현재 heartbeat wrapper가 compute_global_ask_answer()를 별도 asyncio.create_task()로 분리했는데, 바깥 process_global_ask_job() task가 shutdown/user cancellation으로 취소될 때 finally에서 beater만 취소하고 worker task는 취소·await하지 않습니다. 기존 direct-await 경계에서는 native cancellation이 계산 작업으로 전파됐지만 이 head에서는 child operation이 detached 상태로 계속 실행될 수 있습니다. 이는 #975의 native-cancellation/liveness 경계와도 충돌합니다. 현재 timeout_source="shutdown" 테스트는 child coroutine이 스스로 CancelledError를 던지는 경우만 다뤄서 이 regression을 잡지 못합니다. 외부에서 wrapper/process task를 cancel했을 때 operation이 실제로 cancel되고 detached task가 남지 않는 realistic RED를 먼저 추가한 뒤, wrapper finally에서 미완료 worker를 cancel+gather하는 최소 causal fix가 필요합니다. 동시에 PR 본문의 “does not ... add liveness heartbeats”는 current diff와 불일치하므로 live authority에 맞게 고쳐야 합니다.

@seonghobae
seonghobae marked this pull request as draft September 7, 2026 14:10
Owner-task cancellation left compute_global_ask_answer running as a
detached child of the claim-heartbeat wrapper. Cancel and await that
worker in finally, and remove the one-shot repair workflow whose RED
now lives in the product test suite.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fresh exact-head repair review on 3dbba6b15733c6e517d70461446eb1049eba57bf: the queued one-shot run 34131105502 did not execute its RED or fix. It was admitted only after the branch had already advanced from workflow authority 3f0b63d... to this concurrent product commit, so its Verify authority before repair step correctly failed and all repair/test steps were skipped. I adopted the intervening delta instead of treating that failure as a product regression. The current source now cancels and gathers both the inner Ask worker and heartbeat in finally, which is the causal cleanup required by review 5132764499, and the temporary workflow is gone; however this commit has no exact-head backend GREEN because repository Tests are skipped while Draft. Keep the cancellation regression test and run current-head PostgreSQL-backed validation before any completion or merge claim.

@seonghobae
seonghobae marked this pull request as ready for review September 7, 2026 14:55
Omitted ORCHESTRATOR_ANSWER_TIMEOUT_SECONDS now means no LineageWeave
elapsed socket limit, matching post-chat's null transport default.
An explicit finite value still has to stay below the 600 s job
deadline, which remains until the heartbeat is independently approved.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid current-head runtime finding: the new 30 s claim heartbeat/CAS fencing now distinguishes a live owner from an orphan, but process_global_ask_job() still wraps the entire model-backed computation in asyncio.timeout(JOB_DEADLINE_SECONDS) with JOB_DEADLINE_SECONDS = 600. That terminates a healthy reasoning/stream/tool-call operation solely because elapsed wall time reached 600 s, despite the live claim continuing to renew. This conflicts with the current execution contract: model timeout defaults null; user cancellation, provider completion/failure, admin policy and owner-liveness loss must remain distinct; elapsed time alone must not terminate a healthy model-backed operation. The orphan-recovery threshold is also still derived from that hard deadline even though heartbeat freshness is now the authoritative liveness signal.

Repair boundary: first add a realistic test in which the operation remains live across the former 600 s boundary while the claim heartbeat continues, and require no failed settlement/cancellation from elapsed time alone. Then remove the LineageWeave hard execution timeout from the active operation path and make orphan reclaim depend on stale heartbeat/claim generation rather than a guaranteed deadline. Keep explicit deployment/admin timeout policy separate if one is introduced; do not copy provider timeout policy or invent a default. Preserve native cancellation, CAS settlement, bounded failure detail and recovery after an actually dead worker.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Current-head ADR/operability finding: this branch now owns a material runtime decision—claim-generation CAS settlement, periodic liveness renewal, stale-owner reclaim semantics, native cancellation, and removal of elapsed-time-only execution termination—but its changed-file set contains no ADR. AGENTS.md makes ADRs normative and requires a decision record before architectural/runtime policy changes; issue #975 also requires the operability/recovery decision record with alternatives, rejected timer-only recovery, rollback, and exact evidence. Keep the existing elapsed-deadline RED as the behavior gate, then in the same causal repair allocate an unclaimed Proposed ADR for the ownership-fenced liveness invariant and make the operability/gap docs code-current. Do not mark the ADR Accepted before protected-main acceptance. No new provider/model policy belongs in that ADR.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acceptance-gap finding on the same exact head: #975 explicitly requires the ownership/recovery invariant against real PostgreSQL claim/settlement transactions and says not to rely on synthetic sleep-only assertions. The changed test surface here still uses in-memory/mock pool connections; tests/test_global_ask_elapsed_deadline.py is useful RED for elapsed cancellation, but it cannot prove the SQL CAS/heartbeat/reclaim invariant. Add a PostgreSQL-backed regression using the repository's real 0165_global_ask_job.sql schema (the normal Tests job already provisions PostgreSQL): Worker A must hold/renew one running claim generation, recovery must not reclaim while that heartbeat is current; when liveness actually becomes stale/reclaimed, the prior owner must fail CAS settlement and the queued successor remains authoritative. Keep Valkey as a wake-up stub if needed—the database row is already the documented source of truth. This test belongs in the same causal repair before #975 can be treated as verified.

Codex added 2 commits September 8, 2026 01:47
The 600 s asyncio timeout still cancelled a renewing Ask owner.
Compute now runs under claim-generation liveness only. ADR 0370
records that Proposed policy. Orphan recovery uses three missed
heartbeats. Provider TimeoutError stays unavailable, not a worker
deadline. PostgreSQL-backed race evidence remains a follow-up.
Issue #975 requires the reclaim invariant against actual claim and
settlement transactions, not only in-memory fakes. After 0001 and
0165 replay, the previous owner's compare-and-set is UPDATE 0 and
the new owner settles.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Current-head repair finding on fbe68078a325d760e82105b7175492176d29453d: the elapsed compute deadline has been removed from process_global_ask_job(), and the new real-PostgreSQL CAS regression is a valid intervening delta to adopt. However backend/app/config.py still carries GLOBAL_ASK_JOB_DEADLINE_SECONDS = 600 and rejects an explicit finite ORCHESTRATOR_ANSWER_TIMEOUT_SECONDS >= 600 solely because it must be below that removed worker deadline. global_ask_queue.py also keeps the dead deadline import/alias, and the orphan-recovery docstring still says a per-job deadline guarantees settlement. This preserves deleted timeout policy in configuration and documentation and contradicts this PR's stated separation of nullable transport timeout from heartbeat/claim liveness. Realistic RED: an explicit finite positive transport timeout such as 900 s must be accepted after the worker deadline is removed, while omitted/blank remains null and zero/negative/non-finite values remain rejected. Minimal repair: validate only finite-positive explicit transport timeout, remove the dead worker-deadline constant/import/alias and stale recovery wording, and keep heartbeat-grace orphan recovery/native cancellation/CAS settlement unchanged. Do not introduce provider policy or a new default.

@seonghobae
seonghobae marked this pull request as draft September 7, 2026 18:35
@seonghobae
seonghobae marked this pull request as ready for review September 7, 2026 18:38
Tests run 34153018910 failed the public-docstring gate because the
nested Ask heartbeat coroutine was named beat. Rename it _beat so the
helper is not a public production definition.

Copy link
Copy Markdown
Contributor Author

RCA on exact head 29d226529 Tests run 34153018910: collection succeeded, then tests/test_public_docstrings.py failed because the nested Ask heartbeat coroutine at backend/app/global_ask_queue.py:141 was named beat. Frontend on that run stayed GREEN.

Repair on 262d496a9 renames that nested helper to _beat so it is not a public production definition. Local: 38 tests green including the docstring gate, elapsed-deadline, cancellation, transport timeout, queue, and settings contracts. Keep #979 unmerged until repository Tests are terminal GREEN on this exact head, parent #974 remains valid, hosted security checks are acceptable, and independent APPROVE is present. No self-approval.

Copy link
Copy Markdown
Contributor Author

Exact-head 262d496a9 repository Tests run 34156704752 is terminal GREEN (Full test suite 22m50s, Frontend GREEN). Merge state is CLEAN.

Prior review findings on this stack are closed on this head: native cancellation of the inner worker, claim-generation CAS settlement, live heartbeat vs elapsed-time cancellation, real PostgreSQL reclaim (test_postgres_stale_ask_owner_cannot_settle_after_reclaim), nullable transport timeout including explicit 900 s, and the public-docstring gate (_beat).

Parent #974 (def15fc69) Tests/Frontend are GREEN and remain BLOCKED on independent APPROVE plus org review gates (Noema, OpenCode, Strix, CodeQL-compat). This PR has no independent APPROVE. CodeRabbit/Devin skips are not APPROVE. No self-approval and no merge.

Next unproven liveness edge, not changed on this GREEN head: if the heartbeat task ends while compute is still running without lost being set, the owner would continue without renewals. Keep that as a follow-up RED rather than invalidating this exact-head GREEN.

A heartbeat exception left compute running without renewals until the
test timed out. Treat a finished heartbeat with a live worker as a lost
claim so the owner is cancelled instead of settling.

Copy link
Copy Markdown
Contributor Author

Follow-up on GREEN 262d496a9: a heartbeat exception left compute running without renewals. RED test_dead_heartbeat_aborts_live_ask_operation timed out (TimeoutError) waiting for settlement. Repair 875e9364a treats a finished heartbeat with a live worker as a lost claim and cancels the owner. Local: 21 tests green (cancellation, elapsed-deadline, docstring, queue). Repository Tests on this new head are not polled. Keep unmerged until Tests are terminal GREEN here, parent #974 remains valid, and independent APPROVE is present. No self-approval.

Leftover-map validation #980 already allocated ADR 0370 for comparison
axis-singular. Keep that number on the leftover stack and record Ask
claim-generation liveness as Proposed ADR 0371.

Copy link
Copy Markdown
Contributor Author

Exact-head 875e9364a Tests run 34164205806 was terminal GREEN (22m51s) + Frontend GREEN. No independent APPROVE; no merge.

Repair finding vs leftover validation #980: that lane already allocated docs/adr/0370-leftover-map-compare-axis-singular.md. This stack had independently used ADR 0370 for Ask claim liveness. Commit fef48b14f reallocates Ask claim-generation liveness to Proposed ADR 0371 so the leftover number stays on #980/#830. Repository Tests on fef48b14f are not polled. Keep unmerged until Tests GREEN on this new head, parent #974 remains valid, and independent APPROVE is present.

Copy link
Copy Markdown
Contributor Author

Exact-head fef48b14f Tests run 34167781764 is terminal GREEN (22m52s) + Frontend GREEN. Merge CLEAN. No independent APPROVE; no merge. Parent #974 remains Tests GREEN and BLOCKED on REVIEW_REQUIRED.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode reviewed the current-head product diff. Coverage is a separate gate.

Changed files

  • backend/app/config.py — API and service runtime
  • backend/app/global_ask_queue.py — API and service runtime
  • backend/app/main.py — API and service runtime
  • backend/tests/test_config.py — regression suite
  • docs/adr/0371-ask-claim-generation-liveness.md — operator or user guidance
  • docs/product-technical-gap-baseline.md — operator or user guidance
  • tests/test_global_ask_claim_cancellation.py — regression suite
  • tests/test_global_ask_elapsed_deadline.py — regression suite
  • tests/test_global_ask_queue.py — regression suite
  • tests/test_global_ask_transport_timeout.py — regression suite
  • tests/test_schema.py — regression suite

Changed behavior

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Backend: config.py (3 files)"]
  S1 --> I1["API and service runtime"]
  I1 --> R1["Review risk: Backend: config.py (3 files)"]
  R1 --> V1["backend tests"]
  Evidence --> S2["Test: test_config.py"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test: test_config.py"]
  R2 --> V2["targeted test run"]
  Evidence --> S3["Docs: 0371-ask-claim-generation-liveness.md (2 files)"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs: 0371-ask-claim-generation-liveness.md (2 files)"]
  R3 --> V3["docs review"]
  Evidence --> S4["Test: test_global_ask_claim_cancellation.py (5 files)"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_global_ask_claim_cancellation.py (5 files)"]
  R4 --> V4["targeted test run"]
Loading

Findings

No source-backed product finding is synthesized from the coverage gate. A coverage miss belongs in the status comment.

  • Head SHA: fef48b14f302dd40e1fa83096810cc880a5c4c66
  • Workflow run: 34173501051
  • Workflow attempt: 1
  • Coverage gate: failure

Review outcome

Coverage is a gate, not the review. This body reviews the changed product files.

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Backend: config.py (3 files)"]
  S1 --> I1["API and service runtime"]
  I1 --> R1["Review risk: Backend: config.py (3 files)"]
  R1 --> V1["backend tests"]
  Evidence --> S2["Test: test_config.py"]
  S2 --> I2["regression suite"]
  I2 --> R2["Review risk: Test: test_config.py"]
  R2 --> V2["targeted test run"]
  Evidence --> S3["Docs: 0371-ask-claim-generation-liveness.md (2 files)"]
  S3 --> I3["operator or user guidance"]
  I3 --> R3["Review risk: Docs: 0371-ask-claim-generation-liveness.md (2 files)"]
  R3 --> V3["docs review"]
  Evidence --> S4["Test: test_global_ask_claim_cancellation.py (5 files)"]
  S4 --> I4["regression suite"]
  I4 --> R4["Review risk: Test: test_global_ask_claim_cancellation.py (5 files)"]
  R4 --> V4["targeted test run"]
Loading

@opencode-agent

opencode-agent Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

Coverage evidence did not pass, so approval is blocked. The formal pull-request review is the source-backed diff review, not this status comment.

Copy link
Copy Markdown
Contributor Author

Still exact-head fef48b14f Tests GREEN (run 34167781764). No independent APPROVE; no merge. Parent #974 remains Tests GREEN and BLOCKED on REVIEW_REQUIRED.

@seonghobae seonghobae added bug Something isn't working priority: medium labels Sep 8, 2026 — with ChatGPT Codex Connector

Copy link
Copy Markdown
Contributor Author

Head advanced concurrently to 6662ea5df. Tests run 34182446558 is terminal GREEN (23m48s) + Frontend GREEN. Still no independent APPROVE; no merge.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Independent current-head review of 6662ea5df6f8f026b54a083677d3e0ee98d2d6d2 (not APPROVE; same-author reviews do not satisfy the protected-branch independent-approval rule).

Prior product findings on this stack are addressed in the current source:

  • owner cancellation cancels and gathers the inner compute task and heartbeat (tests/test_global_ask_claim_cancellation.py)
  • live compute is not terminated by elapsed wall time; JOB_DEADLINE_SECONDS / GLOBAL_ASK_JOB_DEADLINE_SECONDS are gone
  • omitted/blank Ask socket timeout stays null; explicit 900 s is accepted (backend/tests/test_config.py)
  • settlement is compare-and-set on claim updated_at; stale-owner PostgreSQL proof is in tests/test_schema.py
  • inflight renewal is drained before settlement; a failed renewal rejects the simultaneous answer
  • Proposed ADR 0371 is present and not marked Accepted

Hosted evidence on this exact head: Frontend and Full test suite GREEN (34182446558). This is stacked on #974 (codex/ask-timeout-attribution-20260907 @ def15fc69). Do not merge to main from this child. #974 Tests/Frontend are GREEN; remaining #974 failures are noema-review, opencode-review, strix (6h), and CodeQL compatibility analysis — not the product suite. Parent-first protected merge, then retarget.

Unresolved (not treated as current-head product defects in this delta): the inherited three-heartbeat orphan ratio is still uncalibrated, as ADR 0371 already states. No self-approval, force-push, or bypass.

@seonghobae
seonghobae marked this pull request as draft September 11, 2026 20:20

This branch has not been deployed

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

Labels

bug Something isn't working priority: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant