Skip to content

feat(llm-rubric): narrowed affected-context evaluation (closes #281) - #290

Merged
uda-lab-agent merged 3 commits into
mainfrom
issue-281-narrowed-context
Jul 5, 2026
Merged

feat(llm-rubric): narrowed affected-context evaluation (closes #281)#290
uda-lab-agent merged 3 commits into
mainfrom
issue-281-narrowed-context

Conversation

@uda-lab-agent

Copy link
Copy Markdown
Collaborator

Summary

Implements S5 of umbrella #277 (incremental audit trunk): closes #281.

A multi-file TargetSpec routed to llm-rubric — the narrowed affected context, (S1 changed set) ∩ (S3 rule scope), never the whole reference closure — is now assembled into a single prompt with per-file --- <path> --- headers, evaluated in one provider call per strategy iteration, under a token budget with deterministic lexicographic truncation and auditable evidence. multi_target_unsupported is replaced on this path only; the literal params.targets (≤5 entries) mechanism and the github / external backends keep the unchanged decline.

Design: docs/design/multi-target.md §2.2–2.3 and the new §9.12 (ratified S5 contract).

Per-criterion evidence

  1. Scoped rule + changed set over 3 files → one call, per-file headers, assembled-set evidence. _run_affected_context assembles and dispatches through the existing single-evaluation machinery (one provider call × strategy N). affected_context evidence lists included_files + tokens_estimated. Tests: TestCriterion1SingleCallPerFileHeaders (direct spec + full scope/changed path).
  2. Over-budget → deterministic lexicographic truncation, omitted files named. Budget precedence params.token_budget > dotenv GATE_KEEPER_TOKEN_BUDGET > 32000; estimate ceil(len/3.2). Truncation keeps a lexicographic prefix; dropped files are named in truncation_warning. Tests: TestCriterion2Truncation.
  3. Zero survivors → UNAVAILABLE, fail-closed, no provider call. affected_context_empty evidence; covers both over-budget lead file and all-unreadable sets. Tests: TestCriterion3ZeroSurvivors.
  4. Cache key uses the assembled-content hash. eval_cache._build_target_entries emits one __affected_context__ entry whose content_sha256 hashes the surviving assembled body (re-running the same deterministic assembler), so budget-driven truncation is reflected in the key. Tests: TestCriterion4CacheKey (incl. content-edit miss, truncation-change miss, integration hit → zero extra calls).
  5. Mixed target_kind — decided in §9.12.3. The dynamic path assembles every in-scope file without per-file kind gating (per-file kind inference is the heuristic §2.2 forbids; silent drops break the "no file without a truncation_warning" invariant). The run-level Deterministically short-circuit target_kind mismatch before invoking llm-rubric #178 precheck stays the only kind gate, unchanged and run-level (§9.7). Documented in the same PR (multi-target.md §9.12.3, §9.7). Tests: TestCriterion5MixedKind.
  6. Fixture: whole-closure vs narrowed token estimate. TestCriterion6WholeVsNarrowed runs the same scoped rule against the full closure and a narrowed changed set, asserting the narrowed tokens_estimated is materially lower.

Decisions worth noting

  • Mixed-kind (criterion 5): conservative, fail-closed, spec-aligned — assemble the author-declared scope in full, record exactly what was sent, never fabricate a per-file kind judgment or silently drop a file. Full rationale in multi-target.md §9.12.3.
  • params.targets preserved: a rule declaring the literal params.targets list handed a multi-file TargetSpec keeps multi_target_unsupported — the two axes never mix on one code path.

Verification

uv run pytest (1742 passed), uv run ruff check ., uv run ruff format --check ., uv run pyright (0 errors) all green. textlint clean on changed docs.

Docs

multi-target.md §9.12 + status pointers (§2.2, §9.7, §9.10), eval-cache.md §9, rule-ir.md token_budget row, cli-reference.md new "Narrowed affected-context assembly" section.

Closes #281. Part of umbrella #277.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wx8qUs1ipy6Nf82hu2eDhg

Generalize the multi-target prompt-assembly path so an engine-computed
dynamic file set — (S1 changed set) ∩ (S3 rule scope) — routed to
llm-rubric is assembled into a single narrowed prompt with per-file
headers, under a token budget, with deterministic lexicographic
truncation and auditable evidence. Replaces multi_target_unsupported on
this path only; the literal params.targets (≤5) mechanism and the
github/external backends keep the unchanged decline.

Implements S5 of umbrella #277 (incremental audit trunk), per
docs/design/multi-target.md §2.2-2.3 and the new §9.12.

- _assemble_affected_context / _run_affected_context in llm_rubric.py:
  read the effective set, order lexicographically, concatenate under
  `--- <path> ---` headers, evaluate in one provider call per strategy
  iteration via the existing single-evaluation machinery.
- Token budget precedence params.token_budget > dotenv
  GATE_KEEPER_TOKEN_BUDGET > 32000; estimate ceil(len/3.2). Over-budget
  truncates to a lexicographic prefix and names omitted files in
  truncation_warning; zero survivors fail closed to UNAVAILABLE with
  affected_context_empty (no provider call).
- eval-cache (#69 S2-B): the assembled-content hash is the target
  content hash — _build_target_entries emits one __affected_context__
  entry hashing the surviving assembled body, so budget-driven
  truncation is reflected in the key (criterion 4).
- Mixed target_kind (criterion 5 / §9.7): decided in §9.12.3 — the
  dynamic path assembles every in-scope file without per-file kind
  gating; the run-level #178 precheck stays the only kind gate.
- Docs: multi-target.md §9.12 + status pointers, eval-cache.md §9,
  rule-ir.md token_budget row, cli-reference.md.
- Tests: tests/test_affected_context.py covers all six acceptance
  criteria incl. a fixture demonstrating whole-closure vs narrowed token
  estimate; updated stale multi_target_unsupported assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx8qUs1ipy6Nf82hu2eDhg
@t-uda

t-uda commented Jul 5, 2026

Copy link
Copy Markdown
Owner

@codex please review this PR

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

gate-keeper dogfooding

rule status judgment primary_reason
rule-dogfooding-rules-L23 PASS pass The PR description body clearly describes the user-visible change by explaining that multi-file affected context is now assembled into one prompt with truncation and evidence behavior, while preservi...
rule-dogfooding-rules-L24 PASS pass The artifact is a PR description and it explicitly documents verification in a dedicated verification section with the commands run and their results.
rule-dogfooding-rules-L25 PASS pass This PR description does not hardcode any absolute filesystem location tied to a specific user, host username, devcontainer user, or workspace-root layout.

Tokens: in=7707 out=624, latency: 6965 ms, model: gpt-5.4, prompt_version: v7

See docs/dogfooding.md for the per-rule promotion path.

@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: 9334bbfedb

ℹ️ 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 src/gate_keeper/backends/eval_cache.py
…x P2 #290)

Codex P2: the S5 affected-context cache entry hashed only the included
assembled body, so a file that stays omitted under the same budget — when
added or edited — left the key unchanged and could hit an old entry,
serving stale affected_context / truncation_warning evidence that never
named it.

The omitted and unreadable files, and the effective token budget, surface
in the cached diagnostic's evidence but never enter the assembled body, so
they must participate in the key. The dynamic targets entry now also
carries the omitted files ({path, content_sha256} each), the unreadable
labels, and token_budget. _AssemblyResult exposes omitted_files (with
content) alongside the omitted-label list. Any change to the omitted /
unreadable set, an omitted file's content, or the budget (incl. a dotenv
GATE_KEEPER_TOKEN_BUDGET change that leaves survivors unchanged) now
misses and recomputes correct evidence.

Tests: test_omitted_file_change_changes_key,
test_budget_change_without_truncation_change_changes_key. Docs:
multi-target.md §9.12.4 and eval-cache.md §9 updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx8qUs1ipy6Nf82hu2eDhg
@uda-lab-agent
uda-lab-agent merged commit a512301 into main Jul 5, 2026
6 checks passed
uda-lab-agent added a commit that referenced this pull request Jul 5, 2026
…dicates (closes #291) (#292)

* fix(llm-rubric): decline instead of fabricating on cross-artifact predicates (closes #291)

When a rule's predicate ranges over artifacts absent from the rendered
target (e.g. "classify every module under src/" judged against only the
doc), prompt v7 emitted a confident pass/fail with fabricated within-target
grounding rather than declining. The #291 dogfood run missed real drift
(13 unclassified modules), false-flagged a correct `deferred` classification,
and false-failed two code-invariant rules against a doc target.

Fix direction (a): broaden the decline contract. v8 adds a second authorised
`unsupported` reason, `unsupported_reason: "cross_artifact_predicate"`, valid
for any rule (annotated or not). The model returns `unsupported` and names the
absent artifacts instead of guessing; the backend maps this to
`Status.UNSUPPORTED` with `evidence.kind=cross_artifact_predicate` and a
remediation pointing at the existing S5 remedy (`params.target_scope` /
multi-target, PR #290 / multi-target.md §9). Fail-closed: an explicit,
evidence-bearing non-verdict, never a silent pass.

Over-trigger guard: the instruction fires only when the absent artifacts are
essential to the predicate — a rule that merely names another file but is
decidable from the target is judged normally. Self-contained rules keep
judging pass/fail.

Aggregation (following the target-kind-mismatch precedent): a cross-artifact
decline counts as an ordinary `unsupported` vote under consensus, and is
conclusive without escalation under review/adaptive (a re-run against the same
insufficient target cannot recover it).

PROMPT_VERSION v7 -> v8 (eval-cache keys on it by design; old entries miss).

Tests: parser accepts the new shape; single-strategy maps to UNSUPPORTED for
annotated and unannotated rules; self-contained pass is not declined; consensus
counts the decline as an unsupported vote; stray reason on pass is dropped. Two
bench fixtures model the #291 scenario (a doc table + a src-tree completeness
rule -> unsupported; a self-contained companion -> pass).

Docs: docs/semantic-rules.md (cross-artifact predicates) and docs/llm-rubric.md
(decline verdicts) document the behavior and the target_scope remedy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx8qUs1ipy6Nf82hu2eDhg

* fix(eval/bench): count cross_artifact_predicate evidence as telemetry-bearing

Codex P2 (PR #292): a bench entry expecting `unsupported` that receives the new
`cross_artifact_predicate` decline evidence was marked PASS, but the telemetry
loop only recognised `llm_judgment` / `target_kind_mismatch` /
`llm_quote_fabrication`, so those #291 fixtures reported tokens_in/out=0,
latency_ms=0, and model/prompt_version=None despite the evidence carrying those
fields. Add `cross_artifact_predicate` to `_TELEMETRY_BEARING_KINDS` and cover it
with a bench test mirroring the target-kind-mismatch telemetry test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx8qUs1ipy6Nf82hu2eDhg

---------

Co-authored-by: uda-lab-agent <uda-lab-agent@hermes-engineering.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@t-uda
t-uda deleted the issue-281-narrowed-context branch July 5, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

llm-rubric: narrowed affected-context evaluation (dynamic multi-file assembly)

2 participants