thumbgate 1.31.0 → 1.34.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/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +40 -3
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +21 -0
- package/config/gates/claim-verifiers.example.json +42 -0
- package/config/gates/claim-verifiers.json +25 -0
- package/config/mcp-allowlists.json +21 -0
- package/hooks/hooks.json +1 -1
- package/package.json +14 -9
- package/public/index.html +2 -2
- package/public/numbers.html +2 -2
- package/scripts/agent-readiness.js +110 -0
- package/scripts/auto-wire-hooks.js +20 -8
- package/scripts/cli-schema.js +14 -0
- package/scripts/feedback-schema.js +3 -0
- package/scripts/file-ledger-lock.js +130 -0
- package/scripts/financial-control-plane.js +1514 -0
- package/scripts/gates-engine.js +185 -7
- package/scripts/gemini-embedding-policy.js +1 -0
- package/scripts/hook-runtime.js +5 -0
- package/scripts/hook-stop-anti-claim.js +63 -3
- package/scripts/human-escalation.js +353 -41
- package/scripts/provider-action-normalizer.js +11 -4
- package/scripts/tool-registry.js +95 -5
- package/scripts/universal-claim-evaluator.js +767 -0
- package/scripts/vector-store.js +60 -27
- package/scripts/workflow-sentinel.js +77 -11
- package/server.json +2 -2
- package/src/api/server.js +2 -0
package/scripts/vector-store.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
} = require('./local-model-profile');
|
|
11
11
|
const {
|
|
12
12
|
prepareEmbeddingText,
|
|
13
|
+
normalizeEmbeddingKind,
|
|
13
14
|
resolveGeminiEmbeddingConfig,
|
|
14
15
|
resolveGeminiModelResource,
|
|
15
16
|
resolveGeminiTaskType,
|
|
@@ -110,7 +111,7 @@ function hasSemanticEmbeddingProvider() {
|
|
|
110
111
|
if (hasLocalTransformerProvider()) return true;
|
|
111
112
|
try {
|
|
112
113
|
const config = resolveGeminiEmbeddingConfig();
|
|
113
|
-
return config.provider === 'coreai' || Boolean(config.
|
|
114
|
+
return config.provider === 'coreai' || Boolean(config.apiKey);
|
|
114
115
|
} catch {
|
|
115
116
|
return false;
|
|
116
117
|
}
|
|
@@ -211,7 +212,7 @@ async function embedWithGemini(text, options = {}) {
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
if (typeof fetch !== 'function') {
|
|
214
|
-
throw new
|
|
215
|
+
throw new TypeError('Gemini embeddings require global fetch. Use Node 18.18+ or the local embedding provider.');
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
const modelResource = resolveGeminiModelResource(config.model);
|
|
@@ -291,7 +292,18 @@ async function embedWithOllama(text, options = {}) {
|
|
|
291
292
|
throw new Error('Ollama embeddings require THUMBGATE_OLLAMA_EMBED_MODEL');
|
|
292
293
|
}
|
|
293
294
|
if (typeof fetch !== 'function') {
|
|
294
|
-
throw new
|
|
295
|
+
throw new TypeError('Ollama embeddings require global fetch. Use Node 18.18+.');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Apply Nomic-style asymmetric prefixes. nomic-embed-text was trained
|
|
299
|
+
// with "search_query:" / "search_document:" role prefixes, which improve
|
|
300
|
+
// query-document matching fidelity on the dense retrieval path.
|
|
301
|
+
const kind = normalizeEmbeddingKind(options.kind);
|
|
302
|
+
let inputText = String(text || '');
|
|
303
|
+
if (kind === 'query') {
|
|
304
|
+
inputText = `search_query: ${inputText}`;
|
|
305
|
+
} else if (kind === 'document') {
|
|
306
|
+
inputText = `search_document: ${inputText}`;
|
|
295
307
|
}
|
|
296
308
|
|
|
297
309
|
let response;
|
|
@@ -301,7 +313,7 @@ async function embedWithOllama(text, options = {}) {
|
|
|
301
313
|
headers: { 'Content-Type': 'application/json' },
|
|
302
314
|
body: JSON.stringify({
|
|
303
315
|
model: config.model,
|
|
304
|
-
input:
|
|
316
|
+
input: inputText,
|
|
305
317
|
truncate: true,
|
|
306
318
|
dimensions: options.outputDimensionality || undefined,
|
|
307
319
|
}),
|
|
@@ -322,6 +334,39 @@ async function embedWithOllama(text, options = {}) {
|
|
|
322
334
|
return vector.map(Number);
|
|
323
335
|
}
|
|
324
336
|
|
|
337
|
+
async function tryGeminiManagedEmbedding(text, options, geminiConfig) {
|
|
338
|
+
if (!geminiConfig.apiKey && !_geminiEmbedderForTests) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const vector = await embedWithGemini(text, options);
|
|
343
|
+
_lastEmbeddingProfile = {
|
|
344
|
+
generatedAt: new Date().toISOString(),
|
|
345
|
+
source: 'managed',
|
|
346
|
+
activeProfile: {
|
|
347
|
+
id: 'gemini',
|
|
348
|
+
model: geminiConfig.model,
|
|
349
|
+
outputDimensionality: geminiConfig.outputDimensionality,
|
|
350
|
+
task: options.task || geminiConfig.defaultTask,
|
|
351
|
+
rationale: geminiConfig.enabled
|
|
352
|
+
? 'Managed Gemini Embedding 2 path with task-specific query/document prefixes.'
|
|
353
|
+
: 'Managed Gemini Embedding 2 fallback after local providers exhausted.',
|
|
354
|
+
},
|
|
355
|
+
fallbackUsed: !geminiConfig.enabled,
|
|
356
|
+
...(!geminiConfig.enabled ? { fallbackReason: 'local_providers_exhausted' } : {}),
|
|
357
|
+
};
|
|
358
|
+
return vector;
|
|
359
|
+
} catch (geminiError) {
|
|
360
|
+
if (!geminiConfig.fallbackToLocal) {
|
|
361
|
+
throw geminiError;
|
|
362
|
+
}
|
|
363
|
+
// Do not log raw provider/user-controlled error text (Sonar jssecurity:S5145).
|
|
364
|
+
const code = geminiError && (geminiError.code || geminiError.name || 'Error');
|
|
365
|
+
console.warn(`Gemini embedding fallback: ${code}`);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
325
370
|
async function embed(text, options = {}) {
|
|
326
371
|
if (process.env.THUMBGATE_VECTOR_STUB_EMBED === 'true') {
|
|
327
372
|
// Deterministic 384-dim unit vector: first element = 1.0, rest = 0.0
|
|
@@ -373,29 +418,8 @@ async function embed(text, options = {}) {
|
|
|
373
418
|
}
|
|
374
419
|
}
|
|
375
420
|
if (geminiConfig.enabled) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
_lastEmbeddingProfile = {
|
|
379
|
-
generatedAt: new Date().toISOString(),
|
|
380
|
-
source: 'managed',
|
|
381
|
-
activeProfile: {
|
|
382
|
-
id: 'gemini',
|
|
383
|
-
model: geminiConfig.model,
|
|
384
|
-
outputDimensionality: geminiConfig.outputDimensionality,
|
|
385
|
-
task: options.task || geminiConfig.defaultTask,
|
|
386
|
-
rationale: 'Managed Gemini Embedding 2 path with task-specific query/document prefixes.',
|
|
387
|
-
},
|
|
388
|
-
fallbackUsed: false,
|
|
389
|
-
};
|
|
390
|
-
return vector;
|
|
391
|
-
} catch (geminiError) {
|
|
392
|
-
if (!geminiConfig.fallbackToLocal) {
|
|
393
|
-
throw geminiError;
|
|
394
|
-
}
|
|
395
|
-
// Do not log raw provider/user-controlled error text (Sonar jssecurity:S5145).
|
|
396
|
-
const code = geminiError && (geminiError.code || geminiError.name || 'Error');
|
|
397
|
-
console.warn(`Gemini embedding fallback: ${code}`);
|
|
398
|
-
}
|
|
421
|
+
const vector = await tryGeminiManagedEmbedding(text, options, geminiConfig);
|
|
422
|
+
if (vector) return vector;
|
|
399
423
|
}
|
|
400
424
|
if (hasLocalTransformerProvider()) {
|
|
401
425
|
try {
|
|
@@ -410,6 +434,14 @@ async function embed(text, options = {}) {
|
|
|
410
434
|
}
|
|
411
435
|
}
|
|
412
436
|
|
|
437
|
+
// Gemini managed fallback — only when API key present but Gemini is not the
|
|
438
|
+
// explicitly selected provider. Honors fallbackToLocal in the catch block
|
|
439
|
+
// so that THUMBGATE_GEMINI_EMBED_FALLBACK_LOCAL=false makes Gemini mandatory.
|
|
440
|
+
if (geminiConfig.apiKey && !geminiConfig.enabled) {
|
|
441
|
+
const vector = await tryGeminiManagedEmbedding(text, options, geminiConfig);
|
|
442
|
+
if (vector) return vector;
|
|
443
|
+
}
|
|
444
|
+
|
|
413
445
|
const vector = embedWithFeatureHash(text);
|
|
414
446
|
// Feature-hash is a last-resort degrade, not production semantic quality.
|
|
415
447
|
// Callers (prove/eval/chat health) must treat quality_tier=degraded.
|
|
@@ -536,6 +568,7 @@ module.exports = {
|
|
|
536
568
|
TABLE_NAME,
|
|
537
569
|
getEmbeddingConfig,
|
|
538
570
|
getLastEmbeddingProfile,
|
|
571
|
+
getActiveEmbeddingProfile: getLastEmbeddingProfile,
|
|
539
572
|
setPipelineLoaderForTests,
|
|
540
573
|
setLanceLoaderForTests,
|
|
541
574
|
setGeminiEmbedderForTests,
|
|
@@ -22,6 +22,11 @@ const {
|
|
|
22
22
|
buildWorkflowControl,
|
|
23
23
|
normalizeProviderAction,
|
|
24
24
|
} = require('./provider-action-normalizer');
|
|
25
|
+
const {
|
|
26
|
+
detectEconomicAction,
|
|
27
|
+
evaluateFinancialControl,
|
|
28
|
+
getFinancialControlRuntimeOptions,
|
|
29
|
+
} = require('./financial-control-plane');
|
|
25
30
|
|
|
26
31
|
const GOVERNANCE_STATE_PATH = path.join(process.env.HOME || '/tmp', '.thumbgate', 'governance-state.json');
|
|
27
32
|
const DEFAULT_PROTECTED_FILE_GLOBS = [
|
|
@@ -40,7 +45,6 @@ const DEFAULT_PROTECTED_FILE_GLOBS = [
|
|
|
40
45
|
const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
|
|
41
46
|
const HIGH_RISK_BASH_PATTERN = /\b(?:git\s+(?:add|commit|push)|gh\s+(?:pr\s+(?:create|merge)|workflow\s+run|release\s+create)|npm\s+publish|yarn\s+publish|pnpm\s+publish|rm\s+-rf)\b/i;
|
|
42
47
|
const BACKGROUND_AGENT_PATTERN = /\b(?:async(?:-job|-task)?|autonomous|background|cron|dispatch|heartbeat|job runner|job-runner|queue|queued|schedule|scheduled|worker|workflow run)\b/i;
|
|
43
|
-
const ECONOMIC_ACTION_PATTERN = /\b(?:billing|charge|credit memo|invoice|payment(?: link|s)?|payout|refund|stripe|subscription(?:s| creation| update| cancel| delete)?|top-?up)\b/i;
|
|
44
48
|
const CUSTOMER_SYSTEM_PATTERN = /\b(?:crm|customer|email|hubspot|intercom|mailgun|resend|salesforce|support|zendesk)\b/i;
|
|
45
49
|
|
|
46
50
|
const SURFACE_RULES = [
|
|
@@ -259,7 +263,7 @@ function classifyActionProfile(toolInput = {}) {
|
|
|
259
263
|
const economicAction = Boolean(
|
|
260
264
|
toolInput.economicAction === true
|
|
261
265
|
|| metadata.economicAction === true
|
|
262
|
-
||
|
|
266
|
+
|| detectEconomicAction('', { ...toolInput, metadata: { ...metadata, context: combined } })
|
|
263
267
|
);
|
|
264
268
|
const customerSystemAction = Boolean(
|
|
265
269
|
toolInput.customerSystemAction === true
|
|
@@ -573,6 +577,7 @@ function scoreRisk({
|
|
|
573
577
|
taskScopeViolation,
|
|
574
578
|
protectedSurface,
|
|
575
579
|
costControl,
|
|
580
|
+
financialControl,
|
|
576
581
|
workflowControl,
|
|
577
582
|
workflowContract,
|
|
578
583
|
actionProfile,
|
|
@@ -692,6 +697,15 @@ function scoreRisk({
|
|
|
692
697
|
{ mode: costControl.mode, reasons: costControl.reasons }
|
|
693
698
|
);
|
|
694
699
|
}
|
|
700
|
+
if (financialControl?.mode === 'block') {
|
|
701
|
+
addDriver(
|
|
702
|
+
drivers,
|
|
703
|
+
'financial_control',
|
|
704
|
+
0.65,
|
|
705
|
+
'Deterministic purchase controls rejected the economic action.',
|
|
706
|
+
{ reasonCodes: financialControl.reasonCodes }
|
|
707
|
+
);
|
|
708
|
+
}
|
|
695
709
|
if (workflowControl && workflowControl.workflow && workflowControl.workflow.pattern !== 'single_action') {
|
|
696
710
|
const workflow = workflowControl.workflow;
|
|
697
711
|
if (workflow.pattern === 'agent') {
|
|
@@ -814,6 +828,7 @@ function buildEvidence({
|
|
|
814
828
|
protectedSurface,
|
|
815
829
|
normalizedAction,
|
|
816
830
|
costControl,
|
|
831
|
+
financialControl,
|
|
817
832
|
workflowControl,
|
|
818
833
|
workflowContract,
|
|
819
834
|
actionProfile,
|
|
@@ -827,6 +842,13 @@ function buildEvidence({
|
|
|
827
842
|
if (costControl && costControl.mode && costControl.mode !== 'allow') {
|
|
828
843
|
evidence.push(`Cost control ${costControl.mode}: ${costControl.reasons.join(' ')}`);
|
|
829
844
|
}
|
|
845
|
+
if (financialControl?.economicAction) {
|
|
846
|
+
evidence.push(
|
|
847
|
+
financialControl.mode === 'allow'
|
|
848
|
+
? `Financial control allow: approved reservation ${financialControl.authorization?.reservationId || 'unknown'}.`
|
|
849
|
+
: `Financial control block: ${financialControl.reasons.join(' ')}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
830
852
|
if (workflowControl && workflowControl.workflow && workflowControl.workflow.pattern !== 'single_action') {
|
|
831
853
|
const workflow = workflowControl.workflow;
|
|
832
854
|
evidence.push(
|
|
@@ -960,6 +982,7 @@ function buildRemediations({
|
|
|
960
982
|
learnedPolicy,
|
|
961
983
|
executionSurface,
|
|
962
984
|
costControl,
|
|
985
|
+
financialControl,
|
|
963
986
|
workflowControl,
|
|
964
987
|
workflowContract,
|
|
965
988
|
actionProfile,
|
|
@@ -1000,10 +1023,10 @@ function buildRemediations({
|
|
|
1000
1023
|
}
|
|
1001
1024
|
if (actionProfile && actionProfile.economicAction) {
|
|
1002
1025
|
push(
|
|
1003
|
-
'
|
|
1004
|
-
'
|
|
1005
|
-
'
|
|
1006
|
-
'Money-touching actions
|
|
1026
|
+
'financial_requisition_lifecycle',
|
|
1027
|
+
'Complete the purchase-control lifecycle',
|
|
1028
|
+
'Create a purchase requisition, obtain independent human approval through the reviewer API, reserve its exact budget, and attach the matching source-message scope before retrying.',
|
|
1029
|
+
'Money-touching actions require a single-use, auditable authorization instead of an advisory checkpoint.'
|
|
1007
1030
|
);
|
|
1008
1031
|
}
|
|
1009
1032
|
if (actionProfile && actionProfile.customerSystemAction) {
|
|
@@ -1064,6 +1087,14 @@ function buildRemediations({
|
|
|
1064
1087
|
'High token or cost estimates should be reviewed before the model/tool loop continues.'
|
|
1065
1088
|
);
|
|
1066
1089
|
}
|
|
1090
|
+
if (financialControl?.mode === 'block' && financialControl.reasonCodes.includes('zero_spend_budget')) {
|
|
1091
|
+
push(
|
|
1092
|
+
'honor_zero_spend_budget',
|
|
1093
|
+
'Honor the zero-spend budget',
|
|
1094
|
+
'Use a no-cost path. Do not add a card, start a paid trial, buy credits, or upgrade a plan.',
|
|
1095
|
+
'An explicit $0 budget is a hard prohibition, not an omitted configuration value.'
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1067
1098
|
if (workflowContract?.active && workflowContract.violations.length > 0) {
|
|
1068
1099
|
const codes = new Set(workflowContract.violations.map((violation) => violation.code));
|
|
1069
1100
|
if (codes.has('missing_required_evidence')) {
|
|
@@ -1145,6 +1176,13 @@ function buildReasoning(report) {
|
|
|
1145
1176
|
if (report.costControl && report.costControl.mode !== 'allow') {
|
|
1146
1177
|
lines.push(`Cost control: ${report.costControl.mode} — ${report.costControl.reasons.join(' ')}`);
|
|
1147
1178
|
}
|
|
1179
|
+
if (report.financialControl?.economicAction) {
|
|
1180
|
+
lines.push(
|
|
1181
|
+
report.financialControl.mode === 'allow'
|
|
1182
|
+
? `Financial control: approved reservation ${report.financialControl.authorization?.reservationId || 'unknown'}.`
|
|
1183
|
+
: `Financial control: block — ${report.financialControl.reasons.join(' ')}`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1148
1186
|
if (report.workflowControl && report.workflowControl.workflow.pattern !== 'single_action') {
|
|
1149
1187
|
lines.push(
|
|
1150
1188
|
`Workflow control: ${report.workflowControl.mode} for ${report.workflowControl.workflow.pattern} with inspection ${report.workflowControl.workflow.hasInspectionEvidence ? 'present' : 'missing'}.`
|
|
@@ -1255,6 +1293,7 @@ function buildDecisionControl({
|
|
|
1255
1293
|
integrity,
|
|
1256
1294
|
protectedSurface,
|
|
1257
1295
|
costControl,
|
|
1296
|
+
financialControl,
|
|
1258
1297
|
workflowControl,
|
|
1259
1298
|
workflowContract,
|
|
1260
1299
|
actionProfile,
|
|
@@ -1269,6 +1308,7 @@ function buildDecisionControl({
|
|
|
1269
1308
|
const hasOperationalBlockers = Boolean(integrity?.blockers?.length);
|
|
1270
1309
|
const hasCostWarning = costControl?.mode === 'warn';
|
|
1271
1310
|
const hasCostBlock = costControl?.mode === 'block';
|
|
1311
|
+
const hasFinancialBlock = financialControl?.mode === 'block';
|
|
1272
1312
|
const hasWorkflowWarning = workflowControl?.mode === 'warn';
|
|
1273
1313
|
const hasWorkflowBlock = workflowControl?.mode === 'block';
|
|
1274
1314
|
const hasContractWarning = workflowContract?.mode === 'warn';
|
|
@@ -1277,6 +1317,7 @@ function buildDecisionControl({
|
|
|
1277
1317
|
|| (decision === 'allow' && (reversibility !== 'two_way_door' || hasOperationalBlockers || hasCostWarning || hasWorkflowWarning || hasContractWarning));
|
|
1278
1318
|
const executionMode = decision === 'deny'
|
|
1279
1319
|
|| hasCostBlock
|
|
1320
|
+
|| hasFinancialBlock
|
|
1280
1321
|
|| hasWorkflowBlock
|
|
1281
1322
|
|| hasContractBlock
|
|
1282
1323
|
? 'blocked'
|
|
@@ -1302,7 +1343,7 @@ function buildDecisionControl({
|
|
|
1302
1343
|
decisionOwner,
|
|
1303
1344
|
reversibility,
|
|
1304
1345
|
deliberation,
|
|
1305
|
-
requiresHumanApproval: (executionMode === 'checkpoint_required' && decisionOwner !== 'agent') || hasCostBlock || hasWorkflowBlock || hasContractBlock,
|
|
1346
|
+
requiresHumanApproval: (executionMode === 'checkpoint_required' && decisionOwner !== 'agent') || hasCostBlock || hasFinancialBlock || hasWorkflowBlock || hasContractBlock,
|
|
1306
1347
|
recommendedAction: executionMode === 'blocked'
|
|
1307
1348
|
? 'halt'
|
|
1308
1349
|
: executionMode === 'checkpoint_required'
|
|
@@ -1379,8 +1420,8 @@ function hasSoftControlWarning({ workflowContract, workflowControl, costControl,
|
|
|
1379
1420
|
|| (learnedRecall && riskScore >= 0.34);
|
|
1380
1421
|
}
|
|
1381
1422
|
|
|
1382
|
-
function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl, workflowContract, actionProfile }) {
|
|
1383
|
-
if (costControl?.mode === 'block' || workflowControl?.mode === 'block' || workflowContract?.mode === 'block') {
|
|
1423
|
+
function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, financialControl, workflowControl, workflowContract, actionProfile }) {
|
|
1424
|
+
if (financialControl?.mode === 'block' || costControl?.mode === 'block' || workflowControl?.mode === 'block' || workflowContract?.mode === 'block') {
|
|
1384
1425
|
return 'deny';
|
|
1385
1426
|
}
|
|
1386
1427
|
|
|
@@ -1406,7 +1447,7 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
|
|
|
1406
1447
|
return 'deny';
|
|
1407
1448
|
}
|
|
1408
1449
|
|
|
1409
|
-
if (actionProfile?.economicAction || (actionProfile?.backgroundAgent && riskScore >= 0.3)) {
|
|
1450
|
+
if ((actionProfile?.economicAction && financialControl?.mode !== 'allow') || (actionProfile?.backgroundAgent && riskScore >= 0.3)) {
|
|
1410
1451
|
return 'warn';
|
|
1411
1452
|
}
|
|
1412
1453
|
|
|
@@ -1448,7 +1489,26 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1448
1489
|
const affectedFiles = Array.isArray(options.affectedFiles)
|
|
1449
1490
|
? options.affectedFiles.map((filePath) => normalizePosix(filePath)).filter(Boolean)
|
|
1450
1491
|
: collectAffectedFiles(normalizedToolName, normalizedToolInput, repoRoot);
|
|
1451
|
-
|
|
1492
|
+
let actionProfile = classifyActionProfile(normalizedToolInput);
|
|
1493
|
+
const financialControl = evaluateFinancialControl({
|
|
1494
|
+
toolName: normalizedToolName,
|
|
1495
|
+
toolInput: normalizedToolInput,
|
|
1496
|
+
actionProfile,
|
|
1497
|
+
costControl,
|
|
1498
|
+
budget: options.budget || toolInput.budget || {},
|
|
1499
|
+
financialControl: options.financialControl || toolInput.financialControl,
|
|
1500
|
+
}, getFinancialControlRuntimeOptions({
|
|
1501
|
+
feedbackDir: options.feedbackDir
|
|
1502
|
+
|| process.env.THUMBGATE_FEEDBACK_DIR
|
|
1503
|
+
|| (repoRoot ? path.join(repoRoot, '.thumbgate') : null),
|
|
1504
|
+
}));
|
|
1505
|
+
// The financial detector has additional fail-closed classifiers for opaque
|
|
1506
|
+
// browser/computer mutations. Reflect its verdict in the shared action
|
|
1507
|
+
// profile so downstream learning, risk scoring, and reporting cannot treat
|
|
1508
|
+
// a blocked financial action as non-economic.
|
|
1509
|
+
if (financialControl.economicAction && !actionProfile.economicAction) {
|
|
1510
|
+
actionProfile = { ...actionProfile, economicAction: true };
|
|
1511
|
+
}
|
|
1452
1512
|
const highRiskAction = isHighRiskAction(normalizedToolName, normalizedToolInput, affectedFiles);
|
|
1453
1513
|
const baseBranch = options.baseBranch
|
|
1454
1514
|
|| (governanceState.branchGovernance && governanceState.branchGovernance.baseBranch)
|
|
@@ -1540,6 +1600,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1540
1600
|
taskScopeViolation,
|
|
1541
1601
|
protectedSurface: protectedSurfaceForRisk,
|
|
1542
1602
|
costControl,
|
|
1603
|
+
financialControl,
|
|
1543
1604
|
workflowControl,
|
|
1544
1605
|
workflowContract,
|
|
1545
1606
|
actionProfile,
|
|
@@ -1567,6 +1628,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1567
1628
|
},
|
|
1568
1629
|
command: normalizedToolInput.command || '',
|
|
1569
1630
|
costControl,
|
|
1631
|
+
financialControl,
|
|
1570
1632
|
workflowControl,
|
|
1571
1633
|
workflowContract,
|
|
1572
1634
|
actionProfile,
|
|
@@ -1580,6 +1642,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1580
1642
|
protectedSurface: protectedSurfaceForRisk,
|
|
1581
1643
|
normalizedAction,
|
|
1582
1644
|
costControl,
|
|
1645
|
+
financialControl,
|
|
1583
1646
|
workflowControl,
|
|
1584
1647
|
workflowContract,
|
|
1585
1648
|
actionProfile,
|
|
@@ -1593,6 +1656,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1593
1656
|
learnedPolicy,
|
|
1594
1657
|
executionSurface,
|
|
1595
1658
|
costControl,
|
|
1659
|
+
financialControl,
|
|
1596
1660
|
workflowControl,
|
|
1597
1661
|
workflowContract,
|
|
1598
1662
|
actionProfile,
|
|
@@ -1607,6 +1671,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1607
1671
|
toolName: normalizedToolName,
|
|
1608
1672
|
normalizedAction,
|
|
1609
1673
|
costControl,
|
|
1674
|
+
financialControl,
|
|
1610
1675
|
workflowControl,
|
|
1611
1676
|
workflowContract,
|
|
1612
1677
|
decision,
|
|
@@ -1643,6 +1708,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1643
1708
|
integrity,
|
|
1644
1709
|
protectedSurface: protectedSurfaceForRisk,
|
|
1645
1710
|
costControl,
|
|
1711
|
+
financialControl,
|
|
1646
1712
|
workflowControl,
|
|
1647
1713
|
workflowContract,
|
|
1648
1714
|
actionProfile,
|
package/server.json
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
"source": "github",
|
|
9
9
|
"url": "https://github.com/IgorGanapolsky/ThumbGate"
|
|
10
10
|
},
|
|
11
|
-
"version": "1.
|
|
11
|
+
"version": "1.34.0",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
16
16
|
"identifier": "thumbgate",
|
|
17
|
-
"version": "1.
|
|
17
|
+
"version": "1.34.0",
|
|
18
18
|
"runtimeHint": "npx",
|
|
19
19
|
"runtimeArguments": [
|
|
20
20
|
{
|
package/src/api/server.js
CHANGED
|
@@ -9453,6 +9453,7 @@ a{color:#8b9}</style></head><body><form class="card" method="post" action="/oaut
|
|
|
9453
9453
|
escalationId,
|
|
9454
9454
|
}, {
|
|
9455
9455
|
authenticatedActor,
|
|
9456
|
+
approvalSigningKey: humanReviewerConfig.key,
|
|
9456
9457
|
feedbackDir: requestFeedbackDir,
|
|
9457
9458
|
}));
|
|
9458
9459
|
} catch (error) {
|
|
@@ -10577,6 +10578,7 @@ a{color:#8b9}</style></head><body><form class="card" method="post" action="/oaut
|
|
|
10577
10578
|
params: body.params,
|
|
10578
10579
|
mcp: body.mcp,
|
|
10579
10580
|
mcpToolCall: body.mcpToolCall,
|
|
10581
|
+
financialControl: body.financialControl || body.financial_control,
|
|
10580
10582
|
budget: body.budget,
|
|
10581
10583
|
usage: body.usage,
|
|
10582
10584
|
};
|