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.
@@ -233,22 +233,25 @@ export class RunService {
233
233
  };
234
234
  }
235
235
  planOperationExecution(input) {
236
- const permission = this.evaluatePermissionForRun(input);
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: input,
243
+ operation: resolved.value,
241
244
  decision: permission.value.decision,
242
- ...(input.gitBoundaryInspector === undefined ? {} : { gitBoundaryInspector: input.gitBoundaryInspector }),
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: input.runId,
249
+ runId: resolved.value.runId,
247
250
  kind: 'execution-plan',
248
- title: `Execution plan: ${input.category} ${input.operation}`,
251
+ title: `Execution plan: ${resolved.value.category} ${resolved.value.operation}`,
249
252
  payload: {
250
- runId: input.runId,
251
- requestedOperation: operationMetadata(input),
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: input.operation,
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 metadataValidation = this.validateVgxManagedPreflightMetadata(input);
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(input);
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(input, outcome, {
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 permissionEvent = details.value.events.find((event) => event.id === approval.value.decisionEventId);
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 run = this.runs.getById(input.runId);
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 decision = evaluatePermission(input);
575
+ const contextConflict = workflowConflictDecision(inputWithRunContext, run.value.workflow);
576
+ const decision = contextConflict ?? evaluatePermission(inputWithRunContext);
566
577
  const timestamp = new Date().toISOString();
567
- const agent = input.agent === undefined ? { id: run.value.selectedAgentId } : { id: input.agent.id, name: input.agent.name, mode: input.agent.mode };
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(input, agent, input.reusePendingApproval === true);
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: input.runId,
595
+ runId: inputWithRunContext.runId,
585
596
  kind: 'permission-decision',
586
- title: `Permission ${reusedDecision.decision}: ${input.category} ${input.operation}`,
597
+ title: `Permission ${reusedDecision.decision}: ${inputWithRunContext.category} ${inputWithRunContext.operation}`,
587
598
  payload: {
588
- runId: input.runId,
589
- category: input.category,
590
- operation: input.operation,
591
- requestedOperation: operationMetadata(input),
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: input.runId,
628
+ runId: inputWithRunContext.runId,
612
629
  kind: 'permission-decision',
613
- title: `Permission ${decision.decision}: ${input.category} ${input.operation}`,
630
+ title: `Permission ${decision.decision}: ${inputWithRunContext.category} ${inputWithRunContext.operation}`,
614
631
  payload: {
615
- runId: input.runId,
616
- category: input.category,
617
- operation: input.operation,
618
- requestedOperation: operationMetadata(input),
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: input.category,
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: input.runId, decisionEventId: event.value.id });
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 (!jsonEqual(event.payload.requestedOperation, requestedOperation))
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 && jsonEqual(requestedOperation, operationMetadata(operation));
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {