ovrule-lab 0.1.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/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # Ovrule SDK
2
+
3
+ TypeScript client for Ovrule case-file classification, guardrails, and receipt verification.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install ovrule
9
+ ```
10
+
11
+ ## 1. Simple classify
12
+
13
+ ```ts
14
+ import { classify } from "ovrule";
15
+
16
+ const receipt = await classify("Support agent wants to refund $5,000 without manager approval.");
17
+
18
+ console.log(receipt.decision);
19
+ console.log(receipt.summary);
20
+ ```
21
+
22
+ ## 2. `guard()` around an agent tool call
23
+
24
+ ```ts
25
+ import { guard } from "ovrule";
26
+
27
+ const result = await guard({
28
+ scenario: "Finance agent wants to wire $18,000 to a new vendor after bank details changed.",
29
+ policyPack: "finance",
30
+ });
31
+
32
+ if (!result.allowed) {
33
+ console.error(result.decision, result.suggestedFixes);
34
+ throw new Error("Tool call blocked by Ovrule.");
35
+ }
36
+
37
+ await issueWireTransfer();
38
+ ```
39
+
40
+ ## 3. LangChain middleware integration
41
+
42
+ ```ts
43
+ import { OvruleClient } from "ovrule";
44
+
45
+ const ovrule = new OvruleClient({ baseUrl: "https://your-ovrule-deployment.vercel.app" });
46
+
47
+ export async function beforeToolInvoke(input: string) {
48
+ const guard = await ovrule.guard({
49
+ scenario: input,
50
+ policyPack: "general",
51
+ });
52
+
53
+ if (!guard.allowed) {
54
+ return {
55
+ blocked: true,
56
+ receipt: guard.receipt,
57
+ fixes: guard.suggestedFixes,
58
+ };
59
+ }
60
+
61
+ return { blocked: false };
62
+ }
63
+ ```
64
+
65
+ ## API
66
+
67
+ - `classify(action, options?)`
68
+ - `guard(action, options?)`
69
+ - `verify(receipt, signature)`
@@ -0,0 +1,106 @@
1
+ export type Decision = "ADMISSIBLE" | "AMBIGUOUS" | "REFUSED";
2
+ export type Verdict = "PASS" | "WARN" | "FAIL";
3
+ export type PolicyPackId = "general" | "customer_support" | "healthcare" | "finance";
4
+ export type RuleName = "SAFETY" | "AUTHORIZATION" | "CAUSAL VALIDITY" | "REVERSIBILITY" | "IMPACT SCOPE" | "CONSENT";
5
+ export type RuleTraceItem = {
6
+ rule: RuleName;
7
+ verdict: Verdict;
8
+ reason: string;
9
+ };
10
+ export type AffectedParty = {
11
+ label: string;
12
+ type: "user" | "customer" | "employee" | "third_party" | "public" | "system" | "other";
13
+ impact: "low" | "medium" | "high";
14
+ };
15
+ export type EvidenceItem = {
16
+ label: string;
17
+ kind: "user_statement" | "policy" | "system_state" | "transaction_data" | "external_signal" | "other";
18
+ summary: string;
19
+ };
20
+ export type MissingInformationItem = {
21
+ field: string;
22
+ whyItMatters: string;
23
+ couldFlip: "PASS" | "WARN" | "FAIL" | "decision";
24
+ };
25
+ export type HistoryEvent = {
26
+ id: string;
27
+ receiptId: string;
28
+ eventType: "created" | "contested" | "overridden" | "revised" | "annotated";
29
+ actorType: "system" | "human_reviewer" | "user";
30
+ actorLabel?: string;
31
+ note?: string;
32
+ payload: Record<string, unknown>;
33
+ createdAt: string;
34
+ };
35
+ export type FixSuggestion = {
36
+ edit: string;
37
+ flips: string[];
38
+ rewrittenAction: string;
39
+ };
40
+ export type CaseFileReceipt = {
41
+ scenario: string;
42
+ decision: Decision;
43
+ proposedAction: string;
44
+ claimedGoal: string;
45
+ affectedParties: AffectedParty[];
46
+ authorityBasis: string;
47
+ evidenceUsed: EvidenceItem[];
48
+ evidenceMissing: EvidenceItem[];
49
+ severity: "low" | "medium" | "high";
50
+ riskScore: number;
51
+ summary: string;
52
+ whyOkay: string[];
53
+ whyFail: string[];
54
+ missingInformation: MissingInformationItem[];
55
+ ruleTrace: RuleTraceItem[];
56
+ receiptId: string;
57
+ hash: string;
58
+ timestamp: string;
59
+ receiptMetadata: {
60
+ receiptId: string;
61
+ hash: string;
62
+ timestamp: string;
63
+ };
64
+ history: HistoryEvent[];
65
+ challengeHistory: HistoryEvent[];
66
+ policyPack?: PolicyPackId;
67
+ suggestedFixes?: FixSuggestion[];
68
+ signature?: string;
69
+ };
70
+ export type ProposedAction = {
71
+ scenario: string;
72
+ claimedGoal?: string;
73
+ affectedParties?: string[];
74
+ authorityBasis?: string;
75
+ policyPack?: PolicyPackId;
76
+ };
77
+ export type GuardResult = {
78
+ allowed: boolean;
79
+ decision: Decision;
80
+ suggestedFixes: FixSuggestion[];
81
+ receipt: CaseFileReceipt;
82
+ };
83
+ export type OvruleClientOptions = {
84
+ baseUrl?: string;
85
+ fetch?: typeof globalThis.fetch;
86
+ };
87
+ type RequestOptions = {
88
+ signal?: AbortSignal;
89
+ policyPack?: PolicyPackId;
90
+ };
91
+ export declare class OvruleClient {
92
+ private readonly baseUrl;
93
+ private readonly fetchImpl;
94
+ constructor(options?: OvruleClientOptions);
95
+ classify(action: string | ProposedAction, options?: RequestOptions): Promise<CaseFileReceipt>;
96
+ guard(action: string | ProposedAction, options?: RequestOptions): Promise<GuardResult>;
97
+ verify(receipt: CaseFileReceipt, signature: string, options?: {
98
+ signal?: AbortSignal;
99
+ }): Promise<boolean>;
100
+ }
101
+ export declare function classify(action: string | ProposedAction, options?: RequestOptions & OvruleClientOptions): Promise<CaseFileReceipt>;
102
+ export declare function guard(action: string | ProposedAction, options?: RequestOptions & OvruleClientOptions): Promise<GuardResult>;
103
+ export declare function verify(receipt: CaseFileReceipt, signature: string, options?: {
104
+ signal?: AbortSignal;
105
+ } & OvruleClientOptions): Promise<boolean>;
106
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ function normalizeAction(action, options) {
2
+ if (typeof action === "string") {
3
+ return {
4
+ scenario: action,
5
+ policyPack: options?.policyPack,
6
+ };
7
+ }
8
+ return {
9
+ ...action,
10
+ policyPack: action.policyPack ?? options?.policyPack,
11
+ };
12
+ }
13
+ async function readJson(response) {
14
+ const data = (await response.json());
15
+ if (!response.ok) {
16
+ throw new Error(data.error ?? `Ovrule request failed with status ${response.status}.`);
17
+ }
18
+ return data;
19
+ }
20
+ export class OvruleClient {
21
+ baseUrl;
22
+ fetchImpl;
23
+ constructor(options = {}) {
24
+ this.baseUrl = options.baseUrl?.replace(/\/$/, "") ?? "";
25
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
26
+ if (!this.fetchImpl) {
27
+ throw new Error("No fetch implementation available. Pass one in OvruleClient options.");
28
+ }
29
+ }
30
+ async classify(action, options = {}) {
31
+ const payload = normalizeAction(action, options);
32
+ const response = await this.fetchImpl(`${this.baseUrl}/api/classify`, {
33
+ method: "POST",
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ Accept: "text/event-stream",
37
+ },
38
+ body: JSON.stringify({
39
+ scenario: payload.scenario,
40
+ policyPack: payload.policyPack,
41
+ }),
42
+ signal: options.signal,
43
+ });
44
+ if (!response.ok) {
45
+ const errorBody = await response.json().catch(() => null);
46
+ const message = errorBody && typeof errorBody === "object" && "error" in errorBody && typeof errorBody.error === "string"
47
+ ? errorBody.error
48
+ : `Ovrule classify failed with status ${response.status}.`;
49
+ throw new Error(message);
50
+ }
51
+ const reader = response.body?.getReader();
52
+ if (!reader) {
53
+ throw new Error("Ovrule classify did not return a readable response stream.");
54
+ }
55
+ const decoder = new TextDecoder();
56
+ let buffer = "";
57
+ let finalReceipt = null;
58
+ while (true) {
59
+ const { done, value } = await reader.read();
60
+ if (done) {
61
+ break;
62
+ }
63
+ buffer += decoder.decode(value, { stream: true });
64
+ const blocks = buffer.split("\n\n");
65
+ buffer = blocks.pop() ?? "";
66
+ for (const block of blocks) {
67
+ const lines = block.split("\n");
68
+ const eventName = lines.find((line) => line.startsWith("event:"))?.slice(6).trim();
69
+ const data = lines
70
+ .filter((line) => line.startsWith("data:"))
71
+ .map((line) => line.slice(5).trim())
72
+ .join("\n");
73
+ if (!eventName || !data) {
74
+ continue;
75
+ }
76
+ const parsed = JSON.parse(data);
77
+ if (parsed.type === "analysis.completed") {
78
+ finalReceipt = parsed.receipt;
79
+ }
80
+ if (parsed.type === "session.error") {
81
+ throw new Error(parsed.message);
82
+ }
83
+ }
84
+ }
85
+ if (!finalReceipt) {
86
+ throw new Error("Ovrule classify stream finished without a final receipt.");
87
+ }
88
+ return finalReceipt;
89
+ }
90
+ async guard(action, options = {}) {
91
+ const receipt = await this.classify(action, options);
92
+ const allowed = receipt.decision === "ADMISSIBLE";
93
+ const suggestedFixes = receipt.suggestedFixes ?? [];
94
+ return {
95
+ allowed,
96
+ decision: receipt.decision,
97
+ suggestedFixes,
98
+ receipt,
99
+ };
100
+ }
101
+ async verify(receipt, signature, options = {}) {
102
+ const response = await this.fetchImpl(`${this.baseUrl}/api/verify`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ },
107
+ body: JSON.stringify({ receipt, signature }),
108
+ signal: options.signal,
109
+ });
110
+ const data = await readJson(response);
111
+ return data.valid;
112
+ }
113
+ }
114
+ const defaultClient = new OvruleClient();
115
+ export async function classify(action, options) {
116
+ if (options?.baseUrl || options?.fetch) {
117
+ return new OvruleClient(options).classify(action, options);
118
+ }
119
+ return defaultClient.classify(action, options);
120
+ }
121
+ export async function guard(action, options) {
122
+ if (options?.baseUrl || options?.fetch) {
123
+ return new OvruleClient(options).guard(action, options);
124
+ }
125
+ return defaultClient.guard(action, options);
126
+ }
127
+ export async function verify(receipt, signature, options) {
128
+ if (options?.baseUrl || options?.fetch) {
129
+ return new OvruleClient(options).verify(receipt, signature, options);
130
+ }
131
+ return defaultClient.verify(receipt, signature, options);
132
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "ovrule-lab",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for Ovrule case-file classification, guardrails, and receipt verification.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "clean": "rm -rf dist",
22
+ "prepublishOnly": "npm run clean && npm run build"
23
+ },
24
+ "keywords": [
25
+ "ovrule",
26
+ "ai-governance",
27
+ "ai-audit",
28
+ "agent-safety",
29
+ "receipt-verification",
30
+ "typescript-sdk"
31
+ ],
32
+ "license": "MIT",
33
+ "sideEffects": false,
34
+ "devDependencies": {
35
+ "typescript": "^5.8.3"
36
+ }
37
+ }