Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions docs/health-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ No underlying capability is silently removed:
Checks are read-only. Opening Health refreshes a missing or older-than-five-
minutes result once; no background timer polls the system. **Run checks** is
the explicit refresh action. A result has a stable identifier, group, label,
status, value, bounded detail, affected component, and an optional next action.
status, value, bounded detail, affected component, owner attribution, sanitized
source path, and an optional next action.
Check status is one of `ok`, `warning`, `error`, or `info`; report state is
`healthy`, `warning`, or `error`. The UI adds transient `checking` and initial
`not checked` states.
Expand Down Expand Up @@ -53,12 +54,30 @@ and recent runtime errors remain as quiet icon-and-text rows. Other successful
implementation checks stay hidden: they provide no user action and surface
automatically if their state becomes abnormal.

An expanded error exposes a stable `SHIBUMI-HEALTH/<CHECK-ID>` code and two
explicit actions. **Copy** places the bounded code, result, version,
component, evidence, and suggested action on the clipboard. **Open issue**
opens this repository's GitHub issue form with the same report prefilled; it
does not submit anything. The Copy action briefly changes to **Copied** as
feedback. Warnings remain review-only and do not encourage a
### Ownership attribution

Every check carries an `owner` of `shibumi`, `omarchy`, `third-party`, or
`unknown`, plus a sanitized `sourcePath` and optional `pluginId`. Runtime log
findings are grouped by this attribution instead of being presented as one
Shibumi error. `/usr/share/omarchy/**` findings are Omarchy-owned; installed
`hancore.shibumi.*` roots are Shibumi-owned; and unrelated user plugin roots,
including OmaConnect, are third-party-owned only when the local install state
or plugin registry verifies the ID. Bare names and unverified explicit plugin
fields remain `unknown`. Competing Shibumi and non-Shibumi sources on one line
also remain `unknown`; a canonical Omarchy path with a competing local path or
explicit foreign plugin ID is likewise ambiguous. An Omarchy path may still
outvote an incidental, unanchored plugin name. Ownership must not be assigned
by guesswork.

An expanded error exposes a stable `SHIBUMI-HEALTH/<CHECK-ID>` code and a
**Copy** action. Copy places only the bounded, sanitized code, result, owner,
version, plugin/source identity, evidence, and suggested action on the
clipboard. A Shibumi-owned error with `issueEligible: true` additionally gets
**Open issue**, which opens this repository's GitHub issue form with the same
report prefilled; it does not submit anything. Omarchy, third-party, and
unknown findings never receive a Shibumi issue action; their next step points
to the relevant owner or upstream support path. The Copy action briefly changes
to **Copied** as feedback. Warnings remain review-only and do not encourage a
bug report without evidence of an actual failure.

The collapsed report fits without a scrollbar. Expanding an Attention detail
Expand Down
30 changes: 28 additions & 2 deletions hancore.shibumi.control-center/ControlMainPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,26 @@ Column {
return "Checked " + Qt.formatDateTime(new Date(epoch * 1000), "HH:mm")
}

function ownerLabel(owner) {
if (owner === "shibumi") return "Shibumi"
if (owner === "omarchy") return "Omarchy"
if (owner === "third-party") return "Third party"
return "Unattributed"
}

