Skip to content

Add first-class OpenAI-compatible LLM provider - #1505

Open
Chris0Jeky wants to merge 8 commits into
mainfrom
issue-1306/openai-compatible
Open

Add first-class OpenAI-compatible LLM provider#1505
Chris0Jeky wants to merge 8 commits into
mainfrom
issue-1306/openai-compatible

Conversation

@Chris0Jeky

@Chris0Jeky Chris0Jeky commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Status

CHANGES REQUESTED / PARKED at exact head 6ee17a4849427d679722a9d1e034d4622536f4fe. Do not merge or start a third fix/review round on this head.

Final review found 15 valid unresolved items: 14 are recorded in #1306's parked-head acceptance remainder, and the cross-provider ambient-proxy/SSRF boundary is tracked in #1513. The Windows concurrent-card CI recurrence is tracked in #1512. All 11 open inline threads have been triaged and intentionally remain unresolved until a future scoped implementation supplies fixes.

Summary

  • add a first-class, opt-in OpenAICompatible provider without changing OpenAI defaults
  • stream upstream SSE deltas with bounded bodies/events and persisted degradation
  • keep production construction confined to internal API registration and align Compose, Render, configuration, and provider setup surfaces
  • add provider, quota, circuit, streaming, persistence, DI, egress, deployment, and architecture regressions

Product alignment

This is the bounded REVIVAL-10 provider slice: it lowers transcript-triage integration friction while preserving Taskdeck's review-first trust boundary. It does not add direct mutation authority. The final review showed that quota, circuit, raw-byte, fallback, persistence, docs, and shared proxy enforcement still need another scoped implementation before this can ship.

Verification — parked head

  • focused Application/API/Architecture lane: 277 passed, 1 existing architecture skip
  • pre-final full backend suite: 7,570 passed, 5 skipped, 0 failed
  • changed-file formatting, Compose baseline config, docs/Golden/GitHub-ops governance, diff hygiene, DCO, and clean worktree: passed
  • Required CI rerun: green, including Windows API Integration and E2E Smoke
  • CI Extended and CodeQL: green

NOT verified

  • resolution of the final 3 HIGH, 10 MEDIUM, and 2 LOW findings
  • live smoke against a real compatible endpoint (requires a maintainer-supplied key)
  • visibly incremental chat streaming in a running dev UI against that endpoint
  • actual cloud deployment

Refs #1306, #1512, and #1513.

Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
@Chris0Jeky Chris0Jeky added backend llm revival Revival pivot wave (ADR-0044, docs/REVIVAL_PLAN.md) Priority II labels Jul 27, 2026
@Chris0Jeky Chris0Jeky moved this from Pending to Review in Taskdeck Execution Jul 27, 2026
@Chris0Jeky

Copy link
Copy Markdown
Owner Author

@codex review

Please review exact head bf4549a. Focus on correctness, security, SSE behavior, and provider egress/configuration boundaries.

@Chris0Jeky

Chris0Jeky commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

Security/configuration adversarial review — exact head bf4549a7baf0b7d4b49d3c194c010db0996ec8aa

I inspected the linked issue, full diff, surrounding selection/DI/SSRF/chat-stream code, and all current PR comments/reviews/threads. The only pre-existing comment is the @codex review request; it is informational and there are no prior findings or unresolved threads.

HIGH

  1. The advertised DNS-level SSRF control is bypassable when a system proxy is active.

    • Where: backend/src/Taskdeck.Api/Extensions/LlmProviderRegistration.cs:116-129 (and the inherited handler pattern at 93-170), plus backend/src/Taskdeck.Api/Workers/OutboundWebhookConnectCallback.cs:15-20.
    • Risk: the new SocketsHttpHandler leaves UseProxy at its default true. Taskdeck's callback validates only context.DnsEndPoint. In .NET's proxy path, the transport connects the callback to the proxy endpoint and then asks the proxy to CONNECT/resolve the configured origin; see the .NET runtime proxy branches, the callback endpoint construction, and the CONNECT tunnel delegation. A public/system proxy can therefore resolve a public-looking configured hostname to a private/internal address after Taskdeck's static hostname check. This contradicts the PR/docs claim that the connection-time callback prevents DNS rebinding.
    • Required fix/test: fail closed with UseProxy = false for protected egress clients, or introduce an explicit proxy design that proves the origin address remains policy-approved rather than merely validating the proxy. Add a regression exercising a configured proxy and a target whose resolution is private. Because this is the shared handler posture, audit/fix the existing OpenAI/Gemini/Ollama/webhook registrations or track any remainder explicitly.
  2. A hostile or faulty compatible endpoint can hold a server connection indefinitely after headers and exhaust memory without a response budget.

    • Where: OpenAiCompatibleLlmProvider.cs:128-175 (ResponseHeadersRead, unbounded non-SSE ReadAsStringAsync, and unbounded StreamReader.ReadLineAsync), :171-217 (unbounded event/content accumulation), and ChatService.cs:602,629-636 (a second unbounded accumulation of every delta).
    • Risk: with ResponseHeadersRead, HttpClient.Timeout stops applying once headers arrive; Microsoft explicitly requires a separate content timeout (documentation). An upstream can send headers and stall/trickle forever, send one enormous data: line, or ignore max_tokens and stream arbitrary output. The provider and chat service retain duplicate growing buffers, allowing connection/thread-pool pressure and process memory exhaustion.
    • Required fix/test: enforce an upstream content/idle deadline linked to caller cancellation, a bounded SSE line/event size, a bounded cumulative UTF-8 response/delta budget, and bounded non-SSE/completion-body reads (including early Content-Length rejection). End with a sanitized terminal error/degraded event. Add deterministic tests for a stalled post-header stream, over-limit single event, over-limit aggregate stream, and over-limit JSON fallback body.

