> ## Documentation Index
> Fetch the complete documentation index at: https://sdlc-rstack.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Evidence Center

> The requirement → evidence traceability matrix, and the on-demand drift scanner that catches debris a fast-moving governed run leaves behind.

The **Evidence Center** (page id `traceability`, issue #282) answers one question per requirement:
*is this actually proven, or just claimed?* It renders a matrix of every requirement in scope
against five evidence kinds — implementation, test, security, compliance, approval — sourced
entirely from a run's own on-disk artifacts (`evidence.jsonl`, `validation.json`,
`approvals.json`). Nothing here is inferred from the browser; the page's own header comment states
the rule directly: it "never derives a pass from missing browser data and never recalculates
readiness." Below the matrix sits the **Traceability Drift** panel (issue #74), an on-demand scan
of the same run for requirements nobody picked up, completed work missing its contracts, and stale
file references.

## How it works

### The evidence projection (server-owned)

`buildEvidenceProjection` in `src/observability/dashboard/state/evidence.js` builds the whole
model server-side on every poll; the client only renders it. For each run in scope, every
requirement (`run.requirements`) becomes one row, and each of the five `EVIDENCE_KINDS`
(`implementation`, `test`, `security`, `compliance`, `approval`) becomes one cell in that row:

* **implementation / security / compliance** cells read `evidence.jsonl` entries whose kind (via
  a keyword match on `kind`/`task_id`/`stage_id`) matches the column and whose content references
  the requirement (`referencesRequirement` — an explicit `requirement_id` field, or a text-includes
  fallback).
* **test** cells add validator check results (`task.validation.checks`) on top of the ledger
  signals.
* **approval** cells read `run.approvals` records, cross-checked with
  `approvalHistoryIssues`/`validateApprovalRecord` from the harness's own approval-audit module —
  a record that fails that audit renders as `INVALID`, not a false pass.

Each cell resolves to one of three states — **verified** (a real PASS/APPROVED signal, not
integrity-damaged, no invalid records), **failed** (any FAIL/BLOCKED/REJECTED signal), or
**unknown** (nothing observed, or the backing file is flagged in the run's integrity collector).
`unknown` is a first-class state, never silently coerced into a pass.

The top-level model also carries:

| Field                                                  | Meaning                                                                                                        |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `summary.expected` / `verified` / `failed` / `unknown` | Cell counts across every row                                                                                   |
| `summary.coveragePercent`                              | `verified / expected`, or `null` when nothing is expected yet                                                  |
| `status`                                               | `blocked` (any failed cell) → `unknown` (nothing expected, or any unknown cell) → `verified`                   |
| `sources`                                              | De-duplicated list of every backing source reference across all cells                                          |
| `rationale`                                            | Flattened `{requirementId, kind, status, sourceRefs}` — one entry per cell, for the "Readiness rationale" view |

This same `evidenceCenter` object also feeds the Release Readiness verdict
(`state/readiness.js`) as one check among several — so a requirement with failed or unknown
evidence shows up as a release blocker, not just a quiet cell in this page.

<Note>
  A separate, older per-run summary (`buildTraceMap` in `state/traceability.js`, sent to the
  client as `state.traceMap`) is still computed on every poll, but no UI page currently renders
  it — the Evidence Center you see today is built entirely on `evidenceCenter`. Treat `traceMap`
  as legacy plumbing, not the live model.
</Note>

### Three views over one model

The page (`src/observability/dashboard/ui/pages/traceability.js`) never re-fetches — `EVIDENCE_VIEW`
just re-renders the same `model` three ways:

* **Summary** — the KPI strip (coverage / verified / failed / unknown) plus a release evidence
  verdict, followed by the same matrix as below.
* **Matrix** — one row per requirement, one column per evidence kind, filterable by cell status
  (`EVIDENCE_STATUS`: all / verified / failed / unknown) via `setEvidenceStatus`.
* **Readiness rationale** — the flat `rationale` list, one card per requirement×kind pair, each
  showing its status pill and up to two clickable source references.

A cell's source reference renders as a **clickable button** (`evidenceSourceButton`) only when the
source is `linkable` — i.e. its path is scoped to `.rstack/` with no traversal. Clicking it calls
`viewArtifact()` (`ui/drawer.js`), which fetches `/api/artifact?run=...&path=...` and opens the
file in the drawer (rendered by `artifact-render.js`, or a raw `<pre>` fallback). This is the same
mechanism the Run Report and other pages use to open a real deliverable in place — Evidence Center
never links out to a filesystem path directly, only through that sandboxed endpoint.

A toolbar button, **"Export same projection,"** downloads the exact in-memory `model` object as
`rstack-evidence-projection.json` — what you see is what you get, byte for byte.

### Traceability Drift panel

Below the matrix, `fillDriftCard` fetches `/api/drift?run=<runId>` for the first run in scope
(`(s.runs || [])[0]`) and renders its findings. The server route (`handleDrift` in
`server.js`) calls the shared harness scanner, `scanRunDrift` (`src/core/harness/drift.js`) — the
identical function the `rstack-agents drift` CLI verb runs. The scanner reads the run's real
artifacts and reports:

1. **Requirements without a task** — a stage-02 requirement no task's id/title/description/
   acceptance criteria mentions.
2. **Task completeness** — a task missing status, owner/agent, or a traceable stage/artifact.
3. **Missing builder/validator contracts** on a task marked `PASS`/`DONE_WITH_CONCERNS` (a waived
   task, per an on-record approval, gets a warning instead of an error).
4. **Validator PASS with zero passing checks recorded** — a verdict with no evidence behind it.
5. **Stale file references** — files a builder claims to have modified, or evidence points at,
   that no longer exist on disk.
6. **Evidence pointing at an unknown task**, or an **approved artifact that no longer exists**
   (virtual gate names like `guardrail-override:004-impl` are excluded — they name a gate, not a
   file).
7. **Readiness contradiction** — `readiness.json` reports `READY` while tasks are still
   `FAIL`/`BLOCKED`.

Each finding carries a `severity` (`error` = a broken evidence chain, `warning` = intact but
degrading), a `type`, the `artifact` path it's about, a human `message`, and a `remediation`. The
run's overall drift `status` is `FAIL` on any error, `WARN` on any warning, else `PASS`. The card
shows the top 12 findings plus four summary chips (requirements / tasks / missing evidence / stale
references); an empty findings list renders as an honest "No drift detected."

## Try it

Open the Evidence Center from the Business Hub nav, or drive the same scanner from the CLI:

```bash theme={null}
# Scan the newest/only run in the current project
npx rstack-agents drift

# Scan a specific run, or every run under .rstack/runs
npx rstack-agents drift <runId>
npx rstack-agents drift --all

# Machine-readable, same shape the /api/drift endpoint returns
npx rstack-agents drift --json

# Promote warnings to a non-zero exit for CI
npx rstack-agents drift --strict
```

<Info>
  `--json`/`--all` are real flags on the `drift` command (`bin/rstack-agents.js`); default exit
  is warning-tolerant (non-zero only on `error`-severity findings) so a CI pipeline can adopt the
  gate without breaking on cosmetic drift.
</Info>

## Related

* [Overview & Navigation](/business-hub/overview-and-navigation) — the six-destination shell this page lives in
* [Operations Center](/business-hub/operations-center) — health/integrations/recovery, the sibling projection built the same server-owned way