function checkDetail(check) {
const lines = []
if (String(check.status || "") === "error")
lines.push("Code: " + diagnosticCode(check))
if (String(check.owner || "") !== "")
lines.push("Owner: " + ownerLabel(String(check.owner)))
if (String(check.detail || "") !== "") lines.push(String(check.detail))
if (String(check.component || "") !== "")
lines.push("Component: " + String(check.component))
if (String(check.pluginId || "") !== "")
lines.push("Plugin: " + String(check.pluginId))
if (String(check.sourcePath || "") !== "")
lines.push("Source: " + String(check.sourcePath))
if (String(check.action || "") !== "")
lines.push("Next: " + String(check.action))
return lines.join("\n")
Expand All @@ -126,12 +139,17 @@ Column {
const fields = [
"Code: " + diagnosticCode(check),
"Status: " + String(check.status || "unknown"),
"Owner: " + ownerLabel(String(check.owner || "unknown")),
"Check: " + String(check.label || "Unknown check"),
"Result: " + String(check.value || ""),
"Version: Shibumi " + installedShibumiVersion
]
if (String(check.component || "") !== "")
fields.push("Component: " + String(check.component))
if (String(check.pluginId || "") !== "")
fields.push("Plugin: " + String(check.pluginId))
if (String(check.sourcePath || "") !== "")
fields.push("Source: " + String(check.sourcePath))
if (String(check.detail || "") !== "")
fields.push("Detail: " + String(check.detail))
if (String(check.action || "") !== "")
Expand All @@ -149,6 +167,9 @@ Column {
}

function diagnosticIssueUrl(check) {
if (!check || String(check.status || "") !== "error"
|| check.issueEligible !== true
|| String(check.owner || "") !== "shibumi") return ""
const title = "[Health] " + diagnosticCode(check) + " · "
+ String(check.label || "Runtime error")
const body = "<!-- Generated by Shibumi Health -->\n\n```text\n"
Expand All @@ -158,7 +179,8 @@ Column {
}

function openDiagnosticIssue(check) {
Qt.openUrlExternally(diagnosticIssueUrl(check))
const url = diagnosticIssueUrl(check)
if (url !== "") Qt.openUrlExternally(url)
}

TextEdit {
Expand Down Expand Up @@ -414,6 +436,9 @@ Column {
readonly property bool expanded: interactive && root.expandedCheckId
=== String(check.id || "")
readonly property bool reportable: String(check.status || "") === "error"
readonly property bool issueEligible: reportable
&& check.issueEligible === true
&& String(check.owner || "") === "shibumi"

implicitHeight: rowContent.implicitHeight + Commons.Style.space(14)
radius: root.controller.controlRadius
Expand Down Expand Up @@ -547,7 +572,8 @@ Column {

CompactSettingChoice {
id: openIssue
width: Commons.Style.space(90)
visible: checkRow.issueEligible
width: visible ? Commons.Style.space(90) : 0
controller: root.controller
label: "Open issue"
primary: true
Expand Down
54 changes: 54 additions & 0 deletions hancore.shibumi.control-center/HealthService.qml
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,67 @@ Item {
return runChecks(false)
}

function sanitizeDiagnosticText(value, limit) {
let text = String(value || "")
.replace(/\u0000/g, "")
const containsSensitive = /authorization|cookie|credential|password|secret|ssid|token/i
.test(text)
text = text
.replace(/\r/g, "")
.replace(/\/home\/[^\/\s]+/g, "~")
.replace(/\b(?:https?|ftp):\/\/[^\s]+/gi, "[URL redacted]")
.replace(/\b(?:bearer|basic)\s+[^\s]+/gi,
"[authorization redacted]")
.replace(/\b(?:password|passwd|passphrase|token|secret|cookie|credential|ssid|authorization)\b\s*["']?\s*[:=]\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,}]+)/gi,
"[sensitive value redacted]")
if (containsSensitive
|| /authorization|cookie|credential|password|secret|ssid|token/i.test(text))
return "[sensitive diagnostic redacted]"
const max = Math.max(1, Number(limit || 320))
return text.length <= max ? text : text.slice(0, max - 1) + "…"
}

function acceptReport(raw) {
try {
const parsed = JSON.parse(String(raw || "{}"))
const owners = ["shibumi", "omarchy", "third-party", "unknown"]
const statuses = ["ok", "warning", "error", "info"]
if (Number(parsed.schemaVersion || 0) !== 1
|| !Array.isArray(parsed.checks)
|| typeof parsed.summary !== "string")
throw new Error("unsupported report")
parsed.checks = parsed.checks.map(function(check) {
if (!check || typeof check !== "object"
|| typeof check.id !== "string"
|| typeof check.status !== "string"
|| statuses.indexOf(check.status) < 0)
throw new Error("invalid check")
if (check.owner !== undefined
&& (typeof check.owner !== "string"
|| owners.indexOf(check.owner) < 0))
throw new Error("invalid check owner")
const normalized = Object.assign({}, check)
normalized.owner = check.owner === undefined ? "unknown" : check.owner
const rawDiagnostic = String(check.value || "") + " "
+ String(check.detail || "") + " "
+ String(check.component || "") + " "
+ String(check.sourcePath || "") + " "
+ String(check.action || "")
const sensitive = /authorization|cookie|credential|password|secret|ssid|token/i
.test(rawDiagnostic)
normalized.label = root.sanitizeDiagnosticText(check.label, 160)
normalized.value = root.sanitizeDiagnosticText(check.value, 160)
normalized.detail = root.sanitizeDiagnosticText(check.detail, 900)
normalized.component = root.sanitizeDiagnosticText(check.component, 240)
normalized.action = root.sanitizeDiagnosticText(check.action, 240)
normalized.sourcePath = root.sanitizeDiagnosticText(
check.sourcePath, 240)
normalized.pluginId = root.sanitizeDiagnosticText(check.pluginId, 240)
normalized.upstream = root.sanitizeDiagnosticText(check.upstream, 240)
normalized.issueEligible = normalized.owner === "shibumi"
&& check.issueEligible === true && !sensitive
return normalized
})
report = parsed
failure = ""
return true
Expand Down
Loading