@rigour-labs/core 6.8.0 → 6.8.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/inference/cloud-provider.js +12 -2
- package/package.json +11 -8
- package/dist/brief/briefing.test.d.ts +0 -1
- package/dist/brief/briefing.test.js +0 -107
- package/dist/context/automatic-index-cache.test.d.ts +0 -1
- package/dist/context/automatic-index-cache.test.js +0 -30
- package/dist/context/automatic-index.test.d.ts +0 -1
- package/dist/context/automatic-index.test.js +0 -45
- package/dist/context/cache-engine.test.d.ts +0 -1
- package/dist/context/cache-engine.test.js +0 -99
- package/dist/context/dependency-graph.test.d.ts +0 -1
- package/dist/context/dependency-graph.test.js +0 -13
- package/dist/context/index-status.test.d.ts +0 -1
- package/dist/context/index-status.test.js +0 -26
- package/dist/context.test.d.ts +0 -1
- package/dist/context.test.js +0 -228
- package/dist/deep/agent-review.test.d.ts +0 -1
- package/dist/deep/agent-review.test.js +0 -53
- package/dist/deep/code-context.test.d.ts +0 -1
- package/dist/deep/code-context.test.js +0 -48
- package/dist/deep/code-pass.test.d.ts +0 -1
- package/dist/deep/code-pass.test.js +0 -112
- package/dist/deep/code-review-prompt.test.d.ts +0 -1
- package/dist/deep/code-review-prompt.test.js +0 -20
- package/dist/deep/code-verifier.test.d.ts +0 -1
- package/dist/deep/code-verifier.test.js +0 -54
- package/dist/deep/diff-tests/calls.test.d.ts +0 -1
- package/dist/deep/diff-tests/calls.test.js +0 -33
- package/dist/deep/diff-tests/run.test.d.ts +0 -1
- package/dist/deep/diff-tests/run.test.js +0 -58
- package/dist/deep/fact-extractor.test.d.ts +0 -1
- package/dist/deep/fact-extractor.test.js +0 -581
- package/dist/deep/parse-findings.test.d.ts +0 -1
- package/dist/deep/parse-findings.test.js +0 -25
- package/dist/deep/pr-review.test.d.ts +0 -1
- package/dist/deep/pr-review.test.js +0 -80
- package/dist/deep/prompts.test.d.ts +0 -1
- package/dist/deep/prompts.test.js +0 -235
- package/dist/deep/reference-pack.test.d.ts +0 -1
- package/dist/deep/reference-pack.test.js +0 -46
- package/dist/deep/related-changes.test.d.ts +0 -1
- package/dist/deep/related-changes.test.js +0 -33
- package/dist/deep/review-context-export.test.d.ts +0 -1
- package/dist/deep/review-context-export.test.js +0 -32
- package/dist/deep/review-tools.test.d.ts +0 -1
- package/dist/deep/review-tools.test.js +0 -39
- package/dist/deep/risk.test.d.ts +0 -1
- package/dist/deep/risk.test.js +0 -101
- package/dist/deep/verifier.test.d.ts +0 -1
- package/dist/deep/verifier.test.js +0 -635
- package/dist/discovery.test.d.ts +0 -1
- package/dist/discovery.test.js +0 -93
- package/dist/environment.test.d.ts +0 -1
- package/dist/environment.test.js +0 -94
- package/dist/firewall/firewall.test.d.ts +0 -1
- package/dist/firewall/firewall.test.js +0 -117
- package/dist/firewall/trust-boundaries.test.d.ts +0 -1
- package/dist/firewall/trust-boundaries.test.js +0 -64
- package/dist/firewall/trusted-control.test.d.ts +0 -1
- package/dist/firewall/trusted-control.test.js +0 -188
- package/dist/gates/agent-team.test.d.ts +0 -1
- package/dist/gates/agent-team.test.js +0 -113
- package/dist/gates/ast.test.d.ts +0 -1
- package/dist/gates/ast.test.js +0 -112
- package/dist/gates/checkpoint.test.d.ts +0 -1
- package/dist/gates/checkpoint.test.js +0 -105
- package/dist/gates/content.test.d.ts +0 -1
- package/dist/gates/content.test.js +0 -73
- package/dist/gates/coverage.test.d.ts +0 -1
- package/dist/gates/coverage.test.js +0 -53
- package/dist/gates/dedupe-failures.test.d.ts +0 -1
- package/dist/gates/dedupe-failures.test.js +0 -12
- package/dist/gates/deep-analysis.test.d.ts +0 -1
- package/dist/gates/deep-analysis.test.js +0 -86
- package/dist/gates/deep-intent.test.d.ts +0 -1
- package/dist/gates/deep-intent.test.js +0 -51
- package/dist/gates/deep-timeout.test.d.ts +0 -1
- package/dist/gates/deep-timeout.test.js +0 -10
- package/dist/gates/deprecated-apis.test.d.ts +0 -1
- package/dist/gates/deprecated-apis.test.js +0 -318
- package/dist/gates/deprecated-dependencies.test.d.ts +0 -1
- package/dist/gates/deprecated-dependencies.test.js +0 -55
- package/dist/gates/frontend-secret-exposure.test.d.ts +0 -1
- package/dist/gates/frontend-secret-exposure.test.js +0 -148
- package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.js +0 -27
- package/dist/gates/hallucinated-imports/js-resolver-types.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports/js-resolver-types.test.js +0 -14
- package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports-sveltekit.test.js +0 -132
- package/dist/gates/hallucinated-imports.test.d.ts +0 -1
- package/dist/gates/hallucinated-imports.test.js +0 -1206
- package/dist/gates/js-style-context.test.d.ts +0 -1
- package/dist/gates/js-style-context.test.js +0 -35
- package/dist/gates/logic-drift.test.d.ts +0 -1
- package/dist/gates/logic-drift.test.js +0 -52
- package/dist/gates/phantom-apis.test.d.ts +0 -1
- package/dist/gates/phantom-apis.test.js +0 -396
- package/dist/gates/promise-safety.test.d.ts +0 -1
- package/dist/gates/promise-safety.test.js +0 -34
- package/dist/gates/runner.test.d.ts +0 -1
- package/dist/gates/runner.test.js +0 -77
- package/dist/gates/scoped-gates.test.d.ts +0 -1
- package/dist/gates/scoped-gates.test.js +0 -52
- package/dist/gates/security-patterns-owasp.test.d.ts +0 -1
- package/dist/gates/security-patterns-owasp.test.js +0 -186
- package/dist/gates/security-patterns.test.d.ts +0 -1
- package/dist/gates/security-patterns.test.js +0 -194
- package/dist/gates/semantic-bugs.test.d.ts +0 -1
- package/dist/gates/semantic-bugs.test.js +0 -76
- package/dist/gates/side-effect-analysis.test.d.ts +0 -1
- package/dist/gates/side-effect-analysis.test.js +0 -162
- package/dist/gates/style-drift.test.d.ts +0 -1
- package/dist/gates/style-drift.test.js +0 -26
- package/dist/gates/test-quality.test.d.ts +0 -1
- package/dist/gates/test-quality.test.js +0 -325
- package/dist/gates/trusted-reviews.test.d.ts +0 -1
- package/dist/gates/trusted-reviews.test.js +0 -16
- package/dist/gates/unindexed-reads/queries.test.d.ts +0 -1
- package/dist/gates/unindexed-reads/queries.test.js +0 -54
- package/dist/gates/unindexed-reads/schema.test.d.ts +0 -1
- package/dist/gates/unindexed-reads/schema.test.js +0 -81
- package/dist/gates/unindexed-reads/unindexed-reads.test.d.ts +0 -1
- package/dist/gates/unindexed-reads/unindexed-reads.test.js +0 -90
- package/dist/hooks/checker.test.d.ts +0 -1
- package/dist/hooks/checker.test.js +0 -159
- package/dist/hooks/dlp-confidence.test.d.ts +0 -1
- package/dist/hooks/dlp-confidence.test.js +0 -51
- package/dist/hooks/dlp-feedback.test.d.ts +0 -1
- package/dist/hooks/dlp-feedback.test.js +0 -131
- package/dist/hooks/input-validator.test.d.ts +0 -1
- package/dist/hooks/input-validator.test.js +0 -329
- package/dist/hooks/templates.test.d.ts +0 -1
- package/dist/hooks/templates.test.js +0 -27
- package/dist/inference/brain-placeholder.test.d.ts +0 -1
- package/dist/inference/brain-placeholder.test.js +0 -28
- package/dist/inference/cloud-provider.test.d.ts +0 -1
- package/dist/inference/cloud-provider.test.js +0 -139
- package/dist/inference/executable.test.d.ts +0 -1
- package/dist/inference/executable.test.js +0 -41
- package/dist/inference/http-download.test.d.ts +0 -1
- package/dist/inference/http-download.test.js +0 -109
- package/dist/inference/llama-engine-checksum.test.d.ts +0 -1
- package/dist/inference/llama-engine-checksum.test.js +0 -27
- package/dist/inference/llama-engine.test.d.ts +0 -1
- package/dist/inference/llama-engine.test.js +0 -51
- package/dist/inference/llama-process.test.d.ts +0 -1
- package/dist/inference/llama-process.test.js +0 -61
- package/dist/inference/local-model.test.d.ts +0 -1
- package/dist/inference/local-model.test.js +0 -23
- package/dist/inference/model-download.test.d.ts +0 -1
- package/dist/inference/model-download.test.js +0 -125
- package/dist/inference/model-manager.test.d.ts +0 -1
- package/dist/inference/model-manager.test.js +0 -24
- package/dist/inference/types.test.d.ts +0 -1
- package/dist/inference/types.test.js +0 -19
- package/dist/memory/recall.test.d.ts +0 -1
- package/dist/memory/recall.test.js +0 -36
- package/dist/pattern-index/indexer.test.d.ts +0 -6
- package/dist/pattern-index/indexer.test.js +0 -197
- package/dist/pattern-index/matcher.test.d.ts +0 -6
- package/dist/pattern-index/matcher.test.js +0 -238
- package/dist/pattern-index/pattern-reuse.test.d.ts +0 -1
- package/dist/pattern-index/pattern-reuse.test.js +0 -76
- package/dist/pattern-index/semantic-runtime.test.d.ts +0 -1
- package/dist/pattern-index/semantic-runtime.test.js +0 -31
- package/dist/pattern-index/staleness.test.d.ts +0 -6
- package/dist/pattern-index/staleness.test.js +0 -211
- package/dist/review/agent-fixes.test.d.ts +0 -1
- package/dist/review/agent-fixes.test.js +0 -32
- package/dist/review/backtest-init.test.d.ts +0 -1
- package/dist/review/backtest-init.test.js +0 -136
- package/dist/review/backtest-judges.test.d.ts +0 -1
- package/dist/review/backtest-judges.test.js +0 -32
- package/dist/review/backtest-last.test.d.ts +0 -1
- package/dist/review/backtest-last.test.js +0 -109
- package/dist/review/backtest.test.d.ts +0 -1
- package/dist/review/backtest.test.js +0 -183
- package/dist/review/baseline.test.d.ts +0 -1
- package/dist/review/baseline.test.js +0 -22
- package/dist/review/branch-checks.test.d.ts +0 -1
- package/dist/review/branch-checks.test.js +0 -48
- package/dist/review/check-outcomes.test.d.ts +0 -1
- package/dist/review/check-outcomes.test.js +0 -56
- package/dist/review/code-patterns.test.d.ts +0 -1
- package/dist/review/code-patterns.test.js +0 -142
- package/dist/review/dead-code.test.d.ts +0 -1
- package/dist/review/dead-code.test.js +0 -154
- package/dist/review/deep-runs.test.d.ts +0 -1
- package/dist/review/deep-runs.test.js +0 -23
- package/dist/review/effectiveness.test.d.ts +0 -1
- package/dist/review/effectiveness.test.js +0 -42
- package/dist/review/fix-scope.test.d.ts +0 -1
- package/dist/review/fix-scope.test.js +0 -104
- package/dist/review/generated-files.test.d.ts +0 -1
- package/dist/review/generated-files.test.js +0 -25
- package/dist/review/migration-order.test.d.ts +0 -1
- package/dist/review/migration-order.test.js +0 -62
- package/dist/review/quiet.test.d.ts +0 -1
- package/dist/review/quiet.test.js +0 -38
- package/dist/review/receipt.test.d.ts +0 -1
- package/dist/review/receipt.test.js +0 -60
- package/dist/review/review-task.test.d.ts +0 -1
- package/dist/review/review-task.test.js +0 -89
- package/dist/review/review.test.d.ts +0 -1
- package/dist/review/review.test.js +0 -203
- package/dist/review/reviewer/adapters.test.d.ts +0 -1
- package/dist/review/reviewer/adapters.test.js +0 -100
- package/dist/review/reviewer/api-judge.test.d.ts +0 -1
- package/dist/review/reviewer/api-judge.test.js +0 -108
- package/dist/review/reviewer/background.test.d.ts +0 -1
- package/dist/review/reviewer/background.test.js +0 -118
- package/dist/review/reviewer/context.test.d.ts +0 -1
- package/dist/review/reviewer/context.test.js +0 -45
- package/dist/review/reviewer/exec.test.d.ts +0 -1
- package/dist/review/reviewer/exec.test.js +0 -59
- package/dist/review/reviewer/inputs.test.d.ts +0 -1
- package/dist/review/reviewer/inputs.test.js +0 -52
- package/dist/review/reviewer/panel.test.d.ts +0 -1
- package/dist/review/reviewer/panel.test.js +0 -110
- package/dist/review/reviewer/record.test.d.ts +0 -1
- package/dist/review/reviewer/record.test.js +0 -40
- package/dist/review/reviewer/rule-writer.test.d.ts +0 -1
- package/dist/review/reviewer/rule-writer.test.js +0 -52
- package/dist/review/reviewer/settings.test.d.ts +0 -1
- package/dist/review/reviewer/settings.test.js +0 -57
- package/dist/review/reviewer/usage.test.d.ts +0 -1
- package/dist/review/reviewer/usage.test.js +0 -14
- package/dist/review/reviewer.test.d.ts +0 -1
- package/dist/review/reviewer.test.js +0 -792
- package/dist/review/stories.test.d.ts +0 -1
- package/dist/review/stories.test.js +0 -58
- package/dist/review/toolchain.test.d.ts +0 -1
- package/dist/review/toolchain.test.js +0 -108
- package/dist/review/typed/redundancy.test.d.ts +0 -1
- package/dist/review/typed/redundancy.test.js +0 -315
- package/dist/review/typed/schema-nullability.test.d.ts +0 -1
- package/dist/review/typed/schema-nullability.test.js +0 -63
- package/dist/review-learning/human-edits.test.d.ts +0 -1
- package/dist/review-learning/human-edits.test.js +0 -47
- package/dist/review-learning/repo-rules.test.d.ts +0 -1
- package/dist/review-learning/repo-rules.test.js +0 -93
- package/dist/review-learning/review-learning.test.d.ts +0 -1
- package/dist/review-learning/review-learning.test.js +0 -249
- package/dist/safety.test.d.ts +0 -1
- package/dist/safety.test.js +0 -42
- package/dist/semantic/benchmark.test.d.ts +0 -1
- package/dist/semantic/benchmark.test.js +0 -22
- package/dist/semantic/intent/intent.test.d.ts +0 -1
- package/dist/semantic/intent/intent.test.js +0 -46
- package/dist/semantic/learn/learn.test.d.ts +0 -1
- package/dist/semantic/learn/learn.test.js +0 -67
- package/dist/semantic/origins.test.d.ts +0 -1
- package/dist/semantic/origins.test.js +0 -77
- package/dist/semantic/project-facts.test.d.ts +0 -1
- package/dist/semantic/project-facts.test.js +0 -45
- package/dist/semantic/sites/call-sites.test.d.ts +0 -1
- package/dist/semantic/sites/call-sites.test.js +0 -46
- package/dist/services/adaptive-thresholds.test.d.ts +0 -1
- package/dist/services/adaptive-thresholds.test.js +0 -53
- package/dist/services/agent-history.test.d.ts +0 -1
- package/dist/services/agent-history.test.js +0 -69
- package/dist/services/context-scope-summary.test.d.ts +0 -1
- package/dist/services/context-scope-summary.test.js +0 -17
- package/dist/services/context-telemetry-service.test.d.ts +0 -1
- package/dist/services/context-telemetry-service.test.js +0 -182
- package/dist/services/cursor-usage-sync.test.d.ts +0 -1
- package/dist/services/cursor-usage-sync.test.js +0 -173
- package/dist/services/engineering-knowledge-graph.test.d.ts +0 -1
- package/dist/services/engineering-knowledge-graph.test.js +0 -76
- package/dist/services/model-pricing.test.d.ts +0 -1
- package/dist/services/model-pricing.test.js +0 -44
- package/dist/services/observed-savings.test.d.ts +0 -1
- package/dist/services/observed-savings.test.js +0 -37
- package/dist/services/score-history.test.d.ts +0 -1
- package/dist/services/score-history.test.js +0 -61
- package/dist/smoke.test.d.ts +0 -1
- package/dist/smoke.test.js +0 -17
- package/dist/storage/cache-cleanup.test.d.ts +0 -1
- package/dist/storage/cache-cleanup.test.js +0 -54
- package/dist/storage/context-telemetry.test.d.ts +0 -1
- package/dist/storage/context-telemetry.test.js +0 -80
- package/dist/storage/db.test.d.ts +0 -1
- package/dist/storage/db.test.js +0 -46
- package/dist/storage/fix-lessons.test.d.ts +0 -1
- package/dist/storage/fix-lessons.test.js +0 -61
- package/dist/storage/lessons.test.d.ts +0 -1
- package/dist/storage/lessons.test.js +0 -81
- package/dist/storage/local-encryption.test.d.ts +0 -1
- package/dist/storage/local-encryption.test.js +0 -34
- package/dist/storage/local-memory.test.d.ts +0 -1
- package/dist/storage/local-memory.test.js +0 -55
- package/dist/storage/share-memory.test.d.ts +0 -1
- package/dist/storage/share-memory.test.js +0 -34
- package/dist/storage/team-diagnostics.test.d.ts +0 -1
- package/dist/storage/team-diagnostics.test.js +0 -51
- package/dist/storage/team-scope.test.d.ts +0 -1
- package/dist/storage/team-scope.test.js +0 -22
- package/dist/storage/team-store.test.d.ts +0 -1
- package/dist/storage/team-store.test.js +0 -11
- package/dist/storage/team-sync-scope.test.d.ts +0 -1
- package/dist/storage/team-sync-scope.test.js +0 -100
- package/dist/storage/team-vector-store.test.d.ts +0 -1
- package/dist/storage/team-vector-store.test.js +0 -56
- package/dist/storage/telemetry-scope.test.d.ts +0 -1
- package/dist/storage/telemetry-scope.test.js +0 -38
- package/dist/task/thread.test.d.ts +0 -1
- package/dist/task/thread.test.js +0 -133
- package/dist/telemetry/telemetry.test.d.ts +0 -1
- package/dist/telemetry/telemetry.test.js +0 -64
- package/dist/types/index.test.d.ts +0 -1
- package/dist/types/index.test.js +0 -33
- package/dist/utils/command-line.test.d.ts +0 -1
- package/dist/utils/command-line.test.js +0 -8
- package/dist/utils/diff-removed.test.d.ts +0 -1
- package/dist/utils/diff-removed.test.js +0 -29
- package/dist/utils/diff.test.d.ts +0 -1
- package/dist/utils/diff.test.js +0 -38
- package/dist/utils/glob-paths.test.d.ts +0 -1
- package/dist/utils/glob-paths.test.js +0 -40
- package/dist/utils/profile.test.d.ts +0 -1
- package/dist/utils/profile.test.js +0 -68
- package/dist/utils/scanner.test.d.ts +0 -1
- package/dist/utils/scanner.test.js +0 -48
- package/dist/utils/scope.test.d.ts +0 -1
- package/dist/utils/scope.test.js +0 -38
package/dist/discovery.test.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import os from 'os';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { DiscoveryService } from './discovery.js';
|
|
6
|
-
import { containsToken, detectParadigm, stripComments } from './discovery-signals.js';
|
|
7
|
-
describe('DiscoveryService', () => {
|
|
8
|
-
let cwd;
|
|
9
|
-
beforeEach(() => { cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'discovery-')); });
|
|
10
|
-
afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
|
|
11
|
-
function write(rel, body) {
|
|
12
|
-
fs.mkdirSync(path.dirname(path.join(cwd, rel)), { recursive: true });
|
|
13
|
-
fs.writeFileSync(path.join(cwd, rel), body);
|
|
14
|
-
}
|
|
15
|
-
const discover = () => new DiscoveryService().discover(cwd);
|
|
16
|
-
it('detects an API from a declared dependency', async () => {
|
|
17
|
-
write('package.json', JSON.stringify({ dependencies: { express: '^4.0.0' } }));
|
|
18
|
-
const result = await discover();
|
|
19
|
-
expect(result.matches.preset).toEqual({ name: 'api', marker: 'dependency:express' });
|
|
20
|
-
});
|
|
21
|
-
it('detects a scoped framework package such as @nestjs/core', async () => {
|
|
22
|
-
write('package.json', JSON.stringify({ dependencies: { '@nestjs/core': '^10.0.0' } }));
|
|
23
|
-
expect((await discover()).matches.preset?.marker).toBe('dependency:nestjs');
|
|
24
|
-
});
|
|
25
|
-
it('detects a Python API with its ignore patterns', async () => {
|
|
26
|
-
write('requirements.txt', 'flask==2.0.0\n');
|
|
27
|
-
const result = await discover();
|
|
28
|
-
expect(result.matches.preset?.name).toBe('api');
|
|
29
|
-
expect(result.config.ignore).toEqual(expect.arrayContaining(['venv/**', '__pycache__/**', '*.pyc']));
|
|
30
|
-
});
|
|
31
|
-
it('detects a UI project from a config file with its ignore patterns', async () => {
|
|
32
|
-
write('next.config.js', 'module.exports = {};\n');
|
|
33
|
-
const result = await discover();
|
|
34
|
-
expect(result.matches.preset?.name).toBe('ui');
|
|
35
|
-
expect(result.config.ignore).toEqual(expect.arrayContaining(['node_modules/**', '.next/**']));
|
|
36
|
-
});
|
|
37
|
-
it('detects Svelte from its dependencies, not "reactivity" or "public" in comments', async () => {
|
|
38
|
-
write('package.json', JSON.stringify({ devDependencies: { '@sveltejs/kit': '^2.0.0', svelte: '^5.0.0' } }));
|
|
39
|
-
write('eslint.config.js', '// Svelte 5 rules (state_referenced_locally, a11y, reactivity)\nexport default [];\n');
|
|
40
|
-
write('playwright.config.ts', '// rides the public internet\nexport default {};\n');
|
|
41
|
-
write('src/lib/format.ts', 'export function format(v: number) { return v.toFixed(2); }\nexport const pad = (s: string) => s.padStart(2);\n');
|
|
42
|
-
const result = await discover();
|
|
43
|
-
expect(result.matches.preset).toEqual({ name: 'ui', marker: 'dependency:svelte' });
|
|
44
|
-
expect(result.matches.paradigm?.name).toBe('functional');
|
|
45
|
-
});
|
|
46
|
-
it('does not read a framework name in source text as a dependency', async () => {
|
|
47
|
-
write('src/middleware.ts', 'export function run(next: () => void) { next(); }\n');
|
|
48
|
-
expect((await discover()).matches.preset).toBeUndefined();
|
|
49
|
-
});
|
|
50
|
-
it('identifies OOP from class declarations in a subfolder', async () => {
|
|
51
|
-
write('src/Service.ts', 'export class MyService {}\nexport class OtherService {}\n');
|
|
52
|
-
const result = await discover();
|
|
53
|
-
expect(result.matches.paradigm).toEqual({ name: 'oop', marker: 'declarations: 2 classes, 0 functions' });
|
|
54
|
-
});
|
|
55
|
-
it('needs several recurring domain keywords before choosing a domain preset', async () => {
|
|
56
|
-
write('src/billing.ts', 'export const ledger = new Map<string, number>();\n');
|
|
57
|
-
write('src/report.ts', "import { ledger } from './billing';\nexport const total = () => ledger.size;\n");
|
|
58
|
-
expect((await discover()).matches.preset).toBeUndefined();
|
|
59
|
-
write('src/pay.ts', 'export function payment(ledger: Map<string, number>) { return ledger; }\n');
|
|
60
|
-
write('src/refund.ts', 'export function refund(payment: number) { return -payment; }\n');
|
|
61
|
-
expect((await discover()).matches.preset).toEqual({ name: 'fintech', marker: 'content:payment,ledger' });
|
|
62
|
-
});
|
|
63
|
-
it('needs a file marker to exist, not to be mentioned', async () => {
|
|
64
|
-
write('src/serve.ts', "export const page = () => 'index.html';\n");
|
|
65
|
-
expect((await discover()).matches.preset).toBeUndefined();
|
|
66
|
-
});
|
|
67
|
-
it('detects notebooks by file, not by the word ipynb in code', async () => {
|
|
68
|
-
write('src/scan.ts', "export const exts = ['.ipynb', '.py'];\n");
|
|
69
|
-
expect((await discover()).matches.preset).toBeUndefined();
|
|
70
|
-
write('notebooks/explore.ipynb', '{}');
|
|
71
|
-
expect((await discover()).matches.preset).toEqual({ name: 'data', marker: 'file:*.ipynb' });
|
|
72
|
-
});
|
|
73
|
-
it('leaves the paradigm unset for an empty project', async () => {
|
|
74
|
-
expect((await discover()).matches.paradigm).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
describe('discovery signals', () => {
|
|
78
|
-
it('matches whole tokens only', () => {
|
|
79
|
-
expect(containsToken('import x from "react"', 'react')).toBe(true);
|
|
80
|
-
expect(containsToken('reactivity', 'react')).toBe(false);
|
|
81
|
-
expect(containsToken('@vue/compiler', 'vue')).toBe(false);
|
|
82
|
-
expect(containsToken('GET /health', 'health')).toBe(false);
|
|
83
|
-
});
|
|
84
|
-
it('drops comments but keeps URLs in strings', () => {
|
|
85
|
-
const text = stripComments('/* public */\nconst u = "https://x.io"; // public\n# public\n');
|
|
86
|
-
expect(text).not.toContain('public');
|
|
87
|
-
expect(text).toContain('https://x.io');
|
|
88
|
-
});
|
|
89
|
-
it('declines to guess a paradigm when neither side clearly leads', () => {
|
|
90
|
-
expect(detectParadigm(['export class A {}\nexport function f() {}\n'])).toBeNull();
|
|
91
|
-
expect(detectParadigm(['export class A {}\nexport function f() {}\nexport function g() {}\n'])?.name).toBe('functional');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/environment.test.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { GateRunner } from './gates/runner.js';
|
|
3
|
-
import { ConfigSchema } from './types/index.js';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import os from 'os';
|
|
7
|
-
describe('Environment Alignment Gate', () => {
|
|
8
|
-
const testDir = path.join(os.tmpdir(), 'rigour-temp-test-env-' + process.pid);
|
|
9
|
-
beforeEach(async () => {
|
|
10
|
-
await fs.ensureDir(testDir);
|
|
11
|
-
});
|
|
12
|
-
afterEach(async () => {
|
|
13
|
-
await fs.remove(testDir);
|
|
14
|
-
});
|
|
15
|
-
it('should detect tool version mismatch (Explicit)', async () => {
|
|
16
|
-
const rawConfig = {
|
|
17
|
-
gates: {
|
|
18
|
-
environment: {
|
|
19
|
-
enabled: true,
|
|
20
|
-
enforce_contracts: false,
|
|
21
|
-
tools: {
|
|
22
|
-
node: ">=99.0.0" // Guaranteed to fail
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
const config = ConfigSchema.parse(rawConfig);
|
|
28
|
-
const runner = new GateRunner(config);
|
|
29
|
-
const report = await runner.run(testDir);
|
|
30
|
-
expect(report.status).toBe('FAIL');
|
|
31
|
-
const envFailure = report.failures.find(f => f.id === 'environment-alignment');
|
|
32
|
-
expect(envFailure).toBeDefined();
|
|
33
|
-
expect(envFailure?.details).toContain('node');
|
|
34
|
-
expect(envFailure?.details).toContain('version mismatch');
|
|
35
|
-
});
|
|
36
|
-
it('should detect missing environment variables', async () => {
|
|
37
|
-
const rawConfig = {
|
|
38
|
-
gates: {
|
|
39
|
-
environment: {
|
|
40
|
-
enabled: true,
|
|
41
|
-
required_env: ["RIGOUR_TEST_VAR_MISSING"]
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const config = ConfigSchema.parse(rawConfig);
|
|
46
|
-
const runner = new GateRunner(config);
|
|
47
|
-
const report = await runner.run(testDir);
|
|
48
|
-
expect(report.status).toBe('FAIL');
|
|
49
|
-
expect(report.failures[0].details).toContain('RIGOUR_TEST_VAR_MISSING');
|
|
50
|
-
});
|
|
51
|
-
it('should discover contracts from pyproject.toml', async () => {
|
|
52
|
-
// Create mock pyproject.toml with a version that will surely fail
|
|
53
|
-
await fs.writeFile(path.join(testDir, 'pyproject.toml'), `
|
|
54
|
-
[tool.ruff]
|
|
55
|
-
ruff = ">=99.14.0"
|
|
56
|
-
`);
|
|
57
|
-
const rawConfig = {
|
|
58
|
-
gates: {
|
|
59
|
-
environment: {
|
|
60
|
-
enabled: true,
|
|
61
|
-
enforce_contracts: true,
|
|
62
|
-
tools: {} // Should discover ruff from file
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
const config = ConfigSchema.parse(rawConfig);
|
|
67
|
-
const runner = new GateRunner(config);
|
|
68
|
-
const report = await runner.run(testDir);
|
|
69
|
-
// This might pass or fail depending on the local ruff version,
|
|
70
|
-
// but we want to check if the gate attempted to check ruff.
|
|
71
|
-
// If ruff is missing, it will fail with "is missing".
|
|
72
|
-
const ruffFailure = report.failures.find(f => f.details.includes('ruff'));
|
|
73
|
-
expect(ruffFailure).toBeDefined();
|
|
74
|
-
});
|
|
75
|
-
it('should prioritize environment gate and run it first', async () => {
|
|
76
|
-
const rawConfig = {
|
|
77
|
-
gates: {
|
|
78
|
-
max_file_lines: 1,
|
|
79
|
-
environment: {
|
|
80
|
-
enabled: true,
|
|
81
|
-
required_env: ["MANDATORY_SECRET_MISSING"]
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const config = ConfigSchema.parse(rawConfig);
|
|
86
|
-
// Create a file that would fail max_file_lines
|
|
87
|
-
await fs.writeFile(path.join(testDir, 'large.js'), 'line1\nline2\nline3');
|
|
88
|
-
const runner = new GateRunner(config);
|
|
89
|
-
const report = await runner.run(testDir);
|
|
90
|
-
// In a real priority system, we might want to stop after environment failure.
|
|
91
|
-
// Currently GateRunner runs all, but environment-alignment has been unshifted.
|
|
92
|
-
expect(Object.keys(report.summary)[0]).toBe('environment-alignment');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { evaluateTypedCommand, parseCommandLine, evaluateWriteScope, evaluateActionDeterministic, runAdversarialCorpus, CapabilityBroker, validateAgentClaimedScope, } from './index.js';
|
|
3
|
-
describe('typed command firewall', () => {
|
|
4
|
-
it('denies shell metacharacters', () => {
|
|
5
|
-
const ev = evaluateTypedCommand('ls; rm -rf /');
|
|
6
|
-
expect(ev.decision).toBe('deny');
|
|
7
|
-
expect(ev.ruleId).toBe('shell.no-meta');
|
|
8
|
-
});
|
|
9
|
-
it('treats a Windows shim as the program it starts, and still denies an unknown one', () => {
|
|
10
|
-
expect(evaluateTypedCommand('D:\\repo\\node_modules\\.bin\\vitest.cmd run a.test.ts').decision).toBe('allow');
|
|
11
|
-
expect(evaluateTypedCommand('D:\\tools\\curl.exe https://evil.example').decision).toBe('deny');
|
|
12
|
-
});
|
|
13
|
-
it('denies unknown binaries including node/npx', () => {
|
|
14
|
-
expect(evaluateTypedCommand('curl https://evil.example').decision).toBe('deny');
|
|
15
|
-
expect(evaluateTypedCommand('node -e "1"').decision).toBe('deny');
|
|
16
|
-
expect(evaluateTypedCommand('npx cowsay hi').decision).toBe('deny');
|
|
17
|
-
});
|
|
18
|
-
it('denies git push', () => {
|
|
19
|
-
const ev = evaluateTypedCommand('git push origin main');
|
|
20
|
-
expect(ev.decision).toBe('deny');
|
|
21
|
-
expect(ev.ruleId).toBe('git.destructive');
|
|
22
|
-
});
|
|
23
|
-
it('allows vitest', () => {
|
|
24
|
-
const ev = evaluateTypedCommand('vitest run packages/rigour-core');
|
|
25
|
-
expect(ev.decision).toBe('allow');
|
|
26
|
-
expect(parseCommandLine('vitest run x')?.bin).toBe('vitest');
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
describe('scope enforcement', () => {
|
|
30
|
-
it('denies unbound writer when scopes exist', () => {
|
|
31
|
-
const ev = evaluateWriteScope('/repo', 'packages/rigour-core/src/x.ts', [
|
|
32
|
-
{ agentId: 'a1', taskScope: ['packages/rigour-core/**'] },
|
|
33
|
-
]);
|
|
34
|
-
expect(ev.decision).toBe('scope-violation');
|
|
35
|
-
expect(ev.ruleId).toBe('scope.unbound');
|
|
36
|
-
});
|
|
37
|
-
it('flags out-of-scope writes for bound agent', () => {
|
|
38
|
-
const ev = evaluateWriteScope('/repo', '.github/workflows/x.yml', [
|
|
39
|
-
{ agentId: 'a1', taskScope: ['packages/rigour-core/**'] },
|
|
40
|
-
], 'a1');
|
|
41
|
-
expect(ev.decision).toBe('scope-violation');
|
|
42
|
-
});
|
|
43
|
-
it('allows in-scope writes for bound agent', () => {
|
|
44
|
-
const ev = evaluateWriteScope('/repo', 'packages/rigour-core/src/firewall/types.ts', [
|
|
45
|
-
{ agentId: 'a1', taskScope: ['packages/rigour-core/**'] },
|
|
46
|
-
], 'a1');
|
|
47
|
-
expect(ev.decision).toBe('allow');
|
|
48
|
-
});
|
|
49
|
-
it('rejects broad self-registration scopes', () => {
|
|
50
|
-
expect(validateAgentClaimedScope(['**/*']).ok).toBe(false);
|
|
51
|
-
expect(validateAgentClaimedScope(['.github/**']).ok).toBe(false);
|
|
52
|
-
expect(validateAgentClaimedScope(['packages/rigour-core/**']).ok).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe('capability broker', () => {
|
|
56
|
-
it('requires explicit authority in enforce mode and reports shadow decisions in observe mode', () => {
|
|
57
|
-
const base = { defaultTtlMs: 1000, toolAllowlist: ['*'], agentScopes: [] };
|
|
58
|
-
const enforced = new CapabilityBroker({ ...base, authorizationMode: 'enforce' });
|
|
59
|
-
expect(enforced.evaluateProposal({ action: 'mcp.call', resource: 'github__get_issue' })).toMatchObject({
|
|
60
|
-
decision: 'deny', ruleId: 'capability.required',
|
|
61
|
-
});
|
|
62
|
-
const observed = new CapabilityBroker({ ...base, authorizationMode: 'observe' });
|
|
63
|
-
expect(observed.evaluateProposal({ action: 'mcp.call', resource: 'github__get_issue' })).toMatchObject({
|
|
64
|
-
decision: 'allow', simulatedDecision: 'deny', ruleId: 'capability.observe-required',
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
it('denies undeclared MCP tools', () => {
|
|
68
|
-
const broker = new CapabilityBroker({
|
|
69
|
-
defaultTtlMs: 1000,
|
|
70
|
-
toolAllowlist: ['rigour_check'],
|
|
71
|
-
agentScopes: [],
|
|
72
|
-
});
|
|
73
|
-
const ev = broker.evaluateProposal({
|
|
74
|
-
action: 'mcp.call',
|
|
75
|
-
resource: 'slack.post_message',
|
|
76
|
-
});
|
|
77
|
-
expect(ev.decision).toBe('deny');
|
|
78
|
-
expect(ev.ruleId).toBe('mcp.undeclared');
|
|
79
|
-
});
|
|
80
|
-
it('issues one-use capabilities', () => {
|
|
81
|
-
const broker = new CapabilityBroker({
|
|
82
|
-
defaultTtlMs: 60_000,
|
|
83
|
-
toolAllowlist: ['*'],
|
|
84
|
-
agentScopes: [],
|
|
85
|
-
});
|
|
86
|
-
const grant = broker.issue('mcp.call', 'rigour_check');
|
|
87
|
-
const ok = broker.evaluateProposal({
|
|
88
|
-
action: 'mcp.call',
|
|
89
|
-
resource: 'rigour_check',
|
|
90
|
-
capabilityId: grant.id,
|
|
91
|
-
});
|
|
92
|
-
expect(ok.decision).toBe('allow');
|
|
93
|
-
const again = broker.evaluateProposal({
|
|
94
|
-
action: 'mcp.call',
|
|
95
|
-
resource: 'rigour_check',
|
|
96
|
-
capabilityId: grant.id,
|
|
97
|
-
});
|
|
98
|
-
expect(again.decision).toBe('deny');
|
|
99
|
-
expect(again.ruleId).toBe('capability.reuse');
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
describe('adversarial corpus', () => {
|
|
103
|
-
it('passes default regression corpus', () => {
|
|
104
|
-
const report = runAdversarialCorpus();
|
|
105
|
-
expect(report.failed).toBe(0);
|
|
106
|
-
expect(report.passed).toBeGreaterThan(0);
|
|
107
|
-
});
|
|
108
|
-
it('evaluateActionDeterministic matches expected deny', () => {
|
|
109
|
-
const ev = evaluateActionDeterministic({
|
|
110
|
-
action: 'mcp.call',
|
|
111
|
-
resource: 'github.merge',
|
|
112
|
-
toolAllowlist: [],
|
|
113
|
-
agentScopes: [],
|
|
114
|
-
});
|
|
115
|
-
expect(ev.decision).toBe('deny');
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'child_process';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
-
import { issueArbitrationToken, signArbitrationDecision, verifyArbitrationDecision } from './arbitration-token.js';
|
|
7
|
-
import { admitForCi, createAttestation } from './attestation.js';
|
|
8
|
-
let repo;
|
|
9
|
-
const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' });
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'trust-'));
|
|
12
|
-
git('init', '-q', '-b', 'main');
|
|
13
|
-
git('config', 'user.email', 't@example.com');
|
|
14
|
-
git('config', 'user.name', 't');
|
|
15
|
-
git('config', 'commit.gpgsign', 'false');
|
|
16
|
-
fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
|
|
17
|
-
git('add', '-A');
|
|
18
|
-
git('commit', '-qm', 'init');
|
|
19
|
-
});
|
|
20
|
-
afterEach(() => fs.rmSync(repo, { recursive: true, force: true }));
|
|
21
|
-
describe('human arbitration', () => {
|
|
22
|
-
it('accepts only a decision signed with the issued token, once', async () => {
|
|
23
|
-
const token = await issueArbitrationToken(repo, 'r1');
|
|
24
|
-
expect(fs.existsSync(path.join(repo, '.rigour', 'arbitration-tokens.json'))).toBe(false);
|
|
25
|
-
// A line any process can append to the event log carries no valid proof.
|
|
26
|
-
expect(verifyArbitrationDecision(token, 'r1', 'approve', undefined)).toBe(false);
|
|
27
|
-
expect(verifyArbitrationDecision(token, 'r1', 'approve', 'f'.repeat(64))).toBe(false);
|
|
28
|
-
const proof = await signArbitrationDecision(repo, 'r1', 'approve');
|
|
29
|
-
expect(verifyArbitrationDecision(token, 'r1', 'approve', proof)).toBe(true);
|
|
30
|
-
expect(verifyArbitrationDecision(token, 'r1', 'reject', proof)).toBe(false);
|
|
31
|
-
expect(await signArbitrationDecision(repo, 'r1', 'approve')).toBeNull();
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
describe('CI admission', () => {
|
|
35
|
-
const tx = (over = {}) => ({
|
|
36
|
-
id: 'tx1', agentId: 'agent', scope: ['**/*'], policyHash: 'policy-a', capabilitiesIssued: [], filesChanged: ['a.ts'], ...over,
|
|
37
|
-
});
|
|
38
|
-
const pass = { status: 'PASS', failedGates: [] };
|
|
39
|
-
it('admits a fresh, bound bundle whose files are unchanged', async () => {
|
|
40
|
-
await createAttestation(repo, { transaction: tx(), gateResults: pass });
|
|
41
|
-
expect(await admitForCi(repo)).toMatchObject({ admit: true });
|
|
42
|
-
});
|
|
43
|
-
it('refuses when an attested file differs from what was signed, even at the same commit', async () => {
|
|
44
|
-
await createAttestation(repo, { transaction: tx(), gateResults: pass });
|
|
45
|
-
fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 2;\n'); // HEAD and its tree are unchanged
|
|
46
|
-
expect(await admitForCi(repo)).toMatchObject({ admit: false, reason: expect.stringContaining('artifactDigest') });
|
|
47
|
-
});
|
|
48
|
-
it('refuses a bundle produced under another policy than the one required', async () => {
|
|
49
|
-
await createAttestation(repo, { transaction: tx(), gateResults: pass });
|
|
50
|
-
expect(await admitForCi(repo, { policyHash: 'policy-b' })).toMatchObject({ admit: false });
|
|
51
|
-
expect(await admitForCi(repo, { policyHash: 'policy-a' })).toMatchObject({ admit: true });
|
|
52
|
-
});
|
|
53
|
-
it('refuses a bundle bound to no commit or tree', async () => {
|
|
54
|
-
const noRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'trust-norepo-'));
|
|
55
|
-
try {
|
|
56
|
-
fs.writeFileSync(path.join(noRepo, 'a.ts'), 'export const a = 1;\n');
|
|
57
|
-
await createAttestation(noRepo, { transaction: tx(), gateResults: pass });
|
|
58
|
-
expect(await admitForCi(noRepo)).toMatchObject({ admit: false, reason: 'Attestation is bound to no commit or tree' });
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
fs.rmSync(noRepo, { recursive: true, force: true });
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { appendExecutionReceipt, consumeTrustedGrant, getTrustedControlDir, issueTrustedGrant, listTrustedGrants, normalizeMcpAction, saveGatewayConfig, verifyExecutionReceiptChain, } from './index.js';
|
|
6
|
-
const repositories = [];
|
|
7
|
-
const controlRoots = [];
|
|
8
|
-
async function repository() {
|
|
9
|
-
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-gateway-test-'));
|
|
10
|
-
repositories.push(cwd);
|
|
11
|
-
return cwd;
|
|
12
|
-
}
|
|
13
|
-
async function controlRoot() {
|
|
14
|
-
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-control-test-'));
|
|
15
|
-
controlRoots.push(root);
|
|
16
|
-
return root;
|
|
17
|
-
}
|
|
18
|
-
afterEach(async () => {
|
|
19
|
-
for (const cwd of repositories.splice(0)) {
|
|
20
|
-
await fs.remove(cwd);
|
|
21
|
-
}
|
|
22
|
-
for (const root of controlRoots.splice(0))
|
|
23
|
-
await fs.remove(root);
|
|
24
|
-
});
|
|
25
|
-
describe('trusted gateway control', () => {
|
|
26
|
-
it('stores configuration outside the repository', async () => {
|
|
27
|
-
const cwd = await repository();
|
|
28
|
-
const root = await controlRoot();
|
|
29
|
-
const location = await saveGatewayConfig(cwd, {
|
|
30
|
-
version: 1,
|
|
31
|
-
mode: 'observe',
|
|
32
|
-
principalId: 'owner',
|
|
33
|
-
agentId: 'agent-1',
|
|
34
|
-
taskId: 'task-1',
|
|
35
|
-
servers: { github: { command: 'github-mcp', allow: ['get_issue'] } },
|
|
36
|
-
}, root);
|
|
37
|
-
expect(location.startsWith(cwd)).toBe(false);
|
|
38
|
-
expect(await fs.pathExists(location)).toBe(true);
|
|
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
|
-
});
|
|
53
|
-
it('consumes a capability exactly once', async () => {
|
|
54
|
-
const cwd = await repository();
|
|
55
|
-
const root = await controlRoot();
|
|
56
|
-
const grant = await issueTrustedGrant(cwd, {
|
|
57
|
-
issuerId: 'owner', subjectId: 'agent-1', taskId: 'task-1',
|
|
58
|
-
action: 'mcp.call', resource: 'mcp://github/create_issue', ttlMs: 60_000,
|
|
59
|
-
}, root);
|
|
60
|
-
const proposal = {
|
|
61
|
-
id: grant.id, subjectId: 'agent-1', taskId: 'task-1',
|
|
62
|
-
action: 'mcp.call', resource: 'mcp://github/create_issue',
|
|
63
|
-
};
|
|
64
|
-
expect((await consumeTrustedGrant(cwd, proposal, root)).ok).toBe(true);
|
|
65
|
-
expect((await consumeTrustedGrant(cwd, proposal, root)).ok).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
it('rejects path traversal in a capability id', async () => {
|
|
68
|
-
const cwd = await repository();
|
|
69
|
-
const root = await controlRoot();
|
|
70
|
-
const result = await consumeTrustedGrant(cwd, {
|
|
71
|
-
id: '../../gateway', subjectId: 'agent-1', taskId: 'task-1',
|
|
72
|
-
action: 'mcp.call', resource: 'mcp://github/create_issue',
|
|
73
|
-
}, root);
|
|
74
|
-
expect(result).toMatchObject({ ok: false, reason: 'Invalid capability id' });
|
|
75
|
-
});
|
|
76
|
-
it('rejects delegated authority that expands its parent', async () => {
|
|
77
|
-
const cwd = await repository();
|
|
78
|
-
const root = await controlRoot();
|
|
79
|
-
const parent = await issueTrustedGrant(cwd, {
|
|
80
|
-
issuerId: 'owner', subjectId: 'lead-agent', taskId: 'task-1',
|
|
81
|
-
action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 60_000,
|
|
82
|
-
}, root);
|
|
83
|
-
await expect(issueTrustedGrant(cwd, {
|
|
84
|
-
issuerId: 'lead-agent', subjectId: 'worker-agent', taskId: 'task-1',
|
|
85
|
-
action: 'mcp.call', resource: 'mcp://github/delete_repo', ttlMs: 30_000,
|
|
86
|
-
parentCapabilityId: parent.id,
|
|
87
|
-
}, root)).rejects.toThrow('preserve parent action and resource');
|
|
88
|
-
});
|
|
89
|
-
it('consumes the parent when delegated authority is issued', async () => {
|
|
90
|
-
const cwd = await repository();
|
|
91
|
-
const root = await controlRoot();
|
|
92
|
-
const parent = await issueTrustedGrant(cwd, {
|
|
93
|
-
issuerId: 'owner', subjectId: 'lead-agent', taskId: 'task-1',
|
|
94
|
-
action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 60_000,
|
|
95
|
-
}, root);
|
|
96
|
-
const child = await issueTrustedGrant(cwd, {
|
|
97
|
-
issuerId: 'lead-agent', subjectId: 'worker-agent', taskId: 'task-1',
|
|
98
|
-
action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 30_000,
|
|
99
|
-
parentCapabilityId: parent.id,
|
|
100
|
-
}, root);
|
|
101
|
-
expect(child.parentCapabilityId).toBe(parent.id);
|
|
102
|
-
const parentResult = await consumeTrustedGrant(cwd, {
|
|
103
|
-
id: parent.id, subjectId: 'lead-agent', taskId: 'task-1',
|
|
104
|
-
action: 'mcp.call', resource: 'mcp://github/get_issue',
|
|
105
|
-
}, root);
|
|
106
|
-
expect(parentResult).toMatchObject({ ok: false, reason: 'Capability already consumed' });
|
|
107
|
-
});
|
|
108
|
-
it('invalidates grants when the trusted policy changes', async () => {
|
|
109
|
-
const cwd = await repository();
|
|
110
|
-
const root = await controlRoot();
|
|
111
|
-
const base = {
|
|
112
|
-
version: 1, principalId: 'owner', agentId: 'agent-1', taskId: 'task-1',
|
|
113
|
-
servers: { github: { command: 'github-mcp', allow: ['get_issue'] } },
|
|
114
|
-
};
|
|
115
|
-
await saveGatewayConfig(cwd, { ...base, mode: 'observe' }, root);
|
|
116
|
-
const grant = await issueTrustedGrant(cwd, {
|
|
117
|
-
issuerId: 'owner', subjectId: 'agent-1', taskId: 'task-1',
|
|
118
|
-
action: 'mcp.call', resource: 'mcp://github/get_issue', ttlMs: 60_000,
|
|
119
|
-
}, root);
|
|
120
|
-
await saveGatewayConfig(cwd, { ...base, mode: 'enforce' }, root);
|
|
121
|
-
const result = await consumeTrustedGrant(cwd, {
|
|
122
|
-
id: grant.id, subjectId: 'agent-1', taskId: 'task-1',
|
|
123
|
-
action: 'mcp.call', resource: 'mcp://github/get_issue',
|
|
124
|
-
}, root);
|
|
125
|
-
expect(result).toMatchObject({ ok: false, reason: 'Capability policy is stale' });
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
describe('action normalization and receipts', () => {
|
|
129
|
-
it('defaults unknown tools to external writes instead of assuming read-only', () => {
|
|
130
|
-
const action = normalizeMcpAction({
|
|
131
|
-
actorId: 'agent-1', taskId: 'task-1', server: 'custom', tool: 'execute', args: {},
|
|
132
|
-
});
|
|
133
|
-
expect(action.sideEffect).toBe('external-write');
|
|
134
|
-
expect(action.blastRadius).toBe('unknown');
|
|
135
|
-
});
|
|
136
|
-
it('detects receipt tampering', async () => {
|
|
137
|
-
const cwd = await repository();
|
|
138
|
-
const root = await controlRoot();
|
|
139
|
-
const action = normalizeMcpAction({
|
|
140
|
-
actorId: 'agent-1', taskId: 'task-1', server: 'github', tool: 'get_issue', args: {},
|
|
141
|
-
});
|
|
142
|
-
await appendExecutionReceipt(cwd, {
|
|
143
|
-
action, mode: 'observe', decision: 'allow', simulatedDecision: 'deny',
|
|
144
|
-
reason: 'Observed', policyHash: 'policy', outcome: 'forwarded',
|
|
145
|
-
}, root);
|
|
146
|
-
expect(await verifyExecutionReceiptChain(cwd, root)).toMatchObject({ valid: true, count: 1 });
|
|
147
|
-
const log = path.join(getTrustedControlDir(cwd, root), 'receipts.jsonl');
|
|
148
|
-
const receipt = JSON.parse((await fs.readFile(log, 'utf8')).trim());
|
|
149
|
-
receipt.reason = 'tampered';
|
|
150
|
-
await fs.writeFile(log, `${JSON.stringify(receipt)}\n`);
|
|
151
|
-
expect((await verifyExecutionReceiptChain(cwd, root)).valid).toBe(false);
|
|
152
|
-
});
|
|
153
|
-
it('serializes concurrent receipt appends into one valid chain', async () => {
|
|
154
|
-
const cwd = await repository();
|
|
155
|
-
const root = await controlRoot();
|
|
156
|
-
const action = normalizeMcpAction({
|
|
157
|
-
actorId: 'agent-1', taskId: 'task-1', server: 'github', tool: 'get_issue', args: {},
|
|
158
|
-
});
|
|
159
|
-
await Promise.all(Array.from({ length: 8 }, () => appendExecutionReceipt(cwd, {
|
|
160
|
-
action, mode: 'observe', decision: 'allow', reason: 'Concurrent call',
|
|
161
|
-
policyHash: 'policy', outcome: 'forwarded',
|
|
162
|
-
}, root)));
|
|
163
|
-
expect(await verifyExecutionReceiptChain(cwd, root)).toMatchObject({ valid: true, count: 8 });
|
|
164
|
-
});
|
|
165
|
-
it('keeps the trusted receipt when the Studio projection is blocked', async () => {
|
|
166
|
-
const cwd = await repository();
|
|
167
|
-
const root = await controlRoot();
|
|
168
|
-
await fs.writeFile(path.join(cwd, '.rigour'), 'sandbox blocks this projection');
|
|
169
|
-
const action = normalizeMcpAction({
|
|
170
|
-
actorId: 'agent-1', taskId: 'task-1', server: 'github', tool: 'get_issue', args: {},
|
|
171
|
-
});
|
|
172
|
-
await expect(appendExecutionReceipt(cwd, {
|
|
173
|
-
action, mode: 'observe', decision: 'allow', reason: 'Projection optional',
|
|
174
|
-
policyHash: 'policy', outcome: 'forwarded',
|
|
175
|
-
}, root)).resolves.toMatchObject({ outcome: 'forwarded' });
|
|
176
|
-
expect(await verifyExecutionReceiptChain(cwd, root)).toMatchObject({ valid: true, count: 1 });
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
describe('receipt lock contention', () => {
|
|
180
|
-
it('retries while another append releases the lock, including Windows delete-pending errors', async () => {
|
|
181
|
-
const { isLockContention } = await import('./execution-receipt.js');
|
|
182
|
-
expect(isLockContention('EEXIST', 'linux')).toBe(true);
|
|
183
|
-
expect(isLockContention('EPERM', 'win32')).toBe(true);
|
|
184
|
-
expect(isLockContention('EBUSY', 'win32')).toBe(true);
|
|
185
|
-
expect(isLockContention('EPERM', 'linux')).toBe(false); // a real permission error elsewhere
|
|
186
|
-
expect(isLockContention('ENOENT', 'win32')).toBe(false);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|