@visiq/claude-code-harness 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,89 @@
1
+ # @visiq/claude-code-harness
2
+
3
+ VisIQ governance for the **Claude Code CLI** — real-time action monitoring &
4
+ pre-execution blocking plus retrieval monitoring, blocking and **in-context
5
+ redaction**, wired in through Claude Code's native hooks. No fork, no proxy, no
6
+ source changes to Claude Code.
7
+
8
+ It is the CLI-coding-agent analog of the VisIQ SDK harness (`@visiq/harness`) and
9
+ OpenClaw plugin: a thin adapter over the shared, tagged decision engine
10
+ (`@visiq/runtime` `UnifiedRuntime`) and the `@visiq/redact` masking primitive.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g @visiq/claude-code-harness
16
+
17
+ # 1) wire the hooks into your Claude Code settings (user-level by default)
18
+ visiq-claude-code install # → ~/.claude/settings.json
19
+ # visiq-claude-code install --project # → ./.claude/settings.json (shared)
20
+
21
+ # 2) provide credentials (or set VISIQ_API_KEY / VISIQ_AGENT_ID in the env)
22
+ visiq-claude-code configure --api-key <key> --agent-id <id> --base-url https://api.visiqlabs.com
23
+
24
+ # 3) verify
25
+ visiq-claude-code doctor
26
+ ```
27
+
28
+ Now every Claude Code session on this machine is governed by your VisIQ rules.
29
+
30
+ ## How it works
31
+
32
+ `install` adds three hooks that all invoke one fast dispatcher bin
33
+ (`visiq-claude-code-hook`):
34
+
35
+ | Hook | Matcher | What it does |
36
+ |------|---------|--------------|
37
+ | `SessionStart` | all | Registers the agent (`cli_harness`) so it shows connected, and pre-warms the on-disk rule bundle so the first tool call evaluates locally. |
38
+ | `PreToolUse` | all tools | The enforcement gate. Evaluates the action (+retrieval) facet and returns **allow / deny / ask**, or rewrites the arguments (**arg masking**) via `updatedInput`. `deny` blocks the tool *before it runs*. |
39
+ | `PostToolUse` | retrieval tools | Re-evaluates the retrieval facet against the returned content and, on a `redact`/`deny` decision, replaces the result the model sees via `updatedToolOutput` — **in-context redaction**. |
40
+
41
+ Decisions are computed **locally** against a signed rule bundle cached on disk,
42
+ so the hot path is offline-capable and never blocks the agent on the network.
43
+ The same bundle + evaluator the backend runs is used client-side, so client and
44
+ server decisions match by construction.
45
+
46
+ ### Capabilities
47
+
48
+ | Capability | Supported | Mechanism |
49
+ |---|---|---|
50
+ | Monitor actions in real time | ✅ | `PreToolUse` sees every tool call (full args) before exec |
51
+ | Block an action pre-execution | ✅ | `permissionDecision:"deny"` (blocks even under `bypassPermissions`) |
52
+ | Mask action arguments | ✅ | `updatedInput` rewrites `tool_input` before exec |
53
+ | Monitor retrieval | ✅ | `PreToolUse` (query) + `PostToolUse` (result) on WebFetch/WebSearch/Read/Grep/… |
54
+ | Block retrieval | ✅ | `PreToolUse` deny (pre-fetch) or `PostToolUse` withhold |
55
+ | Redact retrieval in-context | ✅ | `PostToolUse` `updatedToolOutput` structurally replaces `tool_response` |
56
+
57
+ ## Configuration
58
+
59
+ Credentials resolve by precedence (highest wins):
60
+
61
+ 1. Env: `VISIQ_API_KEY`, `VISIQ_AGENT_ID`, `VISIQ_BASE_URL` (or `VISIQ_ENDPOINT`)
62
+ 2. `VISIQ_CONFIG_PATH` JSON file
63
+ 3. `~/.visiq/claude-code/config.json` (written by `configure`)
64
+
65
+ State (bundle cache, session markers) lives under `~/.visiq/claude-code/`
66
+ (override with `VISIQ_HOME`).
67
+
68
+ ## Fail modes
69
+
70
+ - **Unconfigured** (no credentials): every hook is a **no-op** — an installed but
71
+ unconfigured harness never breaks Claude Code.
72
+ - **Configured, evaluation error / cold-start with no bundle** (enforce mode):
73
+ **fail-closed** — actions are denied and retrieval content is withheld (G001).
74
+ - **Monitor mode**: decisions are recorded as telemetry but never block.
75
+
76
+ ## Uninstall
77
+
78
+ ```bash
79
+ visiq-claude-code uninstall # removes only the VisIQ hooks; leaves yours intact
80
+ ```
81
+
82
+ ## Caveats
83
+
84
+ - Each hook is a short-lived subprocess (Claude Code's hook model), so there is a
85
+ small per-tool process-start cost. A future local-daemon transport (`type:"http"`
86
+ hooks) will remove it; the current disk-cached-bundle design keeps evaluation
87
+ local and correct in the meantime.
88
+ - Subagent (`Task`) internal tool calls are governed by the subagent's own hook
89
+ scope; main-session hooks cover the main loop.
@@ -0,0 +1 @@
1
+ import{readFile as h}from"node:fs/promises";import{homedir as g}from"node:os";import{join as u,isAbsolute as _,resolve as w}from"node:path";var P="@visiq/claude-code-harness";function s(){let n=process.env.VISIQ_HOME;return n&&n.trim().length>0?n:u(g(),".visiq","claude-code")}function C(){return u(s(),"config.json")}function S(n){return n==="~"||n.startsWith("~/")?u(g(),n.slice(1).replace(/^\//,"")):n}async function k(n){try{let e=_(n)?n:w(n),t=await h(e,"utf-8");return JSON.parse(t)}catch{return null}}function c(n,...e){for(let t of e){let r=n[t];if(typeof r=="string"&&r.length>0)return r}}function f(n){if(!n||typeof n!="object")return{};let e=n,t={},r=c(e,"apiKey","api_key"),i=c(e,"agentId","agent_id"),o=c(e,"baseUrl","base_url");return r&&(t.apiKey=r),i&&(t.agentId=i),o&&(t.baseUrl=o),t}async function l(n){if(!n)return{};let e=await k(S(n));if(!e||typeof e!="object")return{};let t=e,r=t.plugins?.entries,i=r&&typeof r=="object"?r[P]:void 0;return i&&typeof i=="object"&&i.config?f(i.config):f(t)}function x(n=process.env){let e={};return n.VISIQ_API_KEY&&(e.apiKey=n.VISIQ_API_KEY),n.VISIQ_AGENT_ID&&(e.agentId=n.VISIQ_AGENT_ID),n.VISIQ_BASE_URL?e.baseUrl=n.VISIQ_BASE_URL:n.VISIQ_ENDPOINT&&(e.baseUrl=n.VISIQ_ENDPOINT),e}function U(n,e,t){let r={...n,...e,...t};if(!r.apiKey||!r.agentId)return null;let i={apiKey:r.apiKey,agentId:r.agentId};return r.baseUrl&&(i.baseUrl=r.baseUrl),i}async function F(){let n=x(),e=await l(process.env.VISIQ_CONFIG_PATH),t=await l(C());return U(t,e,n)}import{mkdir as d,readFile as A,writeFile as p,stat as E}from"node:fs/promises";import{createHash as N}from"node:crypto";import{join as a}from"node:path";var $=3e4,V=4e3;function v(n){return n.replace(/[^a-zA-Z0-9._-]/g,"_").slice(0,200)||"default"}function m(n){let e=N("sha1").update(n).digest("hex").slice(0,10);return`${v(n)}-${e}`}function L(n,e,t){return t-n.fetchedAt<e}function I(n){return a(s(),"bundles",`${m(n)}.json`)}function y(n){return a(s(),"sessions",`${m(n)}.registered`)}async function H(n){try{let e=await A(I(n),"utf-8"),t=JSON.parse(e);return t&&typeof t.fetchedAt=="number"&&"body"in t?t:null}catch{return null}}async function G(n,e,t){try{let r=I(n);await d(a(s(),"bundles"),{recursive:!0});let i={fetchedAt:Date.now(),version:t,body:e};await p(r,JSON.stringify(i),{mode:384})}catch{}}async function J(n,e,t){try{let i=`${n.replace(/\/+$/,"")}/rules/bundle?agent_id=${encodeURIComponent(t)}`,o=await fetch(i,{method:"GET",headers:{Authorization:`Bearer ${e}`,Accept:"application/json"},signal:AbortSignal.timeout(V)});if(!o.ok)return null;let b=o.headers.get("etag");return{body:await o.json(),version:b}}catch{return null}}async function M(n){try{return await E(y(n)),!0}catch{return!1}}async function z(n){try{await d(a(s(),"sessions"),{recursive:!0}),await p(y(n),String(Date.now()),{mode:384})}catch{}}export{s as a,C as b,x as c,U as d,F as e,$ as f,v as g,L as h,H as i,G as j,J as k,M as l,z as m};
@@ -0,0 +1 @@
1
+ var p="visiq-claude-code-hook",g=30,h="WebFetch|WebSearch|Read|Grep|Glob|LSP|NotebookRead|ListMcpResourcesTool|ReadMcpResourceTool|mcp__.*",m=[{event:"SessionStart",matcher:""},{event:"PreToolUse",matcher:"*"},{event:"PostToolUse",matcher:h}];function a(t,e){return!!t.hooks?.some(o=>typeof o.command=="string"&&(o.command.includes("visiq-claude-code")||e!==void 0&&o.command===e))}function k(t,e){return{matcher:t,hooks:[{type:"command",command:e,timeout:30}]}}function l(t,e=p){let o={...t},n={...o.hooks??{}};for(let{event:r,matcher:s}of m){let c=(Array.isArray(n[r])?n[r]:[]).filter(u=>!a(u,e));n[r]=[...c,k(s,e)]}return o.hooks=n,o}function d(t,e){let o={...t};if(!o.hooks)return o;let n={};for(let[r,s]of Object.entries(o.hooks)){let i=(Array.isArray(s)?s:[]).filter(c=>!a(c,e));i.length>0&&(n[r]=i)}return o.hooks=n,o}function f(t){let{hooks:e}=t;return e?Object.values(e).some(o=>(Array.isArray(o)?o:[]).some(n=>a(n))):!1}export{p as a,g as b,h as c,a as d,l as e,d as f,f as g};
@@ -0,0 +1,4 @@
1
+ import{f as se,h as le,i as ce,j as ue,k as de,l as pe,m as P}from"./chunk-3GWLCI47.js";var st=new Set(["WebFetch","WebSearch","Read","Grep","Glob","LSP","NotebookRead","ListMcpResourcesTool","ReadMcpResourceTool"]),lt=/(^|_)(search|fetch|read|list|get|query|retrieve|lookup|find|describe|download|export|dump)(_|$)/i;function ct(e){if(typeof e!="string"||!e.startsWith("mcp__"))return typeof e=="string"?e:"";let t=e.lastIndexOf("__");return t>=0?e.slice(t+2):e}function z(e){return typeof e!="string"?!1:st.has(e)?!0:e.startsWith("mcp__")?lt.test(ct(e)):!1}function fe(e){return z(e)?["retrieval","action"]:["action"]}var ut=["query","q","url","prompt","pattern","file_path","path"];function L(e){if(!e||typeof e!="object")return;let t=e,n=[];for(let r of ut){let i=t[r];typeof i=="string"&&i.length>0&&n.push(i)}return n.length>0?n.join(" "):void 0}function I(e,t){let n=t?`VisIQ ${t}`:"VisIQ",r=e&&e.trim().length>0?e.trim():"blocked by policy";return`[${n}] ${r}`}function me(e){let t={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"allow"}};return e&&(t.hookSpecificOutput.updatedInput=e),t}function F(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:e}}}function ge(e){return{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:e}}}function S(e,t){let n={hookSpecificOutput:{hookEventName:"PostToolUse",updatedToolOutput:e}};return t&&(n.hookSpecificOutput.additionalContext=t),n}function Fn(e){try{let t=JSON.parse(e);return t&&typeof t=="object"&&typeof t.hook_event_name=="string"?t:null}catch{return null}}var dt=[{name:"private_key",re:/-----BEGIN (?:[A-Z]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z]+ )?PRIVATE KEY-----/g},{name:"jwt",re:/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g},{name:"aws_access_key",re:/\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA|AIPA)[0-9A-Z]{16}\b/g},{name:"github_token",re:/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}\b|\bgithub_pat_[A-Za-z0-9_]{22,}\b/g},{name:"slack_token",re:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g},{name:"stripe_secret",re:/\b(?:sk|rk)_live_[A-Za-z0-9]{16,}\b/g},{name:"google_api_key",re:/\bAIza[0-9A-Za-z_-]{35}\b/g},{name:"llm_api_key",re:/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}\b/g},{name:"bearer_token",re:/\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/g},{name:"connection_string",re:/\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqps?):\/\/[^\s/@:]+:[^\s/@:]+@[^\s'"]+/g},{name:"us_ein",re:/\b\d{2}-\d{7}\b/g},{name:"ssn",re:/\b\d{3}[- ]\d{2}[- ]\d{4}\b/g},{name:"email",re:/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g},{name:"swift_bic",re:/\b[A-Z]{6}[A-Z0-9]{2}(?:[A-Z0-9]{3})?\b/g},{name:"us_phone",re:/\b(?:\+?1[ .-]?)?\(?\d{3}\)?[ .-]?\d{3}[ .-]?\d{4}\b/g},{name:"ipv4",re:/\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/g},{name:"mac_address",re:/\b(?:[0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}\b/g}];var h=Object.fromEntries(dt.map(e=>[e.name,e.re.source])),pt=["private_key","jwt","aws_access_key","github_token","slack_token","stripe_secret","google_api_key","llm_api_key","bearer_token","connection_string","pan","iban","aba_routing","us_ein","swift_bic","ssn","email","us_phone","ipv4","mac_address"],ft={private_key:{id:"private_key",label:"Private keys (PEM)",description:"PEM-encoded private keys (RSA / EC / EdDSA / OpenSSH).",category:"secret",example:`-----BEGIN PRIVATE KEY-----
2
+ MIIEv\u2026
3
+ -----END PRIVATE KEY-----`,template:{pattern:h.private_key,mode:"full"},defaultEnabled:!0},jwt:{id:"jwt",label:"JWT / bearer tokens",description:"JSON Web Tokens (base64url header.payload.signature).",category:"secret",example:"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.dBjftJeZ4CVP",template:{pattern:h.jwt,mode:"full"},defaultEnabled:!0},aws_access_key:{id:"aws_access_key",label:"AWS access key IDs",description:"AWS access / identity key ids (AKIA / ASIA / AROA \u2026).",category:"secret",example:"AKIAIOSFODNN7EXAMPLE",template:{pattern:h.aws_access_key,mode:"full"},defaultEnabled:!0},github_token:{id:"github_token",label:"GitHub tokens",description:"GitHub personal / OAuth tokens (classic ghp_\u2026 + fine-grained).",category:"secret",example:"ghp_0123456789abcdef0123456789abcdef0123",template:{pattern:h.github_token,mode:"full"},defaultEnabled:!0},slack_token:{id:"slack_token",label:"Slack tokens",description:"Slack bot / user / app tokens (xoxb- / xoxp- \u2026).",category:"secret",example:"xoxb-1234567890-abcdefghijkl",template:{pattern:h.slack_token,mode:"full"},defaultEnabled:!0},stripe_secret:{id:"stripe_secret",label:"Stripe secret keys",description:"Stripe live secret / restricted keys (sk_live_ / rk_live_).",category:"secret",example:"sk_live_0123456789abcdefABCDEF",template:{pattern:h.stripe_secret,mode:"full"},defaultEnabled:!0},google_api_key:{id:"google_api_key",label:"Google API keys",description:"Google API keys (AIza\u2026).",category:"secret",example:"AIzaSyA1234567890abcdefghijklmnopqrstuv",template:{pattern:h.google_api_key,mode:"full"},defaultEnabled:!0},llm_api_key:{id:"llm_api_key",label:"LLM provider API keys",description:"OpenAI / Anthropic secret keys (sk-\u2026, sk-proj-\u2026, sk-ant-\u2026).",category:"secret",example:"sk-ant-api03-AbCdEf0123456789AbCdEf01",template:{pattern:h.llm_api_key,mode:"full"},defaultEnabled:!0},bearer_token:{id:"bearer_token",label:"Authorization bearer tokens",description:"Credentials following an HTTP `Authorization: Bearer \u2026` header.",category:"secret",example:"Bearer abcdef0123456789abcdef0123456789",template:{pattern:h.bearer_token,mode:"full"},defaultEnabled:!0},connection_string:{id:"connection_string",label:"Connection strings (with password)",description:"Database / broker DSNs carrying an inline password (scheme://user:pass@host).",category:"secret",example:"postgres://app:s3cr3t@db.internal:5432/main",template:{pattern:h.connection_string,mode:"full"},defaultEnabled:!0},pan:{id:"pan",label:"Payment card numbers (PAN)",description:"Luhn-valid 13\u201319 digit primary account numbers (PCI-DSS).",category:"financial",example:"4242 4242 4242 4242",template:{pattern:"\\b\\d{13,19}\\b",mode:"partial",keepLast:4},defaultEnabled:!0},iban:{id:"iban",label:"IBAN bank account numbers",description:"ISO 13616 IBANs (mod-97 validated); keeps the last 4 visible.",category:"financial",example:"DE89 3704 0044 0532 0130 00",template:{pattern:"\\b[A-Z]{2}\\d{2}(?:[ ]?[A-Z0-9]){10,30}\\b",mode:"partial",keepLast:4},defaultEnabled:!0},aba_routing:{id:"aba_routing",label:"ABA routing numbers",description:"US bank routing-transit numbers (9-digit, checksum-validated). DEFAULT-OFF (9-digit false-positive surface).",category:"financial",example:"021000021",template:{pattern:"\\b\\d{9}\\b",mode:"full"},defaultEnabled:!1},us_ein:{id:"us_ein",label:"US Employer ID (EIN)",description:"US tax Employer Identification Numbers (XX-XXXXXXX).",category:"financial",example:"12-3456789",template:{pattern:h.us_ein,mode:"full"},defaultEnabled:!0},swift_bic:{id:"swift_bic",label:"SWIFT / BIC codes",description:"Bank SWIFT/BIC codes (8 or 11 chars). DEFAULT-OFF (matches uppercase tokens).",category:"financial",example:"DEUTDEFF500",template:{pattern:h.swift_bic,mode:"full"},defaultEnabled:!1},ssn:{id:"ssn",label:"US Social Security numbers",description:"Dashed / spaced US SSNs (e.g. 123-45-6789).",category:"pii",example:"123-45-6789",template:{pattern:h.ssn,mode:"partial",keepLast:4},defaultEnabled:!0},email:{id:"email",label:"Email addresses",description:"Email addresses (the mailbox is masked, the domain kept).",category:"pii",example:"alice@acme.com",template:{pattern:h.email,mode:"email"},defaultEnabled:!0},us_phone:{id:"us_phone",label:"US phone numbers",description:"US phone numbers in common formats. DEFAULT-OFF (10-digit false-positive surface).",category:"pii",example:"(415) 555-0132",template:{pattern:h.us_phone,mode:"partial",keepLast:4},defaultEnabled:!1},ipv4:{id:"ipv4",label:"IPv4 addresses",description:"Dotted-quad IPv4 addresses. DEFAULT-OFF (common + usually benign).",category:"identifier",example:"203.0.113.42",template:{pattern:h.ipv4,mode:"full"},defaultEnabled:!1},mac_address:{id:"mac_address",label:"MAC addresses",description:"Hardware MAC addresses. DEFAULT-OFF.",category:"identifier",example:"3c:22:fb:1a:2b:3c",template:{pattern:h.mac_address,mode:"full"},defaultEnabled:!1}},Mn=new Set(pt.filter(e=>!ft[e].defaultEnabled));var G="[REDACTED]",E="\u2022",D=e=>/[A-Za-z0-9]/.test(e);function _e(e,t,n,r){let i=Array.from(e),o=i.length,a=Math.max(0,Math.floor(t)||0),s=Math.max(0,Math.floor(n)||0),c=r.length>0?r:E,p=a+s>=o;return i.map((d,_)=>!p&&(_<a||_>=o-s)?d:D(d)?c:d).join("")}function mt(e,t){let n=e.indexOf("@");if(n<=0||n>=e.length-1)return _e(e,0,0,t);let r=e.slice(0,n),i=e.slice(n),o=t.length>0?t:E;return(r.length<=1?o:r[0]+Array.from(r.slice(1)).map(a=>D(a)?o:a).join(""))+i}function gt(e,t,n){let r=n.length>0?n:E,i=new Array(e.length).fill(!1);if(typeof t=="string"&&t.length>0)try{let p=new RegExp(t,"g"),d;for(;(d=p.exec(e))!==null;){if(d[0].length===0){p.lastIndex++;continue}for(let _=d.index;_<d.index+d[0].length;_++)i[_]=!0}}catch{}let o=!1,a=!0;for(let p=0;p<e.length;p++)if(D(e[p])&&(o=!0,!i[p])){a=!1;break}let s=o&&a,c="";for(let p=0;p<e.length;p++){let d=e[p];!s&&i[p]||!D(d)?c+=d:c+=r}return c}function he(e,t){let n=t.mode??"full";return n==="partial"?_e(e,t.keepFirst??0,t.keepLast??0,t.maskChar??E):n==="email"?mt(e,t.maskChar??E):n==="custom"?gt(e,t.keepPattern,t.maskChar??E):t.replacement??G}function _t(e,t){return typeof e=="string"?he(e,t):t.replacement??G}function ye(e){let t=[],n=e.replacement??G;for(let r of e.patterns??[])typeof r!="string"||r.length===0||t.push({source:r,replacer:()=>n});for(let r of e.directives??[]){let{pattern:i}=r;typeof i!="string"||i.length===0||t.push({source:i,replacer:o=>he(o,r)})}return t}function be(e,t){let n=e;for(let{source:r,replacer:i}of t){let o;try{o=new RegExp(r,"g")}catch(a){let s=a instanceof Error?a.message:String(a);throw new Error(`[visiq-redact] Invalid redaction pattern "${r}": ${s}`)}n=n.replace(o,a=>i(a))}return n}function V(e,t){return e==null?e:typeof e=="string"?ht(e,t):typeof e=="object"?yt(e,t):e}function ht(e,t){if(typeof e!="string")return e;let n=ye(t);return n.length===0?e:be(e,n)}function yt(e,t){let n=new Map;for(let s of t.fields??[])typeof s=="string"&&s.length>0&&n.set(s,{field:s,mode:"full",replacement:t.replacement});for(let s of t.directives??[])typeof s.field=="string"&&s.field.length>0&&n.set(s.field,s);let r=ye(t),i=n.size>0,o=r.length>0,a=s=>{if(s==null)return s;if(Array.isArray(s))return s.map(c=>a(c));if(typeof s=="object"){let c=s,p=Object.create(Object.getPrototypeOf(c));for(let d of Object.keys(c)){let _=i?n.get(d):void 0;if(_){p[d]=_t(c[d],_);continue}p[d]=a(c[d])}return p}return typeof s=="string"&&o?be(s,r):s};return a(e)}import{z as u}from"zod";import*as v from"node:os";var ve="interceptor_source",bt=u.enum(["enforce","audit","off"]),vt=u.enum(["closed","open"]),Ae=u.enum(["enforce","monitor","off"]),At=u.object({apiKey:u.string().min(1,"apiKey is required"),agentId:u.string().min(1,"agentId is required"),endpoint:u.string().url().optional(),mode:bt.optional(),timeoutMs:u.number().int().positive().optional(),hitlTimeoutMs:u.number().int().positive().optional(),failBehavior:vt.optional()}),qn=u.object({agent_id:u.string().min(1),target_app:u.string().min(1),action:u.string().min(1),context:u.record(u.unknown()).optional(),session_id:u.string().min(1).max(255).optional(),telemetry:u.record(u.unknown()).optional()}),wt=u.object({field:u.string().optional(),pattern:u.string().optional(),replacement:u.string().optional(),mode:u.enum(["full","partial","email"]).optional(),keepFirst:u.number().optional(),keepLast:u.number().optional(),maskChar:u.string().optional()}),kt=u.object({decision_id:u.string().min(1),decision:u.enum(["permit","deny","approval_required","mask"]),reason:u.string().optional(),rule_code:u.string().nullable().optional(),enforced:u.boolean().optional(),agent_mode:Ae.optional(),arg_redaction_rules:u.array(wt).optional(),plane:u.string().nullable().optional(),operation:u.string().nullable().optional(),is_retrieval:u.boolean().optional(),metadata:u.record(u.unknown()).optional()}),It=u.object({field:u.string().min(1),operator:u.enum(["equals","in","not_equals","not_in","glob","gt","lt","gte","lte","contains","not_contains"]),value:u.union([u.string(),u.number(),u.array(u.string())])}),St=u.object({id:u.string().min(1),rule_code:u.string().default(""),name:u.string().min(1),description:u.string().nullable().default(null),effect:u.enum(["allow","deny","hitl","mask"]),resource_type:u.string().min(1),rego_source:u.string().default(""),target_app:u.string().nullable().default(null),action_pattern:u.string().nullable().default(null),conditions:u.array(It).default([]),priority:u.number().int().default(0)}),Et=u.object({version:u.string().min(1),agent_mode:Ae.optional(),rules:u.array(St),no_coverage:u.object({no_coverage_defaults:u.record(u.string()),autopilot_enabled:u.boolean(),enduser_hitl_enabled:u.boolean(),hitl_timeout_seconds:u.number()}).optional()}),Ct=2e3,we=class{config;originalFetch;constructor(e,t){this.config=e,this.originalFetch=t}async evaluate(e){let t=new AbortController,n=setTimeout(()=>t.abort(),this.config.timeoutMs),r;try{r=await this.originalFetch(`${this.config.endpoint}/allow/evaluate`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify(e),signal:t.signal})}finally{clearTimeout(n)}if(!r.ok)throw new Error(`ALLOW evaluate request failed: HTTP ${r.status} ${r.statusText}`);let i=await r.json();return kt.parse(i)}async fetchBundle(){let e=this.config.agentId?`?agent_id=${encodeURIComponent(this.config.agentId)}`:"",t=`${this.config.endpoint}/allow/rules/bundle${e}`,n=new AbortController,r=setTimeout(()=>n.abort(),this.config.timeoutMs),i;try{i=await this.originalFetch(t,{method:"GET",headers:{Authorization:`Bearer ${this.config.apiKey}`,Accept:"application/json"},signal:n.signal})}finally{clearTimeout(r)}if(!i.ok)throw new Error(`ALLOW rules/bundle fetch failed: HTTP ${i.status} ${i.statusText}`);let o=await i.json();return Et.parse(o)}async sendTelemetry(e){if(e.length===0)return;let t=`${this.config.endpoint}/allow/telemetry`,n=await this.originalFetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({decisions:e})});if(!n.ok)throw new Error(`ALLOW telemetry delivery failed: HTTP ${n.status} ${n.statusText}`)}async reportExecutionResult(e,t,n){let r=`${this.config.endpoint}/allow/execution-events`;try{let i=await this.originalFetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({decision_id:e,result:t,details:n})});i.ok||console.warn(`[ALLOW] Execution result report failed: HTTP ${i.status}`)}catch(i){console.warn("[ALLOW] Execution result report failed:",i)}}async registerEnvironment(e){if(!this.config.agentId)return;let t=`${this.config.endpoint}/allow/agents/register`;try{let n=await this.originalFetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({agent_id:this.config.agentId,os:e.os,hostname:e.hostname,ip:e.ip,username:e.username,...this.config.harnessKind?{kind:this.config.harnessKind}:{}})});n.ok||console.warn(`[ALLOW] Agent registration failed: HTTP ${n.status} ${n.statusText}`)}catch(n){console.warn("[ALLOW] Agent registration failed:",n)}}async waitForApproval(e){if(!e)throw new Error("waitForApproval: decisionId must be a non-empty string");let t=Date.now()+this.config.hitlTimeoutMs;for(;Date.now()<t;){let n=new AbortController,r=setTimeout(()=>n.abort(),this.config.timeoutMs),i;try{i=await this.originalFetch(`${this.config.endpoint}/v1/allow/decisions/${encodeURIComponent(e)}`,{method:"GET",headers:{Authorization:`Bearer ${this.config.apiKey}`},signal:n.signal})}finally{clearTimeout(r)}if(!i.ok)throw new Error(`ALLOW decisions poll failed: HTTP ${i.status} ${i.statusText}`);let o=await i.json();if(typeof o!="object"||o===null)throw new Error("ALLOW decisions poll returned unexpected non-object response");let a=o,s=a.hitl_result;if(s==="approved")return{decision_id:e,decision:"permit",reason:"Approved by human reviewer"};if(s==="rejected")return{decision_id:e,decision:"deny",reason:typeof a.reason=="string"?a.reason:"Rejected by human reviewer"};if(s==="timeout"||s==="expired")return{decision_id:e,decision:"deny",reason:"HITL approval timed out"};await Tt(Ct)}return{decision_id:e,decision:"deny",reason:"HITL approval timed out"}}};function Tt(e){return new Promise(t=>setTimeout(t,e))}function ke(){let e={};try{let t=`${v.platform()} ${v.release()}`.trim();t&&(e.os=t)}catch{}try{let t=v.hostname();t&&(e.hostname=t)}catch{}try{let{username:t}=v.userInfo();t&&(e.username=t)}catch{}try{let t=xt();t&&(e.ip=t)}catch{}return e}function xt(){let e=v.networkInterfaces();for(let t of Object.keys(e))for(let n of e[t]??[])if((n.family==="IPv4"||n.family===4)&&!n.internal&&n.address)return n.address}import{z as f}from"zod";import{createHash as Ot}from"crypto";import{createHash as Vn}from"crypto";var Rt=class extends Error{constructor(e,t){super(`eval_conflict_error: complete rules must not produce multiple outputs for "${e}" (got ${t.map(n=>JSON.stringify(n)).join(", ")})`),this.variable=e,this.values=t,this.name="RegoConflictError"}variable;values},Ie=new Map;function Se(e){return e.map(t=>t.trim()).filter(t=>t.length>0&&t!=="true")}function Ue(e){let t=Ot("sha256").update(e).digest("hex"),n=Ie.get(t);if(n)return n;let r=new Map,i=[],o=e.split(`
4
+ `).map(c=>Nt(c)).filter(c=>c.trim().length>0),a=0;for(;a<o.length;){let c=o[a].trim();if(c.startsWith("package ")||c.startsWith("import ")){a++;continue}let p=c.match(/^default\s+(\w+)\s*:?=\s*(.+)$/);if(p){let l=p[1],g=p[2].trim();r.set(l,R(g)),a++;continue}let d=c.match(/^(\w+)\s*:?=\s*(.+?)(?:\s+if)?\s*\{(.*)$/);if(d){let l=d[1],g=d[2].trim(),m=d[3].trim(),y=[];if(m&&m!=="}"){let N=m.endsWith("}")?m.slice(0,-1).trim():m;N&&y.push(N),m.endsWith("}")||(a++,a=M(o,a,y))}else m==="}"||(a++,a=M(o,a,y));let k=Ce(y);i.push({variable:l,value:R(g),conditions:k,rawConditions:Se(y)}),a++;continue}let _=c.match(/^(\w+)(?:\s+if)?\s*\{(.*)$/);if(_){let l=_[1],g=_[2].trim(),m=[];if(g&&g!=="}"){let k=g.endsWith("}")?g.slice(0,-1).trim():g;k&&m.push(k),g.endsWith("}")||(a++,a=M(o,a,m))}else g==="}"||(a++,a=M(o,a,m));let y=Ce(m);i.push({variable:l,value:"true",conditions:y,rawConditions:Se(m)}),a++;continue}a++}let s={defaults:r,rules:i};return Ie.set(t,s),s}function qe(e,t){let n={};for(let[o,a]of e.defaults)n[o]=a;let r=-1,i={};for(let o=0;o<e.rules.length;o++){let a=e.rules[o];if(a.conditions.every(s=>Mt(s,t))){if(i[a.variable]===void 0)i[a.variable]=a.value,n[a.variable]=a.value;else if(i[a.variable]!==a.value)throw new Rt(a.variable,[i[a.variable],a.value]);r===-1&&(r=o)}}return r!==-1?{bindings:n,matched:!0,matchedRuleIndex:r}:{bindings:n,matched:!1,matchedRuleIndex:-1}}function Nt(e){let t=!1,n="";for(let r=0;r<e.length;r++){let i=e[r];if(t)i===n&&e[r-1]!=="\\"&&(t=!1);else if(i==='"'||i==="'")t=!0,n=i;else if(i==="#")return e.slice(0,r)}return e}function M(e,t,n){let r=1,i=t;for(;i<e.length&&r>0;){let o=e[i].trim();for(let a of o)a==="{"&&r++,a==="}"&&r--;if(r>0)n.push(o),i++;else{let a=o.slice(0,o.lastIndexOf("}")).trim();return a&&n.push(a),i}}return i}function R(e){return e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'")?e.slice(1,-1):e}function Pt(e){return e.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\")}var Ee=new Map;function Lt(e){let t=Ee.get(e);if(t!==void 0)return t;let n;try{n=new RegExp(e)}catch{n=null}return Ee.set(e,n),n}function Ce(e){let t=[];for(let n of e){let r=n.split(";").map(i=>i.trim()).filter(i=>i.length>0);for(let i of r){let o=Ft(i);o&&t.push(o)}}return t}function Ft(e){let t=!1,n=e.trim();if(n.startsWith("not ")&&(t=!0,n=n.slice(4).trim()),n==="true"||n==="false")return{type:"literal_bool",negated:t,field:"",value:n==="true"};if(n.includes("[_]"))return{type:"truthy",negated:!1,field:Z,value:e.trim()};let r=n.match(/^count\(([^)]+)\)\s*(>=|<=|>|<)\s*(\d+)$/);if(r)return{type:"count_gt",negated:t,field:r[1].trim(),operator:r[2],value:parseInt(r[3],10)};let i=n.match(/^startswith\(([^,]+),\s*"([^"]*)"\)$/);if(i)return{type:"startswith",negated:t,field:i[1].trim(),value:i[2]};let o=n.match(/^endswith\(([^,]+),\s*"([^"]*)"\)$/);if(o)return{type:"endswith",negated:t,field:o[1].trim(),value:o[2]};let a=n.match(/^contains\(([^,]+),\s*"([^"]*)"\)$/);if(a)return{type:"contains",negated:t,field:a[1].trim(),value:a[2]};let s=n.match(/^regex\.match\(\s*("(?:[^"\\]|\\.)*")\s*,\s*((?:input|data)[^\s)]*)\s*\)$/);if(s)return{type:"regex_match",negated:t,field:s[2].trim(),value:Pt(s[1])};let c=n.match(/^("(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?|true|false)\s+in\s+((?:input|data)[^\s]*)$/);if(c)return{type:"array_includes",negated:t,field:c[2].trim(),value:$e(c[1])};let p=n.match(/^([^\s]+)\s+in\s+(\[.*\])$/);if(p){let y=Dt(p[2]);if(y!==null)return{type:"in_set",negated:t,field:p[1].trim(),values:y}}let d=n.match(/^([^\s=!<>]+)\s*(>=|<=|>|<)\s*(-?\d+(?:\.\d+)?)$/);if(d)return{type:"comparison",negated:t,field:d[1].trim(),operator:d[2],value:parseFloat(d[3])};let _=n.match(/^("(?:[^"\\]|\\.)*"|-?\d+(?:\.\d+)?|true|false)\s*==\s*((?:input|data)\S*)$/);if(_)return{type:"equality",negated:t,field:_[2].trim(),value:R(_[1].trim())};let l=n.match(/^([^\s=!]+)\s*==\s*(.+)$/);if(l)return{type:"equality",negated:t,field:l[1].trim(),value:R(l[2].trim())};let g=n.match(/^([^\s=!]+)\s*!=\s*(.+)$/);if(g)return{type:"inequality",negated:t,field:g[1].trim(),value:R(g[2].trim())};let m=n.match(/^(input(?:\.[a-zA-Z0-9_]+|\["[^"]+"\]|\['[^']+'\])+)$/);return m?{type:"truthy",negated:t,field:m[1].trim()}:{type:"truthy",negated:!1,field:Z,value:e.trim()}}var Z="__unrecognized_condition_forces_deny__";function $e(e){let t=e.trim();return t==="true"?!0:t==="false"?!1:/^-?\d+(?:\.\d+)?$/.test(t)?parseFloat(t):t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'")?t.slice(1,-1):t}function Dt(e){let t=e.trim().replace(/^\[/,"").replace(/\]$/,"").trim();if(t.length===0)return[];let n=[],r="",i=!1,o="";for(let s=0;s<t.length;s++){let c=t[s];i?(r+=c,c===o&&t[s-1]!=="\\"&&(i=!1)):c==='"'||c==="'"?(r+=c,i=!0,o=c):c===","?(n.push(r.trim()),r=""):r+=c}if(i)return null;r.trim().length>0&&n.push(r.trim());let a=[];for(let s of n){let c=s.startsWith('"')&&s.endsWith('"')&&s.length>=2||s.startsWith("'")&&s.endsWith("'")&&s.length>=2,p=/^-?\d+(?:\.\d+)?$/.test(s);if(!c&&!p&&!(s==="true"||s==="false"))return null;a.push($e(s))}return a}function Mt(e,t){if(e.field===Z)return!1;let n;switch(e.type){case"equality":{let r=b(e.field,t);n=j(r,e.value);break}case"inequality":{let r=b(e.field,t);r==null?n=!1:n=!j(r,e.value);break}case"startswith":{let r=b(e.field,t);typeof r!="string"?n=!1:n=r.startsWith(String(e.value));break}case"endswith":{let r=b(e.field,t);typeof r!="string"?n=!1:n=r.endsWith(String(e.value));break}case"contains":{let r=b(e.field,t);typeof r!="string"?n=!1:n=r.includes(String(e.value));break}case"regex_match":{let r=b(e.field,t);if(typeof r!="string")n=!1;else{let i=Lt(String(e.value??""));n=i?i.test(r):!1}break}case"truthy":{let r=b(e.field,t);n=$t(r);break}case"count_gt":{let r=b(e.field,t),i=typeof e.value=="number"?e.value:0,o=Ut(r);o===null?n=!1:n=Te(o,e.operator??">",i);break}case"comparison":{let r=b(e.field,t),i=typeof e.value=="number"?e.value:NaN,o=qt(r);Number.isNaN(o)||Number.isNaN(i)||!e.operator?n=!1:n=Te(o,e.operator,i);break}case"in_set":{let r=b(e.field,t),i=e.values??[];Y(r)?n=i.some(o=>j(r,o)):n=!1;break}case"array_includes":{let r=b(e.field,t);Array.isArray(r)?n=r.some(i=>j(i,e.value)):n=!1;break}case"literal_bool":{n=e.value===!0;break}default:n=!1}return e.negated?!n:n}function b(e,t){let n=e;n.startsWith("input.")?n=n.slice(6):n.startsWith("input[")&&(n=n.slice(5));let r=jt(n);if(r===null)return;let i=t;for(let o of r){if(i==null||typeof i!="object")return;i=i[o]}return i}function jt(e){let t=[],n=0,r="";for(;n<e.length;){let i=e[n];if(i===".")r.length>0&&(t.push(r),r=""),n++;else if(i==="["){r.length>0&&(t.push(r),r="");let o=e[n+1];if(o!=='"'&&o!=="'")return null;let a=e.indexOf(`${o}]`,n+2);if(a===-1)return null;t.push(e.slice(n+2,a)),n=a+2}else r+=i,n++}return r.length>0&&t.push(r),t}function Ut(e){return Array.isArray(e)||typeof e=="string"?e.length:e&&typeof e=="object"?Object.keys(e).length:null}function qt(e){return typeof e=="number"?e:typeof e=="string"&&e.trim()!==""?Number(e):NaN}function Te(e,t,n){switch(t){case">":return e>n;case">=":return e>=n;case"<":return e<n;case"<=":return e<=n;default:return!1}}function Y(e){return typeof e=="string"||typeof e=="number"||typeof e=="boolean"}function j(e,t){return!Y(e)||!Y(t)?!1:String(e)===String(t)}function $t(e){return!(e==null||e===!1||e===0||e==="")}function Bt(e){return e.trim()}function Wt(e){return e.map(Bt).filter(t=>t.length>0&&t!=="true")}function zt(e,t=0){let n=3735928559^t,r=1103547991^t;for(let i=0;i<e.length;i++){let o=e.charCodeAt(i);n=Math.imul(n^o,2654435761),r=Math.imul(r^o,1597334677)}return n=Math.imul(n^n>>>16,2246822507),n^=Math.imul(r^r>>>13,3266489909),r=Math.imul(r^r>>>16,2246822507),r^=Math.imul(n^n>>>13,3266489909),4294967296*(2097151&r)+(n>>>0)}var Gt="\0";function Be(e,t){let n=[e.trim(),...Wt(t)].join(Gt);return`b_${zt(n).toString(36)}`}var Vt=["tier1","tier2","tier3"];function We(e){return typeof e=="string"&&Vt.includes(e)}var Ht=["read_only","transactional","data_processor","external_comms","code_exec","privileged"],Kt=["internal","customer_facing","experimental","third_party"],ze=[...Ht,...Kt];function Zt(e){return typeof e=="string"&&ze.includes(e)}function Ge(e){let t=new Set;for(let n of e)Zt(n)&&t.add(n);return ze.filter(n=>t.has(n))}var $={trust_tier:null,categories:[],business_function:null},Yt=["read_only","record_create","record_update","record_delete","data_export","data_share","data_subject_request","consent_change","funds_transfer","funds_disbursement","payroll_run","trade_execution","ledger_mutation","credit_decision","decision_adverse","account_restriction","account_closure","card_management","purchase_commitment","vendor_management","deal_management","pricing_change","contract_execution","legal_hold","regulatory_filing","personnel_action","employment_termination","compensation_change","campaign_send","content_publish","inventory_adjustment","order_fulfillment","account_servicing","credential_reset","access_grant","permission_change","credential_management","auth_policy_change","security_config_change","logging_change","alert_management","code_change","infrastructure_provision","deployment_change","communication_send","other"];function Ve(e){if(typeof e!="string"&&typeof e!="number")return null;let t=String(e).trim().replace(/([a-z0-9])([A-Z])/g,"$1_$2").toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"");return t.length>0?t:null}var Jt={wire_transfer:"funds_transfer",international_transfer:"funds_transfer",cross_border_payment:"funds_transfer",send_swift:"funds_transfer",send_wire:"funds_transfer",transfer_funds:"funds_transfer",update_wire_instructions:"funds_transfer",add_beneficiary:"funds_transfer",modify_payee:"funds_transfer",disburse_funds:"funds_disbursement",initiate_ach:"funds_disbursement",issue_refund:"funds_disbursement",execute_trade:"trade_execution",place_order:"trade_execution",post_journal_entry:"ledger_mutation",adjust_ledger:"ledger_mutation",modify_general_ledger:"ledger_mutation",raise_limit:"credit_decision",increase_credit_limit:"credit_decision",extend_credit:"credit_decision",decline_credit:"decision_adverse",deny_application:"decision_adverse",adverse_action:"decision_adverse",block_card:"card_management",cancel_card:"card_management",place_hold:"account_restriction",freeze_account:"account_restriction",restrict_account:"account_restriction",close_account:"account_closure",export_data:"data_export",export_customer_list:"data_export",download_report:"data_export",download_customer_list:"data_export",bulk_export:"data_export",share_data:"data_share",send_to_partner:"data_share",delete_records:"record_delete",delete_user:"record_delete",drop_table:"record_delete",grant_role:"access_grant",change_permissions:"permission_change",modify_access:"permission_change",rotate_keys:"credential_management",disable_mfa:"auth_policy_change",modify_security_config:"security_config_change",disable_logging:"logging_change",purge_logs:"logging_change",suppress_alert:"alert_management",dismiss_alert:"alert_management",rollback:"deployment_change",modify_billing:"record_update",close_sar:"record_update",send_email:"communication_send",send_sms:"communication_send",fulfill_dsar:"data_subject_request",process_dsar:"data_subject_request",fulfill_data_request:"data_subject_request",process_erasure_request:"data_subject_request",honor_deletion_request:"data_subject_request",export_subject_data:"data_subject_request",erase_subject_data:"data_subject_request",delete_subject_data:"data_subject_request",record_consent:"consent_change",capture_consent:"consent_change",update_consent:"consent_change",withdraw_consent:"consent_change",revoke_consent:"consent_change",opt_out:"consent_change",opt_in:"consent_change",run_payroll:"payroll_run",process_payroll:"payroll_run",submit_payroll:"payroll_run",approve_payroll:"payroll_run",create_purchase_order:"purchase_commitment",issue_purchase_order:"purchase_commitment",approve_purchase_order:"purchase_commitment",submit_purchase_order:"purchase_commitment",approve_purchase:"purchase_commitment",approve_requisition:"purchase_commitment",commit_spend:"purchase_commitment",onboard_vendor:"vendor_management",add_vendor:"vendor_management",create_supplier:"vendor_management",approve_vendor:"vendor_management",update_vendor_bank:"vendor_management",update_supplier_bank:"vendor_management",modify_supplier:"vendor_management",create_opportunity:"deal_management",update_opportunity:"deal_management",advance_deal_stage:"deal_management",update_deal_stage:"deal_management",close_deal:"deal_management",mark_closed_won:"deal_management",mark_closed_lost:"deal_management",issue_quote:"pricing_change",send_quote:"pricing_change",approve_discount:"pricing_change",apply_discount:"pricing_change",override_price:"pricing_change",update_pricing:"pricing_change",approve_special_pricing:"pricing_change",execute_contract:"contract_execution",sign_contract:"contract_execution",countersign_contract:"contract_execution",sign_agreement:"contract_execution",execute_agreement:"contract_execution",sign_nda:"contract_execution",execute_nda:"contract_execution",sign_document:"contract_execution",esign_document:"contract_execution",place_legal_hold:"legal_hold",place_litigation_hold:"legal_hold",apply_legal_hold:"legal_hold",release_legal_hold:"legal_hold",file_regulatory_report:"regulatory_filing",file_sar:"regulatory_filing",submit_regulatory_filing:"regulatory_filing",file_compliance_report:"regulatory_filing",file_disclosure:"regulatory_filing",onboard_employee:"personnel_action",hire_employee:"personnel_action",change_employee_role:"personnel_action",promote_employee:"personnel_action",transfer_employee:"personnel_action",update_job_title:"personnel_action",terminate_employee:"employment_termination",offboard_employee:"employment_termination",deactivate_employee:"employment_termination",separate_employee:"employment_termination",adjust_salary:"compensation_change",change_compensation:"compensation_change",approve_raise:"compensation_change",grant_equity:"compensation_change",grant_stock:"compensation_change",issue_bonus:"compensation_change",update_pay_rate:"compensation_change",send_campaign:"campaign_send",launch_campaign:"campaign_send",schedule_campaign:"campaign_send",send_bulk_email:"campaign_send",send_marketing_email:"campaign_send",send_newsletter:"campaign_send",blast_email:"campaign_send",publish_post:"content_publish",publish_content:"content_publish",publish_blog:"content_publish",post_to_social:"content_publish",schedule_post:"content_publish",adjust_inventory:"inventory_adjustment",write_off_inventory:"inventory_adjustment",update_stock_level:"inventory_adjustment",reconcile_inventory:"inventory_adjustment",fulfill_order:"order_fulfillment",ship_order:"order_fulfillment",dispatch_order:"order_fulfillment",mark_order_shipped:"order_fulfillment",update_customer_account:"account_servicing",modify_customer_account:"account_servicing",update_customer_contact:"account_servicing",change_customer_settings:"account_servicing",reset_customer_password:"credential_reset",reset_password:"credential_reset",force_password_reset:"credential_reset",reset_mfa:"credential_reset",unlock_account:"credential_reset",reset_customer_credentials:"credential_reset",merge_pull_request:"code_change",merge_pr:"code_change",merge_branch:"code_change",push_to_branch:"code_change",push_to_main:"code_change",push_code:"code_change",commit_code:"code_change",force_push:"code_change",provision_infrastructure:"infrastructure_provision",provision_resource:"infrastructure_provision",create_instance:"infrastructure_provision",terminate_instance:"infrastructure_provision",spin_up_server:"infrastructure_provision",tear_down_server:"infrastructure_provision",destroy_resource:"infrastructure_provision",create_cluster:"infrastructure_provision"};function Qt(e){let t=Ve(e);return t===null?"other":Jt[t]||(/^(delete|remove|purge|drop|destroy|wipe)_/.test(t)?"record_delete":/^(export|download|extract|dump)_/.test(t)?"data_export":/^(create|add|insert)_/.test(t)?"record_create":/^(update|edit|modify|patch|set)_/.test(t)?"record_update":t==="send_email"||t==="send_sms"||/^(notify|email|sms)_/.test(t)?"communication_send":/^(read|get|list|search|fetch|view|query)_/.test(t)||t==="read"||t==="get"?"read_only":"other")}var xe={github:"version_control",gitlab:"version_control",bitbucket:"version_control",stripe:"payments",adyen:"payments",braintree:"payments",paypal:"payments",square:"payments",salesforce:"crm",hubspot:"crm",pipedrive:"crm",slack:"communication",teams:"communication",twilio:"communication",sendgrid:"communication",mailgun:"communication",gmail:"communication",outlook:"communication",zoom:"communication",aws:"cloud_infra",gcp:"cloud_infra",azure:"cloud_infra",okta:"identity",auth0:"identity",entra:"identity",onelogin:"identity",duo:"identity",snowflake:"data_warehouse",bigquery:"data_warehouse",databricks:"data_warehouse",redshift:"data_warehouse",jira:"ticketing",zendesk:"ticketing",servicenow:"ticketing",freshdesk:"ticketing",linear:"ticketing",workday:"hr",bamboohr:"hr",gusto:"hr",netsuite:"finance_erp",quickbooks:"finance_erp",sap:"finance_erp",xero:"finance_erp",s3:"storage",gcs:"storage",dropbox:"storage",box:"storage",postgres:"database",postgresql:"database",mysql:"database",mongodb:"database",redis:"database"};function Xt(e){let t=Ve(e);return t===null?"other":xe[t]??xe[t.replace(/_/g,"")]??"other"}function X(e,t){let n={...e};if(t.action!==void 0&&t.action!==null&&n.action_class===void 0&&(n.action_class=Qt(t.action)),t.targetApp!==void 0&&t.targetApp!==null){let r=n.context&&typeof n.context=="object"&&!Array.isArray(n.context)?{...n.context}:{};r.app_category===void 0&&(r.app_category=Xt(t.targetApp)),n.context=r}return n}var Oe=64,en=64,w={retrieved_data_categories:[],retrieved_classifications:[],action_classes:[],action_counts:{},denied_count:0,inhibited_count:0,event_count:0,target_apps:[]};function He(){return{retrieved_data_categories:[],retrieved_classifications:[],action_classes:[],action_counts:{},denied_count:0,inhibited_count:0,event_count:0,target_apps:[]}}var tn=new Set(["deny","approval_required","escalate"]);function U(e,t){if(t.length===0)return[...e];let n=new Set(e);for(let i of t)n.add(i);let r=[...n].sort();return r.length>Oe?r.slice(0,Oe):r}function nn(e){return e?e.filter(t=>typeof t=="string"&&t.length>0):[]}function q(e){return typeof e=="string"&&e.length>0?e:null}function Ke(e,t){let n=nn(t.data_categories),r=q(t.classification),i=q(t.action_class),o=i&&Yt.includes(i)?i:null,a=q(t.target_app),s=q(t.decision),c={...e.action_counts};if(o)if(o in c)c[o]=(c[o]??0)+1;else if(Object.keys(c).length<en)c[o]=1;else{let p=Object.keys(c).sort(),d=p[p.length-1];d!==void 0&&o<d&&(delete c[d],c[o]=1)}return{retrieved_data_categories:U(e.retrieved_data_categories,n),retrieved_classifications:U(e.retrieved_classifications,r?[r]:[]),action_classes:U(e.action_classes,o?[o]:[]),action_counts:c,denied_count:e.denied_count+(s==="deny"?1:0),inhibited_count:e.inhibited_count+(s&&tn.has(s)?1:0),event_count:e.event_count+1,target_apps:U(e.target_apps,a?[a]:[])}}function rn(e,t){if(!e||e.trim().length===0)return{decision:"permit",matched:!0};try{let n=Ue(e),r=qe(n,{target_app:t.target_app,action:t.action,...t.context,context:t.context,normalized:t.normalized??{},agent:t.agent??$,session:t.session??w});if(!r.matched)return{decision:"deny",matched:!1};let i=null,o=r.bindings.decision;if(o!==void 0?i=J(o):r.bindings.approval_required==="true"?i="approval_required":r.bindings.mask==="true"?i="mask":r.bindings.allow==="true"&&(i="permit"),i===null)return{decision:"deny",matched:!0};let a=i==="mask"?an(n,r.matchedRuleIndex,i):void 0;return{decision:i,matched:!0,...a?{branchId:a}:{}}}catch{return{decision:"deny",matched:!1}}}function J(e){switch(e){case"permit":case"allow":return"permit";case"deny":return"deny";case"approval_required":return"approval_required";case"mask":return"mask";default:return"deny"}}function an(e,t,n){if(t<0)return;let r=e.rules[t];if(!(!r||(r.variable==="decision"?J(r.value):J(r.variable))!==n))return Be(n,r.rawConditions)}function Re(e,t){if(!e||e.trim().length===0)return{action:"deny",matched:!1};try{let n=Ue(e),r=qe(n,{agent_id:t.agent_id,operation:t.operation,resource_type:t.resource_type,resource_metadata:t.resource_metadata,trust_tier:t.trust_tier??"",surface:t.surface??"",query:t.query??"",normalized:t.normalized??{},agent:t.agent??$,session:t.session??w});if(!r.matched)return{action:"deny",matched:!1};let i=null,o=r.bindings.action??r.bindings.decision;if(o!==void 0?i=Q(o):r.bindings.escalate==="true"?i="escalate":r.bindings.redact==="true"?i="redact":r.bindings.allow==="true"&&(i="allow"),i===null)return{action:"deny",matched:!0};let a=i==="redact"?on(n,r.matchedRuleIndex,i):void 0;return{action:i,matched:!0,...a?{branchId:a}:{}}}catch{return{action:"deny",matched:!1}}}function Q(e){switch(e){case"allow":return"allow";case"deny":return"deny";case"redact":return"redact";case"escalate":return"escalate";default:return"deny"}}function on(e,t,n){if(t<0)return;let r=e.rules[t];if(!(!r||(r.variable==="decision"||r.variable==="action"?Q(r.value):Q(r.variable))!==n))return Be(n,r.rawConditions)}var sn="generic",Ze=[{id:"finance_accounting",name:"Finance & Accounting",icon:"Landmark",description:"Payments, invoicing, ledgers, transactions, refunds, financial reporting, SOX/AML/KYC controls."},{id:"hr",name:"Human Resources",icon:"Users",description:"Employee records, payroll, hiring/recruiting, benefits, personnel actions and PII of staff."},{id:"engineering",name:"Engineering",icon:"Code",description:"Source code, build/deploy pipelines, infrastructure changes, production systems and developer tooling."},{id:"it_security",name:"IT & Security",icon:"ShieldCheck",description:"Credentials, secrets, access control, configuration, threat/exfiltration prevention and security operations."},{id:"legal",name:"Legal",icon:"Scale",description:"Contracts, NDAs, litigation, counsel communications and legal-hold material."},{id:"privacy_compliance",name:"Privacy & Compliance",icon:"BadgeCheck",description:"GDPR/CCPA/HIPAA, consent, data-subject rights, special-category data and regulatory data handling."},{id:"sales",name:"Sales",icon:"TrendingUp",description:"CRM, leads, deals, quotes, pipeline and customer commercial relationships."},{id:"marketing",name:"Marketing",icon:"Megaphone",description:"Campaigns, content, brand, social channels and prospect/audience data."},{id:"operations",name:"Operations",icon:"Cog",description:"Business operations, logistics, internal workflows, vendor/ops tooling and process automation."},{id:"customer_support",name:"Customer Support",icon:"LifeBuoy",description:"Support tickets, customer communications, account servicing and help-desk actions."},{id:"procurement",name:"Procurement",icon:"ShoppingCart",description:"Purchasing, supplier management, purchase orders, sourcing and spend approvals."},{id:sn,name:"Generic",icon:"Shapes",description:"Catch-all for cross-cutting rules or rules that do not fit a specific function. Assign when no specific function clearly applies."}],zn=Ze.map(e=>e.id),ln=new Map(Ze.map(e=>[e.id,e]));function Ye(e){return e!=null&&ln.has(e)}function cn(e,t){if(!e||e==="*")return!0;let n=e.split(",").map(r=>r.trim()).filter(r=>r.length>0);return n.length===0?!1:n.some(r=>un(r,t))}function un(e,t){return e==="*"?!0:e.includes("*")?pn(e).test(t):e===t}function dn(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var Ne=new Map;function pn(e){let t=Ne.get(e);if(t)return t;let n=e.split("*").map(i=>dn(i)).join(".*"),r=new RegExp(`^${n}$`);return Ne.set(e,r),r}function ee(e){return e==="open"||e==="closed"?e:null}function fn(e){let t=e.toLowerCase().replace(/[_\-.:/]+/g," ");return/\b(read|get|list|view|fetch|query|search|describe|show|export|download)\b/.test(t)?"read":/\b(delete|remove|destroy|purge|drop|truncate)\b/.test(t)?"delete":/\b(admin|configure|manage|provision|grant|revoke|assign|unassign)\b/.test(t)?"admin":"write"}function H(e,t){let n=t==="approve"?"ALLOW":t.toUpperCase();return`D-${e.toUpperCase()}-${n}`}function O(e,t,n,r="tenant"){let i=e.charAt(0).toUpperCase()+e.slice(1);return r==="agent"?t==="deny"?`${i} actions on ${n} are denied by this agent's fail-safe override`:`${i} actions on ${n} are permitted by this agent's fail-safe override`:t==="deny"?`${i} actions on ${n} are denied by default`:t==="approve"?`${i} actions on ${n} are permitted by default`:`${i} actions on ${n} require human review`}function Je(e){let t=fn(e.action),n=e.config.no_coverage_defaults??{},r=ee(e.agentNoCoverage),i={autopilot:!1,enduserHitlBypassed:!1};if(e.mode!=="enforce"){let c=n[t]??"deny";return{...i,decision:"permit",rule_code:H(t,c),reason:O(t,c,e.targetApp),hitlCategory:null}}if(e.config.autopilot_enabled&&r!=="closed")return{...i,autopilot:!0,decision:"permit",rule_code:H(t,n[t]??"deny"),reason:"Autopilot: draft rule created for engineer review; request permitted",hitlCategory:"engineer"};let o;r==="open"?o="approve":r==="closed"?o="deny":o=n[t]??"deny";let a=H(t,o),s=r!==null?"agent":"tenant";return o==="approve"?{...i,decision:"permit",rule_code:a,reason:O(t,"approve",e.targetApp,s),hitlCategory:null}:o==="ask"?e.config.enduser_hitl_enabled===!1?{...i,decision:"permit",rule_code:a,reason:`${O(t,"ask",e.targetApp)} (end-user HITL disabled \u2014 bypassed)`,hitlCategory:null,enduserHitlBypassed:!0}:{...i,decision:"approval_required",rule_code:a,reason:O(t,"ask",e.targetApp),hitlCategory:"enduser"}:{...i,decision:"deny",rule_code:a,reason:O(t,"deny",e.targetApp,s),hitlCategory:"engineer"}}var mn=["action","retrieval","delegation"];function gn(e){return typeof e=="string"&&mn.includes(e)}var _n=new Set(["rules"]);function Qe(e){if(typeof e.id!="string"||typeof e.vendor_id!="string")return null;let t=typeof e.origin_table=="string"?e.origin_table:"";if(!_n.has(t))return null;let n=Array.isArray(e.applies_to)?e.applies_to.filter(gn):[];return n.length===0?null:{id:e.id,originTable:t,vendorId:e.vendor_id,name:typeof e.name=="string"?e.name:"",description:typeof e.description=="string"?e.description:null,regoSource:typeof e.rego_source=="string"?e.rego_source:null,priority:typeof e.priority=="number"?e.priority:0,enabled:e.enabled===!0,appliesTo:n,targetApp:typeof e.target_app=="string"?e.target_app:null,actionPattern:typeof e.action_pattern=="string"?e.action_pattern:null,trustTier:typeof e.trust_tier=="string"?e.trust_tier:null,surface:typeof e.surface=="string"?e.surface:null,principalExclusions:Array.isArray(e.principal_exclusions)?e.principal_exclusions.filter(r=>typeof r=="string"):null,bypassActive:e.bypass_active===!0,bypassReason:typeof e.bypass_reason=="string"?e.bypass_reason:null,bypassExpiresAt:typeof e.bypass_expires_at=="string"?e.bypass_expires_at:null,redactionSpec:e.redaction_spec??null,failClosedOnEmptySpec:e.fail_closed_on_empty_spec===!0,hitlFallback:e.hitl_fallback==="mask"?"mask":"deny",ruleCode:typeof e.rule_code=="string"?e.rule_code:null}}var hn={tier1:3,tier2:2,tier3:1};function Pe(e){return e?hn[e]??null:null}function Le(e,t){if(!t)return{matched:!0,reason_code:"POLICY_ALLOW",reason:"resource has no minimum trust tier requirement"};let n=Pe(e),r=Pe(t);return n===null?{matched:!1,reason_code:"TIER_MISMATCH",reason:`agent trust tier '${e??"(none)"}' is not a recognized tier`}:r===null?{matched:!1,reason_code:"TIER_MISMATCH",reason:`resource minimum trust tier '${t}' is not a recognized tier`}:n<r?{matched:!1,reason_code:"TIER_MISMATCH",reason:`agent trust tier '${e}' (level ${n}) is below the required minimum '${t}' (level ${r})`}:{matched:!0,reason_code:"POLICY_ALLOW",reason:`agent trust tier '${e}' (level ${n}) meets the required minimum '${t}' (level ${r})`}}function Fe(e,t){return!t||t.length===0?{matched:!1,reason_code:"POLICY_ALLOW",reason:"no principal exclusions on this rule"}:t.includes(e)?{matched:!0,reason_code:"PRINCIPAL_EXCLUDED",reason:`agent '${e}' is in the principal exclusion list`}:{matched:!1,reason_code:"POLICY_ALLOW",reason:`agent '${e}' is not in the principal exclusion list`}}function De(e,t){return t?e?e===t?{matched:!0,reason_code:"SURFACE_RESTRICTION",reason:`surface '${e}' matches rule surface restriction`}:{matched:!1,reason_code:"POLICY_ALLOW",reason:`input surface '${e}' does not match rule surface '${t}'`}:{matched:!1,reason_code:"POLICY_ALLOW",reason:`no surface on input \u2014 rule surface '${t}' restriction does not apply`}:{matched:!1,reason_code:"POLICY_ALLOW",reason:"rule has no surface restriction"}}var yn={matched:!1,decision:"deny",reason:"No matching rule",reasonCode:null,ruleId:null,ruleCode:null,ruleDescription:null};function Me(e){return e==="allow"?"permit":e==="redact"||e==="escalate"||e==="deny"?e:"deny"}function bn(e,t){if(Array.isArray(e))return e;if(e&&typeof e=="object"&&e.version===2&&typeof e.branches=="object"&&e.branches!==null&&!Array.isArray(e.branches)){let{branches:n}=e;if(t&&Array.isArray(n[t]))return n[t];let r=Object.keys(n);return r.length===1&&Array.isArray(n[r[0]])?n[r[0]]:void 0}}function te(e,t){let n=bn(e,t);if(!Array.isArray(n)||n.length===0)return;let r=n.filter(i=>i&&typeof i=="object"&&(typeof i.field=="string"||typeof i.pattern=="string"));return r.length>0?r:void 0}function A(e,t){return e.decision==="redact"&&!t.transformableResponse?{...e,decision:"deny",reason:`${e.reason} (redaction not enforceable on this call \u2014 denied fail-closed)`,redactionRules:void 0,downgraded:{from:"redact",to:"deny",why:"response not transformable at this call site"}}:e}function je(e,t,n){if(e.decision!=="redact")return e;let r=te(t.redactionSpec,n);return r?{...e,redactionRules:r}:t.failClosedOnEmptySpec?{...e,decision:"deny",reason:`${e.reason} (redact rule has no enforceable redaction_spec \u2014 denied fail-closed, G001)`,redactionRules:void 0,downgraded:{from:"redact",to:"deny",why:"redact rule has no enforceable redaction_spec"}}:e}function vn(e,t,n){if(e.decision!=="mask")return e;let r=te(t.redactionSpec,n);return r?{...e,redactionRules:r}:{...e,decision:"deny",reason:`${e.reason} (mask rule has no resolvable redaction directives \u2014 denied fail-closed, G001)`,redactionRules:void 0,downgraded:{from:"mask",to:"deny",why:"mask rule has no resolvable redaction directives"}}}function K(e,t,n){if(e.decision!=="approval_required"&&e.decision!=="escalate")return e;if(t.hitlFallback!=="mask")return{...e,hitlFallback:"deny"};let r=te(t.redactionSpec,n);return r?{...e,hitlFallback:"mask",redactionRules:r}:{...e,hitlFallback:"deny",downgraded:{from:e.decision,to:"deny",why:"hitl_fallback=mask but no enforceable redaction_spec \u2014 no-approval falls back to deny (G001)"}}}function Xe(e,t){let n=[...e].sort((l,g)=>g.priority-l.priority),r=t.resourceMetadata!==null||t.operation!==null,i=t.action!==null&&t.targetApp!==null,o=t.facets.includes("retrieval")&&r,a=(t.facets.includes("action")||t.facets.includes("delegation"))&&i,s=t.resourceMetadata??{},c=typeof s.min_trust_tier=="string"?s.min_trust_tier:null,p=Array.isArray(s.excluded_principals)?s.excluded_principals:null,d=Array.isArray(s.restricted_surfaces)?s.restricted_surfaces:null,_={agent_id:t.agentId,operation:t.operation??"retrieve",resource_type:t.resourceType??"",resource_metadata:s,trust_tier:t.trustTier,surface:t.surface,query:t.query,normalized:t.normalized,agent:t.agent,session:t.session??w};for(let l of n){if(o&&l.appliesTo.includes("retrieval")){if(l.bypassActive&&l.bypassExpiresAt&&new Date(l.bypassExpiresAt).getTime()>Date.now())return A({matched:!0,decision:"permit",reason:l.bypassReason??"Emergency bypass active",reasonCode:"EMERGENCY_BYPASS",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},t.capabilities);if(l.principalExclusions&&l.principalExclusions.length>0){let m=Fe(t.agentId,l.principalExclusions);if(m.matched)return A({matched:!0,decision:"deny",reason:m.reason,reasonCode:"PRINCIPAL_EXCLUDED",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},t.capabilities)}if(p&&p.length>0){let m=Fe(t.agentId,p);if(m.matched)return A({matched:!0,decision:"deny",reason:m.reason,reasonCode:"PRINCIPAL_EXCLUDED",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},t.capabilities)}if(l.surface){let m=De(t.surface,l.surface);if(m.matched){let y=Re(l.regoSource,_),k=y.matched?y.action:"deny",N=Me(k),oe=y.matched?y.branchId:void 0;return A(K(je({matched:!0,decision:N,reason:m.reason,reasonCode:k==="allow"?"POLICY_ALLOW":"SURFACE_RESTRICTION",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},l,oe),l,oe),t.capabilities)}continue}if(d&&d.length>0&&t.surface&&De(t.surface,d.includes(t.surface)?t.surface:null).matched)return A({matched:!0,decision:"deny",reason:`surface '${t.surface}' is in the restricted surfaces list for this resource`,reasonCode:"SURFACE_RESTRICTION",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},t.capabilities);if(l.trustTier){let m=Le(t.trustTier,l.trustTier);if(!m.matched)return A({matched:!0,decision:"deny",reason:m.reason,reasonCode:"TIER_MISMATCH",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},t.capabilities)}if(c){let m=Le(t.trustTier,c);if(!m.matched)return A({matched:!0,decision:"deny",reason:m.reason,reasonCode:"TIER_MISMATCH",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},t.capabilities)}let g=Re(l.regoSource,_);if(g.matched){let m=Me(g.action);return A(K(je({matched:!0,decision:m,reason:`Matched rule: ${l.name}`,reasonCode:g.action==="allow"?"POLICY_ALLOW":"POLICY_DENY",ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},l,g.branchId),l,g.branchId),t.capabilities)}}if(a&&(l.appliesTo.includes("action")||l.appliesTo.includes("delegation"))){if(l.targetApp!==null&&l.targetApp!==t.targetApp||!cn(l.actionPattern,t.action??""))continue;let g=rn(l.regoSource,{target_app:t.targetApp??"",action:t.action??"",context:t.context,normalized:t.normalized,agent:t.agent,session:t.session??w});if(g.matched)return A(K(vn({matched:!0,decision:g.decision,reason:l.description??l.name,reasonCode:null,ruleId:l.id,ruleCode:l.ruleCode,ruleDescription:l.description},l,g.branchId),l,g.branchId),t.capabilities)}}return yn}function An(e,t=process.env){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return e;let n=t.ALLOW_TIMEOUT_MS;if(n!==void 0){let r=Number.parseInt(n,10);if(Number.isFinite(r)&&r>0)return r}return 5e3}var wn=5e3,kn=1e4,et=1e3,In=512,Sn=1800*1e3,En="monitor",Cn=f.object({operations:f.array(f.enum(["retrieval","action","delegation"])).min(1,"at least one operation is required"),agentId:f.string().min(1,"agentId is required"),sessionId:f.string().min(1).max(255).optional(),toolName:f.string().optional(),toolArgs:f.record(f.unknown()).optional(),targetResource:f.string().optional(),resourceType:f.string().optional(),resourceMetadata:f.record(f.unknown()).optional(),normalized:f.record(f.unknown()).optional(),query:f.string().optional(),contentPreview:f.string().optional(),telemetry:f.record(f.unknown()).optional()}),Tn=f.object({version:f.string().min(1),agent_mode:f.enum(["enforce","monitor","off"]).optional(),agent_attributes:f.object({trust_tier:f.string().nullable().optional(),categories:f.array(f.string()).optional(),business_function:f.string().nullable().optional(),no_coverage:f.string().nullable().optional()}).optional(),rules:f.array(f.record(f.unknown())),no_coverage:f.object({no_coverage_defaults:f.record(f.string()),autopilot_enabled:f.boolean(),enduser_hitl_enabled:f.boolean(),hitl_timeout_seconds:f.number().optional()}).optional()});function xn(e){let t=e.trust_tier??null,n=e.business_function??"";return{trust_tier:We(t)?t:null,categories:Ge(e.categories??[]),business_function:Ye(n)?n:null}}function tt(e){switch(e){case"permit":return 0;case"mask":case"redact":return 3;case"approval_required":case"escalate":return 4;case"deny":return 5;default:return 5}}function ne(e){let t=null;for(let n of e)(t===null||tt(n.decision)>tt(t.decision))&&(t=n);return t}function On(e){return e==="permit"?"permit":e==="mask"||e==="redact"?"mask":e==="approval_required"||e==="escalate"?"approval_required":"deny"}function Rn(e){switch(e){case"permit":return"allow";case"redact":return"redact";case"escalate":return"escalate";default:return"deny"}}var rt=class{client;originalFetch;resolvedApiKey;resolvedEndpoint;resolvedAgentId;harnessKind;rules=[];agentAttributes=null;agentNoCoverage=null;sessions=new Map;noCoverage=null;bundleVersion=null;bundleEtag=null;bundleLoaded=!1;bundleFetchInProgress=!1;initialBundlePromise=null;refreshTimer=null;agentMode=En;agentModeConfirmed=!1;telemetryBuffer=[];telemetryTimer=null;envRegistered=!1;registerPromise=null;constructor(e,t,n,r,i){let o=e??process.env.VISIQ_API_KEY??process.env.ALLOW_API_KEY??process.env.VISIQ_ALLOW_API_KEY,a=t??process.env.VISIQ_ENDPOINT??process.env.ALLOW_ENDPOINT,s=n??process.env.VISIQ_AGENT_ID??process.env.ALLOW_AGENT_ID??"";if(this.resolvedApiKey=o,this.resolvedEndpoint=a,this.resolvedAgentId=s,this.harnessKind=r,this.originalFetch=globalThis.fetch.bind(globalThis),o&&a){let c={apiKey:o,agentId:s,endpoint:a,mode:"enforce",timeoutMs:An(i),hitlTimeoutMs:12e4,failBehavior:"closed",...r?{harnessKind:r}:{}};this.client=new we(c,this.originalFetch),this.initialBundlePromise=this.fetchBundleBackground(),this.refreshTimer=setInterval(()=>{this.fetchBundleBackground()},wn),nt(this.refreshTimer),this.telemetryTimer=setInterval(()=>{this.flushTelemetry()},kn),nt(this.telemetryTimer)}else this.client=null}async evaluate(e){let t=Cn.parse(e),n=Date.now(),r=this.agentMode;if(r==="off")return this.offDecision(t);if(!this.bundleLoaded){let _=this.agentModeConfirmed&&(this.agentMode==="monitor"||this.agentMode==="off")?this.agentMode:"enforce",l=this.denyAll(t,"No unified bundle loaded and no local rules \u2014 fail-closed (G001)");return this.applyAgentMode(l,_,t,n)}let i=this.readSession(t.sessionId),o=[],a=[];for(let _ of t.operations){let l=this.resolveOperation(_,t,i);_==="retrieval"?a.push(l):o.push(l)}let s=ne(o),c=ne(a),p=ne([s,c].filter(_=>_!==null)),d=this.buildDecision(p,s,c,r);return this.foldSession(t,r==="enforce"?d.decision:"permit"),d.action.decision==="approval_required"&&r==="enforce"&&await this.registerHitl(t,d),this.applyAgentMode(d,r,t,n)}readSession(e){if(!e)return w;let t=this.sessions.get(e);return t?Date.now()-t.touchedAt>Sn?(this.sessions.delete(e),w):t.state:w}foldSession(e,t){let{sessionId:n}=e;if(!n)return;let r=this.readSession(n),i=e.resourceMetadata??{},o=e.operations.includes("retrieval"),a=e.operations.includes("action")||e.operations.includes("delegation"),s=a?X(e.normalized??{},{action:e.toolName??"",targetApp:e.targetResource??e.toolName??""}):e.normalized??{},c=Ke(r===w?He():r,{data_categories:o&&Array.isArray(i.data_categories)?i.data_categories:void 0,classification:o?i.classification:void 0,action_class:a?s.action_class:void 0,target_app:a?e.targetResource??e.toolName:void 0,decision:t});if(this.sessions.delete(n),this.sessions.set(n,{state:c,touchedAt:Date.now()}),this.sessions.size>In){let p=this.sessions.keys().next().value;p!==void 0&&this.sessions.delete(p)}}resolveOperation(e,t,n){let r=Xe(this.rules,this.buildInput(e,t,n));if(r.matched)return r;if(e==="retrieval")return{...r,reason:"No matching rule \u2014 retrieval default deny (G001)"};if(this.noCoverage){let i=Je({action:t.toolName??t.targetResource??"",targetApp:t.targetResource??t.toolName??"",mode:"enforce",config:this.noCoverage,agentNoCoverage:this.agentNoCoverage});return{matched:!0,decision:i.decision,reason:i.reason,reasonCode:null,ruleId:null,ruleCode:i.rule_code,ruleDescription:null}}return{...r,reason:"No matching rule and no no-coverage config \u2014 fail-closed (G001)"}}buildInput(e,t,n){let r=t.toolName??"",i=t.targetResource??t.toolName??"",o=X(t.normalized??{},{action:r,targetApp:i}),a={agentId:t.agentId,agent:this.agentAttributes??$,session:n,normalized:o},s=this.agentAttributes?.trust_tier??null;return e==="retrieval"?{...a,facets:["retrieval"],targetApp:null,action:null,context:{},operation:"retrieve",resourceType:t.resourceType??t.targetResource??"",resourceMetadata:t.resourceMetadata??{},trustTier:s,surface:null,query:t.query??null,capabilities:{transformableResponse:!0}}:{...a,facets:[e==="delegation"?"delegation":"action"],targetApp:i,action:r,context:t.toolName?{[t.toolName]:t.toolArgs??{}}:{},operation:null,resourceType:null,resourceMetadata:null,trustTier:s,surface:null,query:null,capabilities:{transformableResponse:!1}}}buildDecision(e,t,n,r){let i=e?.decision??"deny",o=t?this.actionSub(t):{decision:null,allowed:!0},a=n?this.retrievalSub(n):{action:null};return{decision:i,allowed:i==="permit"||i==="mask"||i==="redact",reason:e?.reason??"No matching rule",ruleId:e?.ruleId??null,ruleCode:e?.ruleCode??null,enforced:r==="enforce",agentMode:r,action:o,retrieval:a}}actionSub(e){let t=e.decision;return t==="mask"?{decision:"mask",allowed:!0,argRedactionRules:e.redactionRules??[]}:t==="approval_required"?{decision:"approval_required",allowed:!1,...e.hitlFallback==="mask"&&e.redactionRules?{argRedactionRules:e.redactionRules,hitlFallback:"mask"}:{}}:t==="permit"?{decision:"permit",allowed:!0}:{decision:"deny",allowed:!1}}retrievalSub(e){let t=Rn(e.decision);return t==="redact"&&e.redactionRules?{action:t,redactionRules:e.redactionRules}:t==="escalate"&&e.hitlFallback==="mask"&&e.redactionRules?{action:t,redactionRules:e.redactionRules,hitlFallback:"mask"}:{action:t}}applyAgentMode(e,t,n,r){let i=Date.now()-r;if(t==="enforce"){let a={...e,enforced:!0,agentMode:"enforce"};return this.bufferTelemetry(n,a,i,!0),a}let o={...e,allowed:!0,enforced:!1,agentMode:"monitor"};return this.bufferTelemetry(n,o,i,!1),o}offDecision(e){let t=e.operations.includes("action")||e.operations.includes("delegation"),n=e.operations.includes("retrieval");return{decision:"permit",allowed:!0,reason:"agent_mode=off \u2014 evaluation bypassed",ruleId:null,ruleCode:null,enforced:!1,agentMode:"off",action:t?{decision:"permit",allowed:!0}:{decision:null,allowed:!0},retrieval:n?{action:"allow"}:{action:null}}}denyAll(e,t){let n=e.operations.includes("action")||e.operations.includes("delegation"),r=e.operations.includes("retrieval");return{decision:"deny",allowed:!1,reason:t,ruleId:null,ruleCode:null,enforced:!0,agentMode:"enforce",action:n?{decision:"deny",allowed:!1}:{decision:null,allowed:!0},retrieval:r?{action:"deny"}:{action:null}}}async registerHitl(e,t){if(this.client)try{let n={agent_id:e.agentId,target_app:e.targetResource??e.toolName??"",action:e.toolName??"",context:e.toolName?{[e.toolName]:e.toolArgs??{}}:{},...e.sessionId?{session_id:e.sessionId}:{},...e.telemetry?{telemetry:e.telemetry}:{}},r=await this.client.evaluate(n);r.decision_id&&(t.action.decisionId=r.decision_id)}catch(n){console.warn("[UNIFIED] HITL registration failed \u2014 staying fail-closed (G001):",n)}}applyBundle(e){let t=Tn.parse(e);this.bundleLoaded&&t.version===this.bundleVersion||(this.rules=t.rules.map(n=>Qe(n)).filter(n=>n!==null),this.agentAttributes=t.agent_attributes?xn(t.agent_attributes):null,this.agentNoCoverage=ee(t.agent_attributes?.no_coverage),this.bundleVersion=t.version,this.bundleLoaded=!0,this.noCoverage=t.no_coverage?{no_coverage_defaults:t.no_coverage.no_coverage_defaults,autopilot_enabled:t.no_coverage.autopilot_enabled,enduser_hitl_enabled:t.no_coverage.enduser_hitl_enabled}:null,t.agent_mode!==void 0&&(this.agentMode=t.agent_mode,this.agentModeConfirmed=!0))}async awaitBundleReady(e=5e3){if(!this.initialBundlePromise||this.bundleLoaded)return;let t,n=new Promise(r=>{t=setTimeout(r,e)});try{await Promise.race([this.initialBundlePromise,n])}catch{}finally{t&&clearTimeout(t)}}async fetchBundleBackground(){if(!(this.bundleFetchInProgress||!this.resolvedEndpoint||!this.resolvedApiKey)){this.bundleFetchInProgress=!0;try{let e=this.resolvedAgentId?`?agent_id=${encodeURIComponent(this.resolvedAgentId)}`:"",t={Authorization:`Bearer ${this.resolvedApiKey}`,Accept:"application/json"};this.bundleEtag&&(t["If-None-Match"]=this.bundleEtag);let n=await this.originalFetch(`${this.resolvedEndpoint}/rules/bundle${e}`,{method:"GET",headers:t});if(n.status===304)return;if(!n.ok)throw new Error(`unified bundle fetch failed: HTTP ${n.status}`);let r=n.headers.get("ETag");r&&(this.bundleEtag=r);let i=await n.json();this.applyBundle(i)}catch(e){this.bundleLoaded?console.warn("[UNIFIED] Bundle refresh failed (using cached rules):",e):console.warn("[UNIFIED] Initial bundle fetch failed (fail-closed until loaded):",e)}finally{this.bundleFetchInProgress=!1}}}hasRules(){return this.bundleLoaded&&this.rules.length>0}hasClient(){return this.client!==null}getEndpoint(){return this.resolvedEndpoint}getApiKey(){return this.resolvedApiKey}getBundleVersion(){return this.bundleVersion}getAgentMode(){return this.agentMode}getAgentAttributes(){return this.agentAttributes}getSessionState(e){return this.readSession(e)}registerEnvironment(e){!this.client||this.envRegistered||!this.resolvedAgentId||(this.envRegistered=!0,this.registerPromise=this.client.registerEnvironment(e))}registerHostEnvironment(){this.registerEnvironment(ke())}bufferTelemetry(e,t,n,r){this.telemetryBuffer.push({decision_id:crypto.randomUUID(),agent_id:e.agentId,target_app:e.targetResource??e.toolName??"",action:e.toolName??e.operations.join("+"),...e.sessionId?{session_id:e.sessionId}:{},decision:On(t.decision),reason:t.reason,evaluated_at:new Date().toISOString(),rule_id:t.ruleId,rule_code:t.ruleCode,enforced:r,agent_mode:this.agentMode,latency_ms:n,evaluation_mode:"local",raw_event:{agent_id:e.agentId,operations:e.operations,...e.toolName?{tool_name:e.toolName}:{},...e.targetResource?{target_resource:e.targetResource}:{},...e.resourceType?{resource_type:e.resourceType}:{}},...this.harnessKind?{context:{[ve]:this.harnessKind}}:{}}),this.enforceTelemetryCap()}async flushTelemetry(){if(this.telemetryBuffer.length===0||!this.client)return;let e=this.telemetryBuffer.splice(0);try{await this.client.sendTelemetry(e)}catch(t){console.error("[UnifiedRuntime] telemetry flush failed (non-fatal):",t)}}enforceTelemetryCap(){let e=this.telemetryBuffer.length-et;e>0&&(this.telemetryBuffer.splice(0,e),console.warn(`[UnifiedRuntime] telemetry buffer full \u2014 dropped ${e} oldest entries (cap ${et})`))}async flush(){if(this.registerPromise)try{await this.registerPromise}catch{}await this.flushTelemetry()}dispose(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null),this.telemetryTimer&&(clearInterval(this.telemetryTimer),this.telemetryTimer=null),this.flushTelemetry()}};function nt(e){e&&typeof e=="object"&&"unref"in e&&e.unref()}function re(e){let t=[],n=[],r,i=[];for(let a of e){if(a.mode==="partial"||a.mode==="email"||a.mode==="custom"){let c={mode:a.mode};a.field&&(c.field=a.field),a.pattern&&(c.pattern=a.pattern),typeof a.keepFirst=="number"&&(c.keepFirst=a.keepFirst),typeof a.keepLast=="number"&&(c.keepLast=a.keepLast),a.maskChar&&(c.maskChar=a.maskChar),a.keepPattern&&(c.keepPattern=a.keepPattern),a.replacement!==void 0&&(c.replacement=a.replacement),i.push(c);continue}a.field&&t.push(a.field),a.pattern&&n.push(a.pattern);let{replacement:s}=a;s&&r===void 0&&(r=s)}let o={};return t.length>0&&(o.fields=t),n.length>0&&(o.patterns=n),r!==void 0&&(o.replacement=r),i.length>0&&(o.directives=i),o}var Nn="[VisIQ: retrieval withheld by policy]";function B(e){return ie(e)}function ie(e){if(e==null)return e;if(typeof e=="string")return e.length>0?Nn:e;if(typeof e=="number")return 0;if(typeof e=="boolean")return!1;if(Array.isArray(e))return e.map(ie);if(typeof e=="object"){let t=Object.create(null);for(let n of Object.keys(e))t[n]=ie(e[n]);return t}return e}function it(e){return e.fields&&e.fields.length>0||e.patterns&&e.patterns.length>0?!0:!!e.directives?.some(t=>t.field&&t.field.length>0||t.pattern&&t.pattern.length>0)}function at(e,t){if(!e.enforced)return null;let{action:n,retrieval:r}=e,i=I(e.reason,e.ruleCode);if(n.decision==="deny"||r.action==="deny")return F(i);if(n.decision==="approval_required"||r.action==="escalate")return ge(i);if(n.decision==="mask"&&n.argRedactionRules&&n.argRedactionRules.length>0){let o=re(n.argRedactionRules);if(it(o)){let a=V(t,o)??t;if(JSON.stringify(a)!==JSON.stringify(t))return me(a)}return null}return null}function ot(e,t){if(!e.enforced)return null;let{retrieval:n}=e,r=I(e.reason,e.ruleCode);if(n.action==="redact"){let i=re(n.redactionRules??[]);if(!it(i))return S(B(t),r);let o=V(t,i);return JSON.stringify(o)===JSON.stringify(t)?null:S(o,r)}return n.action==="deny"?S(B(t),r):null}var Pn="cli_harness";function C(e){return new rt(e.apiKey,e.baseUrl,e.agentId,Pn)}function W(e){let t={operations:e.operations,agentId:e.agentId,toolName:e.toolName,toolArgs:e.toolInput,resourceType:"tool_result",resourceMetadata:{toolName:e.toolName,...e.toolUseId?{toolCallId:e.toolUseId}:{},...e.sessionId?{sessionId:e.sessionId}:{},...e.cwd?{cwd:e.cwd}:{}}};return e.query&&(t.query=e.query),t}async function T(e,t,n=se){let{agentId:r}=t,i=await ce(r);if(i&&le(i,n,Date.now())&&ae(e,i.body))return;let o=e.getEndpoint();if(o){let a=await de(o,t.apiKey,r);if(a&&ae(e,a.body)){await ue(r,a.body,a.version);return}}if(!(i&&ae(e,i.body)))try{await e.awaitBundleReady(2500)}catch{}}function ae(e,t){try{return e.applyBundle(t),!0}catch{return!1}}async function x(e){try{await e.flush()}catch{}try{e.dispose()}catch{}}async function pr(e,t){let n=C(t),r=e.tool_input??{};try{await pe(e.session_id)||(n.registerHostEnvironment(),await P(e.session_id)),await T(n,t);let i=W({operations:fe(e.tool_name),agentId:t.agentId,toolName:e.tool_name,toolInput:r,sessionId:e.session_id,cwd:e.cwd,toolUseId:e.tool_use_id,query:L(r)}),o=await n.evaluate(i);return at(o,r)}catch{return F(I("evaluation error (fail-closed)",null))}finally{await x(n)}}async function yr(e,t){if(!z(e.tool_name))return null;let n=C(t);try{await T(n,t);let r=e.tool_input??{},i=W({operations:["retrieval"],agentId:t.agentId,toolName:e.tool_name,toolInput:r,sessionId:e.session_id,cwd:e.cwd,toolUseId:e.tool_use_id,query:L(r)}),o=await n.evaluate(i);return ot(o,e.tool_response)}catch{return S(B(e.tool_response),I("evaluation error (fail-closed)",null))}finally{await x(n)}}async function wr(e,t){let n=C(t);try{n.registerHostEnvironment(),await T(n,t,0),await P(e.session_id)}finally{await x(n)}return null}export{ct as a,z as b,fe as c,L as d,I as e,me as f,F as g,ge as h,S as i,Fn as j,Nn as k,B as l,at as m,ot as n,Pn as o,C as p,W as q,T as r,x as s,pr as t,yr as u,wr as v};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import{j as r,t as i,u as a,v as u}from"../chunk-K5THPWCB.js";import{e as s}from"../chunk-3GWLCI47.js";var p=process.env.VISIQ_DEBUG==="1",f=p?(...o)=>{process.stderr.write(`${o.map(String).join(" ")}
3
+ `)}:()=>{},d=console;for(let o of["log","info","debug","warn","error"])d[o]=f;function l(){return new Promise(o=>{if(process.stdin.isTTY){o("");return}let t=[],n=!1,e=()=>{n||(n=!0,o(Buffer.concat(t).toString("utf-8")))};setTimeout(e,15e3).unref?.(),process.stdin.on("data",c=>t.push(c)),process.stdin.on("end",e),process.stdin.on("error",e)})}async function m(){let o=r(await l());if(!o)return;let t=await s();if(!t)return;let n=null;switch(o.hook_event_name){case"PreToolUse":n=await i(o,t);break;case"PostToolUse":n=await a(o,t);break;case"SessionStart":n=await u(o,t);break;default:n=null}n&&process.stdout.write(JSON.stringify(n))}m().then(()=>process.exit(0)).catch(()=>process.exit(0));
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import{a as f,e as m,f as w,g as d}from"../chunk-6DMSMBWM.js";import{a as l,b as u,c as g,e as p,i as y}from"../chunk-3GWLCI47.js";import{mkdir as I,readFile as v,writeFile as h}from"node:fs/promises";import{existsSync as j}from"node:fs";import{homedir as k}from"node:os";import{dirname as $,isAbsolute as C,join as a,resolve as Q}from"node:path";import{parseArgs as V}from"node:util";function N(){let{positionals:e,values:n}=V({args:process.argv.slice(2),options:{user:{type:"boolean"},project:{type:"boolean"},settings:{type:"string"},command:{type:"string"},"api-key":{type:"string"},"agent-id":{type:"string"},"base-url":{type:"string"}},strict:!1,allowPositionals:!0});return{positionals:e,values:n}}function S(e){let n=e.settings;return typeof n=="string"&&n.length>0?C(n)?n:Q(n):e.project?a(process.cwd(),".claude","settings.json"):a(k(),".claude","settings.json")}async function c(e){if(!j(e))return{};try{let n=await v(e,"utf-8"),t=JSON.parse(n);return t&&typeof t=="object"?t:{}}catch{return{}}}async function P(e,n){await I($(e),{recursive:!0}),await h(e,`${JSON.stringify(n,null,2)}
3
+ `,"utf-8")}async function U(e){let n=S(e),t=typeof e.command=="string"?e.command:f,s=await c(n);return await P(n,m(s,t)),process.stdout.write(`[VisIQ] Installed Claude Code governance hooks \u2192 ${n}
4
+ SessionStart + PreToolUse + PostToolUse \u2192 \`${t}\`
5
+ Next: set credentials (\`visiq-claude-code configure\`) then start Claude Code.
6
+ `),0}async function O(e){let n=S(e),t=typeof e.command=="string"?e.command:void 0,s=await c(n);return await P(n,w(s,t)),process.stdout.write(`[VisIQ] Removed Claude Code governance hooks from ${n}
7
+ `),0}async function R(e){let n=g(),t=typeof e["api-key"]=="string"&&e["api-key"]||n.apiKey,s=typeof e["agent-id"]=="string"&&e["agent-id"]||n.agentId,o=typeof e["base-url"]=="string"&&e["base-url"]||n.baseUrl;if(!t||!s)return process.stderr.write(`[VisIQ] configure needs --api-key and --agent-id (or VISIQ_API_KEY / VISIQ_AGENT_ID env).
8
+ `),1;let i={apiKey:t,agentId:s};o&&(i.baseUrl=o);let r=u();return await I($(r),{recursive:!0}),await h(r,`${JSON.stringify(i,null,2)}
9
+ `,{mode:384}),process.stdout.write(`[VisIQ] Wrote credentials \u2192 ${r} (agent ${s}${o?`, endpoint ${o}`:""})
10
+ `),0}async function A(){let e=await p(),n=a(k(),".claude","settings.json"),t=a(process.cwd(),".claude","settings.json"),s=d(await c(n)),o=d(await c(t)),i=e?await y(e.agentId):null,r=["[VisIQ] Claude Code harness \u2014 doctor",` credentials: ${e?"resolved":"MISSING (run `visiq-claude-code configure`)"}`,` agent id: ${e?.agentId??"\u2014"}`,` endpoint: ${e?.baseUrl??process.env.VISIQ_ENDPOINT??"(default)"}`,` state dir: ${l()}`,` hooks (user): ${s?"installed":"not installed"} \u2014 ${n}`,` hooks (proj): ${o?"installed":"not installed"} \u2014 ${t}`,` bundle cache: ${i?`present (fetched ${new Date(i.fetchedAt).toISOString()})`:"none"}`];return process.stdout.write(`${r.join(`
11
+ `)}
12
+ `),e?0:1}function b(){process.stdout.write(`Usage: visiq-claude-code <install|uninstall|configure|doctor> [flags]
13
+
14
+ install [--user|--project|--settings <path>] [--command <cmd>]
15
+ uninstall [--user|--project|--settings <path>]
16
+ configure [--api-key <key>] [--agent-id <id>] [--base-url <url>]
17
+ doctor
18
+ `)}async function D(){let{positionals:e,values:n}=N(),t=e[0],s=0;switch(t){case"install":s=await U(n);break;case"uninstall":s=await O(n);break;case"configure":s=await R(n);break;case"doctor":s=await A();break;case"help":case void 0:b();break;default:process.stderr.write(`[VisIQ] Unknown command: ${t}
19
+ `),b(),s=1}process.exit(s)}D().catch(e=>{process.stderr.write(`[VisIQ] ${e instanceof Error?e.message:String(e)}
20
+ `),process.exit(1)});
@@ -0,0 +1,676 @@
1
+ /**
2
+ * Credential + config-dir resolution for the VisIQ Claude Code harness.
3
+ *
4
+ * Resolution precedence (highest wins):
5
+ * 1. Environment variables: VISIQ_API_KEY / VISIQ_AGENT_ID / VISIQ_BASE_URL
6
+ * (VISIQ_ENDPOINT accepted as a baseUrl fallback — the canonical var the
7
+ * demo terminal + `visiq()` factory export)
8
+ * 2. JSON config file at VISIQ_CONFIG_PATH (flat, or nested under
9
+ * plugins.entries["@visiq/claude-code-harness"].config)
10
+ * 3. ~/.visiq/claude-code/config.json (flat)
11
+ *
12
+ * Unlike the OpenClaw gateway plugin there is NO interactive TTY prompt: hooks
13
+ * are non-interactive subprocesses invoked by Claude Code. The `configure` CLI
14
+ * writes source (3).
15
+ *
16
+ * This function NEVER throws and NEVER logs the apiKey value (G004). It returns
17
+ * null when creds are absent; hooks treat null as "unconfigured → fail-open
18
+ * no-op" so an installed-but-unconfigured harness never breaks Claude Code.
19
+ */
20
+ export interface VisiqConfig {
21
+ apiKey: string;
22
+ agentId: string;
23
+ baseUrl?: string;
24
+ }
25
+ export interface PartialConfig {
26
+ apiKey?: string;
27
+ agentId?: string;
28
+ baseUrl?: string;
29
+ }
30
+ /**
31
+ * The VisIQ harness state directory (config + bundle cache + session markers).
32
+ * Overridable via VISIQ_HOME (used by tests + the conformance harness to
33
+ * isolate per-run state). Defaults to ~/.visiq/claude-code.
34
+ */
35
+ export declare function visiqHomeDir(): string;
36
+ /** Path to the flat credential file the `configure` CLI writes. */
37
+ export declare function configFilePath(): string;
38
+ /** Resolve the env-var layer, honoring the VISIQ_ENDPOINT baseUrl fallback. */
39
+ export declare function envConfig(env?: NodeJS.ProcessEnv): PartialConfig;
40
+ /**
41
+ * Merge the resolved layers by precedence (env > VISIQ_CONFIG_PATH > home
42
+ * config). Pure — split out so it can be unit-tested without disk I/O.
43
+ */
44
+ export declare function mergeConfig(home: PartialConfig, configPath: PartialConfig, env: PartialConfig): VisiqConfig | null;
45
+ /**
46
+ * Resolve harness credentials following the precedence chain. Returns null when
47
+ * apiKey/agentId cannot be resolved. Never throws.
48
+ */
49
+ export declare function loadConfig(): Promise<VisiqConfig | null>;
50
+ /**
51
+ * Claude Code tool taxonomy — action vs retrieval classification.
52
+ *
53
+ * VisIQ governs two facets per tool call:
54
+ * - ACTION — side-effecting tools (shell, file writes, MCP mutations)
55
+ * - RETRIEVAL — tools that pull external/foreign content INTO the model's
56
+ * context (web fetch/search, file/dir reads, MCP resource reads)
57
+ *
58
+ * The set below is the canonical Claude Code built-in tool list (verified
59
+ * against the running CLI, v2.1.x — the `tool_name` values a hook receives).
60
+ * Retrieval tools are tagged with BOTH facets (`['retrieval','action']`) so an
61
+ * action rule (e.g. "block all web egress") also governs them, mirroring the
62
+ * OpenClaw plugin's dual-facet tagging. Everything else is a pure action.
63
+ *
64
+ * Fail-safe posture (G001): an UNKNOWN tool is treated as an ACTION (governed,
65
+ * never silently ungoverned). MCP tools whose bare name looks like a read are
66
+ * ALSO run through the retrieval facet (fails SAFE toward more governance).
67
+ */
68
+ /**
69
+ * Reduce a possibly-namespaced MCP tool name to its leaf: `mcp__brave__web_search`
70
+ * → `web_search`. Non-MCP (and non-string, defensively) names are returned as-is.
71
+ */
72
+ export declare function bareToolName(toolName: string): string;
73
+ /** Does this tool call pull foreign content into the model's context? */
74
+ export declare function isRetrievalTool(toolName: string): boolean;
75
+ /** Governance facets to evaluate for a given tool. */
76
+ export type Operation = "action" | "retrieval";
77
+ /**
78
+ * The `operations[]` to hand `UnifiedRuntime.evaluate`. Retrieval tools carry
79
+ * BOTH facets (an action rule can still govern a fetch); everything else is a
80
+ * pure action.
81
+ */
82
+ export declare function toolOperations(toolName: string): Operation[];
83
+ /**
84
+ * Extract a human-meaningful query string from a tool's input for retrieval
85
+ * evaluation + telemetry. Joins the first-present query-shaped fields; returns
86
+ * `undefined` when none are present (so the caller omits `query`).
87
+ */
88
+ export declare function extractQuery(toolInput: unknown): string | undefined;
89
+ /**
90
+ * Server-side per-agent mode. Lives on `allow_agents.mode` and is delivered
91
+ * to the SDK as `agent_mode` in the rules bundle. The SDK branches on this
92
+ * to decide whether to enforce denials.
93
+ *
94
+ * - 'enforce' — deny decisions throw and block tool execution.
95
+ * - 'monitor' — record telemetry with enforced=false; tool runs anyway.
96
+ * - 'off' — skip evaluation entirely.
97
+ */
98
+ export type AgentMode = "enforce" | "monitor" | "off";
99
+ /**
100
+ * How this agent is wired into its runtime — mirrors `allow_agents.kind`.
101
+ *
102
+ * - 'sdk' — wired via @visiq/harness into a framework runtime
103
+ * (LangChain, LlamaIndex, …). This is the platform DEFAULT.
104
+ * - 'cli_harness' — wired via the @visiq/openclaw-plugin or another CLI
105
+ * harness plugin/driver.
106
+ *
107
+ * The harness declares its kind at REGISTRATION (the backend cannot infer it
108
+ * from a generic register call), so the Agents/Discover UI and the fleet
109
+ * harness-liveness probe can attribute it correctly. Omitted → backend default
110
+ * ('sdk').
111
+ */
112
+ export type AgentKind = "sdk" | "cli_harness";
113
+ /**
114
+ * A directive for masking sensitive values in a tool call's ARGUMENTS before the
115
+ * underlying tool runs (the action-facet `mask` outcome). Shares the field shape
116
+ * with the retrieval facet's redaction rule + @visiq/redact so one masking
117
+ * primitive serves across facets. `field` masks a key anywhere in the args (any
118
+ * depth); `pattern` masks every string-leaf regex match; `mode` selects
119
+ * full/partial/email.
120
+ */
121
+ export interface ArgRedactionRule {
122
+ field?: string;
123
+ pattern?: string;
124
+ replacement?: string;
125
+ mode?: "full" | "partial" | "email";
126
+ keepFirst?: number;
127
+ keepLast?: number;
128
+ maskChar?: string;
129
+ }
130
+ declare const TRUST_TIERS: readonly [
131
+ "tier1",
132
+ "tier2",
133
+ "tier3"
134
+ ];
135
+ export type TrustTier = (typeof TRUST_TIERS)[number];
136
+ declare const AGENT_CATEGORIES: readonly [
137
+ "read_only",
138
+ "transactional",
139
+ "data_processor",
140
+ "external_comms",
141
+ "code_exec",
142
+ "privileged",
143
+ "internal",
144
+ "customer_facing",
145
+ "experimental",
146
+ "third_party"
147
+ ];
148
+ export type AgentCategory = (typeof AGENT_CATEGORIES)[number];
149
+ /**
150
+ * The agent attribute bundle exposed to rules as `input.agent` on both planes.
151
+ * `trust_tier` may be null (unassigned → caller-supplied tier honored);
152
+ * `categories` defaults to an empty array (never null);
153
+ * `business_function` is one of the `BUSINESS_FUNCTION_IDS` (see
154
+ * business-functions.ts) or null (unassigned → conditions on it don't match).
155
+ *
156
+ * NOTE (dependency-light): `business_function` is typed `string | null` rather
157
+ * than a narrowed union so this SSOT module imports nothing from
158
+ * business-functions.ts — the closed-set validation lives at the write/eval
159
+ * boundaries (isBusinessFunctionId) + the DB CHECK, keeping this type the
160
+ * trivially-importable shape it has always been.
161
+ */
162
+ export interface AgentEvalAttributes {
163
+ trust_tier: TrustTier | null;
164
+ categories: AgentCategory[];
165
+ /** One of BUSINESS_FUNCTION_IDS, or null when unassigned. */
166
+ business_function: string | null;
167
+ }
168
+ /**
169
+ * The session trajectory bundle exposed to rules as `input.session` on both
170
+ * planes. ALWAYS present with every key materialized ([]/0/{}) — empty ≠
171
+ * missing, so grammar behavior (membership, count(), numeric compares) is
172
+ * byte-identical everywhere and no arm can fire or skip on key-presence
173
+ * differences.
174
+ */
175
+ export interface SessionEvalState {
176
+ /** Union of resource_metadata.data_categories over prior retrieval events. */
177
+ retrieved_data_categories: string[];
178
+ /** Union of resource_metadata.classification tokens over prior retrievals. */
179
+ retrieved_classifications: string[];
180
+ /** Distinct action_class buckets attempted earlier in the session. */
181
+ action_classes: string[];
182
+ /** Per-action-class attempt counters (prior events only). */
183
+ action_counts: Record<string, number>;
184
+ /** Prior `deny` decisions in the session. */
185
+ denied_count: number;
186
+ /** Prior inhibited decisions (deny + approval_required + escalate). */
187
+ inhibited_count: number;
188
+ /** Total prior governed events in the session. */
189
+ event_count: number;
190
+ /** Distinct target apps touched earlier in the session. */
191
+ target_apps: string[];
192
+ }
193
+ /**
194
+ * Redaction directive — the shared masking primitive (SSOT).
195
+ *
196
+ * A single directive masks a structured field by name and/or a pattern in
197
+ * string content. The SAME shape serves the retrieval facet (RECALL redact — a
198
+ * response transform) and the action facet (ALLOW mask — an argument
199
+ * transform), and mirrors the SDK/plugin RedactionRule + `@visiq/redact` so one
200
+ * masking primitive is used everywhere. Lifted here (zero-dep) so the shared
201
+ * unified evaluator (`unified-evaluation.ts`) carries no backend dependency; the
202
+ * backend `services/recall-engine.ts` keeps its own identical `RecallRedactionRule`
203
+ * for the wire/DB shape — both must stay structurally identical.
204
+ */
205
+ export interface RecallRedactionRule {
206
+ field?: string;
207
+ pattern?: string;
208
+ replacement?: string;
209
+ /**
210
+ * Masking mode (SOTA, format-preserving): "full" replaces the whole match
211
+ * (default); "partial" keeps keepFirst/keepLast chars and masks the rest with
212
+ * maskChar (separators preserved — e.g. •••-••-6789); "email" keeps the first
213
+ * local char + the full domain (a••••@acme.com). Applied via @visiq/redact.
214
+ */
215
+ mode?: "full" | "partial" | "email";
216
+ keepFirst?: number;
217
+ keepLast?: number;
218
+ maskChar?: string;
219
+ }
220
+ export type UnifiedDecisionValue = "permit" | "deny" | "approval_required" | "redact" | "escalate" | "mask";
221
+ /**
222
+ * Generically-available agent environment reported ONCE at registration
223
+ * (migration 131). Never sent in per-event telemetry. All fields best-effort.
224
+ */
225
+ export interface AgentEnvironment {
226
+ /** e.g. "darwin 24.6.0" (os.platform() + os.release()). */
227
+ os?: string;
228
+ /** os.hostname(). */
229
+ hostname?: string;
230
+ /** Primary non-internal IPv4 (server falls back to the request source IP). */
231
+ ip?: string;
232
+ /** os.userInfo().username. */
233
+ username?: string;
234
+ }
235
+ /** The facets an event participates in (the unified `operations` tag). */
236
+ export type UnifiedOperation = "retrieval" | "action" | "delegation";
237
+ /** One governed agent event, tagged with the facets it participates in. */
238
+ export interface UnifiedEvaluateEvent {
239
+ /** Which facets this event participates in (≥1). */
240
+ operations: UnifiedOperation[];
241
+ agentId: string;
242
+ /**
243
+ * Opaque session key for SEQUENCE-AWARE rules (`input.session.*`): events
244
+ * sharing a sessionId accumulate trajectory evidence in-process, so a rule
245
+ * can gate the CURRENT event on what the agent already retrieved/attempted
246
+ * earlier in the same session. Harnesses thread their native session id
247
+ * (LangChain run session, Claude Code hook session_id, OpenClaw sessionKey).
248
+ * Absent ⇒ no accumulation; session conditions see a fresh-empty state.
249
+ */
250
+ sessionId?: string;
251
+ /** Tool/action name — drives the action + delegation legs. */
252
+ toolName?: string;
253
+ toolArgs?: Record<string, unknown>;
254
+ /** Target app/resource the action acts on (defaults to toolName). */
255
+ targetResource?: string;
256
+ /** Retrieval resource type (the retrieval leg). */
257
+ resourceType?: string;
258
+ resourceMetadata?: Record<string, unknown>;
259
+ query?: string;
260
+ /**
261
+ * Nested normalized event (`{ action_class, write: {...}, ... }`) for
262
+ * `input.normalized.*` rule conditions — supplied by harnesses that classify
263
+ * locally from cached action-schema mappings. Absent ⇒ conditions on
264
+ * normalized fields don't match (cold-start parity with an unmapped schema).
265
+ */
266
+ normalized?: Record<string, unknown>;
267
+ /** A snippet of the retrieved/produced content (reserved; not yet evaluated). */
268
+ contentPreview?: string;
269
+ telemetry?: Record<string, unknown>;
270
+ }
271
+ /** The action-facet sub-decision (null leg when the event had no action op). */
272
+ export interface UnifiedActionSubDecision {
273
+ decision: "permit" | "deny" | "approval_required" | "mask" | null;
274
+ /** Whether the action leg permits the call to proceed. */
275
+ allowed: boolean;
276
+ /** Backend audit-row id for a registered approval_required (HITL poll key). */
277
+ decisionId?: string;
278
+ /**
279
+ * For a `mask` decision: how to redact the tool ARGUMENTS before it runs.
280
+ * ALSO populated for an `approval_required` decision whose rule opted into the
281
+ * mask fallback (see `hitlFallback`), so a never-approved request can mask the
282
+ * arguments instead of being blocked.
283
+ */
284
+ argRedactionRules?: ArgRedactionRule[];
285
+ /**
286
+ * No-approval policy for an `approval_required` decision: 'mask' → a
287
+ * never-approved request falls back to masking the arguments (argRedactionRules)
288
+ * rather than blocking. Absent/'deny' → block on no-approval (G001 default).
289
+ */
290
+ hitlFallback?: "mask" | "deny";
291
+ }
292
+ /** The retrieval-facet sub-decision (null leg when the event had no retrieval op). */
293
+ export interface UnifiedRetrievalSubDecision {
294
+ action: "allow" | "deny" | "redact" | "escalate" | null;
295
+ /**
296
+ * For a `redact` decision: how to mask the retrieved RESPONSE. ALSO populated
297
+ * for an `escalate` decision whose rule opted into the mask fallback, so a
298
+ * never-approved retrieval can mask the response instead of being withheld.
299
+ */
300
+ redactionRules?: RecallRedactionRule[];
301
+ /**
302
+ * No-approval policy for an `escalate` decision: 'mask' → a never-approved
303
+ * request falls back to redacting the response (redactionRules) rather than
304
+ * withholding it. Absent/'deny' → withhold on no-approval (G001 default).
305
+ */
306
+ hitlFallback?: "mask" | "deny";
307
+ }
308
+ /** The merged unified decision — top-level verdict + both facet sub-decisions. */
309
+ export interface UnifiedDecision {
310
+ /** Merged most-restrictive verdict (union vocabulary). */
311
+ decision: UnifiedDecisionValue;
312
+ /**
313
+ * Convenience: may the event proceed? True for permit/mask/redact (proceed,
314
+ * possibly transformed); false for deny/approval_required/escalate. In
315
+ * monitor mode this is forced true (observe-only) while `decision` keeps the
316
+ * would-be verdict.
317
+ */
318
+ allowed: boolean;
319
+ reason: string;
320
+ ruleId: string | null;
321
+ ruleCode: string | null;
322
+ /** Whether the runtime acts on this decision (false in monitor mode). */
323
+ enforced: boolean;
324
+ /** The agent mode the decision was evaluated under. */
325
+ agentMode: AgentMode;
326
+ action: UnifiedActionSubDecision;
327
+ retrieval: UnifiedRetrievalSubDecision;
328
+ }
329
+ declare class UnifiedRuntime {
330
+ private readonly client;
331
+ private readonly originalFetch;
332
+ private readonly resolvedApiKey;
333
+ private readonly resolvedEndpoint;
334
+ private readonly resolvedAgentId;
335
+ private readonly harnessKind;
336
+ private rules;
337
+ /** Server-authoritative agent attributes from the bundle (null until shipped). */
338
+ private agentAttributes;
339
+ /**
340
+ * Per-agent no-coverage fail-safe override from the bundle's
341
+ * agent_attributes ('open' forces permit, 'closed' forces deny — beating
342
+ * autopilot; null/absent inherits the tenant no_coverage defaults). Kept
343
+ * OUTSIDE {@link agentAttributes}: it steers uncovered resolution, not
344
+ * input.agent.* rule conditions.
345
+ */
346
+ private agentNoCoverage;
347
+ /**
348
+ * Per-session trajectory accumulators for sequence-aware rules — insertion-
349
+ * ordered Map used as an LRU (re-insert on touch, evict oldest past
350
+ * SESSION_LRU_MAX, idle-expire past SESSION_IDLE_TTL_MS). Process-lifetime
351
+ * memory: a restart legitimately starts sessions fresh-empty. NEVER cleared
352
+ * by applyBundle — rules change, history doesn't.
353
+ */
354
+ private sessions;
355
+ private noCoverage;
356
+ private bundleVersion;
357
+ private bundleEtag;
358
+ private bundleLoaded;
359
+ private bundleFetchInProgress;
360
+ private initialBundlePromise;
361
+ private refreshTimer;
362
+ private agentMode;
363
+ /** True once the backend has EXPLICITLY confirmed this agent's mode. A
364
+ * schema-defaulted/cold-start monitor is NOT confirmed — so a no-bundle deny
365
+ * still fails closed to enforce rather than trusting a permissive default. */
366
+ private agentModeConfirmed;
367
+ private telemetryBuffer;
368
+ private telemetryTimer;
369
+ private envRegistered;
370
+ private registerPromise;
371
+ constructor(apiKey?: string, endpoint?: string, agentId?: string, harnessKind?: AgentKind, timeoutMs?: number);
372
+ /**
373
+ * Evaluate one governed agent event across ALL its tagged operations and
374
+ * return the merged unified decision with both facet sub-decisions.
375
+ */
376
+ evaluate(event: UnifiedEvaluateEvent): Promise<UnifiedDecision>;
377
+ /** Read the session's PRIOR-events state; fresh/absent/expired ⇒ empty. */
378
+ private readSession;
379
+ /** Fold one decided event into its session (LRU touch + bounds enforcement). */
380
+ private foldSession;
381
+ /**
382
+ * Evaluate ONE operation against the cached rules, then apply that facet's
383
+ * uncovered semantics: action/delegation → no-coverage defaults (or
384
+ * fail-closed deny); retrieval → DEFAULT_DENY.
385
+ */
386
+ private resolveOperation;
387
+ /** Build the facet-specific {@link UnifiedEvaluationInput} for one operation. */
388
+ private buildInput;
389
+ /** Compose the merged {@link UnifiedDecision} from the per-leg results. */
390
+ private buildDecision;
391
+ private actionSub;
392
+ private retrievalSub;
393
+ /**
394
+ * Apply the agent_mode envelope and record telemetry. enforce returns the
395
+ * real verdict; monitor forces allowed=true (observe-only) while preserving
396
+ * the would-be `decision` + sub-decisions, with enforced=false.
397
+ */
398
+ private applyAgentMode;
399
+ /** The agent_mode=off short-circuit — permit everything, no telemetry. */
400
+ private offDecision;
401
+ /** Build a fail-closed deny-all decision for cold start / no-config paths. */
402
+ private denyAll;
403
+ /**
404
+ * Register an `approval_required` action with the backend HITL queue and
405
+ * stamp the returned decision id onto the action sub-decision so the caller
406
+ * can poll for resolution. Best-effort: a fetch failure leaves the decision
407
+ * `approval_required` + blocked (fail-closed) — never silently permitted.
408
+ */
409
+ private registerHitl;
410
+ /**
411
+ * Preload a bundle WITHOUT any network call (tests + warm-start). Validates
412
+ * the envelope (G003), parses each row with the SAME parseEffectiveRule the
413
+ * backend uses, and adopts agent_mode + no_coverage. A version that matches
414
+ * the loaded one is a no-op.
415
+ */
416
+ applyBundle(bundle: unknown): void;
417
+ /**
418
+ * Await the INITIAL background bundle fetch so the next {@link evaluate}
419
+ * resolves locally instead of failing closed on a cold start. Bounded and
420
+ * never throws. No-op when no backend is configured or rules are loaded.
421
+ */
422
+ awaitBundleReady(timeoutMs?: number): Promise<void>;
423
+ private fetchBundleBackground;
424
+ hasRules(): boolean;
425
+ hasClient(): boolean;
426
+ /** The resolved backend endpoint, for callers driving the HITL wait loop. */
427
+ getEndpoint(): string | undefined;
428
+ /** The resolved API key, for callers driving the HITL wait loop. */
429
+ getApiKey(): string | undefined;
430
+ getBundleVersion(): string | null;
431
+ getAgentMode(): AgentMode;
432
+ /** The bundle-shipped server-authoritative agent attributes (null until shipped). */
433
+ getAgentAttributes(): AgentEvalAttributes | null;
434
+ /** The accumulated PRIOR-events trajectory for a session (empty if none). */
435
+ getSessionState(sessionId: string): SessionEvalState;
436
+ /** Report the agent's environment once per runtime instance (fire-and-forget). */
437
+ registerEnvironment(env: AgentEnvironment): void;
438
+ /** Capture + register the host environment (the single init entry point). */
439
+ registerHostEnvironment(): void;
440
+ private bufferTelemetry;
441
+ private flushTelemetry;
442
+ private enforceTelemetryCap;
443
+ /** Drain pending registration + telemetry and await it (graceful shutdown). */
444
+ flush(): Promise<void>;
445
+ dispose(): void;
446
+ }
447
+ /**
448
+ * Claude Code hook I/O contract — types + pure builders.
449
+ *
450
+ * These shapes were VERIFIED empirically against the real `claude` CLI
451
+ * (v2.1.x), not just docs — two doc claims were wrong and are corrected here:
452
+ *
453
+ * 1. The PostToolUse INPUT field carrying the tool result is `tool_response`
454
+ * (a STRUCTURED object, e.g. Bash → `{stdout,stderr,interrupted,...}`,
455
+ * Read → `{type,file:{content,...}}`), NOT the doc's `tool_output` string.
456
+ * 2. The PostToolUse OUTPUT override `updatedToolOutput` must STRUCTURALLY
457
+ * REPLACE `tool_response` — a bare string is IGNORED for structured tools.
458
+ * So a redactor must re-emit the same shape with sensitive leaves masked.
459
+ *
460
+ * PreToolUse `permissionDecision:"deny"` blocks the tool BEFORE execution (exit
461
+ * 0 + JSON) and does so even under `--permission-mode bypassPermissions`;
462
+ * `updatedInput` rewrites the tool arguments before execution. Both verified.
463
+ *
464
+ * Everything here is PURE (no process I/O) so it is exhaustively unit-testable.
465
+ * The thin stdin/stdout plumbing lives in `./cli/hook.ts`.
466
+ */
467
+ /** Fields common to every Claude Code hook invocation's stdin JSON. */
468
+ export interface HookCommonInput {
469
+ session_id: string;
470
+ transcript_path?: string;
471
+ cwd?: string;
472
+ prompt_id?: string;
473
+ permission_mode?: string;
474
+ hook_event_name: string;
475
+ }
476
+ export interface PreToolUseInput extends HookCommonInput {
477
+ hook_event_name: "PreToolUse";
478
+ tool_name: string;
479
+ tool_input: Record<string, unknown>;
480
+ tool_use_id?: string;
481
+ }
482
+ export interface PostToolUseInput extends HookCommonInput {
483
+ hook_event_name: "PostToolUse";
484
+ tool_name: string;
485
+ tool_input: Record<string, unknown>;
486
+ /** VERIFIED: structured object (or string for string-result tools). */
487
+ tool_response: unknown;
488
+ tool_use_id?: string;
489
+ duration_ms?: number;
490
+ }
491
+ export interface SessionStartInput extends HookCommonInput {
492
+ hook_event_name: "SessionStart";
493
+ source?: string;
494
+ }
495
+ /** PreToolUse decision — allow/deny/ask, with optional arg mutation. */
496
+ export interface PreToolUseOutput {
497
+ hookSpecificOutput: {
498
+ hookEventName: "PreToolUse";
499
+ permissionDecision: "allow" | "deny" | "ask";
500
+ permissionDecisionReason?: string;
501
+ /** Replaces `tool_input` before the tool runs (arg masking). */
502
+ updatedInput?: Record<string, unknown>;
503
+ };
504
+ }
505
+ /** PostToolUse decision — REPLACE the tool result the model consumes. */
506
+ export interface PostToolUseOutput {
507
+ hookSpecificOutput: {
508
+ hookEventName: "PostToolUse";
509
+ /** Structurally replaces `tool_response` in the model's context. */
510
+ updatedToolOutput: unknown;
511
+ additionalContext?: string;
512
+ };
513
+ }
514
+ /** Format a decision reason with the VisIQ + rule-code prefix (`[VisIQ R-123] …`). */
515
+ export declare function formatReason(reason: string | undefined, ruleCode: string | null): string;
516
+ export declare function buildPreAllow(updatedInput?: Record<string, unknown>): PreToolUseOutput;
517
+ export declare function buildPreDeny(reason: string): PreToolUseOutput;
518
+ export declare function buildPreAsk(reason: string): PreToolUseOutput;
519
+ export declare function buildPostRedact(updatedToolOutput: unknown, note?: string): PostToolUseOutput;
520
+ /**
521
+ * Parse a hook's stdin JSON safely. Returns `null` on any malformed input —
522
+ * a hook must never crash Claude Code, so the caller treats null as "abstain".
523
+ */
524
+ export declare function parseHookInput(raw: string): HookCommonInput | null;
525
+ /** Sentinel written over retrieval content that is blocked/withheld post-fetch. */
526
+ export declare const WITHHELD_SENTINEL = "[VisIQ: retrieval withheld by policy]";
527
+ /**
528
+ * Withhold EVERY primitive leaf of a value (string → sentinel, number → 0,
529
+ * boolean → false), preserving object/array structure so `updatedToolOutput`
530
+ * still matches the tool_response shape (a bare string override is ignored for
531
+ * structured tools). Unlike a pattern-based blank, this leaves NO numeric or
532
+ * boolean secret behind — a full withhold must reveal nothing of any type.
533
+ */
534
+ export declare function blankContent(toolResponse: unknown): unknown;
535
+ /**
536
+ * Map an evaluated decision to a PreToolUse output, or `null` to abstain
537
+ * (tool proceeds under the user's own permission flow). Merges the action +
538
+ * retrieval facets most-restrictively.
539
+ */
540
+ export declare function mapPreDecision(decision: UnifiedDecision, toolInput: Record<string, unknown>): PreToolUseOutput | null;
541
+ /**
542
+ * Map an evaluated retrieval decision to a PostToolUse output that redacts (or
543
+ * withholds) the tool result the model consumes, or `null` to leave it intact.
544
+ */
545
+ export declare function mapPostRedaction(decision: UnifiedDecision, toolResponse: unknown): PostToolUseOutput | null;
546
+ /**
547
+ * Pure helpers to install/remove the VisIQ hooks in a Claude Code settings.json
548
+ * object. Kept free of I/O so the idempotent merge is exhaustively unit-tested;
549
+ * the CLI (`cli/main.ts`) owns reading/writing the file.
550
+ *
551
+ * The harness wires three events to ONE fast dispatcher bin:
552
+ * - SessionStart (all sources) → register + pre-warm the bundle cache
553
+ * - PreToolUse (all tools) → allow/deny/ask/arg-mask enforcement gate
554
+ * - PostToolUse (retrieval) → in-context retrieval redaction
555
+ */
556
+ /** The bin Claude Code invokes for every wired event (resolved on PATH). */
557
+ export declare const HOOK_COMMAND = "visiq-claude-code-hook";
558
+ /** Per-hook wall-clock ceiling (our hook does ≤1 bounded fetch + local eval). */
559
+ export declare const HOOK_TIMEOUT_SECONDS = 30;
560
+ /**
561
+ * PostToolUse matcher: the retrieval tools whose RESULT can carry foreign
562
+ * content worth redacting (built-ins + any MCP tool; the hook itself re-checks
563
+ * `isRetrievalTool` so a broad MCP match is safe + cheap-to-abstain).
564
+ */
565
+ export declare const RETRIEVAL_MATCHER = "WebFetch|WebSearch|Read|Grep|Glob|LSP|NotebookRead|ListMcpResourcesTool|ReadMcpResourceTool|mcp__.*";
566
+ export interface CommandHook {
567
+ type: "command";
568
+ command: string;
569
+ timeout?: number;
570
+ }
571
+ export interface MatcherGroup {
572
+ matcher?: string;
573
+ hooks?: CommandHook[];
574
+ }
575
+ export type HooksMap = Record<string, MatcherGroup[]>;
576
+ export type Settings = Record<string, unknown> & {
577
+ hooks?: HooksMap;
578
+ };
579
+ /**
580
+ * Is this matcher group one of ours? A group matches when any of its hook
581
+ * commands is EXACTLY the command being (un)installed, or contains the default
582
+ * `visiq-claude-code` marker. Threading the exact command keeps install/uninstall
583
+ * idempotent even for a custom `--command` that lacks the default marker.
584
+ */
585
+ export declare function isVisiqGroup(group: MatcherGroup, command?: string): boolean;
586
+ /**
587
+ * Merge the VisIQ hooks into a settings object. Idempotent: any prior VisIQ
588
+ * groups are dropped first, so running install repeatedly yields ONE set of
589
+ * VisIQ groups and never disturbs the user's own hooks. Returns a new object.
590
+ */
591
+ export declare function installHooks(settings: Settings, command?: string): Settings;
592
+ /**
593
+ * Remove every VisIQ hook group from a settings object (uninstall). Leaves the
594
+ * user's own hooks intact; prunes now-empty event arrays. Returns a new object.
595
+ */
596
+ export declare function uninstallHooks(settings: Settings, command?: string): Settings;
597
+ /** Are the VisIQ hooks currently installed in this settings object? */
598
+ export declare function hooksInstalled(settings: Settings): boolean;
599
+ export declare function handlePreToolUse(input: PreToolUseInput, config: VisiqConfig): Promise<PreToolUseOutput | null>;
600
+ export declare function handlePostToolUse(input: PostToolUseInput, config: VisiqConfig): Promise<PostToolUseOutput | null>;
601
+ export declare function handleSessionStart(input: SessionStartInput, config: VisiqConfig): Promise<null>;
602
+ /** The agent-kind label this harness registers under (existing enum value). */
603
+ export declare const HARNESS_KIND: "cli_harness";
604
+ /** Construct a runtime for a resolved config. */
605
+ export declare function createRuntime(config: VisiqConfig): UnifiedRuntime;
606
+ /**
607
+ * Build the `evaluate()` event for a tool call. Pure.
608
+ */
609
+ export declare function buildEvaluateEvent(opts: {
610
+ operations: Operation[];
611
+ agentId: string;
612
+ toolName: string;
613
+ toolInput: Record<string, unknown>;
614
+ sessionId?: string;
615
+ cwd?: string;
616
+ toolUseId?: string;
617
+ query?: string;
618
+ }): UnifiedEvaluateEvent;
619
+ /**
620
+ * Warm a runtime's local bundle so `evaluate()` runs offline + never spuriously
621
+ * cold-start denies. Fallback ladder (most→least preferred):
622
+ * 1. fresh disk cache → applyBundle (fast, no network);
623
+ * 2. stale/absent → raw fetch → applyBundle + persist;
624
+ * 3. fetch failed but a STALE cache exists → applyBundle(stale) (a stale
625
+ * bundle is strictly better than a cold-start deny flake);
626
+ * 4. nothing on disk + fetch failed → awaitBundleReady (last resort; may
627
+ * cold-start → fail-closed deny in enforce, which is the correct posture).
628
+ * Best-effort; never throws.
629
+ */
630
+ export declare function warmFromCache(runtime: UnifiedRuntime, config: VisiqConfig, ttlMs?: number): Promise<void>;
631
+ /** Drain telemetry + registration then release timers. Bounded; never throws. */
632
+ export declare function finishRuntime(runtime: UnifiedRuntime): Promise<void>;
633
+ /**
634
+ * On-disk rule-bundle cache + raw bundle fetch + per-session register marker.
635
+ *
636
+ * WHY a disk cache: unlike the OpenClaw gateway (one long-lived process holding
637
+ * a warm `UnifiedRuntime`), every Claude Code hook is a SHORT-LIVED subprocess.
638
+ * Without a warm bundle, `UnifiedRuntime.evaluate()` cold-starts and — in
639
+ * enforce mode — fail-closed DENIES every tool call (G001), which would brick
640
+ * the agent on the first call and turn any slow/blipped bundle fetch into a
641
+ * flake. So SessionStart fetches the signed bundle once and persists it here;
642
+ * each tool hook `applyBundle()`s it for a LOCAL, offline-capable evaluation.
643
+ *
644
+ * The cache stores the exact `GET /rules/bundle` body — the shape
645
+ * `UnifiedRuntime.applyBundle(body)` accepts verbatim.
646
+ */
647
+ /** Default max age before a cached bundle is considered stale + refreshed. */
648
+ export declare const DEFAULT_BUNDLE_TTL_MS = 30000;
649
+ export interface CachedBundle {
650
+ fetchedAt: number;
651
+ version: string | null;
652
+ body: unknown;
653
+ }
654
+ /** Filesystem-safe form of an agent/session id (never used for auth). */
655
+ export declare function sanitizeId(id: string): string;
656
+ /** Is a cache entry younger than `ttlMs`? Pure. */
657
+ export declare function isFresh(entry: CachedBundle, ttlMs: number, now: number): boolean;
658
+ /** Read the cached bundle for an agent, or null if absent/corrupt. */
659
+ export declare function readBundleCache(agentId: string): Promise<CachedBundle | null>;
660
+ /** Persist a fetched bundle body for an agent. Best-effort; never throws. */
661
+ export declare function writeBundleCache(agentId: string, body: unknown, version: string | null): Promise<void>;
662
+ /**
663
+ * Fetch the signed rule bundle directly from the backend (the same
664
+ * `GET {endpoint}/rules/bundle?agent_id=` route `UnifiedRuntime` uses). Returns
665
+ * `{ body, version }` on success, null on any failure. Best-effort; never throws.
666
+ */
667
+ export declare function fetchBundle(endpoint: string, apiKey: string, agentId: string): Promise<{
668
+ body: unknown;
669
+ version: string | null;
670
+ } | null>;
671
+ /** Has this session already completed the register handshake? */
672
+ export declare function isSessionRegistered(sessionId: string): Promise<boolean>;
673
+ /** Mark this session as registered so later tool hooks skip re-registration. */
674
+ export declare function markSessionRegistered(sessionId: string): Promise<void>;
675
+
676
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{a as E,b as T,c as h,d as C,e as u,f as x,g as S}from"./chunk-6DMSMBWM.js";import{a as n,b as s,c as a,d as l,e as P,f as U,g as F,h as b,i as q,j as K,k as m,l as p,m as f,n as d,o as I,p as L,q as k,r as v,s as y,t as A,u as B,v as M}from"./chunk-K5THPWCB.js";import{a as e,b as o,c as t,d as r,e as i,f as c,g as R,h as g,i as H,j as O,k as _,l as D,m as N}from"./chunk-3GWLCI47.js";export{c as DEFAULT_BUNDLE_TTL_MS,I as HARNESS_KIND,E as HOOK_COMMAND,T as HOOK_TIMEOUT_SECONDS,h as RETRIEVAL_MATCHER,m as WITHHELD_SENTINEL,n as bareToolName,p as blankContent,k as buildEvaluateEvent,q as buildPostRedact,U as buildPreAllow,b as buildPreAsk,F as buildPreDeny,o as configFilePath,L as createRuntime,t as envConfig,l as extractQuery,_ as fetchBundle,y as finishRuntime,P as formatReason,B as handlePostToolUse,A as handlePreToolUse,M as handleSessionStart,S as hooksInstalled,u as installHooks,g as isFresh,s as isRetrievalTool,D as isSessionRegistered,C as isVisiqGroup,i as loadConfig,d as mapPostRedaction,f as mapPreDecision,N as markSessionRegistered,r as mergeConfig,K as parseHookInput,H as readBundleCache,R as sanitizeId,a as toolOperations,x as uninstallHooks,e as visiqHomeDir,v as warmFromCache,O as writeBundleCache};
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@visiq/claude-code-harness",
3
+ "version": "0.1.0",
4
+ "description": "VisIQ governance harness for the Claude Code CLI — action monitor/block + retrieval monitor/block/redact via Claude Code hooks",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "visiq-claude-code": "./dist/cli/main.js",
10
+ "visiq-claude-code-hook": "./dist/cli/hook.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "dependencies": {
19
+ "zod": "^3.23.8"
20
+ },
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "license": "MIT",
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/VISIQ-LABS/xy.git",
35
+ "directory": "packages/visiq-claude-code-harness"
36
+ },
37
+ "keywords": [
38
+ "visiq",
39
+ "claude-code",
40
+ "claude",
41
+ "anthropic",
42
+ "hooks",
43
+ "ai-governance",
44
+ "allow",
45
+ "recall",
46
+ "record"
47
+ ],
48
+ "peerDependencies": {
49
+ "@langchain/core": ">=0.3.0",
50
+ "llamaindex": ">=0.11.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "@langchain/core": {
54
+ "optional": true
55
+ },
56
+ "llamaindex": {
57
+ "optional": true
58
+ }
59
+ }
60
+ }