gateia 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -33,14 +33,14 @@ const result = await gate({
33
33
  model: "gpt-4.1",
34
34
  prompt: "Analyze this refund request against policy...",
35
35
  contract: RefundSchema,
36
- policies: ["support-safe", "no-hallucinations"]
36
+ policies: ["support-safe"]
37
37
  });
38
38
 
39
39
  console.log(result.safeOutput);
40
40
  // { valid: true, reason: "Item damaged in transit", refund_amount: 49.99 }
41
41
 
42
42
  console.log(result.enforcement.appliedPolicies);
43
- // ['finance-safe', 'no-hallucinations']
43
+ // [{ id: 'finance-safe', outcome: 'pass' }]
44
44
 
45
45
  console.log(result.traceId);
46
46
  // "uuid..."
@@ -69,22 +69,92 @@ GATEIA_MOCK_ADAPTERS=true
69
69
  - `policies`: Array of policy IDs (strings) or Policy objects.
70
70
  - `behavior`: Logic for repair, retries, and blocking.
71
71
 
72
+ **Returns:** `Promise<GateResult<T>>`
73
+
74
+ ```typescript
75
+ type GateResult<T> = {
76
+ safeOutput?: T; // Type-safe validated output
77
+ traceId: string; // Unique ID for this request
78
+
79
+ enforcement: {
80
+ appliedPolicies: Array<{
81
+ id: string;
82
+ mode: "enforce" | "audit";
83
+ outcome: "pass" | "warn" | "block";
84
+ reasons?: string[];
85
+ }>;
86
+
87
+ contract: {
88
+ outcome: "pass" | "fail" | "repaired";
89
+ errors?: Array<{ path: string; message: string }>;
90
+ };
91
+
92
+ actions: Array<{ type: "rewrite" | "block"; policyId?: string; }>;
93
+ violations: Array<{
94
+ policyId: string;
95
+ code: string;
96
+ severity: "low"|"med"|"high";
97
+ message: string;
98
+ }>;
99
+ };
100
+
101
+ usage: {
102
+ model: string;
103
+ provider: string;
104
+ latencyMs: number;
105
+ tokens?: { prompt: number; completion: number; total: number };
106
+ costUsd?: number;
107
+ };
108
+ };
109
+ ```
110
+
72
111
  ### Policies
73
112
 
74
- Gateia includes built-in policies:
75
- - `finance-safe`: Blocks "guaranteed returns".
76
- - `no-hallucinations`: (Audit only) Placeholder for hallucination checks.
113
+ ## Policy Library
77
114
 
78
- You can define custom policies:
115
+ Gateia comes with a set of built-in policies you can use immediately.
116
+
117
+ | Policy ID | Function | Checks For | Outcome |
118
+ |-----------|----------|------------|---------|
119
+ | **`finance-safe`** | Financial Compliance | "Guaranteed returns", "No risk", "Guaranteed approval", "100% guaranteed" | `BLOCK` |
120
+ | **`support-safe`** | Support Safety (Alias) | Same as above. Useful for refund processing agents. | `BLOCK` |
121
+ | **`pii-safe`** | Data Privacy | Email addresses (`x@y.com`), Phone numbers (Format: `123-456-7890`) | `BLOCK` |
122
+ | **`secrets-safe`** | Security | API Keys (OpenAI, AWS, GitHub), Private Keys | `BLOCK` |
123
+ | **`markup-safe`** | XSS Prevention | `<script>`, `<iframe>`, `javascript:` URIs | `BLOCK` |
124
+
125
+ ### Custom Policies
79
126
  ```typescript
80
- {
81
- id: "my-policy",
82
- mode: "enforce",
127
+ import { gate, Policy } from 'gateia';
128
+
129
+ // 1. Define your custom policy
130
+ const noCompetitors: Policy = {
131
+ id: 'no-competitors',
132
+ mode: 'enforce', // 'audit' to just warn
83
133
  check: (output) => {
84
- if (output.includes("bad word")) {
85
- return { outcome: "block", violations: [...] };
134
+ // output is strict typed from your Contract (or string if simple)
135
+ const text = JSON.stringify(output).toLowerCase();
136
+
137
+ if (text.includes('acme corp')) {
138
+ return {
139
+ outcome: 'block',
140
+ violations: [{
141
+ policyId: 'no-competitors',
142
+ code: 'COMPETITOR_MENTION',
143
+ message: 'Mentioned competitor Acme Corp',
144
+ severity: 'high',
145
+ evidence: { snippet: 'acme corp' }
146
+ }]
147
+ };
86
148
  }
87
- return { outcome: "pass" };
149
+ return { outcome: 'pass' };
88
150
  }
89
- }
151
+ };
152
+
153
+ // 2. Use it in the gate
154
+ await gate({
155
+ model: 'gpt-4',
156
+ prompt: 'Who is the best provider?',
157
+ contract: z.string(),
158
+ policies: ['finance-safe', noCompetitors] // Mix built-in ID strings and custom objects
159
+ });
90
160
  ```
