100%

Goal

Add the verdict layer that distinguishes intel from a row-dump (R1) — hybrid authoring per the approved design (R10): heuristic verdicts computed by the runtime for every tier (the deterministic, always-non-empty default), with the BLUF + cross-tier [JOIN] lines as an opt-in LLM-authored overlay that never gates whether a tier carries a verdict.

Verdict-producer ownership — one producer, no parallel paths

  • The dossier renders through the single Jinja2 path render.render_package()render.build_pkg_context()render._verdict_line() (render.py:121-129) → verdicts.verdict_for() (verdicts.py:21). The hand-built render_dossier() / _render_tier() / _tier_verdict() are retired (render.py:6-7 docstring) — there is no parallel render path and no _tier_verdict producer.

  • verdict_for is the SOLE per-tier VERDICT: producer; _verdict_line is its SOLE deterministic call site, invoked once per tier inside build_pkg_context when the tier’s pkg object is bound.

  • scripts/intel_package_runtime/verdicts.py exports the single verdict-producer contract verdict_for(tier_name: str, rows: list, dark: bool, joins: list | None = None) -> str (verdicts.py:21). It returns the one-line VERDICT: <assessment> string for any tier, dispatching to a per-tier heuristic (_HEURISTICS, verdicts.py:157) and falling back to the total dark / coverage-gaps / generic row-count phrasing in _base_verdict (verdicts.py:38) for tiers without a dedicated heuristic. The returned string is always non-empty (the dark / generic-row-count branches guarantee it).

  • joins data-flow + shape: joins is the list of cross-tier join records the dossier’s [JOIN] lines carry (e.g. test-posture x churn, surprise x community-span). Shape: [{"tiers": [<tier_name>, <tier_name>], "note": str}]. The [JOIN]-pair derivation is OWNED BY the opt-in LLM overlay (bluf.derive_joins, bluf.py:27), NOT by the deterministic dossier path: the overlay computes the cross-tier [JOIN] records from the assembled tiers dict and passes the per-tier joins_by_tier slice through render_package(envelope, joins_by_tier=...) so each tier’s _verdict_line forwards joins=<that tier's joins> to verdict_for. The deterministic build_pkg_context(envelope) call leaves joins_by_tier=None (render.py:138 default) so every tier renders with joins=None — consistent with “heuristic verdicts never depend on joins”. A tier’s heuristic verdict NEVER depends on joins being non-None; verdicts._with_joins (verdicts.py:67) appends the [JOIN] note only when present, never gating the base verdict.

  • build_pkg_context (render.py:136) is the SINGLE place a tier’s verdict is computed: it calls _verdict_line(tiers, <name>, joins_by_tier) (render.py:121) once per analytic tier when binding that tier’s pkg object (e.g. render.py:399, 408, 423, 427). _verdict_line reads the tier’s rows + dark off the envelope and returns verdicts.verdict_for(name, rows, dark, joins=joins). joins_by_tier is threaded only on the overlay path (bluf.render_with_overlay, bluf.py:131, calls render_package(envelope, joins_by_tier=joins_by_tier)); the deterministic render_package(envelope) call leaves it None. There is exactly one function that produces a tier verdict (verdict_for) and one deterministic call site (_verdict_line inside build_pkg_context). No second producer coexists.

Heuristic verdicts (the deterministic default)

verdicts.py carries per-tier HEURISTIC functions (registered in _HEURISTICS, verdicts.py:157), each consuming the tier’s own rows from the dossier (build_dossier in envelope.py:221 returns {objective_symbols, tiers: {<name>: {rows, dark}}}; _verdict_line reads rows + dark off each tier before calling the heuristic):

  • test-posture (_verdict_test_posture, verdicts.py:90) — zero-coverage detection: it counts the objective rows whose tc / test_caller_count is exactly 0 and, when ANY such row exists, FLAGS the tier (N/M objective symbol(s) have ZERO test callers ... coverage gap to close, naming a sample). When the objective row set is empty, the zero-coverage filter is empty, so the heuristic reports all 0 objective symbol(s) carry >=1 test caller rather than flagging — an empty objective set does not assert a coverage gap. A NON-empty objective set with no tc==0 row is the “coverage present” case; the FLAG fires only on a real zero-caller row.
  • surprises (_verdict_surprises, verdicts.py:105) — surprise call-resolution-artifact detection: select_surprises (tiers.py:165) emits a per-row artifact_suspected boolean — set via the "ori_parse::" in callee predicate at tiers.py:183-185 — flagging surprise_score edges whose callee resolved into a parser symbol (e.g. .and_then() mis-resolving to ori_parse::outcome::and_then) as a name-collision artifact, not a real surprise. The _verdict_surprises heuristic READS that artifact_suspected flag off each row and phrases the verdict around it (N surprise edge(s); M flagged as call-resolution artifacts ... K genuine to trace); it NEVER re-declares its own combinator-name list. The artifact signal has one source — the artifact_suspected flag the surprises tier emits — consumed by verdicts.py, never duplicated.
  • churn (_verdict_churn, verdicts.py:115) — rank the per-symbol hotspot_score rows (already ORDER BY ... DESC from the tier query) and name the top churned symbol with its hotspot_score.
  • co-change (_verdict_co_change, verdicts.py:127) — summarize the Commit TOUCHED co-change set (which symbols move together) and name the top symbol with its shared-commit count.
  • community-span (_verdict_community_span, verdicts.py:139) — cross-community detection: count the distinct community_id values across rows and FLAG a cross-community contract surface when more than one community spans the objective set.

