@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.
- package/dist/firewall/trusted-control.d.ts +1 -0
- package/dist/firewall/trusted-control.js +19 -0
- package/dist/firewall/trusted-control.test.js +14 -1
- package/dist/services/engineering-knowledge-graph.d.ts +15 -2
- package/dist/services/engineering-knowledge-graph.js +98 -1
- package/dist/services/engineering-knowledge-graph.test.js +17 -0
- package/package.json +6 -6
|
@@ -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.
|
|
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.
|
|
71
|
-
"@rigour-labs/brain-
|
|
72
|
-
"@rigour-labs/brain-
|
|
73
|
-
"@rigour-labs/brain-
|
|
74
|
-
"@rigour-labs/brain-
|
|
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",
|