thumbgate 1.27.12 → 1.27.13

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 (132) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.well-known/llms.txt +2 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +2 -4
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/mcp/server-stdio.js +1 -1
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  9. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  10. package/bin/cli.js +78 -259
  11. package/config/gate-templates.json +0 -228
  12. package/config/gates/claim-verification.json +0 -18
  13. package/package.json +35 -25
  14. package/public/assets/brand/thumbgate-logo-transparent.svg +22 -0
  15. package/public/assets/brand/thumbgate-mark-inline-v3.svg +19 -0
  16. package/public/assets/brand/thumbgate-mark.svg +11 -5
  17. package/public/blog.html +0 -30
  18. package/public/brand/thumbgate-mark.svg +9 -5
  19. package/public/chatgpt-app.html +2 -2
  20. package/public/compare.html +2 -1
  21. package/public/dashboard.html +1 -1
  22. package/public/federal.html +1 -1
  23. package/public/index.html +95 -216
  24. package/public/learn.html +59 -35
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +2 -2
  27. package/public/pro.html +7 -7
  28. package/scripts/aws-blocks-guardrails.js +228 -0
  29. package/scripts/cli-schema.js +22 -10
  30. package/scripts/dashboard-chat.js +2 -1
  31. package/scripts/document-intake.js +1 -49
  32. package/scripts/durability/step.js +3 -3
  33. package/scripts/gate-stats.js +5 -11
  34. package/scripts/gates-engine.js +0 -49
  35. package/scripts/gemini-embedding-policy.js +2 -1
  36. package/scripts/hook-stop-anti-claim.js +116 -184
  37. package/scripts/hosted-config.js +0 -12
  38. package/scripts/lesson-search.js +1 -15
  39. package/scripts/llm-client.js +187 -5
  40. package/scripts/plausible-domain-config.js +3 -1
  41. package/scripts/seo-gsd.js +240 -1
  42. package/scripts/tool-registry.js +2 -2
  43. package/scripts/vector-store.js +44 -0
  44. package/scripts/workspace-evolver.js +62 -2
  45. package/src/api/server.js +340 -131
  46. package/public/assets/brand/thumbgate-mark-inline.svg +0 -15
  47. package/public/compare/adopt-ai.html +0 -219
  48. package/public/compare/agentix-labs.html +0 -197
  49. package/public/compare/ai-experience-orchestration.html +0 -216
  50. package/public/compare/anthropic-claude-for-legal.html +0 -260
  51. package/public/compare/anthropic-containment.html +0 -280
  52. package/public/compare/arcade.html +0 -175
  53. package/public/compare/arcjet.html +0 -239
  54. package/public/compare/bumblebee.html +0 -307
  55. package/public/compare/claude-code-hooks.html +0 -294
  56. package/public/compare/databricks-unity-ai-gateway.html +0 -215
  57. package/public/compare/fallow.html +0 -351
  58. package/public/compare/heidi.html +0 -233
  59. package/public/compare/mem0.html +0 -342
  60. package/public/compare/oak-and-sparrow-gatekeeper.html +0 -289
  61. package/public/compare/rein.html +0 -236
  62. package/public/compare/sigmashake.html +0 -256
  63. package/public/compare/speclock.html +0 -342
  64. package/public/guides/agent-harness-optimization.html +0 -342
  65. package/public/guides/agentic-web-governance.html +0 -406
  66. package/public/guides/ai-agent-governance-sprint.html +0 -415
  67. package/public/guides/ai-agent-pre-action-approval-gates.html +0 -401
  68. package/public/guides/ai-agent-workflow-migration-checklist.html +0 -392
  69. package/public/guides/ai-deployment-readiness.html +0 -415
  70. package/public/guides/ai-mode-ads-agent-governance.html +0 -401
  71. package/public/guides/ai-search-topical-presence.html +0 -342
  72. package/public/guides/autoresearch-agent-safety.html +0 -342
  73. package/public/guides/background-agent-governance.html +0 -358
  74. package/public/guides/best-tools-stop-ai-agents-breaking-production.html +0 -363
  75. package/public/guides/browser-automation-safety.html +0 -342
  76. package/public/guides/chatgpt-ads-trust.html +0 -353
  77. package/public/guides/claude-code-feedback.html +0 -339
  78. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  79. package/public/guides/claude-code-skills-guardrails.html +0 -343
  80. package/public/guides/claude-desktop.html +0 -356
  81. package/public/guides/code-knowledge-graph-guardrails.html +0 -365
  82. package/public/guides/codex-cli-guardrails.html +0 -339
  83. package/public/guides/cursor-agent-guardrails.html +0 -339
  84. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  85. package/public/guides/database-agent-safety.html +0 -406
  86. package/public/guides/deepseek-v4-runtime-guardrails.html +0 -346
  87. package/public/guides/developer-machine-supply-chain-guardrails.html +0 -358
  88. package/public/guides/gcp-mcp-guardrails.html +0 -147
  89. package/public/guides/gemini-cli-feedback-memory.html +0 -339
  90. package/public/guides/gpt-5-5-model-evaluation.html +0 -358
  91. package/public/guides/internal-ai-engineering-stack-guardrails.html +0 -348
  92. package/public/guides/long-running-agent-context-management.html +0 -346
  93. package/public/guides/mcp-tool-governance.html +0 -401
  94. package/public/guides/multica-thumbgate-setup.html +0 -134
  95. package/public/guides/native-messaging-host-security.html +0 -342
  96. package/public/guides/policy-engine-pre-action-gates.html +0 -346
  97. package/public/guides/pre-action-checks.html +0 -342
  98. package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +0 -342
  99. package/public/guides/prompt-tricks-to-workflow-rules.html +0 -365
  100. package/public/guides/proxy-pointer-rag-guardrails.html +0 -352
  101. package/public/guides/rag-precision-tuning-guardrails.html +0 -352
  102. package/public/guides/reasoning-compression-guardrails.html +0 -346
  103. package/public/guides/relational-knowledge-ai-recommendations.html +0 -342
  104. package/public/guides/roo-code-alternative-cline.html +0 -339
  105. package/public/guides/semantic-programmatic-seo-guardrails.html +0 -352
  106. package/public/guides/seo-agent-skills-guardrails.html +0 -344
  107. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -342
  108. package/public/learn/ac-dc-runtime-enforcement.html +0 -277
  109. package/public/learn/agent-harness-pattern.html +0 -181
  110. package/public/learn/agent-identity-connector-governance.html +0 -146
  111. package/public/learn/agent-swarms-shared-gates.html +0 -173
  112. package/public/learn/agentic-enterprise-context-brain.html +0 -117
  113. package/public/learn/agentic-os-team-governance.html +0 -146
  114. package/public/learn/ai-agent-governance.html +0 -158
  115. package/public/learn/ai-agent-persistent-memory.html +0 -211
  116. package/public/learn/anthropomorphic-claim-gates.html +0 -180
  117. package/public/learn/background-agent-control-layer.html +0 -184
  118. package/public/learn/claude-code-goal-with-rubrics.html +0 -205
  119. package/public/learn/codex-role-plugins-need-governance.html +0 -125
  120. package/public/learn/cost-aware-agent-gate-routing.html +0 -173
  121. package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +0 -157
  122. package/public/learn/deterministic-agent-workflows.html +0 -185
  123. package/public/learn/feedback-loop-vs-decision-layer.html +0 -283
  124. package/public/learn/from-prototype-to-production.html +0 -223
  125. package/public/learn/learn.css +0 -51
  126. package/public/learn/mcp-pre-action-checks-explained.html +0 -172
  127. package/public/learn/pretix-stripe-connect-marketplaces.html +0 -161
  128. package/public/learn/regulated-agent-execution-boundary.html +0 -196
  129. package/public/learn/spec-driven-development.html +0 -168
  130. package/public/learn/stop-ai-agent-force-push.html +0 -134
  131. package/public/learn/vibe-coding-safety-net.html +0 -142
  132. package/scripts/reddit-browser-notification-watch.js +0 -230
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbgate",
3
3
  "description": "One 👎 becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
4
- "version": "1.27.7",
4
+ "version": "1.27.8",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky",
7
7
  "email": "ig5973700@gmail.com",
@@ -64,10 +64,11 @@ npx thumbgate init --agent claude-code
64
64
  - Agent discovery: https://thumbgate.ai/.well-known/mcp.json
65
65
  - Progressive tool index: https://thumbgate.ai/.well-known/mcp/tools.json
66
66
  - Context footprint report: https://thumbgate.ai/.well-known/mcp/footprint.json
67
+ - Headroom context compression guardrails: https://thumbgate.ai/guides/headroom-context-compression-guardrails
68
+ - Sovereign coding model guardrails: https://thumbgate.ai/guides/sovereign-coding-model-guardrails
67
69
  - Agentic web governance: https://thumbgate.ai/guides/agentic-web-governance
68
70
  - AI Mode ads and agent governance: https://thumbgate.ai/guides/ai-mode-ads-agent-governance
69
71
  - MCP tool governance: https://thumbgate.ai/guides/mcp-tool-governance
70
- - Policy engines need pre-action gates: https://thumbgate.ai/guides/policy-engine-pre-action-gates
71
72
  - AI agent pre-action approval gates: https://thumbgate.ai/guides/ai-agent-pre-action-approval-gates
72
73
  - Agent skills: https://thumbgate.ai/.well-known/mcp/skills.json
73
74
  - MCP applications: https://thumbgate.ai/.well-known/mcp/applications.json
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.27.7",
3
+ "version": "1.27.8",
4
4
  "description": "ThumbGate — 👍👎 feedback that teaches your AI agent. Thumbs down a mistake, it never happens again.",
5
5
  "homepage": "https://thumbgate.ai",
6
6
  "transport": "stdio",
package/README.md CHANGED
@@ -16,10 +16,6 @@ The product is a self-improving enforcement layer: thumbs-down feedback, prompt
16
16
  <img src="docs/media/thumbgate-demo.gif" alt="ThumbGate blocking an AI agent's dangerous commands (rm -rf, force-push, chmod 777) in real time, while letting safe commands through" width="820" />
17
17
  </p>
18
18
 
19
- <p align="center">
20
- <img src="docs/diagrams/pre-action-gate-loop.svg" alt="ThumbGate pre-action gate loop: agent intent, PreToolUse gate, block or allow with audit receipt" width="920" />
21
- </p>
22
-
23
19
  ```
24
20
  Agent tries: rm -rf tests/
25
21
  ThumbGate: ⛔ BLOCKED — "Never delete test directories"
@@ -388,6 +384,8 @@ npx thumbgate model-candidates --workload=dashboard-analysis --provider=openai -
388
384
  npx thumbgate native-messaging-audit # inspect local browser bridges and extension hosts
389
385
  npx thumbgate dashboard --open # open local project-scoped dashboard in browser
390
386
  thumbgate-dashboard # standalone browser dashboard shortcut (run '/project:thumbgate-dashboard' in Claude/Grok)
387
+ npx thumbgate check-update # check if a new version is available on npm/GitHub
388
+ npx thumbgate self-update # update ThumbGate to the latest version globally
391
389
  npx thumbgate serve # start MCP server on stdio
392
390
  npx thumbgate bench # run reliability benchmark
393
391
  npx thumbgate bench --programbench-smoke # include cleanroom whole-repo proof lane
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.27.7", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.27.8", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.27.7", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.27.8", "thumbgate", "gate-check"]
12
12
  }
13
13
  }
14
14
  }
@@ -231,7 +231,7 @@ const {
231
231
  finalizeSession: finalizeFeedbackSession,
232
232
  } = require('../../scripts/feedback-session');
233
233
 
234
- const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.7' };
234
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.8' };
235
235
  const COMMERCE_CATEGORIES = [
236
236
  'product_recommendation',
237
237
  'brand_compliance',
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.27.7",
10
+ "thumbgate@1.27.8",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const DEFAULT_ENDPOINT = 'https://api.oraclestechnologies.com/v1/guardian/analyze';
5
+
6
+ function requireApiKey(env = process.env) {
7
+ const key = env.ETHICORE_API_KEY || env.GUARDIAN_API_KEY || env.ORACLES_GUARDIAN_API_KEY;
8
+ if (!key) {
9
+ throw new Error('ETHICORE_API_KEY env var is required');
10
+ }
11
+ return key;
12
+ }
13
+
14
+ async function analyzeText(text, options = {}) {
15
+ if (!String(text || '').trim()) {
16
+ throw new Error('analyzeText requires text');
17
+ }
18
+
19
+ const env = options.env || process.env;
20
+ const endpoint = options.endpoint || env.ETHICORE_GUARDIAN_ENDPOINT || DEFAULT_ENDPOINT;
21
+ const apiKey = options.apiKey || requireApiKey(env);
22
+ const fetchImpl = options.fetch || fetch;
23
+
24
+ const response = await fetchImpl(endpoint, {
25
+ method: 'POST',
26
+ headers: {
27
+ Authorization: `Bearer ${apiKey}`,
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ body: JSON.stringify({ text }),
31
+ });
32
+
33
+ const bodyText = await response.text();
34
+ let body = bodyText;
35
+ try {
36
+ body = bodyText ? JSON.parse(bodyText) : {};
37
+ } catch {
38
+ // Keep non-JSON body for diagnostics.
39
+ }
40
+
41
+ if (!response.ok) {
42
+ throw new Error(`Ethicore Guardian API ${response.status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
43
+ }
44
+
45
+ return body;
46
+ }
47
+
48
+ function createEthicorePolicyCheck(options = {}) {
49
+ return async function ethicorePolicyCheck(action = {}) {
50
+ const toolText = [
51
+ action.toolName,
52
+ action.actionType,
53
+ action.command,
54
+ action.path,
55
+ action.url,
56
+ action.input ? JSON.stringify(action.input) : '',
57
+ ].filter(Boolean).join('\n');
58
+
59
+ return analyzeText(toolText || JSON.stringify(action), options);
60
+ };
61
+ }
62
+
63
+ module.exports = {
64
+ DEFAULT_ENDPOINT,
65
+ analyzeText,
66
+ createEthicorePolicyCheck,
67
+ requireApiKey,
68
+ };
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { normalizeProviderAction } = require('../../scripts/provider-action-normalizer');
5
+
6
+ const BLOCK_DECISIONS = new Set([
7
+ 'block',
8
+ 'blocked',
9
+ 'deny',
10
+ 'denied',
11
+ 'disallow',
12
+ 'disallowed',
13
+ 'fail',
14
+ 'failed',
15
+ 'forbid',
16
+ 'forbidden',
17
+ 'reject',
18
+ 'rejected',
19
+ 'unsafe',
20
+ 'violation',
21
+ ]);
22
+
23
+ const REVIEW_DECISIONS = new Set([
24
+ 'approval',
25
+ 'approval-required',
26
+ 'approval_required',
27
+ 'approve',
28
+ 'human-review',
29
+ 'human_review',
30
+ 'manual-review',
31
+ 'manual_review',
32
+ 'review',
33
+ 'requires-approval',
34
+ 'requires_approval',
35
+ 'requires-review',
36
+ 'requires_review',
37
+ ]);
38
+
39
+ const ALLOW_DECISIONS = new Set([
40
+ 'accept',
41
+ 'accepted',
42
+ 'allow',
43
+ 'allowed',
44
+ 'ok',
45
+ 'pass',
46
+ 'passed',
47
+ 'permit',
48
+ 'permitted',
49
+ 'safe',
50
+ ]);
51
+
52
+ function asObject(value) {
53
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
54
+ }
55
+
56
+ function asArray(value) {
57
+ return Array.isArray(value) ? value : [];
58
+ }
59
+
60
+ function firstString(...values) {
61
+ for (const value of values) {
62
+ const text = String(value || '').trim();
63
+ if (text) return text;
64
+ }
65
+ return '';
66
+ }
67
+
68
+ function normalizeDecisionToken(value) {
69
+ return String(value || '')
70
+ .trim()
71
+ .toLowerCase()
72
+ .replace(/\s+/g, '-');
73
+ }
74
+
75
+ function normalizeEvidence(value) {
76
+ const direct = asArray(value.evidence);
77
+ const citations = asArray(value.citations);
78
+ const violations = asArray(value.violations);
79
+ const reasons = asArray(value.reasons);
80
+ const reasoning = asArray(value.reasoning);
81
+ const threatTypes = asArray(value.threat_types || value.threatTypes)
82
+ .map((threatType) => ({
83
+ code: String(threatType || '').trim(),
84
+ message: `Threat type: ${String(threatType || '').trim()}`,
85
+ source: 'guardian',
86
+ severity: firstString(value.threat_level, value.threatLevel),
87
+ }));
88
+ return [...direct, ...citations, ...violations, ...reasons, ...reasoning, ...threatTypes]
89
+ .map((entry) => {
90
+ if (typeof entry === 'string') return { text: entry };
91
+ const object = asObject(entry);
92
+ if (!Object.keys(object).length) return null;
93
+ return {
94
+ id: firstString(object.id, object.ruleId, object.rule_id, object.code),
95
+ text: firstString(object.text, object.reason, object.message, object.description, object.title),
96
+ source: firstString(object.source, object.provider, object.policy),
97
+ severity: firstString(object.severity, object.level),
98
+ raw: object,
99
+ };
100
+ })
101
+ .filter(Boolean);
102
+ }
103
+
104
+ function extractPolicyDecision(input = {}) {
105
+ const event = asObject(input);
106
+ for (const candidate of [
107
+ event.policyDecision,
108
+ event.policy_decision,
109
+ event.guardrailResult,
110
+ event.guardrail_result,
111
+ event.result,
112
+ ]) {
113
+ const object = asObject(candidate);
114
+ if (Object.keys(object).length) return object;
115
+ }
116
+ return event;
117
+ }
118
+
119
+ function classifyPolicyDecision(input = {}) {
120
+ const value = extractPolicyDecision(input);
121
+ const token = normalizeDecisionToken(firstString(
122
+ value.decision,
123
+ value.action,
124
+ value.status,
125
+ value.result,
126
+ value.verdict,
127
+ value.outcome,
128
+ value.effect,
129
+ value.recommended_action,
130
+ value.recommendedAction,
131
+ ));
132
+
133
+ if (
134
+ value.allowed === false
135
+ || value.is_safe === false
136
+ || value.isSafe === false
137
+ || value.accepted === false
138
+ || value.blocked === true
139
+ || value.denied === true
140
+ || BLOCK_DECISIONS.has(token)
141
+ ) {
142
+ return 'block';
143
+ }
144
+ if (
145
+ value.requiresApproval === true
146
+ || value.requires_approval === true
147
+ || value.reviewRequired === true
148
+ || value.review_required === true
149
+ || REVIEW_DECISIONS.has(token)
150
+ ) {
151
+ return 'approval_required';
152
+ }
153
+ if (
154
+ value.allowed === true
155
+ || value.is_safe === true
156
+ || value.isSafe === true
157
+ || value.accepted === true
158
+ || ALLOW_DECISIONS.has(token)
159
+ ) {
160
+ return 'allow';
161
+ }
162
+ return 'unknown';
163
+ }
164
+
165
+ function normalizePolicyDecision(input = {}, options = {}) {
166
+ const value = extractPolicyDecision(input);
167
+ const decision = classifyPolicyDecision(value);
168
+ const source = firstString(
169
+ options.source,
170
+ value.source,
171
+ value.provider,
172
+ value.engine,
173
+ value.policyEngine,
174
+ value.policy_engine,
175
+ 'policy-engine'
176
+ );
177
+ const reason = firstString(
178
+ value.reason,
179
+ value.message,
180
+ value.explanation,
181
+ value.summary,
182
+ asArray(value.reasoning).join('; '),
183
+ asArray(value.reasons).join('; '),
184
+ decision === 'unknown' ? 'Policy engine returned an unknown decision; approval required before execution.' : ''
185
+ );
186
+
187
+ return {
188
+ allowed: decision === 'allow',
189
+ blocked: decision === 'block',
190
+ approvalRequired: decision === 'approval_required' || decision === 'unknown',
191
+ decision,
192
+ reason,
193
+ source,
194
+ confidence: Number.isFinite(Number(value.confidence)) ? Number(value.confidence) : null,
195
+ policyId: firstString(value.policyId, value.policy_id, value.ruleId, value.rule_id, value.id),
196
+ severity: firstString(value.severity, value.level, value.threat_level, value.threatLevel),
197
+ score: Number.isFinite(Number(value.score))
198
+ ? Number(value.score)
199
+ : (Number.isFinite(Number(value.threat_score)) ? Number(value.threat_score) : null),
200
+ evidence: normalizeEvidence(value),
201
+ raw: value,
202
+ };
203
+ }
204
+
205
+ function normalizePolicyAction(input = {}) {
206
+ const event = asObject(input);
207
+ return {
208
+ ...normalizeProviderAction({
209
+ ...event,
210
+ provider: firstString(event.provider, event.agentRuntime, event.runtime, 'policy-engine'),
211
+ toolName: firstString(event.toolName, event.tool_name, event.name),
212
+ input: asObject(event.toolInput || event.input || event.arguments || event.args),
213
+ }),
214
+ policyContext: asObject(event.policyContext || event.policy_context),
215
+ };
216
+ }
217
+
218
+ function createPolicyEngineGuard({
219
+ policyCheck,
220
+ executeTool,
221
+ gateCheck,
222
+ onDecision,
223
+ source = 'policy-engine',
224
+ } = {}) {
225
+ if (typeof policyCheck !== 'function') {
226
+ throw new TypeError('createPolicyEngineGuard requires a policyCheck function');
227
+ }
228
+ if (typeof executeTool !== 'function') {
229
+ throw new TypeError('createPolicyEngineGuard requires an executeTool function');
230
+ }
231
+
232
+ return async function guardedPolicyTool(input = {}) {
233
+ const normalizedAction = normalizePolicyAction(input);
234
+ const policyDecision = normalizePolicyDecision(await policyCheck(normalizedAction), { source });
235
+ const gateDecision = typeof gateCheck === 'function'
236
+ ? normalizePolicyDecision(await gateCheck({ normalizedAction, policyDecision }), { source: 'thumbgate' })
237
+ : null;
238
+ const effectiveDecision = gateDecision && !gateDecision.allowed ? gateDecision : policyDecision;
239
+
240
+ if (typeof onDecision === 'function') {
241
+ await onDecision({ normalizedAction, policyDecision, gateDecision, effectiveDecision });
242
+ }
243
+
244
+ if (!effectiveDecision.allowed) {
245
+ const error = new Error(effectiveDecision.reason || 'ThumbGate blocked this action before execution.');
246
+ error.code = effectiveDecision.approvalRequired ? 'THUMBGATE_APPROVAL_REQUIRED' : 'THUMBGATE_BLOCKED';
247
+ error.thumbgate = { normalizedAction, policyDecision, gateDecision, effectiveDecision };
248
+ throw error;
249
+ }
250
+
251
+ return executeTool(input, { normalizedAction, policyDecision, gateDecision, effectiveDecision });
252
+ };
253
+ }
254
+
255
+ module.exports = {
256
+ createPolicyEngineGuard,
257
+ extractPolicyDecision,
258
+ normalizePolicyAction,
259
+ normalizePolicyDecision,
260
+ };