thumbgate 1.16.3 → 1.16.4

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.
@@ -39,6 +39,9 @@ const DEFAULT_PROTECTED_FILE_GLOBS = [
39
39
  ];
40
40
  const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
41
41
  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
+ 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
+ const CUSTOMER_SYSTEM_PATTERN = /\b(?:crm|customer|email|hubspot|intercom|mailgun|resend|salesforce|support|zendesk)\b/i;
42
45
 
43
46
  const SURFACE_RULES = [
44
47
  { key: 'policy', pattern: /^(?:AGENTS\.md|CLAUDE(?:\.local)?\.md|GEMINI\.md|config\/gates\/|config\/mcp-allowlists\.json|scripts\/tool-registry\.js)/ },
@@ -234,6 +237,38 @@ function isHighRiskAction(toolName, toolInput = {}, affectedFiles = []) {
234
237
  return HIGH_RISK_BASH_PATTERN.test(String(toolInput.command || ''));
235
238
  }
236
239
 
240
+ function classifyActionProfile(toolInput = {}) {
241
+ const command = String(toolInput.command || '');
242
+ const metadata = toolInput && typeof toolInput.metadata === 'object' ? toolInput.metadata : {};
243
+ const source = String(toolInput.source || metadata.source || '');
244
+ const runType = String(toolInput.runType || metadata.runType || '');
245
+ const combined = [command, source, runType, metadata.context || ''].filter(Boolean).join(' ');
246
+
247
+ const backgroundAgent = Boolean(
248
+ toolInput.backgroundAgent === true
249
+ || toolInput.scheduled === true
250
+ || metadata.backgroundAgent === true
251
+ || metadata.scheduled === true
252
+ || BACKGROUND_AGENT_PATTERN.test(combined)
253
+ );
254
+ const economicAction = Boolean(
255
+ toolInput.economicAction === true
256
+ || metadata.economicAction === true
257
+ || ECONOMIC_ACTION_PATTERN.test(combined)
258
+ );
259
+ const customerSystemAction = Boolean(
260
+ toolInput.customerSystemAction === true
261
+ || metadata.customerSystemAction === true
262
+ || CUSTOMER_SYSTEM_PATTERN.test(combined)
263
+ );
264
+
265
+ return {
266
+ backgroundAgent,
267
+ economicAction,
268
+ customerSystemAction,
269
+ };
270
+ }
271
+
237
272
  function isProtectedApprovalRelevant(toolName, toolInput = {}) {
238
273
  if (EDIT_LIKE_TOOLS.has(toolName)) return true;
239
274
  if (toolName !== 'Bash') return false;
@@ -399,6 +434,7 @@ function scoreRisk({
399
434
  protectedSurface,
400
435
  costControl,
401
436
  workflowControl,
437
+ actionProfile,
402
438
  }) {
403
439
  const drivers = [];
404
440
  const commandInfo = classifyCommand(toolInput.command || '');
@@ -406,6 +442,21 @@ function scoreRisk({
406
442
  if (isHighRiskAction(toolName, toolInput, affectedFiles)) {
407
443
  addDriver(drivers, 'high_risk_action', 0.18, 'Command or edit pattern is classified as high risk.');
408
444
  }
445
+ if (actionProfile && actionProfile.backgroundAgent) {
446
+ addDriver(drivers, 'background_agent', 0.18, 'Background or scheduled agent context reduces real-time human supervision.');
447
+ }
448
+ if (actionProfile && actionProfile.economicAction) {
449
+ addDriver(drivers, 'economic_action', 0.24, 'Action appears to touch billing, refunds, invoices, payouts, or subscriptions.');
450
+ }
451
+ if (actionProfile && actionProfile.customerSystemAction) {
452
+ addDriver(drivers, 'customer_system_action', 0.14, 'Action appears to touch customer-facing systems or communications.');
453
+ }
454
+ if (actionProfile && actionProfile.backgroundAgent && actionProfile.economicAction) {
455
+ addDriver(drivers, 'background_economic_combo', 0.08, 'Background autonomy plus money movement needs a tighter checkpoint.');
456
+ }
457
+ if (actionProfile && actionProfile.backgroundAgent && actionProfile.customerSystemAction) {
458
+ addDriver(drivers, 'background_customer_combo', 0.06, 'Background autonomy plus customer systems raises downstream trust risk.');
459
+ }
409
460
  if (commandInfo.isPrCreate
410
461
  || commandInfo.isPrMerge
411
462
  || commandInfo.isWorkflowRun
@@ -612,6 +663,7 @@ function buildEvidence({
612
663
  normalizedAction,
613
664
  costControl,
614
665
  workflowControl,
666
+ actionProfile,
615
667
  }) {
616
668
  const evidence = [];
617
669
  if (normalizedAction && normalizedAction.provider !== 'unknown') {
@@ -631,6 +683,15 @@ function buildEvidence({
631
683
  evidence.push(`Workflow control ${workflowControl.mode}: ${workflowControl.reasons.join(' ')}`);
632
684
  }
633
685
  }
686
+ if (actionProfile && actionProfile.backgroundAgent) {
687
+ evidence.push('Background or scheduled agent context detected for this action.');
688
+ }
689
+ if (actionProfile && actionProfile.economicAction) {
690
+ evidence.push('Economic action keywords detected (billing, refunds, payouts, invoices, or subscriptions).');
691
+ }
692
+ if (actionProfile && actionProfile.customerSystemAction) {
693
+ evidence.push('Customer-system keywords detected (email, CRM, or support surface).');
694
+ }
634
695
  if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
635
696
  evidence.push(`Memory guard predicted ${memoryGuard.mode}: ${memoryGuard.reason}`);
636
697
  }
@@ -740,6 +801,7 @@ function buildRemediations({
740
801
  executionSurface,
741
802
  costControl,
742
803
  workflowControl,
804
+ actionProfile,
743
805
  }) {
744
806
  const remediations = [];
745
807
  const seen = new Set();
@@ -767,6 +829,30 @@ function buildRemediations({
767
829
  );
768
830
  }
769
831
  addIntegrityRemediations(push, integrity);
832
+ if (actionProfile && actionProfile.backgroundAgent) {
833
+ push(
834
+ 'background_agent_checkpoint',
835
+ 'Add an operator checkpoint',
836
+ 'Pause the background run at a review step before it continues into code, deploy, money, or customer actions.',
837
+ 'Queued autonomy needs an explicit approval boundary before irreversible work proceeds.'
838
+ );
839
+ }
840
+ if (actionProfile && actionProfile.economicAction) {
841
+ push(
842
+ 'economic_action_approval',
843
+ 'Require operator approval for money movement',
844
+ 'Require an explicit operator checkpoint before refunds, payouts, invoice sends, or subscription changes execute.',
845
+ 'Money-touching actions are costly to reverse and need a clear human owner.'
846
+ );
847
+ }
848
+ if (actionProfile && actionProfile.customerSystemAction) {
849
+ push(
850
+ 'customer_system_guardrail',
851
+ 'Add a customer-system hold point',
852
+ 'Review customer-facing email, CRM, or support actions before sending or mutating external state.',
853
+ 'Customer-facing actions can create trust damage even when the underlying code path is correct.'
854
+ );
855
+ }
770
856
  if (memoryGuard && memoryGuard.mode && memoryGuard.mode !== 'allow') {
771
857
  push(
772
858
  'retrieve_lessons',
@@ -850,6 +936,16 @@ function buildReasoning(report) {
850
936
  lines.push(`Deliberation policy: ${report.decisionControl.deliberation.mode} before final approval.`);
851
937
  }
852
938
  }
939
+ if (report.actionProfile) {
940
+ const activeFlags = [
941
+ report.actionProfile.backgroundAgent ? 'background agent' : null,
942
+ report.actionProfile.economicAction ? 'economic action' : null,
943
+ report.actionProfile.customerSystemAction ? 'customer system' : null,
944
+ ].filter(Boolean);
945
+ if (activeFlags.length) {
946
+ lines.push(`Action profile: ${activeFlags.join(', ')}.`);
947
+ }
948
+ }
853
949
  if (report.learnedPolicy && report.learnedPolicy.enabled && report.learnedPolicy.prediction) {
854
950
  lines.push(
855
951
  `Learned policy predicted ${report.learnedPolicy.prediction.label} (${report.learnedPolicy.prediction.confidence}).`
@@ -885,7 +981,7 @@ function getSentinelActionType(toolName) {
885
981
  return '';
886
982
  }
887
983
 
888
- function classifyReversibility({ command, blastRadius, integrity, protectedSurface }) {
984
+ function classifyReversibility({ command, blastRadius, integrity, protectedSurface, actionProfile }) {
889
985
  const text = String(command || '');
890
986
  const blockers = integrity && Array.isArray(integrity.blockers) ? integrity.blockers : [];
891
987
  const destructiveCommand = /\bgit\s+push\b.*(?:--force|-f)\b/i.test(text)
@@ -901,10 +997,16 @@ function classifyReversibility({ command, blastRadius, integrity, protectedSurfa
901
997
  ? protectedSurface.unapprovedProtectedFiles.length > 0
902
998
  : false;
903
999
  const hardBlockers = blockers.some((blocker) => /publish|merge|release|protected/i.test(String(blocker.code || '')));
1000
+ const economicAction = Boolean(actionProfile && actionProfile.economicAction);
1001
+ const customerSystemAction = Boolean(actionProfile && actionProfile.customerSystemAction);
1002
+ const backgroundAgent = Boolean(actionProfile && actionProfile.backgroundAgent);
904
1003
 
905
- if (destructiveCommand || releaseSensitive || unapprovedProtected || hardBlockers) {
1004
+ if (destructiveCommand || releaseSensitive || unapprovedProtected || hardBlockers || economicAction) {
906
1005
  return 'one_way_door';
907
1006
  }
1007
+ if ((backgroundAgent && customerSystemAction) || customerSystemAction) {
1008
+ return 'reviewable';
1009
+ }
908
1010
  if ((blastRadius && blastRadius.fileCount >= 4) || (blastRadius && blastRadius.surfaceCount >= 2)) {
909
1011
  return 'reviewable';
910
1012
  }
@@ -966,12 +1068,14 @@ function buildDecisionControl({
966
1068
  protectedSurface,
967
1069
  costControl,
968
1070
  workflowControl,
1071
+ actionProfile,
969
1072
  }) {
970
1073
  const reversibility = classifyReversibility({
971
1074
  command,
972
1075
  blastRadius,
973
1076
  integrity,
974
1077
  protectedSurface,
1078
+ actionProfile,
975
1079
  });
976
1080
  const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
977
1081
  const hasCostWarning = Boolean(costControl && costControl.mode === 'warn');
@@ -1020,7 +1124,7 @@ function buildDecisionControl({
1020
1124
  };
1021
1125
  }
1022
1126
 
1023
- function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl }) {
1127
+ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl, actionProfile }) {
1024
1128
  const hasOperationalBlockers = Boolean(integrity && Array.isArray(integrity.blockers) && integrity.blockers.length > 0);
1025
1129
  if (costControl && costControl.mode === 'block') {
1026
1130
  return 'deny';
@@ -1067,6 +1171,9 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
1067
1171
  || blastRadius.unapprovedProtectedFiles > 0
1068
1172
  )
1069
1173
  );
1174
+ const economicAction = Boolean(actionProfile && actionProfile.economicAction);
1175
+ const backgroundAgent = Boolean(actionProfile && actionProfile.backgroundAgent);
1176
+ const customerSystemAction = Boolean(actionProfile && actionProfile.customerSystemAction);
1070
1177
 
1071
1178
  if (lowRiskHandoff) {
1072
1179
  return 'allow';
@@ -1074,6 +1181,9 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
1074
1181
  if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
1075
1182
  return 'deny';
1076
1183
  }
1184
+ if (economicAction || (backgroundAgent && customerSystemAction) || (backgroundAgent && riskScore >= 0.3)) {
1185
+ return 'warn';
1186
+ }
1077
1187
  if ((workflowControl && workflowControl.mode === 'warn') || (costControl && costControl.mode === 'warn') || riskScore >= 0.45 || (learnedWarning && riskScore >= 0.3) || (learnedRecall && riskScore >= 0.34)) {
1078
1188
  return 'warn';
1079
1189
  }
@@ -1112,6 +1222,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1112
1222
  const affectedFiles = Array.isArray(options.affectedFiles)
1113
1223
  ? options.affectedFiles.map((filePath) => normalizePosix(filePath)).filter(Boolean)
1114
1224
  : collectAffectedFiles(normalizedToolName, normalizedToolInput, repoRoot);
1225
+ const actionProfile = classifyActionProfile(normalizedToolInput);
1115
1226
  const highRiskAction = isHighRiskAction(normalizedToolName, normalizedToolInput, affectedFiles);
1116
1227
  const baseBranch = options.baseBranch
1117
1228
  || (governanceState.branchGovernance && governanceState.branchGovernance.baseBranch)
@@ -1174,6 +1285,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1174
1285
  protectedSurface: protectedSurfaceForRisk,
1175
1286
  costControl,
1176
1287
  workflowControl,
1288
+ actionProfile,
1177
1289
  });
1178
1290
  const executionSurface = buildDockerSandboxPlan({
1179
1291
  toolName: normalizedToolName,
@@ -1199,6 +1311,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1199
1311
  command: normalizedToolInput.command || '',
1200
1312
  costControl,
1201
1313
  workflowControl,
1314
+ actionProfile,
1202
1315
  });
1203
1316
  const evidence = buildEvidence({
1204
1317
  integrity,
@@ -1210,6 +1323,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1210
1323
  normalizedAction,
1211
1324
  costControl,
1212
1325
  workflowControl,
1326
+ actionProfile,
1213
1327
  });
1214
1328
  const remediations = buildRemediations({
1215
1329
  integrity,
@@ -1221,6 +1335,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1221
1335
  executionSurface,
1222
1336
  costControl,
1223
1337
  workflowControl,
1338
+ actionProfile,
1224
1339
  });
1225
1340
  const summary = decision === 'allow'
1226
1341
  ? 'No predictive workflow blockers detected.'
@@ -1242,6 +1357,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1242
1357
  evidence,
1243
1358
  remediations,
1244
1359
  executionSurface,
1360
+ actionProfile,
1245
1361
  memoryGuard,
1246
1362
  learnedPolicy,
1247
1363
  taskScopeViolation,
@@ -1267,6 +1383,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
1267
1383
  protectedSurface: protectedSurfaceForRisk,
1268
1384
  costControl,
1269
1385
  workflowControl,
1386
+ actionProfile,
1270
1387
  });
1271
1388
  report.reasoning = buildReasoning(report);
1272
1389
  return report;
@@ -1282,6 +1399,7 @@ module.exports = {
1282
1399
  buildReasoning,
1283
1400
  buildRemediations,
1284
1401
  buildTaskScopeViolation,
1402
+ classifyActionProfile,
1285
1403
  classifySurface,
1286
1404
  collectAffectedFiles,
1287
1405
  evaluateWorkflowSentinel,