Skip to content

Evidence safety and baseline governance

Status: accepted and partially implemented. Independently human-reviewed on 2026-07-15. Safe transcript persistence and recursive transcript-fixture validation are implemented. Sensitive retention, committed baselines, report roots, and CI artifact upload remain disabled pending their separate reviewed enablement boundaries.

Terminal evidence can contain credentials, personal data, host paths, and proprietary application output. The safe default is to capture only sanitized, non-persistent evidence. This policy applies to direct adapters, terminal adapters, fixtures, reports, and future CI artifact publication.

Classification and handling

Evidence type Default classification Persistent handling
Transcript records, including state, event data, diagnostic details, and x- extensions restricted Validate the closed protocol shape, then recursively classify and redact before persistence; undeclared generic protocol members fail closed.
Synthetic transcript fixtures public May be committed only after recursive transcript redaction validation.
User input and text paste restricted Redact by default; persist only synthetic or explicitly approved values.
Clipboard value secret Do not capture or persist the value by default; record only a redaction marker when event evidence is needed.
Rendered frame or raw ANSI bytes restricted Redact before persistence; raw bytes are diagnostic evidence, not a baseline oracle.
Process error, command line, environment, or exit diagnostic restricted Redact secrets and normalize sandbox paths before persistence.
Filesystem path restricted Store only a sandbox-relative path; otherwise redact it.
CI artifact restricted Disabled until this policy's implementation and review are complete.

public evidence is safe for a public repository after validation. restricted evidence may contain project or user information and must be sanitized before it becomes public. secret evidence can authenticate, identify, or otherwise harm its owner; it is never committed, uploaded, or exposed in a readable diff.

Secure capture defaults

Future evidence capture has these modes:

  • safe (default): sensitive fields are redacted; clipboard values are never retained; paths are sandbox-relative or redacted; persistence is allowed only for validated public evidence.
  • non-persistent: evidence stays in process memory for the current verdict. It may pass through the in-memory codec but is not written to a fixture, reported, or uploaded.
  • sensitive (explicit opt-in): may retain restricted evidence locally for a debugging session, but never enables repository persistence or CI upload. It requires an explicit caller setting.

No mode permits secret evidence to enter committed files or CI artifacts. CI must use safe mode and no artifact-upload step until a separately reviewed policy change enables it.

Sensitive-mode retention is bounded: evidence may be written only beneath a caller-supplied directory outside the repository, readable only by the initiating OS account and the current debugging process. The implementation must refuse persistent sensitive mode when it cannot establish those boundaries. It must delete the directory when the run completes and must never retain it for more than 24 hours after creation; a caller may request earlier deletion. Child processes, CI workers, other OS accounts, and repository tools have no access boundary exception.

Redaction contract

Redaction happens inside the persistence boundary before a fixture writer, report renderer, or artifact publisher receives encoded bytes. The raw transcript codec validates and canonicalizes in memory but does not claim safe persistence. Redaction is deterministic and generally replaces a value with the exact marker <redacted:reason>; it must not retain the original value, its length, hash, or a reversible encoding. Timezone and semantic-key values use the fixed valid UTC and ["Escape"] sentinels respectively, so post-redaction protocol validation cannot admit a permissive placeholder — UTC is the only timezone value v1 can apply, and ["Escape"] is a registry member.

Safe persistence classifies validated records before applying any generic free-text credential patterns. Every v1 string-bearing position has this disposition:

Position Disposition
Envelope protocol and kind; capability constraint and status; clock, filesystem, network, input-mouse, process, and exit tagged-enum strings Preserve after protocol validation.
Envelope run_id and id; replay-subject format and selector tokens; decimal seed; locale Preserve as replay structure after protocol validation. Credential regexes do not scan these fields.
Timezone Replace with the fixed valid UTC sentinel in requested configuration and any effective value. The request is an unconstrained string, so the sentinel — not a <redacted:...> marker — is what keeps the redacted record protocol-valid; an applied v1 effective value is already UTC, and any other named request either terminates through the structured unsupported path or the run fails before its receipt.
Filesystem root and network allow-list host Replace with deterministic sandbox/positional markers in both requested and effective configuration.
Terminal capability names Replace by ordered positional markers in both requested and effective configuration, preserving ordering, uniqueness, and equality.
Input semantic key chord Replace the entire chord with the fixed registry-valid ["Escape"] sentinel, revealing neither the original base nor modifiers. Input text and clipboard text are blanket-redacted.
Observation state and event data Blanket-redact. Event type is blanket-redacted.
UI region ID and focus Replace through one deterministic per-UI ID map so focus still names its region.
UI region role and UI mode Blanket-redact. Frame lines use one marker per original position.
Diagnostic, failure, and unsupported-result code/message/details Blanket-redact; unsupported-result constraint remains the validated enum.
Process and final signal value Replace with the same deterministic signal marker so exit coherence remains valid.
Every x- member Rename by sorted position to x-redacted-NNNN within its object and blanket-redact its value.

Recognizable credentials in genuinely free text remain a secondary defense, including bearer/basic authorization, GitHub and OpenAI-style tokens, AWS access IDs, JWTs, Slack tokens, and PEM private-key material. Sensitive/path key classification remains a fail-closed defense for unvalidated JSON. A missed or unknown semantic value is restricted rather than silently public.

Fixture and artifact writers must invoke the same redactor; no path-specific writer may serialize raw evidence directly. Tests must construct nested fixture and artifact destinations to prove that changing a normal output path does not bypass redaction.

Codec and persistence boundary

serialize_transcript() is the pure validated v1 codec. Its returned bytes may still contain restricted or secret evidence and therefore must not be written to a repository file, report, artifact, or other persistent destination.

