@rigour-labs/core 6.2.0 → 6.2.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.
@@ -29,6 +29,7 @@ interface GrantInput {
29
29
  parentCapabilityId?: string;
30
30
  }
31
31
  export declare function loadTrustedGrant(cwd: string, id: string, controlRoot?: string): Promise<CapabilityGrant | null>;
32
+ export declare function listTrustedGrants(cwd: string, limit?: number, controlRoot?: string): Promise<CapabilityGrant[]>;
32
33
  export declare function issueTrustedGrant(cwd: string, input: GrantInput, controlRoot?: string): Promise<CapabilityGrant>;
33
34
  export declare function consumeTrustedGrant(cwd: string, input: {
34
35
  id: string;
@@ -117,6 +117,25 @@ export async function loadTrustedGrant(cwd, id, controlRoot) {
117
117
  return null;
118
118
  return fs.readJson(source);
119
119
  }
120
+ export async function listTrustedGrants(cwd, limit = 100, controlRoot) {
121
+ const grantsDir = path.join(getTrustedControlDir(cwd, controlRoot), 'capabilities');
122
+ if (!await fs.pathExists(grantsDir))
123
+ return [];
124
+ const files = (await fs.readdir(grantsDir))
125
+ .filter((file) => /^[0-9a-f-]{36}\.json$/i.test(file));
126
+ const grants = await Promise.all(files.map(async (file) => {
127
+ try {
128
+ return await fs.readJson(path.join(grantsDir, file));
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ }));
134
+ const requested = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 100;
135
+ return grants.filter((grant) => grant !== null)
136
+ .sort((a, b) => b.expiresAt - a.expiresAt)
137
+ .slice(0, requested);
138
+ }
120
139
  function assertDelegationSubset(parent, input) {
121
140
  if (parent.used)
122
141
  throw new Error('Parent capability is already consumed');
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from 'vitest';
2
2
  import fs from 'fs-extra';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
- import { appendExecutionReceipt, consumeTrustedGrant, getTrustedControlDir, issueTrustedGrant, normalizeMcpAction, saveGatewayConfig, verifyExecutionReceiptChain, } from './index.js';
5
+ import { appendExecutionReceipt, consumeTrustedGrant, getTrustedControlDir, issueTrustedGrant, listTrustedGrants, normalizeMcpAction, saveGatewayConfig, verifyExecutionReceiptChain, } from './index.js';
6
6
  const repositories = [];
7
7
  const controlRoots = [];
8
8
  async function repository() {
@@ -37,6 +37,19 @@ describe('trusted gateway control', () => {
37
37
  expect(location.startsWith(cwd)).toBe(false);
38
38
  expect(await fs.pathExists(location)).toBe(true);
39
39
  });
40
+ it('lists valid grants without failing on malformed control files', async () => {
41
+ const cwd = await repository();
42
+ const root = await controlRoot();
43
+ const grant = await issueTrustedGrant(cwd, {
44
+ issuerId: 'owner', subjectId: 'agent-1', taskId: 'task-1',
45
+ action: 'mcp.call', resource: 'mcp://files/read', ttlMs: 60_000,
46
+ }, root);
47
+ const grantsDir = path.join(getTrustedControlDir(cwd, root), 'capabilities');
48
+ await fs.writeFile(path.join(grantsDir, 'not-a-grant.json'), '{broken');
49
+ await expect(listTrustedGrants(cwd, 10, root)).resolves.toEqual([
50
+ expect.objectContaining({ id: grant.id, resource: 'mcp://files/read' }),
51
+ ]);
52
+ });
40
53
  it('consumes a capability exactly once', async () => {
41
54
  const cwd = await repository();
42
55
  const root = await controlRoot();
@@ -1,7 +1,8 @@
1
1
  import type { DependencyGraph } from '../context/dependency-graph.js';
2
+ import type { CapabilityGrant, ExecutionReceipt } from '../firewall/types.js';
2
3
  import type { LessonRecord } from '../storage/lessons.js';
3
4
  import type { AgentHistoryEvent, AgentRun } from './agent-history.js';
4
- export type KnowledgeNodeType = 'repository' | 'file' | 'agent' | 'task' | 'run' | 'advice' | 'pattern' | 'memory' | 'lesson' | 'policy' | 'outcome';
5
+ export type KnowledgeNodeType = 'repository' | 'file' | 'agent' | 'task' | 'run' | 'gateway' | 'action' | 'capability' | 'advice' | 'pattern' | 'memory' | 'lesson' | 'policy' | 'outcome';
5
6
  export interface KnowledgeNode {
6
7
  id: string;
7
8
  type: KnowledgeNodeType;
@@ -16,7 +17,7 @@ export interface KnowledgeEdge {
16
17
  id: string;
17
18
  from: string;
18
19
  to: string;
19
- type: 'contains' | 'imports' | 'performed' | 'belongs_to' | 'touched' | 'produced' | 'resulted_in' | 'defined_in' | 'protects' | 'remembered' | 'guided' | 'informed';
20
+ type: 'contains' | 'imports' | 'performed' | 'belongs_to' | 'touched' | 'produced' | 'resulted_in' | 'defined_in' | 'protects' | 'remembered' | 'guided' | 'informed' | 'routes_through' | 'governed_by' | 'authorized_by' | 'delegated_from';
20
21
  }
21
22
  export interface EngineeringKnowledgeGraph {
22
23
  schemaVersion: 1;
@@ -49,6 +50,18 @@ interface GraphInput {
49
50
  detail?: string;
50
51
  source?: string;
51
52
  }>;
53
+ gateway?: {
54
+ mode: 'observe' | 'enforce';
55
+ agentId: string;
56
+ taskId: string;
57
+ serverCount: number;
58
+ toolCount: number;
59
+ chainValid: boolean;
60
+ receiptCount: number;
61
+ chainReason?: string;
62
+ } | null;
63
+ receipts?: ExecutionReceipt[];
64
+ capabilities?: CapabilityGrant[];
52
65
  maxFiles?: number;
53
66
  }
54
67
  export declare function buildEngineeringKnowledgeGraph(input: GraphInput): EngineeringKnowledgeGraph;
@@ -5,6 +5,100 @@ function shortPath(value) {
5
5
  function shortLabel(value, limit = 72) {
6
6
  return value.length > limit ? `${value.slice(0, limit - 1)}…` : value;
7
7
  }
8
+ function addGatewayFoundation(input, repoId, addNode, addEdge) {
9
+ const gatewayId = 'gateway:rigour-mcp';
10
+ const policyId = 'policy:rigour-mcp-gateway';
11
+ const gateway = input.gateway;
12
+ addNode({
13
+ id: gatewayId,
14
+ type: 'gateway',
15
+ label: 'Trusted MCP gateway',
16
+ detail: `${gateway.serverCount} servers · ${gateway.toolCount} allowed tools`,
17
+ state: gateway.mode,
18
+ repositoryId: input.repository.id,
19
+ weight: 8,
20
+ evidence: { chainValid: gateway.chainValid, receiptCount: gateway.receiptCount, chainReason: gateway.chainReason },
21
+ });
22
+ addNode({ id: policyId, type: 'policy', label: 'MCP capability policy', detail: `${gateway.mode} mode`, state: gateway.mode, weight: 7 });
23
+ addEdge(gatewayId, policyId, 'governed_by');
24
+ addEdge(policyId, repoId, 'protects');
25
+ const agentId = `agent:${gateway.agentId}`;
26
+ const taskId = `task:${gateway.taskId}`;
27
+ addNode({ id: agentId, type: 'agent', label: gateway.agentId, weight: 5 });
28
+ addNode({ id: taskId, type: 'task', label: gateway.taskId, weight: 4 });
29
+ addEdge(agentId, taskId, 'performed');
30
+ return { gatewayId, policyId };
31
+ }
32
+ function addCapabilityEvidence(capabilities, gatewayId, writer) {
33
+ const { addNode, addEdge } = writer;
34
+ const selected = capabilities.slice(0, 100);
35
+ const selectedIds = new Set(selected.map(capability => capability.id));
36
+ for (const capability of selected) {
37
+ const capabilityId = `capability:${capability.id}`;
38
+ const state = capability.used ? 'consumed' : capability.expiresAt < Date.now() ? 'expired' : 'available';
39
+ addNode({
40
+ id: capabilityId,
41
+ type: 'capability',
42
+ label: `${capability.action} · ${shortLabel(capability.resource, 48)}`,
43
+ detail: `${capability.subjectId ?? capability.agentId ?? 'unknown actor'} · ${state}`,
44
+ state,
45
+ weight: 3,
46
+ evidence: { expiresAt: capability.expiresAt, issuerId: capability.issuerId, subjectId: capability.subjectId },
47
+ });
48
+ addEdge(capabilityId, gatewayId, 'authorized_by');
49
+ if (capability.parentCapabilityId) {
50
+ const parentId = `capability:${capability.parentCapabilityId}`;
51
+ if (!selectedIds.has(capability.parentCapabilityId)) {
52
+ addNode({ id: parentId, type: 'capability', label: 'Parent capability', detail: 'Referenced by delegated grant', state: 'referenced', weight: 2 });
53
+ }
54
+ addEdge(capabilityId, parentId, 'delegated_from');
55
+ }
56
+ }
57
+ }
58
+ function addReceiptEvidence(receipts, repositoryId, gatewayId, policyId, writer) {
59
+ const { addNode, addEdge } = writer;
60
+ for (const receipt of receipts.slice(-100)) {
61
+ const actionId = `action:${receipt.id}`;
62
+ const agentId = `agent:${receipt.action.actorId}`;
63
+ const taskId = `task:${receipt.action.taskId}`;
64
+ const outcomeId = `outcome:receipt:${receipt.id}`;
65
+ addNode({ id: agentId, type: 'agent', label: receipt.action.actorId, weight: 5 });
66
+ addNode({ id: taskId, type: 'task', label: receipt.action.taskId, weight: 4 });
67
+ addNode({
68
+ id: actionId,
69
+ type: 'action',
70
+ label: receipt.action.operation,
71
+ detail: `${receipt.action.resource} · ${receipt.mode}`,
72
+ state: receipt.simulatedDecision ? `would ${receipt.simulatedDecision}` : receipt.decision,
73
+ repositoryId,
74
+ weight: receipt.outcome === 'denied' ? 5 : 3,
75
+ evidence: {
76
+ receiptId: receipt.id,
77
+ outcome: receipt.outcome,
78
+ mode: receipt.mode,
79
+ decision: receipt.decision,
80
+ simulatedDecision: receipt.simulatedDecision,
81
+ createdAt: receipt.createdAt,
82
+ },
83
+ });
84
+ addNode({ id: outcomeId, type: 'outcome', label: receipt.outcome, detail: receipt.reason, state: receipt.outcome, weight: 3 });
85
+ addEdge(agentId, actionId, 'performed');
86
+ addEdge(actionId, taskId, 'belongs_to');
87
+ addEdge(actionId, gatewayId, 'routes_through');
88
+ addEdge(actionId, policyId, 'governed_by');
89
+ addEdge(actionId, outcomeId, 'resulted_in');
90
+ if (receipt.capabilityId)
91
+ addEdge(actionId, `capability:${receipt.capabilityId}`, 'authorized_by');
92
+ }
93
+ }
94
+ function addGatewayEvidence(input, repoId, addNode, addEdge) {
95
+ if (!input.gateway)
96
+ return;
97
+ const { gatewayId, policyId } = addGatewayFoundation(input, repoId, addNode, addEdge);
98
+ const writer = { addNode, addEdge };
99
+ addCapabilityEvidence(input.capabilities ?? [], gatewayId, writer);
100
+ addReceiptEvidence(input.receipts ?? [], input.repository.id, gatewayId, policyId, writer);
101
+ }
8
102
  export function buildEngineeringKnowledgeGraph(input) {
9
103
  const nodes = new Map();
10
104
  const edges = new Map();
@@ -100,6 +194,7 @@ export function buildEngineeringKnowledgeGraph(input) {
100
194
  });
101
195
  addEdge(repoId, memoryId, 'remembered');
102
196
  }
197
+ addGatewayEvidence(input, repoId, addNode, addEdge);
103
198
  for (const event of input.events) {
104
199
  if (!event.guidance)
105
200
  continue;
@@ -171,7 +266,9 @@ export function buildEngineeringKnowledgeGraph(input) {
171
266
  || input.runs.length > 100
172
267
  || input.lessons.length > 150
173
268
  || (input.patterns?.length ?? 0) > 120
174
- || (input.memories?.length ?? 0) > 100,
269
+ || (input.memories?.length ?? 0) > 100
270
+ || (input.receipts?.length ?? 0) > 100
271
+ || (input.capabilities?.length ?? 0) > 100,
175
272
  counts,
176
273
  };
177
274
  }
@@ -56,4 +56,21 @@ describe('engineering knowledge graph', () => {
56
56
  expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'run:run-1', to: 'advice:run-1', type: 'guided' }));
57
57
  expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'pattern:pattern-1', to: 'advice:run-1', type: 'informed' }));
58
58
  });
59
+ it('shows trusted gateway receipts, simulated denials and capability lineage', () => {
60
+ const graph = buildEngineeringKnowledgeGraph({
61
+ repository: { id: 'repo-a', name: 'payments' }, events: [], runs: [], lessons: [],
62
+ gateway: { mode: 'observe', agentId: 'agent-1', taskId: 'task-1', serverCount: 1, toolCount: 2, chainValid: true, receiptCount: 1 },
63
+ capabilities: [{ id: 'child', action: 'mcp.call', resource: 'mcp://github/get_issue', expiresAt: Date.now() + 1000, policyHash: 'hash', used: false, issuerId: 'owner', subjectId: 'agent-1', parentCapabilityId: 'parent' }],
64
+ receipts: [{
65
+ version: 1, id: 'receipt-1', repositoryId: 'repo-a', mode: 'observe', decision: 'allow', simulatedDecision: 'deny',
66
+ reason: 'would deny', policyHash: 'hash', outcome: 'forwarded', createdAt: '2026-01-01T00:00:00Z', digest: 'digest', signature: 'signature',
67
+ action: { version: 1, actorId: 'agent-1', taskId: 'task-1', channel: 'mcp', operation: 'github__get_issue', resource: 'mcp://github/get_issue', environment: 'local', sideEffect: 'read', reversibility: 'high', sensitivity: 'normal', blastRadius: 'single-resource' },
68
+ capabilityId: 'child',
69
+ }],
70
+ });
71
+ expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'gateway', evidence: expect.objectContaining({ chainValid: true }) }));
72
+ expect(graph.nodes).toContainEqual(expect.objectContaining({ type: 'action', state: 'would deny' }));
73
+ expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'capability:child', to: 'capability:parent', type: 'delegated_from' }));
74
+ expect(graph.edges).toContainEqual(expect.objectContaining({ from: 'action:receipt-1', to: 'gateway:rigour-mcp', type: 'routes_through' }));
75
+ });
59
76
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
4
4
  "description": "AI-native quality gate engine with local Bayesian learning. AST analysis, drift detection, Fix Packet generation, and agent self-healing across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -67,11 +67,11 @@
67
67
  "pg": "^8.16.3",
68
68
  "sqlite3": "^5.1.7",
69
69
  "openai": "^4.104.0",
70
- "@rigour-labs/brain-darwin-arm64": "6.2.0",
71
- "@rigour-labs/brain-linux-arm64": "6.2.0",
72
- "@rigour-labs/brain-darwin-x64": "6.2.0",
73
- "@rigour-labs/brain-win-x64": "6.2.0",
74
- "@rigour-labs/brain-linux-x64": "6.2.0"
70
+ "@rigour-labs/brain-darwin-arm64": "6.2.1",
71
+ "@rigour-labs/brain-darwin-x64": "6.2.1",
72
+ "@rigour-labs/brain-linux-arm64": "6.2.1",
73
+ "@rigour-labs/brain-linux-x64": "6.2.1",
74
+ "@rigour-labs/brain-win-x64": "6.2.1"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@types/fs-extra": "^11.0.4",