MEDIUM

  1. ExtraHeaders validation accepts routing, hop-by-hop, proxy-credential, cookie, and server-owned attribution headers.

    • Where: LlmProviderSelectionPolicy.cs:264-290 and OpenAiCompatibleLlmProvider.cs:306-315.
    • Risk: the framework probe only rejects syntactically invalid/content-only names. On the current runtime it accepts Host, Proxy-Authorization, Connection, Transfer-Encoding, Cookie, and x-taskdeck-*. Host can alter virtual-host routing after the URI passed SSRF validation; proxy/hop-by-hop headers can change transport semantics; and x-taskdeck-user-token / correlation / board / session headers are appended after the server-derived values, allowing configuration to duplicate or spoof the managed-key attribution contract. Only Authorization is currently reserved.
    • Required fix/test: use a narrow allowlist for known compatible-vendor metadata headers (or at minimum deny Host, proxy/hop-by-hop/cookie headers, all authorization variants, and the complete x-taskdeck-* namespace). Validate the bearer API key against CR/LF/invalid header serialization too. Add selection and emitted-request tests for each reserved family and for preservation of exactly one server-derived attribution value.
  2. Valid JSON with a hostile schema escapes the SSE error contract and tears down the already-started 200 response.

    • Where: OpenAiCompatibleLlmProvider.cs:344-375; StreamAsync has only try/finally at :120-258, and ChatController.cs:177-185 has no exception boundary after beginning SSE.
    • Risk: choices[0] is not checked to be an object; delta is not checked to be an object; and non-null finish_reason is read with GetString() without checking String. Payloads where choices[0] is numeric, delta is a string, or finish_reason is numeric throw InvalidOperationException, while the parser catches only JsonException. Microsoft documents that JsonElement.TryGetProperty throws when the value is not an object (documentation). Network, read, and broken-circuit exceptions also escape. The client then receives a truncated HTTP 200 stream instead of the promised terminal error event, and the exception reaches host logging.
    • Required fix/test: validate every JSON ValueKind; convert all non-cancellation parse/read/transport/circuit failures into a sanitized terminal LlmTokenEvent; retain cancellation propagation. Add schema-malformed cases plus HttpRequestException, IOException, and broken-circuit/timeout coverage, including failure after a delivered delta.

LOW

  1. The concrete typed provider can bypass the authoritative selection gates if injected directly.
    • Where: concrete registration at LlmProviderRegistration.cs:110-129, interface selection at :173-193, and provider self-validation at OpenAiCompatibleLlmProvider.cs:297-304.
    • Risk: repository search shows current production consumers use ILlmProvider, so there is no active bypass today. However, OpenAiCompatibleLlmProvider is independently resolvable and its own guard ignores EnableLiveProviders, the selected Provider, and the actual host environment; it even treats AllowLiveProvidersInDevelopment alone as permission for localhost validation. A future direct injection can make public live calls while the master live-provider gate is off. The production connect callback still denies localhost, which limits current impact but does not protect public HTTPS calls.
    • Required fix/test: make the concrete transport inaccessible outside the selection factory, or inject/enforce the authoritative provider decision/environment in the concrete provider as defense in depth. Add an architecture/DI regression proving application consumers cannot resolve/use it around the ILlmProvider gate.

Controls verified

  • Direct (non-proxy) connections retain connect-time IP filtering, redirect following is disabled, and Production does not enable the development-localhost transport exception.
  • Bearer auth is constructed from ApiKey; provider logs do not include response bodies or configured header values, and buffered completion exceptions use SensitiveDataRedactor.
  • The new typed client is wired to a reusable provider-specific circuit-breaker policy and the existing quota/kill-switch path remains above ILlmProvider in ChatService.

No files were changed. Per repository policy, every finding above needs a fix plus regression evidence (or a linked tracking issue only where the remainder is genuinely outside this PR).

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Correctness / protocol adversarial review — exact head bf4549a7baf0b7d4b49d3c194c010db0996ec8aa

I inspected the linked issue, full exact-head diff, provider/selection/DI code, the chat SSE + quota/persistence call chain, focused tests, and all current PR comments/reviews/threads before posting. The earlier @codex review request is informational. The security/configuration review at #1505 (comment) is current and unresolved; I independently confirmed its post-header timeout/unbounded-body finding and its non-cancellation transport/schema exception finding, so I am not duplicating those two here. Its other findings likewise still require author triage/fixes.

HIGH

  1. Normal OpenAI-wire SSE responses are committed as a small output-only estimate instead of actual or conservatively reserved usage.
    • Where: backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs:202-204,329-371, flowing into backend/src/Taskdeck.Application/Services/ChatService.cs:643-675,692-712.
    • Risk: the request sets stream:true but never requests stream_options.include_usage. The OpenAI Chat Completions contract only supplies streaming usage when that option is set; its usage chunk then has an empty choices array (official API reference). This parser rejects empty choices and exits on the earlier non-null finish_reason anyway. When usage is absent, it forces EstimateTokens(streamedContent) into the terminal event. That estimate counts only output characters, not the system prompt, conversation, board context, or user input. ChatService sees the positive value as actual usage and commits it, bypassing its deliberately conservative 2,000-token reservation-estimate path. A large prompt with a one-token answer can therefore consume provider budget while barely decrementing Taskdeck quota.
    • Required fix/test: do not manufacture a positive “actual” usage count when upstream usage is absent. Either support the standard include_usage chunk (including empty choices and the finish-chunk → usage-chunk → [DONE] order) with a compatibility fallback, or emit TokensUsed:null so ChatService commits the reservation estimate. Add provider + StreamResponseAsync tests for standard usage ordering and for a completed stream with no usage proving the estimate, not output length, is committed.

