Skip to content

Surface the model's cut-set in finding detail; realign shadow would-act counts to the typed decision - #311

Merged
thejefflarson merged 1 commit into
mainfrom
thejefflarson/jef-674-surface-the-models-cut-set-in-finding-detail-realign-shadow
Aug 1, 2026
Merged

Surface the model's cut-set in finding detail; realign shadow would-act counts to the typed decision#311
thejefflarson merged 1 commit into
mainfrom
thejefflarson/jef-674-surface-the-models-cut-set-in-finding-detail-realign-shadow

Conversation

@thejefflarson

Copy link
Copy Markdown
Owner

Summary

Finding detail (ADR-0034): CutBlock (engine/web/src/findings/detail.jsx) no longer renders the proposed cut as a single opaque signature string. It now renders the model's actual cut-set — one line per contained node: the fenced node key, a fixed mechanism string (ProposedAction::describe, never model/untrusted text), the entry-vs-downstream role, and an advisory blast-radius note (recomputed the same way the model's own menu line did, via a new shared incident::cut_blast_note helper). Honest empty states:

  • attack with an empty cut-set → explicit "attack, no cut warranted" (a valid ADR-0034 D1 decision, not an error).
  • uncertain → NEVER a green all-clear; a distinct caution line.
  • No incident decision made yet for the entry → "awaiting judgement".

report.rs (JEF-143 aggregation, JEF-674): would-act classification is now keyed on the TYPED Decision::Incident (assessment + cuts) in effect at each Breach line's timestamp, never on the Breach line's own verdict PROSE (the old verdict_would_act string-prefix match is deleted). A Breach line reading "exploitable" with no backing Incident line (a pre-ADR-0034 journal) no longer inflates the would-act count — it replays display-only into the honest left_alone tail. attack + empty contain is split into its own AttackNoCutEntry class, distinct from both would_act and left_alone. The headline Report::would_act_count() now counts DISTINCT contained nodes across the window (a decision can name the entry plus a downstream workload), not distinct entries.

View-model shape (new prop)

FindingProps gained a new, non-optional cuts: CutSetProps field (the old cut: Option<String> stays — it still drives the chain-diagram's cut-hop marker):

pub struct CutSetProps {
    pub assessment: &'static str,   // "attack" | "no-attack" | "uncertain" | "awaiting"
    pub rows: Vec<CutRowProps>,
}
pub struct CutRowProps {
    pub node: String,
    pub mechanism: &'static str,
    pub is_entry: bool,
    pub blast_note: String,
}

Sourced from a new Finding.incident: Option<IncidentSummary> field, populated in Finding::from_chain from the engine's per-pass BTreeMap<String, IncidentDecision> (threaded through Findings::publish_chains, called at both process() call sites — the pre-adjudication publish passes the carried-forward prior decision map, the post-adjudication re-publish passes this pass's fresh one).

Testing

  • Rust: engine/src/engine/state/report_tests.rs (new, split out to keep report.rs under the 1,000-line cap) — 6 new tests covering: would-act requires the typed Attack + non-empty cuts (not prose); prose alone with no Incident line never inflates the count (the JEF-674 bug fix, directly asserted); short-lived episode closing via a typed NoAttack; attack + empty cuts as its own honest class; would_act_count() counting distinct nodes (not entries) across two entries with one shared node; the empty-journal case. cargo nextest run --workspace: 1122 passed, 2 skipped (pre-existing, unrelated).
  • Web: engine/web/test/cut-set.test.jsx (new) — 5 tests: per-node row rendering (fenced key/role/mechanism/blast-note); "attack, no cut warranted"; uncertain never renders green; awaiting state; XSS-safe node-key escaping. npm test: 116 passed (18 files). npm run build/typecheck/lint: clean.

