Skip to content

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

Merged
uda-lab-agent merged 2 commits into
mainfrom
issue-291-decline-not-fabricate
Jul 5, 2026
Merged

fix(llm-rubric): decline instead of fabricating on cross-artifact predicates (closes #291)#292
uda-lab-agent merged 2 commits into
mainfrom
issue-291-decline-not-fabricate

Conversation

@uda-lab-agent

Copy link
Copy Markdown
Collaborator

What & why

Closes #291 (dogfood finding on trunk #63 — semantic rubric quality).

When an llm-rubric rule's predicate ranges over artifacts not present in the rendered target (e.g. "the scaffold-status table should classify every module under src/", judged against only the doc), prompt v7 emitted a confident pass/fail with fabricated within-target grounding instead of declining. In the #291 live run against yomotsusaka's repo-rules.md (gpt-5.4), this:

  • missed the real drift — 13 modules absent from the table (the dominant, actual violation) were never mentioned, because the backend never sees the src/ tree;
  • false-positived on the reason it did give — flagged a legitimately deferred stub (transfer.py) as "already implemented";
  • false-failed two source-code-invariant rules (L163, L164) judged against a doc target they cannot ground.

Fix — direction (a): decline rather than fabricate

Extends the existing unsupported decline contract. Before v8 the only authorised decline was the target-kind-mismatch case (gated on a target_kind annotation). v8 adds a second reason discriminator:

"unsupported_reason": "target_kind_mismatch" | "cross_artifact_predicate" | null

cross_artifact_predicate is valid for any rule (annotated or not): the model returns unsupported and names the absent artifacts in primary_reason instead of guessing. The backend maps it 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, docs/design/multi-target.md §9). Direction (b) already shipped; this fix points authors at it rather than rebuilding it.

Fail-closed: UNSUPPORTED is a non-pass status — 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 itself is judged normally. Encoded in the prompt wording and tested on both sides.

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 v7v8 (the eval-cache keys on it by design, so old entries correctly miss).

Before / after (live, gpt-5.4)

Before (v7, #291 verdict against the doc): status: fail with a fabricated reason ("transfer.py … already implements its MVP responsibility") — wrong, and silent about the 13 missing modules.

After (v8, #291-shaped bench fixture): status: unsupported

Determining whether the scaffold-status table covers every module under src/pkg/ excluding __init__.py requires seeing the actual src/pkg/ source tree, which is not included above.

Validation

  • uv run pytest — 1751 passed (7 new TestCrossArtifactPredicateDecline tests + updated v7→v8 assertions).
  • uv run ruff check ., uv run ruff format --check ., uv run pyright — all green.
  • textlint on changed docs — exit 0.
  • Live bench (gpt-5.4, prompt v8), 5-entry slice, reproducibility=2: 5/5 correct, avg reproducibility 1.00.
    • completeness-06-cross-artifact-src-treeunsupported (new decline reproduces on the real model)
    • completeness-07-self-contained-doc-headerpass (over-trigger guard holds on the same target)
    • clarity-01 → pass, clarity-02 → fail (no regression on grounded rules)
    • target-kind-mismatch-01 → unsupported (existing decline path intact)
    • API cost: ~12k input / ~800 output tokens across ~11 calls (single-digit cents).

Files

  • src/gate_keeper/backends/llm_rubric.py — schema field + parser, both prompt templates (v8), decline helpers, single/consensus/review wiring, adaptive docstring.
  • src/gate_keeper/bench.py — recognise cross_artifact_predicate evidence for expected-unsupported entries.
  • tests/fixtures/semantic/ — two llm-rubric: completeness/cross-artifact rules get fabricated evidence + missed drift (dogfood, #63) #291-modeled fixtures + target.
  • docs/semantic-rules.md, docs/llm-rubric.md — cross-artifact-predicate guidance + target_scope remedy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wx8qUs1ipy6Nf82hu2eDhg

…dicates (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
@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 explains the user-visible behavioral change: the system now declines unsupported cross-artifact rubric judgments instead of fabricating grounded pass/fail verdicts.
rule-dogfooding-rules-L24 PASS pass The PR description includes an explicit validation section with test commands, results, and live verification details.
rule-dogfooding-rules-L25 PASS pass The PR description does not hardcode any absolute filesystem locations tied to a specific user, username, or workspace root.

Tokens: in=9546 out=615, latency: 7306 ms, model: gpt-5.4, prompt_version: v8

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: 948d4c25f0

ℹ️ 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/bench.py
…-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
@uda-lab-agent
uda-lab-agent merged commit 2e49f90 into main Jul 5, 2026
6 checks passed
@uda-lab-agent
uda-lab-agent deleted the issue-291-decline-not-fabricate branch July 5, 2026 14:09
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: completeness/cross-artifact rules get fabricated evidence + missed drift (dogfood, #63)

2 participants