Skip to content

fix(noema): harden truncated structured verdict publication - #1606

Open
seonghobae wants to merge 49 commits into
mainfrom
fix/noema-truncated-completion-contract-20260901
Open

fix(noema): harden truncated structured verdict publication#1606
seonghobae wants to merge 49 commits into
mainfrom
fix/noema-truncated-completion-contract-20260901

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Current-main Ready successor to closed #1602. The branch was non-destructively reconciled onto protected main@5d1b9b2109991689d02301fb3577a4d79dbe386f; the effective delta is exactly CHANGELOG.md, scripts/ci/noema_review_gate.py, and tests/test_noema_truncated_completion_contract.py. Temporary one-shot workflow/driver files were removed after verification.

This preserves #1602's bounded structured-completion recovery and also closes the exact current review findings: comment verdicts can no longer smuggle non-string rendered evidence into GitHub Markdown, every rendered reviewed/probe/finding field is typed and bounded, line values must be positive integers, decoded-but-schema-invalid verdicts receive a typed InvalidVerdictError repair retry and stable invalid_verdict_after_retry terminal diagnostic, and completion-envelope decoding remains fail closed.

TDD evidence from hosted one-shot run 33510393580: the three new regressions were required to fail before production repair; the repair then produced 9 passed in the focused structured-completion contract and 2327 passed, 1 skipped, 21 subtests passed in the full repository suite, followed by compileall and git diff --check. The run itself concluded success. No reviewer identity, provider route, branch protection, security threshold, merge authority, or direct-provider fallback changed.

Fresh exact-head review/check evidence on this successor is authoritative; predecessor PR reviews/checks do not transfer. If the unchanged current head remains mechanically mergeable with no substantive finding and required workflows alone remain queued behind the saturated central Actions fleet, apply the documented QUEUE_SATURATION_CHICKEN_EGG boundary only after re-fetching live state.


Devin Review

Summary by CodeRabbit

  • 새로운 기능
    • 구조화된 리뷰 결과가 출력 제한으로 잘린 경우, 메타데이터를 보존하고 한 차례 복구를 시도합니다.
    • JSON 형식과 리뷰 결과의 필드·길이를 엄격히 검증합니다.
    • 반복적인 잘림, 잘못된 JSON, 유효하지 않은 결과를 서로 구분해 안전하게 실패 처리합니다.
  • 버그 수정
    • 공급자 응답이 불완전하거나 비정상적인 경우 원시 출력이 공개 로그에 노출되지 않도록 개선했습니다.
  • 문서
    • 위 리뷰 결과 복구 및 실패 처리 동작을 변경 로그에 추가했습니다.
  • 테스트
    • 잘림, 재시도, 메타데이터 검증 및 결과 형식 경계에 대한 회귀 테스트를 추가했습니다.

seonghobae and others added 23 commits September 1, 2026 21:05
Preserve current Strix and SBOM control-plane changes while carrying only the verified Noema structured-completion production source, regression contract, and changelog forward. The completed one-shot workflow and repair driver remain absent.
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 45 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: e694ceb9-0d71-4d41-b54c-fe505ff88f07

📥 Commits

Reviewing files that changed from the base of the PR and between aafa309 and b54f51a.

📒 Files selected for processing (1)
  • tests/test_noema_truncated_completion_contract.py
📝 Walkthrough

Walkthrough

Noema가 OpenAI 호환 완료 메타데이터를 보존하고, JSON verdict의 출력 범위를 검증하며, finish_reason=length에 한 번 재시도하도록 변경했습니다. 관련 회귀 테스트와 main restack 자동화도 추가했습니다.

Changes

Noema 완료 복구

