From 32c33e90eb1fb5a4e6a9bc57575c416d1090e906 Mon Sep 17 00:00:00 2001 From: Mani Sarkar Date: Tue, 25 Aug 2026 21:56:06 +0100 Subject: [PATCH 1/3] =?UTF-8?q?docs(plan):=20blend=20quality=20tabs=20into?= =?UTF-8?q?=20slice=2042=20as=20A14=E2=80=93A15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Supersedes the standalone slice 54 stub so dashboard ≥80 vs Rest triage stays one Wave J contract with GWT-42.11–42.16 and updated trackers. --- docs/plan/DECISIONS.md | 2 + docs/plan/PROGRESS.md | 7 +- docs/plan/TRAIL.md | 4 +- .../slice-42-dashboard-data-quality-fixes.md | 132 ++++++++++++++++-- 4 files changed, 130 insertions(+), 15 deletions(-) diff --git a/docs/plan/DECISIONS.md b/docs/plan/DECISIONS.md index 11a0c29..decdd8b 100644 --- a/docs/plan/DECISIONS.md +++ b/docs/plan/DECISIONS.md @@ -187,5 +187,7 @@ | 2026-08-25 | planning | Amended slices 26+28 (not new slice) | `/tw-verify` Quality column (`N/100` from `items.quality_score`) + blocked-note footer de-dupe. Quality-only metrics; amend-in-place like slice 42. Slice 30 one-line sync. Skill implementation deferred to `slice/28-*` execution. DECIDED | | 2026-08-25 | slice-28 | Waive Before-Check: 26+27 gate PASS on main | Formal Wave H gate-evidence for 26/27 remains PLANNED on `main` (Frontline PRs #78/#80 never merged to main). Substitute: Phase-1 `agent-hooks` skills + this PR publishes `docs/user-guide/frontline-output-contract.md` as output SSOT; enable/disable skills already on main. (waived: DECISIONS 2026-08-25) | | 2026-08-25 | slice-28 | Quality + footer IMPLEMENTED | Restored Frontline `guard/verify.py` onto `slice/28-tw-verify-quality`; Quality `N/100` + blocked footer; agent-hooks `tw-verify` / `tw-self-check` / `tw-disable` synced. IMPLEMENTED | +| 2026-08-25 | planning | Added slice 54 — quality score tabs | Wave J follow-on (not new wave). Embed **Quality ≥ 80** / **Rest** tabs in `Tripwire.dc.html`; skills only; threshold 80 on Tessl `items.quality_score`. Depends 42+47. Stub: `docs/plan/slices/10-J-dashboard-data-quality/slice-54-quality-score-tabs.md`. EFP continuation Path B. DECIDED | +| 2026-08-25 | planning | **SUPERSEDED** slice 54 → 42 A14–A15 | User confirmed blend into existing slice. A14 = quality tab filter (≥80 vs Rest); A15 = counts + empty-state copy. GWT-42.11–42.16; sub-task 13; slice-54 stub deleted. No new plan/wave. DECIDED | | 2026-08-25 | sync-docs | Slice 28 feature-delta discoverability | PROGRESS execution-order 🔀; ARCHITECTURE Wave H Phase 1; docs hub + reading-router-results Tessl quality ↔ `/tw-verify` Quality; STATUS PROPOSED/Future SUPERSEDED for Phase 1. APPLIED | | 2026-08-25 | sync-docs | Slice 28 Sources attribution | Discoverability: agent-hooks README, docs hub, ARCHITECTURE/STATUS Wave H, reading-router-results, slice stub, tw-verify skill intro — Quality=Tessl / Status=Cisco+Snyk. Contract + verify.py already had Sources. README/AGENTS NO_CHANGE. APPLIED | diff --git a/docs/plan/PROGRESS.md b/docs/plan/PROGRESS.md index a8701f4..94198a2 100644 --- a/docs/plan/PROGRESS.md +++ b/docs/plan/PROGRESS.md @@ -8,7 +8,7 @@ | 12 | [`12-L-…`](slices/12-L-tessl-5-row-expansion/) | **L — Tessl 5-row expansion** | 45 ✅ · 46 ✅ · 47 ✅ · 48 ✅ · 49 ✅ · 50 ✅ · **51** 🔀 · 52 📋 | 45–50 ✅ · **51** 🔀 | | 13 | [`13-M-…`](slices/13-M-llm-usage-tracking/) | **M — LLM usage / cost observability** | **53** | 📋 plan-only (ADR-0016 follow-on; not J/L/G) | | 11 | [`11-K-…`](slices/11-K-docs-ux-plain-language/) | **K — Docs UX plain language + compaction** | **44** | 🔀 | -| 10 | [`10-J-…`](slices/10-J-dashboard-data-quality/) | **J — Dashboard Data Quality Fixes** | 42 | A1–A13 ✅ | +| 10 | [`10-J-…`](slices/10-J-dashboard-data-quality/) | **J — Dashboard Data Quality Fixes** | 42 (A14–A15 📋) | A1–A13 ✅ · quality tabs delta | | 9 | [`09-I-…`](slices/09-I-landing-intro-restyle/) | **I — Landing Intro + Visual Refresh** | 41 ✅ · 43 | 41 ✅ · 43 ✅ | | 1 | [`01-A-…`](slices/01-A-live-path-gwt/) | **A — Live path + GWT** | 1 → 2 → 3 · 4 | ✅ · 4 📦 | | 2 | [`02-B-…`](slices/02-B-characterization-evidence/) | **B — Characterization + evidence sync** | 6 → 5 | ✅ | @@ -161,7 +161,7 @@ Plan", 2026-08-15). Governance blocks *merge*, not prototyping. ### J — Dashboard Data Quality Fixes | # | Slice | MoSCoW | Status | Started | Completed | Est. time | |---|-------|--------|--------|---------|-----------|-----------| -| 42 | [slice-42-dashboard-data-quality-fixes](slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md) | Must | ✅ PASSED (A9–A13 · [#98](https://github.com/neomatrix369/tripwire/pull/98); A1–A8 #95) | 2026-08-19 | — | ~75 min delta | +| 42 | [slice-42-dashboard-data-quality-fixes](slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md) | Must | 📋 PLANNED (A14–A15 delta reopen) | — | — | ~45 min delta | ### K — Docs UX plain language + compaction | # | Slice | MoSCoW | Status | Started | Completed | Est. time | @@ -198,6 +198,7 @@ Plan", 2026-08-15). Governance blocks *merge*, not prototyping. | 4 (historical) | Remotion sibling + VO assets missing | 📦 closed with demo/hackathon deferral — reinstate slice 4 if film day returns | ## Forward Roadmap +- Wave **J** (slice **42** A14–A15) dashboard quality tabs — **📋 plan-only** — embed ≥80 vs Rest triage in `prototypes/dc-dashboard/`; execute when explicitly started (`slice/42-quality-score-tabs`). - Wave **M** (slice 53) LLM usage / cost observability is **📋 plan-only** — ADR-0016 follow-on; execute when explicitly started (`slice/53-llm-usage-tracking`). - Waves **A–C**, coverage Slice 14, and Slice 17 are merged and closed. Slice 15 is retained as a deferred claim-audit artifact, not active work. - **Wave H (Frontline):** plan-only stubs 23–39 on branch `frontline-hackathon-london-2026-agent-hooks`. Execute Musts 23→32 with human tests after H1/H2 and HARD GATE at 32 before Should work. @@ -274,3 +275,5 @@ Plan", 2026-08-15). Governance blocks *merge*, not prototyping. | 2026-08-25 | slice/51-review-security | nw-review | 51 | APPROVED | software-crafter-reviewer; After-Checks ticked; still 🔨 until commit/PR | | 2026-08-25 | slice/51-review-security | clean-commit | 51 | 🔀 ON BRANCH | `6cbded7` feat + `c96c459` style; pushed; awaiting `/create-pr` | | 2026-08-25 | slice/51-review-security | sync-docs | 51 | APPLIED | Trackers 🔨→🔀; prerequisites + OPTIONAL_SCANNER_KEYS name Security; adapters heading; README/AGENTS NO_CHANGE | +| 2026-08-25 | main / plan | enhanced-flow-planner Path B | 54 | 📋 PLANNED | Dashboard quality tabs ≥80 vs Rest; Wave J; depends 42+47 | +| 2026-08-25 | planning | Blended slice 54 → 42 A14–A15 | 42 | 📋 delta reopen | Quality tabs augment slice 42 in place; slice 54 stub removed; branch `slice/42-quality-score-tabs`. DECIDED | diff --git a/docs/plan/TRAIL.md b/docs/plan/TRAIL.md index 4d7a182..b5a14d8 100644 --- a/docs/plan/TRAIL.md +++ b/docs/plan/TRAIL.md @@ -91,7 +91,7 @@ Groups are ordered by when the wave ran (or will run), not by slice number. | 8 | `slices/08-H-agent-guard-integration/` (stubs pending) | **H — Claude Code Agent Guard integration** | H0 governance → H1–H7 (hackathon plan §9 steps 0–7) | 🔨 | | 8 | [`slices/08-H-frontline-agent-hooks/`](slices/08-H-frontline-agent-hooks/) | **H — Frontline agent hooks** | 23→32 Must · 33–38 Should · 39 Could | 📋 plan-only | | 9 | [`slices/09-I-landing-intro-restyle/`](slices/09-I-landing-intro-restyle/) | **I — Landing Intro + Visual Refresh** | 41 ✅ · **43** ✅ | [#96](https://github.com/neomatrix369/tripwire/pull/96) | -| 10 | [`slices/10-J-dashboard-data-quality/`](slices/10-J-dashboard-data-quality/) | **J — Dashboard Data Quality Fixes** | 42 ✅ A1–A13 ([#98](https://github.com/neomatrix369/tripwire/pull/98)) · A1–A8 [#95](https://github.com/neomatrix369/tripwire/pull/95) | — | +| 10 | [`slices/10-J-dashboard-data-quality/`](slices/10-J-dashboard-data-quality/) | **J — Dashboard Data Quality Fixes** | 42 ✅ A1–A13 ([#98](https://github.com/neomatrix369/tripwire/pull/98)) · **A14–A15** 📋 quality tabs | — | | 11 | [`slices/11-K-docs-ux-plain-language/`](slices/11-K-docs-ux-plain-language/) | **K — Docs UX plain language + compaction** | **44** 🔀 | — | **Status legend**: `📋 PLANNED · 🔨 IN PROGRESS · ✅ PASSED · 🔀 ON BRANCH · 🔴 BLOCKED · 📦 CLOSED — DEFERRED/WON'T` @@ -246,7 +246,7 @@ Audit report: `~/.claude/plans/iterate-through-all-of-lovely-stearns.md` | # | File | Name | MoSCoW | Status | Depends on | Issue | Read time | |---|------|------|--------|--------|------------|-------|-----------| -| 42 | [slice-42-dashboard-data-quality-fixes](slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md) | Dashboard Data Quality Fixes (A1–A13 Tessl quality + tooltips + labels) | Must | ✅ | none | [#95](https://github.com/neomatrix369/tripwire/pull/95) · [#98](https://github.com/neomatrix369/tripwire/pull/98) | ~3 min | +| 42 | [slice-42-dashboard-data-quality-fixes](slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md) | Dashboard Data Quality Fixes (A1–A13 ✅ + **A14–A15** quality tabs) | Must | 📋 (A14–A15 delta) | none | [#95](https://github.com/neomatrix369/tripwire/pull/95) · [#98](https://github.com/neomatrix369/tripwire/pull/98) | ~3 min + ~45 min delta | ### K — Docs UX plain language + compaction (opened 2026-08-20) diff --git a/docs/plan/slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md b/docs/plan/slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md index fae7db5..bf672eb 100644 --- a/docs/plan/slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md +++ b/docs/plan/slices/10-J-dashboard-data-quality/slice-42-dashboard-data-quality-fixes.md @@ -1,9 +1,9 @@ # Slice 42 — Dashboard Data Quality Fixes -> Wave J | MoSCoW: **Must** | Status: 🔀 ON BRANCH (A9–A13 delta) | Est: ~120 min + ~60 min delta -> Depends on: none (independent; complements slices 21 and 22) -> Prior merge: [#95](https://github.com/neomatrix369/tripwire/pull/95) shipped A1–A8 ✅ — this reopen adds **A9–A13** only -> Branch: `slice/42-tessl-quality-card-surfacing` +> Wave J | MoSCoW: **Must** | Status: 📋 PLANNED (A14–A15 delta reopen) | Est: ~120 min + ~60 min (A9–A13) + ~45 min (A14–A15) +> Depends on: none (independent; complements slices 21 and 22). **A14–A15** requires A9–A13 ✅ ([#98](https://github.com/neomatrix369/tripwire/pull/98)) + slice 47 ✅ on `main`. +> Prior merge: [#95](https://github.com/neomatrix369/tripwire/pull/95) shipped A1–A8 ✅ · [#98](https://github.com/neomatrix369/tripwire/pull/98) shipped A9–A13 ✅ — this reopen adds **A14–A15** only +> Branch (when building A14–A15): `slice/42-quality-score-tabs` --- @@ -25,6 +25,11 @@ Tessl-unreachable states have no explicit indicator — operators cannot triage review” from the card face or the detail panel header. **Pile-on:** `risk 0.75` has no hover explaining that risk is weighted finding density `(3×red+1×amber)/checks`, not card colour. +**Delta (2026-08-25):** After A9–A13, operators still scroll the full skills grid to triage by Tessl +quality. Embed **Quality ≥ 80** / **Rest (below / unscored)** filter tabs in the dashboard toolbar +(skills only; threshold **80/100** on Tessl Review Quality axis). **Augment slice 42 in place** — +standalone slice 54 stub superseded (see DECISIONS 2026-08-25). + **Source**: Anomaly audit in `~/.claude/plans/iterate-through-all-of-lovely-stearns.md` and scratchpad investigation reports (2026-08-19). Delta request: enhanced-flow-planner continuation (augment existing Wave J slice — do not open a new slice). @@ -48,11 +53,13 @@ scratchpad investigation reports (2026-08-19). Delta request: enhanced-flow-plan | A11 | **FE/UX** | Medium | `risk N.NN` label has no hover explanation — operators cannot tell formula, range, or that card colour ≠ risk density | All cards showing risk | | A12 | **FE/UX** | Medium | Even after A9, quality badge has no hover explaining Tessl 0–100 meaning / provenance (parity gap vs A11 risk tooltip) | Skills showing `Q …` | | A13 | **FE/UX** | Medium | Operator-facing copy uses schema jargon (`risk_score`, ambiguous list “Score”, some locus/avail phrasing) | All dashboard surfaces | +| A14 | **FE/UX** | Medium | No quick filter to separate high Tessl quality skills (≥80) from below-threshold or unscored | Skills (Tessl-eligible) | +| A15 | **FE/UX** | Low | Quality tab counts and empty-state copy not wired when filters yield zero skills | Dashboard toolbar | > **Note**: A7 count is inflated by A1 (FE can't see the findings for out-of-window runs). > Re-audit after A1 fix to get the true DB-only count. > -> **Shipped (PR #95):** A1–A8. **Open delta:** A9–A13 (quality on top + tooltips + operator-friendly labels). +> **Shipped (PR #95):** A1–A8. **Shipped (PR #98):** A9–A13. **Open delta:** A14–A15 (quality score tabs). > > **Propagation check (2026-08-20):** Live adapter already maps `items.quality_score` → `item.quality` and into Tessl `output.quality_score` (`tripwire-live.js`). **UI top-of-card does not render it yet** — only the expanded Tessl row, and only when truthy. A9/A10/A12 close that gap. @@ -228,6 +235,48 @@ Schema / API field names may stay snake_case in code and tooltips’ technical l **Out of scope:** renaming DB columns, API fields, or developer console/`outputJson` dumps. +### GWT-42.11 — Two quality tabs render on the dashboard (A14) — **delta open** + +**Given** the operator is on the Tripwire dashboard (past intro) +**When** the dashboard toolbar renders +**Then** two quality triage tabs are visible: **Quality ≥ 80** and **Rest (below / unscored)** +**And** exactly one tab is active at a time (default: **Quality ≥ 80**) + +### GWT-42.12 — High tab lists only skills with quality ≥ 80 (A14) + +**Given** live or mock data includes skills with mixed quality scores (e.g. 92, 88, 75, 61, null) +**When** the operator selects **Quality ≥ 80** +**Then** the grid/list shows only **skill** items where `typeof quality === 'number' && quality >= 80` +**And** MCP servers are excluded regardless of other filters + +### GWT-42.13 — Rest tab lists below-threshold and unscored skills (A14) + +**Given** the same mixed dataset +**When** the operator selects **Rest (below / unscored)** +**Then** the grid/list shows only **skill** items where quality is `null`/missing **or** `< 80` +**And** items with quality exactly 79 appear in Rest, not High + +### GWT-42.14 — Quality tabs compose with search and type filters (A14) + +**Given** the operator has typed a search query and/or selected **Skills** in the existing type tabs +**When** they switch between quality tabs +**Then** results are the intersection of search + type filter + active quality tab +**And** clearing filters resets quality tab to default, search, and type filter + +### GWT-42.15 — Empty state when a quality tab has no matches (A15) + +**Given** filters yield zero skills for the active quality tab +**When** the dashboard renders +**Then** the existing filter-empty panel appears with copy naming the active quality tab +**And** **Clear filters** resets quality tab to default, search, and type filter + +### GWT-42.16 — Tab counts reflect filtered skills (A15) + +**Given** the dashboard has loaded item data +**When** quality tabs render +**Then** each tab label includes a count of matching **skills** (e.g. `≥ 80 (12)`) +**And** counts update when Live realtime refresh replaces item data + --- ## Before-Checks @@ -249,6 +298,14 @@ Schema / API field names may stay snake_case in code and tooltips’ technical l - [x] `quality-gates.sh` passes on the delta branch base before starting - [x] Coordinate with slice 43: both touch `Tripwire.dc.html` — prefer execute after #96 merges, or rebase onto 43 tip +### Delta (A14–A15) — second reopen + +- [ ] On current `main` (post-#98): confirm A9–A13 quality badges render; no quality triage tabs yet +- [ ] Confirm slice 47 ✅ on `main` (`items.quality_score` scoped to Tessl Review Quality) +- [ ] Branch `slice/42-quality-score-tabs` created from current `main` +- [ ] No concurrent edit on `Tripwire.dc.html` (check slices 48/51 if ON BRANCH — rebase if needed) +- [ ] `quality-gates.sh` passes on the delta branch base before starting + --- ## TDD Execution Order @@ -540,6 +597,28 @@ Q — = never scanned / no score yet. Q ? = scanned but Tessl did not yield a sc --- +### Sub-task 13 — Fix A14–A15: quality score triage tabs (FE/UX) — **delta** + +**Files**: +- `prototypes/dc-dashboard/tripwire-status.js` — pure helpers: `qualityMeetsThreshold(item, floor)`, `qualityTabBucket(item)`, `filterItemsByQualityTab(items, tab)` +- `prototypes/dc-dashboard/Tripwire.dc.html` — state `qualityTab` (`high` | `rest`); toolbar tab buttons; wire into `filtered` pipeline after type/search/status filters; extend `filterEmptyCopy` / `clearFilters` +- `prototypes/dc-dashboard/test/tripwire-status.test.js` — GWT-42.12–42.13 unit tests (79 vs 80 boundary, null, NaN) + +**Behaviour**: +1. Two tabs: **Quality ≥ 80** (default) and **Rest (below / unscored)** — skills-only filter (`item.type === 'skill'`). +2. Threshold **80** on `item.quality` (already mapped from `items.quality_score` in `tripwire-live.js`). +3. Tab labels include skill counts (GWT-42.16); empty state names active tab (GWT-42.15). +4. Reuse A11/A12 tooltip helpers — do not duplicate quality explanation strings. + +**Out of scope:** Supabase schema; Tessl scanner; `/tw-verify` output; canvas artifact removal. + +**Tests**: +- Unit: high bucket excludes 79 and null; rest includes 61 and null; boundary 80/79 +- Unit or manual: search + type + quality intersection (GWT-42.14) +- Manual smoke: tab chrome + counts on `http://127.0.0.1:8765/` + +--- + ## After-Checks ### Shipped (A1–A8) — historical, PR #95 @@ -570,6 +649,21 @@ Q — = never scanned / no score yet. Q ? = scanned but Tessl did not yield a sc - [x] Doc audit below complete for delta - [x] code review passed (nw-review — APPROVED 2026-08-20) +### Delta (A14–A15) — second reopen gate + +- [ ] GWT-42.11: Quality ≥ 80 / Rest tabs visible; default High +- [ ] GWT-42.12: unit — high bucket excludes 79, null +- [ ] GWT-42.13: unit — rest bucket includes null and scores < 80 +- [ ] GWT-42.14: search + type + quality intersection (unit or manual) +- [ ] GWT-42.15: empty state copy names active quality tab (manual smoke) +- [ ] GWT-42.16: tab labels show skill counts (manual smoke) +- [ ] `(cd prototypes/dc-dashboard && npm test && npm run lint)` passes +- [ ] `./scripts/quality-gates.sh` passes +- [ ] Complexity evidence: prototype dashboard **reporting** only — same policy as A9–A13 delta +- [ ] `docs/plan/gate-evidence/slice-42.json` updated for A14–A15 delta (`prior_pass` retains #98) +- [ ] Doc audit below complete for A14–A15 +- [ ] `/nw-review` APPROVED before ✅ PASSED + --- ## Code Review (nw-review, slice-42 delta) @@ -616,6 +710,12 @@ Q — = never scanned / no score yet. Q ? = scanned but Tessl did not yield a sc - [x] `CHANGELOG.md`: entry for Tessl quality card/panel surfacing + risk/quality tooltips + label polish - [x] Screenshot or smoke note: quality badge + risk hover + plain labels visible on grid + detail (optional if mock fixtures cover tests) +### Delta (A14–A15) + +- [ ] `docs/plan/DECISIONS.md`: log A14–A15 reopen; note slice 54 superseded +- [ ] `CHANGELOG.md`: entry for dashboard quality score triage tabs (≥80 vs Rest) +- [ ] `docs/STATUS.md`: IMPLEMENTED note when merged + --- ## Open Questions (for discussion before 🔨) @@ -635,20 +735,30 @@ Q — = never scanned / no score yet. Q ? = scanned but Tessl did not yield a sc 5. **Quality tooltip**: required with A9 — Tessl 0–100 skill-review explanation; parity with risk hover; not optional. 6. **Operator labels**: use GWT-42.10 glossary (`Risk density`, `Tessl quality`, plain locus/avail); no snake_case in chrome. +### Resolved (A14–A15) — USER-CONFIRMED reopen 2026-08-25 + +1. **Blend, not new slice**: quality tabs are A14–A15 delta on slice 42; slice 54 stub removed. +2. **Threshold**: 80/100 fixed (Tessl Review Quality axis); not configurable in v1. +3. **Scope**: skills only; MCP servers never appear in quality tab filters. +4. **Default tab**: Quality ≥ 80 (operators see the “good” set first). +5. **Branch**: `slice/42-quality-score-tabs` from current `main`. + --- ## Gate Status -🔀 ON BRANCH — A9–A13 implemented on `slice/42-tessl-quality-card-surfacing`. nw-review **APPROVED** 2026-08-20 — ready for ✅ PASSED + merge of PR #98. +✅ PASSED — A1–A13 merged ([#95](https://github.com/neomatrix369/tripwire/pull/95) + [#98](https://github.com/neomatrix369/tripwire/pull/98)). + +📋 PLANNED — A14–A15 quality score tabs (second delta reopen on slice 42). ```json { "slice": 42, - "gate_status": "ON_BRANCH", - "delta": "A9-A13-quality-risk-tooltips-labels", - "prior_pass": "#95", - "branch": "slice/42-tessl-quality-card-surfacing", + "gate_status": "PLANNED", + "delta": "A14-A15-quality-score-tabs", + "prior_pass": "#95 + #98", + "branch": "slice/42-quality-score-tabs", "open_questions": [], - "reopen_confirmed": "2026-08-20" + "reopen_confirmed": "2026-08-25" } ``` From bf32f1717b02167fb4bb61e8a1156a3e289336dd Mon Sep 17 00:00:00 2001 From: Mani Sarkar Date: Tue, 25 Aug 2026 21:58:46 +0100 Subject: [PATCH 2/3] fix(tessl): emit per-step dashboard progress during Modal scans Bulk running placeholders made all five Tessl rows look frozen for the whole group run even while Lint and Review were completing. Tessl rows now update step-by-step via on_scanner_progress; dashboard labels blocked/stale states. --- CHANGELOG.md | 7 ++++ docs/ARCHITECTURE.md | 2 +- docs/STATUS.md | 5 +++ prototypes/dc-dashboard/tripwire-status.js | 4 +++ sandbox/scan_app.py | 8 ++++- sandbox/scanners.py | 38 +++++++++++++++------- 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d952a..04c1175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Tessl dashboard progress during Modal scans: skip bulk `running` placeholders for + all five Tessl rows at group start; Lint and Review (Quality) now persist + `running`→terminal via `on_scanner_progress` so only the active step shows + Running. Dashboard adds status pills for `blocked`, `stale`, `interrupted`, and + `timed_out` (`tripwire-status.js`). + ### Added - `/tw-verify` Quality column + blocked footer (slice 28): Scan Status table is **Name | Type | Status | Quality | Note**; Quality shows Tessl diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c0f1243..0460464 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -307,7 +307,7 @@ separate `scan_run_scanners` row (slice 46 ✅ persist scan_run `a36cad9f`): [#109](https://github.com/neomatrix369/tripwire/pull/109)) uses `tessl review run quality --json --workspace` and stamps `tessl_run_id` from `tessl review view --last --json`, then seeds in-process -`_TesslIdContext["review_quality"]` for slices 49–51 (GWT-47.5). It is orthogonal to findings and to `risk_score`. **IMPLEMENTED (UI):** slice 48 synthesises "Not Available Yet" sentinel rows for Tessl capabilities absent from the scan_run (never stored as placeholders). **IMPLEMENTED (unit, slice 49):** Scenario Generation writes a real `scan_run_scanners` row (`scenario generate` → `download` into `/evals/`, `resume_checkpoint`, mid-scan persist). **IMPLEMENTED (unit, slice 50):** Eval starts `blocked`, auto-chains after Scenario Gen when `evals/` is populated (`eval run --runs 3 -y` + `eval view`; stale on scenario re-run; project create/repair preflight). **IMPLEMENTED (unit, slice 51):** Security Review writes `"Tessl: Review (Security)"` via `_run_tessl_review("security")` after Eval; `upstream_run_ids.review_quality` is attached before invoke; expanded Security row shows linked Quality findings when that ID is populated — see +`_TesslIdContext["review_quality"]` for slices 49–51 (GWT-47.5). It is orthogonal to findings and to `risk_score`. **IMPLEMENTED (UI):** slice 48 synthesises "Not Available Yet" sentinel rows for Tessl capabilities absent from the scan_run (never stored as placeholders). **IMPLEMENTED (unit, slice 49):** Scenario Generation writes a real `scan_run_scanners` row (`scenario generate` → `download` into `/evals/`, `resume_checkpoint`, mid-scan persist). **IMPLEMENTED:** Tessl dashboard progress — `scan_app` skips bulk `running` placeholders for the Tessl group; `run_tessl` persists Lint and Review (Quality) step-by-step via `on_scanner_progress` (not only at group end). **IMPLEMENTED (unit, slice 50):** Eval starts `blocked`, auto-chains after Scenario Gen when `evals/` is populated (`eval run --runs 3 -y` + `eval view`; stale on scenario re-run; project create/repair preflight). **IMPLEMENTED (unit, slice 51):** Security Review writes `"Tessl: Review (Security)"` via `_run_tessl_review("security")` after Eval; `upstream_run_ids.review_quality` is attached before invoke; expanded Security row shows linked Quality findings when that ID is populated — see [design/tessl-5-row-expansion.md](./design/tessl-5-row-expansion.md) and slices 49–51; scenario→eval pipeline is generate → download → `eval run` on disk `evals/` (sandbox-populated; host `evals/` is not a vuln-scan input — packing diff --git a/docs/STATUS.md b/docs/STATUS.md index a6b1c18..2f130ed 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -122,6 +122,11 @@ Reachable through production entry points / config: stamp, `resume_checkpoint` + mid-scan persist via `on_scanner_progress`. Missing token → `needs_setup`; missing `.tessl-plugin/plugin.json` → `failed`. IMPLEMENTED (unit) — `sandbox/scanners.py` / `sandbox/scan_app.py` (slice 49 ✅ #112) +- Tessl dashboard mid-scan progress — `on_scanner_start` no longer bulk-inserts + all five Tessl sources as `running`; `run_tessl()` emits per-step progress for + Lint and Review (Quality) via `on_scanner_progress` (Scenario/Eval/Security + already did). Fixes frozen-all-Running appearance during Modal scans. + IMPLEMENTED — `sandbox/scanners.py` / `sandbox/scan_app.py` / `tripwire-status.js` - Tessl Eval auto-chain — `run_tessl()` emits `"Tessl: Eval"` as `blocked` before Scenario Generation, then auto-chains to `queued`→`running` when generation completes and `/evals/` has scenarios; `tessl eval run --runs 3 -y diff --git a/prototypes/dc-dashboard/tripwire-status.js b/prototypes/dc-dashboard/tripwire-status.js index 87c9f0d..2640e28 100644 --- a/prototypes/dc-dashboard/tripwire-status.js +++ b/prototypes/dc-dashboard/tripwire-status.js @@ -205,6 +205,10 @@ export const SCANNER_EXEC_META = { unreachable: { color: STATUS_META.error.color, label: "✗ Unreachable" }, not_applicable: { color: STATUS_META.grey.color, label: "— N/A" }, failed: { color: STATUS_META.error.color, label: "✗ Failed" }, + blocked: { color: STATUS_META.grey.color, label: "⊘ Blocked" }, + stale: { color: STATUS_META.amber.color, label: "Stale" }, + interrupted: { color: STATUS_META.amber.color, label: "Interrupted" }, + timed_out: { color: STATUS_META.amber.color, label: "Timed Out" }, not_available_yet: { color: STATUS_META.grey.color, label: "Not Available Yet", diff --git a/sandbox/scan_app.py b/sandbox/scan_app.py index c35c0b7..7843bca 100644 --- a/sandbox/scan_app.py +++ b/sandbox/scan_app.py @@ -28,7 +28,7 @@ if str(_SANDBOX_DIR) not in sys.path: sys.path.insert(0, str(_SANDBOX_DIR)) -from scanners import run_all_scanners # noqa: E402 +from scanners import TESSL_SOURCES, run_all_scanners # noqa: E402 app = modal.App("tripwire-scan") @@ -85,6 +85,10 @@ _LEGACY_SCANNER_KEYS = frozenset({"scan_run_id", "scanner_source", "status", "checks_run"}) +# Tessl rows are inserted/updated step-by-step via on_scanner_progress — not bulk +# "running" placeholders (which made all five look frozen for the whole group). +_TESSL_SOURCE_SET = frozenset(TESSL_SOURCES) + def _is_column_error(exc: Exception) -> bool: return bool(_PGRST_COLUMN_RE.search(str(exc))) @@ -209,6 +213,8 @@ def _on_scanner_start(sources): """Insert running placeholder rows so the dashboard shows progress.""" now = datetime.now(UTC).isoformat() for source in sources: + if source in _TESSL_SOURCE_SET: + continue try: supabase.table("scan_run_scanners").insert( { diff --git a/sandbox/scanners.py b/sandbox/scanners.py index a947bb1..ae51a07 100644 --- a/sandbox/scanners.py +++ b/sandbox/scanners.py @@ -1801,14 +1801,21 @@ def run_tessl( ctx = id_context if id_context is not None else _new_tessl_id_context() # --- Tessl: Lint (auth-free, synchronous) --- + lint_source = "Tessl: Lint" if not _which("npx"): - rows.append(_unreachable("Tessl: Lint", "npx not available (node/npm missing from image)")) + lint_row = _unreachable(lint_source, "npx not available (node/npm missing from image)") + rows.append(lint_row) + _emit_tessl_row_progress(on_row_progress, lint_row) else: + _emit_tessl_row_progress( + on_row_progress, + {"scanner_source": lint_source, "status": "running", "checks_run": 0}, + ) code, out, err = _run(["npx", "--yes", "tessl@latest", "skill", "lint", workdir]) console = _build_console(out, err) if code != 0: lint_row = { - "scanner_source": "Tessl: Lint", + "scanner_source": lint_source, "status": "failed", "checks_run": 0, "detail": (err or out or "lint subprocess exited non-zero").strip()[:4000], @@ -1818,7 +1825,7 @@ def run_tessl( else: checks_run, detail = _parse_tessl_lint_detail(out) lint_row = { - "scanner_source": "Tessl: Lint", + "scanner_source": lint_source, "status": "completed", "checks_run": checks_run, "detail": detail, @@ -1826,29 +1833,38 @@ def run_tessl( if console: lint_row["console_output"] = console rows.append(lint_row) + _emit_tessl_row_progress(on_row_progress, lint_row) # --- Tessl: Review (Quality) (TESSL_TOKEN + resolved workspace) --- score = None workspace: str | None = None workspace_detail = "" + review_source = "Tessl: Review (Quality)" if not os.environ.get("TESSL_TOKEN"): - rows.append(_skipped("Tessl: Review (Quality)", reason="needs_setup")) + review_row = _skipped(review_source, reason="needs_setup") + rows.append(review_row) + _emit_tessl_row_progress(on_row_progress, review_row) _update_tessl_id_context(ctx, "review_quality", None) else: workspace, workspace_detail = _resolve_tessl_workspace() if not workspace: - rows.append( - _skipped( - "Tessl: Review (Quality)", - reason="needs_setup", - detail=workspace_detail - or "Tessl workspace unresolved — set TESSL_WORKSPACE or ensure tessl login", - ) + review_row = _skipped( + review_source, + reason="needs_setup", + detail=workspace_detail + or "Tessl workspace unresolved — set TESSL_WORKSPACE or ensure tessl login", ) + rows.append(review_row) + _emit_tessl_row_progress(on_row_progress, review_row) _update_tessl_id_context(ctx, "review_quality", None) else: + _emit_tessl_row_progress( + on_row_progress, + {"scanner_source": review_source, "status": "running", "checks_run": 0}, + ) score, review_row = _run_tessl_review("quality", workdir, workspace) rows.append(review_row) + _emit_tessl_row_progress(on_row_progress, review_row) _update_tessl_id_context(ctx, "review_quality", review_row.get("tessl_run_id")) # --- Tessl: Eval (blocked until Scenario Generation completes) --- From 3cf4441481a62cefcf9470331706644f5d5a2e2c Mon Sep 17 00:00:00 2001 From: Mani Sarkar Date: Tue, 25 Aug 2026 22:01:38 +0100 Subject: [PATCH 3/3] docs(changelog): merge duplicate Fixed heading under Unreleased --- CHANGELOG.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c1175..cccb02b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed -- Tessl dashboard progress during Modal scans: skip bulk `running` placeholders for - all five Tessl rows at group start; Lint and Review (Quality) now persist - `running`→terminal via `on_scanner_progress` so only the active step shows - Running. Dashboard adds status pills for `blocked`, `stale`, `interrupted`, and - `timed_out` (`tripwire-status.js`). - ### Added - `/tw-verify` Quality column + blocked footer (slice 28): Scan Status table is **Name | Type | Status | Quality | Note**; Quality shows Tessl @@ -121,6 +114,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 avoids delayed native `title=` attributes. ### Fixed +- Tessl dashboard progress during Modal scans: skip bulk `running` placeholders for + all five Tessl rows at group start; Lint and Review (Quality) now persist + `running`→terminal via `on_scanner_progress` so only the active step shows + Running. Dashboard adds status pills for `blocked`, `stale`, `interrupted`, and + `timed_out` (`tripwire-status.js`). - Live dashboard latest-state accuracy (partial slice 21): `db/schema.sql` adds `dashboard_latest_runs` view (`DISTINCT ON (item_id)`); `tripwire-live.js` queries it instead of a global `scan_runs?limit=2000` page and batches