MEDIUM

  1. The rejected-stream fallback silently changes the chat request into the instruction-extraction/JSON contract.

    • Where: OpenAiCompatibleLlmProvider.cs:123-139,275-284; CompleteAsync decides extraction mode at :47-53.
    • Risk: the initial stream correctly clones a null-system-prompt request to SystemPrompt = string.Empty, suppressing the extraction prompt and response_format. On a 400/404/405/422/501, however, EmitBufferedFallbackAsync receives the original request. A normal chat request has SystemPrompt == null, so the fallback adds the instruction-extraction system prompt and response_format:json_object. Endpoints that reject streaming therefore receive different semantics, may make a third request after rejecting response_format, and can return an extraction envelope instead of the conversational completion that was requested.
    • Required fix/test: buffer the same effective conversational request used for streaming (while retaining the explicit degraded metadata). Extend the fallback test to assert that the second payload also omits response_format and does not inject the extraction system prompt; keep a separate test for the intended non-streaming extraction retry.
  2. Every non-null finish_reason is reported as a successful complete stream, including truncation and filtering.

    • Where: OpenAiCompatibleLlmProvider.cs:358-371,199-205.
    • Risk: length, content_filter, tool_calls, and deprecated function_call are collapsed into the same IsDone boolean as stop. In particular, the non-streaming path explicitly marks finish_reason=length degraded, but the streaming path emits a clean terminal event and ChatService persists the partial output as an ordinary assistant answer. Filtering/tool-call termination can even produce an empty “success”.
    • Required fix/test: preserve the finish reason through parsing; treat only the supported normal terminal reason(s) as clean success, and surface truncation/filter/unsupported tool termination as sanitized degraded/error completion metadata. Add focused tests for at least length and content_filter, plus an end-to-end persistence assertion.
  3. The new degraded/error event state is forwarded live but discarded from persisted chat history.

    • Where: new fields in ILlmProvider.cs:68-76; ChatService.cs:599-658 only accumulates content/tokens/provider/model and constructs ChatMessage with its default messageType:text and no degradedReason.
    • Risk: buffered fallback is marked degraded only on the live terminal SSE event. After reload, the same answer appears as a normal text response, contrary to the established persistent degraded contract in the non-streaming path. Likewise, an upstream error event after one delta ends enumeration normally; ChatService persists the partial content as an ordinary answer even though the client just received an error completion.
    • Required fix/test: carry terminal IsDegraded / DegradedReason / Error through StreamResponseAsync and define the partial-error persistence rule (at minimum persist it as degraded, or deliberately do not persist it). Add ChatService tests for buffered fallback and delta-then-error, asserting MessageType, DegradedReason, content, and quota settlement.

Verification / residuals

  • Exact-head focused command passed: dotnet test backend/tests/Taskdeck.Application.Tests/Taskdeck.Application.Tests.csproj -c Release -m:1 --no-restore --filter FullyQualifiedName~OpenAiCompatibleLlmProviderTests|FullyQualifiedName~ChatServiceTests.StreamResponseAsync16/16.
  • Those tests do not exercise the standard usage-chunk shape, no-usage quota settle, fallback prompt equivalence, non-stop finish reasons, or persisted degraded/error state described above.
  • Cancellation itself is correctly propagated in the provider test. The dedicated non-streaming response_format 400/422 retry is otherwise coherent; finding 2 is specifically that the streaming fallback accidentally enters that different contract.
  • Live compatible-vendor smoke and visibly incremental running-app proof remain unverified, as the PR already states.

No files were changed. Every finding in this comment and the earlier security review requires a fix plus regression evidence (or a linked issue only for a genuinely out-of-scope shared remainder) before this head is merge-ready.

@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: bf4549a7ba

ℹ️ 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 backend/src/Taskdeck.Api/Extensions/LlmProviderRegistration.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/LlmProviderSelectionPolicy.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/ILlmProvider.cs Outdated
Comment thread backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Testing

  • node scripts/check-docs-governance.mjs
  • node scripts/check-golden-principles.mjs
  • node scripts/check-github-ops-governance.mjs
  • git diff --check
  • git status --short --branch — clean working tree after commit.
  • ⚠️ dotnet build backend/src/Taskdeck.Application/Taskdeck.Application.csproj -c Release --no-restore — unavailable because this environment has no dotnet executable.
  • ⚠️ dotnet test backend/tests/Taskdeck.Application.Tests/Taskdeck.Application.Tests.csproj -c Release -m:1 --filter "FullyQualifiedName~OpenAiCompatibleLlmProviderTests|FullyQualifiedName~LlmProviderSelectionPolicyTests|FullyQualifiedName~LlmProviderResilienceTests" — not runnable without the .NET SDK; queued for CI recertification.
  • ⚠️ dotnet test backend/tests/Taskdeck.Api.Tests/Taskdeck.Api.Tests.csproj -c Release -m:1 --filter "FullyQualifiedName~LlmProviderRegistrationTests" — not runnable without the .NET SDK.
  • ⚠️ dotnet test backend/tests/Taskdeck.Architecture.Tests/Taskdeck.Architecture.Tests.csproj -c Release -m:1 --filter "FullyQualifiedName~Invariant08_EgressEnvelope_OutboundHttpConstrained" — not runnable without the .NET SDK.

