thumbgate 1.27.18 → 1.27.19
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/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +4 -3
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +33 -12
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +249 -30
- package/adapters/chatgpt/openapi.yaml +12 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/gemini/function-declarations.json +1 -0
- package/adapters/mcp/server-stdio.js +263 -11
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +1429 -121
- package/bin/postinstall.js +1 -8
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +216 -0
- package/config/gates/claim-verification.json +12 -0
- package/config/gates/default.json +31 -2
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +23 -13
- package/config/merge-quality-checks.json +0 -1
- package/config/model-candidates.json +121 -6
- package/config/post-deploy-marketing-pages.json +80 -0
- package/config/tessl-tiles.json +1 -3
- package/openapi/openapi.yaml +12 -0
- package/package.json +1 -1
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +72 -20
- package/public/compare.html +31 -8
- package/public/dashboard.html +930 -166
- package/public/federal.html +2 -2
- package/public/guide.html +33 -13
- package/public/index.html +469 -111
- package/public/learn.html +183 -18
- package/public/lessons.html +168 -10
- package/public/numbers.html +7 -7
- package/public/pro.html +34 -11
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/agent-readiness.js +20 -3
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/auto-promote-gates.js +82 -10
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/billing.js +93 -1
- package/scripts/bot-detection.js +61 -3
- package/scripts/build-metadata.js +50 -10
- package/scripts/cli-feedback.js +4 -2
- package/scripts/cli-schema.js +97 -0
- package/scripts/cli-telemetry.js +6 -1
- package/scripts/commercial-offer.js +82 -2
- package/scripts/context-manager.js +74 -6
- package/scripts/dashboard.js +68 -2
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-loop.js +123 -1
- package/scripts/feedback-quality.js +87 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +1176 -85
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-runtime.js +20 -14
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/hybrid-feedback-context.js +142 -7
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/license.js +10 -10
- package/scripts/llm-client.js +169 -4
- package/scripts/local-model-profile.js +15 -8
- package/scripts/mcp-config.js +7 -1
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/meta-agent-loop.js +36 -0
- package/scripts/operational-integrity.js +39 -5
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/plausible-server-events.js +9 -6
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/published-cli.js +0 -8
- package/scripts/rate-limiter.js +64 -13
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +260 -10
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/seo-gsd.js +916 -7
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +28 -2
- package/scripts/statusline.sh +20 -4
- package/scripts/telemetry-analytics.js +357 -0
- package/scripts/thompson-sampling.js +31 -10
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-registry.js +169 -1
- package/scripts/vector-store.js +45 -0
- package/scripts/workflow-sentinel.js +286 -53
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +2683 -319
- package/scripts/bot-detector.js +0 -50
|
@@ -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,20 +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
|
+
|
|
1386
|
+
// Background customer-system actions checkpoint (warn), never hard-deny.
|
|
1387
|
+
// The checkpoint IS the mitigation — blocking outright prevents legitimate work.
|
|
1388
|
+
if (actionProfile?.backgroundAgent && actionProfile?.customerSystemAction) {
|
|
1389
|
+
return 'warn';
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
const repeatedHighBlast = isRepeatedHighBlast(memoryGuard, blastRadius);
|
|
1181
1393
|
if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
|
|
1182
1394
|
return 'deny';
|
|
1183
1395
|
}
|
|
1184
|
-
|
|
1396
|
+
|
|
1397
|
+
if (actionProfile?.economicAction || (actionProfile?.backgroundAgent && riskScore >= 0.3)) {
|
|
1185
1398
|
return 'warn';
|
|
1186
1399
|
}
|
|
1187
|
-
|
|
1400
|
+
|
|
1401
|
+
if (hasSoftControlWarning({ workflowContract, workflowControl, costControl, riskScore, learnedWarning, learnedRecall })) {
|
|
1188
1402
|
return 'warn';
|
|
1189
1403
|
}
|
|
1190
1404
|
return 'allow';
|
|
@@ -1233,11 +1447,24 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1233
1447
|
baseBranch,
|
|
1234
1448
|
command: normalizedToolInput.command,
|
|
1235
1449
|
changedFiles: affectedFiles,
|
|
1450
|
+
currentBranch: options.currentBranch
|
|
1451
|
+
|| normalizedToolInput.currentBranch
|
|
1452
|
+
|| normalizedToolInput.branchName
|
|
1453
|
+
|| normalizedToolInput.branch,
|
|
1236
1454
|
headSha: options.headSha || toolInput.headSha,
|
|
1237
1455
|
requirePrForReleaseSensitive: options.requirePrForReleaseSensitive === true,
|
|
1238
1456
|
requireVersionNotBehindBase: options.requireVersionNotBehindBase === true,
|
|
1239
1457
|
branchGovernance: governanceState.branchGovernance,
|
|
1240
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
|
+
);
|
|
1241
1468
|
const taskScopeViolation = buildTaskScopeViolation(governanceState.taskScope, affectedFiles);
|
|
1242
1469
|
const protectedSurface = buildProtectedSurface(governanceState, affectedFiles);
|
|
1243
1470
|
const protectedSurfaceForRisk = isProtectedApprovalRelevant(normalizedToolName, normalizedToolInput)
|
|
@@ -1285,6 +1512,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1285
1512
|
protectedSurface: protectedSurfaceForRisk,
|
|
1286
1513
|
costControl,
|
|
1287
1514
|
workflowControl,
|
|
1515
|
+
workflowContract,
|
|
1288
1516
|
actionProfile,
|
|
1289
1517
|
});
|
|
1290
1518
|
const executionSurface = buildDockerSandboxPlan({
|
|
@@ -1311,6 +1539,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1311
1539
|
command: normalizedToolInput.command || '',
|
|
1312
1540
|
costControl,
|
|
1313
1541
|
workflowControl,
|
|
1542
|
+
workflowContract,
|
|
1314
1543
|
actionProfile,
|
|
1315
1544
|
});
|
|
1316
1545
|
const evidence = buildEvidence({
|
|
@@ -1323,6 +1552,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1323
1552
|
normalizedAction,
|
|
1324
1553
|
costControl,
|
|
1325
1554
|
workflowControl,
|
|
1555
|
+
workflowContract,
|
|
1326
1556
|
actionProfile,
|
|
1327
1557
|
});
|
|
1328
1558
|
const remediations = buildRemediations({
|
|
@@ -1335,6 +1565,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1335
1565
|
executionSurface,
|
|
1336
1566
|
costControl,
|
|
1337
1567
|
workflowControl,
|
|
1568
|
+
workflowContract,
|
|
1338
1569
|
actionProfile,
|
|
1339
1570
|
});
|
|
1340
1571
|
const summary = decision === 'allow'
|
|
@@ -1348,6 +1579,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1348
1579
|
normalizedAction,
|
|
1349
1580
|
costControl,
|
|
1350
1581
|
workflowControl,
|
|
1582
|
+
workflowContract,
|
|
1351
1583
|
decision,
|
|
1352
1584
|
riskScore: risk.score,
|
|
1353
1585
|
band: risk.band,
|
|
@@ -1383,6 +1615,7 @@ function evaluateWorkflowSentinel(toolName, toolInput = {}, options = {}) {
|
|
|
1383
1615
|
protectedSurface: protectedSurfaceForRisk,
|
|
1384
1616
|
costControl,
|
|
1385
1617
|
workflowControl,
|
|
1618
|
+
workflowContract,
|
|
1386
1619
|
actionProfile,
|
|
1387
1620
|
});
|
|
1388
1621
|
report.reasoning = buildReasoning(report);
|
|
@@ -108,14 +108,74 @@ function parseCommandScore(output = '', status = 0, approvalRate = 0.5) {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
function parseCommandLine(cmdString) {
|
|
112
|
+
const args = [];
|
|
113
|
+
let current = '';
|
|
114
|
+
let inDoubleQuote = false;
|
|
115
|
+
let inSingleQuote = false;
|
|
116
|
+
let escaped = false;
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i < cmdString.length; i++) {
|
|
119
|
+
const char = cmdString[i];
|
|
120
|
+
|
|
121
|
+
if (escaped) {
|
|
122
|
+
current += char;
|
|
123
|
+
escaped = false;
|
|
124
|
+
} else if (char === '\\') {
|
|
125
|
+
if (inSingleQuote) {
|
|
126
|
+
current += char;
|
|
127
|
+
} else {
|
|
128
|
+
escaped = true;
|
|
129
|
+
}
|
|
130
|
+
} else if (char === '"' && !inSingleQuote) {
|
|
131
|
+
inDoubleQuote = !inDoubleQuote;
|
|
132
|
+
} else if (char === "'" && !inDoubleQuote) {
|
|
133
|
+
inSingleQuote = !inSingleQuote;
|
|
134
|
+
} else if (char === ' ' && !inDoubleQuote && !inSingleQuote) {
|
|
135
|
+
if (current) {
|
|
136
|
+
args.push(current);
|
|
137
|
+
current = '';
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
current += char;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (current) {
|
|
144
|
+
args.push(current);
|
|
145
|
+
}
|
|
146
|
+
return args;
|
|
147
|
+
}
|
|
148
|
+
|
|
111
149
|
function runCommand(command, {
|
|
112
150
|
cwd = process.cwd(),
|
|
113
151
|
env = process.env,
|
|
114
152
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
115
153
|
} = {}) {
|
|
116
154
|
const startedAt = Date.now();
|
|
117
|
-
const
|
|
118
|
-
|
|
155
|
+
const args = parseCommandLine(command);
|
|
156
|
+
const exec = args.shift();
|
|
157
|
+
|
|
158
|
+
const execBase = require('node:path').basename(exec).toLowerCase();
|
|
159
|
+
let safeExec;
|
|
160
|
+
if (exec === process.execPath) {
|
|
161
|
+
safeExec = process.execPath;
|
|
162
|
+
} else if (execBase === 'node' || execBase === 'node.exe') {
|
|
163
|
+
safeExec = process.execPath;
|
|
164
|
+
} else if (execBase === 'npm') {
|
|
165
|
+
safeExec = 'npm';
|
|
166
|
+
} else if (execBase === 'npm.cmd') {
|
|
167
|
+
safeExec = 'npm.cmd';
|
|
168
|
+
} else if (execBase === 'python3') {
|
|
169
|
+
safeExec = 'python3';
|
|
170
|
+
} else if (execBase === 'python') {
|
|
171
|
+
safeExec = 'python';
|
|
172
|
+
} else if (execBase === 'pytest') {
|
|
173
|
+
safeExec = 'pytest';
|
|
174
|
+
} else {
|
|
175
|
+
throw new Error(`Binary ${exec} is not authorized for workspace evolution.`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const result = spawnSync(safeExec, args, {
|
|
119
179
|
cwd,
|
|
120
180
|
env,
|
|
121
181
|
encoding: 'utf8',
|