@@ -3,6 +3,10 @@ export interface ContractResult<T> {
3
3
  success: boolean;
4
4
  data?: T;
5
5
  error?: string;
6
+ errors?: {
7
+ path: string;
8
+ message: string;
9
+ }[];
6
10
  }
7
11
  export declare class ContractEngine {
8
12
  validate<T>(data: any, schema: z.ZodType<T>): ContractResult<T>;
@@ -8,10 +8,14 @@ class ContractEngine {
8
8
  return { success: true, data: result.data };
9
9
  }
10
10
  else {
11
- const errorMsg = result.error.errors
12
- .map((e) => `${e.path.join('.')}: ${e.message}`)
11
+ const formattedErrors = result.error.errors.map(e => ({
12
+ path: e.path.join('.'),
13
+ message: e.message
14
+ }));
15
+ const errorMsg = formattedErrors
16
+ .map((e) => `${e.path}: ${e.message}`)
13
17
  .join('; ');
14
- return { success: false, error: errorMsg };
18
+ return { success: false, error: errorMsg, errors: formattedErrors };
15
19
  }
16
20
  }
17
21
  // Helper to format error for the LLM repair prompt
package/dist/index.js CHANGED
@@ -21,8 +21,8 @@ const uuid_1 = require("uuid");
21
21
  const registry_1 = require("./adapters/registry");
22
22
  const contract_1 = require("./engine/contract");
23
23
  const policy_1 = require("./engine/policy");
24
- const defaults_1 = require("./engine/defaults");
25
- // Global instances (lazy init or singleton)
24
+ const policies_1 = require("./policies");
25
+ // Global instances
26
26
  const registry = new registry_1.ModelRegistry();
27
27
  const contractEngine = new contract_1.ContractEngine();
28
28
  const policyEngine = new policy_1.PolicyEngine();