Notable Risks / Follow-ups

  • CI must compile and recertify the expanded backend test lane; the documentation deliberately does not claim those new tests passed locally. docs/TESTING_GUIDE.mdL36-L42
  • Issue #1306 still requires the maintainer-key live-vendor request and visibly incremental streaming acceptance check. docs/STATUS.mdL5-L6
  • The maintainer-owned open checklist remains unchanged. Its principal open tracks include #1133, #1123, #1132, #1131, #1166, #1138, #1136, #1137, #1271, #1272, #1274–#1277, #1291, #1303, #1330, #1332, #1335, #1338, #1343, #1347, #1348, #1456, #1458 maintainer check-off, #1482, GEN-00/#1327, REVIVAL-00/#1311, the GEN-12 maintainer checkpoint, merged-worktree pruning, and explicit-base branch discipline. OUTSTANDING_TASKS.mdL25-L56 OUTSTANDING_TASKS.mdL61-L90

View task →

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

Files Touched

  • backend/src/Taskdeck.Api/Extensions/LlmProviderRegistration.cs
  • backend/src/Taskdeck.Api/Extensions/WorkerRegistration.cs
  • backend/src/Taskdeck.Application/Services/ChatService.cs
  • backend/src/Taskdeck.Application/Services/LlmProviderSelectionPolicy.cs
  • backend/src/Taskdeck.Application/Services/OpenAiCompatibleLlmProvider.cs
  • backend/tests/Taskdeck.Api.Tests/LlmProviderRegistrationTests.cs
  • backend/tests/Taskdeck.Application.Tests/Services/ChatServiceTests.cs
  • backend/tests/Taskdeck.Application.Tests/Services/LlmProviderSelectionPolicyTests.cs
  • backend/tests/Taskdeck.Application.Tests/Services/OpenAiCompatibleLlmProviderTests.cs
  • docs/STATUS.md
  • docs/IMPLEMENTATION_MASTERPLAN.md

Testing

  • node scripts/check-docs-governance.mjs
  • node scripts/check-golden-principles.mjs
  • git diff --check
  • git status --short --branch — clean after both commits.
  • git log -2 --format='%h %s%n%(trailers:key=Signed-off-by)' — both commits contain Signed-off-by: trailers.
  • ⚠️ dotnet test backend/tests/Taskdeck.Application.Tests/Taskdeck.Application.Tests.csproj -c Release -m:1 --no-restore --filter 'FullyQualifiedName~OpenAiCompatibleLlmProviderTests|FullyQualifiedName~ChatServiceTests.StreamResponseAsync' — could not run because this container has no dotnet executable.
  • ⚠️ dotnet test backend/Taskdeck.sln -c Release -m:1 — consequently not runnable in this container; hosted CI should execute the required backend suite.
  • ⚠️ bash scripts/check-git-env.sh — no index lock was present, but the script warned that /usr/bin/git resembles a Cygwin/MSYS2 installation.

Risks and Follow-ups

  • Live compatible-vendor smoke testing and visibly incremental UI streaming remain unverified because they require a maintainer-supplied vendor key. This remains the acceptance gate for #1306. docs/STATUS.mdL5-L6
  • The durable maintainer checklist remains unchanged. Open tracked items include #1133, #1142, #1123, #1132, #1131, #1166, #1138, #1136, #1137, #1135/#1140/#1141/#1139, GEN-00/#1327, GEN-12/#1326, REVIVAL-00/#1311, #1271, #1274–#1277, #1269–#1272, #1291, #1173, #1303, #1482, #1458 maintainer check-off, #1330, #1282/#1332/#1335, #1338, #1343, #1345 maintainer check-off, #1347/#1348, #1323, #1456/PR #1457, merged-worktree pruning, and explicit-base branch hygiene. None were checked off.

View task →

Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Combined review-fix evidence — exact head 484b553797920399118cfc69b828c3869513d12c

All findings posted against bf4549a7 and the subsequent local security/correctness reviews are addressed in three signed commits:

  • c976e3d3 closes the first transport/protocol batch: compatible-host egress registration and enforcement; post-header time/body/line/event limits; sanitized terminal transport/schema events; standard streaming-usage consumption; conversational buffered fallback; degraded finish/persistence semantics; full timeout/config/header/URL validation; camel-case SSE contract; timeout-safe health probe; protected-client proxy posture and controlled DI selection.
  • bf2fa68d closes the final adversarial batch: missing authoritative usage keeps the conservative reservation instead of output-only settlement; body-level failures drive the shared circuit while ordinary degraded finishes do not; success and cancellation/early-disposal settle half-open probes; every redirect fails closed; egress records/logs expose origins only; CR-only SSE and empty degraded terminals are handled; normal wire events omit false/null degradation fields; construction is factory-only; deployment/config surfaces are aligned.
  • 484b5537 updates STATUS, MASTERPLAN, and TESTING_GUIDE to the exact final contract and evidence without claiming the maintainer-key canary.

Final local evidence:

  • focused Application/API/Architecture lane twice: 277 passed, 1 existing architecture skip;
  • full backend solution: 7,570 passed, 5 skipped, 0 failed;
  • changed-file formatting, Docker Compose baseline config, docs/Golden/GitHub-ops governance, diff/show checks, DCO, and clean worktree: passed.

The 12 prior inline threads each receive a specific fix reply and are resolved because their reported defects are covered by the final head. Fresh exact-head security and correctness reviews plus Codex and hosted CI are starting now. Live vendor execution, visible multi-event UI streaming, and actual cloud deployment remain NOT verified; issue #1306 stays open and this PR remains maintainer-key gated.

…patible

Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
Signed-off-by: Chris0Jeky <jeky.tck@gmail.com>
@Chris0Jeky