persist_transcript_evidence() is the only supported transcript persistence API. It copies rather than mutates the caller's records, validates that stable snapshot, classifies semantic fields by record kind, redacts them, revalidates the sanitized transcript, and only then writes canonical JSONL to a uniquely created temporary file in the destination directory. It closes that file before atomically replacing the destination and removes the temporary file on tested pre-replacement failures. If the operating system refuses cleanup, the primary persistence error remains authoritative and carries a note about the cleanup failure; the already-sanitized temporary file can remain for operator cleanup. This guarantees atomic replacement only; it does not claim crash-durable storage and performs no file or directory fsync. Safe mode applies the matrix above record-first and field-first, including lockstep transformations where the protocol requires cross-field equality. Closed replay-subject selectors, locale, decimal seed, envelope identity, numeric fields, and tagged enums remain intact after their validators accept them; credential-like substrings in those grammar-constrained or structural values are not a persistence failure. Generic free-text scanning does not run over the whole validated envelope. Likewise, an undeclared non-x- protocol member or a member that is invalid for its tagged variant is malformed transcript structure and is rejected before redaction or destination creation; redaction does not convert a malformed wire contract into a valid one.

Sensitive persistence is intentionally rejected because per-user access, outside-repository containment, bounded lifetime, and cleanup are not yet implemented. General JSON persistence and path-specific raw transcript writers are not supported public APIs.

Every committed .jsonl file under tests/fixtures/transcripts/ has an adjacent <fixture>.jsonl.evidence.json sidecar that explicitly classifies its contents as synthetic public evidence. The sidecar has exactly schema (termverify.fixture-evidence/v1), classification (public-synthetic), and fixture (the adjacent basename). The validator rejects missing, malformed, non-finite, mismatched, duplicate-member, and orphan classifications, then recursively checks fixture values for known restricted evidence. This explicit classification permits protocol fixtures to exercise synthetic text, state, events, and frames without treating a repository path alone as proof that captured evidence is public.

tests/fixtures/baselines/ is the designated baseline root; it is currently absent. The validator already governs it — a baseline added there must carry the approval records below — while the prohibition on adding one at all is policy, not mechanism (see the approval section). Repository artifacts/ and reports/ roots and every GitHub Actions actions/upload-artifact step are mechanism-rejected until separately enabled.

Baseline proposal and approval

This section governs behavioral baselines committed to the TermVerify repository. It does not prescribe approval or branch-protection policy for downstream projects using TermVerify. Downstream projects retain ownership of their own baseline governance; future reusable tooling must keep those policies configurable.

The design is accepted and its validator is implemented: a baseline under the root that carries matching approval records would validate today. Committing any baseline nevertheless remains prohibited by the status header's separate reviewed enablement boundary — a policy gate, not a validator mechanism — and no baseline files are committed today. The designated baseline root is tests/fixtures/baselines/. Once enabled, every baseline under that root must have a nearby <baseline-name>.approval.json sidecar and <baseline-name>.review.md readable-diff record. V1 baselines are UTF-8 text without a byte-order mark, use LF line endings, and end with exactly one LF. Their canonical bytes are the exact resulting UTF-8 file bytes; baseline_sha256 is SHA-256 of those bytes. Any future binary or differently canonicalized baseline type requires a new approval format version. The sidecar has exactly these required members:

Member Rule
format Exactly termverify.baseline-approval/v1.
baseline_sha256 SHA-256 of the canonical baseline bytes.
rationale Non-empty human-readable reason for the expected behavior.
review_mode Exactly independent or maintainer-self-review.
proposed_by Non-empty author identity.
reviewed_by Non-empty human reviewer identity. It differs from proposed_by for independent review and equals it for maintainer-self-review.
reviewed_at UTC RFC 3339 timestamp.
review_url HTTPS GitHub URL identifying a pull request or issue.
review_diff_sha256 SHA-256 of the adjacent readable-diff record.

The readable-diff record starts with before_sha256 (or null for a new baseline) and after_sha256, where after_sha256 exactly equals the sidecar's baseline_sha256; it then contains a non-empty human-readable explanation of the change. It is UTF-8 text without a byte-order mark, uses LF line endings, and ends with exactly one LF; review_diff_sha256 is SHA-256 of its exact file bytes. The baseline path, sidecar path, readable-diff record, canonical digest, and approval record are a single validation unit. The validator rejects a missing, malformed, stale, or orphan sidecar or readable-diff record; it rejects a baseline outside the designated baseline root; it verifies both digests and the after_sha256 binding; and it enforces the identity rule for the selected review mode. Agents and automation may prepare a proposal but cannot be the approving identity. Maintainer self-review requires a separate explicit human approval action after inspecting the digest-bound readable diff and successful required checks. The durable PR or issue URL records that action; metadata does not itself substitute for human review. Independent review is preferred whenever another qualified human is reasonably available.

Enforcement and ownership

The project maintainer owns policy exceptions and redaction-pattern updates. False positives are resolved by improving the deterministic redactor or marking the evidence restricted/non-persistent; they are never resolved by disabling validation for a path. Suspected leakage means revoke affected credentials, remove public exposure, rotate secrets, and add a regression test before resuming capture.

Before enabling baselines or artifacts, CI and local pre-commit run the same evidence-governance validator. The validator's tests cover valid redaction of nested transcript fields/extensions, nested fixture and artifact paths, sensitive-retention boundary failure, missing/stale approval metadata or readable-diff records, invalid identity combinations for each review mode, and rejection of a changed baseline without matching approval and readable-diff digests.