thumbgate 1.34.3 → 1.37.1
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/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
- package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
- package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
- package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
- package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/CONTRIBUTING.md +95 -0
- package/README.md +195 -632
- package/THIRD_PARTY_NOTICES.md +89 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +8 -0
- package/adapters/future-agi/FUTURE_AGI.md +23 -0
- package/adapters/future-agi/config.toml +3 -0
- package/adapters/future-agi/future-agi-bridge.js +9 -0
- package/adapters/future-agi/opencode.json +8 -0
- package/adapters/herdr/herdr-plugin.toml +18 -0
- package/adapters/mcp/server-stdio.js +238 -25
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/workos/WORKOS.md +52 -0
- package/bin/cli.js +373 -5
- package/bin/futureagi-bridge +9 -0
- package/config/gate-templates.json +653 -4
- package/config/gates/actor-critic-audit.json +34 -0
- package/config/gates/default.json +21 -2
- package/config/gates/five-walls-governance.json +34 -0
- package/config/gates/future-agi-guardrails.json +34 -0
- package/config/gates/radware-threat-defense-2026.json +61 -0
- package/config/gates/simatree-data-governance.json +33 -0
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +10 -1
- package/config/model-candidates.json +382 -24
- package/config/model-tiers.json +18 -0
- package/config/post-deploy-marketing-pages.json +10 -0
- package/config/progressive/01-wire-only.json +11 -0
- package/config/progressive/02-dashboard-empty-ok.json +10 -0
- package/config/progressive/03-one-lesson.json +10 -0
- package/config/progressive/04-warn-fires.json +11 -0
- package/config/progressive/05-strict-optional.json +11 -0
- package/config/progressive/README.md +15 -0
- package/config/schemas/broker-execution-receipt.schema.json +139 -0
- package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
- package/conformance/provider-attestation/vectors.json +320 -0
- package/docs/specs/provider-execution-attestation-v1.md +69 -0
- package/openapi/openapi.yaml +15 -0
- package/package.json +401 -147
- package/public/about.html +2 -2
- package/public/ai-malpractice-prevention.html +7 -7
- package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
- package/public/blog/a-receipt-is-not-world-state.html +388 -0
- package/public/blog/git-at-agent-scale.html +374 -0
- package/public/blog/no-llm-in-the-gate.html +133 -0
- package/public/blog.html +80 -0
- package/public/case-studies.html +16 -1
- package/public/compare.html +28 -0
- package/public/diagnostic.html +216 -7
- package/public/docs/connectors.html +39 -0
- package/public/federal.html +2 -2
- package/public/founders.html +639 -0
- package/public/index.html +87 -9
- package/public/install.html +8 -8
- package/public/learn.html +39 -0
- package/public/numbers.html +2 -2
- package/public/peter.html +310 -0
- package/public/platform-partners.html +119 -0
- package/public/pricing.html +24 -3
- package/public/privacy.html +117 -0
- package/public/pro.html +17 -0
- package/public/support.html +62 -0
- package/public/terms.html +130 -0
- package/public/third-party-notices.html +95 -0
- package/public/yt.html +351 -0
- package/scripts/action-receipts.js +133 -3
- package/scripts/adaptive-governance-arena.js +349 -0
- package/scripts/admin-override.js +205 -0
- package/scripts/agent-action-inventory.js +869 -0
- package/scripts/agent-audit-trace.js +42 -2
- package/scripts/agent-egress-policy.js +1117 -0
- package/scripts/agent-memory-lifecycle.js +141 -2
- package/scripts/agent-operations-planner.js +441 -1
- package/scripts/agent-readiness.js +68 -0
- package/scripts/agent-security-central.js +647 -0
- package/scripts/allowlist-bridge-honesty.js +417 -0
- package/scripts/async-job-runner.js +102 -11
- package/scripts/audit-trail.js +212 -0
- package/scripts/auto-promote-gates.js +178 -27
- package/scripts/billing.js +1 -1
- package/scripts/broker-execution-receipts.js +719 -0
- package/scripts/budget-aware-gates-proof.js +423 -0
- package/scripts/claude-feedback-sync.js +29 -3
- package/scripts/claw-harness-production.js +237 -0
- package/scripts/cli-progress.js +111 -0
- package/scripts/cli-schema.js +163 -1
- package/scripts/codex-runbook-flywheel.js +318 -0
- package/scripts/context-footprint.js +186 -0
- package/scripts/contextfs.js +143 -61
- package/scripts/dashboard-limits.js +27 -0
- package/scripts/dashboard.js +279 -9
- package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
- package/scripts/docker-sandbox-planner.js +18 -0
- package/scripts/double-blind-eval-protocol.js +252 -0
- package/scripts/edotenv-rl-gateway.js +259 -0
- package/scripts/ensure-production-search-corpus.js +162 -0
- package/scripts/eval-holdout.js +311 -0
- package/scripts/feedback-aggregate.js +21 -2
- package/scripts/feedback-loop.js +87 -5
- package/scripts/feedback-quality.js +9 -0
- package/scripts/file-ledger-lock.js +4 -1
- package/scripts/financial-control-plane.js +41 -1
- package/scripts/find-dormant-requires.js +118 -0
- package/scripts/fs-utils.js +84 -8
- package/scripts/gate-stats.js +2 -2
- package/scripts/gates-engine.js +859 -58
- package/scripts/generate-case-study-outreach.js +24 -15
- package/scripts/git-at-scale.js +628 -0
- package/scripts/governance-conflict-audit.js +1650 -0
- package/scripts/governance-difficulty-curriculum.js +328 -0
- package/scripts/graphrag-retrieval.js +275 -0
- package/scripts/gurobi-optimizer.js +324 -0
- package/scripts/gurobi_optimizer.py +485 -0
- package/scripts/harness-selector.js +82 -1
- package/scripts/hidden-entry-points.js +284 -0
- package/scripts/human-escalation.js +199 -1
- package/scripts/hybrid-feedback-context.js +152 -19
- package/scripts/intent-governed-execution.js +602 -0
- package/scripts/intervention-policy.js +123 -20
- package/scripts/jit-harness-compose.js +628 -0
- package/scripts/jsonl-watcher.js +10 -0
- package/scripts/lesson-embedding-index.js +95 -12
- package/scripts/lesson-retrieval.js +105 -19
- package/scripts/local-model-profile.js +19 -2
- package/scripts/mailer/resend-mailer.js +1 -1
- package/scripts/matryoshka-embedding.js +235 -0
- package/scripts/mcp-oauth.js +42 -4
- package/scripts/mcp-session-handles.js +1016 -0
- package/scripts/mcp-wiring-doctor.js +314 -0
- package/scripts/memory-firewall.js +115 -2
- package/scripts/memory-scope-readiness.js +299 -0
- package/scripts/memory-vs-rag-route.js +161 -0
- package/scripts/model-tier-router.js +148 -21
- package/scripts/nvidia-specdecode-al-doctor.js +536 -0
- package/scripts/openui-catalog-compose-honesty.js +593 -0
- package/scripts/operational-integrity.js +19 -1
- package/scripts/override-audit.js +213 -0
- package/scripts/package-manager-honesty-doctor.js +458 -0
- package/scripts/pr-manager.js +63 -1
- package/scripts/prove-herdr-adapter.js +52 -0
- package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
- package/scripts/prove-workos.js +73 -0
- package/scripts/provider-attestation-conformance.js +192 -0
- package/scripts/provider-receipt-contract.js +136 -0
- package/scripts/qwen38-max-cost-optimizer.js +401 -0
- package/scripts/radware-threat-defense.js +280 -0
- package/scripts/rag-embedding-identity.js +221 -0
- package/scripts/rag-precision-guardrails.js +112 -2
- package/scripts/remote-feedback-capture.js +159 -0
- package/scripts/research-agent-harness.js +256 -0
- package/scripts/rsi-safety-hillclimb.js +200 -0
- package/scripts/rule-sprawl.js +188 -0
- package/scripts/schedule-manager.js +147 -0
- package/scripts/self-heal.js +8 -0
- package/scripts/session-lease.js +415 -0
- package/scripts/simatree-data-governance.js +347 -0
- package/scripts/slo-alert-engine.js +172 -7
- package/scripts/solver-parity.js +539 -0
- package/scripts/stealth-memory-injection-gate.js +333 -0
- package/scripts/switchyard-router.js +366 -0
- package/scripts/telemetry-analytics.js +84 -27
- package/scripts/temporal-decay-weighting.js +138 -0
- package/scripts/test-all.js +165 -0
- package/scripts/token-savings.js +42 -0
- package/scripts/tool-kpi-tracker.js +108 -5
- package/scripts/tool-registry.js +193 -5
- package/scripts/universal-claim-evaluator.js +14 -2
- package/scripts/vector-store.js +279 -9
- package/scripts/workflow-notebook.js +391 -0
- package/scripts/workflow-sentinel.js +111 -12
- package/scripts/workos-production-guard.js +260 -0
- package/scripts/workspace-search-route.js +515 -0
- package/server.json +2 -2
- package/src/agent-identity-boundary.js +76 -0
- package/src/agent-retrieval-cache.js +155 -0
- package/src/alert-noise-ledger.js +502 -0
- package/src/api/server.js +802 -185
- package/src/git-fast-cache.js +220 -0
- package/src/git-wal-sync.js +156 -0
- package/src/hash-anchored-edit.js +82 -0
- package/src/hermes-platform-protocol.js +475 -0
- package/src/hermes-sync-plane.js +241 -0
- package/src/index.js +30 -1
- package/src/iso42001-compliance-guard.js +97 -0
- package/src/latency-budget.js +244 -0
- package/src/mcp-writeguard.js +316 -0
- package/src/miminions-adapter.js +106 -0
- package/src/pipeline-compass.js +104 -0
- package/src/ppl-alert-pipeline.js +284 -0
- package/src/rendezvous-router.js +90 -0
- package/src/security-questionnaire.js +195 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Gurobi Optimization Engine for ThumbGate
|
|
4
|
+
|
|
5
|
+
Formulates and solves MILP (Mixed-Integer Linear Programming) problems for:
|
|
6
|
+
1. Model Tier & Provider Routing under Cost & Latency Constraints
|
|
7
|
+
2. Active Prevention Rule Knapsack Selection under Latency & Token Budgets
|
|
8
|
+
|
|
9
|
+
Uses gurobipy (v13+) with deterministic fallback logic.
|
|
10
|
+
|
|
11
|
+
Security: --input accepts only inline JSON (no filesystem open). The Node bridge
|
|
12
|
+
always passes JSON.stringify(payload); agentic path injection is impossible.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Any, Dict, List
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import gurobipy as gp
|
|
24
|
+
from gurobipy import GRB
|
|
25
|
+
GUROBI_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
gp = None # type: ignore
|
|
28
|
+
GRB = None # type: ignore
|
|
29
|
+
GUROBI_AVAILABLE = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def extract_iis(model: Any) -> List[str]:
|
|
33
|
+
"""Return IIS constraint names. Empty when computeIIS is unavailable."""
|
|
34
|
+
if not GUROBI_AVAILABLE or model is None:
|
|
35
|
+
return []
|
|
36
|
+
try:
|
|
37
|
+
model.computeIIS()
|
|
38
|
+
return [c.ConstrName for c in model.getConstrs() if bool(getattr(c, "IISConstr", 0))]
|
|
39
|
+
except Exception: # noqa: BLE001
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def stamp_receipt(result: Dict[str, Any], *, model: Any = None) -> Dict[str, Any]:
|
|
44
|
+
"""Pulse steal: proof, not plausible. Never mark a heuristic as certified."""
|
|
45
|
+
out = dict(result)
|
|
46
|
+
solver = str(out.get("solver") or "")
|
|
47
|
+
status = str(out.get("status") or "")
|
|
48
|
+
certified = solver == "gurobi" and status == "OPTIMAL" and out.get("success") is True
|
|
49
|
+
out["certified"] = certified
|
|
50
|
+
out["capturedRevenueUsd"] = 0
|
|
51
|
+
if certified:
|
|
52
|
+
out.setdefault("proof", "gurobi-optimal")
|
|
53
|
+
if model is not None:
|
|
54
|
+
try:
|
|
55
|
+
out["objBound"] = float(model.ObjBound)
|
|
56
|
+
except Exception: # noqa: BLE001
|
|
57
|
+
pass
|
|
58
|
+
try:
|
|
59
|
+
out["mipGap"] = float(model.MIPGap)
|
|
60
|
+
except Exception: # noqa: BLE001
|
|
61
|
+
pass
|
|
62
|
+
elif status.startswith("INFEASIBLE"):
|
|
63
|
+
out.setdefault("proof", "infeasible-iis")
|
|
64
|
+
if "iis" not in out and model is not None:
|
|
65
|
+
out["iis"] = extract_iis(model)
|
|
66
|
+
elif "heuristic" in solver or solver.endswith("-fallback") or "fallback" in solver:
|
|
67
|
+
out.setdefault("proof", "heuristic")
|
|
68
|
+
else:
|
|
69
|
+
out.setdefault("proof", "unproven")
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load_input_payload(raw: str) -> Dict[str, Any]:
|
|
74
|
+
"""Parse --input as inline JSON only (no filesystem access)."""
|
|
75
|
+
text = (raw or "").strip()
|
|
76
|
+
if not text:
|
|
77
|
+
raise ValueError("empty input")
|
|
78
|
+
if not (text.startswith("{") or text.startswith("[")):
|
|
79
|
+
raise ValueError(
|
|
80
|
+
"input must be an inline JSON object (filesystem paths are not accepted)"
|
|
81
|
+
)
|
|
82
|
+
parsed = json.loads(text)
|
|
83
|
+
if not isinstance(parsed, dict):
|
|
84
|
+
raise ValueError("inline JSON input must be an object")
|
|
85
|
+
return parsed
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def create_gurobi_model(name: str):
|
|
89
|
+
"""Creates a Gurobi model with OutputFlag=0 to prevent stdout license spam."""
|
|
90
|
+
env = gp.Env(empty=True)
|
|
91
|
+
env.setParam("OutputFlag", 0)
|
|
92
|
+
env.start()
|
|
93
|
+
return gp.Model(name, env=env)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def solve_model_routing(
|
|
97
|
+
candidates: List[Dict[str, Any]],
|
|
98
|
+
max_budget_usd: float,
|
|
99
|
+
max_latency_ms: float,
|
|
100
|
+
) -> Dict[str, Any]:
|
|
101
|
+
"""Select one model candidate maximizing score under budget and latency."""
|
|
102
|
+
if not candidates:
|
|
103
|
+
return {"success": False, "selected": None, "reason": "No candidates provided"}
|
|
104
|
+
|
|
105
|
+
if not GUROBI_AVAILABLE:
|
|
106
|
+
valid = [
|
|
107
|
+
c
|
|
108
|
+
for c in candidates
|
|
109
|
+
if c.get("cost", 0) <= max_budget_usd and c.get("latency_ms", 0) <= max_latency_ms
|
|
110
|
+
]
|
|
111
|
+
if not valid:
|
|
112
|
+
return stamp_receipt(
|
|
113
|
+
{
|
|
114
|
+
"success": False,
|
|
115
|
+
"selected": None,
|
|
116
|
+
"solver": "heuristic-fallback",
|
|
117
|
+
"objective": None,
|
|
118
|
+
"status": "INFEASIBLE",
|
|
119
|
+
"iis": ["BudgetLimit", "LatencyLimit"],
|
|
120
|
+
"reason": "no candidate satisfies budget and latency",
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
best = max(valid, key=lambda c: c.get("score", 0))
|
|
124
|
+
return stamp_receipt(
|
|
125
|
+
{
|
|
126
|
+
"success": True,
|
|
127
|
+
"selected": best["id"],
|
|
128
|
+
"solver": "heuristic-fallback",
|
|
129
|
+
"objective": float(best.get("score", 0)),
|
|
130
|
+
"status": "HEURISTIC",
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
model = create_gurobi_model("ModelRouting")
|
|
136
|
+
x = {}
|
|
137
|
+
for i, cand in enumerate(candidates):
|
|
138
|
+
cid = cand.get("id", f"c_{i}")
|
|
139
|
+
x[cid] = model.addVar(vtype=GRB.BINARY, name=f"x_{cid}")
|
|
140
|
+
model.update()
|
|
141
|
+
model.setObjective(
|
|
142
|
+
gp.quicksum(
|
|
143
|
+
x[cand.get("id", f"c_{i}")] * cand.get("score", 0.0)
|
|
144
|
+
for i, cand in enumerate(candidates)
|
|
145
|
+
),
|
|
146
|
+
GRB.MAXIMIZE,
|
|
147
|
+
)
|
|
148
|
+
model.addConstr(
|
|
149
|
+
gp.quicksum(x[cand.get("id", f"c_{i}")] for i, cand in enumerate(candidates)) == 1,
|
|
150
|
+
"SelectOne",
|
|
151
|
+
)
|
|
152
|
+
model.addConstr(
|
|
153
|
+
gp.quicksum(
|
|
154
|
+
x[cand.get("id", f"c_{i}")] * cand.get("cost", 0.0)
|
|
155
|
+
for i, cand in enumerate(candidates)
|
|
156
|
+
)
|
|
157
|
+
<= max_budget_usd,
|
|
158
|
+
"BudgetLimit",
|
|
159
|
+
)
|
|
160
|
+
model.addConstr(
|
|
161
|
+
gp.quicksum(
|
|
162
|
+
x[cand.get("id", f"c_{i}")] * cand.get("latency_ms", 0.0)
|
|
163
|
+
for i, cand in enumerate(candidates)
|
|
164
|
+
)
|
|
165
|
+
<= max_latency_ms,
|
|
166
|
+
"LatencyLimit",
|
|
167
|
+
)
|
|
168
|
+
model.optimize()
|
|
169
|
+
if model.status == GRB.OPTIMAL:
|
|
170
|
+
selected_id = next((cid for cid, var in x.items() if var.X > 0.5), None)
|
|
171
|
+
return stamp_receipt(
|
|
172
|
+
{
|
|
173
|
+
"success": True,
|
|
174
|
+
"selected": selected_id,
|
|
175
|
+
"solver": "gurobi",
|
|
176
|
+
"objective": float(model.ObjVal),
|
|
177
|
+
"status": "OPTIMAL",
|
|
178
|
+
},
|
|
179
|
+
model=model,
|
|
180
|
+
)
|
|
181
|
+
iis = extract_iis(model)
|
|
182
|
+
return stamp_receipt(
|
|
183
|
+
{
|
|
184
|
+
"success": False,
|
|
185
|
+
"selected": None,
|
|
186
|
+
"solver": "gurobi",
|
|
187
|
+
"objective": None,
|
|
188
|
+
"status": "INFEASIBLE",
|
|
189
|
+
"iis": iis,
|
|
190
|
+
"reason": "no feasible candidate under budget and latency",
|
|
191
|
+
},
|
|
192
|
+
model=model,
|
|
193
|
+
)
|
|
194
|
+
except Exception as exc: # noqa: BLE001 — fail-open to heuristic for CI
|
|
195
|
+
valid = [
|
|
196
|
+
c
|
|
197
|
+
for c in candidates
|
|
198
|
+
if c.get("cost", 0) <= max_budget_usd and c.get("latency_ms", 0) <= max_latency_ms
|
|
199
|
+
]
|
|
200
|
+
if not valid:
|
|
201
|
+
return stamp_receipt(
|
|
202
|
+
{
|
|
203
|
+
"success": False,
|
|
204
|
+
"selected": None,
|
|
205
|
+
"solver": f"gurobi-error-fallback: {exc}",
|
|
206
|
+
"objective": None,
|
|
207
|
+
"status": "INFEASIBLE",
|
|
208
|
+
"reason": str(exc),
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
best = max(valid, key=lambda c: c.get("score", 0))
|
|
212
|
+
return stamp_receipt(
|
|
213
|
+
{
|
|
214
|
+
"success": True,
|
|
215
|
+
"selected": best["id"],
|
|
216
|
+
"solver": f"gurobi-error-fallback: {exc}",
|
|
217
|
+
"objective": float(best.get("score", 0)),
|
|
218
|
+
"status": "HEURISTIC",
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def fallback_rule_selection(
|
|
224
|
+
rules: List[Dict[str, Any]],
|
|
225
|
+
max_eval_time_ms: float,
|
|
226
|
+
max_token_footprint: int,
|
|
227
|
+
) -> Dict[str, Any]:
|
|
228
|
+
sorted_rules = sorted(
|
|
229
|
+
rules,
|
|
230
|
+
key=lambda r: float(r.get("risk_mitigation", 0))
|
|
231
|
+
/ max(float(r.get("eval_time_ms", 0.1)), 0.1),
|
|
232
|
+
reverse=True,
|
|
233
|
+
)
|
|
234
|
+
selected = []
|
|
235
|
+
total_time = 0.0
|
|
236
|
+
total_tokens = 0
|
|
237
|
+
total_risk = 0.0
|
|
238
|
+
for rule in sorted_rules:
|
|
239
|
+
r_time = float(rule.get("eval_time_ms", 0.0))
|
|
240
|
+
r_tokens = int(rule.get("token_footprint", 0))
|
|
241
|
+
if total_time + r_time <= max_eval_time_ms and total_tokens + r_tokens <= max_token_footprint:
|
|
242
|
+
selected.append(rule.get("id"))
|
|
243
|
+
total_time += r_time
|
|
244
|
+
total_tokens += r_tokens
|
|
245
|
+
total_risk += float(rule.get("risk_mitigation", 0.0))
|
|
246
|
+
return stamp_receipt(
|
|
247
|
+
{
|
|
248
|
+
"success": True,
|
|
249
|
+
"selected_rules": selected,
|
|
250
|
+
"solver": "heuristic-knapsack",
|
|
251
|
+
"objective": total_risk,
|
|
252
|
+
"used_time_ms": total_time,
|
|
253
|
+
"used_tokens": total_tokens,
|
|
254
|
+
"status": "HEURISTIC",
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def solve_rule_selection(
|
|
260
|
+
rules: List[Dict[str, Any]],
|
|
261
|
+
max_eval_time_ms: float,
|
|
262
|
+
max_token_footprint: int,
|
|
263
|
+
) -> Dict[str, Any]:
|
|
264
|
+
"""0-1 knapsack over prevention rules under time and token budgets."""
|
|
265
|
+
if not rules:
|
|
266
|
+
return {"success": False, "selected_rules": [], "reason": "No rules provided"}
|
|
267
|
+
|
|
268
|
+
if not GUROBI_AVAILABLE:
|
|
269
|
+
return fallback_rule_selection(rules, max_eval_time_ms, max_token_footprint)
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
model = create_gurobi_model("RuleKnapsackSelection")
|
|
273
|
+
y = {}
|
|
274
|
+
for i, rule in enumerate(rules):
|
|
275
|
+
rid = rule.get("id", f"r_{i}")
|
|
276
|
+
y[rid] = model.addVar(vtype=GRB.BINARY, name=f"rule_{rid}")
|
|
277
|
+
model.setObjective(
|
|
278
|
+
gp.quicksum(
|
|
279
|
+
y[rule.get("id", f"r_{i}")] * rule.get("risk_mitigation", 0.0)
|
|
280
|
+
for i, rule in enumerate(rules)
|
|
281
|
+
),
|
|
282
|
+
GRB.MAXIMIZE,
|
|
283
|
+
)
|
|
284
|
+
model.addConstr(
|
|
285
|
+
gp.quicksum(
|
|
286
|
+
y[rule.get("id", f"r_{i}")] * rule.get("eval_time_ms", 0.0)
|
|
287
|
+
for i, rule in enumerate(rules)
|
|
288
|
+
)
|
|
289
|
+
<= max_eval_time_ms,
|
|
290
|
+
"EvalTimeLimit",
|
|
291
|
+
)
|
|
292
|
+
model.addConstr(
|
|
293
|
+
gp.quicksum(
|
|
294
|
+
y[rule.get("id", f"r_{i}")] * rule.get("token_footprint", 0)
|
|
295
|
+
for i, rule in enumerate(rules)
|
|
296
|
+
)
|
|
297
|
+
<= max_token_footprint,
|
|
298
|
+
"TokenFootprintLimit",
|
|
299
|
+
)
|
|
300
|
+
model.optimize()
|
|
301
|
+
if model.status == GRB.OPTIMAL:
|
|
302
|
+
selected_ids = [rid for rid, var in y.items() if var.X > 0.5]
|
|
303
|
+
used_time = sum(
|
|
304
|
+
r.get("eval_time_ms", 0.0) for r in rules if r.get("id") in selected_ids
|
|
305
|
+
)
|
|
306
|
+
used_tokens = sum(
|
|
307
|
+
r.get("token_footprint", 0) for r in rules if r.get("id") in selected_ids
|
|
308
|
+
)
|
|
309
|
+
return stamp_receipt(
|
|
310
|
+
{
|
|
311
|
+
"success": True,
|
|
312
|
+
"selected_rules": selected_ids,
|
|
313
|
+
"solver": "gurobi",
|
|
314
|
+
"objective": float(model.ObjVal),
|
|
315
|
+
"used_time_ms": used_time,
|
|
316
|
+
"used_tokens": used_tokens,
|
|
317
|
+
"status": "OPTIMAL",
|
|
318
|
+
},
|
|
319
|
+
model=model,
|
|
320
|
+
)
|
|
321
|
+
return stamp_receipt(
|
|
322
|
+
{
|
|
323
|
+
"success": False,
|
|
324
|
+
"selected_rules": [],
|
|
325
|
+
"solver": "gurobi",
|
|
326
|
+
"objective": None,
|
|
327
|
+
"status": "INFEASIBLE",
|
|
328
|
+
"iis": extract_iis(model),
|
|
329
|
+
"reason": "rule knapsack infeasible",
|
|
330
|
+
},
|
|
331
|
+
model=model,
|
|
332
|
+
)
|
|
333
|
+
except Exception as exc: # noqa: BLE001
|
|
334
|
+
res = fallback_rule_selection(rules, max_eval_time_ms, max_token_footprint)
|
|
335
|
+
res["solver"] = f"gurobi-error-fallback: {exc}"
|
|
336
|
+
return stamp_receipt(res)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def solve_fleet_dispatch(
|
|
340
|
+
jobs: List[Dict[str, Any]],
|
|
341
|
+
max_ram_mb: int = 32768,
|
|
342
|
+
max_cpu_cores: int = 10,
|
|
343
|
+
max_concurrency: int = 4,
|
|
344
|
+
) -> Dict[str, Any]:
|
|
345
|
+
"""MILP Job-Shop / Machine-Scheduling to maximize dispatched priority while preventing starvation."""
|
|
346
|
+
if not jobs:
|
|
347
|
+
return {"success": False, "selected_jobs": [], "reason": "No jobs provided"}
|
|
348
|
+
|
|
349
|
+
if not GUROBI_AVAILABLE:
|
|
350
|
+
valid_jobs = []
|
|
351
|
+
cur_ram, cur_cpu = 0, 0
|
|
352
|
+
for j in sorted(jobs, key=lambda x: x.get("priority", 1), reverse=True):
|
|
353
|
+
r = j.get("ram_mb", 1024)
|
|
354
|
+
c = j.get("cpu_cores", 1)
|
|
355
|
+
if len(valid_jobs) < max_concurrency and cur_ram + r <= max_ram_mb and cur_cpu + c <= max_cpu_cores:
|
|
356
|
+
valid_jobs.append(j.get("id"))
|
|
357
|
+
cur_ram += r
|
|
358
|
+
cur_cpu += c
|
|
359
|
+
return {
|
|
360
|
+
"success": True,
|
|
361
|
+
"selected_jobs": valid_jobs,
|
|
362
|
+
"solver": "heuristic-fallback",
|
|
363
|
+
"allocated_ram_mb": cur_ram,
|
|
364
|
+
"allocated_cpu_cores": cur_cpu,
|
|
365
|
+
"status": "HEURISTIC",
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
model = create_gurobi_model("FleetDispatch")
|
|
370
|
+
z = {}
|
|
371
|
+
for i, j in enumerate(jobs):
|
|
372
|
+
jid = j.get("id", f"j_{i}")
|
|
373
|
+
z[jid] = model.addVar(vtype=GRB.BINARY, name=f"job_{jid}")
|
|
374
|
+
model.setObjective(
|
|
375
|
+
gp.quicksum(
|
|
376
|
+
z[j.get("id", f"j_{i}")] * j.get("priority", 1.0)
|
|
377
|
+
for i, j in enumerate(jobs)
|
|
378
|
+
),
|
|
379
|
+
GRB.MAXIMIZE,
|
|
380
|
+
)
|
|
381
|
+
model.addConstr(
|
|
382
|
+
gp.quicksum(z[j.get("id", f"j_{i}")] for i, j in enumerate(jobs)) <= max_concurrency,
|
|
383
|
+
"MaxConcurrency",
|
|
384
|
+
)
|
|
385
|
+
model.addConstr(
|
|
386
|
+
gp.quicksum(
|
|
387
|
+
z[j.get("id", f"j_{i}")] * j.get("ram_mb", 1024)
|
|
388
|
+
for i, j in enumerate(jobs)
|
|
389
|
+
)
|
|
390
|
+
<= max_ram_mb,
|
|
391
|
+
"MaxRAM",
|
|
392
|
+
)
|
|
393
|
+
model.addConstr(
|
|
394
|
+
gp.quicksum(
|
|
395
|
+
z[j.get("id", f"j_{i}")] * j.get("cpu_cores", 1)
|
|
396
|
+
for i, j in enumerate(jobs)
|
|
397
|
+
)
|
|
398
|
+
<= max_cpu_cores,
|
|
399
|
+
"MaxCPU",
|
|
400
|
+
)
|
|
401
|
+
model.optimize()
|
|
402
|
+
if model.status == GRB.OPTIMAL:
|
|
403
|
+
selected = [jid for jid, var in z.items() if var.X > 0.5]
|
|
404
|
+
used_ram = sum(j.get("ram_mb", 1024) for j in jobs if j.get("id") in selected)
|
|
405
|
+
used_cpu = sum(j.get("cpu_cores", 1) for j in jobs if j.get("id") in selected)
|
|
406
|
+
return {
|
|
407
|
+
"success": True,
|
|
408
|
+
"selected_jobs": selected,
|
|
409
|
+
"solver": "gurobi",
|
|
410
|
+
"objective": float(model.ObjVal),
|
|
411
|
+
"allocated_ram_mb": used_ram,
|
|
412
|
+
"allocated_cpu_cores": used_cpu,
|
|
413
|
+
"status": "OPTIMAL",
|
|
414
|
+
"optimality_gap": 0.0,
|
|
415
|
+
}
|
|
416
|
+
return {"success": False, "selected_jobs": [], "status": f"INFEASIBLE_{model.status}"}
|
|
417
|
+
except Exception as exc: # noqa: BLE001
|
|
418
|
+
return {"success": False, "selected_jobs": [], "error": str(exc)}
|
|
419
|
+
|
|
420
|
+
def stamp_decision_governance(result: Dict[str, Any]) -> Dict[str, Any]:
|
|
421
|
+
"""Action layer stays human-oversight. A solve is not a PreToolUse apply."""
|
|
422
|
+
body = dict(result or {})
|
|
423
|
+
solver = str(body.get("solver") or "")
|
|
424
|
+
status = str(body.get("status") or "")
|
|
425
|
+
repeatable = solver == "gurobi" and status == "OPTIMAL"
|
|
426
|
+
body["autoApply"] = False
|
|
427
|
+
body["humanOversightRequired"] = True
|
|
428
|
+
body["capturedRevenueUsd"] = 0
|
|
429
|
+
body["repeatable"] = repeatable
|
|
430
|
+
body["plausibleOnly"] = not repeatable
|
|
431
|
+
return body
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def run_mode(mode: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
435
|
+
if mode == "routing":
|
|
436
|
+
return stamp_decision_governance(
|
|
437
|
+
solve_model_routing(
|
|
438
|
+
candidates=payload.get("candidates", []),
|
|
439
|
+
max_budget_usd=float(payload.get("max_budget_usd", 1.0)),
|
|
440
|
+
max_latency_ms=float(payload.get("max_latency_ms", 5000.0)),
|
|
441
|
+
)
|
|
442
|
+
)
|
|
443
|
+
if mode == "rules":
|
|
444
|
+
return stamp_decision_governance(
|
|
445
|
+
solve_rule_selection(
|
|
446
|
+
rules=payload.get("rules", []),
|
|
447
|
+
max_eval_time_ms=float(payload.get("max_eval_time_ms", 50.0)),
|
|
448
|
+
max_token_footprint=int(payload.get("max_token_footprint", 1000)),
|
|
449
|
+
)
|
|
450
|
+
)
|
|
451
|
+
if mode == "dispatch":
|
|
452
|
+
return stamp_decision_governance(
|
|
453
|
+
solve_fleet_dispatch(
|
|
454
|
+
jobs=payload.get("jobs", []),
|
|
455
|
+
max_ram_mb=int(payload.get("max_ram_mb", 32768)),
|
|
456
|
+
max_cpu_cores=int(payload.get("max_cpu_cores", 10)),
|
|
457
|
+
max_concurrency=int(payload.get("max_concurrency", 4)),
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
return stamp_decision_governance({"success": False, "error": f"Unknown mode: {mode}"})
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def main(argv: List[str] | None = None) -> int:
|
|
464
|
+
parser = argparse.ArgumentParser(description="Gurobi Optimization Engine for ThumbGate")
|
|
465
|
+
parser.add_argument("--mode", choices=["routing", "rules", "dispatch"], required=True)
|
|
466
|
+
parser.add_argument(
|
|
467
|
+
"--input",
|
|
468
|
+
required=True,
|
|
469
|
+
help="Inline JSON object only (no filesystem paths; agent-safe)",
|
|
470
|
+
)
|
|
471
|
+
args = parser.parse_args(argv)
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
payload = load_input_payload(args.input)
|
|
475
|
+
result = run_mode(args.mode, payload)
|
|
476
|
+
except Exception as err: # noqa: BLE001 — CLI always emits JSON
|
|
477
|
+
print(json.dumps(stamp_decision_governance({"success": False, "error": f"Failed to parse input: {err}"})))
|
|
478
|
+
return 1
|
|
479
|
+
|
|
480
|
+
print(json.dumps(result, indent=2))
|
|
481
|
+
return 0 if result.get("success") else 1
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
if __name__ == "__main__":
|
|
485
|
+
sys.exit(main())
|
|
@@ -26,12 +26,35 @@ const HARNESSES = Object.freeze({
|
|
|
26
26
|
'code-edit': path.join(HARNESS_DIR, 'code-edit.json'),
|
|
27
27
|
'db-write': path.join(HARNESS_DIR, 'db-write.json'),
|
|
28
28
|
routine: path.join(HARNESS_DIR, 'routine.json'),
|
|
29
|
+
'actor-critic-audit': path.join(HARNESS_DIR, 'actor-critic-audit.json'),
|
|
30
|
+
'future-agi-guardrails': path.join(HARNESS_DIR, 'future-agi-guardrails.json'),
|
|
31
|
+
'five-walls-governance': path.join(HARNESS_DIR, 'five-walls-governance.json'),
|
|
32
|
+
'simatree-data-governance': path.join(HARNESS_DIR, 'simatree-data-governance.json'),
|
|
33
|
+
'radware-threat-defense': path.join(HARNESS_DIR, 'radware-threat-defense-2026.json'),
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
// ---------------------------------------------------------------------------
|
|
32
37
|
// Detection patterns
|
|
33
38
|
// ---------------------------------------------------------------------------
|
|
34
39
|
|
|
40
|
+
const SIMATREE_PATTERNS = [
|
|
41
|
+
/\b(simatree|data_lifecycle|why_before_how|pmo_transformation|bayesian_uncertainty|lakehouse_governance)\b/i,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const FIVE_WALLS_PATTERNS = [
|
|
45
|
+
/\b(five_walls|five-walls|action_safety|hard_deny|preconditions|index_and_leaf|identity_propagation)\b/i,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const FUTURE_AGI_PATTERNS = [
|
|
49
|
+
/\b(future-agi|futureagi|agentcc|traceai|self_healing|adversarial_simulation|simulate_agent)\b/i,
|
|
50
|
+
/\b(prompt_injection|jailbreak|eval_rubric|guardrail_scanner)\b/i,
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const ACTOR_CRITIC_PATTERNS = [
|
|
54
|
+
/\b(publish_causal_report|causal_inference|target_trial|target-trial|actor-critic|actor_critic|process_audit)\b/i,
|
|
55
|
+
/\b(placebo_test|counterfactual|propensity_score|double_ml)\b/i,
|
|
56
|
+
];
|
|
57
|
+
|
|
35
58
|
const DEPLOY_PATTERNS = [
|
|
36
59
|
/\brailway\s+(deploy|up|run)\b/i,
|
|
37
60
|
/\bdocker\s+(push|build)\b/i,
|
|
@@ -73,6 +96,24 @@ const CODE_EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdi
|
|
|
73
96
|
* @param {object|string} toolInput - raw tool input object or string
|
|
74
97
|
* @returns {string|null} absolute path to harness JSON, or null
|
|
75
98
|
*/
|
|
99
|
+
/**
|
|
100
|
+
* Full-payload text for pattern scanning. extractCommandText short-circuits on
|
|
101
|
+
* file_path for Edit/Write, which never carries an injection string — the
|
|
102
|
+
* content/new_string fields do.
|
|
103
|
+
*
|
|
104
|
+
* @param {object|string} toolInput
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
function extractPayloadText(toolInput) {
|
|
108
|
+
if (!toolInput) return '';
|
|
109
|
+
if (typeof toolInput === 'string') return toolInput;
|
|
110
|
+
try {
|
|
111
|
+
return JSON.stringify(toolInput);
|
|
112
|
+
} catch {
|
|
113
|
+
return extractCommandText(toolInput);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
76
117
|
function selectHarness(toolName, toolInput) {
|
|
77
118
|
// 1. Explicit override
|
|
78
119
|
if (process.env.THUMBGATE_HARNESS) {
|
|
@@ -82,8 +123,31 @@ function selectHarness(toolName, toolInput) {
|
|
|
82
123
|
if (path.isAbsolute(override)) return override;
|
|
83
124
|
}
|
|
84
125
|
|
|
85
|
-
//
|
|
126
|
+
// 1b. Radware / Bot Manager threat defense — auto-select when evaluateThreat or rate burst
|
|
127
|
+
// flags ShadowLeak, ZombieAgent, suspicious-bot challenge, or rate burst.
|
|
128
|
+
// Without this branch the registry entry is inert for normal PreToolUse.
|
|
129
|
+
try {
|
|
130
|
+
const { evaluateThreat } = require('./radware-threat-defense.js');
|
|
131
|
+
const payloadText = extractPayloadText(toolInput) || extractCommandText(toolInput) || String(toolName || '');
|
|
132
|
+
// Content threats only here. Rate-burst is enforced structurally in gates-engine
|
|
133
|
+
// (persisted history) so selector probes cannot false-select this harness.
|
|
134
|
+
const threat = evaluateThreat(payloadText);
|
|
135
|
+
if (threat.blocked || threat.challenged || threat.severity !== 'none') {
|
|
136
|
+
return HARNESSES['radware-threat-defense'];
|
|
137
|
+
}
|
|
138
|
+
} catch {
|
|
139
|
+
// Fail open on selector errors; gates still apply when harness forced via env.
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. Edit/Write tools get the code-edit harness, UNLESS the payload itself
|
|
143
|
+
// trips a Future AGI pattern. Returning code-edit unconditionally meant
|
|
144
|
+
// the prompt-injection gates declared for Edit/Write were never active
|
|
145
|
+
// under automatic selection, since only the selected harness is loaded.
|
|
86
146
|
if (CODE_EDIT_TOOL_NAMES.has(toolName)) {
|
|
147
|
+
const payloadText = extractPayloadText(toolInput);
|
|
148
|
+
if (payloadText && FUTURE_AGI_PATTERNS.some((p) => p.test(payloadText))) {
|
|
149
|
+
return HARNESSES['future-agi-guardrails'];
|
|
150
|
+
}
|
|
87
151
|
return HARNESSES['code-edit'];
|
|
88
152
|
}
|
|
89
153
|
|
|
@@ -93,6 +157,18 @@ function selectHarness(toolName, toolInput) {
|
|
|
93
157
|
if (DB_WRITE_PATTERNS.some((p) => p.test(commandText))) {
|
|
94
158
|
return HARNESSES['db-write'];
|
|
95
159
|
}
|
|
160
|
+
if (ACTOR_CRITIC_PATTERNS.some((p) => p.test(commandText))) {
|
|
161
|
+
return HARNESSES['actor-critic-audit'];
|
|
162
|
+
}
|
|
163
|
+
if (FUTURE_AGI_PATTERNS.some((p) => p.test(commandText))) {
|
|
164
|
+
return HARNESSES['future-agi-guardrails'];
|
|
165
|
+
}
|
|
166
|
+
if (FIVE_WALLS_PATTERNS.some((p) => p.test(commandText))) {
|
|
167
|
+
return HARNESSES['five-walls-governance'];
|
|
168
|
+
}
|
|
169
|
+
if (SIMATREE_PATTERNS.some((p) => p.test(commandText))) {
|
|
170
|
+
return HARNESSES['simatree-data-governance'];
|
|
171
|
+
}
|
|
96
172
|
if (ROUTINE_PATTERNS.some((p) => p.test(commandText))) {
|
|
97
173
|
return HARNESSES.routine;
|
|
98
174
|
}
|
|
@@ -459,6 +535,8 @@ function extractCommandText(toolInput) {
|
|
|
459
535
|
return '';
|
|
460
536
|
}
|
|
461
537
|
|
|
538
|
+
const { evaluateThreat, evaluatePretoolDefense, checkRateBurst } = require('./radware-threat-defense.js');
|
|
539
|
+
|
|
462
540
|
module.exports = {
|
|
463
541
|
selectHarness,
|
|
464
542
|
selectHarnessName,
|
|
@@ -473,6 +551,9 @@ module.exports = {
|
|
|
473
551
|
buildSolverWorkflowGovernance,
|
|
474
552
|
formatSolverWorkflowGovernance,
|
|
475
553
|
extractCommandText,
|
|
554
|
+
evaluateThreat,
|
|
555
|
+
evaluatePretoolDefense,
|
|
556
|
+
checkRateBurst,
|
|
476
557
|
HARNESSES,
|
|
477
558
|
DEPLOY_PATTERNS,
|
|
478
559
|
DB_WRITE_PATTERNS,
|