Copy link
Copy Markdown
Owner Author

@codex review exact head 6ee17a4, please. All 12 prior inline findings are fixed, replied to, and resolved; review the final reconciled base and docs.

@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: 6ee17a4849

ℹ️ 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 +440 to +443
if (parsed.Error is not null)
{
yield return TerminalError(parsed.Error);
yield break;

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 Charge post-dispatch stream failures against quota

When a compatible endpoint accepts the request but the first SSE event is malformed, the response exceeds a limit before any delta, or the body read fails early, this emits only an error terminal. ChatService.StreamResponseAsync treats an error-only stream as never started and releases its reservation, even though the upstream request was dispatched and may have been billed, allowing repeated failed calls to bypass Taskdeck's quota. Propagate explicit pre/post-dispatch billing metadata and commit the estimate for post-dispatch failures.

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.

Valid finding, accepted as changes requested and tracked as item 1 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment on lines +168 to +171
if (((ICollection<KeyValuePair<string, ProviderFailureState>>)_providerFailures).Remove(
new KeyValuePair<string, ProviderFailureState>(circuitName, existing)))
{
RecordState(circuitName, CircuitState.Closed);

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 Preserve Polly state when closing the companion breaker

The companion body/protocol breaker writes Closed into the same public snapshot used by Polly without checking Polly's actual state. If one concurrent request opens Polly on a 5xx while an already-in-flight request later succeeds, this success removes only the companion state and overwrites the snapshot with Closed, although Polly continues rejecting calls until its break expires; the health endpoint therefore reports a false closed state. Track the two gates separately or publish Closed only when both are closed.

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.

Valid finding, accepted as changes requested and tracked as item 4 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment on lines +955 to +957
bytes = checked(bytes + Encoding.UTF8.GetByteCount([next.Value]));
if (bytes > maxBytes)
throw new LlmProviderResponseLimitException("single SSE line byte budget exceeded");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Count complete Unicode scalars for SSE byte budgets

For non-BMP text such as emoji, counting each UTF-16 code unit independently encodes the two surrogate halves as replacement sequences, reporting 6 bytes instead of the actual 4-byte UTF-8 scalar. An emoji-heavy SSE line or response can therefore be rejected as over budget even while its wire bytes remain within the configured limits; count complete surrogate pairs/Unicode scalars or enforce the limit on raw bytes.

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.

Valid finding, accepted as changes requested and tracked as item 6 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment thread docs/TESTING_GUIDE.md
- `docs/GOLDEN_PRINCIPLES.md`

## Current Verified Totals (2026-05-16)
## Last Cross-Stack Verified Baseline (2026-05-16)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Update references after renaming the totals section

Renaming this heading leaves docs/STATUS.md:1151 directing readers to the former “Current Verified Totals” section and docs/LIVING_DOCUMENTS_GUIDE.md:100 still naming that section as the testing guide's high-churn contract. Preserve the established heading or update both canonical references so the documented navigation remains valid.

AGENTS.md reference: AGENTS.md:L116-L118

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.

Valid finding, accepted as changes requested and tracked as item 13 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment thread docs/STATUS.md
Last Updated: 2026-07-27

REVIVAL-10 OpenAI-compatible provider slice (2026-07-27, `#1306`):
- **The backend has a first-class, opt-in `OpenAICompatible` provider for OpenAI Chat Completions-compatible endpoints, with the review-found transport and persistence gaps closed locally.** Production/non-development selection requires an API key, model, positive budgets, and a public HTTPS base URL; gated `http://localhost` remains development-only. The compatible client alone disables ambient proxies, refuses every redirect without following it, retains URL plus connection-time DNS/IP SSRF checks, and ensures egress-envelope violations and logs record only sanitized origins. Response bodies, SSE lines/events, and full response time are bounded; LF, CRLF, and CR framing are accepted. When authoritative usage is absent after upstream dispatch, quota settlement conservatively commits the original reservation estimate. Transport/protocol/timeout/limit failures count against the shared buffered/streaming circuit, ordinary degraded finish reasons do not, and cancellation or early disposal releases a half-open probe. Empty degraded terminals persist as sanitized reloadable assistant history, while normal SSE wire events omit false/null degradation fields. The concrete transport is internal and production construction is confined to reviewed API registration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Reconcile the provider inventory in STATUS

This new current-state entry says OpenAICompatible is supported, while the same source-of-truth document's active constraint at docs/STATUS.md:230 still says the LLM flow supports only OpenAI and Gemini and attributes extraction/context behavior only to those providers. Update the older inventory so operators do not receive contradictory supported-provider guidance from STATUS.

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

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.

Valid finding, accepted as changes requested and tracked as item 14 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

"Live provider response body failed.",
LlmProviderFailureKind.ResponseBody);
}
catch (Exception ex)

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 Let typed egress violations escape provider fallback

This broad catch also captures EgressViolationException raised when the compatible pipeline blocks a redirect or unregistered host and converts it into an ordinary degraded completion. Although the handler still blocks and logs the request, callers lose the typed security-boundary signal used for dedicated persistence and telemetry handling elsewhere, contrary to the exception's explicit never-swallow contract at EgressEnvelopeHandler.cs:395. Rethrow this exception before the generic fallback catch and cover both buffered and streaming paths.

AGENTS.md reference: AGENTS.md:L10-L10

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.

Valid finding, accepted as changes requested and tracked as item 8 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment on lines +131 to +133
the same URL and DNS-level SSRF checks as OpenAI. Plain HTTP is accepted only
for loopback development endpoints when both the development environment and
`AllowLiveProvidersInDevelopment` gate permit it. Keep keys in a secret store;

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 Document only the loopback form the validator accepts

