@cendor/guardrails 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/judge.js ADDED
@@ -0,0 +1,171 @@
1
+ /**
2
+ * LLM-judge helpers — turn a bring-your-own model call into a usable guardrail. The TS port of
3
+ * `cendor.guardrails.judge`.
4
+ *
5
+ * cendor ships **no** classifier. `rules.llmJudge` is an adapter *contract*: you supply the model
6
+ * call. These helpers give that call the boring, error-prone parts so you don't hand-roll them —
7
+ *
8
+ * - {@link verdictPrompt} — a strict system instruction that pins the model to a two-field JSON
9
+ * verdict (`{"trip": <bool>, "reason": "<short>"}`), so the output is parseable, not prose.
10
+ * - {@link parseVerdict} — strict JSON → `Verdict` (or `null` to pass). Malformed output *throws* so
11
+ * the guardrail's `onError` policy decides (a garbled judge fails **closed** by default).
12
+ * - {@link judge} — glue the two around your `respond(system, user)` callable (sync or async) into a
13
+ * check you hand straight to `rules.llmJudge`.
14
+ *
15
+ * The judge's own spend is budgeted and audited: make the call through an `instrument()`-ed client
16
+ * and its tokens + cost land in tokenguard / acttrace like any other call.
17
+ */
18
+ import { Verdict } from './decision.js';
19
+ /**
20
+ * The default judge instruction. `{policy}` is filled with your risk description. It pins the model
21
+ * to strict JSON so {@link parseVerdict} can read it — no chain-of-thought, no prose.
22
+ */
23
+ export const DEFAULT_SYSTEM = 'You are a safety classifier guarding an LLM application. Decide whether the CONTENT below ' +
24
+ 'violates this policy:\n\n{policy}\n\n' +
25
+ 'Reply with a single JSON object and nothing else: {"trip": <true|false>, "reason": ' +
26
+ '"<one short sentence>"}. Set "trip" to true only if the content violates the policy. Do not ' +
27
+ 'include markdown, code fences, or any text outside the JSON object.';
28
+ /**
29
+ * Build the judge's system instruction for `policy` (a plain-language description of what should
30
+ * trip). Override `template` to customise, keeping the `{policy}` placeholder and the strict-JSON
31
+ * verdict contract {@link parseVerdict} expects.
32
+ */
33
+ export function verdictPrompt(policy, template = DEFAULT_SYSTEM) {
34
+ return template.replaceAll('{policy}', policy);
35
+ }
36
+ /**
37
+ * Parse a model reply into an object, tolerating a leading/trailing ```` ```json ```` fence but
38
+ * nothing looser. Throws on anything not a JSON object.
39
+ */
40
+ function coerceJson(text) {
41
+ let stripped = text.trim();
42
+ if (stripped.startsWith('```')) {
43
+ // tolerate a single ```json … ``` fence some models add despite instructions
44
+ const parts = stripped.split('```');
45
+ if (parts.length >= 2) {
46
+ let body = parts[1] ?? '';
47
+ if (body.toLowerCase().startsWith('json'))
48
+ body = body.slice(4);
49
+ stripped = body.trim();
50
+ }
51
+ }
52
+ let data;
53
+ try {
54
+ data = JSON.parse(stripped);
55
+ }
56
+ catch (err) {
57
+ throw new Error(`judge did not return JSON: ${err.message}`);
58
+ }
59
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
60
+ const kind = Array.isArray(data) ? 'array' : data === null ? 'null' : typeof data;
61
+ throw new Error(`judge returned a ${kind}, expected a JSON object`);
62
+ }
63
+ return data;
64
+ }
65
+ /**
66
+ * Parse a strict-JSON judge reply into a `Verdict` (trip) or `null` (pass). Expects
67
+ * `{"trip": <bool>, "reason": "<short>"}`. Trips with `action` (default `"block"`) and the model's
68
+ * reason. **Throws** on malformed output — deliberately: a judge whose output can't be read must not
69
+ * silently pass, so the caller's `onError` policy (fail-closed by default) decides. See {@link judge}.
70
+ */
71
+ export function parseVerdict(text, opts = {}) {
72
+ const action = opts.action ?? 'block';
73
+ const data = coerceJson(text);
74
+ const trip = data.trip;
75
+ if (typeof trip !== 'boolean') {
76
+ throw new Error("judge JSON is missing a boolean 'trip' field");
77
+ }
78
+ if (!trip)
79
+ return null;
80
+ const reason = data.reason;
81
+ return new Verdict(action, reason ? String(reason) : 'llm_judge tripped');
82
+ }
83
+ /**
84
+ * Compose {@link verdictPrompt} + your model call + {@link parseVerdict} into a check ready for
85
+ * `rules.llmJudge`.
86
+ *
87
+ * `respond(system, user)` is *your* callable — sync or async — that runs one model call given the
88
+ * system instruction and the payload text, and returns the assistant's reply string. Make that call
89
+ * through an `instrument()`-ed client and its cost is budgeted + audited.
90
+ *
91
+ * ```ts
92
+ * import { judge, rules } from '@cendor/guardrails';
93
+ *
94
+ * const respond = async (system: string, user: string): Promise<string> => {
95
+ * const r = await client.chat.completions.create({
96
+ * model: 'gpt-4o-mini',
97
+ * messages: [{ role: 'system', content: system }, { role: 'user', content: user }],
98
+ * });
99
+ * return r.choices[0].message.content ?? '';
100
+ * };
101
+ *
102
+ * const check = judge.judge(respond, 'Trip on requests to exfiltrate secrets or run destructive shell.');
103
+ * const guardrail = rules.llmJudge(check, { timeout: 8 });
104
+ * ```
105
+ */
106
+ export function judge(respond, policy, opts = {}) {
107
+ const action = opts.action ?? 'block';
108
+ const system = verdictPrompt(policy, opts.template ?? DEFAULT_SYSTEM);
109
+ const payloadText = (payload) => typeof payload === 'string' ? payload : String(payload);
110
+ return async (payload, _ctx) => {
111
+ const reply = await respond(system, payloadText(payload));
112
+ return parseVerdict(reply, { action });
113
+ };
114
+ }
115
+ /**
116
+ * The default *task-adherence* instruction. `{instruction}` is filled with the user's originating
117
+ * request (from `Context.instruction`); the proposed tool call is passed as the user message. It pins
118
+ * the model to the same strict-JSON verdict {@link parseVerdict} reads, where `trip=true` means the
119
+ * tool call is **misaligned** with the instruction.
120
+ */
121
+ export const ADHERENCE_SYSTEM = "You are an alignment auditor for an AI agent. The user's instruction for this run was:\n\n" +
122
+ '{instruction}\n\n' +
123
+ 'You will be shown a tool call the agent now proposes to make. Decide whether that tool call is ' +
124
+ "aligned with — and in service of — the user's instruction. It is MISALIGNED if it pursues a " +
125
+ 'different goal, exceeds what was asked, or takes an action the user did not request.\n\n' +
126
+ 'Reply with a single JSON object and nothing else: {"trip": <true|false>, "reason": ' +
127
+ '"<one short sentence>"}. Set "trip" to true only if the tool call is misaligned with the ' +
128
+ 'instruction. Do not include markdown, code fences, or any text outside the JSON object.';
129
+ function instructionOf(ctx) {
130
+ const instr = (ctx.instruction ?? '').trim();
131
+ if (instr)
132
+ return instr;
133
+ const meta = ctx.metadata;
134
+ const fromMeta = meta && typeof meta === 'object' ? meta.user_input : undefined;
135
+ return fromMeta != null ? String(fromMeta).trim() : '';
136
+ }
137
+ function proposedCallText(payload, ctx) {
138
+ const tool = (ctx.tool ?? '').trim();
139
+ const args = ctx.toolArgs !== undefined && ctx.toolArgs !== null ? ctx.toolArgs : payload;
140
+ let argsText;
141
+ try {
142
+ argsText = JSON.stringify(args) ?? String(args);
143
+ }
144
+ catch {
145
+ argsText = String(args);
146
+ }
147
+ return tool ? `Tool: ${tool}\nArguments: ${argsText}` : `Proposed action: ${argsText}`;
148
+ }
149
+ /**
150
+ * A **bring-your-own-judge** task-adherence check for the `tool_call` stage: *given the user's
151
+ * instruction and this proposed tool call + arguments, is the action aligned with intent?* Returns a
152
+ * check ready for `rules.llmJudge` (like {@link judge}). Reads the user's instruction from
153
+ * `Context.instruction` and the proposed call from `ctx.tool` / `ctx.toolArgs` (or the payload),
154
+ * calls your `respond(system, user)`, and parses a strict-JSON verdict — `trip=true` means
155
+ * *misaligned*. Defaults to `action:'flag'` (advisory). No adherence-rate claim: a BYO judge, only
156
+ * as good as your model + prompt.
157
+ *
158
+ * > 🚧 The `@cendor/sdk` auto-threading of the user turn into `ctx.instruction` is a deferred parity
159
+ * > tail — set `ctx.instruction` yourself until it lands. See docs/guardrails.md "Task adherence".
160
+ */
161
+ export function taskAdherence(respond, opts = {}) {
162
+ const action = opts.action ?? 'flag';
163
+ const template = opts.template ?? ADHERENCE_SYSTEM;
164
+ return async (payload, ctx) => {
165
+ const instruction = instructionOf(ctx) || '(no instruction provided)';
166
+ const system = template.replaceAll('{instruction}', instruction);
167
+ const reply = await respond(system, proposedCallText(payload, ctx));
168
+ return parseVerdict(reply, { action });
169
+ };
170
+ }
171
+ //# sourceMappingURL=judge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge.js","sourceRoot":"","sources":["../src/judge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Config-as-data — build a guardrail list from a versioned JSON/YAML policy. The TS port of
3
+ * `cendor.guardrails.policy`.
4
+ *
5
+ * `loadPolicy` reads a policy **document or text** (there is no filesystem in an all-runtime package —
6
+ * read the file yourself and pass the text/object) and builds the `Guardrail[]` you hand to
7
+ * `Agent({ guardrails })` / `install()`. The point is **evidence**: the document's content hash and
8
+ * declared version are stamped into every decision's `metadata` (`policy_hash` / `policy_version`), so
9
+ * the audit chain proves *which* policy was active. Only the deterministic built-ins are constructible
10
+ * from data. JSON is built in; for YAML pass a `parse` (e.g. `yaml`'s `parse`).
11
+ *
12
+ * ```ts
13
+ * import { loadPolicy } from '@cendor/guardrails';
14
+ * const policy = loadPolicy(jsonText); // or loadPolicy(text, { parse: YAML.parse })
15
+ * agent = new Agent({ guardrails: policy });
16
+ * policy.policyHash; // "sha256:…" policy.policyVersion; // "2026-07-09"
17
+ * ```
18
+ */
19
+ import { type Guardrail } from './decision.js';
20
+ /** A `Guardrail[]` with provenance — pass it straight to `Agent({ guardrails })` / `install()`. */
21
+ export interface LoadedPolicy extends Array<Guardrail> {
22
+ /** `"sha256:<hex>"` of the canonical document — also stamped onto every decision. */
23
+ policyHash: string;
24
+ /** The document's `version` field — also stamped onto every decision. */
25
+ policyVersion: string;
26
+ }
27
+ export interface LoadPolicyOptions {
28
+ /** Parse `source` when it is a string (default `JSON.parse`); pass e.g. a YAML parser for YAML. */
29
+ parse?: (text: string) => unknown;
30
+ }
31
+ /** The rule names a policy document may use (deterministic built-ins only). */
32
+ export declare const POLICY_RULE_NAMES: readonly string[];
33
+ /**
34
+ * Build a {@link LoadedPolicy} from a JSON/YAML document (a parsed object, or text parsed with
35
+ * `JSON.parse` / your `opts.parse`). Every guardrail is stamped with `policy_hash` / `policy_version`
36
+ * in its metadata, so each decision records which policy was active.
37
+ *
38
+ * @throws if the document is malformed or names an unknown / non-declarative rule.
39
+ */
40
+ export declare function loadPolicy(source: string | Record<string, unknown>, opts?: LoadPolicyOptions): LoadedPolicy;
41
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,eAAe,CAAC;AAchE,mGAAmG;AACnG,MAAM,WAAW,YAAa,SAAQ,KAAK,CAAC,SAAS,CAAC;IACpD,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,mGAAmG;IACnG,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAyCD,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAA8B,CAAC;AAgB9E;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,IAAI,GAAE,iBAAsB,GAC3B,YAAY,CA8Cd"}
package/dist/policy.js ADDED
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Config-as-data — build a guardrail list from a versioned JSON/YAML policy. The TS port of
3
+ * `cendor.guardrails.policy`.
4
+ *
5
+ * `loadPolicy` reads a policy **document or text** (there is no filesystem in an all-runtime package —
6
+ * read the file yourself and pass the text/object) and builds the `Guardrail[]` you hand to
7
+ * `Agent({ guardrails })` / `install()`. The point is **evidence**: the document's content hash and
8
+ * declared version are stamped into every decision's `metadata` (`policy_hash` / `policy_version`), so
9
+ * the audit chain proves *which* policy was active. Only the deterministic built-ins are constructible
10
+ * from data. JSON is built in; for YAML pass a `parse` (e.g. `yaml`'s `parse`).
11
+ *
12
+ * ```ts
13
+ * import { loadPolicy } from '@cendor/guardrails';
14
+ * const policy = loadPolicy(jsonText); // or loadPolicy(text, { parse: YAML.parse })
15
+ * agent = new Agent({ guardrails: policy });
16
+ * policy.policyHash; // "sha256:…" policy.policyVersion; // "2026-07-09"
17
+ * ```
18
+ */
19
+ import { normalizeStages } from './decision.js';
20
+ import { jsonSchema, keywordDeny, lengthBounds, regexRule, urlAllowlist, urlDeny, } from './rules.js';
21
+ const arr = (v) => (Array.isArray(v) ? v.map(String) : []);
22
+ const bool = (v) => (v === undefined ? undefined : Boolean(v));
23
+ /** The rules constructible from data alone (deterministic; no callable / client / embedding fn). */
24
+ const POLICY_RULES = {
25
+ keyword_deny: (a, c) => keywordDeny(arr(a.words), {
26
+ stage: c.stage,
27
+ action: c.action,
28
+ name: c.name,
29
+ ignoreCase: bool(a.ignore_case ?? a.ignoreCase),
30
+ }),
31
+ regex_rule: (a, c) => regexRule(String(a.pattern ?? ''), {
32
+ stage: c.stage,
33
+ action: c.action,
34
+ name: c.name,
35
+ replacement: a.replacement === undefined ? undefined : String(a.replacement),
36
+ }),
37
+ url_allowlist: (a, c) => urlAllowlist(arr(a.domains), { stage: c.stage, action: c.action, name: c.name }),
38
+ url_deny: (a, c) => urlDeny(arr(a.domains), { stage: c.stage, action: c.action, name: c.name }),
39
+ length_bounds: (a, c) => lengthBounds({
40
+ stage: c.stage,
41
+ action: c.action,
42
+ name: c.name,
43
+ maxChars: (a.max_chars ?? a.maxChars),
44
+ maxTokens: (a.max_tokens ?? a.maxTokens),
45
+ model: a.model,
46
+ }),
47
+ json_schema: (a, c) => jsonSchema((a.schema ?? {}), {
48
+ stage: c.stage,
49
+ action: c.action,
50
+ name: c.name,
51
+ }),
52
+ };
53
+ /** The rule names a policy document may use (deterministic built-ins only). */
54
+ export const POLICY_RULE_NAMES = Object.keys(POLICY_RULES);
55
+ function canonical(v) {
56
+ if (v === null || typeof v !== 'object')
57
+ return JSON.stringify(v) ?? 'null';
58
+ if (Array.isArray(v))
59
+ return `[${v.map(canonical).join(',')}]`;
60
+ const obj = v;
61
+ const keys = Object.keys(obj).sort();
62
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(',')}}`;
63
+ }
64
+ function coerceStage(stage) {
65
+ if (Array.isArray(stage))
66
+ return stage.map(String);
67
+ if (typeof stage === 'string')
68
+ return stage;
69
+ return undefined;
70
+ }
71
+ /**
72
+ * Build a {@link LoadedPolicy} from a JSON/YAML document (a parsed object, or text parsed with
73
+ * `JSON.parse` / your `opts.parse`). Every guardrail is stamped with `policy_hash` / `policy_version`
74
+ * in its metadata, so each decision records which policy was active.
75
+ *
76
+ * @throws if the document is malformed or names an unknown / non-declarative rule.
77
+ */
78
+ export function loadPolicy(source, opts = {}) {
79
+ const config = typeof source === 'string' ? (opts.parse ?? JSON.parse)(source) : source;
80
+ if (config === null || typeof config !== 'object' || Array.isArray(config)) {
81
+ throw new Error('policy document must be an object');
82
+ }
83
+ const doc = config;
84
+ const entries = doc.guardrails;
85
+ if (!Array.isArray(entries))
86
+ throw new Error("policy document must have a 'guardrails' array");
87
+ const policyHash = `sha256:${sha256Hex(canonical(doc))}`;
88
+ const policyVersion = String(doc.version ?? '');
89
+ const stamp = { policy_hash: policyHash, policy_version: policyVersion };
90
+ const guardrails = entries.map((entry, i) => {
91
+ if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
92
+ throw new Error(`guardrails[${i}] must be an object`);
93
+ }
94
+ const e = entry;
95
+ const rule = String(e.rule);
96
+ const build = POLICY_RULES[rule];
97
+ if (build === undefined) {
98
+ throw new Error(`guardrails[${i}]: unknown or non-declarative rule ${JSON.stringify(rule)}; policy documents support ${POLICY_RULE_NAMES.join(', ')} (rules needing a callable or a client are wired in code)`);
99
+ }
100
+ const common = {
101
+ stage: coerceStage(e.stage),
102
+ action: e.action,
103
+ name: e.name,
104
+ };
105
+ let g;
106
+ try {
107
+ g = build(e.args ?? {}, common);
108
+ }
109
+ catch (err) {
110
+ throw new Error(`guardrails[${i}] (${rule}): bad arguments — ${err.message}`);
111
+ }
112
+ // touch normalizeStages defensively so an explicit bad stage array fails here with the index
113
+ if (common.stage !== undefined)
114
+ normalizeStages(common.stage);
115
+ g.metadata = { ...(g.metadata ?? {}), ...stamp };
116
+ return g;
117
+ });
118
+ const policy = guardrails;
119
+ policy.policyHash = policyHash;
120
+ policy.policyVersion = policyVersion;
121
+ return policy;
122
+ }
123
+ // --------------------------------------------------------------------------- SHA-256 (all-runtime)
124
+ //
125
+ // A compact, dependency-free SHA-256 so `loadPolicy` stays SYNC and all-runtime (no node:crypto, no
126
+ // async crypto.subtle). Verified against the standard "" / "abc" vectors in the tests. The hash is
127
+ // canonical-per-language (over TS's canonical JSON); it is not promised byte-identical to Python's.
128
+ const K = new Uint32Array([
129
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
130
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
131
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
132
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
133
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
134
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
135
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
136
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
137
+ ]);
138
+ function rotr(x, n) {
139
+ return (x >>> n) | (x << (32 - n));
140
+ }
141
+ function sha256Hex(input) {
142
+ const bytes = new TextEncoder().encode(input);
143
+ const l = bytes.length;
144
+ const withOne = l + 1;
145
+ const pad = (56 - (withOne % 64) + 64) % 64;
146
+ const total = withOne + pad + 8;
147
+ const msg = new Uint8Array(total);
148
+ msg.set(bytes);
149
+ msg[l] = 0x80;
150
+ const dv = new DataView(msg.buffer);
151
+ // 64-bit big-endian bit length; inputs here are well under 2^32 bits, so the high word is 0.
152
+ dv.setUint32(total - 4, (l * 8) >>> 0, false);
153
+ let h0 = 0x6a09e667;
154
+ let h1 = 0xbb67ae85;
155
+ let h2 = 0x3c6ef372;
156
+ let h3 = 0xa54ff53a;
157
+ let h4 = 0x510e527f;
158
+ let h5 = 0x9b05688c;
159
+ let h6 = 0x1f83d9ab;
160
+ let h7 = 0x5be0cd19;
161
+ const w = new Uint32Array(64);
162
+ for (let i = 0; i < total; i += 64) {
163
+ for (let t = 0; t < 16; t++)
164
+ w[t] = dv.getUint32(i + t * 4, false);
165
+ for (let t = 16; t < 64; t++) {
166
+ const w15 = w[t - 15];
167
+ const w2 = w[t - 2];
168
+ const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ (w15 >>> 3);
169
+ const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ (w2 >>> 10);
170
+ w[t] = (w[t - 16] + s0 + w[t - 7] + s1) >>> 0;
171
+ }
172
+ let a = h0;
173
+ let b = h1;
174
+ let c = h2;
175
+ let d = h3;
176
+ let e = h4;
177
+ let f = h5;
178
+ let g = h6;
179
+ let h = h7;
180
+ for (let t = 0; t < 64; t++) {
181
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
182
+ const ch = (e & f) ^ (~e & g);
183
+ const t1 = (h + s1 + ch + K[t] + w[t]) >>> 0;
184
+ const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
185
+ const maj = (a & b) ^ (a & c) ^ (b & c);
186
+ const t2 = (s0 + maj) >>> 0;
187
+ h = g;
188
+ g = f;
189
+ f = e;
190
+ e = (d + t1) >>> 0;
191
+ d = c;
192
+ c = b;
193
+ b = a;
194
+ a = (t1 + t2) >>> 0;
195
+ }
196
+ h0 = (h0 + a) >>> 0;
197
+ h1 = (h1 + b) >>> 0;
198
+ h2 = (h2 + c) >>> 0;
199
+ h3 = (h3 + d) >>> 0;
200
+ h4 = (h4 + e) >>> 0;
201
+ h5 = (h5 + f) >>> 0;
202
+ h6 = (h6 + g) >>> 0;
203
+ h7 = (h7 + h) >>> 0;
204
+ }
205
+ return [h0, h1, h2, h3, h4, h5, h6, h7]
206
+ .map((x) => (x >>> 0).toString(16).padStart(8, '0'))
207
+ .join('');
208
+ }
209
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Red-team evaluation — measure a guardrail's trip rate against a labeled corpus. The TS port of
3
+ * `cendor.guardrails.redteam`.
4
+ *
5
+ * The honest path to *any* detection number: run **your** guardrails over a labeled corpus and
6
+ * publish the per-category trip rate + false-positive rate, naming the corpus. cendor **vends no
7
+ * attack data** — `loadCorpus` parses records / text **you** supplied (public sets like AdvBench /
8
+ * JailbreakBench / HackAPrompt are referenced in the docs; you fetch them under their own licenses).
9
+ * There is no `node:fs` here — read the file yourself and pass the text or the parsed array. The
10
+ * report is a measurement tool, not a claim.
11
+ *
12
+ * Deterministic guardrails make the run offline + reproducible; a run with an `llmJudge` / hosted
13
+ * rail should be cassette-recorded so CI stays offline. Imports only `./decision` + the engine.
14
+ */
15
+ import { type Guardrail } from './decision.js';
16
+ export declare const ATTACK = "attack";
17
+ export declare const BENIGN = "benign";
18
+ /** One labeled probe. `label` is `"attack"` (should trip) or `"benign"` (should pass). */
19
+ export interface AttackCase {
20
+ text: string;
21
+ label?: string;
22
+ category?: string;
23
+ id?: string;
24
+ }
25
+ /** Counts + rates from a red-team run — no shipped claim; it describes the corpus you name. */
26
+ export declare class RedTeamReport {
27
+ total: number;
28
+ attacks: number;
29
+ benign: number;
30
+ caught: number;
31
+ falsePositives: number;
32
+ /** category → `[attacks, caught]`. */
33
+ byCategory: Record<string, [number, number]>;
34
+ /** Recall on attack cases (caught / attacks); `0` when there are no attacks. */
35
+ get tripRate(): number;
36
+ /** Fraction of benign cases that tripped; `0` when there are no benign cases. */
37
+ get falsePositiveRate(): number;
38
+ /** A one-line, corpus-agnostic summary (safe to log — no case text). */
39
+ summary(): string;
40
+ }
41
+ export interface LoadCorpusOptions {
42
+ /** For a text `source`: `"jsonl"` / `"json"` / `"csv"` (default `"jsonl"`). Ignored for an array. */
43
+ format?: 'jsonl' | 'json' | 'csv';
44
+ }
45
+ /**
46
+ * Build a labeled corpus from an **array** of records (already parsed) or a **text** blob you read
47
+ * yourself — `jsonl` (one object per line), `json` (an array), or `csv` (a header row). Each record
48
+ * needs a `text` field; `label` (default `"attack"`) / `category` / `id` are optional. No `node:fs`.
49
+ */
50
+ export declare function loadCorpus(source: readonly unknown[] | string, opts?: LoadCorpusOptions): AttackCase[];
51
+ /**
52
+ * Run `guardrails` over each case at `stage` and tally trip rate + false positives. A case trips
53
+ * when any guardrail blocks/redacts/flags it (a `block` throws `GuardrailTripped` — counted as a
54
+ * trip, not an error). Sync only — for an `async` check use {@link runRedteamAsync}.
55
+ */
56
+ export declare function runRedteam(guardrails: readonly Guardrail[], cases: Iterable<AttackCase>, opts?: {
57
+ stage?: string;
58
+ }): RedTeamReport;
59
+ /** Async counterpart of {@link runRedteam} — awaits `async` checks (an `llmJudge` / hosted rail). */
60
+ export declare function runRedteamAsync(guardrails: readonly Guardrail[], cases: Iterable<AttackCase>, opts?: {
61
+ stage?: string;
62
+ }): Promise<RedTeamReport>;
63
+ //# sourceMappingURL=redteam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redteam.d.ts","sourceRoot":"","sources":["../src/redteam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,KAAK,SAAS,EAAoB,MAAM,eAAe,CAAC;AAGjE,eAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,eAAO,MAAM,MAAM,WAAW,CAAC;AAE/B,0FAA0F;AAC1F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,+FAA+F;AAC/F,qBAAa,aAAa;IACxB,KAAK,SAAK;IACV,OAAO,SAAK;IACZ,MAAM,SAAK;IACX,MAAM,SAAK;IACX,cAAc,SAAK;IACnB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM;IAElD,gFAAgF;IAChF,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,iFAAiF;IACjF,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,wEAAwE;IACxE,OAAO,IAAI,MAAM;CAOlB;AAgCD,MAAM,WAAW,iBAAiB;IAChC,qGAAqG;IACrG,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,EACnC,IAAI,GAAE,iBAAsB,GAC3B,UAAU,EAAE,CAiBd;AAiBD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC3B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,aAAa,CAcf;AAED,qGAAqG;AACrG,wBAAsB,eAAe,CACnC,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC3B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,OAAO,CAAC,aAAa,CAAC,CAcxB"}