hub v0.43.0: remote app-log diagnostics — copyable issues + context + on-demand log tails + range/dismissal fixes

- store: app_log_issues gains context/context_customer (first capture wins) + dismissed_at (resurface only on last_seen > dismissed_at); log_tail_requests (pending operator intents, consume-once) + app_log_tails (transient, keep last 2 per app)
- api: /report ingests log_tails (stores + clears the request); ACK advertises log_tail_requests (same additive omit-when-empty pattern as escrow)
- web: Known Issues rows click-to-expand (full copyable message + context with provenance + explicit affected-customers list); Dismiss replaces Delete; period selector now filters issues (F); ?customer= filtered view + customer-page drill-down links (H); per-app Request-log-tail button + pending badge + App Log Tails section + ordered tail view with line numbers + .log download; customer-visible log_tail_requested event
- tests: store (context first-capture/late-adopt, range filter, dismissal old-window vs new-occurrence, tail request/fulfill/prune/scoping), api ACK round-trip, web render (expanded row, customer page sections, tail view + download + cross-customer 404)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-10 16:03:32 +02:00
parent 0cc70a7a5a
commit c084046af0
15 changed files with 1180 additions and 44 deletions
+36
View File
@@ -440,6 +440,42 @@ func (s *Store) migrate() error {
return err
}
// v0.43.0 — remote app-log diagnostics. Additive columns on app_log_issues:
// context = JSON array of ±5 redacted lines around the FIRST occurrence (first capture
// wins — stable repro context, no churn); context_customer = whose box it came from
// (provenance); dismissed_at = dismissal instead of futile deletion (the controller
// re-sends recurring issues — a delete is undone minutes later; a dismissal stays until
// a genuinely NEW occurrence with last_seen > dismissed_at resurfaces the row).
s.db.Exec(`ALTER TABLE app_log_issues ADD COLUMN context TEXT`)
s.db.Exec(`ALTER TABLE app_log_issues ADD COLUMN context_customer TEXT`)
s.db.Exec(`ALTER TABLE app_log_issues ADD COLUMN dismissed_at DATETIME`)
// log_tail_requests: the operator's pending "send me this app's logs" intents — the
// pull-based ACK flag (the hub NEVER connects into a box; the controller sees the flag
// in its report ACK and ships the tail on its next cycle). One active request per
// (customer, app) — a re-click refreshes requested_at. Cleared when the tail arrives
// (consume-once). app_log_tails: the received tails, transient — keep the last 2 per
// (customer, app), older pruned at insert.
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS log_tail_requests (
customer_id TEXT NOT NULL,
app_name TEXT NOT NULL,
requested_at DATETIME NOT NULL,
PRIMARY KEY (customer_id, app_name)
);
CREATE TABLE IF NOT EXISTS app_log_tails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer_id TEXT NOT NULL,
app_name TEXT NOT NULL,
collected_at DATETIME NOT NULL,
received_at DATETIME NOT NULL,
lines_json TEXT NOT NULL
);
`)
if err != nil {
return err
}
return nil
}