This says plain HTTP works for loopback development endpoints, but ValidateLlmProviderUrl permits non-HTTPS only when the host is literally localhost, and compatible-provider validation rejects numeric IP hosts. Consequently http://127.0.0.1/...—the loopback form explicitly advertised earlier in this guide for LM Studio and LocalAI—selects Mock even with both development gates enabled. Narrow the claim to the localhost hostname or make validation accept loopback IPs consistently.

AGENTS.md reference: AGENTS.md:L116-L118

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.

Valid finding, accepted as changes requested and tracked as item 12 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment on lines +408 to +413
using var reader = new StreamReader(
stream,
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true),
detectEncodingFromByteOrderMarks: true,
bufferSize: 4096,
leaveOpen: false);

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 Enforce SSE limits on received UTF-8 bytes

Enabling BOM-based encoding detection lets a UTF-16 or UTF-32 BOM replace the supplied strict UTF-8 decoder. The parser then enforces line and aggregate limits against decoded text re-encoded as UTF-8 rather than bytes actually received, so an ASCII JSON stream encoded as UTF-16 can consume roughly twice MaxResponseBytes on the wire while remaining below the counter. Disable encoding auto-detection, optionally handle only a UTF-8 BOM, or enforce limits on the raw response stream.

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.

Valid finding, accepted as changes requested and tracked as item 6 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment on lines +708 to +712
if (!choice.TryGetProperty("message", out var message) ||
message.ValueKind != JsonValueKind.Object ||
!message.TryGetProperty("content", out var contentElement) ||
contentElement.ValueKind != JsonValueKind.String)
return false;

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 Handle null content-filter completions before parsing failure

Buffered parsing requires message.content to be a string before it examines finish_reason. A content-filter or refusal completion with content: null and finish_reason: "content_filter" is therefore recorded as a protocol failure, making the explicit content-filter degradation handling unreachable and allowing repeated user safety refusals to open the provider circuit. Parse the finish reason/refusal shape first and surface a sanitized degraded completion without counting it as provider failure.

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.

Valid finding, accepted as changes requested and tracked as item 9 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

Comment on lines +854 to +856
private static bool IsStreamingRejection(HttpStatusCode statusCode) =>
statusCode is HttpStatusCode.BadRequest or HttpStatusCode.NotFound or HttpStatusCode.MethodNotAllowed or
HttpStatusCode.NotImplemented or HttpStatusCode.UnprocessableEntity;

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 the buffered fallback reachable after HTTP 501

The provider classifies HTTP 501 as a streaming-rejection signal and intends to retry without SSE, but the production named client wraps requests in HandleTransientHttpError, which treats every 5xx as a circuit failure. With the valid CircuitBreaker:FailureThreshold=1, the first 501 opens Polly before the fallback executes, so the second request through the same client is rejected instead of producing the documented buffered completion. Exclude this recognized compatibility response from the compatible breaker or route the fallback through a path unaffected by that newly opened circuit.

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.

Valid finding, accepted as changes requested and tracked as item 10 in the #1306 parked-head acceptance remainder: #1306 (comment) . PR #1505 is parked under the two-round ceiling, so this thread intentionally remains unresolved until a future scoped implementation supplies a tested fix.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

CI recovery checkpoint for exact head 6ee17a4849427d679722a9d1e034d4622536f4fe:

The PR remains blocked: red CI has not yet cleared, fresh final reviews have additional unresolved correctness/security findings, and the maintainer live-provider/UI-stream canary is still outstanding. No merge conclusion is being drawn from GitHub's lenient protection.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Fresh adversarial security / egress / deployment review

Reviewed exact head 6ee17a4849427d679722a9d1e034d4622536f4fe against base c3f9abaa9f95870b10edd8a81b922c7af6f66986. Before posting this comment I inspected all 7 PR conversation comments, all 14 submitted reviews, all 23 inline threads (12 resolved, 11 unresolved; hasNextPage=false), and all 0 comments on linked issue #1306.

Disposition: changes required; do not merge this head.

HIGH

  1. The prior proxy/SSRF finding is only fixed for the new compatible client; the other protected outbound pipelines still validate the proxy rather than the origin.

    LlmProviderRegistration.cs:94-153 calls CreateProtectedSocketsHttpHandler(...) with the default disableSystemProxy: false for OpenAI, Gemini, and Ollama, while :186-202 sets UseProxy = true. LlmProviderRegistrationTests.cs:63-70,86-99 deliberately locks in UseProxy=true for those clients and the webhook transport. In .NET proxy modes the connection path passes the proxy host to ConnectToTcpHostAsync, which constructs the DnsEndPoint delivered to ConnectCallback; the proxy then resolves or tunnels to the origin (runtime source, proxy selection, callback endpoint, CONNECT tunnel). With a permitted/public system proxy, the advertised DNS/private-address/rebinding check therefore applies to the proxy, not the configured origin.

    The exact-head compatible pipeline correctly uses UseProxy=false; this finding is the unclosed remainder explicitly called out by the earlier HIGH review. Targeted issue searches found no open tracker for that remainder. Disable proxies for every transport whose security claim relies on origin ConnectCallback validation, or implement a proxy-aware origin enforcement design, and exercise the actual named pipelines with an explicit proxy test.

  2. A client abort before the first non-error delta can bypass both quota settlement and the half-open cooldown.

    This extends the already-open post-dispatch quota thread. ChatService.cs:619-647,724-760 releases the reservation unless a non-error event was observed. If the provider request has been dispatched but the caller cancels/disposes while waiting for the first event, no billable marker reaches ChatService, so the reservation is released. If that request held the half-open lease, CircuitBreakerStateTracker.AbandonProviderRequest (:177-197) sets OpenUntilUtc = UtcNow; the next caller can immediately acquire another probe instead of waiting the configured break duration. The tests at OpenAiCompatibleLlmProviderTests.cs:542-602 enshrine immediate reacquisition.

    An authenticated client can consequently cause repeated upstream work without consuming the hourly/daily LLM quota and can defeat the 60-second circuit cooldown, although the API hot-path limiter still bounds request rate. Carry explicit pre/post-dispatch settlement metadata, conservatively commit the estimate after dispatch, and retain/restart a full break after an abandoned half-open probe. Add a repeated-cancellation test proving at most one upstream dispatch per break period.

