Files
clean-architecture-frontend…/docs/superpowers/plans/2026-08-02-provider-raw-guardian.md
T

500 lines
24 KiB
Markdown

# Provider Evidence Guardian Transaction Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make guardian startup cleanup derive authority only from identities allocated before spawn while preserving no-replace raw/sealed publication and immediate safe retry.
**Architecture:** Before spawn, the client pins the canonical raw/evidence directories and exclusively allocates nonce-private raw-staging/sealed-temp inodes whose handles and identities it retains. The guardian inherits directory fd 3/fd 4 and private-file fd 5/fd 6, binds strictly validated aliases to those inherited identities, and transfers raw authority with a no-replace hard link before authenticated READY. The client and guardian clean only pre-recorded identities; neither promotes a pathname-discovered inode to ownership.
**Tech Stack:** Node.js 24 TypeScript, Vitest, Linux file identities and procfs, systemd user scopes, bubblewrap, cgroup v2.
## Global Constraints
- Tasks 1-7 are the historical round-four/five record. Round-six Task 8 runs only in `/tmp/guardian-race-fix-y05lvLi1/repo` on top of `d781692`; never modify the original workspace, `/tmp/task3-integration-mU4L7J2u`, or the security-finalizer repository.
- Use RED-GREEN-REFACTOR for every production behavior change.
- Guardian argv contains only `process.execPath` and the trusted guardian script; its environment is empty, fd 3/fd 4 are the identity-pinned raw/evidence directories, and fd 5/fd 6 are the identity-pinned private raw/sealed allocations.
- Every request/ack is canonical length-prefixed JSON with exact ordered fields, strict UTF-8, no NUL, total bounds, a 32-byte nonce, and constant-time authentication.
- Canonical raw and sealed paths are derived from guardian `cwd` and provider kind; paths and identities are not accepted in the guard request.
- Provider wall timeout is at most 30 minutes and post-processing allowance is exactly 10 minutes; the guardian maximum lease is 40 minutes.
- Publication is no-replace and directory-durable. Abort/death/deadline cleans every raw/temp/final path that still names a pinned owned inode.
- Preserve all cleanup failures with the primary failure using `AggregateError`.
- Do not add PID-exhaustion loops or claim `RLIMIT_NPROC` enforcement.
- Do not run or report live systemd/bwrap tests as passing while the approval limit prevents execution.
- Never forward raw provider stdout/stderr bytes to supervisor or CI logs.
---
### Task 1: Versioned Transaction Protocol
**Files:**
- Modify: `scripts/lib/provider-guardian-protocol.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
**Interfaces:**
- Produces:
```ts
type ProviderGuardianGuard = Readonly<{
kind: "vulnerability" | "provenance";
nonce: Buffer;
deadlineEpochMs: number;
}>;
type ProviderGuardianReady = Readonly<{
nonce: Buffer;
rawDev: number;
rawIno: number;
sealedTempLeaf: string;
sealedDev: number;
sealedIno: number;
}>;
type ProviderGuardianPublish = Readonly<{
nonce: Buffer;
sealedDev: number;
sealedIno: number;
size: number;
sha256: string;
}>;
function encodeProviderGuardianGuard(input: ProviderGuardianGuard): Buffer;
function decodeProviderGuardianReady(payload: Buffer, nonce: Buffer): ProviderGuardianReady;
function encodeProviderGuardianPublish(input: ProviderGuardianPublish): Buffer;
function decodeProviderGuardianPublished(payload: Buffer, nonce: Buffer): void;
function encodeProviderGuardianCommit(nonce: Buffer): Buffer;
```
- [ ] **Step 1: Write failing exact-protocol tests**
Assert that guard contains no path or identity, READY returns authenticated identities, publish binds exact identity/size/SHA-256, PUBLISHED authenticates the same nonce, and duplicate/reordered/trailing/oversized/invalid UTF-8/NUL/short-nonce frames fail.
```ts
expect(JSON.parse(encodeProviderGuardianGuard(guard).subarray(4).toString())).toEqual({
type: "guard", version: 2, kind: "vulnerability",
nonce: nonce.toString("hex"), deadlineEpochMs,
});
expect(() => decodeProviderGuardianReady(duplicateNoncePayload, nonce)).toThrow(/canonical|fields/u);
```
- [ ] **Step 2: Run focused RED**
Run: `node_modules/.bin/vitest run tests/unit/task3-selective-integration.test.ts --reporter=default --maxWorkers=1`
Expected: FAIL because the v2 guard/READY/publish/PUBLISHED APIs do not exist and the old guard still accepts identities.
- [ ] **Step 3: Implement the minimal v2 codecs**
Use one bounded prefix/strict decode utility, exact ordered key arrays, canonical re-encoding, lowercase 64-hex nonces/SHA-256, safe positive integers, and `timingSafeEqual` for every acknowledgement/authentication comparison.
- [ ] **Step 4: Run focused GREEN**
Run the Step 2 command and require the protocol tests to pass.
### Task 2: Guardian-Owned Raw and Sealed Transaction
**Files:**
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Modify: `scripts/lib/provider-raw-cleanup.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
**Interfaces:**
- Consumes: Task 1 codecs.
- Produces: real process state machine `guard -> READY -> publish -> PUBLISHED -> commitPending -> EOF success`.
- [ ] **Step 1: Write failing real-process creation tests**
Cover no-frame and partial-frame EOF with no files, authenticated READY-created raw/temp identities and modes, full-frame parent EOF cleanup before commit, deadline cleanup, and a near-timeout successful transaction.
```ts
child.stdin.end(partialFrame);
await completion;
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
expect(await lstat(rawPath)).toMatchObject({ mode: expect.any(Number) });
```
- [ ] **Step 2: Verify creation RED**
Run the focused test and require failure because the existing guardian expects supervisor-created identity and emits line-based READY.
- [ ] **Step 3: Implement exclusive creation and READY**
Derive canonical leaves, create raw and random sealed sibling temp with `O_EXCL|O_NOFOLLOW`, set raw/temp `0600`, fstat identities, close raw, keep temp handle, and emit bounded READY. On every error, attempt all owned cleanup before nonzero exit.
- [ ] **Step 4: Write failing publish/state tests**
Write validated bytes to the pinned temp, request publish, require PUBLISHED and final mode/hash/identity, then verify commit waits for EOF. Send one later trailing byte after commit and require final cleanup/nonzero exit. Kill the parent after PUBLISHED and require raw/temp/final absence.
- [ ] **Step 5: Implement no-replace durable publish and serialized terminal cleanup**
Verify held descriptor/path identity, `nlink=1`, `0400`, size, and SHA-256. Use `link(temp, final)`, `unlink(temp)`, final lstat identity, and parent-directory fsync. Serialize frame and EOF handling so a publish/death race cannot bypass cleanup. Commit removes raw and sets `commitPending`; only clean EOF exits zero.
- [ ] **Step 6: Run real-process GREEN**
Run focused tests and require zero raw/temp/final/process residuals in every failure case.
### Task 3: Authenticated Client Lease and Fallback Cleanup
**Files:**
- Modify: `scripts/lib/provider-guardian-client.ts`
- Modify: `scripts/lib/validated-json-artifact.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
- Modify: `tests/unit/validated-json-artifact.test.ts`
**Interfaces:**
- Produces:
```ts
type ProviderGuardianLease = Readonly<{
pid: number;
rawPath: string;
rawIdentity: Readonly<{ dev: number; ino: number }>;
sealedPath: string;
sealedTempPath: string;
sealedIdentity: Readonly<{ dev: number; ino: number }>;
prematureExit: Promise<Error>;
publish(bytes: Buffer): Promise<void>;
commit(): Promise<void>;
abort(): Promise<void>;
}>;
function serializeValidatedJsonArtifact(input: ValidatedJsonArtifactInput): Buffer;
```
- [ ] **Step 1: Write failing client transaction tests**
Require exact guardian argv and empty environment, READY identity capture, pinned temp write/fsync/mode, PUBLISHED wait, exactly-one terminal action, post-READY guardian SIGKILL cleanup of raw/temp/final, and cleanup error aggregation.
- [ ] **Step 2: Verify client RED**
Run focused and validated-writer tests. Expect missing publish/identity/serializer APIs.
- [ ] **Step 3: Implement serialization and lease**
Extract the existing schema-parse/pretty-JSON/newline serialization without changing `writeValidatedJsonArtifact`. Open the returned temp with `O_NOFOLLOW`, fstat identity, truncate/write/chmod `0400`/fsync/fstat/close, send authenticated publish metadata, and wait for PUBLISHED. Fallback cleanup attempts raw, temp, and final using READY identities and aggregates failures.
- [ ] **Step 4: Run client GREEN**
Run the Step 2 tests and require exact bytes, identities, cleanup, and no residual child.
### Task 4: Supervisor Transaction and Scope-Active Latch
**Files:**
- Modify: `scripts/run-and-validate-provider.ts`
- Modify: `tests/unit/task3-selective-integration.test.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Consumes: Task 3 lease and serializer.
- Produces: guardian-owned raw/provider execution, awaited publication, output append, commit/EOF, and scope-confined kill ownership.
- [ ] **Step 1: Write failing supervisor ordering/latch tests**
Require no `createProviderOutput`, lease start before provider, lease raw identity passed to scope, serialized bytes published before output append, commit after output append, and postprocess allowance included in lease. Add a pure scope-latch unit boundary or static contract proving the guardian callback can call `killProviderUnit` only while `scopeActive` is true.
- [ ] **Step 2: Verify supervisor RED**
Run focused tests and expect the old create/write/cleanup ordering assertions to fail.
- [ ] **Step 3: Integrate the lease transaction**
Start guardian in `executeProvider`, use READY raw path/identity for provider bind and capture, publish serialized validated evidence through the lease, append output, then commit. Remove supervisor raw creation and normal sealed writer publication. Keep only identity-bound lease fallback cleanup.
Set `PROVIDER_POSTPROCESS_TIMEOUT_MS = 600_000` and request `providerWallTimeoutMs + PROVIDER_POSTPROCESS_TIMEOUT_MS`.
- [ ] **Step 4: Implement scope-active guardian exit ownership**
Race an awaited scope-completion promise against termination. The guardian callback records its error and invokes termination only while `scopeActive`; the same function sets the latch false exactly once when kill/collection or normal collection completes. The callback never throws or creates an unobserved kill promise after the latch closes.
- [ ] **Step 5: Run supervisor GREEN**
Run focused tests and type/lint checks. Live systemd tests remain unexecuted and are not reported as passing.
### Task 5: Regression Fixtures and Documentation
**Files:**
- Modify: `tests/unit/task3-selective-integration.test.ts`
- Modify: `tests/unit/ci-artifact-contract.test.ts`
- Modify: `docs/operations/ci-quality-gates.md`
- Modify: `docs/security/supply-chain.md`
- Modify: `docs/superpowers/specs/2026-08-02-provider-raw-guardian-design.md`
- [ ] **Step 1: Complete real-process regressions**
Cover no/partial frame, parent kill near READY, post-READY guardian kill, PUBLISHED parent death, publish/commit race, later-chunk commit trailing data, deadline/near-timeout, same-workspace retry, and zero guardian/raw/temp/final residuals.
- [ ] **Step 2: Specify live regressions**
Add active-scope guardian kill, post-scope/precommit guardian kill, supervisor hard death after PUBLISHED with same-workspace retry, and detached descendant attempts for both an external marker and raw append. Every case requires zero cgroup/process/file residuals. Do not execute these tests under the current approval limit.
- [ ] **Step 3: Correct operations and security docs**
Document guardian-owned creation/publication, READY/PUBLISHED identities, ten-minute postprocess lease, commitPending/EOF success, no-replace link publication, scopeActive kill ownership, regular-file `GITHUB_OUTPUT`, and explicit live-test limitation.
- [ ] **Step 4: Fresh verification**
Run focused real-process tests, validated artifact tests, direct Node/test/recipe TypeScript configs, full lint, artifact schemas, CI contract, generated workflow byte check, and `git diff --check`. Record broad-suite sandbox `EPERM` separately and never convert unexecuted live tests into PASS.
- [ ] **Step 5: Review and commit round four**
Confirm only the isolated worktree changed, no protocol secret/path enters argv, cleanup checks both sealed names by identity, and only the temp `node_modules` symlink remains untracked. Create a separate round-four implementation commit above the design/plan commit.
### Task 6: Round-Five Pre-READY Recovery Authority
**Files:**
- Modify: `scripts/lib/provider-guardian-protocol.ts`
- Modify: `scripts/lib/provider-guardian-client.ts`
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Test: `tests/unit/provider-guardian-transaction.test.ts`
**Interfaces:**
- Produces: `providerGuardianSealedTempLeaf(kind, nonce): string`, inherited raw/evidence directory fds 3/4, and descriptor-relative startup/lease cleanup.
- [x] **Step 1: Write failing pre-READY hard-death tests**
Start the real client without awaiting READY, observe its direct guardian child,
kill the guardian when either deterministic transaction leaf first appears, and
require startup rejection, zero raw/temp/final residuals, and a successful
same-workspace `startProviderGuardian(...).abort()` retry. Also require the temp
leaf computed before spawn to equal READY exactly and inherited fd 3/fd 4 to
remain directories during the lease.
- [x] **Step 2: Run focused RED**
Run: `node_modules/.bin/vitest run tests/unit/provider-guardian-transaction.test.ts --reporter=default --maxWorkers=1`
Expected: FAIL because the client has neither pre-spawn directory handles nor a
deterministic temp leaf and cannot clean a guardian killed before READY.
- [x] **Step 3: Implement pinned descriptor recovery**
Open and verify the canonical raw/evidence directories with
`O_DIRECTORY|O_NOFOLLOW`; derive the temp leaf from provider kind and the first
16 nonce bytes; spawn with those handles at fd 3/fd 4. Use only
`/proc/self/fd/<fd>/<leaf>` for guardian creation, publication, sync, and cleanup.
On startup failure, open each exact leaf through the still-live client
descriptor, fstat a regular single-link inode, close the discovery handle, and
run identity-bound quarantine/unlink. Aggregate primary, cleanup, and directory
close errors. Retain both handles until commit/abort terminates.
- [x] **Step 4: Run focused GREEN**
Run the Step 2 command and require the pre-READY kill/retry and all round-four
transaction tests to pass.
### Task 7: Round-Five Log Privacy and Terminal Fail-Closed Behavior
**Files:**
- Modify: `scripts/run-and-validate-provider.ts`
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Test: `tests/unit/provider-guardian-transaction.test.ts`
- Test: `tests/unit/ci-artifact-contract.test.ts`
**Interfaces:**
- Consumes: Task 6 descriptor-pinned transaction.
- Produces: bounded discard of provider output and nonzero guardian termination even when diagnostic fds are closed.
- [x] **Step 1: Write failing privacy and closed-stderr tests**
Run a successful provider that both receives and prints a unique
`VULNERABILITY_PROVIDER_*` credential, then assert the credential is absent from
supervisor stdout/stderr while the sealed signed evidence succeeds. Replace the
FD-limit provider's stderr marker expectations with evidence/side-channel state.
Spawn a real guardian with stderr's read side destroyed, establish owned files,
then abort or send invalid input and require zero files plus a nonzero exit.
- [x] **Step 2: Run targeted RED**
Run the focused guardian and selected CI artifact tests. Expect credential
disclosure and the existing raw provider stderr marker assertions to fail the
new contract; the EPIPE case can exit without the required nonzero terminal.
The executable non-live RED observed six expected failures: missing deterministic
leaf/fd inheritance/output limiter, retained pre-READY raw, and closed-stderr
exit 0. The live credential-printing fixture is authored but remains NOT RUN.
- [x] **Step 3: Implement minimal privacy and terminal fixes**
Continue counting provider stdout/stderr bytes against the aggregate output
limit but discard captured bytes instead of retaining or forwarding them. Make
guardian fd-close and stderr diagnostics best effort, run cleanup first, and
place `process.exit(exitCode)` or self-`SIGKILL` in an unconditional final
branch that cannot be skipped by `EPIPE`/`EBADF`.
- [x] **Step 4: Run targeted GREEN and regression verification**
Run focused guardian tests, selected non-live privacy tests, Node/test
TypeScript, affected ESLint, docs readiness, and `git diff --check`. Do not run
live systemd/bwrap tests under the approval limit.
- [x] **Step 5: Commit round five implementation**
Commit production, tests, and operational/security documentation separately
above this round-five design/plan commit. Record live systemd/bwrap as NOT RUN.
Round-five verification record:
- Focused real-process/unit GREEN: 4 files, 52 tests passed.
- Direct Node and test TypeScript projects: PASS.
- Affected ESLint with zero warnings: PASS.
- Documentation readiness: `PASS_SCOPED`.
- `git diff --check`: PASS.
- Live systemd/bwrap credential, FD-limit, cgroup, and hard-death fixtures:
**NOT RUN** because the active approval limit forbids those executions.
### Task 8: Round-Six Pre-READY Inode Ownership
**Files:**
- Modify: `scripts/lib/provider-guardian-protocol.ts`
- Modify: `scripts/lib/provider-guardian-client.ts`
- Modify: `scripts/lib/provider-raw-guardian.ts`
- Test: `tests/unit/provider-guardian-transaction.test.ts`
- Modify: `docs/security/supply-chain.md`
- Modify: `docs/operations/ci-quality-gates.md`
- Modify: `docs/superpowers/specs/2026-08-02-provider-raw-guardian-design.md`
**Interfaces:**
- Produces:
```ts
function providerGuardianRawStagingLeaf(
kind: ProviderGuardianKind,
nonce: Buffer,
): string;
type RecoveryAuthority = Readonly<{
rawDirectoryHandle: FileHandle;
evidenceDirectoryHandle: FileHandle;
rawStagingHandle: FileHandle;
sealedTempHandle: FileHandle;
rawIdentity: Readonly<{ dev: number; ino: number }>;
sealedIdentity: Readonly<{ dev: number; ino: number }>;
rawStagingPinnedPath: string;
rawPinnedPath: string;
sealedTempPinnedPath: string;
sealedPinnedPath: string;
}>;
```
- [ ] **Step 1: Write the deterministic external-canary RED**
Create a temporary guardian fixture that writes a spawn marker and remains
alive without producing READY. Start the real client, wait for that marker (so
`assertRecoveryLeavesMissing` has completed), create a fixed-raw canary, kill
the direct guardian, and require startup rejection without canary deletion or
mutation.
```ts
const canaryBytes = Buffer.from("external-canary\n");
const canaryHandle = await open(rawPath, constants.O_CREAT | constants.O_EXCL |
constants.O_WRONLY | constants.O_NOFOLLOW, 0o600);
await canaryHandle.writeFile(canaryBytes);
const canaryIdentity = await canaryHandle.stat();
await canaryHandle.close();
process.kill(guardianPid, "SIGKILL");
await expect(starting).rejects.toThrow(/provider guardian/u);
expect(await readFile(rawPath)).toEqual(canaryBytes);
expect(await lstat(rawPath)).toMatchObject({
dev: canaryIdentity.dev,
ino: canaryIdentity.ino,
});
```
- [ ] **Step 2: Run the canary RED and confirm the ownership bug**
Run:
`node_modules/.bin/vitest run tests/unit/provider-guardian-transaction.test.ts -t "preserves an external raw canary" --reporter=default --maxWorkers=1`
Expected: FAIL with `ENOENT` when reading the canary because
`discoverAndCleanupOwnedLeaf` opens the current raw pathname and promotes the
external inode to cleanup authority.
- [ ] **Step 3: Add private-leaf derivation and client allocations**
Derive raw staging and sealed temp from the same first 16 nonce bytes:
```ts
return `.${baseLeaf(kind)}.guardian-${nonce.subarray(0, 16).toString("hex")}.raw.tmp`;
```
Through the pinned directory paths, create raw staging and sealed temp with
`O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW`, mode `0600`; require regular file, link count
one, mode `0600`, and size zero; store identities before spawn. Spawn with fd
3-fd 6. If allocation, validation, or spawn fails, identity-clean every private
alias and close every opened handle while preserving primary and cleanup/close
errors in one `AggregateError`.
- [ ] **Step 4: Add concurrency, bootstrap, and link-before-READY RED tests**
Add real-process tests that require:
```ts
// no/partial frame: bootstrap-owned private aliases are removed
child.stdin!.end(partialFrame);
await expect(readdir(rawDirectory)).resolves.toEqual([]);
// same kind: exactly one READY lease, loser never removes winner raw
const results = await Promise.allSettled([startProviderGuardian(input), startProviderGuardian(input)]);
expect(results.filter(({ status }) => status === "fulfilled")).toHaveLength(1);
expect(results.filter(({ status }) => status === "rejected")).toHaveLength(1);
// canonical raw link exists but READY has not been accepted
process.kill(guardianPid, "SIGKILL");
await expect(starting).rejects.toThrow(/provider guardian/u);
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
```
The link-before-READY test watches only the fixed raw basename, obtains the
direct child pid before the event, and kills on that exact link event so private
allocation events cannot satisfy the synchronization point. Each test performs
a same-workspace retry and requires no owned private/canonical residue.
- [ ] **Step 5: Implement guardian bootstrap identity binding**
At process bootstrap, fstat fd 5/fd 6 and read `/proc/self/fd/5|6`. Accept an
alias only if `dirname(readlink)` is the canonical expected directory, basename
is a direct child matching the exact raw-staging or sealed-temp lowercase-hex
grammar, both names encode the same kind/nonce prefix, and descriptor-relative
lstat equals the inherited fd identity/type/mode/size/link count. Store the fd
identity before reading any pathname; the pathname only becomes an alias for
that identity.
On valid guard, require exact `providerGuardianRawStagingLeaf(kind, nonce)` and
`providerGuardianSealedTempLeaf(kind, nonce)` matches. Use
`link(rawStaging, rawCanonical)` without replacement, check both aliases equal
the inherited raw identity with link count two, unlink raw staging, fsync fd 3,
and check raw canonical remains the same identity with link count one before
READY. Use the inherited sealed identity for READY and publication.
- [ ] **Step 6: Replace discovery cleanup and close all private fds**
Delete `discoverAndCleanupOwnedLeaf`. Client pre-READY and fallback cleanup
attempts raw staging/canonical with only `recovery.rawIdentity`, then sealed
temp/final with only `recovery.sealedIdentity`. Guardian no/partial-frame and
terminal cleanup uses only its bootstrap fd identities and bound aliases.
On success and every failure branch, attempt all cleanup first, close guardian
fd 5/fd 6 duplicates and client fd 3-fd 6 handles exactly once, and append every
close failure to the existing aggregate. Never open a current leaf to obtain a
new cleanup identity.
- [ ] **Step 7: Run focused GREEN and regressions**
Run:
```bash
node_modules/.bin/vitest run tests/unit/provider-guardian-transaction.test.ts --reporter=default --maxWorkers=1
node_modules/.bin/vitest run tests/unit/provider-output-limiter.test.ts tests/unit/ci-artifact-contract.test.ts --reporter=default --maxWorkers=1
node_modules/.bin/tsc --project tsconfig.node.json
node_modules/.bin/tsc --project tsconfig.test.json
node_modules/.bin/eslint scripts/lib/provider-guardian-protocol.ts scripts/lib/provider-guardian-client.ts scripts/lib/provider-raw-guardian.ts tests/unit/provider-guardian-transaction.test.ts --max-warnings=0
node scripts/verify-documentation-readiness.ts
git diff --check
```
Require focused tests, Node/test TypeScript, affected ESLint, documentation
readiness, and whitespace verification to pass. Live systemd/bwrap fixtures
remain **NOT RUN** under the current approval limit.
- [ ] **Step 8: Review and commit round six**
Confirm the original workspace, `/tmp/task3-integration-mU4L7J2u`, and the
security-finalizer repository are unchanged; only the temporary `node_modules`
symlink is untracked. Commit production/tests/docs together above design commit
`0b1a1db` and report the isolated path, commit SHA, RED evidence, and fresh GREEN
evidence.