# CTM Feedback And Telemetry Design

The feedback flow has two distinct jobs:

1. Help a user file useful CTM/WALL-E feedback from the product UI.
2. Let maintainers retrieve submitted feedback from the Fly.io telemetry server
   with the telemetry skill.

Local CTM storage is still useful for drafts, autosave, and recovery, but it is
not the destination for the primary "Send feedback" action.

## UX Contract

The composer defaults to **Send to CTM maintainers**.

Destination choices:

- **Send to CTM maintainers** (`maintainer_report`): sends the title,
  description, quality-check result, session metadata, evidence manifest, and
  attachment metadata to the Fly.io feedback endpoint. Transcript and
  scrollback previews stay local.
- **Send with selected evidence** (`diagnostic_bundle`): sends the same report
  plus selected evidence previews. This is the explicit high-context path for
  rendering bugs, session state issues, and other problems that need transcript
  or terminal excerpts.
- **Save locally only** (`local_only`): saves in the CTM SQLite database only.
  Maintainers cannot retrieve it through telemetry.
- **Remember in local Wall-E** (`local_walle`): saves in CTM and writes a local
  Wall-E memory. It does not upload to Fly.io.

The modal must state where the report goes in plain language near the
destination selector and in the "Where this goes" side panel. The submit button
label changes with the selected destination so the final action is explicit.

## Data Flow

1. The browser creates and autosaves a draft through
   `POST /api/feedback/drafts` and `PUT /api/feedback/:id`.
2. The quality check triages the local draft and prepares an evidence manifest.
3. `POST /api/feedback/:id/submit` marks the draft submitted locally.
4. CTM routes the submitted payload based on `consent_level`:
   - `maintainer_report` and `diagnostic_bundle` call the remote feedback
     client.
   - `local_walle` calls the local Wall-E memory ingest endpoint.
   - `local_only` stops after the local DB update.
5. The Fly.io Wall-E server stores remote reports in `feedback_reports` and also
   records a `ctm_feedback_shared` telemetry event for aggregate counts.
6. The telemetry skill retrieves reports through the authenticated telemetry
   feedback endpoint.

## Remote API

Client upload:

- `POST /api/wall-e/feedback/submit`
- Body:
  - `id`: pseudonymous install id
  - `machine`: stable pseudonymous machine bucket
  - `v`, `os`, `node`: client version/runtime fields
  - `user`: optional owner hash/name fields controlled by telemetry settings
  - `feedback`: report payload

Server retrieval:

- `GET /api/wall-e/telemetry/feedback?key=...`
- Optional filters: `limit`, `offset`, `type`, `status`, `severity`, `q`
- Returns submitted report rows with parsed `triage_result`,
  `evidence_manifest`, `context_previews`, and `attachments`.

Telemetry summary also includes:

- `feedback_count`
- `feedback_by_type`
- `feedback_by_status`
- `recent_feedback`

## Abuse Controls

The Fly.io feedback endpoint is public, so it must assume direct hostile
traffic. Protection is layered:

- **Trusted client IP source**: on Fly.io, prefer `Fly-Client-IP`. Use
  `X-Forwarded-For` only as a fallback because clients can influence it outside
  a trusted proxy chain.
- **Strict envelope validation**: remote feedback requires a telemetry install
  id, machine bucket, feedback id, remote consent level, and title or
  description. The endpoint does not synthesize identities for malformed
  clients.
- **Observed IP trust**: an IP address is not treated as trusted identity data
  until the same install/machine pair has been observed on that IP for at least
  24 hours through telemetry or feedback submissions. Before that, the endpoint
  may still throttle the IP as an abuse source, but it does not write the IP
  into report telemetry as trusted user identity.
- **Durable rate limits**: feedback submissions are limited to 2 per minute
  across install id, machine bucket, install+machine identity, and the observed
  IP bucket. Limits are stored in SQLite so a process restart does not clear
  them.
- **Duplicate suppression**: a normalized hash of title, description, type,
  severity, consent, evidence manifest, and selected diagnostic previews is
  tracked per install+machine identity. Repeated identical reports return the
  original feedback id and do not create another report/event row.
- **Payload limits**: the upload body is capped at 128KB. The server also caps
  individual text fields, selected context preview count, and attachment
  metadata count before writing to storage.
- **Authenticated retrieval**: report reads stay behind
  `WALLE_TELEMETRY_SECRET`.

If the Fly deployment grows to multiple machines without a shared SQLite volume,
move `feedback_rate_limits`, `feedback_ip_identities`, and
`feedback_report_dedupe` to a shared store such as Redis or a single write
region before increasing traffic.

## Storage

Local CTM table `feedback_items` tracks both remote and local-Wall-E state:

- `remote_feedback_id`
- `remote_status`
- `remote_error`
- `walle_memory_id`
- `walle_status`

Fly.io telemetry database table `feedback_reports` stores submitted remote
reports. Context previews are stored only for `diagnostic_bundle`; attachment
storage is metadata-only unless a future upload path explicitly adds image
bytes.

## Privacy

- Feedback upload is intentional and user-visible. It is not hidden product
  telemetry. Automatic telemetry opt-out does not disable an explicit feedback
  submission; set `CTM_FEEDBACK=0` to disable this upload path completely.
- The default remote path excludes transcript and scrollback previews.
- The high-context evidence path is opt-in in the destination selector.
- Local-only and local-Wall-E paths do not upload to Fly.io.
- The telemetry server keeps the same pseudonymous install/machine model as
  existing telemetry.

## Admin Board

The future feedback inbox/triage board should not use the normal CTM browser
token as the only authorization boundary. CTM is currently a local-first
single-user app, but the board aggregates screenshots, transcript excerpts,
paths, model IDs, and agent diagnostics. Treat it as an admin surface from day
one.

Access model:

- Default: the board is disabled.
- Local owner access: localhost can open the board only after an explicit admin
  unlock action in settings.
- Remote access: require a separate `CTM_ADMIN_TOKEN`, not just `ctm_token`.
- Future multi-user: replace the token with an `admin_users` allowlist stored in
  CTM settings and backed by whichever identity provider exists then.

Recommended request gate:

1. Request must already pass CTM auth.
2. Request must pass `isAdminRequest`.
3. Mutating admin routes must require a CSRF-safe POST with the admin token.
4. Every board read/write is recorded in `feedback_admin_audit`.

Board routes:

- `GET /admin/feedback`: board shell, hidden unless admin-unlocked.
- `GET /api/admin/feedback`: list feedback items with redacted previews.
- `GET /api/admin/feedback/:id`: detail view.
- `POST /api/admin/feedback/:id/status`: change lifecycle state.
- `POST /api/admin/feedback/:id/export`: create an issue/task/export bundle.

Board UX should use a dense operations layout:

- Left rail: saved filters by status/type/severity.
- Center list: title, status, actionable score, session link, latest event.
- Right detail: evidence manifest, screenshots, triage transcript, Wall-E memory
  result, remote submit result, and fix lifecycle.
- Default previews are redacted. The admin explicitly expands high-sensitivity
  transcript or scrollback context.