MEDIUM

  1. A stale ordinary success can close the body/protocol circuit opened by a newer concurrent request.

    When no companion state exists, TryEnterProviderRequest (:51-64) returns a default lease with no generation. If requests A and B both enter, B can cross the body-failure threshold and open the companion circuit, then A can complete successfully; RecordProviderSuccess (:160-174) accepts A's generation-less lease, removes B's new open state, and publishes Closed. For 200-header/body-failure cases Polly remains closed, so this removes the only effective body/protocol breaker, not merely its health display. Issue generation/versioned leases to all requests and ignore outcomes predating the state they would mutate; cover the failure-opens / stale-success-finishes ordering.

    This is related to, but stronger than, the current Polly snapshot thread, which covers false public state while Polly itself remains open.

  2. The configured SSE byte ceiling is not a ceiling on received bytes.

    I independently confirmed the current raw-byte limit thread. OpenAiCompatibleLlmProvider.cs:407-429 permits BOM encoding replacement, and :955 re-encodes decoded UTF-16 code units as UTF-8 for accounting. A read-only UTF-32 probe produced wireBytes=4196 while the counter saw 1048; at the allowed 4 MiB ceiling this admits roughly 16 MiB on the wire per response. Enforce the limit on the raw stream and accept strict UTF-8 (optionally one UTF-8 BOM).

  3. Typed egress violations are still converted to ordinary provider degradation.

    I concur with the current egress-violation thread: the outbound request remains blocked, but broad provider catches erase the typed boundary signal required for dedicated security handling and telemetry. Rethrow EgressViolationException on buffered and streaming paths.

  4. A non-empty completion with authoritative total_tokens: 0 releases quota.

    I concur with the current zero-usage thread. Treat impossible zero usage as unknown and commit the reservation estimate.

All 11 current unresolved inline threads were inspected; this comment does not repeat the remaining correctness/docs findings, which still require one-pass triage under the repository review policy.

Evidence

Focused read-only test runs:

dotnet test backend/tests/Taskdeck.Application.Tests/Taskdeck.Application.Tests.csproj -c Release -m:1 --no-build --no-restore --filter "FullyQualifiedName~OpenAiCompatibleLlmProviderTests|FullyQualifiedName~LlmProviderSelectionPolicyTests|FullyQualifiedName~StreamResponseAsync_ProviderYieldsOnlyErrorEvent_ShouldReleaseNotCommit"
Passed 112 / 112

dotnet test backend/tests/Taskdeck.Api.Tests/Taskdeck.Api.Tests.csproj -c Release -m:1 --no-build --no-restore --filter "FullyQualifiedName~LlmProviderRegistrationTests"
Passed 14 / 14

dotnet test backend/tests/Taskdeck.Architecture.Tests/Taskdeck.Architecture.Tests.csproj -c Release -m:1 --no-build --no-restore --filter "FullyQualifiedName~OpenAiCompatibleProviderArchitectureTests"
Passed 2 / 2

The worktree remained clean at the exact head.

Hosted checks are not green: 36 current check runs = 23 success, 12 skipped, 1 failure. Windows API Integration failed CardUpdateConflictTests.ConcurrentCardCreation_SameColumn_AllCreatedNoDuplicates with two HTTP 500 responses (job result: 1 failed, 2,135 passed, 4 skipped / 2,140). The corresponding Ubuntu job passed, and no concurrency test file is in this PR diff; that is not grounds to dismiss the failure as flaky.

Verified controls and residuals

Static/current-head inspection did verify that the compatible client itself disables proxies and redirects, uses the egress envelope plus DNS-level connection callback, validates its URL/API key/headers, applies response deadlines and bounded parsing, and uses redacted exception summaries. Hosted Gitleaks and CodeQL are green.

NOT verified: a maintainer-key live canary against OpenRouter/Groq/DeepSeek; visible incremental streaming through the real chat endpoint/UI; a system-proxy canary; an end-to-end request through the untouched production named-handler chain (the redirect test replaces EgressEnvelopeHandler.InnerHandler with a stub); or an actual Docker Compose/Render deployment using the new settings. Deployment parity is asserted by static string-presence tests, not a deployed runtime.

Copy link
Copy Markdown
Owner Author

Fresh adversarial correctness / state-machine review — exact head 6ee17a4849427d679722a9d1e034d4622536f4fe

Disposition: changes required. This pass adds one HIGH-scope extension to the existing quota finding and two MEDIUM findings/edge cases.

Before posting, I verified the local and live head match and inspected all current linked-issue comments (0), PR conversation comments (9), submitted reviews (14), and 35 inline comments across 23 threads (12 resolved, 11 unresolved; pagination complete). The new Codex inline review and the security review arrived during this pass; I read them and avoid repeating their already-posted findings.

