Skip to content

feat(security): plugin architecture + Aikido Safe Chain install-gate + Patches-style /security - #123

Merged
alamb-hex merged 37 commits into
mainfrom
feat/security-plugins-and-restyle
Jun 3, 2026
Merged

feat(security): plugin architecture + Aikido Safe Chain install-gate + Patches-style /security#123
alamb-hex merged 37 commits into
mainfrom
feat/security-plugins-and-restyle

Conversation

@alamb-hex

Copy link
Copy Markdown
Collaborator

Summary

Introduces a two-track security plugin architecture alongside the existing ScanSource stack, ships Aikido Safe Chain as the first install-gate plugin, and restyles /security into a Patches-style per-project accordion. Folds in two pre-held fixes that are ancestors of this branch (#109 dev-server-aware patching, override-pin → floor migration).

What's included

Plugin architecture — additive; existing ScanSource path untouched

  • SecurityPlugin discriminated union (installGate | complianceAudit) in src/lib/security/plugins/
  • Static registry + per-project enable config + helpers

Aikido Safe Chain (first installGate plugin)

  • Host detection (30s cache), command rewriter, block/clean output parser — all dependency-injected for unit tests
  • Wired into /update: when enabled for a project, the install binary is swapped to aikido-pnpm to interpose on the install
  • /security/safe-chain detail page with a per-project enable table

New APIs

  • GET /api/security/plugins — registry + host availability
  • GET /api/security/plugins/[id]/status?projectId=… — per-project card
  • POST /api/projects/[id]/plugins/[pluginId] — enable/disable (AUTO_APPLY-gated)

/security restyle

  • Dropped FleetProjectRail; new ProjectSecurityAccordion (Patches-style collapsible rows) owns per-project CVE Lite state
  • SecurityHeader + fleet-aggregate SecuritySummaryBar + SourcePluginCards (sources + plugins row)
  • Severity pills on rows, Scan menu + OSV DB sync, findings grouped by parent package / CVE

Remediation & audit

  • SecurityException tracking (classification + audit trail), RemediationPanel (multi-option grype remediation), change-control-style attempt logging on Apply paths

Bundled pre-held fixes (ancestors of this branch)

Closes

Follow-ups (NOT closed by this PR)

Verification

  • Typecheck: clean
  • Tests: 215 pass (34 files)
  • Build: green — all routes incl. /security, /security/safe-chain, and the plugin APIs are in the manifest

Merge note

37 commits. Two prior squash-merges on this repo silently dropped commits pushed near merge time (#104, #108). Prefer a merge commit or rebase-merge to preserve history, or diff main against the branch tree after a squash to confirm nothing dropped (especially the security-relevant d020f56 / 167ce28).

🤖 Generated with Claude Code

alamb-hex and others added 30 commits May 24, 2026 11:51
Migrate the remaining exact-pin overrides to security floors so none can become
a stale downgrade pin or block a future patch (follow-up to the qs >=6.15.2 fix):

  postcss     8.5.15  -> ^8.5.15    (stay in 8.x — Next/Tailwind need postcss 8)
  ip-address  10.2.0  -> ^10.2.0
  vite        8.0.13  -> ^8.0.13
  esbuild     0.28.0  -> >=0.28.0   (0.x minors are breaking-by-convention, so a
                                     caret would block esbuild's security minors)

No resolution change: all four resolve to the same versions as before; the
lockfile diff is override-spec-only. typecheck clean, 108 tests pass, cve-lite
offline scan findingCount 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… self-patch (#109)

Applying a patch runs an install that churns node_modules; if the target
project's dev server is live (esp. Turbopack) it loses node_modules/next
mid-reinstall and dies. This was self-inflicted when hexops patched itself —
the apply request is served by the very process the install kills, surfacing
to the user as "Failed to fetch".

New guard in process-manager (TDD, injectable deps):
- decideDevServerGuard: block-self | orchestrate | passthrough.
- isHexopsSelf: target path === hexops cwd (the self-patch case — which cannot
  stop→apply→restart itself, so it is refused with a clear "apply from CLI"
  message rather than crashing).
- runWithDevServerGuard: for a tracked managed dev server, stop → run → clear
  .next → restart (restored even if the operation throws); ProcessEntry now
  records the start mode so the restart is faithful.

Wired into the apply paths:
- /update and /security/cve-lite/[id]/fix — full block-self + orchestrate.
- /override-remove — block-self (before editing package.json) + orchestrate.
- /escalate — block-self only; orchestrated stop/restart is a follow-up.

Responses now carry a devServerGuard summary; self-patch returns 409.

Verified: 120 tests pass (12 new), typecheck clean, production build ✓.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
InstallGatePlugin (wrapInstall) + ComplianceAuditPlugin (audit) sharing
SecurityPluginBase. Forward declaration for ComplianceReport — concrete
shape lands with the first audit plugin. No registry, no consumers yet.

Refs spec docs/superpowers/specs/2026-05-26-security-refactor-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skeleton for SECURITY_PLUGINS array. First plugin registered in a later
commit so each step stays reviewable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ProjectConfig gains optional plugins?: Record<string, ProjectPluginConfig>.
getProjectPluginConfig / isPluginEnabledForProject (default false, opt-in)
+ setProjectPluginConfig with injected writer for testability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each plugin's isAvailable + renderCard runs concurrently. A throwing
plugin becomes an error-status card and does not affect siblings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
isSafeChainAvailable shells safe-chain --version (probe injectable).
PluginHostStatus carries version on success, reason+installHint on failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces head of [pnpm|npm|yarn, ...args] with the matching aikido-*
wrapper. Absolute paths handled via basename. No-op for unknown heads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure parser: exit 0 → not blocked; non-zero + BLOCK marker → blocked with
parsed advisory refs + tail-line message; non-zero without marker →
treated as regular install failure (caller bubbles up). Fixtures captured.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Assembles availability + rewrite + parse-result into an InstallGatePlugin.
renderCard branches on host availability + per-project enablement.
wrapInstall is a no-op unless both conditions hold; otherwise rewrites the
command and parses the result. Registered as the first SECURITY_PLUGINS
entry. Internals exposed under _internals for dep-injected tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chains wrapInstall calls left-to-right; processResult fans out
onResult in reverse and surfaces the first-blocking plugin plus a
per-plugin run summary. Pure logic, no IO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns the static plugin registry with each plugin's host status
(isAvailable result). Per-project status served by a sibling route.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns per-project plugin status (host availability + renderCard payload).
400 on missing projectId, 404 on unknown plugin or project.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches src/app/patches/page.tsx header exactly: border-b border-zinc-800
px-6 py-4 + h1 text-xl font-semibold + subtitle text-xs text-zinc-500 +
right-aligned actions (last-scan span + Rescan Button with RefreshCw
animate-spin). Uses shadcn Button + lucide-react + cn util.

Component only — not wired into /security/page.tsx (that lands in T18).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches src/app/patches/page.tsx summary bar (lines 1075-1102):
border-b border-zinc-800 px-6 py-3 bg-zinc-900/50 + horizontal flex row
of conditional [colored dot + count] chips per severity tone. Empty
state shows 'No findings' in text-green-400. Tones: red/orange/yellow/zinc
for critical/high/medium/low.

Component only — not wired into /security/page.tsx (that lands in T18).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three components: SourceCard (per ScanSource), PluginCard (per
SecurityPlugin), SourcePluginCards (grid container). Cards use the
existing zinc/dot tonal language. This row is the plugin-driven slot —
new plugins registered in src/lib/security/plugins/ surface here
automatically.

Not wired into /security/page.tsx yet (T18 does that).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the SecurityHeader + SecuritySummaryBar + SourcePluginCards row at
the top of the /security main content area. Restyle in place: keeps
FleetProjectRail, CveLiteToolbar, CveLiteScanControls, CveLiteManage,
PendingCommitBanner, FixPlan, CveLiteFindings, ConfirmDialog all intact.

Replaces the inline SourceStrip with the new card-style row that hosts
both ScanSources (pnpm-audit, grype, cve-lite) and SecurityPlugins
(Safe Chain). New plugins registered in src/lib/security/plugins/
appear here automatically.

Plugin status is fetched per-project via /api/security/plugins +
/api/security/plugins/[id]/status (T10 + T11). Severity counts on the
summary bar are derived from the existing CVE Lite report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors src/app/api/projects/[id]/holds/route.ts conventions: typed body
interface (no zod), getProjects()+findIndex+saveConfig pattern,
{success,error} response shape. AUTO_APPLY_ENABLED guard returns 409
matching /update.

Persists via setProjectPluginConfig (T3) — writer captures the index
update + saves the whole hexops.config.json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Minimal viable installGate integration: installPackages gains optional
installBinOverride that does a regex-replace on the leading shell token
of the built install command. /update computes the override before the
install call by asking each enabled installGate plugin's wrapInstall
([packageManager]) and taking the rewritten command head.

Safe Chain therefore routes through aikido-pnpm/aikido-npm/aikido-yarn
when enabled for a project; blocks fail the install naturally via the
existing error path. Audit-log meta gains an optional installGate
{ plugin, binOverride } annotation. No new response branches.

A richer integration that threads applyInstallGate.processResult into
the install spawn + adds a 409 'blocked' response is a follow-up — the
plugin orchestrator (T9) is already in place for that.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per-plugin detail page reached via the Configure link on the Safe Chain
card on /security. Sections: header, host-status, per-project enable
table (Disable/Enable buttons gated when host is missing). Consumes
GET /api/security/plugins, GET /api/security/plugins/safe-chain/status,
POST /api/projects/[id]/plugins/safe-chain.

Visual chrome mirrors SecurityHeader: border-b border-zinc-800,
text-xl/sm font weights matching the rest of the security restyle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/security is now a true fleet view that mirrors /patches' grouped mode.
Each project is a collapsible accordion row (border + chevron + name +
finding-count badge + per-project Scan button on the header). Expanding
shows that project's full CVE Lite UI inline (toolbar, scan controls,
manage strip, SourcePluginCards, FixPlan, CveLiteFindings, commit banner,
confirm dialog).

All per-project state, fetches, handlers, and effects moved from
SecurityHubInner into a self-contained ProjectSecurityAccordion
component. Parent page becomes a ~95-line orchestrator: fleet-aggregate
SecurityHeader + SecuritySummaryBar (sums across projects) + a list of
accordions. FleetProjectRail no longer imported by /security (the file
itself stays in src/components/security/ for other potential consumers).

URL ?project=<id> still works: that project starts expanded on mount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… state

SecurityHeader's Rescan button used to just re-read the
/api/security/findings cache, leaving the header stuck at '0 findings
across 0 sources / Never scanned' in a fresh worktree. Now it POSTs
/api/projects/[id]/security-scan for every project (mapWithConcurrency
limit 3) and shows 'Scanning N/M projects…' progress until done.
Empty-state copy replaces the misleading zero-counts with
'{N} projects · no scans cached yet'.

Per-accordion Scan button now also POSTs the 3-source scan endpoint
before the cve-lite scan and pings onAnyDataChanged so the header's
fleet aggregate updates as each project lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Header gets:
  - 'Scan ▾' button-group (no dropdown-menu in ui/): Scan All / pnpm /
    grype / cve-lite. Per-tool fleet scans use ?sources=<csv> query.
  - 'Sync OSV DB' button — exposes the existing /api/security/cve-lite/sync
    endpoint at fleet level (was previously only reachable through each
    project's CveLiteManage strip).
  - Per-source progress meters strip rendered during in-flight scans. Three
    slim bars (one per source), each advances as per-project responses land
    with that source in their results. Only the selected sources' bars
    render when running a single-tool scan.

Backend: POST /api/projects/[id]/security-scan accepts optional
?sources=<csv> query param. Filters SOURCES before invoking
scanProjectWithSources. CAVEAT: partial-source scans overwrite the
merged cache with only the scanned sources' results. Cache merging
across partial scans is a follow-up if it becomes a problem.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each project's collapsed accordion row now shows per-severity pills
(critical/high/medium/low) when findings exist — replaces the single
"N findings" outline badge. Pills only render for severity counts > 0,
ordered worst-first. Green ✓ Clean badge unchanged when no findings.
Color palette mirrors SecuritySummaryBar + Patches.

Per-project severity counts aggregated in the page-level refresh()
alongside the existing fleet aggregate and passed to each accordion
as an optional prop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pills on the collapsed row show 3-source merged severity counts. Until
now the expanded body only rendered cve-lite-only data, so a row with
grype-discovered findings looked empty when expanded.

Add an 'All findings (N)' section at the top of the expanded body that
renders the same merged-findings data the pills are derived from. Each
row: severity chip + package + version + advisory id(s) + sources chips
+ optional fix-version hint + divergent badge. Capped at 50 rows with
overflow text; existing CveLite fix-plan + per-finding Apply UI stays
below for cve-lite-driven workflows.

Per-project findings array piped from the page-level
/api/security/findings fetch as a new accordion prop alongside severity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…urceCard status

SourceCard: 'ok' status no longer reads green when the source has
findings. Tone now picked from (status, findingCount): ok+0 → green
'clean'; ok+>0 → amber 'N findings'; failed → red; unavailable →
zinc; timeout → orange. Finding count moves into the right-side
label; subtitle keeps just the ScanSource label + ms.

Accordion 'All findings' section is now collapsible (defaults open
when N≤10, collapsed otherwise to avoid 41+ row sprawl). Inside, flat
finding rows are replaced by per-package groupings: one row per
unique package@version with worst-severity chip + per-severity counts
+ sources union + optional fix version. Click a package row to
expand its individual CVE rows.

Each CVE row's advisory-id area shows a native title attr hover with
the finding's title, detail (truncated), CVSS score, and references
(shadcn HoverCard not available; native title fallback used).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
grype walks node_modules and reports vulnerabilities in embedded
binaries (e.g. stdlib@go1.23.12 inside node_modules/esbuild/bin/esbuild).
The 'package' grype reports is meaningless for remediation — the
actionable unit is the parent npm package that ships the binary.

New helper deriveParentPackage(finding) parses finding.path for the
outermost node_modules/<pkg>/ segment (scope-aware). The accordion
groups by parent package when one can be derived, falling back to
the existing package@version grouping otherwise. Header shows
'<parent> via <reported>' when they differ.

Also adds 'security' to LogCategory union (needed for B).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rail

Suppressing findings without accountability is anti-security. Replace
the would-be 'suppress' button with a proper exception/deviation system
backed by durable storage + classification + audit log.

Model (src/lib/security/exceptions.ts):
  - SecurityException = { id, projectId, parentPackage, scope:'parent-
    package', classification, reason, notes?, createdBy, createdAt,
    expiresAt?, reviewedBy?, reviewedAt?, revokedAt?, revokedBy?,
    revokeReason? }. Classifications: risk-accepted, false-positive,
    compensating-control, deferred, unfixable, deviation.
  - Storage: .hexops/exceptions-<projectId>.json — durable, NOT under
    cache/. JSON file per project, append-mostly so revoke history
    survives.
  - isExceptionActive() respects revokedAt + expiresAt; expiry lapses
    automatically on the next read.
  - activeExceptionParentSet(projectId) is the consumer-friendly query.
  - _setExceptionsDirForTest() mirrors the persistence.ts test pattern.

API:
  - GET  /api/projects/[id]/security/exceptions             — list all
  - POST /api/projects/[id]/security/exceptions             — file new
  - POST /api/projects/[id]/security/exceptions/[id]/revoke — soft-delete
  All mutations gated on AUTO_APPLY_ENABLED + audited via
  logger.info('security', 'exception_filed' | 'exception_revoked', …).
  Added 'security' to LogCategory union + updated exhaustive maps in
  log-viewer.tsx and log-reader.ts.

UI:
  - File-exception button on each actionable parent-package row in the
    'All findings' section. Click opens ExceptionDialog (classification
    dropdown + reason + optional notes + optional expiry).
  - New 'Active exceptions (N)' section in the accordion body lists
    open exceptions with classification badge, expiry, and revoke
    button.
  - Page-level severity aggregation + pills + fleet SummaryBar now
    EXCLUDE findings whose parent package has an active exception.
    Suppressed findings stay visible in the per-row 'Active exceptions'
    section.

Future (not in scope): fleet /security/exceptions overview page,
separation-of-duties approval workflow, expiry notifications, SARIF
export of exceptions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 'File exception' button on a PackageRow was nested inside the outer
whole-row click target which was a <button>. HTML disallows nested
button elements; React + Turbopack surface this as a hydration error.

Convert the outer click target to a <div role='button' tabIndex={0}>
with onClick + onKeyDown (Enter/Space) + aria-expanded + aria-controls.
Inner File-exception button stays as a real <button>. Visual chrome
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Accepted risk should be visually loud, not buried in the same zinc palette
as the rest of the chrome. Active exceptions section + each row get an
amber treatment (border, bg, AlertTriangle icon). Collapsed accordion
rows show an amber 'N exception(s)' chip alongside the severity pills
so reviewers see at a glance which projects have accepted-risk surface.

Each exception row is now click-to-expand (chevron + aria-controls)
revealing the full reason, notes, filed-by, dates, expiry. Edit button
alongside Revoke opens the ExceptionDialog in edit mode (pre-filled
values, 'Save changes' label) and PATCHes the new endpoint at
/api/projects/[id]/security/exceptions/[exceptionId]. Audit log
gets exception_modified with the changed fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
alamb-hex and others added 7 commits May 27, 2026 03:49
Per-project finding-states index at .hexops/finding-states-<id>.json
keyed by dedupKey. Runner diffs each scan against stored states and
emits logger.info('security', 'finding_detected' | 'finding_redetected'
| 'finding_resolved') with the finding id, severity, package, sources,
and advisory ids in meta. Every finding now has an audit trail of its
existence, not just operator actions on it.

UI: finding rows show 'first seen <relative>' from the persisted
firstSeenAt; findings detected within the last 24h get a 'new' chip.
SourceCards + PluginCards get scope subtitles ('lockfile scanner',
'filesystem/binary scanner', 'install-time interceptor') so
discrepancies between scanners are legible — lockfile scanners can't
catch what filesystem scanners find in embedded binaries, and the UI
should say so.

/api/security/findings now returns findingStates per project alongside
the existing findings/sources data; the security page propagates it
into each accordion which passes it to FindingRow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously rendered as plain zinc text — easy to miss next to the
severity pills, exception chip, and new chip. Convert to a pill with
cyan tones (border-cyan-500/30 text-cyan-300 bg-cyan-500/10), distinct
from every other color already in play in the security UI. Applied on
both FindingRow (per-CVE) and PackageRow (group-level).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each parent-package row in 'All findings' expands to a remediation
panel showing: detection source(s), package(s), fix version(s) with
the cyan pill, and an amber note when the fix version targets an
embedded artifact rather than the npm package directly.

Four actions per row:
  - Apply fix…       opens RemediationDialog (target version + override
                     checkbox); submits to /api/projects/[id]/update
                     with auditContext.source = 'grype'; opens the
                     existing PendingCommitBanner on success.
  - Override pin…    same dialog, override defaults on.
  - Send to Patches  anchor link to /patches?project=<id>#pkg-<name>
                     (the existing patches view; package anchor is
                     best-effort — future work to add a real filter
                     hook).
  - View references  modal listing every advisory URL across the
                     group's CVEs.

RemediationDialog pre-fills target version: for direct-match findings,
the first version from grype's fixedIn; for parent-embedded findings,
'latest' plus an amber note explaining the npm-level remediation maps
to the parent package rather than the embedded artifact.

Patch-scanner does NOT yet ingest grype findings — 'Send to Patches'
is a deep-link only. A real injection / queue hook is tracked separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RemediationDialog no longer closes when /update returns. It stays open
through the full Apply pipeline:

  configuring → installing → rescanning → verifying → outcome

Auto-rescan: after the install, POST /api/projects/[id]/security-scan
to refresh the merged 3-source cache. No more relying on the user to
hit Rescan manually after a fix.

Outcome verification: re-pull /api/security/findings and count how
many of this group's findings remain. Result drives one of three
outcome states inside the dialog:
  - resolved   — all findings cleared
  - partial    — some findings cleared
  - unresolved — no findings cleared (upstream may not have shipped
                 a fix yet; common for grype binary findings)

The unresolved/partial outcome surfaces a 'File exception' shortcut
so the user can pivot from 'tried to fix' to 'documented exception'
in one move — important when upstream is the blocker.

Error state surfaces the install/rescan/verify error inline.

Streaming install stdout to the client (SSE) is tracked separately
as a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every Apply now gets a generated attemptId (rem_<uuid>). All log events
for that attempt carry the same attemptId in meta so the lifecycle can
be reconstructed via grep:

  remediation_initiated         { attemptId, source, parameters }
  remediation_install_complete  { attemptId, source, packages, ... }
  remediation_install_failed    { attemptId, source, attemptedPackages }
  remediation_completed         { attemptId, outcome: { status,
                                  previousFindingCount,
                                  currentFindingCount,
                                  findingsCovered, findingsResolved,
                                  findingsRemaining } }

The existing security_remediation_applied event is renamed to
remediation_install_complete (semantically clearer) and threaded with
attemptId. The /update route now logs remediation_initiated BEFORE the
install runs so failure cases still have a record of intent.

New endpoint: POST /api/projects/[id]/security/remediation/[attemptId]
/complete. Client POSTs the verified outcome (resolved | partial |
unresolved | error) after the rescan + verify phases. No AUTO_APPLY
gate — it's audit metadata, not a mutation.

RemediationDialog surfaces the tracking ID in the outcome panel so
the user has a change-control reference to cite.

Also renames the cve-lite --fix route's security_remediation_applied
emit to remediation_install_complete under the 'security' category for
consistency. No remaining callers of the old event name.

cve-lite Apply also flows through /update so it now gets the same
remediation_initiated + remediation_install_complete trail with its
own attemptId once cve-lite's submit handler is updated to generate
one (small follow-up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Close the gap left by commit 02b82a1 (which wired change-control logging
on the grype path only). applyOne (per-finding cve-lite Apply) and
fixAll (cve-lite --fix bulk) now both generate a rem_<uuid> attemptId,
thread it through to /update or /api/security/cve-lite/[id]/fix in the
auditContext, and POST to /security/remediation/[attemptId]/complete
after the rescan + commit-banner steps.

The cve-lite fix route additionally logs remediation_initiated before
the fix runs and remediation_install_failed on the failure branches —
mirroring the /update route's pattern.

cve-lite Apply now produces the same threaded log trail as grype:
remediation_initiated → remediation_install_complete → remediation_completed,
all sharing one attemptId per Apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…te self-link (#117)

#116 — applyInstallGate (apply-install-gate.ts) and runAllPluginCards
(runner.ts) were scaffolding-only. /update hand-rolls a minimal single-plugin
binary-swap and never calls processResult; the plugins GET route is host-only
(no project context); and the accordion builds PluginCardEntry[] client-side
from the per-plugin status endpoint — so neither orchestrator had a prod caller
or a near-term one. Removed both modules + their tests. Preserved the live UI
contract by moving the PluginCardEntry type into types.ts and repointing its
five importers. Richer multi-plugin chaining + processResult block-parsing stays
the #116 follow-up (design in git history + spec), to build when a second
installGate plugin or block-parsing UX actually lands.

#117 — dropped sourceDeepLinks={{ 'cve-lite': '/security' }} from the security
accordion; the cve-lite card's "Open dashboard" linked back to the same page.
SourceCard gates the link on deepLinkHref, so removing the prop simply hides it;
the optional prop stays supported for future legitimate cross-page links.

Verified: typecheck clean, 215 tests pass, build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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