mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 14:34:42 +01:00
Each red line in the census report keeps its stored error and adds the machine cause with its evidence, the failure_detail values (Expected/Received, or each failing judge dimension with its mean, sample count and the sample sentence naming it), the slice artifact and shard directory that hold the transcript, a fetch command (eval:pass-rates --run <id> --case <id>), and 'after a repair:' with a command that selects only that case (E2E ids via --case, judges via their selection and an exact test-name filter). A session that completed and failed a check says so instead of 'assertion (success)'. Report text that reaches a PR comment, issue or committed file goes through scripts/lib/published-text.ts: control and bidi characters stripped, credential-scan patterns redacted, mentions neutralized, HTML and link brackets escaped inline, blocks in a fence longer than any backtick run, length capped. Verdicts and collector verdict objects are unchanged.
37 lines
1.9 KiB
TypeScript
37 lines
1.9 KiB
TypeScript
/**
|
|
* The one sanitizer for model- or fixture-written text that reaches a public
|
|
* issue, PR comment or committed file (census report, tracking issue,
|
|
* eval:pass-rates output). Control and bidi characters are stripped, the
|
|
* repo's credential-scan patterns (lib/redact-engine, the engine the quality
|
|
* gate's credential scan runs) are redacted, @-mentions are neutralized and
|
|
* length is capped. Inline text also has HTML and Markdown link brackets
|
|
* escaped; blocks go inside a code fence longer than any backtick run they
|
|
* contain, so their content stays literal and cannot close the fence.
|
|
*/
|
|
import { redactFindingSpans } from '../../lib/redact-engine';
|
|
|
|
export const PUBLISHED_TEXT_MAX = 1000;
|
|
|
|
const cap = (text: string, max: number) => (text.length > max ? `${text.slice(0, max - 1)}…` : text);
|
|
|
|
function clean(text: string): string {
|
|
// eslint-disable-next-line no-control-regex
|
|
const stripped = text.replace(/[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2066-\u2069]/g, ' ').slice(0, 64 * 1024);
|
|
const redacted = redactFindingSpans(stripped) ?? '[redacted: credential-shaped text that could not be isolated]';
|
|
return redacted.replace(/@(?=[A-Za-z0-9_-])/g, '@\u200b');
|
|
}
|
|
|
|
/** One inline line: cleaned, HTML and Markdown link brackets escaped, capped. */
|
|
export function sanitizePublishedText(text: string, max = PUBLISHED_TEXT_MAX): string {
|
|
return cap(clean(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
.replace(/\[/g, '[').replace(/\]/g, ']'), max);
|
|
}
|
|
|
|
/** Cleaned, capped lines inside a fence longer than the longest backtick run they contain. */
|
|
export function publishedFence(lines: readonly string[], max = PUBLISHED_TEXT_MAX): string[] {
|
|
const body = lines.map(line => cap(clean(line), max));
|
|
const longest = Math.max(0, ...body.flatMap(line => (line.match(/`+/g) ?? []).map(run => run.length)));
|
|
const fence = '`'.repeat(Math.max(3, longest + 1));
|
|
return [fence, ...body, fence];
|
|
}
|