HIGH — extension to the existing post-dispatch quota finding

  1. The same cancellation hole exists on buffered CompleteAsync, not only the streamed zero-delta path.

    The open post-dispatch quota thread and the security review correctly cover error-only/cancel-before-first-delta streaming. The buffered path also dispatches at OpenAiCompatibleLlmProvider.cs:88,560-561, then deliberately rethrows caller cancellation at lines 167-170. Because no LlmCompletionResult reaches ChatService, quotaBilledTokens remains zero and the finalizer releases the reservation at ChatService.cs:539-562. A caller abort after dispatch can therefore evade the same “commit the estimate after upstream dispatch” rule even without using SSE.

    Required: make dispatched/settle state survive exceptional cancellation on both provider APIs, and add a controlled buffered-cancel-after-dispatch test alongside the streamed cases. Pre-dispatch configuration/circuit rejection must still release.

MEDIUM

  1. Generation-less leases also allow a stale pre-open failure to re-poison the circuit after a successful half-open probe.

    The security review already covers the forward ordering where an old success erases a newer open companion state. The mirror ordering is also unsafe: request A enters before the circuit opens and receives the default lease (CircuitBreakerStateTracker.cs:59-77); newer failures open it; a later half-open probe succeeds and removes state at lines 160-173; then A's old failure completes. Because there is no state and A's lease is not marked as a probe, lines 112-121 create a fresh failure generation (and reopen immediately when FailureThreshold == 1). A probe that proved recovery can thus be undone by work from before the prior open transition.

    Required: generation/epoch every admitted request and ignore results older than the state they would mutate. In addition to the already-requested failure-open/stale-success test, pin half-open-success/pre-open-stale-failure.

  2. An error-only streamed terminal is not persisted, so reload loses the outcome.

    ChatService records an error as a degraded reason at ChatService.cs:680-684, but enables the empty-result placeholder only for IsDegraded && Error is null at lines 673-678. Compatible TerminalError events carry empty content and do not set IsDegraded (OpenAiCompatibleLlmProvider.cs:784-796), so lines 691-705 skip the database write. The live client sees a message.complete error, but session history contains no assistant outcome after reload. Current tests cover partial-content errors and empty non-error degradation, not this branch.

    Required: persist a sanitized degraded assistant placeholder for terminal error-only streams and add a reload assertion.

Read-only verification

  • Application focus: 74 passed / 0 failed / 0 skipped
    dotnet test backend/tests/Taskdeck.Application.Tests/Taskdeck.Application.Tests.csproj -c Release -m:1 --no-restore --filter "FullyQualifiedName~OpenAiCompatibleLlmProviderTests|FullyQualifiedName~ChatServiceTests.StreamResponseAsync|FullyQualifiedName~LlmCaptureTriageExtractorTests"
  • API focus: 37 passed / 0 failed / 0 skipped
    dotnet test backend/tests/Taskdeck.Api.Tests/Taskdeck.Api.Tests.csproj -c Release -m:1 --no-restore --filter "FullyQualifiedName~LlmProviderRegistrationTests|FullyQualifiedName~CircuitBreakerTests"
  • Architecture focus: 13 passed / 0 failed / 1 existing skip
    dotnet test backend/tests/Taskdeck.Architecture.Tests/Taskdeck.Architecture.Tests.csproj -c Release -m:1 --no-restore --filter "FullyQualifiedName~OpenAiCompatibleProviderArchitectureTests|FullyQualifiedName~RoadmapInvariantTests"
  • git diff --check c3f9abaa9f95870b10edd8a81b922c7af6f66986...6ee17a4849427d679722a9d1e034d4622536f4fe: passed.
  • Hosted exact-head evidence was 23 passed / 1 failed / 12 skipped when checked. Windows API Integration failed an unrelated create-card concurrency test (2,135 passed / 4 skipped / 1 failed); the separate CI recovery checkpoint tracks the bounded rerun and issue [CI] Diagnose post-#1373 Windows concurrent-card HTTP 500 #1512.

NOT verified: a maintainer-key OpenRouter/Groq/DeepSeek canary, visibly incremental streaming through the real UI/dev-up path, or cloud deployment.

@Chris0Jeky

Copy link
Copy Markdown
Owner Author

Parked after bounded final review — exact head 6ee17a4849427d679722a9d1e034d4622536f4fe

Disposition: CHANGES REQUESTED / PARKED. Do not merge and do not start a third fix/review round on this head.

The complete final inventory is recorded on #1306: 14 provider/streaming/quota/circuit/persistence/docs findings remain acceptance work there. The cross-provider ambient-proxy/SSRF finding is separately tracked by #1513 (Next / Priority I). The Windows concurrent-card CI recurrence remains #1512; its identical-head rerun passed, which supports nondeterminism but does not prove a cause or close the issue.

All 11 open inline threads are valid and will receive a reply pointing to their tracked resolution owner. They remain unresolved because no fix exists on this head. The final independent security and correctness reviews are likewise accepted as changes requested.

Verified on the parked head

  • Required CI rerun is green, including Windows API Integration and E2E Smoke.
  • CI Extended and CodeQL are green.
  • The worktree remains clean at the exact remote head.

NOT verified

  • maintainer-key OpenRouter/Groq/DeepSeek traffic
  • visibly incremental streaming through the real API/UI
  • cloud deployment
  • resolution of any of the final-round findings

A future implementation should start as a newly scoped branch/PR from current main or an explicitly chosen successor, not by silently reopening this review cascade.

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

Labels

backend llm Priority II revival Revival pivot wave (ADR-0044, docs/REVIVAL_PLAN.md)

Projects

Status: Blocked

Development

Successfully merging this pull request may close these issues.

1 participant