feedBack/docs/diagnostics-bundle-spec.md
Byron Gamatos 7fb568c727
docs: correct plugin URL casing after the feedBack rename (#576)
Cosmetic follow-up to #537 (doc-only).

- Virtuoso: README + CHANGELOG used lowercase
  `got-feedback/feedback-plugin-virtuoso`; the canonical repo (like every
  other got-feedback repo) is capital-B `feedBack-plugin-virtuoso`. Brought
  it in line with the sibling rows.
- Community plugin references in CLAUDE.md, TODO.md, docs/, and the bundled
  tuner README were over-renamed to `feedBack-*` by the rename, but those
  repos are owned by community members who never renamed them
  (topkoa/stems+notedetect, OmikronApex/tuner, masc0t/update-manager).
  Restored their real `slopsmith-*` names. got-feedback's own `feedBack-*`
  references in the same files are left untouched.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:20:23 +02:00

19 KiB

FeedBack Diagnostics Bundle — Format Specification

This document is the authoritative reference for the feedBack-diag-*.zip file produced by Settings → Export Diagnostics (feedBack#166).

The bundle is consumed by humans (maintainers reading bug reports) and AI agents (auto-triage, code-aware assistants). Every JSON file inside the zip carries an explicit schema field so consumers can dispatch by version without guessing.


Overview

A diagnostic bundle is a plain ZIP archive. The default filename is:

feedBack-diag-<feedBack-version>-<YYYYMMDD-HHMMSS>.zip

Top-level layout:

feedBack-diag-0.2.4-20260503-143022.zip
├── manifest.json          AI-friendly index, schema 1
├── README.txt             Human-friendly: what's in here, how to read
├── system/
│   ├── version.json       feedBack + python + OS
│   ├── env.json           allowlisted env vars only (no secrets)
│   ├── hardware.json      backend hardware (container-limited if Docker)
│   └── plugins.json       loaded + orphan plugins, with git info
├── logs/
│   ├── server.log         tail of LOG_FILE (last ~5 MB), redacted if requested
│   ├── server.pretty.log  human-readable companion when LOG_FORMAT=json (auto-detected)
│   └── server.log.meta.json
├── client/
│   ├── console.json       all console levels + window errors + rejections
│   ├── hardware.json      browser-visible hardware: WebGL/WebGPU, host OS
│   ├── local_storage.json filtered
│   └── ua.json            browser, screen, page URL on export
└── plugins/<plugin_id>/   per-plugin contributed diagnostics

Sections are conditional on the user's include toggles (system, hardware, logs, console, plugins). Missing sections are not represented in manifest.json's files array.


manifest.json (bundle-level, schema 1)

{
  "schema": 1,                          // bundle schema; bump = breaking change
  "exported_at": "2026-05-03T14:30:22Z",
  "feedBack_version": "0.2.4",
  "runtime": "docker",                  // "docker" | "electron" | "bare"
  "redacted": true,                     // were redactions applied?
  "files": [
    { "path": "system/version.json", "kind": "json", "schema": "system.version.v1", "size": 312 },
    { "path": "logs/server.log",     "kind": "text", "lines": 41203, "size": 5242880 }
  ],
  "redactions": {                       // present when redacted=true
    "paths_replaced": 142,
    "ips_replaced": 3,
    "song_names_replaced": 27,
    "secrets_replaced": 1
  },
  "notes": [
    "container masks host CPU/RAM in system/hardware.json — real host info lives in client/hardware.json"
  ]
}

Field semantics:

  • schema: 1 — top-level bundle schema. Increment only on breaking changes to the layout (file moves, mandatory new sections). New optional fields are NOT a schema bump; consumers must ignore unknown keys.
  • runtime — single source of truth for "where was this bundle produced" so an agent can pick the right interpretation rules. See Runtime kinds.
  • files[].schema — present only when the file's first-level JSON object carries a string schema field (e.g. "system.hardware.v1").
  • files[].kind"json" | "text" | "binary".
  • notes — human-readable callouts. Always present; may be empty.

Per-file schemas

system.version.v1system/version.json

{
  "schema": "system.version.v1",
  "feedBack_version": "0.2.4",
  "python":   { "version": "3.12.4", "implementation": "CPython", "executable": "/usr/bin/python" },
  "os":       { "system": "Linux", "release": "6.5.0", "machine": "x86_64" },
  "exported_at": "2026-05-03T14:30:22Z"
}

system.env.v1system/env.json

{
  "schema": "system.env.v1",
  "vars": {
    "LOG_LEVEL": "INFO",
    "LOG_FORMAT": "json",
    "FEEDBACK_RUNTIME": "electron"
  }
}

Allowlisted env var keys only (see ENV_ALLOWLIST in lib/diagnostics_bundle.py): LOG_LEVEL, LOG_FORMAT, LOG_FILE, FEEDBACK_RUNTIME, PORT, HOST, TZ, PYTHONUNBUFFERED, DEMUCS_SERVER_URL. New entries require an allowlist edit; secrets must never be added.

system.hardware.v1system/hardware.json

{
  "schema": "system.hardware.v1",
  "runtime": { "kind": "docker", "in_docker": true, "in_kubernetes": false },
  "os":      { "system": "Linux", "release": "6.5.0", "version": "...", "machine": "x86_64" },
  "cpu": {
    "brand": "AMD Ryzen 9 7950X 16-Core Processor",
    "arch": "x86_64",
    "cores_logical": 32,
    "cores_physical": 16,
    "freq_mhz_current": 4500,
    "freq_mhz_max": 5700
  },
  "memory": { "total_bytes": 67108864000, "available_bytes": 42000000000 },
  "gpu": [
    {
      "source": "nvidia-smi",
      "name": "NVIDIA GeForce RTX 4070",
      "driver": "550.54.14",
      "memory_total_mb": 12282
    }
  ],
  "notes": ["container masks host CPU/RAM"]
}

gpu is a list (zero, one, or many entries). Source values in the wild: "nvidia-smi", "rocm-smi", "system_profiler". Container deployments without NVIDIA Container Toolkit will have an empty list and a notes entry explaining why.

system.plugins.v1system/plugins.json

{
  "schema": "system.plugins.v1",
  "plugins": [
    {
      "id": "stems",
      "name": "Stems",
      "version": "1.2.0",
      "type": null,
      "loaded": true,
      "has_screen": true,
      "has_script": true,
      "has_settings": false,
      "has_routes": true,
      "diagnostics_declared": true,
      "dir": "stems",
      "standards": ["capability-pipelines.v1"],
      "capabilities": {
        "stems": { "roles": ["owner", "provider"], "commands": ["mute", "restore"] }
      },
      "capability_validation_warnings": [],
      "capability_unsupported_versions": [],
      "compatibility_shims": [],
      "git": { "sha": "abc123d", "remote": "https://github.com/topkoa/slopsmith-plugin-stems.git" }
    }
  ],
  "orphans": [
    {
      "id": "broken",
      "name": "Broken Plugin",
      "version": "0.1.0",
      "loaded": false,
      "dir": "broken",
      "path": "/home/user/.config/feedBack/plugins/broken"
    }
  ]
}

orphans covers plugin directories that contain a plugin.json but are NOT in LOADED_PLUGINS. Two sub-cases:

  • Failed-to-load (no evicted field): the plugin id is not loaded at all — usually requirements install failure or manifest error. A plugin appearing only in orphans without evicted is the single best diagnostic signal for "user installed plugin X but it's not working".
  • Evicted/superseded ("evicted": true): the plugin id IS loaded, but from a different directory. Typical cause: bundled-wins logic discarded an old user-installed clone in favour of the in-tree copy. Also covers bundled plugin directories whose routes failed and whose server fell back to a user copy (the bundled dir then has a different path from the loaded entry). Check the server startup log for the specific failure reason.

dir is the bare directory name. path is the full resolved absolute path to the orphan directory — the key disambiguator when the bundled copy and a user-installed copy share the same directory name (e.g. both highway_3d). In a redacted bundle path has home-dir and config-dir prefixes replaced with placeholder tokens (e.g. <HOME>/..., <CONFIG_DIR>/...) so filesystem paths and usernames do not leak.

Capability fields are redaction-safe manifest metadata. Invalid capability declarations are excluded from capabilities and explained in capability_validation_warnings; legacy surfaces still appear as compatibility_shims so maintainers can see which old fields were bridged into the capability model. Unsupported future capability-pipelines versions appear in capability_unsupported_versions and should be treated as non-executable runtime intent.

Client-side capability snapshots contributed under plugins/capabilities/client.json use schema feedBack.capabilities.diagnostics.v1. They include current pipelines, participants, conflicts, missing providers, user overrides, active or orphaned claims, claim lifecycle records, compatibility shim hit counts, unsupported-version reports, and recent decisions. The runtime caps this snapshot at 64 KB by trimming older recentDecisions first while preserving current graph state.

logs.server.v1logs/server.log.meta.json

{
  "schema": "logs.server.v1",
  "log_file": "/data/log/feedBack.log",
  "exists": true,
  "size_bytes": 8388608,
  "tail_bytes": 5242880,
  "truncated": true
}

The companion logs/server.log is the raw text tail (UTF-8). When LOG_FORMAT=json, every line is independently parseable as JSON. When the file exceeds 5 MB, the partial first line is dropped before serialization so log parsers don't choke.

When the tail is JSON-per-line (auto-detected by content, not by env var), an additional logs/server.pretty.log companion is written: human-readable lines of the form <timestamp> [<LEVEL>] <event> k=v k=v. Mixed-format tails (a config flip mid-run) preserve non-JSON lines verbatim. The original server.log is still emitted unchanged for machine consumers. server.log.meta.json:pretty_companion is set to true whenever server.pretty.log is present.

client.console.v1client/console.json

{
  "schema": "client.console.v1",
  "entries": [
    {
      "t": 1714752622123,
      "kind": "console",            // "console" | "error" | "rejection"
      "level": "warn",              // "log" | "info" | "warn" | "error" | "debug"
      "msg": "WebSocket disconnected: 1006",
      "args": ["WebSocket disconnected: 1006"],
      "ua": "Mozilla/5.0 ...",
      "screen": { "width": 2560, "height": 1440, "devicePixelRatio": 1, "colorDepth": 24 }
    },
    {
      "t": 1714752623456,
      "kind": "rejection",
      "level": "error",
      "msg": "fetch failed",
      "stack": "Error: ...\n    at ...",
      "ua": "...",
      "screen": { ... }
    }
  ]
}

Bounded ring buffer: 500 entries, ~250 KB cap. Each entry's args may contain truncated stringifications of non-string console arguments — depth limit 4, key cap 30, string truncation at 1024 chars, circular refs serialized as "[circular]".

client.hardware.v1client/hardware.json

{
  "schema": "client.hardware.v1",
  "runtime": {
    "kind": "electron",
    "electron": "28.1.0",
    "chrome": "120.0.6099.109",
    "node": "18.18.2",
    "v8": "12.0.267.8",
    "app_version": "0.2.4"
  },
  "navigator": {
    "userAgent": "Mozilla/5.0 ...",
    "platform": "Win32",
    "hardwareConcurrency": 16,
    "deviceMemory": 8,
    "languages": ["en-US"]
  },
  "userAgentData": {
    "platform": "Windows",
    "platformVersion": "15.0.0",
    "architecture": "x86",
    "model": "",
    "bitness": "64"
  },
  "screen": { "width": 2560, "height": 1440, "devicePixelRatio": 1, "colorDepth": 24 },
  "webgl": {
    "available": true,
    "vendor": "Google Inc. (NVIDIA)",
    "renderer": "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0)",
    "version": "WebGL 2.0 (OpenGL ES 3.0 Chromium)",
    "shading_language_version": "WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)",
    "max_texture_size": 16384,
    "redacted": false
  },
  "webgpu": {
    "available": true,
    "adapter_info": {
      "vendor": "nvidia",
      "architecture": "ada",
      "device": "",
      "description": ""
    }
  }
}

runtime.kind rules:

  • "electron" if navigator.userAgent contains Electron/. Versions populated when the desktop launcher exposes window.feedBackElectron via a preload contextBridge.
  • "browser" otherwise.

webgl.redacted: true indicates the browser refused to expose the real renderer string (Firefox privacy mode, Safari ≥17). Treat the vendor and renderer fields as advisory in that case.

client.local_storage.v1client/local_storage.json

{
  "schema": "client.local_storage.v1",
  "data": { "<key>": "<value as string>" }
}

Every key/value in browser localStorage at export time. Plugins typically prefix their keys with their plugin_id.

client.ua.v1client/ua.json

{
  "schema": "client.ua.v1",
  "userAgent": "...",
  "url": "https://feedBack.local/",
  "screen": { ... }
}

Plugin diagnostics — plugins/<plugin_id>/...

Per-plugin directory. Two ways to populate it:

  1. diagnostics.server_files — relpaths under config_dir, copied verbatim. Same allowlist semantics as settings.server_files.
  2. diagnostics.callable<module>:<function>; called with ({"plugin_id", "config_dir"}). Return values:
    • dict / list → written to plugins/<id>/callable.json
    • bytes → written to plugins/<id>/callable.bin
    • str → written to plugins/<id>/callable.txt
    • other types → discarded with a warning Exceptions are caught and logged to the bundle's manifest.notes — a buggy plugin never crashes the export.

Plugins are encouraged to embed their own schema field ("<plugin_id>.diag.v1") in any JSON they emit so future tooling can dispatch by plugin schema.


Runtime kinds

manifest.runtime and system/hardware.json:runtime.kind and client/hardware.json:runtime.kind may take these values:

Kind Backend sees… Frontend sees… Cross-correlate?
docker container-limited host NO — different machines
electron host (Python is child) host YES — same machine
bare host host YES — same machine

Detection precedence (backend):

  1. FEEDBACK_RUNTIME env var ("electron"/"docker"/"bare")
  2. /.dockerenv exists OR /proc/1/cgroup mentions docker/ containerd/kubepodsdocker
  3. Parent process name matches electron or FeedBackelectron
  4. Default: bare

Detection (frontend): Electron/ in user agent → electron, else browser.


Redaction

Applied to logs/server.log text and client/console.json entry messages when redact: true (default). The bundle's manifest.json:redactions reports per-token-class counts.

Token grammar (stable within a single bundle, salted differently between bundles):

Token Source
<DLC_DIR> configured DLC root path
<HOME> user's home directory
<CONFIG_DIR> feedBack config directory
<song:HASH8> song filename / basename (8-char salted SHA-256)
<ip:HASH6> IPv4 / IPv6 address
<redacted> bearer token, key=/token=/api_key= query strings

hardware.json and plugins.json are NOT redacted (no PII). local_storage.json always has values for keys matching secret-name patterns (api_key, token, secret, password, auth, bearer, etc.) replaced with "<redacted>" — this happens unconditionally, regardless of the main redaction toggle, because plugin authors commonly store tokens in localStorage.


Versioning policy

  • Bundle schema (manifest.schema): integer. Bump on breaking layout changes (file relocations, removed required sections, incompatible structural changes to existing schemas). Today: 1.
  • Per-file schemas (<area>.<name>.v<n>): bumped independently. A bundle MAY mix old and new file schemas during transitions.
  • Adding optional fields to an existing schema is NOT a bump. Consumers MUST ignore unknown keys.
  • Removing a field is a bump.

Bundles older than the consumer's known schemas should be processed on a best-effort basis (display what's recognized, warn about the rest).


AI agent reading guide

Start at manifest.json. It lists every file with its schema id — dispatch on schema, never on path or filename heuristics.

Common symptom → file map:

Symptom Files to inspect
Audio not playing system/plugins.json (stems plugin loaded?), grep logs/server.log for ffmpeg/vgmstream, client/console.json for fetch errors
3D highway slow / black client/hardware.json (webgl.renderer, webgpu.adapter_info); client/console.json for WebGL warnings
Plugin error on load grep logs/server.log for Plugin %r, check system/plugins.json:orphans for failed-to-load
WebSocket disconnects client/console.json (level: "warn" / "error")
"Works on my machine" Diff system/version.json + system/env.json + system/hardware.json between bundles
Song-specific bug grep logs/server.log for the song's <song:HASH> token (stable across the bundle)
Cross-platform crash manifest.runtime + system/hardware.json:runtime + client/hardware.json:runtime
Cache / disk issue system/env.json:LOG_FILE, logs/server.log.meta.json:exists

When the bundle was redacted, the redaction token map is documented above. Two log lines mentioning <song:a3f1c2> are about the same song — but a bundle exported separately with the same song will use a different token.

When manifest.runtime == "docker", the backend system/hardware.json reports container-limited values. Real host CPU / RAM / GPU live in client/hardware.json only. Don't cross-correlate.

When manifest.runtime == "electron", both halves describe the same machine.


Plugin contribution contract

// plugin.json
{
  "id": "nam_tone",
  "name": "NAM Tone",
  "version": "1.0.0",
  "diagnostics": {
    "server_files": ["nam_tone.db.diag.json"],
    "callable": "diagnostics:collect"
  }
}

Frontend plugins push diagnostics by calling window.feedBack.diagnostics.contribute(plugin_id, payload) before the user clicks Export. The payload is written to plugins/<id>/client.json (gated on the same "Plugin diagnostics" toggle as backend plugin files).

Backend callable signature:

# plugins/nam_tone/diagnostics.py
def collect(ctx: dict) -> dict | bytes | str:
    """ctx: {'plugin_id': 'nam_tone', 'config_dir': Path(...)}"""
    return {
        "schema": "nam_tone.diag.v1",
        "models": [...],
    }

Best practices:

  • Return small payloads (< 100 KB). Diagnostics are not a backup channel.
  • Embed your own schema field in returned dicts.
  • Never raise — but if you do, the export keeps going and notes the failure.
  • Don't include user secrets, API keys, or session tokens.