verdict_for (verdicts.py:21) routes a tier name to its _HEURISTICS entry when one exists; every other tier — and any dark tier, and the coverage-gaps meta-tier — takes the total _base_verdict fallback (verdicts.py:38). Because the fallback is total, verdict_for is row-grounded and non-empty for all 15 tiers with zero LLM dependency — the verdict-required property holds on the deterministic path alone.

LLM-authored BLUF + [JOIN] overlay (opt-in; graceful-degrade preserved)

Path chosen: the heuristic verdicts are the always-non-empty default; the LLM-authored BLUF + cross-tier [JOIN] lines are an explicit opt-in overlay. This keeps generate.py’s one-shot graceful-degrade contract intact — no new control-flow branch is added to the assembly path, no LLM-failure rejection path is introduced, and envelope.EXIT_REASONS (envelope.py:33, closed frozenset) is NOT extended.

  • The overlay is gated behind an explicit compose_bluf: bool = False parameter on generate (generate.py:122), threaded from the caller, NOT from inside build_package. When compose_bluf is false (the default), the package is assembled and written exactly as today: every tier carries its heuristic verdict_for line via _verdict_line(... joins_by_tier=None), no overlay prompt is composed, and generate returns its existing package_generated / package_generated_degraded exit-state. No LLM call occurs on the default path.
  • When the overlay is requested (and a dossier was built): the production generate overlay branch calls bluf.derive_joins(tiers) (bluf.py:27) to derive the cross-tier [JOIN] pairs from the assembled dossier.tiers dict (this [JOIN]-pair derivation is the overlay’s job, not the dossier-build’s), then bluf.compose_bluf_prompt(envelope, joins, scratch) (bluf.py:75) writes the BLUF + [JOIN] prompt with the resolved tier rows + derived join pairs to scratch ($SCRATCH/verdict-prompt.md) per script-first.md §1, and returns overlay_prompt_path in the exit-state. The SPLICE-BACK that consumes the LLM-emitted BLUF + per-join text — bluf.render_with_overlay(envelope, llm_text, joins_by_tier) (bluf.py:131), validating non-empty + threading the derived joins through render_package(envelope, joins_by_tier=joins_by_tier)build_pkg_context(..., joins_by_tier=...)_verdict_line(... joins_by_tier)verdict_for(..., joins=<that tier's joins>) — is a production-consumable surface at HEAD: generate.splice_overlay(...) (generate.py:202) is the consumer that calls bluf.render_with_overlay, reachable via the CLI splice-overlay subcommand and the generate --compose-bluf flag (__main__.py), exported from api.py (__all__), with scripts/intel_package_runtime/tests/test_overlay_production_path.py driving the real two-pass flow (generate --compose-bluf emits overlay_prompt_path, then splice-overlay consumes the LLM text and re-renders). The overlay splice-back MECHANISM (function + CLI + api export + two-pass test) is wired; the UPSTREAM dossier-build it decorates reaches a real CLI/heal caller only once resolver_fn is threaded through _cmd_generate / heal._regenerate_and_persist — the explicit deliverable of sibling section target-kind-tiers (s-aa802633, sc-0012/w-18edfbb5; surfaces.py:165). The two-pass test injects resolver_fn to build the dossier; full no-seam production-path closure (real CLI → dossier → overlay) is sc-0012, owned by s-aa802633, NOT this section. A missing or empty LLM response is a NON-fatal overlay miss: the splice-back falls back to the deterministic render_package(envelope) (every tier still carries its heuristic verdict with joins=None) and generate emits the existing exit-state — it never rejects the package and never invents a new exit_reason.

The verdict-required property is therefore enforced by the deterministic heuristic default (every tier always carries a verdict_for line), not by an LLM gate — so the gate can never reject on a missing LLM call.

Verdict-required gate

A rendered tier with no verdict is the failure the gate forbids — the contract that makes this body not the old counts-manifest envelope.

  • Work item w-2e1c1210’s verdict-required gate closes BY CONSTRUCTION: verdict_for is total + non-empty over all 15 tiers (the dark / generic-row-count fallback is total, so every tier name routes to a non-empty string). sc-0003 (owner s-1c925ea6 — this section) closes on that deterministic property alone — no LLM call, no runtime fixture, gates it. The existing unit-coverage artifact grounding verdict_for totality + non-emptiness (including the dark-tier phrasing) is scripts/intel_package_runtime/tests/test_verdicts.py; it pairs with the downstream s-15e599fa pins (the negative + positive fixtures below) as the by-construction evidence.
  • sc-0003 is scoped to verdict_for() heuristic correctness — row-grounded, non-empty synthesis per tier, plus the surprise-artifact flag — which is unit-testable directly over verdict_for with constructed row fixtures, no production resolver required. The production-path end-to-end assertion (the real CLI rendering a dossier-genre package.md with NO injected resolver_fn) is sc-0012, owned by s-aa802633, NOT sc-0003. This section does not claim the production-entry-point layer; it claims the heuristic-correctness layer that sc-0012 then exercises end-to-end.
  • The negative + positive fixtures owned by the tests section (s-15e599fa) are the DOWNSTREAM test artifact that pins the property; they are ordered after this section by the lexorank chain (verdict-synthesis -> tests), NOT a depends_on edge.
  • sc-0003 does not require depends_on: ; adding one would create a diamond against the strict-linear-DAG (INV-19).

Test pins — owned by s-15e599fa (referenced, not duplicated)

The matrix lives in the tests section (s-15e599fa, work item w-1f81704d); this section does not re-declare it. The negative + positive pins that gate sc-0003 are:

  • a verdict-not-derived-from-rows fixture is REJECTED (a verdict string that ignores the tier’s rows fails the row-grounded assertion).
  • the surprise-artifact heuristic POSITIVELY flags the ori_parse::outcome::and_then name-collision case from the worked example (intel-package-example.md §3.4).
  • every tier’s verdict is non-empty AND row-grounded (the deterministic verdict_for default).

Spec References

  • scripts/intel_package_runtime/render.py (536 lines — the SINGLE Jinja2 dossier render path: render_package at line 484 → build_pkg_context at line 136 → _verdict_line at line 121 → verdicts.verdict_for; _verdict_line reads each tier’s rows + dark and forwards the per-tier joins_by_tier slice. render_dossier / _render_tier / _tier_verdict are retired per the render.py:6-7 module docstring — no parallel render path)
  • scripts/intel_package_runtime/verdicts.py (194 lines — verdict_for at line 21 is the SOLE per-tier VERDICT: producer; _HEURISTICS registry at line 157 maps the 5 per-tier heuristics; _base_verdict at line 38 is the total dark / coverage-gaps / generic row-count fallback; _with_joins at line 67 appends overlay [JOIN] notes without gating the base verdict)
  • scripts/intel_package_runtime/generate.py (244 lines — the one-shot graceful-degrade compose entry; generate carries the compose_bluf: bool = False opt-in; splice_overlay at line 202 is the production splice-back consumer calling bluf.render_with_overlay; _write_package dispatches render_package for dossier envelopes and render_md for the legacy counts-manifest path; the compose_bluf overlay threads through without a new control-flow branch)
  • scripts/intel_package_runtime/bluf.py (175 lines — derive_joins at line 27 owns the cross-tier [JOIN]-pair derivation; compose_bluf_prompt at line 75 writes $SCRATCH/verdict-prompt.md; render_with_overlay at line 131 splices the BLUF and threads joins through render_package(envelope, joins_by_tier=...))
  • scripts/intel_package_runtime/envelope.py (build_dossier at line 221 returns {objective_symbols, tiers}; EXIT_REASONS closed frozenset at line 33 — NOT extended; deterministic path computes no joins)
  • scripts/intel_package_runtime/tiers.py (select_surprises at line 165 emits the per-row artifact_suspected boolean via the "ori_parse::" in callee predicate at tiers.py:183-185 — the single artifact signal verdicts.py reads, not re-declared in verdicts.py)
  • .claude/rules/script-first.md §1 (scripts compose prompts the LLM reads)
  • intel-package-rebuild-notes.md R1, R10
  • intel-package-example.md §3.4 (the ori_parse::outcome::and_then surprise-artifact case the heuristic pins)

Coherence with siblings

  • Fills the VERDICT: slot pinned by walking-skeleton (s-9948c200, intel-package-example.md §3.6 verdict-line form). The single Jinja2 renderer (render.render_package) already consumes verdict_for’s output: build_pkg_context binds each tier’s verdict field from _verdict_lineverdict_for, and the approved templates/package.md.j2 emits that one non-empty VERDICT: line per tier. The verdict_for(tier_name, rows, dark, joins=None) -> str contract is the stable producer surface the renderer relies on.
  • Shares the tiers.py tier registry from genre-inversion-tiers (s-1fe9a664); verdicts.py keys its _HEURISTICS by the registry’s tier names, declaring no parallel tier vocabulary.
  • depends_on: [] is correct under the INV-19 strict-linear-DAG: the lexorank chain orders walking-skeleton -> genre-inversion-tiers -> verdict-synthesis -> tests, sc-0003 closes by construction on verdict_for’s totality, and adding a diamond edge (e.g. verdict-synthesis -> tests) would violate INV-19 (rationale: plan.json INV-19 + Verdict-required gate above).

Work Items

  • scripts/intel_package_runtime/verdicts.py — the single verdict_for(tier_name, rows, dark, joins=None) -> str producer (verdicts.py:21) with per-tier heuristics in _HEURISTICS (test-posture, surprises/artifact, churn, co-change, community-span) plus the total dark / coverage-gaps / row-count fallback in _base_verdict. The surprise-artifact heuristic reads the per-row artifact_suspected flag that select_surprises emits (tiers.py:183-185), not a hardcoded combinator list in verdicts.py. The renderer calls verdict_for through render._verdict_line (render.py:121) once per tier inside build_pkg_context; the retired _tier_verdict/_render_tier are gone, so there is one producer and one deterministic call site. On the deterministic path joins stays None — verdicts never depend on it.
  • Opt-in LLM-authored BLUF + [JOIN] overlay (bluf.py, OWNS the cross-tier [JOIN]-pair derivation): a compose_bluf: bool = False parameter on generate (default off keeps the one-shot graceful-degrade path unchanged). DELIVERED — both halves wired into the production entry point at HEAD:
    • PROMPT-COMPOSITION half — WIRED into the production generate path: when compose_bluf is on and a dossier was built, bluf.derive_joins derives the [JOIN] pairs from the assembled dossier.tiers dict and bluf.compose_bluf_prompt writes the prompt to scratch per script-first.md §1; generate returns overlay_prompt_path in its exit-state.
    • SPLICE-BACK half — WIRED into a production consumer: generate.splice_overlay(...) (generate.py:202) takes the LLM-emitted BLUF + [JOIN] response, calls bluf.render_with_overlay to validate non-empty + splice it in, and threads the derived joins through render_package(envelope, joins_by_tier=...)build_pkg_context(..., joins_by_tier=...)_verdict_lineverdict_for(..., joins=<joins>). It is consumer-callable via the CLI splice-overlay subcommand + generate --compose-bluf flag (__main__.py) and exported from api.py (__all__). scripts/intel_package_runtime/tests/test_overlay_production_path.py drives the real two-pass production flow (generate --compose-blufsplice-overlay), exercising the REAL entry point, not test-injection only.
    • The deterministic render_package(envelope) call passes no joins (joins_by_tier=None). An empty/missing LLM response falls back to the deterministic heuristic verdicts and keeps the existing exit-state — no new EXIT_REASONS member, no rejection path.
  • Verdict-required gate satisfied by construction: verdict_for is total + non-empty over all 15 tiers, so sc-0003 (owner s-1c925ea6) closes on that deterministic property alone — no depends_on: edge (a diamond would violate INV-19). sc-0003 is the heuristic-correctness layer; the production-entry-point end-to-end assertion is sc-0012 (owner s-aa802633). The gate’s negative + positive pins (verdict-not-from-rows rejected, surprise-artifact flags the ori_parse::outcome::and_then case, every tier non-empty + row-grounded) are owned by s-15e599fa per the Test pins section above; they are the downstream test artifact ordered by the lexorank chain (verdict-synthesis -> tests), not a dependency edge.