Layer / File(s) Summary
완료 계약과 verdict 제한
scripts/ci/noema_review_gate.py
LLMCompletion과 typed 예외를 추가했습니다. 완료 메타데이터와 verdict 필드의 타입, 길이, 카디널리티를 검증합니다.
LLM 요청과 typed 재시도
scripts/ci/noema_review_gate.py, CHANGELOG.md
JSON 모드와 4,096 토큰 예산을 요청합니다. 길이 중단, 잘못된 JSON, 잘못된 verdict를 각각 구분하고 한 번 재시도합니다.
복구 계약 회귀 테스트
tests/test_noema_truncated_completion_contract.py
메타데이터 보존, 재시도, 오류 구분, 출력 경계와 필드 타입을 검증합니다.
검증 및 restack 자동화
.github/workflows/tmp-pr1606-current-main-restack.yml
main 병합과 충돌 처리를 수행합니다. 테스트, diff, Python 구문을 검증한 뒤 대상 브랜치에 push합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to aafa3

The change hardens structured verdict handling but also includes a validation workflow that retains write-capable repository credentials during tests, creating a potential unintended-ref update risk; malformed error paths may also pass the new checks. Merge should wait for the credential handling issue to be fixed and the assertions tightened.

Sequence Diagram(s)

sequenceDiagram
  participant ReviewGate as call_llm
  participant Provider as LLM provider
  participant Parser as extract_llm_completion
  participant Validator as validate_verdict_output_bounds
  ReviewGate->>Provider: JSON verdict request with 4096-token budget
  Provider-->>ReviewGate: completion envelope
  ReviewGate->>Parser: Parse content and bounded metadata
  Parser-->>ReviewGate: LLMCompletion
  ReviewGate->>Validator: Validate verdict fields and bounds
  Validator-->>ReviewGate: Valid verdict or typed error
  ReviewGate->>Provider: Compact repair retry after finish_reason=length
  Provider-->>ReviewGate: Repaired completion
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 기능 변경은 [#1602] 범위와 일치하지만, .github/workflows/tmp-pr1606-current-main-restack.yml 임시 restack 워크플로가 최종 변경 사항에 포함되어 있습니다. PR 목표는 임시 워크플로와 드라이버를 제거하고 기능 파일 세 개만 남기는 것이므로 이 파일은 범위를 벗어납니다. .github/workflows/tmp-pr1606-current-main-restack.yml을 최종 브랜치와 PR diff에서 제거하십시오. 최종 변경 파일이 CHANGELOG.md, scripts/ci/noema_review_gate.py, tests/test_noema_truncated_completion_contract.py만 포함하는지 다시 확인하십시오.
Docstring Coverage ⚠️ Warning Docstring coverage is 79.31% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 2 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 구조화된 verdict의 잘림 및 오류 처리를 강화하는 주요 변경 사항을 정확하고 간결하게 설명합니다.
Linked Issues check ✅ Passed PR은 [#1602]의 핵심 요구 사항을 구현합니다. 완료 메타데이터 보존, JSON 모드와 4,096 토큰 예산, 단일 truncation 재시도, 오류 유형 구분, verdict 출력 경계 검증, fail-closed 처리를 포함합니다. 관련 회귀 테스트도 추가되었습니다.
Full details: Docstring Coverage

Explanation

Docstring coverage is 79.31% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 2 files. (2 skipped: 2 unsupported.)

✨ Finishing Touches 💡 3
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch fix/noema-truncated-completion-contract-20260901
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/noema-truncated-completion-contract-20260901

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.

devin-ai-integration[bot]

This comment was marked as resolved.

seonghobae added a commit that referenced this pull request Sep 1, 2026
)

QUEUE_SATURATION_CHICKEN_EGG: this exact one-line test repair matches protected production's three-argument GITHUB_ENV publication contract. The stale protected-main assertion is independently proven as the sole failure after 2,344 passing tests in #1606's exact-head Strix quality run. Current-head Devin/CodeRabbit statuses are success, there are zero review threads, and all required workflows are queued in a 894-run saturated Actions fleet. No substantive product, security, provenance, or review defect is bypassed.
Preserve the protected-main Strix installer-test repair while carrying the verified three-file Noema structured-verdict hardening forward. Temporary repair workflow/driver files remain absent; no force push or unrelated tree replacement.
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration 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.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 1 new potential issue.

Devin Review

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.

🔍 Focused tests need installed tooling

Local execution lacks pytest, so only compilation and whitespace checks ran. Hosted results remain the available behavioral test evidence.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae seonghobae added documentation Improvements or additions to documentation priority: high High-priority or P1 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior labels Sep 2, 2026 — with ChatGPT Codex Connector

@devin-ai-integration devin-ai-integration 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.

Devin Review found 3 new potential issues.

Devin Review

Comment on lines +1403 to +1404
except RuntimeError as exc:
raise InvalidCompletionError(str(exc)) from exc

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.

🔴 Oversized JSON integers crash reviews

When completion JSON exceeds Python's integer digit limit, json.loads raises ValueError, bypassing both retry wrappers. The required review exits with a traceback.

Prompt for agents
Handle ValueError raised by Python's JSON decoder for integer literals exceeding the configured digit limit. Both extract_llm_completion and extract_json_object can raise it, and call_llm currently catches only RuntimeError around those calls. Convert this parser failure into InvalidCompletionError so the first failure receives one repair attempt and a repeated failure produces the existing bounded invalid_json_after_retry diagnostic. Add envelope-level and verdict-content regression tests using an integer longer than sys.get_int_max_str_digits().
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1455 to +1458
if isinstance(exc, InvalidCompletionError):
raise RuntimeError(
f"Noema LLM response invalid_json_after_retry: {exc}"
) from exc

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.

🔍 Envelope failures get JSON label

Repeated unsupported finish reasons and malformed metadata receive invalid_json_after_retry. Operational diagnostics therefore misidentify valid JSON envelopes as malformed JSON.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1236 to +1246
"Noema LLM response adversarial probe class_evidence must be an object"
)
if len(class_evidence) > NOEMA_MAX_CLASS_EVIDENCE_FIELDS:
raise RuntimeError(
"Noema LLM response adversarial probe class_evidence "
f"exceeds {NOEMA_MAX_CLASS_EVIDENCE_FIELDS} fields"
)
for value in class_evidence.values():
_bounded_text(
value,
"adversarial_validation.probes.class_evidence",

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.

📝 Info: Class evidence keys stay private

Unbounded class_evidence keys do not reach review Markdown or diagnostics. Cardinality and rendered values remain bounded, so publication size stays controlled.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Merge-conflict triage: genuine architectural divergence in scripts/ci/noema_review_gate.py's LLM request schema

mergeable_state is dirty. git fetch origin main && git merge --no-edit in a scratch clone: 4 conflict blocks, all in scripts/ci/noema_review_gate.py (CHANGELOG.md was clean). The decisive one:

# this branch (HEAD):
"response_format": {"type": "json_object"},

# origin/main:
"response_format": _noema_verdict_response_format(
    _required_probe_count(diff, changed_paths)
),

origin/main has independently adopted a structured response_format JSON-schema helper (_noema_verdict_response_format / _required_probe_count, confirmed present on main at lines 187/548) that this branch's simpler json_object request has no knowledge of at all — this branch's own core contribution (truncated-completion recovery: retry-with-feedback prompt injection, typed InvalidVerdictError, bounded rendered-evidence fields) is built against the older unstructured request shape. Reconciling means deciding whether the truncated-completion recovery this branch adds needs to work against the schema-constrained response format main now uses (different retry/repair semantics likely apply to a strict JSON-schema response vs a bare json_object one), not a text-level splice.

This is the same file already found to have deep concurrent divergence in #1198 (a different, expected_head-based mechanism) — noema_review_gate.py currently has at least three independent, non-trivially-composable lines of development racing on main simultaneously (this branch's truncation hardening, #1198's exact-head binding, and whatever landed the _noema_verdict_response_format/probe-count schema already on main). A guessed text splice here risks the required Noema review gate silently regressing on a security-relevant path.

Per this repo's own standard for this exact situation (#1279, #1280, #1382, and today's #1198, #939, #1009, #1158, #1674): aborting cleanly, not pushing a guessed resolution. No changes pushed to this branch.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

The defect is still live on main, but this PR can no longer be merged into it — it needs re-implementing, not resolving

I went to resolve this PR's conflict against main and stopped. Two findings, and the first one matters more than this PR does.

1. The bug this PR fixes is unfixed on main, and I demonstrated it

scripts/ci/noema_review_gate.py on main@7fcada597 contains zero occurrences of finish_reason. The gate never asks the provider whether it truncated its own output.

Structurally-invalid truncation does fail closed, so this is not a total gap — but the interesting case is the one that does not. Driving main's own extract_json_object directly:

A. '{"decision":"approve","summary":"looks fine","findings":[{"severity":"high"'
   → fails closed: NoemaModelOutputError                         ✅

B. '{"decision":"approve","summary":"reviewed","findings":[]}'
   → PARSED as a valid verdict: {'decision': 'approve', ...}     ⚠️

Case B is what an output-budget termination looks like when the cut happens to land after a syntactically complete object. findings is emitted last, so the most likely truncated-but-parseable shape is precisely an APPROVE with an empty or short findings list — a review that was cut off mid-thought, accepted as a genuine approval. main's local repair is deliberately lossless (trailing commas only, _strip_trailing_commas_outside_strings), so it does not manufacture this; the provider does, and the gate simply never checks the field where the provider declares it.

This is the same shape as #1921: the reviewer could not see everything, and approved anyway, with nothing in the output saying so. There the cause is allowed-locations starvation; here it is output-budget truncation. Both end in a silent APPROVE on a required gate.

2. Why the merge cannot proceed mechanically

main has restructured the same function around a different architecture, and this PR's implementation contradicts it:

this PR main today
repair ownership caller-side retry (is_retry, truncated_after_retry, stale-head check before retry) gateway-ownedcaller attempts=1 (gateway owns repair/failover)
content extraction extract_llm_completionLLMCompletion carrying finish_reason extract_llm_message_content (content only)
error taxonomy TruncatedCompletionError / InvalidCompletionError / InvalidVerdictError NoemaModelOutputError + active_phase tracking
observability _extract_served_model, _extract_http_error_telemetry, _stable_failure_diagnostic

Of the five conflicted hunks, three are benign — hunk 1 is two complementary constant sets (mine bound verdict fields, main's add MAX_ALLOWED_LOCATIONS_JSON_BYTES and MAX_HTTP_ERROR_BODY_BYTES; both are wanted), and hunks 2–3 are pure additions where main has nothing. Hunks 4 and 5 are the problem: they are the LLM call path itself, rewritten on both sides toward opposite designs. Resolving them means choosing between caller-side and gateway-owned repair — a decision, not a conflict resolution. Taking my side would silently reintroduce caller-side retry against main's explicit contract.

3. What I propose instead

Do not merge this PR as-is. The valuable part is one check that fits main's architecture without touching repair ownership at all:

  • extend main's content extraction to read choices[0].finish_reason alongside the message content;
  • raise NoemaModelOutputError when it is length (and when it is anything outside {"", "stop"});
  • no retry, no new exception hierarchy, no change to who owns repair — the gateway keeps that.

That is a small, additive change in main's own idiom and its own error type, and it closes case B above. This PR's bounded-field validators (validate_verdict_output_bounds and the NOEMA_MAX_* constants) are independently useful and could follow as their own change.

I have not written that patch, because #1921 proposes narrowing the declared verdict scope in the same file and the two interact — both are about a verdict that overstates what was reviewed. Sequencing them deliberately beats racing them into the same function.

Happy to implement the minimal finish_reason check as a fresh PR against current main on a word from whoever owns this gate. Not pushing anything to this branch meanwhile.


Generated by Claude Code

@seonghobae

Copy link
Copy Markdown
Contributor Author

I am working through this repository's CONFLICTING pull requests and stopped on this one without pushing anything. It cannot be resolved by choosing sides: main has since replaced the function this PR builds on, so a correct merge means re-implementing this PR's design, not resolving text.

What the conflict looks like

scripts/ci/noema_review_gate.py has 5 conflict blocks. Four are tractable:

block resolution
1 — module constants keep both; the two sides add disjoint names (NOEMA_LLM_MAX_COMPLETION_TOKENS … vs MAX_ALLOWED_LOCATIONS_JSON_BYTES, MAX_HTTP_ERROR_BODY_BYTES)
2 — 180 new lines keep this branch's; TruncatedCompletionError, InvalidCompletionError, InvalidVerdictError, StaleHeadDuringRepairRetryError, LLMCompletion, validate_verdict_output_bounds are additive definitions main does not have
3 — prompt text keep only the first line ("Keep the JSON compact: …"). The rest of the block is this branch's repair-mode injection referencing repair_error / is_retry, which main removed — carrying it whole yields undefined names
4 — request payload keep this branch's temperature / max_completion_tokens, take main's response_format: _noema_verdict_response_format(...); main's strict schema supersedes {"type": "json_object"}

Block 5 is the blocker.

Why block 5 has no correct pick

Main's side of block 5 is the only place that assigns current_failure, gateway_note, model_note, elapsed, served_model, active_phase. Code outside every conflict already references them (active_phase ×4, served_model ×8, elapsed ×5), so taking this branch's side leaves those undefined — measured, not guessed: ruff --select F821 reports 10 undefined names and 26 tests fail.

Taking main's side compiles and goes green — and silently deletes this PR's feature:

class TruncatedCompletionError         defined=1  raised=0
class InvalidCompletionError           defined=1  raised=0
class InvalidVerdictError              defined=1  raised=0
class StaleHeadDuringRepairRetryError  defined=1  raised=0
validate_verdict_output_bounds         defined, never called

180 lines of dead code with a fully green tests/test_noema_review_gate.py (114 passed). The reason it looks fine is that the truncation contract lives in its own file, tests/test_noema_truncated_completion_contract.py, which this PR adds — and that file fails 5 of 9.

I then grafted the branch's checks into main's structure (extract_llm_completion, the finish_reason == "length"TruncatedCompletionError path, InvalidCompletionError around decode/parse), preserving active_phase and served_model. F821 clean, test_noema_review_gate.py 114 passed — still 5 of 9 failing here, because the remaining failures are not about raising at all:

Expected regex: 'truncated_after_retry'        Actual: '… finish_reason=length; caller attempts=1 …'
Expected regex: 'invalid_json_after_retry'     Actual: '… was not valid JSON …; caller attempts=1 …'
Expected regex: 'invalid_verdict_after_retry'  Actual: '… unsupported decision; caller attempts=1 …'
Failed: DID NOT RAISE RuntimeError

Those contracts require the retry-and-repair loop: fail once, resend with a repair prompt, and only classify as *_after_retry on the second failure. That loop is this branch's is_retry / repair_error machinery, and main has zero references to either. Rebuilding it on main's rewritten call_llm is re-implementing the PR's core design, and a wrong version would silently change failure classification inside a required review gate. That is the author's call, not a merge decision.

What I suggest

The four tractable blocks above are stated precisely enough to apply directly; block 5 needs the retry loop rebuilt against main's current call_llm shape, with tests/test_noema_truncated_completion_contract.py (9 tests) as the acceptance oracle.

Nothing was pushed to this branch — it is exactly as you left it (b54f51a1).

🤖 Generated with Claude Code

@opencode-agent

opencode-agent Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Scheduled review-feedback autofix for this PR head.

  • Head SHA: b54f51a1c587376334fd3d6a59683b9ffbf22d98

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

Labels

documentation Improvements or additions to documentation priority: high High-priority or P1 work status: needs-review Open pull request requiring current-head review or checks type: bug Defect or incorrect behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant