100%

Goal

  • Land the canonical Jinja2 template + rewrite render.py to render it through one renderer.
  • Per-section DIG DEEPER command blocks parameterized by the objective symbol set (R5).
  • Dense and machine-readable (R2) — no mermaid, no rendered diagrams.

Implementation Sketch

  • Install scripts/intel_package_runtime/templates/package.md.j2 from the approved repo-root ./intel-package-template.md.j2 (target-kind tier guards, verdict slots, dark-tier lines, DIG DEEPER blocks). The approved template’s tier guards use an underscored/abbreviated vocabulary — {% if 'depgraph' in active_tiers %}, neighbors, hidden_callers, cochange, community, test_posture, bug_cluster, missing_abstraction, plan_ownership, prior_art, defect, plus surprises / churn / sentiment — which is NOT the tiers.py REGISTRY SSOT vocabulary (dependency-graph, semantic-neighbors, hidden-callers, co-change, community-span, test-posture, bug-cluster, missing-abstraction, plan-ownership, prior-art). Binding active_tiers straight from the registry hands the template hyphenated keys that no {% if %} guard matches, so every analytic tier guard is false and the package renders only BLUF/terrain/gaps boilerplate with zero tiers. render.py owns the reconciliation seam below.
  • render.py binds the pkg context object (target / graph_state / active_tiers / objective / per-tier rows+verdict+dark) AND owns an explicit registry-key -> template-guard-key normalization map (e.g. dependency-graph -> depgraph, semantic-neighbors -> neighbors, hidden-callers -> hidden_callers, co-change -> cochange, community-span -> community, test-posture -> test_posture, bug-cluster -> bug_cluster, missing-abstraction -> missing_abstraction, plan-ownership -> plan_ownership, prior-art -> prior_art, with surprises / churn / sentiment / terrain identity-mapped). active_tiers is bound to the registry tier names normalized through this map. This map is the single seam between the tiers.py registry SSOT and the template guard vocabulary; nothing else translates tier keys.
  • active_tiers default = the FULL registry tier set (normalized to the template’s guard vocabulary). template-render renders every registry tier and is independently completable on its own. The per-target-kind subset is a later narrowing: target-kind-tiers (s-aa802633, the lexorank successor) consumes the registry owning_target_kinds and NARROWS active_tiers per target kind. That is a forward-handoff in lexorank order — the successor narrows what this node bound to the full set — NOT a depends_on edge to a successor (a backward/diamond edge would violate INV-19). template-render requires nothing from target-kind-tiers to complete.
  • render.py calls verdicts.verdict_for(name, rows, dark, joins) per tier and feeds the returned VERDICT: line into that tier’s Jinja2 per-tier context, honoring the s-1c925ea6 (verdict-synthesis) forward-declared contract that verdict_for is the stable producer this renderer swap consumes. The opt-in BLUF/[JOIN] overlay (bluf.render_with_overlay) threads joins_by_tier through the same render path, so deterministic (joins=None) and overlay renders share one verdict source.
  • The rewrite RE-POINTS the existing call sites onto the Jinja2 renderer and RETIRES the hand-built path: generate.py:_write_package (currently dispatches render_dossier at line 107) and bluf.render_with_overlay (currently imports and calls render_dossier at lines 23/142-143) both target the new Jinja2 render entry point, and the hand-built render_dossier() / _render_tier() / _tier_dig_deeper() are removed. One renderer, no parallel producers — the one-producer discipline genre-inversion-tiers + verdict-synthesis already established.
  • DIG DEEPER commands interpolate the objective seeds into =~ '.*(a|b|c).*' regex predicates so each rendered package’s commands run as-is. The pkg objective context binds BOTH objective.symbols_csv (template lines 83/119/131/163/195) AND objective.surface_csv (template lines 105/153) — surface_csv is required by the hidden-callers (3.3) and test-posture (3.7) DIG DEEPER cypher, so binding only symbols_csv leaves those tiers’ commands unrenderable. Both seeds are produced by a predicate builder that escapes regex metacharacters in each symbol and emits a false-matching predicate when the objective set is empty (mirroring the tiers.py:_name_set_predicate empty -> false convention so an empty objective never produces a match-everything regex).

Spec References

  • ./intel-package-template.md.j2 (the approved template, repo-root-relative; its own header self-describes the install destination as scripts/intel_package_runtime/templates/package.md.j2)
  • scripts/intel_package_runtime/render.py
  • scripts/intel_package_runtime/tiers.py (REGISTRY — the tier-name SSOT the normalization map translates from)
  • scripts/intel_package_runtime/verdicts.py (verdict_for contract fed into the per-tier Jinja2 context)
  • scripts/intel_package_runtime/generate.py + scripts/intel_package_runtime/bluf.py (the two render call sites re-pointed)
  • intel-package-rebuild-notes.md R2, R5

Work Items

  • Install templates/package.md.j2 from the approved repo-root ./intel-package-template.md.j2 (underscored guard keys, verdict slots, dark lines, DIG DEEPER blocks) with ONE documented deviation: the installed template strips the static VERDICT: prefix on the 9 per-tier verdict lines plus the 2 [JOIN] lines, emitting > {{ tier.verdict }} where the approved form has > VERDICT: {{ tier.verdict }}. The strip is required de-dup, not a divergence from intent: verdicts.verdict_for (verdicts.py:23) is the single VERDICT producer and already returns a VERDICT:-prefixed string, so the approved static prefix would render VERDICT: VERDICT:. Cross-ref verdict-synthesis (s-1c925ea6) as the one-producer contract; a render test asserts exactly one VERDICT: per tier line.
  • Rewrite render.py to bind the pkg context and render the template, including: (a) the registry-key -> template-guard-key normalization map (the single registry-to-template-vocabulary seam) with active_tiers defaulting to the FULL normalized registry set (target-kind-tiers s-aa802633 narrows it later via forward-handoff, never a depends_on edge); (b) per-tier verdicts.verdict_for(name, rows, dark, joins) calls feeding the VERDICT: line into the Jinja2 per-tier context per the s-1c925ea6 forward-declared contract; (c) re-pointing generate.py:_write_package (line 107) and bluf.render_with_overlay (lines 23/142-143) onto the new Jinja2 entry point and retiring the hand-built render_dossier() / _render_tier() / _tier_dig_deeper() — one renderer, no parallel paths; (d) constructing the Jinja2 Environment with undefined=StrictUndefined so any template field the pkg context fails to bind raises UndefinedError at render time instead of silently emitting an empty string — the mechanical guard that the adapter populates EVERY field the approved template references.
  • DIG DEEPER command interpolation: bind BOTH objective.symbols_csv and objective.surface_csv into the pkg context via a predicate builder that escapes regex metacharacters per symbol and emits a false-matching predicate for an empty objective set.

Test Pins — golden coverage owned by s-15e599fa (referenced, not duplicated)

The matrix lives in the tests section (s-15e599fa); this section references the pins its StrictUndefined adapter must satisfy (ordered after this node by the lexorank chain, NOT a depends_on edge):

  • POSITIVE: rendering the approved package.md.j2 (with undefined=StrictUndefined) over a FULLY-populated pkg fixture — pkg.consumed_at, bluf, gaps, entry_points, target.repo, active_tiers, objective.symbols_csv + objective.surface_csv, and every per-tier rows/verdict/dark object — succeeds with no UndefinedError. This pins that the adapter binds every field the approved template references.
  • NEGATIVE: a pkg fixture missing ANY template-referenced field raises UndefinedError under StrictUndefined — the renderer fails loud on an unbound field rather than emitting a silently-empty dossier.
  • POSITIVE: active_tiers bound to the full normalized registry set renders every analytic tier guard true (no tier silently dropped by a registry-key vs template-guard-key mismatch).

sc-0004 verification — the four predicates this section’s StrictUndefined adapter must satisfy are ALREADY pinned by existing test_render.py cases (matrix owned by s-15e599fa; named here, not duplicated):

  • StrictUndefined unbound-field rejection: test_strict_undefined_negative_missing_field_raises + test_strict_undefined_negative_missing_nested_field_raises.
  • Every-tier-guard rendering on the per-kind narrowed registry set: test_active_tiers_bug_set_renders_every_bug_analytic_guard_true (+ test_active_tiers_is_per_kind_narrowed_set).
  • DIG DEEPER bash-block presence per rendered tier: the render golden test_generate_writes_dossier_through_jinja2 + the walking-skeleton golden test_positive_adjacency_verdict_and_dig_deeper.
  • Empty-objective never-match predicate (empty objective set -> false-matching regex, never match-everything): test_objective_csv_empty_is_never_matching + test_empty_objective_seeds_render_never_matching_predicate; regex metacharacter escaping test_objective_csv_escapes_metacharacters.

Forward-refs — owned by sibling sections, NOT in-section gaps

These are advisory forward-handoffs surfaced during review; template-render does NOT fix them:

  • The per-kind active_tiers selection (active_tiers(kind) = registry inversion of owning_target_kinds) is owned by target-kind-tiers (s-aa802633, w-605bd83b). template-render binds the FULL normalized registry set via active_tiers_from(tiers.keys()) (render.py:66); the per-kind narrowing is the successor’s deliverable.
  • The bug-only defect guard-key is delivered HERE: the template guard {% if 'defect' in active_tiers %} (package.md.j2:41) + the render.py-bound defect block (render.py:388, None-guarded for non-bug). But defect is NOT a registry tier and _REGISTRY_TO_GUARD never emits it, so it is absent from the default full-set active_tiers — the approved ”## 1. The defect” section (intel-package-example.md:31, bug-only) renders ONLY once s-aa802633’s per-kind selection adds defect to the bug active set. s-aa802633’s current per-kind matrix OMITS defect from bug; closing the approved-example conformance requires s-aa802633 to include it (cross-section, owned by the successor, NOT this section).
  • Dynamic target.repo binding (vs the hardcoded 'ori' at render.py:366) is owned by target-kind-tiers (s-aa802633) kind/target metadata.
  • UndefinedError structured-failure handling at the generate error path is owned by the generate error path, not this renderer swap.