Repository navigation
Conversation
…d, recorded; the dropped blocks counted (#625) The adapter had no request-side thinking control while the SDK accepts one; the response translation dropped unsupported block kinds (thinking, refusal) with a once-per-kind stderr warning while forwarding their usage — the delivered and billed content could differ invisibly. An ENHANCEMENT with the DEFAULT byte-identical (the #242 instrument lesson). The change: PhaseRef.thinking (the SDK's dict shapes; parse-time validation naming the config/phase; the enabled budget bounded below the smallest sub-call cap — the JSON-correction/consistency requests ride the same binding at 4096, and the consistency pass swallows exceptions silently); the build-time tool-phase gate (enhance/verify) BEFORE any adapter build + the adapter-level backstop covering every other tools= call site (the threat-model repo exploration — the phase list cannot name app_context's duality); one adapter per (provider, thinking) tuple; the policy folded into the analyze/verify/llr checkpoint identities only-when-set (zero re-pay for defaults); recorded present-only in the step inputs and results.json; the Go wizard preserves hand-authored phase keys; the per-kind dropped-block counts on every result + the run-cumulative counter through get_totals to the step reports (both branches). Receipts (all run at commit time, on this commit): - RED on base 6dde272 (final bytes, copies byte-identical): python3 -m pytest tests/test_issue625_thinking_policy.py \ tests/test_issue625b_exhaustion_rows.py -q -> 26 failed (value/signature-RED), 1 passed (the reset-lifecycle guard, green at base by design) in 0.99s (per-test rows: red-receipt-625.log) - GREEN at head: the two files 27/27; the battery (anthropic-adapter, provider-warnings, #604, config-schema, backend-identity, #287, #211, #605) all green - Full suite, matched conditions: head 2 failed, 4314 passed, 34 skipped in 319.53s; base 28 failed, 4288 passed, 34 skipped in 237.73s (the 2 failures are the pre-existing SDK-pin pair; skipped 34=34; the passed delta is exactly the 26 RED rows flipping) command: python3 -m pytest tests/ -q - ruff check . -> All checks passed! - go build/vet/test ./internal/config/ -> ok (incl. the new phase-merge row) - Serves: I-ctx (the attacker-model instrument knob, visible + identity- bound), I-out (the dropped-vs-billed reconciliation persisted). De- balancing checked: the default request byte-identical (full-dict equality pinned); validate() carries no policy (the 1-token probe cannot satisfy the budget constraint — documented at the site); the #211 usage_details channel untouched (dropped_blocks is a separate field); add_prior_usage untouched; the Go merge preserves only hand-authored keys.
…6cb7b7d (the CodeQL workflows fired; the pull_request synchronize event reached only them)
|
Conflict with master resolved on @sounil — a code-owner review would be appreciated once the checks land: this change touches the LLM pipeline (phase config, registry, adapters, |
…policy reaches every LLM step's inputs (#625) A non-consuming adapter (its class does not declare the thinking kwarg — bedrock/openai/google today) never sends the policy, so the phase binding — the fingerprint-extra and step-input source of truth — must not report one; the one-time build warning stays the only surface for the unconsumed knob. Default users are unaffected (absent stays absent). The effective policy now also reaches the step reports of the remaining LLM phases (app-context, enhance, verify, dynamic-test, report) present-only, completing the comparison surface the analyze/llm-reachability twins started. Tests: the discriminating non-consumer/consumer/default battery (test_issue625c_effective_policy.py) plus the import-style cleanups in the two existing test files.
Four changes the review arc's challenge wave caught: - A 400 naming thinking on a policy-configured adapter is now FATAL (ThinkingPolicyRejectedError, not a per-unit ERROR row): every unit would fail identically, and the fan-out painted an all-error scan green (exit 0, analyze in steps_completed, verify skipped no_candidates — the replayed shape). The error escapes the per-unit catch-all so the phase handler marks the step errored. - The step reports' dropped_blocks are now PER-STEP DELTAS (the process-global counter snapshots at step entry; only this step's increases are written), and scan.report.json aggregates those deltas by sum — the run-cumulative snapshots re-reported every earlier step's drops (enhance reporting analyze's counts), and summing them overcounted by the prefix. The anthropic.py comment now describes the real path and the invocation-scoped (not resume-persistent) semantics. - The declared SDK floor rises to anthropic[bedrock]>=0.47.0 — the thinking kwarg does not exist before it (verified across 0.40/0.46/0.47), and every call would TypeError per-unit under the old floor. A capability test pins the installed SDK. - The verifier and llm-reachability checkpoint folds gain discriminating tests: deleting either previously survived the whole 625 battery while enabling stale cross-policy checkpoint adoption (executed: SDK calls 2->0 under the mutant). Tests: test_issue625d_review_arc.py (the fatal-400 battery, the delta semantics, the SDK floor, the fold pins) + the 625b/428 tests updated to the delta semantics.
…se too (#625) The review arc's reachability round caught the LLR per-batch catch-all swallowing ThinkingPolicyRejectedError: a provider-rejected llm_reach policy failed every batch identically, promoted nothing, and the scan continued under-seeded while the step read 'partial' and the envelope 'success' — the phase that decides what gets analyzed must fail closed exactly like analyze. _attempt re-raises it before the generic handler, and the scanner's LLR guard lets it escape catch-and-continue.
|
Disposition ruling needed — #625 is closed by #660; this PR needs a decision, not a conflict resolution. #660 (
Gaps on landed master today (independent of this branch's fate — each verified at
What the landed design has that this branch lacks (keep in any successor): Bedrock thinking consumption; the central Design-coupled deltas on this branch (valuable only if per-phase lands): phase-entry Debts either way: the behavioral regression test for the fatal-policy guard; the pre-existing auth-error green-scan class (401 on analyze → exit 0); the standalone-CLI policy-recording scope. The branch and all its receipts stay as-is pending the ruling. Agent: PR-MERGE ses_f3e3e9b11ffetbZil76EvwfDum |
Fixes #625. Follow-ups named below (the echo-preservation design;
thinking_tokenscapture; the Bedrock knob) — recorded, not filed here.What was wrong
Two gaps, one theme — the request-side instrument:
messages.createaccepts one — and the response'soutput_tokensincludes thinking tokens, so the spend rode either way with no way to steer or explain it.stop_reasoncould read as an empty success (the file's own comment).This is an enhancement with the hard constraint the issue itself names: the default stays byte-identical (the #242 instrument lesson — verdict behavior is evaluated separately, never mixed with a usage-accounting change).
The fix
thinkingon the config's phases (the SDK's own dict shapes —adaptive/enabled+budget /disabled; absent = no key sent, an explicitdisabledis a distinct request that survives the round-trip). Parse-time validation rejects malformed shapes naming the config and phase; theenabledbudget is bounded below the pipeline's smallest sub-call cap (4096 — the JSON-correction and stage-1-consistency requests ride the same binding at that cap, a larger budget would fail them mid-scan, and the consistency pass's exception handling would swallow it silently).enhance/verifyreject a policy at registry build before any adapter is built, and an adapter-level backstop covers every othertools=call site — including the threat-model repository exploration, which the build-time list cannot name (it drives both a single-turn path and a tool loop). One adapter per(provider, thinking)tuple.results.json; folded into the analyze/verify/llm-reachability checkpoint identities only-when-set — a resumed scan never adopts verdicts produced under a different policy (the fix(analyzer): recover reasoning-only empty-completion analyze responses #242 gen-params exclusion was conditional; a thinking policy changes ordinary successes, so the fold is the honesty mechanism). Default users' digests are unchanged — zero re-pay. The Go wizard now preserves hand-authored phase keys on re-runs.dropped_blockson every result (per-kind by design — a routine thinking drop can never bury a dropped refusal) plus a run-cumulative per-kind counter that reaches the step reports'token_usage.dropped_blocksin both the success and the accounting-error branches. Present-only — clean runs' artifacts are byte-identical.Evidence
assert_not_called), the parse-time rejection matrix, the round-trip (None-vs-disabled), the tool gate naming the phase, the adapter backstop, the disabled-passes rows, the per-kind counts, the fingerprints' behavioral folds, the step surfaces, the Go merge.max_tokensdoes not invalidate verify checkpoints #287, Cost accounting under-reports by ~15%, concentrated in the Claude Opus 5 phases #211, Silent accounting drops: the report-phase tracker hand-off and the step-report zero-snapshot swallow failures into complete-looking zero cost #605) all green.test_llm_sdk_contract_floorSDK-pin pair, reproduced identically on pristine master; CI's pinned env is authoritative); base 4288 / 34 / 28 — skipped 34=34, the passed delta exactly the RED rows flipping.app_context— which drives the threat-model tool loop (the phase the list cannot name; the adapter-level backstop adopted); and the budget pre-flight's per-call blindness (the consistency pass's bareexceptwould swallow the mid-range-budget failure silently — the parse-time 4096 bound adopted). r2 caught the vacuous warning row and the untested step-report folds; r3 EXHAUSTED after the vacuous-row removal, theSUBCALL_BUDGET_CAPconstant (the pin binds the shadow to the real constants), and the leaking-rowfinally.The de-balancing check (the intent trace)
serves: I-ctx(the attacker-model instrument knob — visible and identity-bound),I-out(the delivered-vs-billed reconciliation persisted).de-balances-checked: the default request byte-identical (full-dict equality pinned); the artifacts present-only on the default path;validate()carries no policy (the 1-token probe cannot satisfy the budget constraint — documented at the site); the #211usage_detailschannel untouched (dropped_blocksis a separate field);add_prior_usageuntouched; the fingerprint folds only-when-set (zero re-pay); the Go merge preserves only hand-authored keys.Notes for the reviewer
dropped_blocksis run-cumulative (the Silent accounting drops: the report-phase tracker hand-off and the step-report zero-snapshot swallow failures into complete-looking zero cost #605 accepted trade — a reader summing across steps overcounts);thinking_tokens(the SDK ≥ 1.5.0 field that does split thinking from output) is deliberately a separate change; Bedrock does not consume the knob (warns once; its shared response translation does gain the counts).CI anomaly (recorded, not laundered): the
testsandgitleaksworkflows did not dispatch for this PR — a repo-wide pull_request dispatch outage, not a failure: two pushes (incl. a retrigger) and a fresh PR number produced only the CodeQL workflows (gh api .../workflows/247980277/runsshows the last pull_request-triggered tests run was for another branch at 09:12Z; theworkflow_dispatchAPI is unavailable for these workflows). The CodeQL matrix IS green (Python/Go/JS-TS/actions analysis + Mergeable — 16 checks, all SUCCESS). The full test suite + Go tests are green locally against the exact commit (the receipts above —python3 -m pytest tests/ -q: 4314 passed / 34 skipped / 2 pre-existing SDK-pin failures;go test ./internal/config/: ok). The merge should wait for the tests workflow to dispatch (or the human verifies via the local receipts).