vgxness 1.8.0 → 1.9.1
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/dist/agents/canonical-agent-manifest.js +22 -19
- package/dist/agents/renderers/claude-renderer.js +3 -1
- package/dist/agents/renderers/opencode-renderer.js +2 -1
- package/dist/behavior/behavior-contract-manifest.js +42 -0
- package/dist/behavior/behavior-contract-schema.js +1 -0
- package/dist/behavior/behavior-contract-validation.js +42 -0
- package/dist/governance/index.js +1 -0
- package/dist/governance/risk-classifier.js +116 -0
- package/dist/mcp/control-plane-snapshot-service.js +272 -0
- package/dist/mcp/control-plane.js +2 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/schema.js +5 -0
- package/dist/mcp/validation.js +19 -1
- package/dist/orchestrator/natural-language-planner.js +34 -17
- package/dist/payload/context-budget-policy.js +17 -0
- package/dist/payload/context-budget-service.js +44 -0
- package/dist/permissions/policy-evaluator.js +88 -11
- package/dist/runs/execution-planning.js +1 -1
- package/dist/runs/run-service.js +129 -35
- package/package.json +1 -1
package/dist/runs/run-service.js
CHANGED
|
@@ -233,22 +233,25 @@ export class RunService {
|
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
planOperationExecution(input) {
|
|
236
|
-
const
|
|
236
|
+
const resolved = this.buildPermissionContextForRun(input);
|
|
237
|
+
if (!resolved.ok)
|
|
238
|
+
return resolved;
|
|
239
|
+
const permission = this.evaluatePermissionForRun(resolved.value);
|
|
237
240
|
if (!permission.ok)
|
|
238
241
|
return permission;
|
|
239
242
|
const plan = planExecutionIsolation({
|
|
240
|
-
operation:
|
|
243
|
+
operation: resolved.value,
|
|
241
244
|
decision: permission.value.decision,
|
|
242
|
-
...(
|
|
245
|
+
...(resolved.value.gitBoundaryInspector === undefined ? {} : { gitBoundaryInspector: resolved.value.gitBoundaryInspector }),
|
|
243
246
|
});
|
|
244
247
|
const sandboxDecision = sandboxDecisionSummary(plan.sandbox);
|
|
245
248
|
const planEvent = this.runs.appendEvent({
|
|
246
|
-
runId:
|
|
249
|
+
runId: resolved.value.runId,
|
|
247
250
|
kind: 'execution-plan',
|
|
248
|
-
title: `Execution plan: ${
|
|
251
|
+
title: `Execution plan: ${resolved.value.category} ${resolved.value.operation}`,
|
|
249
252
|
payload: {
|
|
250
|
-
runId:
|
|
251
|
-
requestedOperation: operationMetadata(
|
|
253
|
+
runId: resolved.value.runId,
|
|
254
|
+
requestedOperation: operationMetadata(resolved.value),
|
|
252
255
|
decisionEventId: permission.value.event.id,
|
|
253
256
|
approvalId: permission.value.approval?.id ?? null,
|
|
254
257
|
plan: plan,
|
|
@@ -262,17 +265,20 @@ export class RunService {
|
|
|
262
265
|
timestamp: new Date().toISOString(),
|
|
263
266
|
},
|
|
264
267
|
relatedType: 'operation',
|
|
265
|
-
relatedId:
|
|
268
|
+
relatedId: resolved.value.operation,
|
|
266
269
|
});
|
|
267
270
|
if (!planEvent.ok)
|
|
268
271
|
return { ok: false, error: planEvent.error };
|
|
269
272
|
return { ok: true, value: { ...permission.value, plan, planEvent: planEvent.value } };
|
|
270
273
|
}
|
|
271
274
|
preflightOperation(input) {
|
|
272
|
-
const
|
|
275
|
+
const resolved = this.buildPermissionContextForRun(input);
|
|
276
|
+
if (!resolved.ok)
|
|
277
|
+
return resolved;
|
|
278
|
+
const metadataValidation = this.validateVgxManagedPreflightMetadata(resolved.value);
|
|
273
279
|
if (!metadataValidation.ok)
|
|
274
280
|
return metadataValidation;
|
|
275
|
-
const planned = this.planOperationExecution(
|
|
281
|
+
const planned = this.planOperationExecution(resolved.value);
|
|
276
282
|
if (!planned.ok)
|
|
277
283
|
return planned;
|
|
278
284
|
const { decision, event, approval, plan, planEvent } = planned.value;
|
|
@@ -283,7 +289,7 @@ export class RunService {
|
|
|
283
289
|
const sandboxRejected = sandbox?.decision === 'rejected';
|
|
284
290
|
const sandboxReason = sandboxRejected ? [{ code: sandbox.reason ?? 'sandbox_boundary', message: sandbox.audit.validationSummary }] : [];
|
|
285
291
|
const outcome = sandboxRejected ? 'blocked' : preflightOutcome(decision);
|
|
286
|
-
this.appendPreflightAuditEvent(
|
|
292
|
+
this.appendPreflightAuditEvent(resolved.value, outcome, {
|
|
287
293
|
permissionEventId: event.id,
|
|
288
294
|
planEventId: planEvent.id,
|
|
289
295
|
...(approval === undefined ? {} : { approvalId: approval.id }),
|
|
@@ -313,14 +319,14 @@ export class RunService {
|
|
|
313
319
|
const details = this.runs.getDetails(approval.value.runId);
|
|
314
320
|
if (!details.ok)
|
|
315
321
|
return details;
|
|
316
|
-
const
|
|
322
|
+
const pendingExecutionEvent = details.value.events.find((event) => isPendingExecutionEventForApproval(event, approval.value.id));
|
|
323
|
+
if (pendingExecutionEvent === undefined)
|
|
324
|
+
return validationFailure('Approved approval has no resumable pending operation event');
|
|
325
|
+
const permissionEvent = permissionEventForPendingExecution(details.value, approval.value, pendingExecutionEvent);
|
|
317
326
|
if (permissionEvent === undefined)
|
|
318
327
|
return validationFailure('Approval permission-decision event is missing');
|
|
319
328
|
if (!isAskPermissionEvent(permissionEvent))
|
|
320
329
|
return validationFailure('Approval is not linked to an ask permission decision');
|
|
321
|
-
const pendingExecutionEvent = details.value.events.find((event) => isPendingExecutionEventForApproval(event, approval.value.id));
|
|
322
|
-
if (pendingExecutionEvent === undefined)
|
|
323
|
-
return validationFailure('Approved approval has no resumable pending operation event');
|
|
324
330
|
const operation = operationFromPendingExecution(pendingExecutionEvent.payload);
|
|
325
331
|
if (operation === undefined)
|
|
326
332
|
return validationFailure('Pending operation metadata is incomplete and cannot be resumed');
|
|
@@ -559,14 +565,19 @@ export class RunService {
|
|
|
559
565
|
: { ok: false, error: executionEvent.error };
|
|
560
566
|
}
|
|
561
567
|
evaluatePermissionForRun(input) {
|
|
562
|
-
const
|
|
568
|
+
const resolved = this.buildPermissionContextForRun(input);
|
|
569
|
+
if (!resolved.ok)
|
|
570
|
+
return resolved;
|
|
571
|
+
const inputWithRunContext = resolved.value;
|
|
572
|
+
const run = this.runs.getById(inputWithRunContext.runId);
|
|
563
573
|
if (!run.ok)
|
|
564
574
|
return { ok: false, error: run.error };
|
|
565
|
-
const
|
|
575
|
+
const contextConflict = workflowConflictDecision(inputWithRunContext, run.value.workflow);
|
|
576
|
+
const decision = contextConflict ?? evaluatePermission(inputWithRunContext);
|
|
566
577
|
const timestamp = new Date().toISOString();
|
|
567
|
-
const agent =
|
|
578
|
+
const agent = inputWithRunContext.agent === undefined ? { id: run.value.selectedAgentId } : { id: inputWithRunContext.agent.id, name: inputWithRunContext.agent.name, mode: inputWithRunContext.agent.mode };
|
|
568
579
|
if (decision.decision === 'ask') {
|
|
569
|
-
const prior = this.findMatchingApproval(
|
|
580
|
+
const prior = this.findMatchingApproval(inputWithRunContext, agent, inputWithRunContext.reusePendingApproval === true);
|
|
570
581
|
if (!prior.ok)
|
|
571
582
|
return prior;
|
|
572
583
|
if (prior.value !== undefined) {
|
|
@@ -581,17 +592,23 @@ export class RunService {
|
|
|
581
592
|
message: `Previously ${prior.value.approval.status} approval ${prior.value.approval.id} denies this exact operation.`,
|
|
582
593
|
};
|
|
583
594
|
const event = this.runs.appendEvent({
|
|
584
|
-
runId:
|
|
595
|
+
runId: inputWithRunContext.runId,
|
|
585
596
|
kind: 'permission-decision',
|
|
586
|
-
title: `Permission ${reusedDecision.decision}: ${
|
|
597
|
+
title: `Permission ${reusedDecision.decision}: ${inputWithRunContext.category} ${inputWithRunContext.operation}`,
|
|
587
598
|
payload: {
|
|
588
|
-
runId:
|
|
589
|
-
category:
|
|
590
|
-
operation:
|
|
591
|
-
|
|
599
|
+
runId: inputWithRunContext.runId,
|
|
600
|
+
category: inputWithRunContext.category,
|
|
601
|
+
operation: inputWithRunContext.operation,
|
|
602
|
+
workflow: inputWithRunContext.workflow ?? null,
|
|
603
|
+
phase: inputWithRunContext.phase ?? null,
|
|
604
|
+
requestedOperation: operationMetadata(inputWithRunContext),
|
|
592
605
|
agent,
|
|
593
606
|
decision: reusedDecision.decision,
|
|
594
607
|
reasons: [{ code: reusedDecision.reason, message: reusedDecision.message }],
|
|
608
|
+
riskTier: reusedDecision.riskTier ?? null,
|
|
609
|
+
riskReasonCodes: reusedDecision.riskReasonCodes ?? [],
|
|
610
|
+
boundary: (reusedDecision.boundary ?? null),
|
|
611
|
+
auditEvidence: reusedDecision.auditEvidence ?? [],
|
|
595
612
|
requiresHumanApproval: reusedDecision.decision === 'ask',
|
|
596
613
|
approvalStatus: prior.value.approval.status,
|
|
597
614
|
reusedApprovalId: prior.value.approval.id,
|
|
@@ -608,29 +625,35 @@ export class RunService {
|
|
|
608
625
|
}
|
|
609
626
|
}
|
|
610
627
|
const event = this.runs.appendEvent({
|
|
611
|
-
runId:
|
|
628
|
+
runId: inputWithRunContext.runId,
|
|
612
629
|
kind: 'permission-decision',
|
|
613
|
-
title: `Permission ${decision.decision}: ${
|
|
630
|
+
title: `Permission ${decision.decision}: ${inputWithRunContext.category} ${inputWithRunContext.operation}`,
|
|
614
631
|
payload: {
|
|
615
|
-
runId:
|
|
616
|
-
category:
|
|
617
|
-
operation:
|
|
618
|
-
|
|
632
|
+
runId: inputWithRunContext.runId,
|
|
633
|
+
category: inputWithRunContext.category,
|
|
634
|
+
operation: inputWithRunContext.operation,
|
|
635
|
+
workflow: inputWithRunContext.workflow ?? null,
|
|
636
|
+
phase: inputWithRunContext.phase ?? null,
|
|
637
|
+
requestedOperation: operationMetadata(inputWithRunContext),
|
|
619
638
|
agent,
|
|
620
639
|
decision: decision.decision,
|
|
621
640
|
reasons: [{ code: decision.reason, message: decision.message }],
|
|
641
|
+
riskTier: decision.riskTier ?? null,
|
|
642
|
+
riskReasonCodes: decision.riskReasonCodes ?? [],
|
|
643
|
+
boundary: (decision.boundary ?? null),
|
|
644
|
+
auditEvidence: decision.auditEvidence ?? [],
|
|
622
645
|
requiresHumanApproval: decision.decision === 'ask',
|
|
623
646
|
approvalStatus: decision.decision === 'ask' ? 'pending' : 'not-required',
|
|
624
647
|
timestamp,
|
|
625
648
|
},
|
|
626
649
|
relatedType: 'permission',
|
|
627
|
-
relatedId:
|
|
650
|
+
relatedId: inputWithRunContext.category,
|
|
628
651
|
});
|
|
629
652
|
if (!event.ok)
|
|
630
653
|
return { ok: false, error: event.error };
|
|
631
654
|
if (decision.decision !== 'ask')
|
|
632
655
|
return { ok: true, value: { decision, event: event.value } };
|
|
633
|
-
const approval = this.runs.createApproval({ runId:
|
|
656
|
+
const approval = this.runs.createApproval({ runId: inputWithRunContext.runId, decisionEventId: event.value.id });
|
|
634
657
|
return approval.ok ? { ok: true, value: { decision, event: event.value, approval: approval.value } } : { ok: false, error: approval.error };
|
|
635
658
|
}
|
|
636
659
|
validateVgxManagedPreflightMetadata(input) {
|
|
@@ -668,6 +691,19 @@ export class RunService {
|
|
|
668
691
|
}
|
|
669
692
|
return { ok: true, value: undefined };
|
|
670
693
|
}
|
|
694
|
+
buildPermissionContextForRun(input) {
|
|
695
|
+
const run = this.runs.getById(input.runId);
|
|
696
|
+
if (!run.ok)
|
|
697
|
+
return { ok: false, error: run.error };
|
|
698
|
+
const phase = input.phase ?? canonicalRunPhase(run.value.phase);
|
|
699
|
+
const merged = {
|
|
700
|
+
...input,
|
|
701
|
+
workflow: input.workflow ?? run.value.workflow,
|
|
702
|
+
...(phase === undefined ? {} : { phase }),
|
|
703
|
+
agentId: input.agentId ?? input.agent?.id ?? run.value.selectedAgentId,
|
|
704
|
+
};
|
|
705
|
+
return { ok: true, value: merged };
|
|
706
|
+
}
|
|
671
707
|
appendPreflightAuditEvent(input, outcome, audit) {
|
|
672
708
|
const run = this.runs.getById(input.runId);
|
|
673
709
|
if (!run.ok || !isVgxManagedWorkflow(run.value.workflow) || !isRiskyPermissionCategory(input.category))
|
|
@@ -774,6 +810,23 @@ function isVgxManagedWorkflow(workflow) {
|
|
|
774
810
|
const normalized = workflow.toLowerCase();
|
|
775
811
|
return normalized === 'sdd' || normalized.startsWith('sdd-') || normalized.includes('sdd') || normalized.includes('vgx');
|
|
776
812
|
}
|
|
813
|
+
function workflowConflictDecision(input, runWorkflow) {
|
|
814
|
+
if (input.workflow === undefined || input.workflow === runWorkflow)
|
|
815
|
+
return undefined;
|
|
816
|
+
const eitherSdd = input.workflow === 'sdd' || runWorkflow === 'sdd' || input.workflow.includes('sdd') || runWorkflow.includes('sdd');
|
|
817
|
+
if (!eitherSdd)
|
|
818
|
+
return undefined;
|
|
819
|
+
return {
|
|
820
|
+
decision: 'ask',
|
|
821
|
+
category: input.category,
|
|
822
|
+
operation: input.operation,
|
|
823
|
+
reason: 'workflow_context',
|
|
824
|
+
message: `Explicit workflow ${input.workflow} conflicts with run workflow ${runWorkflow}; human validation is required before crossing SDD/non-SDD context.`,
|
|
825
|
+
workflow: input.workflow,
|
|
826
|
+
warnings: [`workflow-conflict:${runWorkflow}->${input.workflow}`],
|
|
827
|
+
auditEvidence: [`runWorkflow:${runWorkflow}`, `explicitWorkflow:${input.workflow}`],
|
|
828
|
+
};
|
|
829
|
+
}
|
|
777
830
|
function executionPayload(operation, executorName, status, extra) {
|
|
778
831
|
return {
|
|
779
832
|
status,
|
|
@@ -830,6 +883,11 @@ function resumeGateManualNextCommands(approval, details, operation) {
|
|
|
830
883
|
commands.push(`Review operation manually: ${operation.category} ${operation.operation}`);
|
|
831
884
|
return commands;
|
|
832
885
|
}
|
|
886
|
+
function canonicalRunPhase(phase) {
|
|
887
|
+
if (phase === undefined || phase.trim().length === 0)
|
|
888
|
+
return undefined;
|
|
889
|
+
return phase === 'apply' ? 'apply-progress' : phase;
|
|
890
|
+
}
|
|
833
891
|
function operationMetadata(input) {
|
|
834
892
|
const metadata = { category: input.category, name: input.operation };
|
|
835
893
|
if (input.workspaceRoot !== undefined)
|
|
@@ -840,6 +898,12 @@ function operationMetadata(input) {
|
|
|
840
898
|
metadata.providerToolName = input.providerToolName;
|
|
841
899
|
if (input.sandboxStrategy !== undefined)
|
|
842
900
|
metadata.sandboxStrategy = input.sandboxStrategy;
|
|
901
|
+
if (input.workflow !== undefined)
|
|
902
|
+
metadata.workflow = input.workflow;
|
|
903
|
+
if (input.phase !== undefined)
|
|
904
|
+
metadata.phase = input.phase;
|
|
905
|
+
if (input.agentId !== undefined)
|
|
906
|
+
metadata.agentId = input.agentId;
|
|
843
907
|
if (input.destructive !== undefined)
|
|
844
908
|
metadata.destructive = input.destructive;
|
|
845
909
|
if (input.external !== undefined)
|
|
@@ -859,7 +923,7 @@ function approvalEventMatches(event, input, requestedOperation, agent) {
|
|
|
859
923
|
return false;
|
|
860
924
|
if (event.payload.category !== input.category || event.payload.operation !== input.operation)
|
|
861
925
|
return false;
|
|
862
|
-
if (!
|
|
926
|
+
if (!operationMetadataCompatible(event.payload.requestedOperation, requestedOperation))
|
|
863
927
|
return false;
|
|
864
928
|
if (!jsonEqual(event.payload.agent, agent))
|
|
865
929
|
return false;
|
|
@@ -869,7 +933,23 @@ function isMatchingPreflightPlanEvent(event, operation) {
|
|
|
869
933
|
if (event.kind !== 'execution-plan' || !isObject(event.payload))
|
|
870
934
|
return false;
|
|
871
935
|
const requestedOperation = event.payload.requestedOperation;
|
|
872
|
-
return requestedOperation !== undefined &&
|
|
936
|
+
return requestedOperation !== undefined && operationMetadataCompatible(requestedOperation, operationMetadata(operation));
|
|
937
|
+
}
|
|
938
|
+
function operationMetadataCompatible(preflightOperation, requestedOperation) {
|
|
939
|
+
if (preflightOperation === undefined || requestedOperation === undefined)
|
|
940
|
+
return jsonEqual(preflightOperation, requestedOperation);
|
|
941
|
+
if (!isObject(preflightOperation) || !isObject(requestedOperation))
|
|
942
|
+
return jsonEqual(preflightOperation, requestedOperation);
|
|
943
|
+
if (preflightOperation.category !== requestedOperation.category || preflightOperation.name !== requestedOperation.name)
|
|
944
|
+
return false;
|
|
945
|
+
const strictKeys = ['workspaceRoot', 'targetPath', 'providerToolName', 'sandboxStrategy', 'destructive', 'external', 'privileged', 'ambiguous'];
|
|
946
|
+
for (const key of strictKeys) {
|
|
947
|
+
if (key in preflightOperation && key in requestedOperation && !jsonEqual(preflightOperation[key], requestedOperation[key]))
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
if ('input' in preflightOperation && 'input' in requestedOperation && !jsonEqual(preflightOperation.input, requestedOperation.input))
|
|
951
|
+
return false;
|
|
952
|
+
return true;
|
|
873
953
|
}
|
|
874
954
|
function preflightPlanStatus(event) {
|
|
875
955
|
if (!isObject(event.payload))
|
|
@@ -919,6 +999,12 @@ function isAskPermissionEvent(event) {
|
|
|
919
999
|
function isPendingExecutionEventForApproval(event, approvalId) {
|
|
920
1000
|
return (event.kind === 'operation-execution' && isObject(event.payload) && event.payload.status === 'pending-approval' && event.payload.approvalId === approvalId);
|
|
921
1001
|
}
|
|
1002
|
+
function permissionEventForPendingExecution(details, approval, pendingExecutionEvent) {
|
|
1003
|
+
const decisionEventId = isObject(pendingExecutionEvent.payload) && typeof pendingExecutionEvent.payload.decisionEventId === 'string'
|
|
1004
|
+
? pendingExecutionEvent.payload.decisionEventId
|
|
1005
|
+
: approval.decisionEventId;
|
|
1006
|
+
return details.events.find((event) => event.id === decisionEventId) ?? details.events.find((event) => event.id === approval.decisionEventId);
|
|
1007
|
+
}
|
|
922
1008
|
function operationFromPendingExecution(payload) {
|
|
923
1009
|
if (!isObject(payload))
|
|
924
1010
|
return undefined;
|
|
@@ -955,10 +1041,18 @@ function isObject(value) {
|
|
|
955
1041
|
function isPermissionCategory(value) {
|
|
956
1042
|
return (value === 'read' ||
|
|
957
1043
|
value === 'edit' ||
|
|
1044
|
+
value === 'implementation-edit' ||
|
|
1045
|
+
value === 'spec-write' ||
|
|
1046
|
+
value === 'design-write' ||
|
|
1047
|
+
value === 'task-write' ||
|
|
958
1048
|
value === 'shell' ||
|
|
1049
|
+
value === 'test-run' ||
|
|
1050
|
+
value === 'install' ||
|
|
959
1051
|
value === 'network' ||
|
|
960
1052
|
value === 'git' ||
|
|
1053
|
+
value === 'git-write' ||
|
|
961
1054
|
value === 'memory' ||
|
|
1055
|
+
value === 'memory-write' ||
|
|
962
1056
|
value === 'external-directory' ||
|
|
963
1057
|
value === 'provider-tool' ||
|
|
964
1058
|
value === 'secrets');
|