# Portable reports and attempt evidence

The [shared report decision](https://github.com/CosticaPuntaru/SnagBadger/issues/4#issuecomment-6101797873)
is the canonical product decision. This implementation supplies portable data and
deterministic processing, independently of React, the browser, and a server.

## Working example

Use the pinned toolchain and run:

```sh
pnpm install --frozen-lockfile
pnpm example:report
```

Open `dist/report-example/index.html` directly. The example promotes a comment on
a passing test into a bug report, keeps the actual detour, and redacts a diagnostic
value. Its data is illustrative; no browser is recorded and no ticket is created.
`report.json` contains machine-readable data. A bundle with file evidence also
contains an `attachments/` directory. Copy the whole directory to reopen offline.

## Supported entries

| Entry                          | Responsibility                                                                                                                |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `@snagbadger/capture`          | Portable evidence and recorded-interaction types; no capture engine yet                                                       |
| `@snagbadger/reporting`        | Validate, create, revise, and remove evidence from bug reports                                                                |
| `@snagbadger/reporting/bundle` | Explicit sanitization, portable export/import, offline viewer, provider delivery                                              |
| `@snagbadger/agent-reporting`  | Test-definition snapshots, retained attempts/comments, report promotion, capture checkpoints, regression-readiness assessment |

Functions-only consumers do not import React, CSS, DOM globals, Node libraries,
or the optional bundle/viewer code. All entries use the repository's shared
package version. Serialized report/run/bundle `formatVersion: 1` is a data-format
version, independent of package releases; unsupported versions fail explicitly.

`prepareReportExport(report, options)` reviews narrative and attachment bytes once and returns a validated manifest plus attachment files. It performs no viewer rendering. Bug-report bundles and Test-run exports consume this shared preparation while retaining their own report models. Binary redaction, omission, or unavailable attachment proof clears reproduction and step verification just as narrative sanitization does.

## Reports and edits

`createBugReport({ id, at, ...content })` accepts incomplete content. Missing
expectations, outcomes, and executable reproduction are `null`; missing narrative
text is empty. Hosts supply IDs and ISO timestamps. Reproduction steps are authored
instructions; recorded interactions are observations. Evidence can contain
structured JSON, an attachment descriptor, or an explicit unavailability reason.

`parseBugReport(unknown)` validates and detaches the complete report. It rejects
unknown fields, duplicate identities, dangling evidence/interaction references,
invalid readiness claims, non-JSON diagnostics, and excessive JSON nesting.
Unknown fields are rejected rather than silently losing their meaning. Credentials
and individual agent/collector identities are not modeled fields.

Shared validation is available through `parseReportEvidence`, `parseExpectation`,
and `parseReportTimestamp`. Evidence validation accepts the actual outcome,
hypotheses, steps, interactions, evidence, and reproduction fields together; it
checks their links without requiring a report identity. Test-run parsing uses the
same rules while retaining its own lifecycle and identity checks.

`invalidateReproductionVerification` applies the shared verification rules to
steps and reproduction data. Revisions and sanitization clear step verification;
a changed script additionally becomes incomplete. A different problem clears the
replay claim while preserving verified actions. These operations do not mutate
the supplied state. Normal callers use revision, export, or comment promotion,
which apply these rules automatically.

`reviseBugReport(report, changes, at)` preserves stable identity and the source
snapshot, increments revision, and invalidates previous reproduction/step
verification. `removeReportEvidence` also removes its direct links from steps and
interactions. Changes do not mutate previous values. Source snapshots cannot be
rewritten through the edit API; they are subject to the export review policy.

## Results, comments, and promotion

`parseTestRun(unknown)` snapshots developer-defined tests separately from execution
attempts. `appendAttempt` retains earlier attempts, while `addResultComment` adds
comments to any result. Comments may link to an attempt or describe a result that
has not yet run. Their basis identifies observations, hypotheses, or context.

`defaultRunPolicy(retrySummary)` requires an explicit developer selection of
`latest`, `any-failure`, or `passed-on-retry`. Comment type defaults are problem,
optimization, suggestion, and context; importance defaults are low, medium, high,
and critical in ascending order. Both lists are configurable. Only an explicit
`allowAnyCommentType` permits novel AI-chosen types; importance must remain on the
configured scale. The default helper is optional.

Bind both a failed-result action and the comment's **Create a bug** action to
`createBugFromResult(run, { id, at, resultId, attemptId?, commentIds? })`. It creates
the same editable `BugReport` used by manual reporting. It initially includes the
entire selected attempt's evidence and interactions. Absent authored steps, it
derives unverified instructions from nonempty recorded action descriptions.
It never substitutes the intended test steps for the actual journey.

A comment's incidental problem does not inherit the intended test's expectation
or replay-verification claim. Observation comments populate actual behavior;
hypothesis comments remain hypotheses. Multiple comments from one attempt may
support one report. Mixing comments from different attempts is rejected rather
than silently constructing a reproduction sequence that never occurred. Selecting
an earlier failure explicitly remains possible after a successful retry.

The source snapshot includes the test definition and relevant comments, with
run/result/attempt/comment references. Individual agent identities are deliberately
absent. Provider delivery identifies the authenticated submitting user.

## Sanitization and self-contained export

`createReportBundle(report, options)` requires a named sanitization policy:

- `reviewReport` reviews the whole report, including source snapshots, diagnostic
  keys/values, filenames, and executable instructions. It must preserve identity.
- `reviewAttachment` explicitly includes replacement bytes or omits each available
  attachment. There is no implicit pass-through of screenshot/video originals.
- `readAttachment` supplies bytes by evidence ID, or `null` for unavailable data.
  Retrieval/review errors abort the export; they never trigger a raw fallback.

`redactReportSecrets` is an exact-string helper, including nested values and keys.
It is **not automatic sensitive-data detection** or an image/video sanitizer.
Projects own sanitization rules and binary review. A safe policy may omit binary
attachments until it can review them. The API cannot independently verify that a
developer-provided reviewer identified every secret.

The returned relative files contain only reviewed data and bytes. A manifest records
reviewed/redacted/omitted/unavailable states without removed values. Attachment paths
are generated, never taken from original filenames. Missing/oversized/omitted
attachments retain an explicit evidence record. Script changes during sanitization
mark executable reproduction incomplete; transformed report content does not carry
stale verified instructions. Each export has its own ID and policy identifier: it
is a sanitized derivative of the identified report revision, not an edit of the
in-memory source report.

The format uses `report.json`, `index.html`, and optional `attachments/<n>.<ext>`.
The host chooses directory/archive/download transport. The initial viewer is
unstyled, script-free semantic HTML. It escapes untrusted text, previews only
supported raster/video types, and treats other attachments as downloads. It is not
the final reporter or test-run UI. The structured artifact can be read selectively
without loading attachment bytes into an agent's context.

`importReportBundle` validates versions, paths, byte lengths, references, and the
generated viewer; extra files or hidden originals are rejected. Imported bytes are
copied. It never executes script fields. Byte lengths are consistency checks, not
cryptographic authenticity. Import is not a trust decision and does not replace
sanitization before another submission. The initial format caps JSON text at
10 million characters, JSON nesting at 50 levels, and attachment bytes at 250 MB;
export options can tighten the attachment limits (100 MB per attachment by default).

## Delivery and reproduction boundaries

`submitBugReport(report, options, provider)` uses the same export boundary and
passes the complete sanitized bundle plus readable report data to the provider.
The adapter resolves environmental credentials and produces a receipt identifying
the authenticated user, ticket, URL, and whether the bundle was retained. Delivery
returns report identity/revision and export identity alongside that receipt.
`IncompleteDeliveryError.delivery` preserves the created ticket reference after a
partial delivery. The library does not retry automatically or synchronize edits.

Executable reproduction records a project-selected runner, script, setup-hook
names, required input names, missing prerequisites, and verification evidence.
Those fields are data, not authority to execute imported code. This module does
not supply runners, resolve ticket URLs/IDs, restore login/backend state, or run
developer hooks. Those remain with the dependent replay/provider work. Local
bundles preserve the information those workflows need.

Capture flags combine independently: full video, requested screenshots, each
completed step, the completed final planned step, and the actual end of an attempt.
`requestedScreenshots` identifies every applicable checkpoint, including early
failure. A runner can satisfy simultaneous triggers with one image linked to each
reason. The policy helper does not itself record a browser or generate agent skills.

`assessRegressionCandidate` requires an established expectation, executable
instructions, detection on the buggy state, a pass on the fixed state, and evidence
references for both. It assesses supplied validation results; it does not generate
or execute tests. Missing states keep the artifact a candidate. This is separate
from reproducing a problem before a fix exists.

The [Markdown test workflow](agent-testing.md) now supplies catalog execution,
runtime callbacks/filesystem-bridge orchestration, retained attempts, and a reviewed
HTML run report with editable local bug drafts. A host coordinator still supplies
the actual agent/browser tools; the bridge is not an embedded MCP client or an
unattended automation runner. [Runtime mappings](testing-runtimes.md) describe its
isolation and installed-capability requirements.

Live capture, the final in-app reporter, supplied Jira/Linear authentication and
transport, ticket-link replay, and regression-test generation remain separate
product work. No server/storage design, release, merge, or deployment is introduced
by these portable report APIs.

## Verification

Run `pnpm verify` for the full production gate. The report/result module tests
exercise incomplete and heuristic journeys, retries, independent policies,
promotion, snapshot isolation, sanitization failures/omissions, import validation,
and provider partial delivery. A real Chromium journey opens a bundle from disk
with a screenshot, confirms no external requests, and checks literal malicious text
cannot execute. Built consumer checks verify optional loading and portable types.

Export and provider delivery share one private preparation path. Delivery receives
the sanitized report and its matching bundle without serializing and importing it
again. Independently supplied bundles still undergo full import validation,
including attachment checks and viewer verification.
