Skip to content

feat(security): US4 collapse scan-notification storm into one settled event (Spec 077, MCP-2207)#794

Merged
Dumbris merged 2 commits into
mainfrom
077-us4-notify-collapse
Jul 2, 2026
Merged

feat(security): US4 collapse scan-notification storm into one settled event (Spec 077, MCP-2207)#794
Dumbris merged 2 commits into
mainfrom
077-us4-notify-collapse

Conversation

@Dumbris

@Dumbris Dumbris commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

Spec 077 US4 (MCP-2207): collapse the security-scan notification storm into a single debounced security.scan_settled event per server per scan.

The storm came from per-scanner scan_started/progress/completed/failed SSE events multiplied by reconnect storms (prior partial fixes: #659, MCP-2223). This replaces those per-scanner lifecycle emissions with one settled signal carrying the terminal findings summary.

Supersedes #787, which GitHub auto-closed when US1 (077-us1-baseline) was squash-merged to main as 5b925e45. This branch was rebased fresh onto main (--onto origin/main 0f5838e6) — no conflicts; US4 only touches the runtime scan-notify/event-bus layer and two frontend files, which do not overlap US1's detect/scanner changes.

Changes

Backend (internal/runtime/)

  • New scanNotifyDebouncer (scan_notify.go): terminal-triggered per-server debounce (750ms) with a generation counter guarding the AfterFunc race. Only completed/failed arm the timer; started/progress are dropped.
  • New EventTypeSecurityScanSettled; route runtime EmitSecurityScan* through the debouncer and publish one settled event with the terminal summary.
  • Wire scanNotify into newRuntime alongside the existing coalescer.
  • Collapse the activity log to one handleSecurityScanSettled record per scan, removing the started/completed/failed handlers.

Frontend

  • stores/system.ts: add a security.scan_settled SSE listener dispatching mcpproxy:scan-settled.
  • composables/useSecurityScannerStatus.ts: refresh cached scan totals off that single settled signal instead of per-scanner lifecycle events.

Verification (rebased head 1e46a820, actual output)

  • go build ./... → exit 0
  • go test -race ./internal/runtime/... -count=1 → all packages ok (runtime 118.3s)
  • Reconnect-storm race test: TestScanNotify_ReconnectStormCollapses PASS (2.00s), TestScanNotify_SettledCarriesTerminalSummary PASS (run with -race)
  • golangci-lint run --config .github/.golangci.yml ./internal/runtime/...0 issues
  • npx vue-tsc --noEmit (frontend) → exit 0

Review verdict on the prior head was approve.

Related: Spec 077 (specs/077-scanner-simplification), #786 (US1)

🤖 Generated with Claude Code

Dumbris added 2 commits July 2, 2026 07:55
…r server

Related #786

Spec 077 US4 (MCP-2207): the security-scan notification storm came from
per-scanner scan_started/progress/completed/failed SSE events multiplied by
reconnect storms (prior partial fixes: #659, MCP-2223). Replace those per-scanner
lifecycle emissions with a single debounced security.scan_settled event per
server per scan.

## Changes
- Add scanNotifyDebouncer (internal/runtime/scan_notify.go): terminal-triggered
  per-server debounce with a generation counter guarding the AfterFunc race;
  only completed/failed arm the timer, started/progress are dropped.
- Add EventTypeSecurityScanSettled; route runtime EmitSecurityScan* through the
  debouncer (started/progress become no-ops, completed/failed record terminal
  state) and publish one settled event carrying the terminal findings summary.
- Wire scanNotify (750ms) into newRuntime alongside the existing coalescer.
- Collapse the activity log to one handleSecurityScanSettled record per scan,
  removing the former started/completed/failed handlers.

## Testing
- scan_notify_test.go: a reconnect storm across N servers yields <= N settled
  events (exactly one per server) and zero per-scanner lifecycle events;
  settled event carries the terminal summary.

Related: Spec 077 (specs/077-scanner-simplification)
…lifecycle

Related #786

Spec 077 US4 (MCP-2207): forward the new security.scan_settled SSE event from the
system store as a mcpproxy:scan-settled window event, and have
useSecurityScannerStatus refresh its cached scan totals off that single settled
signal instead of tracking per-scanner lifecycle events.

## Changes
- stores/system.ts: add a security.scan_settled SSE listener that dispatches
  mcpproxy:scan-settled.
- composables/useSecurityScannerStatus.ts: register a module-scope
  mcpproxy:scan-settled listener that triggers a status refresh.

## Testing
- frontend vue-tsc --noEmit clean; vite build succeeds.

Related: Spec 077 (specs/077-scanner-simplification)
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1e46a82
Status: ✅  Deploy successful!
Preview URL: https://c0247aa6.mcpproxy-docs.pages.dev
Branch Preview URL: https://077-us4-notify-collapse.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 63.07692% with 24 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/activity_service.go 0.00% 11 Missing ⚠️
internal/runtime/event_bus.go 55.55% 7 Missing and 1 partial ⚠️
internal/runtime/scan_notify.go 85.71% 3 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: 077-us4-notify-collapse

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 28566532041 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris Dumbris merged commit 99c6390 into main Jul 2, 2026
47 checks passed
@Dumbris Dumbris deleted the 077-us4-notify-collapse branch July 2, 2026 06:04
Dumbris added a commit that referenced this pull request Jul 2, 2026
…iorities, audit fixes (#797)

A multi-agent consistency audit (2026-07-02) found roadmap.yaml stale versus
merged PRs and carrying several false progress badges from wrong spec links.
Corrected CI-filtered telemetry also re-prioritized the personal-edition work.

Statuses corrected per merged PRs:
- scanner-simplification children: US1 #786 / US2 #792 / US4 #794 marked done;
  US3 #793 in_review. Epic stays in_progress. Added deep-scan trust-fix task
  and flagged docs T037-T039 as merge-blocking for #793.
- registries-official-protocol marked done (spec 071 shipped 12/12, #572).

False badges / wrong provenance fixed (per the file's own convention — link
dropped, provenance moved into the note):
- sandbox-isolation no longer links spec 054 (unrelated security-gateway spec).
- ux-audit no longer links spec 064 (unrelated agent-fleet cockpit spec).
- marketplace no longer links spec 070 (that is the registries-search-add spec).
- action-log-transparency no longer links spec 024 (shipped backend, not the
  progress driver for the at-a-glance UX epic).

New epics (telemetry- and audit-driven replan):
- upgrade-nudge (P0, spec 079): ~60% of active installs run pre-v0.40.
- connect-trust (P0, spec 078): 72.4% skip the connect step.
- telemetry-identity (P1, in_progress): hashed machine_id + CI-filter hardening.
- planning-hygiene (P2): automate the checks this audit did by hand.

Windows QA gate: new first child windows-tray-funnel-qa (downloads→actives
12:1 vs macOS 4:1); windows-tray-window now depends on it.

ROADMAP.md regenerated; gen-roadmap.py --check passes.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Dumbris added a commit that referenced this pull request Jul 2, 2026
US1 (#786), US2 (#792), US4 (#794), and US3/deepscan-fixes (this branch) are all
merged. Check off every demonstrably-done task (verified against code + git log)
including the T037-T039 docs sweep. Left unchecked: T005 (contract schemas were
never copied to internal/security/scanner/testdata/ — tests validate behavior
directly), and the final validation gates T040-T042. Tally: 38/42.

Regenerate ROADMAP.md (077 now 38/42, in-flight).

Related #793

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dumbris added a commit that referenced this pull request Jul 2, 2026
…ion (Spec 077) (#793)

* feat(config): unified security.deep_scan block + deprecated-key migration

Add the opt-in security.deep_scan config group (Spec 077 US3) that subsumes the
deprecated top-level scanner_fetch_package_source / scanner_disable_no_new_privileges
keys and gates the heavy Docker scanner layer. deep_scan.enabled defaults false so
only the deterministic in-process baseline runs.

- Add DeepScanConfig{Enabled, FetchPackageSource, DisableNoNewPrivileges, Scanners}
  with swaggertype tags; add nil-safe effective accessors (IsDeepScanEnabled,
  DeepScanScanners, EffectiveFetchPackageSource, IsDisableNoNewPrivileges).
- Remove the orphaned auto_scan_quarantined key (ignored on load if present).
- migrateDeepScanConfig folds the deprecated top-level keys into deep_scan.* on
  every load/hot-reload (wired in initializeRegistries) and clears the legacy
  keys so a re-serialized config exposes only the new surface. Idempotent.
- Round-trip + ignore-removed-key tests.

Related: Spec 077 (specs/077-scanner-simplification)

* feat(security): opt-in deep scan that never blocks or degrades the baseline

Demote the Docker scanner plugins + source extraction to an opt-in "deep scan"
layer (Spec 077 US3). Off by default: only the deterministic in-process baseline
runs and no Docker is invoked; a deep-scan failure is surfaced informationally
and never changes the baseline verdict.

- engine: gate Docker (non-in-process) scanners on deep_scan.enabled via
  deepScanAllowed; when off, resolveScanners drops them entirely so no container
  runs and no failure can degrade the verdict. Optional per-scanner allow-list.
- service: SetDeepScan runtime knob; populate DeepScanDescriptor
  {enabled,ran,available,scanners_failed} from per-scanner job statuses,
  classifying baseline (in-process) vs deep scanners.
- service: remove degradeIfIncompleteCoverage — the scan verdict is now derived
  solely from baseline findings (FR-008/FR-014); a failed deep scanner no longer
  downgrades a clean baseline to "degraded".
- server: wire security.deep_scan.* into the scanner service; read the
  no-new-privileges / fetch-package-source knobs via the effective accessors so
  migrated configs resolve to the unified surface.
- Point user-facing hints/logs at the new deep_scan.* config keys.
- Tests: deep-scan-off-by-default (baseline only, no Docker) and deep-scan
  failure leaves the baseline verdict unchanged with a populated descriptor;
  update the MCP-34.4 Docker-scanner tests to enable deep scan and the former
  MCP-2401 degrade tests to assert the baseline-only verdict.

Related: Spec 077 (specs/077-scanner-simplification)

* docs(api): regenerate OpenAPI for security.deep_scan config surface

Regenerate oas/swagger.yaml + oas/docs.go for the new config.DeepScanConfig
schema and the removed auto_scan_quarantined key. make swagger-verify passes.

Related: Spec 077 (specs/077-scanner-simplification)

* feat(web): surface opt-in deep scan; render deep-scan gaps as info

Spec 077 US3 Web UI: present deep scan as an opt-in affordance and render a
failed/unavailable deep scanner as informational, never an error — the baseline
verdict is authoritative.

- Security.vue: info banner clarifying the deterministic baseline is always on
  and the Docker scanners are an opt-in deep scan that never blocks/degrades it.
- ScanReport.vue: DeepScanDescriptor info block (alert-info) listing unavailable
  deep scanners with an explicit "does not affect the baseline verdict" note.
- api.ts: DeepScanDescriptor type + optional deep_scan on the report.

Related: Spec 077 (specs/077-scanner-simplification)

* fix(security): gate source extraction on deep_scan + wire deep-scan report banner

Address adversarial review findings on Spec 077 US3 (PR #789):

- #1 (MUST): published-package-source extraction is now part of the opt-in
  deep-scan layer. server.go computes the effective fetch as
  IsDeepScanEnabled() && fetch_package_source (default true), and
  SetDeepScan(false) force-disables the resolver's fetch fallback as
  defense-in-depth. Deep scan OFF (default) => no npx/uvx source-fetch
  network egress. Added TestServiceDeepScanGatesPackageSourceFetch and
  updated the doc comments to match.

- #2 (MUST): the deep-scan report banner was dead code — AggregatedReport
  had no DeepScan field, so /scan/report and /scans/{jobId}/report never
  emitted deep_scan. Added DeepScan *DeepScanDescriptor (json deep_scan,
  omitempty) to AggregatedReport and populate it in GetScanReport and
  GetScanReportByJobID (the live report-page path).

- #3 (LOW): buildDeepScanDescriptor now inspects Pass-1 AND Pass-2 scanner
  statuses (variadic jobs, deduped) so heavy trivy/supply-chain scanner
  failures are reflected in scanners_failed/availability.

Baseline verdict logic and the quarantine state machine are unchanged.

Related: Spec 077

* fix(security): confine source resolution + Pass 2 to the opt-in deep-scan layer

Spec 077 US3 promised that with deep scan OFF (the default) only the
deterministic in-process baseline runs — no Docker, no source extraction,
no network egress. Scanner EXECUTION was already gated, but source
RESOLUTION passes still ran. Close the three gaps:

- Finding #1 (HIGH): gate Pass-1 sourceResolver.Resolve on deepScanEnabled().
  With deep scan off, source resolution is skipped entirely (the baseline
  scans tool DEFINITIONS, not source files, so resolved source is unused) —
  no Docker container lookup/extraction and no package-source fetch.
- Finding #2 (HIGH): gate the Pass-2 auto-start (startPass2 → ResolveFullSource,
  the heavy Docker/full-source pass) on deepScanEnabled() in addition to the
  existing not-dry-run/not-URL conditions. Off ⇒ Pass-1 baseline only.
- Finding #3 (MEDIUM): add deep_scan (DeepScanDescriptor: enabled/ran/
  available/scanners_failed) to contracts.SecurityScanSummary, populate it in
  the server.go enricher adapter (was dropped), remove stale "degraded"
  references from the contract comments, and regenerate OpenAPI.

Tests: SourceResolver gains atomic Resolve/ResolveFullSource call counters;
new service-level tests assert neither runs with deep scan off (baseline still
settles a deterministic verdict) and both run with deep scan on; new server
adapter test asserts deep_scan is carried onto the wire summary when populated
and omitted when off.

Invariants preserved: isBlockingFinding stays tier-driven; no
degradeIfIncompleteCoverage; determinism/offline baseline unchanged.

Related: Spec 077

* fix(security): hot-reload deep_scan config + migrate legacy keys on apply (Spec 077 US3)

Codex round-2 findings on PR #793:

Finding #1 (HIGH) — deep_scan changes now hot-reload. DetectConfigChanges
compares Config.Security (deep_scan.{enabled,fetch_package_source,
disable_no_new_privileges,scanners} + deprecated top-level keys) so a lone
security.deep_scan.* edit is reported as a change instead of "no changes
detected". The server subscribes to config.reloaded (both file-edit and
/api/v1/config/apply emit it) and re-runs the scanner wiring via a new
Service.ApplySecurityConfig, so toggling deep scan takes effect without a
restart. Startup and reload now configure the scanner through the same call.

Finding #2 (MEDIUM) — /api/v1/config/apply now normalizes identically to
LoadFromFile. ApplyConfig runs config.MigrateDeepScanConfig on the submitted
config before diffing/saving, folding the deprecated
security.scanner_fetch_package_source / scanner_disable_no_new_privileges keys
into security.deep_scan (auto_scan_quarantined has no struct field, dropped at
decode). An API apply carrying the deprecated keys now saves only the unified
deep_scan surface (SC-007).

Tests: DetectConfigChanges deep-scan detection; Service.ApplySecurityConfig
reconfigures a live service without restart (incl. legacy-key fallback);
ApplyConfig legacy-key migration asserted on the saved file + runtime config.

Constraints respected: no new dependency; tool_quarantine.go untouched; no
US1-removed behavior reintroduced; determinism/offline preserved.

Related: Spec 077

* fix(security): deep-scan trust fixes — FR-014 baseline-only verdict, always-on deep_scan descriptor, enable-time hint

Three verified audit findings against the Spec 077 US3 deep-scan layer
(re-checked against head 7e1d51a before fixing):

FIX 1 (nil-Security gating) — already fixed at HEAD by a25ae2f/7e1d51ad:
source resolution + Pass 2 are gated on deepScanEnabled() at the call
sites and ApplySecurityConfig(nil) forces the layer off via nil-safe
accessors. This commit adds defense-in-depth (the server wiring now calls
ApplySecurityConfig unconditionally, even when Config itself is nil) and a
regression test pinning the exact audit scenario: config.DefaultConfig()
never initializes Config.Security, and that nil block must still yield
deep-scan OFF, no Docker scanners resolved, package-source fetch OFF.

FIX 2 (FR-014 verdict purity) — the "warnings" level was driven by
ThreatLevel across ALL findings, so a tierless deep-scan/external finding
at threat_level=warning flipped a clean baseline to "warnings", while a
tierless threat_level=dangerous finding fell into the Info bucket (an
inversion: LESS effect than warning). GetScanSummary now derives the
verdict at EVERY level solely from baseline (tiered) findings: dangerous
requires >=1 hard-tier finding (unchanged predicate, shared with the
approval gate), warnings requires >=1 warning-level baseline (soft)
finding. Tierless findings still surface in FindingCounts — a tierless
dangerous now counts at warning prominence instead of info — and in the
merged report/RiskScore (FR-009..FR-012 consensus weighting untouched),
but they never move the verdict.

FIX 3 (silent Docker-scanner skip):
(a) buildDeepScanDescriptor no longer returns nil when the layer is off;
    it always emits {enabled:false, ran:false, skipped_scanners:[ids of
    enabled-but-skipped Docker scanners]}, making quickstart scenario 1
    (deep_scan.enabled=false) actually observable in scan/report JSON.
    Field added to scanner + contracts descriptors, REST/SSE projection,
    frontend TS type; OpenAPI regenerated.
(b) POST /security/scanners/{id}/enable now returns a "hint" when a
    Docker-based scanner is enabled while security.deep_scan.enabled is
    false, and `mcpproxy security enable <id>` prints it ("scanner
    enabled, but it will not run until security.deep_scan.enabled=true"),
    plus help-text. SecurityController grew DeepScanEnabled() (implemented
    by scanner.Service already).

Assumptions documented (zero-interruption policy):
- Tierless threat_level=dangerous findings are bucketed as Warning in
  FindingCounts (inform-without-gating prominence) so counts cannot
  contradict the tier-driven Dangerous count / verdict.
- SkippedScanners lists installed/configured (i.e. enabled) non-in-process
  scanners only; merely "available" scanners are not "enabled" and are not
  listed.
- TestGetScanSummaryBothPasses and the descriptor-omitted assertions
  encoded the pre-FR-014 behavior and were updated to the spec-mandated
  contract.

Tests: new regression tests for all three fixes (scanner service, httpapi
handler, CLI hint extraction); go test -race across security/server/
config/httpapi/contracts/cmd green; golangci-lint v2 clean; frontend
vue-tsc clean; scripts/test-api-e2e.sh 65/65.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(security): FR-014 verdict purity on the report page — share the tier-driven verdict with the report payload

Codex review follow-up. The server-list summary (GetScanSummary) derived
the verdict tier-driven/baseline-only (FR-014), but the report page badge
still read the RAW threat-level ReportSummary — with deep scan on, a
tierless Docker-scanner finding classified "dangerous" made the report say
dangerous while the server list said clean, and the same finding counted
as Warning on one surface and Dangerous on the other.

- extract deriveBaselineVerdict() as the single source of truth, shared by
  GetScanSummary and AggregateReports so the two surfaces structurally
  cannot disagree
- AggregatedReport gains Verdict + tier-driven FindingCounts (additive;
  raw Summary counts retained for transparency)
- ScanReport.vue: status badge, threat tiles, Quarantine-button gate and
  the Approve-disable gate (hasUnresolvedCritical) now read the
  tier-driven verdict/finding_counts (raw summary only as a fallback for
  pre-Spec-077 payloads); the approve gate now mirrors the backend
  hard-tier-only isBlockingFinding instead of raw summary.critical
- ServerDetail.vue: approve-confirmation count and the Security-tab
  summary strip prefer tier-driven finding_counts over the raw report
  summary, so a tierless deep-scan "dangerous" finding no longer triggers
  the "Dangerous Findings Detected" modal on a clean baseline
- frontend/src/types/api.ts: SecurityScanSummary catches up with the wire
  contract (deep_scan, scanners_run/failed/total); SecurityScanReport
  gains verdict + finding_counts

Tests: engine-level AggregateReports verdict matrix (tierless-dangerous →
clean/warning-bucket, hard → dangerous, soft → warnings) and an
end-to-end pin that GetScanReportByJobID.Verdict/FindingCounts equal
GetScanSummary for the same scan data.

Assumption documented (zero-interruption policy): ServerCard.vue was
reported as sharing the raw-summary preference but already reads only the
tier-driven security_scan.finding_counts — left unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(security): Spec 077 truth sweep — detect engine is sole in-process detector, deep_scan opt-in

Reconcile the security docs with the post-077 code:

- tool-scanner.md: remove the deleted legacy-TPA-rule coexistence section;
  the detect engine is now the sole in-process detector. Count the actually-
  registered checks — seven (four hard incl. phrase.injection, three soft) —
  and fix the front-matter, "The seven checks", and at-a-glance table.
- security-scanner-plugins.md: drop the legacy-rules claim + six-check count
  from the tpa-descriptions row; document the security.deep_scan block as the
  primary config surface; remove the deprecated auto_scan_quarantined /
  scanner_fetch_package_source docs; note the deep-scan gate + enable hint.
- docker-isolation.md (+ top-level copy): the sandbox/none Docker-scanner skip
  no longer downgrades to security_scan.status:"degraded" (FR-008); it surfaces
  via the always-emitted deep_scan descriptor. Replace the deprecated
  scanner_disable_no_new_privileges instruction with deep_scan.disable_no_new_privileges.
- security-quarantine.md: replace the deleted keyword-heuristic Detection
  Patterns table + Security Analysis JSON with the real quarantine-block shape
  and a pointer to the seven-check detect engine.
- configuration.md: add the security.deep_scan config block + migration note.

Related #793

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(spec-077): reconcile tasks.md checkboxes with shipped reality

US1 (#786), US2 (#792), US4 (#794), and US3/deepscan-fixes (this branch) are all
merged. Check off every demonstrably-done task (verified against code + git log)
including the T037-T039 docs sweep. Left unchecked: T005 (contract schemas were
never copied to internal/security/scanner/testdata/ — tests validate behavior
directly), and the final validation gates T040-T042. Tally: 38/42.

Regenerate ROADMAP.md (077 now 38/42, in-flight).

Related #793

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(server): close bolt DB before TempDir cleanup (Windows unlink failure)

TestScanSummaryEnricherAdapterCarriesDeepScan passed its assertions but
failed on windows-latest: t.TempDir cleanup cannot unlink config.db
while the bbolt handle is open. setupTestStorage registers no close;
add an explicit t.Cleanup in this test's seed helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Dumbris added a commit that referenced this pull request Jul 2, 2026
…ne, connect-trust/upgrade-nudge progress (#803)

check-github now passes with 0 errors: scanner-simplification epic
complete (#786/#792/#793/#794 incl. deep-scan trust fixes + docs
sweep); connect-trust US1 preview (#802) + backup visibility (#799)
done; upgrade-nudge status/log slice (#798) split out as done with
the banner+config remainder tracked separately; telemetry machine_id
client (#796) and hygiene check-github (#800) done. Remaining
warnings are the known windows-tray no-PR-evidence items.

Co-authored-by: Claude Fable 5 <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.

2 participants