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-builtrender_dossier()/_render_tier()/_tier_verdict()are retired (render.py:6-7 docstring) — there is no parallel render path and no_tier_verdictproducer. -
verdict_foris the SOLE per-tierVERDICT:producer;_verdict_lineis its SOLE deterministic call site, invoked once per tier insidebuild_pkg_contextwhen the tier’spkgobject is bound. -
scripts/intel_package_runtime/verdicts.pyexports the single verdict-producer contractverdict_for(tier_name: str, rows: list, dark: bool, joins: list | None = None) -> str(verdicts.py:21). It returns the one-lineVERDICT: <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). -
joinsdata-flow + shape:joinsis 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 assembledtiersdict and passes the per-tierjoins_by_tierslice throughrender_package(envelope, joins_by_tier=...)so each tier’s_verdict_lineforwardsjoins=<that tier's joins>toverdict_for. The deterministicbuild_pkg_context(envelope)call leavesjoins_by_tier=None(render.py:138 default) so every tier renders withjoins=None— consistent with “heuristic verdicts never depend on joins”. A tier’s heuristic verdict NEVER depends onjoinsbeing 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’spkgobject (e.g. render.py:399, 408, 423, 427)._verdict_linereads the tier’srows+darkoff the envelope and returnsverdicts.verdict_for(name, rows, dark, joins=joins).joins_by_tieris threaded only on the overlay path (bluf.render_with_overlay, bluf.py:131, callsrender_package(envelope, joins_by_tier=joins_by_tier)); the deterministicrender_package(envelope)call leaves itNone. There is exactly one function that produces a tier verdict (verdict_for) and one deterministic call site (_verdict_lineinsidebuild_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 whosetc/test_caller_countis 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 reportsall 0 objective symbol(s) carry >=1 test callerrather than flagging — an empty objective set does not assert a coverage gap. A NON-empty objective set with notc==0row 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-rowartifact_suspectedboolean — set via the"ori_parse::" in calleepredicate at tiers.py:183-185 — flaggingsurprise_scoreedges whose callee resolved into a parser symbol (e.g..and_then()mis-resolving toori_parse::outcome::and_then) as a name-collision artifact, not a real surprise. The_verdict_surprisesheuristic READS thatartifact_suspectedflag 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 — theartifact_suspectedflag the surprises tier emits — consumed by verdicts.py, never duplicated. - churn (
_verdict_churn, verdicts.py:115) — rank the per-symbolhotspot_scorerows (alreadyORDER BY ... DESCfrom the tier query) and name the top churned symbol with itshotspot_score. - co-change (
_verdict_co_change, verdicts.py:127) — summarize theCommit TOUCHEDco-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 distinctcommunity_idvalues 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 = Falseparameter ongenerate(generate.py:122), threaded from the caller, NOT from insidebuild_package. Whencompose_blufis false (the default), the package is assembled and written exactly as today: every tier carries its heuristicverdict_forline via_verdict_line(... joins_by_tier=None), no overlay prompt is composed, andgeneratereturns its existingpackage_generated/package_generated_degradedexit-state. No LLM call occurs on the default path. - When the overlay is requested (and a dossier was built): the production
generateoverlay branch callsbluf.derive_joins(tiers)(bluf.py:27) to derive the cross-tier[JOIN]pairs from the assembleddossier.tiersdict (this[JOIN]-pair derivation is the overlay’s job, not the dossier-build’s), thenbluf.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) perscript-first.md §1, and returnsoverlay_prompt_pathin 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 throughrender_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 callsbluf.render_with_overlay, reachable via the CLIsplice-overlaysubcommand and thegenerate --compose-blufflag (__main__.py), exported fromapi.py(__all__), withscripts/intel_package_runtime/tests/test_overlay_production_path.pydriving the real two-pass flow (generate --compose-blufemitsoverlay_prompt_path, thensplice-overlayconsumes 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 onceresolver_fnis 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 injectsresolver_fnto 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 deterministicrender_package(envelope)(every tier still carries its heuristic verdict withjoins=None) andgenerateemits 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_foris 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 (owners-1c925ea6— this section) closes on that deterministic property alone — no LLM call, no runtime fixture, gates it. The existing unit-coverage artifact groundingverdict_fortotality + non-emptiness (including the dark-tier phrasing) isscripts/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 oververdict_forwith constructed row fixtures, no production resolver required. The production-path end-to-end assertion (the real CLI rendering a dossier-genrepackage.mdwith NO injectedresolver_fn) is sc-0012, owned bys-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_onedge. - 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_thenname-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_fordefault).
Spec References
scripts/intel_package_runtime/render.py(536 lines — the SINGLE Jinja2 dossier render path:render_packageat line 484 →build_pkg_contextat line 136 →_verdict_lineat line 121 →verdicts.verdict_for;_verdict_linereads each tier’srows+darkand forwards the per-tierjoins_by_tierslice.render_dossier/_render_tier/_tier_verdictare retired per the render.py:6-7 module docstring — no parallel render path)scripts/intel_package_runtime/verdicts.py(194 lines —verdict_forat line 21 is the SOLE per-tierVERDICT:producer;_HEURISTICSregistry at line 157 maps the 5 per-tier heuristics;_base_verdictat line 38 is the total dark / coverage-gaps / generic row-count fallback;_with_joinsat 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;generatecarries thecompose_bluf: bool = Falseopt-in;splice_overlayat line 202 is the production splice-back consumer callingbluf.render_with_overlay;_write_packagedispatchesrender_packagefor dossier envelopes andrender_mdfor the legacy counts-manifest path; thecompose_blufoverlay threads through without a new control-flow branch)scripts/intel_package_runtime/bluf.py(175 lines —derive_joinsat line 27 owns the cross-tier[JOIN]-pair derivation;compose_bluf_promptat line 75 writes$SCRATCH/verdict-prompt.md;render_with_overlayat line 131 splices the BLUF and threads joins throughrender_package(envelope, joins_by_tier=...))scripts/intel_package_runtime/envelope.py(build_dossierat line 221 returns{objective_symbols, tiers};EXIT_REASONSclosed frozenset at line 33 — NOT extended; deterministic path computes no joins)scripts/intel_package_runtime/tiers.py(select_surprisesat line 165 emits the per-rowartifact_suspectedboolean via the"ori_parse::" in calleepredicate at tiers.py:183-185 — the single artifact signal verdicts.py reads, not re-declared inverdicts.py).claude/rules/script-first.md §1(scripts compose prompts the LLM reads)intel-package-rebuild-notes.mdR1, R10intel-package-example.md §3.4(theori_parse::outcome::and_thensurprise-artifact case the heuristic pins)
Coherence with siblings
- Fills the
VERDICT:slot pinned by walking-skeleton (s-9948c200,intel-package-example.md §3.6verdict-line form). The single Jinja2 renderer (render.render_package) already consumesverdict_for’s output:build_pkg_contextbinds each tier’sverdictfield from_verdict_line→verdict_for, and the approvedtemplates/package.md.j2emits that one non-emptyVERDICT:line per tier. Theverdict_for(tier_name, rows, dark, joins=None) -> strcontract is the stable producer surface the renderer relies on. - Shares the
tiers.pytier registry from genre-inversion-tiers (s-1fe9a664);verdicts.pykeys its_HEURISTICSby 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 onverdict_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 singleverdict_for(tier_name, rows, dark, joins=None) -> strproducer (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-rowartifact_suspectedflag thatselect_surprisesemits (tiers.py:183-185), not a hardcoded combinator list inverdicts.py. The renderer callsverdict_forthroughrender._verdict_line(render.py:121) once per tier insidebuild_pkg_context; the retired_tier_verdict/_render_tierare gone, so there is one producer and one deterministic call site. On the deterministic pathjoinsstaysNone— verdicts never depend on it. - Opt-in LLM-authored BLUF +
[JOIN]overlay (bluf.py, OWNS the cross-tier[JOIN]-pair derivation): acompose_bluf: bool = Falseparameter ongenerate(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
generatepath: whencompose_blufis on and a dossier was built,bluf.derive_joinsderives the[JOIN]pairs from the assembleddossier.tiersdict andbluf.compose_bluf_promptwrites the prompt to scratch perscript-first.md §1;generatereturnsoverlay_prompt_pathin its exit-state. - SPLICE-BACK half — WIRED into a production consumer:
generate.splice_overlay(...)(generate.py:202) takes the LLM-emitted BLUF +[JOIN]response, callsbluf.render_with_overlayto validate non-empty + splice it in, and threads the derived joins throughrender_package(envelope, joins_by_tier=...)→build_pkg_context(..., joins_by_tier=...)→_verdict_line→verdict_for(..., joins=<joins>). It is consumer-callable via the CLIsplice-overlaysubcommand +generate --compose-blufflag (__main__.py) and exported fromapi.py(__all__).scripts/intel_package_runtime/tests/test_overlay_production_path.pydrives the real two-pass production flow (generate --compose-bluf→splice-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 newEXIT_REASONSmember, no rejection path.
- PROMPT-COMPOSITION half — WIRED into the production
- Verdict-required gate satisfied by construction:
verdict_foris total + non-empty over all 15 tiers, so sc-0003 (owners-1c925ea6) closes on that deterministic property alone — nodepends_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 (owners-aa802633). The gate’s negative + positive pins (verdict-not-from-rows rejected, surprise-artifact flags theori_parse::outcome::and_thencase, 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.