thumbgate 1.26.8 → 1.27.3
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/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +2 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +44 -31
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/mcp/server-stdio.js +41 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +184 -8
- package/bin/dashboard-cli.js +7 -0
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +60 -0
- package/config/mcp-allowlists.json +8 -7
- package/config/model-candidates.json +71 -6
- package/package.json +28 -12
- package/public/about.html +162 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-plugin.html +66 -14
- package/public/compare.html +2 -2
- package/public/dashboard.html +224 -36
- package/public/guide.html +2 -2
- package/public/index.html +122 -40
- package/public/learn.html +70 -0
- package/public/lessons.html +129 -6
- package/public/numbers.html +2 -2
- package/public/pricing.html +28 -23
- package/public/pro.html +3 -3
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/classifier-routing.js +130 -0
- package/scripts/cli-schema.js +26 -0
- package/scripts/commercial-offer.js +10 -2
- package/scripts/dashboard-chat.js +199 -51
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/gates-engine.js +301 -67
- package/scripts/hybrid-feedback-context.js +141 -7
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plausible-domain-config.js +86 -0
- package/scripts/plausible-server-events.js +4 -2
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/rate-limiter.js +2 -2
- package/scripts/repeat-metric.js +28 -12
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +80 -0
- package/scripts/seo-gsd.js +113 -0
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/tool-registry.js +37 -0
- package/scripts/workflow-sentinel.js +282 -54
- package/src/api/server.js +466 -60
- package/.claude-plugin/marketplace.json +0 -85
|
@@ -64,11 +64,14 @@ function loadJson(filePath) {
|
|
|
64
64
|
function loadGovernanceState() {
|
|
65
65
|
const raw = loadJson(GOVERNANCE_STATE_PATH);
|
|
66
66
|
return {
|
|
67
|
-
taskScope: raw
|
|
68
|
-
protectedApprovals: Array.isArray(raw
|
|
69
|
-
branchGovernance: raw
|
|
67
|
+
taskScope: raw?.taskScope && typeof raw.taskScope === 'object' ? raw.taskScope : null,
|
|
68
|
+
protectedApprovals: Array.isArray(raw?.protectedApprovals) ? raw.protectedApprovals : [],
|
|
69
|
+
branchGovernance: raw?.branchGovernance && typeof raw.branchGovernance === 'object'
|
|
70
70
|
? raw.branchGovernance
|
|
71
71
|
: null,
|
|
72
|
+
workflowContract: raw?.workflowContract && typeof raw.workflowContract === 'object'
|
|
73
|
+
? raw.workflowContract
|
|
74
|
+
: null,
|
|
72
75
|
};
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -361,6 +364,131 @@ function formatFileList(files, limit = 5) {
|
|
|
361
364
|
return `${items.slice(0, limit).join(', ')} (+${items.length - limit} more)`;
|
|
362
365
|
}
|
|
363
366
|
|
|
367
|
+
function normalizeStringList(values) {
|
|
368
|
+
if (!Array.isArray(values)) return [];
|
|
369
|
+
return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function normalizeWorkflowContract(contract) {
|
|
373
|
+
if (!contract || typeof contract !== 'object') return null;
|
|
374
|
+
return {
|
|
375
|
+
workflowId: String(contract.workflowId || contract.workflow_id || '').trim() || null,
|
|
376
|
+
allowedBranches: normalizeStringList(contract.allowedBranches || contract.allowed_branches),
|
|
377
|
+
blockedActions: normalizeStringList(contract.blockedActions || contract.blocked_actions),
|
|
378
|
+
requiredEvidence: normalizeStringList(contract.requiredEvidence || contract.required_evidence),
|
|
379
|
+
completionGate: String(contract.completionGate || contract.completion_gate || '').trim() || null,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function commandMatchesPattern(command, pattern) {
|
|
384
|
+
const text = String(command || '');
|
|
385
|
+
const raw = String(pattern || '').trim();
|
|
386
|
+
if (!text || !raw) return false;
|
|
387
|
+
try {
|
|
388
|
+
return new RegExp(raw, 'i').test(text);
|
|
389
|
+
} catch {
|
|
390
|
+
return text.toLowerCase().includes(raw.toLowerCase());
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const COMPLETION_ACTION_PATTERNS = [
|
|
395
|
+
/\bgit\s+(?:commit|push)\b/i,
|
|
396
|
+
/\bgh\s+pr\s+(?:create|merge)\b/i,
|
|
397
|
+
/\bgh\s+release\s+create\b/i,
|
|
398
|
+
/\bnpm\s+publish\b/i,
|
|
399
|
+
/\byarn\s+publish\b/i,
|
|
400
|
+
/\bpnpm\s+publish\b/i,
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
function isCompletionLikeAction(command) {
|
|
404
|
+
const text = String(command || '');
|
|
405
|
+
return COMPLETION_ACTION_PATTERNS.some((pattern) => pattern.test(text));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function collectEvidenceLabels(toolInput = {}, options = {}) {
|
|
409
|
+
const values = [];
|
|
410
|
+
for (const source of [
|
|
411
|
+
toolInput.evidence,
|
|
412
|
+
toolInput.evidenceLabels,
|
|
413
|
+
toolInput.proof,
|
|
414
|
+
toolInput.proofArtifacts,
|
|
415
|
+
toolInput.requiredEvidenceSatisfied,
|
|
416
|
+
options.evidence,
|
|
417
|
+
options.evidenceLabels,
|
|
418
|
+
options.proofArtifacts,
|
|
419
|
+
]) {
|
|
420
|
+
if (Array.isArray(source)) values.push(...source);
|
|
421
|
+
else if (source && typeof source === 'object') values.push(...Object.keys(source).filter((key) => source[key]));
|
|
422
|
+
else if (typeof source === 'string') values.push(...source.split(/[,;\n]/));
|
|
423
|
+
}
|
|
424
|
+
return normalizeStringList(values).map((value) => value.toLowerCase());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function evaluateWorkflowContract(contractInput, context = {}) {
|
|
428
|
+
const contract = normalizeWorkflowContract(contractInput);
|
|
429
|
+
if (!contract) {
|
|
430
|
+
return {
|
|
431
|
+
active: false,
|
|
432
|
+
contract: null,
|
|
433
|
+
violations: [],
|
|
434
|
+
mode: 'allow',
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const violations = [];
|
|
438
|
+
const command = String(context.command || '');
|
|
439
|
+
const currentBranch = String(context.currentBranch || '').trim();
|
|
440
|
+
|
|
441
|
+
const blockedAction = contract.blockedActions.find((pattern) => commandMatchesPattern(command, pattern));
|
|
442
|
+
if (blockedAction) {
|
|
443
|
+
violations.push({
|
|
444
|
+
code: 'blocked_action',
|
|
445
|
+
severity: 'block',
|
|
446
|
+
message: `Workflow contract blocks this action: ${blockedAction}`,
|
|
447
|
+
pattern: blockedAction,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (contract.allowedBranches.length > 0 && currentBranch) {
|
|
452
|
+
const allowed = contract.allowedBranches.some((glob) => matchesAnyGlob(currentBranch, [glob]));
|
|
453
|
+
if (!allowed) {
|
|
454
|
+
violations.push({
|
|
455
|
+
code: 'branch_outside_contract',
|
|
456
|
+
severity: 'warn',
|
|
457
|
+
message: `Current branch ${currentBranch} is outside the workflow contract.`,
|
|
458
|
+
currentBranch,
|
|
459
|
+
allowedBranches: contract.allowedBranches,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (contract.requiredEvidence.length > 0 && isCompletionLikeAction(command)) {
|
|
465
|
+
const evidenceLabels = collectEvidenceLabels(context.toolInput || {}, context.options || {});
|
|
466
|
+
const missing = contract.requiredEvidence.filter((label) => !evidenceLabels.includes(label.toLowerCase()));
|
|
467
|
+
if (missing.length > 0) {
|
|
468
|
+
violations.push({
|
|
469
|
+
code: 'missing_required_evidence',
|
|
470
|
+
severity: 'block',
|
|
471
|
+
message: `Workflow completion is missing required evidence: ${missing.join(', ')}`,
|
|
472
|
+
missingEvidence: missing,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const hasBlock = violations.some((violation) => violation.severity === 'block');
|
|
478
|
+
let mode = 'allow';
|
|
479
|
+
if (hasBlock) {
|
|
480
|
+
mode = 'block';
|
|
481
|
+
} else if (violations.length > 0) {
|
|
482
|
+
mode = 'warn';
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
active: true,
|
|
486
|
+
contract,
|
|
487
|
+
violations,
|
|
488
|
+
mode,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
364
492
|
function severityFromScore(score) {
|
|
365
493
|
if (score >= 0.8) return 'critical';
|
|
366
494
|
if (score >= 0.55) return 'high';
|
|
@@ -434,6 +562,7 @@ function scoreRisk({
|
|
|
434
562
|
protectedSurface,
|
|
435
563
|
costControl,
|
|
436
564
|
workflowControl,
|
|
565
|
+
workflowContract,
|
|
437
566
|
actionProfile,
|
|
438
567
|
}) {
|
|
439
568
|
const drivers = [];
|
|
@@ -590,7 +719,18 @@ function scoreRisk({
|
|
|
590
719
|
);
|
|
591
720
|
}
|
|
592
721
|
}
|
|
593
|
-
if (
|
|
722
|
+
if (workflowContract?.active && workflowContract.violations.length > 0) {
|
|
723
|
+
for (const violation of workflowContract.violations) {
|
|
724
|
+
addDriver(
|
|
725
|
+
drivers,
|
|
726
|
+
`workflow_contract_${violation.code}`,
|
|
727
|
+
violation.severity === 'block' ? 0.38 : 0.18,
|
|
728
|
+
violation.message,
|
|
729
|
+
{ workflowId: workflowContract.contract?.workflowId }
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (memoryGuard?.mode && memoryGuard.mode !== 'allow') {
|
|
594
734
|
addDriver(
|
|
595
735
|
drivers,
|
|
596
736
|
'memory_recurrence',
|
|
@@ -599,7 +739,7 @@ function scoreRisk({
|
|
|
599
739
|
{ mode: memoryGuard.mode }
|
|
600
740
|
);
|
|
601
741
|
}
|
|
602
|
-
if (learnedPolicy
|
|
742
|
+
if (learnedPolicy?.enabled && learnedPolicy.prediction) {
|
|
603
743
|
const confidence = learnedPolicy.prediction.confidence || 0;
|
|
604
744
|
const label = learnedPolicy.prediction.label;
|
|
605
745
|
if (label === 'deny' && confidence >= 0.6) {
|
|
@@ -663,6 +803,7 @@ function buildEvidence({
|
|
|
663
803
|
normalizedAction,
|
|
664
804
|
costControl,
|
|
665
805
|
workflowControl,
|
|
806
|
+
workflowContract,
|
|
666
807
|
actionProfile,
|
|
667
808
|
}) {
|
|
668
809
|
const evidence = [];
|
|
@@ -683,10 +824,17 @@ function buildEvidence({
|
|
|
683
824
|
evidence.push(`Workflow control ${workflowControl.mode}: ${workflowControl.reasons.join(' ')}`);
|
|
684
825
|
}
|
|
685
826
|
}
|
|
686
|
-
if (
|
|
827
|
+
if (workflowContract?.active) {
|
|
828
|
+
const workflowId = workflowContract.contract?.workflowId || 'unnamed';
|
|
829
|
+
evidence.push(`Workflow contract active: ${workflowId}.`);
|
|
830
|
+
for (const violation of workflowContract.violations.slice(0, 3)) {
|
|
831
|
+
evidence.push(`Workflow contract ${violation.severity}: ${violation.message}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (actionProfile?.backgroundAgent) {
|
|
687
835
|
evidence.push('Background or scheduled agent context detected for this action.');
|
|
688
836
|
}
|
|
689
|
-
if (actionProfile
|
|
837
|
+
if (actionProfile?.economicAction) {
|
|
690
838
|
evidence.push('Economic action keywords detected (billing, refunds, payouts, invoices, or subscriptions).');
|
|
691
839
|
}
|
|
692
840
|
if (actionProfile && actionProfile.customerSystemAction) {
|
|
@@ -801,6 +949,7 @@ function buildRemediations({
|
|
|
801
949
|
executionSurface,
|
|
802
950
|
costControl,
|
|
803
951
|
workflowControl,
|
|
952
|
+
workflowContract,
|
|
804
953
|
actionProfile,
|
|
805
954
|
}) {
|
|
806
955
|
const remediations = [];
|
|
@@ -895,7 +1044,7 @@ function buildRemediations({
|
|
|
895
1044
|
'Isolated execution limits host damage when a high-risk local action goes wrong.'
|
|
896
1045
|
);
|
|
897
1046
|
}
|
|
898
|
-
if (costControl
|
|
1047
|
+
if (costControl?.mode && costControl.mode !== 'allow') {
|
|
899
1048
|
push(
|
|
900
1049
|
'reduce_model_budget',
|
|
901
1050
|
'Reduce model budget before execution',
|
|
@@ -903,6 +1052,33 @@ function buildRemediations({
|
|
|
903
1052
|
'High token or cost estimates should be reviewed before the model/tool loop continues.'
|
|
904
1053
|
);
|
|
905
1054
|
}
|
|
1055
|
+
if (workflowContract?.active && workflowContract.violations.length > 0) {
|
|
1056
|
+
const codes = new Set(workflowContract.violations.map((violation) => violation.code));
|
|
1057
|
+
if (codes.has('missing_required_evidence')) {
|
|
1058
|
+
push(
|
|
1059
|
+
'attach_workflow_evidence',
|
|
1060
|
+
'Attach workflow evidence before completion',
|
|
1061
|
+
'Attach every required evidence label from the workflow contract before commit, push, PR, merge, release, or publish.',
|
|
1062
|
+
'Deterministic workflows should not claim completion until the run contract is proven.'
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (codes.has('branch_outside_contract')) {
|
|
1066
|
+
push(
|
|
1067
|
+
'switch_to_contract_branch',
|
|
1068
|
+
'Move to an allowed workflow branch',
|
|
1069
|
+
'Switch to a branch allowed by the workflow contract or update the contract before retrying.',
|
|
1070
|
+
'Workflow contracts define where repeatable agent runs are allowed to mutate state.'
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
if (codes.has('blocked_action')) {
|
|
1074
|
+
push(
|
|
1075
|
+
'remove_blocked_workflow_action',
|
|
1076
|
+
'Remove blocked workflow action',
|
|
1077
|
+
'Change the workflow step or request an explicit contract update before retrying this action.',
|
|
1078
|
+
'The run contract blocks this action before execution.'
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
906
1082
|
if (workflowControl && workflowControl.mode && workflowControl.mode !== 'allow') {
|
|
907
1083
|
push(
|
|
908
1084
|
'add_environment_inspection',
|
|
@@ -1068,6 +1244,7 @@ function buildDecisionControl({
|
|
|
1068
1244
|
protectedSurface,
|
|
1069
1245
|
costControl,
|
|
1070
1246
|
workflowControl,
|
|
1247
|
+
workflowContract,
|
|
1071
1248
|
actionProfile,
|
|
1072
1249
|
}) {
|
|
1073
1250
|
const reversibility = classifyReversibility({
|
|
@@ -1077,16 +1254,19 @@ function buildDecisionControl({
|
|
|
1077
1254
|
protectedSurface,
|
|
1078
1255
|
actionProfile,
|
|
1079
1256
|
});
|
|
1080
|
-
const hasOperationalBlockers = Boolean(integrity
|
|
1081
|
-
const hasCostWarning =
|
|
1082
|
-
const hasCostBlock =
|
|
1083
|
-
const hasWorkflowWarning =
|
|
1084
|
-
const hasWorkflowBlock =
|
|
1257
|
+
const hasOperationalBlockers = Boolean(integrity?.blockers?.length);
|
|
1258
|
+
const hasCostWarning = costControl?.mode === 'warn';
|
|
1259
|
+
const hasCostBlock = costControl?.mode === 'block';
|
|
1260
|
+
const hasWorkflowWarning = workflowControl?.mode === 'warn';
|
|
1261
|
+
const hasWorkflowBlock = workflowControl?.mode === 'block';
|
|
1262
|
+
const hasContractWarning = workflowContract?.mode === 'warn';
|
|
1263
|
+
const hasContractBlock = workflowContract?.mode === 'block';
|
|
1085
1264
|
const requiresCheckpoint = decision === 'warn'
|
|
1086
|
-
|| (decision === 'allow' && (reversibility !== 'two_way_door' || hasOperationalBlockers || hasCostWarning || hasWorkflowWarning));
|
|
1265
|
+
|| (decision === 'allow' && (reversibility !== 'two_way_door' || hasOperationalBlockers || hasCostWarning || hasWorkflowWarning || hasContractWarning));
|
|
1087
1266
|
const executionMode = decision === 'deny'
|
|
1088
1267
|
|| hasCostBlock
|
|
1089
1268
|
|| hasWorkflowBlock
|
|
1269
|
+
|| hasContractBlock
|
|
1090
1270
|
? 'blocked'
|
|
1091
1271
|
: requiresCheckpoint
|
|
1092
1272
|
? 'checkpoint_required'
|
|
@@ -1110,7 +1290,7 @@ function buildDecisionControl({
|
|
|
1110
1290
|
decisionOwner,
|
|
1111
1291
|
reversibility,
|
|
1112
1292
|
deliberation,
|
|
1113
|
-
requiresHumanApproval: (executionMode === 'checkpoint_required' && decisionOwner !== 'agent') || hasCostBlock || hasWorkflowBlock,
|
|
1293
|
+
requiresHumanApproval: (executionMode === 'checkpoint_required' && decisionOwner !== 'agent') || hasCostBlock || hasWorkflowBlock || hasContractBlock,
|
|
1114
1294
|
recommendedAction: executionMode === 'blocked'
|
|
1115
1295
|
? 'halt'
|
|
1116
1296
|
: executionMode === 'checkpoint_required'
|
|
@@ -1124,46 +1304,51 @@ function buildDecisionControl({
|
|
|
1124
1304
|
};
|
|
1125
1305
|
}
|
|
1126
1306
|
|
|
1127
|
-
function
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
&&
|
|
1140
|
-
&&
|
|
1141
|
-
);
|
|
1142
|
-
const learnedWarning = Boolean(
|
|
1143
|
-
learnedPrediction
|
|
1144
|
-
&& ['warn', 'verify', 'deny'].includes(learnedPrediction.label)
|
|
1145
|
-
&& learnedPrediction.confidence >= 0.3
|
|
1146
|
-
);
|
|
1147
|
-
const learnedRecall = Boolean(
|
|
1148
|
-
learnedPrediction
|
|
1149
|
-
&& learnedPrediction.label === 'recall'
|
|
1150
|
-
&& learnedPrediction.confidence >= 0.3
|
|
1307
|
+
function isDestructiveBypass(command) {
|
|
1308
|
+
return /\bgit\s+push\b.*(?:--force|-f)\b/i.test(command)
|
|
1309
|
+
|| /\bgh\s+pr\s+merge\b.*--admin\b/i.test(command);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function getLearnedPrediction(learnedPolicy) {
|
|
1313
|
+
return learnedPolicy?.enabled ? learnedPolicy.prediction : null;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function learnedPredictionMatches(prediction, labels, minConfidence) {
|
|
1317
|
+
return Boolean(
|
|
1318
|
+
prediction
|
|
1319
|
+
&& labels.includes(prediction.label)
|
|
1320
|
+
&& prediction.confidence >= minConfidence
|
|
1151
1321
|
);
|
|
1152
|
-
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function isLowBlastRadius(blastRadius) {
|
|
1325
|
+
return blastRadius.fileCount <= 1
|
|
1153
1326
|
&& blastRadius.surfaceCount <= 1
|
|
1154
1327
|
&& blastRadius.releaseSensitiveFiles.length === 0
|
|
1155
1328
|
&& blastRadius.unapprovedProtectedFiles === 0;
|
|
1156
|
-
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function isLowRiskHandoff({
|
|
1332
|
+
command,
|
|
1333
|
+
destructiveBypass,
|
|
1334
|
+
learnedHardStop,
|
|
1335
|
+
blastRadius,
|
|
1336
|
+
hasOperationalBlockers,
|
|
1337
|
+
memoryGuard,
|
|
1338
|
+
riskScore,
|
|
1339
|
+
}) {
|
|
1340
|
+
return /\bgit\s+push\b|\bgh\s+pr\s+(?:create|merge)\b/i.test(command)
|
|
1157
1341
|
&& !destructiveBypass
|
|
1158
1342
|
&& !learnedHardStop
|
|
1159
|
-
&&
|
|
1343
|
+
&& isLowBlastRadius(blastRadius)
|
|
1160
1344
|
&& !hasOperationalBlockers
|
|
1161
|
-
&& memoryGuard
|
|
1162
|
-
&& memoryGuard.mode !== 'allow'
|
|
1345
|
+
&& memoryGuard?.mode !== 'allow'
|
|
1163
1346
|
&& riskScore <= 0.62;
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function isRepeatedHighBlast(memoryGuard, blastRadius) {
|
|
1350
|
+
return Boolean(
|
|
1351
|
+
memoryGuard?.mode === 'block'
|
|
1167
1352
|
&& (
|
|
1168
1353
|
blastRadius.severity === 'high'
|
|
1169
1354
|
|| blastRadius.severity === 'critical'
|
|
@@ -1171,25 +1356,49 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
|
|
|
1171
1356
|
|| blastRadius.unapprovedProtectedFiles > 0
|
|
1172
1357
|
)
|
|
1173
1358
|
);
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
function hasSoftControlWarning({ workflowContract, workflowControl, costControl, riskScore, learnedWarning, learnedRecall }) {
|
|
1362
|
+
return workflowContract?.mode === 'warn'
|
|
1363
|
+
|| workflowControl?.mode === 'warn'
|
|
1364
|
+
|| costControl?.mode === 'warn'
|
|
1365
|
+
|| riskScore >= 0.45
|
|
1366
|
+
|| (learnedWarning && riskScore >= 0.3)
|
|
1367
|
+
|| (learnedRecall && riskScore >= 0.34);
|
|
1368
|
+
}
|
|
1177
1369
|
|
|
1178
|
-
|
|
1370
|
+
function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blastRadius, command, costControl, workflowControl, workflowContract, actionProfile }) {
|
|
1371
|
+
if (costControl?.mode === 'block' || workflowControl?.mode === 'block' || workflowContract?.mode === 'block') {
|
|
1372
|
+
return 'deny';
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const hasOperationalBlockers = Boolean(integrity?.blockers?.length);
|
|
1376
|
+
const destructiveBypass = isDestructiveBypass(command);
|
|
1377
|
+
const learnedPrediction = getLearnedPrediction(learnedPolicy);
|
|
1378
|
+
const learnedHardStop = learnedPredictionMatches(learnedPrediction, ['deny'], 0.7);
|
|
1379
|
+
const learnedWarning = learnedPredictionMatches(learnedPrediction, ['warn', 'verify', 'deny'], 0.3);
|
|
1380
|
+
const learnedRecall = learnedPredictionMatches(learnedPrediction, ['recall'], 0.3);
|
|
1381
|
+
|
|
1382
|
+
if (isLowRiskHandoff({ command, destructiveBypass, learnedHardStop, blastRadius, hasOperationalBlockers, memoryGuard, riskScore })) {
|
|
1179
1383
|
return 'allow';
|
|
1180
1384
|
}
|
|
1385
|
+
|
|
1181
1386
|
// Background customer-system actions checkpoint (warn), never hard-deny.
|
|
1182
1387
|
// The checkpoint IS the mitigation — blocking outright prevents legitimate work.
|
|
1183
|
-
if (backgroundAgent && customerSystemAction) {
|
|
1388
|
+
if (actionProfile?.backgroundAgent && actionProfile?.customerSystemAction) {
|
|
1184
1389
|
return 'warn';
|
|
1185
1390
|
}
|
|
1391
|
+
|
|
1392
|
+
const repeatedHighBlast = isRepeatedHighBlast(memoryGuard, blastRadius);
|
|
1186
1393
|
if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
|
|
1187
1394
|
return 'deny';
|
|
1188
1395
|
}
|
|
1189
|
-
|
|
1396
|
+
|
|
1397
|
+
if (actionProfile?.economicAction || (actionProfile?.backgroundAgent && riskScore >= 0.3)) {
|
|
1190
1398
|
return 'warn';
|
|
1191
1399
|
}
|
|
1192
|
-
|
|
1400
|
+
|
|
1401
|
+
if (hasSoftControlWarning({ workflowContract, workflowControl, costControl, riskScore, learnedWarning, learnedRecall })) {
|
|
1193
1402
|
return 'warn';
|
|
1194
1403
|
}
|
|
1195
1404
|
return 'allow';
|
|
@@ -1238,11 +1447,24 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1238
1447
|
baseBranch,
|
|
1239
1448
|
command: normalizedToolInput.command,
|
|
1240
1449
|
changedFiles: affectedFiles,
|
|
1450
|
+
currentBranch: options.currentBranch
|
|
1451
|
+
|| normalizedToolInput.currentBranch
|
|
1452
|
+
|| normalizedToolInput.branchName
|
|
1453
|
+
|| normalizedToolInput.branch,
|
|
1241
1454
|
headSha: options.headSha || toolInput.headSha,
|
|
1242
1455
|
requirePrForReleaseSensitive: options.requirePrForReleaseSensitive === true,
|
|
1243
1456
|
requireVersionNotBehindBase: options.requireVersionNotBehindBase === true,
|
|
1244
1457
|
branchGovernance: governanceState.branchGovernance,
|
|
1245
1458
|
});
|
|
1459
|
+
const workflowContract = evaluateWorkflowContract(
|
|
1460
|
+
normalizedToolInput.workflowContract || options.workflowContract || governanceState.workflowContract,
|
|
1461
|
+
{
|
|
1462
|
+
command: normalizedToolInput.command || '',
|
|
1463
|
+
currentBranch: integrity.currentBranch,
|
|
1464
|
+
toolInput: normalizedToolInput,
|
|
1465
|
+
options,
|
|
1466
|
+
}
|
|
1467
|
+
);
|
|
1246
1468
|
const taskScopeViolation = buildTaskScopeViolation(governanceState.taskScope, affectedFiles);
|
|
1247
1469
|
const protectedSurface = buildProtectedSurface(governanceState, affectedFiles);
|
|
1248
1470
|
const protectedSurfaceForRisk = isProtectedApprovalRelevant(normalizedToolName, normalizedToolInput)
|
|
@@ -1290,6 +1512,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1290
1512
|
protectedSurface: protectedSurfaceForRisk,
|
|
1291
1513
|
costControl,
|
|
1292
1514
|
workflowControl,
|
|
1515
|
+
workflowContract,
|
|
1293
1516
|
actionProfile,
|
|
1294
1517
|
});
|
|
1295
1518
|
const executionSurface = buildDockerSandboxPlan({
|
|
@@ -1316,6 +1539,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1316
1539
|
command: normalizedToolInput.command || '',
|
|
1317
1540
|
costControl,
|
|
1318
1541
|
workflowControl,
|
|
1542
|
+
workflowContract,
|
|
1319
1543
|
actionProfile,
|
|
1320
1544
|
});
|
|
1321
1545
|
const evidence = buildEvidence({
|
|
@@ -1328,6 +1552,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1328
1552
|
normalizedAction,
|
|
1329
1553
|
costControl,
|
|
1330
1554
|
workflowControl,
|
|
1555
|
+
workflowContract,
|
|
1331
1556
|
actionProfile,
|
|
1332
1557
|
});
|
|
1333
1558
|
const remediations = buildRemediations({
|
|
@@ -1340,6 +1565,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1340
1565
|
executionSurface,
|
|
1341
1566
|
costControl,
|
|
1342
1567
|
workflowControl,
|
|
1568
|
+
workflowContract,
|
|
1343
1569
|
actionProfile,
|
|
1344
1570
|
});
|
|
1345
1571
|
const summary = decision === 'allow'
|
|
@@ -1353,6 +1579,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1353
1579
|
normalizedAction,
|
|
1354
1580
|
costControl,
|
|
1355
1581
|
workflowControl,
|
|
1582
|
+
workflowContract,
|
|
1356
1583
|
decision,
|
|
1357
1584
|
riskScore: risk.score,
|
|
1358
1585
|
band: risk.band,
|
|
@@ -1388,6 +1615,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1388
1615
|
protectedSurface: protectedSurfaceForRisk,
|
|
1389
1616
|
costControl,
|
|
1390
1617
|
workflowControl,
|
|
1618
|
+
workflowContract,
|
|
1391
1619
|
actionProfile,
|
|
1392
1620
|
});
|
|
1393
1621
|
report.reasoning = buildReasoning(report);
|