Report Schema
Every explore strategy writes the same result shape (schemaVersion: 1), so a suite
runner, the ledger or a report can read any run without knowing which strategy produced it. The
same object is data in --json output, result in the persisted
<stem>.result.json, and what MCP get_mission_result returns.
Exit codes
One table for every jevitate command:
| Exit | Class | Meaning |
|---|---|---|
0 | ok | clean, succeeded, check passed, fixed, or the command did what it was asked |
1 | defects | defects found, a gating finding (check), still reproduces (verify-fix, ledger verify, regression run), a success check that did not hold, a stale journey demo |
2 | inconclusive | the run could not finish its work (inconclusive/crashed, a check item errored or the budget ran out, a queued mission could not run, a starved host refused a new run with E_HOST_STARVED, ai status found a key invalid or unverifiable). It proves nothing. |
3 | hang | the app hung, and the hang reproduced on replay |
4 | intermittent | a hang, or a verify-fix signal, fired on some but not every replay |
5 | pending-review | 0.9.0: a self-heal produced a proposed Journey revision (healed-pending-review). Not a failure and not a pass: review it (journey review), then journey promote <id> --proposal <pid>. Missions never end this way. |
64 | usage | a usage or input error, and nothing ran: a bad flag or argument, an unreadable input file, an unknown id, missing keys or a key the provider rejects (E_AI_SETUP_REQUIRED), an unsafe store id (E_INVALID_NAME, 0.14.0), an invalid campaign spec, a target outside the allowlist, --headed without a display, a production environment for demo |
130 / 143 | killed | SIGINT / SIGTERM; the partial result is still written |
Without --json, a command prints a human summary on stdout and a refusal prints
error <CODE>: <message> on stderr. With --json, stdout is one
line: {v, ok, data} or {v, ok: false, error: {code, message}}. The
exit code is the same either way.
Mission outcome
missionOutcome | Exit | Meaning |
|---|---|---|
clean | 0 | finished its budget and found nothing (goal: the success checks held) |
defects-found | 1 | at least one confirmed defect (goal: a success check did not hold) |
inconclusive / crashed | 2 | the run could not do its work (too little coverage, a vacuous check, an unreachable or unresponsive app, a starved host), so its silence proves nothing |
not-started (goal, 0.9.0) | 2 | a goal run that executed no actions maps to inconclusive and carries a goalReason (no-controls, auth-failed, preflight-failed, no-actions) |
hang | 3 | the app hung, and it reproduced on replay |
intermittent | 4 | a hang was observed but did not reproduce on every replay |
missionOutcome is always the canonical verdict, a goal run included. A goal's own
ending is in goalOutcome (succeeded → clean; 0.9.0 adds not-started → inconclusive, a run that never acted, which is never reported succeeded or defects-found (a goal accepted with --allow-vacuous-checks keeps succeeded);
failed, exhausted, blocked → defects-found).
outcome is strategy-specific (a goal's ending, a frontier run's stop reason) and not the
portable verdict. When a run broke or proved nothing, failure.kind says why
(insufficient-coverage, vacuous-check, target-unresponsive,
degraded-environment, stalled (a renderer froze; never a crash),
resource-limit (the run's browsers went over --max-browser-memory),
journey-stale (a --from-journey prefix no longer replays; since 0.7.0 the failure carries the page's URL, title and a short redacted visible-text excerpt),
host-starved (0.9.0: a run stalled while the host was starved; inconclusive, with the measurements in failure.message; JEVITATE_HOST_STARVATION=off turns it off), identity-changed (an action switched the signed-in user and it could not be restored), auth-expired (0.8.0: the persona's session was expired before the mission started; failure.persona names whose), …). Full list:
docs/outcomes.md.
Goal outcome and defect outcome (0.8.0)
A goal run carries two orthogonal verdicts. goalOutcome says whether the agent reached
the goal; defectOutcome ({status: "none" | "defects", byKind}) says whether the
app broke, counting the gating entries of defects[] per kind. A defect never replaces
goalOutcome: a goal reached on an app that answered 500 is goalOutcome: "succeeded"
with defectOutcome.status: "defects". missionOutcome, and so the exit code, is
derived from both by one table:
goalOutcome | defectOutcome: none | defectOutcome: defects |
|---|---|---|
succeeded | clean (0) | defects-found (1) |
failed / exhausted / blocked | defects-found (1) | defects-found (1) |
defects-found | defects-found (1) | defects-found (1) |
hang | hang (3) | hang (3) |
intermittent | intermittent (4) | intermittent (4) |
inconclusive / crashed | that outcome (2) | that outcome (2) |
A goal that was not achieved with no defect still exits 1; read defectOutcome.status: "none"
and goalReason to tell "untested because the agent could not do it" from "tested and found
bugs". A hang or a broken run dominates its defects, which are still listed and counted.
goalReason (on every goal result whose goalOutcome is not succeeded)
is decided by code from the run's state, never parsed from reason:
goalReason | Meaning |
|---|---|
success-check-failed | the model said done, but an independent success check did not hold |
not-found | a find-out goal's report found no answer on the pages it searched |
ungrounded | the model's answer was rejected as not grounded on the observed pages until the run gave up |
blocked-by-policy | the model gave up after the safety policy refused a control it chose |
gave-up | the model reported the goal cannot be advanced from the page |
no-progress | the run stopped because its actions left the page unchanged |
budget | the action or decision budget, or a spend budget, ran out |
hang | hang or intermittent |
vacuous-check | every failing check was satisfied before the first action |
broken-run | the run itself broke or proved nothing: a crash, an unreachable or unresponsive app, a starved host, an unreadable --log-defect oracle, a kill |
defects | goalOutcome: "defects-found" with no more specific cause |
The human summary prints both (GOAL blocked (not-found, stop: blocked), DEFECTS server-log 2),
and jevitate report shows them per run. Results written before 0.8.0 do not have
defectOutcome or goalReason.
Common fields
| Field | Meaning |
|---|---|
schemaVersion | 1. Increases when a common field changes incompatibly; new fields are additive. |
strategy, missionOutcome, goalOutcome, exitCode | What ran and its verdict (above). |
defects | Every defect, whichever oracle found it, each with a 16-hex fingerprint and a kind. advisory: true marks one reported without gating (e.g. Jev's opinion alone); it never sets the outcome and check never gates on it. |
defects[].evidence | --evidence-video only: videoPath (the captioned repro clip), screenshots (just before and at the failing step), failingStep, signal, reproduced, and skipped / captureSkips when media was refused. verify-fix --record-video adds before / after. |
defects[].relatedLogs | --log-triage only: the backend lines and browser signals that relate to this defect, each {epochMs, source, level, text, …} with keptBy request-id (correlated to the defect's request), jev (with a score) or window (the step's error and warning lines, when Jev did not score). Redacted. Signal triage. |
signals | --log-triage only: {path, entries, truncated, triage: {mode, defects, candidates, kept, jevCalls, capped}}. path is the <run>.signals.jsonl timeline next to the result. |
hangs | Every hang, with its fingerprint and reproduction. |
recordingPaths, transcriptPath, resultPath | The Recordings, the decision transcript, and the persisted result. |
videoPaths | --record-video only: every video the run recorded. |
screenshotPaths, screenshotIndex, screenshotsSkipped | --screenshots only: the masked images, the index.md contact sheet, and {step, reason} for each capture refused. |
goalReason, defectOutcome | 0.8.0: why a goal was not reached, and the gating defects counted by kind (advisoryByKind for advisory ones). Outcome table. |
defects[] (server-log) | 0.8.0: every server-log defect carries level, source (the --log-source spec), message (redacted), firstSeenStep and count, so an aggregator never parses reason. |
environmentFaults, expectedValidation | 0.8.0, runs with --log-defect: environmentFaults.causes is [{ruleId, source, message, count}] for backend log lines classed environment (a placeholder key, an unconfigured provider), never defects and never failing the run; expectedValidation is the same shape for validation errors the mission's own input caused. Listed once per batch in jevitate report. Not to be confused with environmentDegraded (a starved host). |
target.id | 0.9.0: the target the run was meant to cover, set by explore --target <id> and by sweep for each target. Key attribution on it, not on the start URL. |
serverLogs.quietSources | 0.8.0: --log-source specs that attached healthy but read zero lines. A working oracle, not a failure; oracleOk is false only when a declared source failed to attach or errored, and oracleReason names each one. |
depth, partialReport | 0.8.0, goal runs: depth is distinct states and pages, actions, decisions, forms submitted and the minimum effort that applied (met); partialReport is a find-out's grounded per-page account when it could not ground an answer. |
safetyOverrides | 0.8.0: each click an --allow-control exemption permitted: {regex, control, ruleId, pattern, step}. |
tags | 0.8.0: the run's --tag key=value metadata, present only when the run was tagged. Never redacted: never put a secret in a tag. |
blockedWrites | Adversarial runs: each misuse write (a POST, PUT, PATCH, DELETE or native form post) aborted in the browser because it targeted an origin outside --allow, with its origin and path and how to allow it. It is never reported as a defect of the app. Safety. |
sideEffects | Every write request the run fired (third-party ones marked). |
target, scope | The seed URL and allowlist (0.8.0 adds startUrl, strategy and persona); the route scope a frontier run was contained to. |
usage | Model calls, tokens and cost (totalUsd, priced). |
hostHealth, environmentDegraded | How starved the host was, and advisories met while it was. A slow render counts as starvation only when host load is at least 1 runnable task per core. hostHealth.resources records what resource governance did: the browser cap and slot, throttle levels, the memory ceiling and the peak browser memory. |
branch | --from-journey runs: {journeyId, step, anchor}, also on each finding, so a defect names the Journey step it was reached from. |
actionDeltas | --action-deltas only: verdict counts and per-action overhead. Each transcript (and goal Recording) step gets a delta: what the action changed, redacted, with a code verdict (no-change, relevant-change, inconclusive). |
transcript[].waitedMs | Time spent in an explicit wait (--reply-wait-ms, a wait step, --job-wait-ms). It is left out of the step's render timing. |
transcript[].dialogs | A step whose action raised a native dialog: each one's type, message, whether it was accepted or dismissed, and why. The default policy is dismiss. |
defects[].judgment | A judgment-flagged-state defect: {probability, after, shown}, the probability Jev gave, the action before it and the controls shown. Still advisory. |
engine | The build that produced the result (below). |
"defects": [{
"kind": "http-5xx",
"fingerprint": "b8b841bad287ceb7",
"title": "HTTP 500 from /demo/api/profile",
"evidence": {
"videoPath": ".jevitate/logs/<date>/adversarial-<stamp>.evidence/b8b841bad287ceb7/clip.webm",
"screenshots": ["…/before-step-4.png", "…/at-step-4.png"],
"failingStep": 4,
"signal": "server returned 500 (PUT /demo/api/profile)",
"reproduced": true
}
}],
"videoPaths": [".jevitate/logs/<date>/adversarial-<stamp>.videos/…webm"],
"screenshotPaths": ["…/adversarial-<stamp>.screenshots/01-profile.png"],
"screenshotIndex": "…/adversarial-<stamp>.screenshots/index.md"
jevitate report (MCP get_report) and report.md carry
relatedLogs on each consolidated defect: the most recent run's, at most 20.
Removed in 0.3.0, after being deprecated in 0.2.0: serverLogDefects (use the
server-log entries in defects), recordingPath (use
recordingPaths[0]), usage.usd (use usage.totalUsd) and
usage.jevPriceSource. schemaVersion stays 1; a 0.2.0 result that still has them validates,
and report, ledger and verify-fix still read them from older result files.
Adversarial invariants and slow saves
A misuse step waits up to 5 s for the writes it started to end before its declared invariants are
checked. If one is still pending, that step's require / always invariants are
inconclusive: counted as unknown, never a violation and never a pass, and the report
names the writes it waited on (for example PUT /api/profile). A step whose write was
blocked (see blockedWrites) does not have its invariants judged either. Invariants with
settle.withinMs keep their own re-check window.
Journey assertion results
journey verify --mutate --json reports journeyId, journeyHash,
base.outcome, each mutation's outcome, failedSites and
blockedWrites, each assertion's site, check, verdict
(sensitive, insensitive, cascade, not-applied,
error, unpaired) and provedBy, and summary counts;
exit 0, 1 or 2 as in Journeys.
journey lint --json returns {id, findings, errors, warnings}. A Journey promoted
with --accept-weak "<reason>" records the reason and the waived rules in
metadata.acceptedWeak.
Sweep results: sweep.result.json (0.8.0)
jevitate sweep writes one aggregate, rewritten after every finished run, with
kind: "sweep". Each target's own result is a normal result under
<out>/<id>/. See Sweeps & Personas.
Field Meaning missionOutcome, exitCodeThe worst target outcome; inconclusive while incomplete or after a stop. complete, reason, aborted, interruptedWhether every target finished, and why not. summaryTargets, ran, resumed, errors, skipped, pending, counts by outcome, deduped defects (advisory ones excluded) and environment failures. summary.notStarted, summary.retried0.9.0: goal targets that executed zero actions (goalOutcome: not-started), counted apart from runs that exercised the app; and targets retried after a host-starved stall. targets[]Per target: id, url, persona, strategy, tags, status (ran, resumed, error, skipped, pending), missionOutcome, goalOutcome, exitCode, defectOutcome, depth, safetyOverrides, failure, environmentFailure, the defect count, and the run's resultPath and envelopePath. defects[]Every defect and hang deduped by fingerprint across targets: one entry per fingerprint with its kind, title, sightingCount, the targets that saw it and one sightings[] entry per target. A defect seen on five pages is one finding with five sightings. environment.causes[], environment.failures[]Every run's environmentDegraded causes grouped by rule, source and message; and runs that failed for an environment or setup reason, grouped by kind.
Journey heal outcomes (0.9.0)
A journey run --self-heal hybrid|full (and check --self-heal) result has an
outcome of ok, healed-pending-review, heal-exhausted or
quarantined. The legacy healed outcome, which exited 0, is gone.
Outcome Exit Meaning healed-pending-review5 the run completed only after retargeting steps the change explains. A proposed revision was written to .proposals/<journeyId>.json; proposal is {id, path, steps, reviewCommand, acceptCommand}. The stored Journey is unchanged. heal-exhausted1 the heal ran out of candidates or budget. The full attempt log (hypothesis, change evidence, candidate, observation, rejection reason) is in result.json and jevitate report.
check.json: baselines and locator health (0.10.0)
Each Journey item of jevitate check (--out default jevitate-check/check.json) can carry:
Field Meaning journeyHashthe review hash of the Journey the item replayed; what its baseline is about. baselinepresent only when the run was clean: steps, totalMs (the first step's start to the last step's completion) and anchors[] of {name, step?, stepId?, atMs}: job_start at 0, each of the Journey's anchors with the milliseconds to its step completing, and job_end. A run that did not complete records no baseline. locatorHealthper item: stable, brittle and steps[] of {stepId?, index, rung, stability, reasons}, where rung is the locator rung that resolved the step (testId, anchor, role+name, role, label, text, css) and stability is stable or brittle. top-level locatorHealth the summary over all Journey items: journeys, stable, brittle, testIdAttributes, maxBrittleSteps and exceeded (when the gate is set), a summary line, and with --baseline a trend (improved, regressed, unchanged, added, removed, brittleDelta, line).
catalog export and publish journeeze read the baselines from this file. The
locator-health --json report and a Journey run's result.json use the same rungs.
A Journey run's result.json (0.10.0) also carries resolved[]: for each step, how
its target actually resolved at replay — {index, stepId?, rung, testIdAttr?, ordinal?, candidates?},
where testIdAttr is the attribute a test id matched (absent = data-testid) and
ordinal/candidates appear when equally named elements had to be told apart — and a
compact locatorHealth for the run. Both are advisory and never change the run's outcome.
The review sheet (journey review --json, MCP review_journey) adds
anchorWarnings[] (code, severity, path, step?, message, fix?)
and locatorHealth (line, stable, brittle and brittleSteps[] with
stepId?, index, kind, rung, level, route?, locator,
reasons and the fix for the app). With --stale and no id it lists the Journeys whose
approval is stale, each with its approved and current hash and stepIdOnly: true when the only change is minted step ids.
An approval's provenance.channel can now also be pr-review; a check or
catalog status with --require-approvals reports an unverifiable one as unverified.
Exit codes did not change in 0.10.0. job approve refusing a broken job
(E_JOB_BROKEN_REF) and journey promote refusing anchor-rule errors
(E_JOURNEY_ANCHOR_RULES) are exit 64; publish journeeze exits
0 (imported), 1 (refused) or 2 (the upload could not finish), and 64 for a setup problem.
Scope regions: coverage and scope.region (0.14.0)
With --scope-region (or a Journey anchor's region), the result changes as follows:
- Adversarial:
coverage.controls is the region's total and exercised controls and decides sufficient. coverage.pageControls is the route-wide count the region excluded, for a reviewer and never in the decision. coverage.region gives the spec and the step counts. - Region lost: a step whose page has no matching region counts in
outOfScopeSteps. After 2 consecutive such steps the run goes back to its start; scope.region.lostResets counts those resets. - Coverage and exploratory carry
scope.region: spec, outOfRegionControls (distinct controls seen outside the region) and stepsWithoutRegion. - scope-unreachable: a region that never matches ends
inconclusive, stop scope-unreachable, failure.kind: "target-unreachable", never clean.
Store ids (recordings, missions, mission targets, publishes) are letters, digits, ., _ and -, starting with a letter or digit, up to 128 characters. An empty id, .., a slash, NUL, : or a drive prefix is refused with E_INVALID_NAME (exit 64), replacing the old "path traversal risk" error.
Blocked writes are environment faults (0.11.0)
A request jevitate refused to send because its origin is not in --allow is an environment fault, not an app defect.
What it provably caused within that action's window (the same step or the next one) is linked to it with causedBy and grouped
under it in reports: a console or page error naming the blocked URL, a failed request with its exact method, origin and path, or a busy
indicator that appeared with the write and never cleared. Anything less certain (a generic "Failed to fetch", a later step) stays an app defect.
A run that had only such findings may now exit clean. jevitate also warns when a blocked loopback origin differs from an allowed one only by
localhost / 127.0.0.1 spelling or by port. Fix the allow list, then prove it with
verify-fix --allow or --env.
Other 0.11.0 result fields: slowSteps (a late 2xx, with its latency) and slowness.coldStarts (advisory, never an exit code),
offAllowlistWrites on a Journey run, preflight: { ok, problems } (and preflight.skipped) in campaign.json,
and check.appVersion. Exit codes did not change: a refused campaign preflight, a refused catalog export and a Journeeze product mismatch are exit 64.
A busy indicator that clears on a page whose CSP forbids 'unsafe-eval' is no longer reported as a ui-no-progress hang.
Approvals and review sheets (0.8.0)
A promoted Journey records metadata.approval:
{ contentHash, at, provenance?, acceptedWeak?, waivers?, acceptedFindings? }. waivers
is [{ kind: "unvetted", reason, items }] from --accept-unvetted;
acceptedFindings records the reason and the acknowledged findings from
--accept-findings. provenance is
{ channel: "tty" | "non-interactive" | "mcp" | "ci" | "pr-review", agentSignals, user?, reason? }:
agentSignals names environment markers (never their values) and user is the OS user
name. An approved persona or job carries approval in personas.json /
jobs.json in the same shape ({ contentHash, at, provenance }, plus
acceptedFindings). Provenance is detection, not proof; see
what human approval guarantees.
journey review --json (and MCP review_journey, review_persona,
review_job) returns the schema-checked sheet: summary, steps, side effects, inputs, proof, the
change since last approval, the content hash, the catalog links, the pre-approval findings, and with
--readiness the readiness checks (readiness/<code>, GtWR findings
readiness/gtwr:<rule>@<field>) and a jev field reporting what ran
(status, asked, cached, usage). catalog status --json
adds approvals. With --require-approvals, jevitate check adds an
approvals item with a hard approval finding per missing, stale or
disallowed-channel approval.
The engine field
Every mission result (goal, feature, coverage/exploratory, adversarial, usability) carries an
engine object identifying exactly which build produced it:
"engine": { "version": "0.8.0", "commit": "a1b2c3d", "builtAt": "2026-09-23T18:04:11Z" }
commit and builtAt come from a git-rev-parse computation baked in at
build time (by esbuild for the published bundle, or a generated module for a
tsc dev build). Either falls back to the literal string "unknown" —
never a fabricated value — when neither injection path ran (e.g. a source tree with no git
history available at build time). jevitate --version prints the same identity:
the bare semver once that alone identifies the build, or
version (commit …, built …) when it can determine more, so two results produced by
different rebuilds in the same session are distinguishable.
Usability report: claims
Since 0.3.0 every usability finding is a claim code verified. A claim comes from a
guard probe (opt-in --probe-guards), from your product facts, or from friction the run
hit. Jev only picks a claim type from a closed list and the control it is about; code checks it,
and a claim that fails is dropped and counted. The finding's text is built from a template over the
verified fields, so a model never writes it.
Claim type Verified when destructive-unguardedThe guard probe clicked the control, a write was attempted (and blocked), and no confirm dialog or confirmation page came first. fact-conflictA plan's price or a trial length on screen differs from every value in .jevitate/product.json. next-step-unclearNo control names the page's intended next step, it is disabled, or the run hit navigational friction there. no-feedback, blocked-action, error-unrecoverableRetry or long-wait friction; a dead end or a disabled control; a failed request after the user's action.
Field Meaning report.claimsThe claim ledger: candidates, verified, merged, refuted, unverifiable, dismissed, budgetTruncated, and every claim in items[] with its status and reason. finding.claim{type, source, verifiedBy, verification, target}. finding.grade{need, ship}: Jev's answers to "do we need this?" and "do we need this to ship this feature?". Code maps them to quality.label: actionable, relevant-minor, generic or wrong. finding.screenshotLive runs: {path, target, box}, a cropped, secret-masked image with the cited control boxed, under usability-<stamp>.findings/. finding.rubricItemIdThe heuristic the claim type is cited under. The rubric no longer produces findings on its own, so heuristicAppendix is empty.
Two confidences. A finding's own confidence (filtered by
--min-confidence, default 0.3) is not quality.confidence, the grade's.
The grade labels a finding and hides nothing unless you pass --show.
Usability report: coverage
Coverage is anti-masquerade: a report with few findings means nothing if most rubric items were
never evaluated. Every report carries:
Field Meaning totalItemsTotal (rubric entry × screen) items considered. evaluatedItems actually judged (includes notApplicable items — a deterministic "does not apply" still counts as evaluated). skippedItems that did not run, each with a reason — never a silently dropped item. An unverifiable guard claim (no --probe-guards, no evidence sidecar) is listed here as claim:destructive-unguarded, never as a finding. budgetTruncatedScreen ids left un-analyzed because the judgment budget ran out. notApplicableItems ruled out by a deterministic applicability gate before any model call (e.g. a "choice overload" heuristic can't apply to a two-button consent screen). Counted as evaluated, so they never make coverage read as incomplete.
Coverage is full only when totalItems > 0,
evaluated === totalItems, skipped.length === 0 and
budgetTruncated.length === 0. Below full coverage, the report's
coverageWarning is present and says, verbatim, not to read it as good UX — findings
reflect only the items that were actually evaluated.
Usability report: suppressed
A candidate that doesn't survive is never silently dropped: it's counted in
report.suppressed (total, byReason, byRubricItem,
byRubricItemRoute, items). Reasons include unverified (code
refuted the claim: wrong type for the friction, or a control the friction never touched),
not-a-problem (Jev dismissed real friction), below-min-confidence,
quality-policy (the grade isn't in --show) and per-page-cap.
Real friction that was suppressed still counts against a clean report.
What clean guarantees — two different fields
There are two cleans for a usability run, and they mean different things. -
result.missionOutcome === "clean" (mission-level) means only that the run and its
analysis completed — the page loaded, the model calls answered, and a report was
produced. It is not a verdict on findings: usability findings are advisory by
design and never flip missionOutcome to defects-found. A completed
review with 12 major findings still reports missionOutcome: "clean" /
exitCode: 0. (With --success checks, a check that did not hold makes it
defects-found, as on a goal run.) Only a broken run (page never rendered, a required model call stayed
unavailable, the engine crashed) reports inconclusive/crashed instead
— including the case where the run finished but the analysis itself failed to produce a report.
-
report.clean (report-level, inside result.report) is the one that
actually says something about findings. It is true only when
coverage is full and there are zero kept findings and zero suppressed
candidates. A report with suppressed-but-real signal (e.g. several findings dropped only for
being below --min-confidence) is deliberately not clean — suppression is never allowed to read as "no issues".
Read result.report.clean, not result.missionOutcome, when you want to
know "did this app pass its usability review."
Per-finding screen/route attribution
Every finding is attributed to exactly where it was observed:
Field Meaning screenIdThe representative screen-state the finding's evidenceRefs resolve in. routeThe normalized route (URL pathname) the finding was observed on. screenIdsEvery screen-state id the same (deduplicated) issue was observed on. occurrencesHow many screen-states on this route exhibited the same issue (the dedupe count). controlsHuman-readable identities of the implicated controls, e.g. button "Accept". quotesVerbatim on-screen text excerpts, each independently verified present on the screen. evidenceRefsResolved refs into the analyzed evidence (e.g. { id: "control:0" }) — only the evidence actually implicated, on screenId.
See also Usability Review and Flags Reference.