Checks

  • cargo fmt, cargo clippy --all-targets -- -D warnings: clean.
  • npm run build && npm run typecheck && npm run lint && npm test (engine/web): clean.
  • /soundcheck:pr-review: no Critical/High findings (pure view/report data-shape change — Preact JSX text interpolation only, no dangerouslySetInnerHTML, no new endpoints/egress/secrets).
  • /simplify: single-pass manual review (Agent tool unavailable in this context) — extracted a shared incident::cut_blast_note helper to remove a duplicated Mitigation-for-blast-radius construction between menu.rs and the new finding-detail resolver, and removed a redundant would-act pre-check in report.rs's episode loop (the inner loop's first iteration already does the identical lookup).

Scope / decisions

  • Kept Finding.cut: Option<String> (the deterministic containment_for fallback) unchanged — it still drives the proven-path chain diagram's cut-hop marker (HopProps::is_cut), which this ticket doesn't touch.
  • coverage_gap classification in report.rs still reads structured EnrichmentCoverage off the Breach timeline (unchanged) — Decision::Incident lines don't carry it, and the Breach timeline stays the aggregation's cadence backbone for that reason. Only the would-act BOOLEAN and contained-node set now come from the typed Incident state in effect at each Breach point.
  • Did not duplicate the model's one-sentence reason inside CutBlock — the existing VerdictBlock already renders it verbatim via Verdict::summary() ("exploitable — {reason}"), unchanged by this PR.
  • DECISION NEEDED (none blocking): Report::attack_no_cut is new data the Action view (engine/src/engine/dashboard/view_model/action.rs, a sibling surface) doesn't yet render — it's available for a follow-up ticket to surface in the Action view's "proposed cuts" lifecycle story.

Closes JEF-674

…shadow would-act counts to the typed decision

Finding detail (ADR-0034): the single opaque cut-signature string is replaced with a
per-node cut-set list (fenced node key + fixed mechanism string + entry-vs-downstream
role + advisory blast-radius note), sourced from the engine's per-pass IncidentDecision
map rather than the deterministic containment_for fallback. Honest empty states:
"attack" with no rows reads as "attack, no cut warranted"; "uncertain" never renders as
a green all-clear; no decision yet reads as "awaiting".

report.rs (JEF-143 aggregation): would-act classification now reads the typed
Decision::Incident (assessment + cuts) in effect at each Breach timestamp, never the
verdict prose match it used before — a Breach line with "exploitable" prose but no
backing Incident line no longer inflates the count. "attack, no cut warranted" is
split into its own AttackNoCutEntry class, distinct from would_act and left_alone.
would_act_count() now counts distinct contained NODES (a decision can name the entry
plus a downstream workload), not distinct entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
@thejefflarson
thejefflarson force-pushed the thejefflarson/jef-674-surface-the-models-cut-set-in-finding-detail-realign-shadow branch from 1793549 to 9a15af6 Compare August 1, 2026 10:13
@thejefflarson
thejefflarson enabled auto-merge (squash) August 1, 2026 10:13
@thejefflarson

Copy link
Copy Markdown
Owner Author

Integration note (architect): rebased onto main after #308/#309/#310 landed — clean rebase, no conflicts (git's 3-way merge absorbed the additive state/view-model re-export lines and the two publish_chains call-site edits by context). Integrated tree verified locally: cargo nextest 1115 pass (incl. the file_size_guard line-cap test), clippy + fmt clean, web typecheck/lint/test green (122). Confirmed report.rs retains no verdict-prose matching (verdict_would_act gone; classification keys on the typed Assessment::Attack && !cuts.is_empty()), and no source file exceeds the 1,000-line cap (largest is 985). mod.rs sits at 856 after the #310 replay_journalrestore.rs split.

@thejefflarson
thejefflarson merged commit 059f993 into main Aug 1, 2026
5 checks passed
@thejefflarson
thejefflarson deleted the thejefflarson/jef-674-surface-the-models-cut-set-in-finding-detail-realign-shadow branch August 1, 2026 10:20
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.

1 participant