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.
Files changed (200) hide show
  1. package/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
  2. package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
  3. package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
  4. package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
  5. package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +1 -0
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/CONTRIBUTING.md +95 -0
  10. package/README.md +195 -632
  11. package/THIRD_PARTY_NOTICES.md +89 -0
  12. package/adapters/claude/.mcp.json +2 -2
  13. package/adapters/forge/forge.yaml +3 -3
  14. package/adapters/future-agi/.mcp.json +8 -0
  15. package/adapters/future-agi/FUTURE_AGI.md +23 -0
  16. package/adapters/future-agi/config.toml +3 -0
  17. package/adapters/future-agi/future-agi-bridge.js +9 -0
  18. package/adapters/future-agi/opencode.json +8 -0
  19. package/adapters/herdr/herdr-plugin.toml +18 -0
  20. package/adapters/mcp/server-stdio.js +238 -25
  21. package/adapters/opencode/opencode.json +1 -1
  22. package/adapters/workos/WORKOS.md +52 -0
  23. package/bin/cli.js +373 -5
  24. package/bin/futureagi-bridge +9 -0
  25. package/config/gate-templates.json +653 -4
  26. package/config/gates/actor-critic-audit.json +34 -0
  27. package/config/gates/default.json +21 -2
  28. package/config/gates/five-walls-governance.json +34 -0
  29. package/config/gates/future-agi-guardrails.json +34 -0
  30. package/config/gates/radware-threat-defense-2026.json +61 -0
  31. package/config/gates/simatree-data-governance.json +33 -0
  32. package/config/mcp-allowlists.json +4 -0
  33. package/config/merge-quality-checks.json +10 -1
  34. package/config/model-candidates.json +382 -24
  35. package/config/model-tiers.json +18 -0
  36. package/config/post-deploy-marketing-pages.json +10 -0
  37. package/config/progressive/01-wire-only.json +11 -0
  38. package/config/progressive/02-dashboard-empty-ok.json +10 -0
  39. package/config/progressive/03-one-lesson.json +10 -0
  40. package/config/progressive/04-warn-fires.json +11 -0
  41. package/config/progressive/05-strict-optional.json +11 -0
  42. package/config/progressive/README.md +15 -0
  43. package/config/schemas/broker-execution-receipt.schema.json +139 -0
  44. package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
  45. package/conformance/provider-attestation/vectors.json +320 -0
  46. package/docs/specs/provider-execution-attestation-v1.md +69 -0
  47. package/openapi/openapi.yaml +15 -0
  48. package/package.json +401 -147
  49. package/public/about.html +2 -2
  50. package/public/ai-malpractice-prevention.html +7 -7
  51. package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
  52. package/public/blog/a-receipt-is-not-world-state.html +388 -0
  53. package/public/blog/git-at-agent-scale.html +374 -0
  54. package/public/blog/no-llm-in-the-gate.html +133 -0
  55. package/public/blog.html +80 -0
  56. package/public/case-studies.html +16 -1
  57. package/public/compare.html +28 -0
  58. package/public/diagnostic.html +216 -7
  59. package/public/docs/connectors.html +39 -0
  60. package/public/federal.html +2 -2
  61. package/public/founders.html +639 -0
  62. package/public/index.html +87 -9
  63. package/public/install.html +8 -8
  64. package/public/learn.html +39 -0
  65. package/public/numbers.html +2 -2
  66. package/public/peter.html +310 -0
  67. package/public/platform-partners.html +119 -0
  68. package/public/pricing.html +24 -3
  69. package/public/privacy.html +117 -0
  70. package/public/pro.html +17 -0
  71. package/public/support.html +62 -0
  72. package/public/terms.html +130 -0
  73. package/public/third-party-notices.html +95 -0
  74. package/public/yt.html +351 -0
  75. package/scripts/action-receipts.js +133 -3
  76. package/scripts/adaptive-governance-arena.js +349 -0
  77. package/scripts/admin-override.js +205 -0
  78. package/scripts/agent-action-inventory.js +869 -0
  79. package/scripts/agent-audit-trace.js +42 -2
  80. package/scripts/agent-egress-policy.js +1117 -0
  81. package/scripts/agent-memory-lifecycle.js +141 -2
  82. package/scripts/agent-operations-planner.js +441 -1
  83. package/scripts/agent-readiness.js +68 -0
  84. package/scripts/agent-security-central.js +647 -0
  85. package/scripts/allowlist-bridge-honesty.js +417 -0
  86. package/scripts/async-job-runner.js +102 -11
  87. package/scripts/audit-trail.js +212 -0
  88. package/scripts/auto-promote-gates.js +178 -27
  89. package/scripts/billing.js +1 -1
  90. package/scripts/broker-execution-receipts.js +719 -0
  91. package/scripts/budget-aware-gates-proof.js +423 -0
  92. package/scripts/claude-feedback-sync.js +29 -3
  93. package/scripts/claw-harness-production.js +237 -0
  94. package/scripts/cli-progress.js +111 -0
  95. package/scripts/cli-schema.js +163 -1
  96. package/scripts/codex-runbook-flywheel.js +318 -0
  97. package/scripts/context-footprint.js +186 -0
  98. package/scripts/contextfs.js +143 -61
  99. package/scripts/dashboard-limits.js +27 -0
  100. package/scripts/dashboard.js +279 -9
  101. package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
  102. package/scripts/docker-sandbox-planner.js +18 -0
  103. package/scripts/double-blind-eval-protocol.js +252 -0
  104. package/scripts/edotenv-rl-gateway.js +259 -0
  105. package/scripts/ensure-production-search-corpus.js +162 -0
  106. package/scripts/eval-holdout.js +311 -0
  107. package/scripts/feedback-aggregate.js +21 -2
  108. package/scripts/feedback-loop.js +87 -5
  109. package/scripts/feedback-quality.js +9 -0
  110. package/scripts/file-ledger-lock.js +4 -1
  111. package/scripts/financial-control-plane.js +41 -1
  112. package/scripts/find-dormant-requires.js +118 -0
  113. package/scripts/fs-utils.js +84 -8
  114. package/scripts/gate-stats.js +2 -2
  115. package/scripts/gates-engine.js +859 -58
  116. package/scripts/generate-case-study-outreach.js +24 -15
  117. package/scripts/git-at-scale.js +628 -0
  118. package/scripts/governance-conflict-audit.js +1650 -0
  119. package/scripts/governance-difficulty-curriculum.js +328 -0
  120. package/scripts/graphrag-retrieval.js +275 -0
  121. package/scripts/gurobi-optimizer.js +324 -0
  122. package/scripts/gurobi_optimizer.py +485 -0
  123. package/scripts/harness-selector.js +82 -1
  124. package/scripts/hidden-entry-points.js +284 -0
  125. package/scripts/human-escalation.js +199 -1
  126. package/scripts/hybrid-feedback-context.js +152 -19
  127. package/scripts/intent-governed-execution.js +602 -0
  128. package/scripts/intervention-policy.js +123 -20
  129. package/scripts/jit-harness-compose.js +628 -0
  130. package/scripts/jsonl-watcher.js +10 -0
  131. package/scripts/lesson-embedding-index.js +95 -12
  132. package/scripts/lesson-retrieval.js +105 -19
  133. package/scripts/local-model-profile.js +19 -2
  134. package/scripts/mailer/resend-mailer.js +1 -1
  135. package/scripts/matryoshka-embedding.js +235 -0
  136. package/scripts/mcp-oauth.js +42 -4
  137. package/scripts/mcp-session-handles.js +1016 -0
  138. package/scripts/mcp-wiring-doctor.js +314 -0
  139. package/scripts/memory-firewall.js +115 -2
  140. package/scripts/memory-scope-readiness.js +299 -0
  141. package/scripts/memory-vs-rag-route.js +161 -0
  142. package/scripts/model-tier-router.js +148 -21
  143. package/scripts/nvidia-specdecode-al-doctor.js +536 -0
  144. package/scripts/openui-catalog-compose-honesty.js +593 -0
  145. package/scripts/operational-integrity.js +19 -1
  146. package/scripts/override-audit.js +213 -0
  147. package/scripts/package-manager-honesty-doctor.js +458 -0
  148. package/scripts/pr-manager.js +63 -1
  149. package/scripts/prove-herdr-adapter.js +52 -0
  150. package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
  151. package/scripts/prove-workos.js +73 -0
  152. package/scripts/provider-attestation-conformance.js +192 -0
  153. package/scripts/provider-receipt-contract.js +136 -0
  154. package/scripts/qwen38-max-cost-optimizer.js +401 -0
  155. package/scripts/radware-threat-defense.js +280 -0
  156. package/scripts/rag-embedding-identity.js +221 -0
  157. package/scripts/rag-precision-guardrails.js +112 -2
  158. package/scripts/remote-feedback-capture.js +159 -0
  159. package/scripts/research-agent-harness.js +256 -0
  160. package/scripts/rsi-safety-hillclimb.js +200 -0
  161. package/scripts/rule-sprawl.js +188 -0
  162. package/scripts/schedule-manager.js +147 -0
  163. package/scripts/self-heal.js +8 -0
  164. package/scripts/session-lease.js +415 -0
  165. package/scripts/simatree-data-governance.js +347 -0
  166. package/scripts/slo-alert-engine.js +172 -7
  167. package/scripts/solver-parity.js +539 -0
  168. package/scripts/stealth-memory-injection-gate.js +333 -0
  169. package/scripts/switchyard-router.js +366 -0
  170. package/scripts/telemetry-analytics.js +84 -27
  171. package/scripts/temporal-decay-weighting.js +138 -0
  172. package/scripts/test-all.js +165 -0
  173. package/scripts/token-savings.js +42 -0
  174. package/scripts/tool-kpi-tracker.js +108 -5
  175. package/scripts/tool-registry.js +193 -5
  176. package/scripts/universal-claim-evaluator.js +14 -2
  177. package/scripts/vector-store.js +279 -9
  178. package/scripts/workflow-notebook.js +391 -0
  179. package/scripts/workflow-sentinel.js +111 -12
  180. package/scripts/workos-production-guard.js +260 -0
  181. package/scripts/workspace-search-route.js +515 -0
  182. package/server.json +2 -2
  183. package/src/agent-identity-boundary.js +76 -0
  184. package/src/agent-retrieval-cache.js +155 -0
  185. package/src/alert-noise-ledger.js +502 -0
  186. package/src/api/server.js +802 -185
  187. package/src/git-fast-cache.js +220 -0
  188. package/src/git-wal-sync.js +156 -0
  189. package/src/hash-anchored-edit.js +82 -0
  190. package/src/hermes-platform-protocol.js +475 -0
  191. package/src/hermes-sync-plane.js +241 -0
  192. package/src/index.js +30 -1
  193. package/src/iso42001-compliance-guard.js +97 -0
  194. package/src/latency-budget.js +244 -0
  195. package/src/mcp-writeguard.js +316 -0
  196. package/src/miminions-adapter.js +106 -0
  197. package/src/pipeline-compass.js +104 -0
  198. package/src/ppl-alert-pipeline.js +284 -0
  199. package/src/rendezvous-router.js +90 -0
  200. 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
- // 2. Edit/Write tools always get code-edit harness
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,