@@ -35,7 +35,7 @@ async function gate(params) {
35
35
  // 2. Resolve Policies
36
36
  const activePolicies = policies.map(p => {
37
37
  if (typeof p === 'string') {
38
- const found = defaults_1.predefinedPolicies[p];
38
+ const found = policies_1.policyLibrary[p];
39
39
  if (!found)
40
40
  throw new types_1.GateiaError(`Unknown policy: ${p}`, traceId);
41
41
  return found;
@@ -126,15 +126,30 @@ async function gate(params) {
126
126
  }
127
127
  }
128
128
  }
129
+ // Construct usage object
130
+ const finalUsage = {
131
+ provider: registry.getAdapter(model).constructor.name,
132
+ model: params.model,
133
+ latencyMs: finalLatency,
134
+ inputTokens: totalTokens.prompt,
135
+ outputTokens: totalTokens.completion,
136
+ totalTokens: totalTokens.total,
137
+ // costUsd: ... // TODO: Add cost calculation logic based on provider/model
138
+ };
129
139
  if (!finalSafeOutput) {
130
140
  // Block/Throw
131
141
  // Construct report
132
142
  const report = {
133
- appliedPolicies: activePolicies.map(p => p.id),
134
- contractOutcome: 'fail',
143
+ appliedPolicies: [], // Policies not applied due to contract failure
144
+ contract: {
145
+ outcome: 'fail',
146
+ errors: contractEngine.validate(rawResult, contract).errors // re-running valid logic or grabbing from lastError
147
+ },
135
148
  actions: [],
136
- violations: [{ policyId: 'contract', message: lastError || "Contract Validation Failed", severity: 'critical' }]
149
+ violations: [{ policyId: 'contract', code: 'CONTRACT_FAIL', message: lastError || "Contract Validation Failed", severity: 'high' }]
137
150
  };
151
+ // For accurate errors we might want to capture them better above instead of re-running or assuming lastError string
152
+ // But for now this matches structure.
138
153
  throw new types_1.GateiaError("Contract Check Failed", traceId, report);
139
154
  }
140
155
  // 4. Apply Policies
@@ -144,47 +159,54 @@ async function gate(params) {
144
159
  const policyResult = await policyEngine.evaluate(activePolicies, finalSafeOutput, policyCtx);
145
160
  let finalViolations = policyResult.violations || [];
146
161
  let enforcementActions = [];
162
+ // Detailed policy reporting
163
+ const appliedRecs = activePolicies.map(p => {
164
+ const policyViolations = finalViolations.filter(v => v.policyId === p.id);
165
+ let outcome = 'pass';
166
+ if (policyViolations.length > 0) {
167
+ const isBlock = policyViolations.some(v => v.severity === 'high');
168
+ outcome = isBlock ? 'block' : 'warn';
169
+ }
170
+ return {
171
+ id: p.id,
172
+ version: p.version,
173
+ mode: p.mode || 'enforce',
174
+ outcome,
175
+ reasons: policyViolations.map(v => v.message)
176
+ };
177
+ });
147
178
  if (policyResult.rewrittenOutput) {
148
179
  finalSafeOutput = policyResult.rewrittenOutput;
149
- enforcementActions.push('rewritten');
180
+ enforcementActions.push({ type: 'rewrite', policyId: 'policy-engine', note: 'Content rewritten by policy' });
150
181
  }
182
+ const report = {
183
+ appliedPolicies: appliedRecs,
184
+ contract: {
185
+ outcome: contractOutcome,
186
+ // If repaired, we could list repairs here if we tracked them detailly
187
+ },
188
+ actions: enforcementActions,
189
+ violations: finalViolations
190
+ };
151
191
  if (policyResult.outcome === 'block') {
152
- const report = {
153
- appliedPolicies: activePolicies.map(p => p.id),
154
- contractOutcome,
155
- actions: enforcementActions,
156
- violations: finalViolations
157
- };
158
192
  const onBlock = behavior?.onBlock || 'throw'; // Default throw?
159
193
  if (onBlock === 'throw') {
160
194
  throw new types_1.GateiaError("Policy Blocked Response", traceId, report);
161
195
  }
162
196
  // Return with no safeOutput? Or just raw?
163
- // Result<T> safeOutput is optional.
164
197
  return {
198
+ safeOutput: undefined,
165
199
  traceId,
166
200
  enforcement: report,
167
- usage: { provider: 'unknown', model: params.model, tokens: totalTokens }, // TODO: propagate provider name
168
- safeOutput: undefined
201
+ usage: finalUsage,
202
+ rawOutput: options?.includeRawOutput ? rawResult : undefined
169
203
  };
170
204
  }
171
- // Success
172
- const report = {
173
- appliedPolicies: activePolicies.map(p => p.id),
174
- contractOutcome,
175
- actions: enforcementActions,
176
- violations: finalViolations
177
- };
178
205
  return {
179
206
  safeOutput: finalSafeOutput,
180
207
  traceId,
181
208
  enforcement: report,
182
- usage: {
183
- provider: registry.getAdapter(model).constructor.name, // quick hack name
184
- model: params.model,
185
- tokens: totalTokens,
186
- latencyMs: finalLatency
187
- },
209
+ usage: finalUsage,
188
210
  rawOutput: options?.includeRawOutput ? rawResult : undefined
189
211
  };
190
212
  }
@@ -0,0 +1,8 @@
1
+ import { Policy } from '../types';
2
+ export declare const financeSafe: Policy;
3
+ export declare const supportSafe: {
4
+ id: string;
5
+ version?: string;
6
+ mode?: "enforce" | "audit";
7
+ check: (output: any, context: import("../types").PolicyContext) => import("../types").PolicyResult | Promise<import("../types").PolicyResult>;
8
+ };
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.supportSafe = exports.financeSafe = void 0;
4
+ exports.financeSafe = {
5
+ id: 'finance-safe',
6
+ mode: 'enforce',
7
+ check: (output) => {
8
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
9
+ const lower = text.toLowerCase();
10
+ // Block guarantees
11
+ const forbidden = [
12
+ 'guaranteed refund',
13
+ 'guaranteed return',
14
+ 'guaranteed approval',
15
+ 'no risk',
16
+ '100% guaranteed'
17
+ ];
18
+ const match = forbidden.find(phrase => lower.includes(phrase));
19
+ if (match) {
20
+ return {
21
+ outcome: 'block',
22
+ violations: [{
23
+ policyId: 'finance-safe',
24
+ code: 'FIN_GUARANTEE',
25
+ message: `Contains forbidden guarantee language: "${match}"`,
26
+ severity: 'high',
27
+ evidence: { snippet: match }
28
+ }]
29
+ };
30
+ }
31
+ return { outcome: 'pass' };
32
+ }
33
+ };
34
+ // Delegate alias
35
+ exports.supportSafe = { ...exports.financeSafe, id: 'support-safe' };
@@ -0,0 +1,6 @@
1
+ import { Policy } from '../types';
2
+ export declare const policyLibrary: Record<string, Policy>;
3
+ export * from './finance';
4
+ export * from './pii';
5
+ export * from './secrets';
6
+ export * from './markup';
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.policyLibrary = void 0;
18
+ const finance_1 = require("./finance");
19
+ const pii_1 = require("./pii");
20
+ const secrets_1 = require("./secrets");
21
+ const markup_1 = require("./markup");
22
+ // Registry of all built-in policies
23
+ exports.policyLibrary = {
24
+ 'finance-safe': finance_1.financeSafe,
25
+ 'support-safe': finance_1.supportSafe,
26
+ 'pii-safe': pii_1.piiSafe,
27
+ 'secrets-safe': secrets_1.secretsSafe,
28
+ 'markup-safe': markup_1.markupSafe
29
+ };
30
+ __exportStar(require("./finance"), exports);
31
+ __exportStar(require("./pii"), exports);
32
+ __exportStar(require("./secrets"), exports);
33
+ __exportStar(require("./markup"), exports);
@@ -0,0 +1,2 @@
1
+ import { Policy } from '../types';
2
+ export declare const markupSafe: Policy;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.markupSafe = void 0;
4
+ exports.markupSafe = {
5
+ id: 'markup-safe',
6
+ mode: 'enforce',
7
+ check: (output) => {
8
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
9
+ const lower = text.toLowerCase();
10
+ // Dangerous HTML/Script patterns
11
+ const patterns = [
12
+ { name: 'Script Tag', regex: /<script[\s\S]*?>/i },
13
+ { name: 'Iframe Tag', regex: /<iframe[\s\S]*?>/i },
14
+ { name: 'Object Tag', regex: /<object[\s\S]*?>/i },
15
+ { name: 'Javascript Protocol', regex: /javascript:/i }
16
+ ];
17
+ const violations = [];
18
+ for (const pattern of patterns) {
19
+ if (pattern.regex.test(text)) {
20
+ violations.push({
21
+ policyId: 'markup-safe',
22
+ code: 'UNSAFE_MARKUP',
23
+ message: `Unsafe markup detected: ${pattern.name}`,
24
+ severity: 'high',
25
+ evidence: { snippet: pattern.regex.source }
26
+ });
27
+ }
28
+ }
29
+ if (violations.length > 0) {
30
+ return { outcome: 'block', violations };
31
+ }
32
+ return { outcome: 'pass' };
33
+ }
34
+ };
@@ -0,0 +1,2 @@
1
+ import { Policy } from '../types';
2
+ export declare const piiSafe: Policy;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.piiSafe = void 0;
4
+ exports.piiSafe = {
5
+ id: 'pii-safe',
6
+ mode: 'enforce',
7
+ check: (output) => {
8
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
9
+ // Simple regex patterns (for MVP)
10
+ const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
11
+ const phoneRegex = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/; // US-centric simple
12
+ const violations = [];
13
+ if (emailRegex.test(text)) {
14
+ violations.push({
15
+ policyId: 'pii-safe',
16
+ code: 'PII_EMAIL',
17
+ message: 'Email address detected in output',
18
+ severity: 'high',
19
+ evidence: { snippet: 'email-detected-redacted' }
20
+ });
21
+ }
22
+ if (phoneRegex.test(text)) {
23
+ violations.push({
24
+ policyId: 'pii-safe',
25
+ code: 'PII_PHONE',
26
+ message: 'Phone number detected in output',
27
+ severity: 'high',
28
+ evidence: { snippet: 'phone-detected-redacted' }
29
+ });
30
+ }
31
+ if (violations.length > 0) {
32
+ return { outcome: 'block', violations };
33
+ }
34
+ return { outcome: 'pass' };
35
+ }
36
+ };
@@ -0,0 +1,2 @@
1
+ import { Policy } from '../types';
2
+ export declare const secretsSafe: Policy;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretsSafe = void 0;
4
+ exports.secretsSafe = {
5
+ id: 'secrets-safe',
6
+ mode: 'enforce',
7
+ check: (output) => {
8
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
9
+ // Common patterns for API keys and secrets
10
+ const patterns = [
11
+ { name: 'OpenAI Key', regex: /sk-[a-zA-Z0-9]{20,}/ },
12
+ { name: 'AWS Access Key', regex: /AKIA[0-9A-Z]{16}/ },
13
+ { name: 'Generic Private Key', regex: /-----BEGIN PRIVATE KEY-----/ },
14
+ { name: 'GitHub Token', regex: /ghp_[a-zA-Z0-9]{36}/ }
15
+ ];
16
+ const violations = [];
17
+ for (const pattern of patterns) {
18
+ if (pattern.regex.test(text)) {
19
+ violations.push({
20
+ policyId: 'secrets-safe',
21
+ code: 'SECRET_DETECTED',
22
+ message: `Potential ${pattern.name} detected`,
23
+ severity: 'high',
24
+ evidence: { snippet: 'REDACTED_SECRET' } // Never show the secret
25
+ });
26
+ }
27
+ }
28
+ if (violations.length > 0) {
29
+ return { outcome: 'block', violations };
30
+ }
31
+ return { outcome: 'pass' };
32
+ }
33
+ };
package/dist/types.d.ts CHANGED
@@ -1,10 +1,18 @@
1
1
  import { z } from 'zod';
2
2
  export type GateOutcome = 'pass' | 'block' | 'warn' | 'rewrite';
3
- export type PolicyAction = 'rewrite' | 'block' | 'log';
3
+ export interface PolicyAction {
4
+ type: 'rewrite' | 'block' | 'log';
5
+ policyId: string;
6
+ note?: string;
7
+ }
4
8
  export interface Violation {
5
9
  policyId: string;
10
+ code: string;
6
11
  message: string;
7
- severity: 'critical' | 'warn';
12
+ severity: 'low' | 'med' | 'high';
13
+ evidence?: {
14
+ snippet: string;
15
+ };
8
16
  }
9
17
  export interface PolicyContext {
10
18
  model: string;
@@ -48,21 +56,40 @@ export interface GateParams<T extends z.ZodTypeAny> {
48
56
  behavior?: GateBehavior;
49
57
  options?: GateOptions;
50
58
  }
59
+ export interface AppliedPolicyRec {
60
+ id: string;
61
+ version?: string;
62
+ mode: 'enforce' | 'audit';
63
+ outcome: GateOutcome;
64
+ reasons?: string[];
65
+ }
66
+ export interface ContractEnforcement {
67
+ name?: string;
68
+ outcome: 'pass' | 'fail' | 'repaired';
69
+ errors?: {
70
+ path: string;
71
+ message: string;
72
+ }[];
73
+ repairs?: {
74
+ op: 'add' | 'remove' | 'replace';
75
+ path: string;
76
+ note?: string;
77
+ }[];
78
+ }
51
79
  export interface GateEnforcementReport {
52
- appliedPolicies: string[];
53
- contractOutcome: 'pass' | 'fail' | 'repaired';
54
- actions: string[];
80
+ appliedPolicies: AppliedPolicyRec[];
81
+ contract: ContractEnforcement;
82
+ actions: PolicyAction[];
55
83
  violations: Violation[];
56
84
  }
57
85
  export interface GateUsage {
58
86
  provider: string;
59
87
  model: string;
60
- latencyMs?: number;
61
- tokens?: {
62
- prompt: number;
63
- completion: number;
64
- total: number;
65
- };
88
+ latencyMs: number;
89
+ inputTokens?: number;
90
+ outputTokens?: number;
91
+ totalTokens?: number;
92
+ costUsd?: number;
66
93
  }
67
94
  export interface GateResult<T> {
68
95
  safeOutput?: T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gateia",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "The trust layer for AI in production. Middleware for model routing, contract enforcement, and policy compliance.",
5
5
  "keywords": [
6
6
  "ai",