@principles/pd-cli 1.118.0 → 1.120.0
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/commands/__tests__/legacy-cleanup.test.d.ts +18 -0
- package/dist/commands/__tests__/legacy-cleanup.test.d.ts.map +1 -0
- package/dist/commands/__tests__/legacy-cleanup.test.js +459 -0
- package/dist/commands/__tests__/legacy-cleanup.test.js.map +1 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts +21 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.d.ts.map +1 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js +179 -0
- package/dist/commands/__tests__/rulecode-flag-wiring.test.js.map +1 -0
- package/dist/commands/__tests__/rulecode-handler.test.d.ts +16 -0
- package/dist/commands/__tests__/rulecode-handler.test.d.ts.map +1 -0
- package/dist/commands/__tests__/rulecode-handler.test.js +285 -0
- package/dist/commands/__tests__/rulecode-handler.test.js.map +1 -0
- package/dist/commands/candidate.d.ts +1 -0
- package/dist/commands/candidate.d.ts.map +1 -1
- package/dist/commands/candidate.js +32 -6
- package/dist/commands/candidate.js.map +1 -1
- package/dist/commands/legacy-cleanup.d.ts +72 -6
- package/dist/commands/legacy-cleanup.d.ts.map +1 -1
- package/dist/commands/legacy-cleanup.js +243 -23
- package/dist/commands/legacy-cleanup.js.map +1 -1
- package/dist/commands/rulecode.d.ts +85 -0
- package/dist/commands/rulecode.d.ts.map +1 -0
- package/dist/commands/rulecode.js +356 -0
- package/dist/commands/rulecode.js.map +1 -0
- package/dist/commands/runtime-internalization-run-rulehost.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-run-rulehost.js +4 -7
- package/dist/commands/runtime-internalization-run-rulehost.js.map +1 -1
- package/dist/index.js +30 -9
- package/dist/index.js.map +1 -1
- package/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/dist/services/rulehost-pipeline-runner.js +31 -15
- package/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/package.json +1 -1
- package/scripts/llm-dogfood.ts +8 -12
- package/src/commands/__tests__/legacy-cleanup.test.ts +596 -0
- package/src/commands/__tests__/rulecode-flag-wiring.test.ts +230 -0
- package/src/commands/__tests__/rulecode-handler.test.ts +369 -0
- package/src/commands/candidate.ts +29 -7
- package/src/commands/legacy-cleanup.ts +335 -27
- package/src/commands/rulecode.ts +434 -0
- package/src/commands/runtime-internalization-run-rulehost.ts +3 -8
- package/src/index.ts +31 -9
- package/src/services/rulehost-pipeline-runner.ts +36 -18
- package/tests/commands/candidate-internalize-lineage.test.ts +44 -0
- package/tests/commands/cli-command-tree.test.ts +40 -0
- package/tests/commands/runtime.test.ts +9 -3
- package/tests/e2e/cross-package-acceptance.test.ts +1 -1
- package/tests/services/rulehost-pipeline-runner.test.ts +86 -2
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser-level tests for `pd rulecode spec|validate|replay` flags (PRI-439 Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* CLI gate rule 7: "Test the real command wiring — when behavior depends on
|
|
5
|
+
* Commander options, add a command-registration or parser test that exercises
|
|
6
|
+
* the actual flags."
|
|
7
|
+
*
|
|
8
|
+
* Tests the real `registerRulecodeCommand` helper (single source of truth
|
|
9
|
+
* shared with `index.ts`). Flag typos in production surface here at
|
|
10
|
+
* parseAsync time, not at handler dispatch.
|
|
11
|
+
*
|
|
12
|
+
* Covers:
|
|
13
|
+
* - `spec` subcommand: --json, --workspace/-w registered; no --code
|
|
14
|
+
* - `validate` subcommand: --code required, --code-file, --json, --workspace/-w
|
|
15
|
+
* - `replay` subcommand: --code required, --code-file, --golden-trace required,
|
|
16
|
+
* --json, --workspace/-w
|
|
17
|
+
* - --no-* negations are NOT registered (no accidental negation)
|
|
18
|
+
* - parseAsync actually dispatches the right opts to the handler
|
|
19
|
+
*/
|
|
20
|
+
import { describe, it, expect } from 'vitest';
|
|
21
|
+
import { Command } from 'commander';
|
|
22
|
+
import { registerRulecodeCommand } from '../rulecode.js';
|
|
23
|
+
function attachCapture(cmd, state) {
|
|
24
|
+
cmd.action(function captureAction(...args) {
|
|
25
|
+
let optsArg = null;
|
|
26
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
27
|
+
const arg = args[i];
|
|
28
|
+
if (arg !== null && typeof arg === 'object' && !(arg instanceof Command)) {
|
|
29
|
+
optsArg = arg;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (optsArg !== null && typeof optsArg === 'object') {
|
|
34
|
+
state.opts = optsArg;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
state.opts = {};
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function freshProgram() {
|
|
42
|
+
const program = new Command();
|
|
43
|
+
program.name('pd').exitOverride();
|
|
44
|
+
return program;
|
|
45
|
+
}
|
|
46
|
+
describe('pd rulecode — flag wiring (CLI gate rule 7)', () => {
|
|
47
|
+
// ── spec subcommand ───────────────────────────────────────────────────────
|
|
48
|
+
it('registers spec subcommand with --json and --workspace', () => {
|
|
49
|
+
const program = freshProgram();
|
|
50
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
51
|
+
const specCmd = rulecodeCmd.commands.find((c) => c.name() === 'spec');
|
|
52
|
+
expect(specCmd).toBeDefined();
|
|
53
|
+
const jsonOpt = specCmd.options.find((o) => o.long === '--json');
|
|
54
|
+
expect(jsonOpt).toBeDefined();
|
|
55
|
+
const wsOpt = specCmd.options.find((o) => o.long === '--workspace');
|
|
56
|
+
expect(wsOpt).toBeDefined();
|
|
57
|
+
expect(wsOpt?.short).toBe('-w');
|
|
58
|
+
});
|
|
59
|
+
it('spec subcommand does NOT register --code', () => {
|
|
60
|
+
const program = freshProgram();
|
|
61
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
62
|
+
const specCmd = rulecodeCmd.commands.find((c) => c.name() === 'spec');
|
|
63
|
+
const codeOpt = specCmd.options.find((o) => o.long === '--code');
|
|
64
|
+
expect(codeOpt).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
// ── validate subcommand ───────────────────────────────────────────────────
|
|
67
|
+
it('registers validate subcommand with --code, --code-file, --json, --workspace', () => {
|
|
68
|
+
const program = freshProgram();
|
|
69
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
70
|
+
const validateCmd = rulecodeCmd.commands.find((c) => c.name() === 'validate');
|
|
71
|
+
expect(validateCmd).toBeDefined();
|
|
72
|
+
const codeOpt = validateCmd.options.find((o) => o.long === '--code');
|
|
73
|
+
expect(codeOpt).toBeDefined();
|
|
74
|
+
const codeFileOpt = validateCmd.options.find((o) => o.long === '--code-file');
|
|
75
|
+
expect(codeFileOpt).toBeDefined();
|
|
76
|
+
const jsonOpt = validateCmd.options.find((o) => o.long === '--json');
|
|
77
|
+
expect(jsonOpt).toBeDefined();
|
|
78
|
+
const wsOpt = validateCmd.options.find((o) => o.long === '--workspace');
|
|
79
|
+
expect(wsOpt).toBeDefined();
|
|
80
|
+
expect(wsOpt?.short).toBe('-w');
|
|
81
|
+
});
|
|
82
|
+
it('validate --code is NOT required at parser level (can use --code-file instead)', async () => {
|
|
83
|
+
const program = freshProgram();
|
|
84
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
85
|
+
const validateCmd = rulecodeCmd.commands.find((c) => c.name() === 'validate');
|
|
86
|
+
const captured = { opts: null };
|
|
87
|
+
attachCapture(validateCmd, captured);
|
|
88
|
+
// parseAsync should NOT reject when --code is missing (handler validates)
|
|
89
|
+
await program.parseAsync(['node', 'pd', 'rulecode', 'validate', '--json']);
|
|
90
|
+
expect(captured.opts).not.toBeNull();
|
|
91
|
+
expect(captured.opts.code).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
// ── replay subcommand ─────────────────────────────────────────────────────
|
|
94
|
+
it('registers replay subcommand with --code, --code-file, --golden-trace, --json, --workspace', () => {
|
|
95
|
+
const program = freshProgram();
|
|
96
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
97
|
+
const replayCmd = rulecodeCmd.commands.find((c) => c.name() === 'replay');
|
|
98
|
+
expect(replayCmd).toBeDefined();
|
|
99
|
+
const codeOpt = replayCmd.options.find((o) => o.long === '--code');
|
|
100
|
+
expect(codeOpt).toBeDefined();
|
|
101
|
+
const codeFileOpt = replayCmd.options.find((o) => o.long === '--code-file');
|
|
102
|
+
expect(codeFileOpt).toBeDefined();
|
|
103
|
+
const gtOpt = replayCmd.options.find((o) => o.long === '--golden-trace');
|
|
104
|
+
expect(gtOpt).toBeDefined();
|
|
105
|
+
const jsonOpt = replayCmd.options.find((o) => o.long === '--json');
|
|
106
|
+
expect(jsonOpt).toBeDefined();
|
|
107
|
+
const wsOpt = replayCmd.options.find((o) => o.long === '--workspace');
|
|
108
|
+
expect(wsOpt).toBeDefined();
|
|
109
|
+
expect(wsOpt?.short).toBe('-w');
|
|
110
|
+
});
|
|
111
|
+
it('replay --golden-trace is required', () => {
|
|
112
|
+
const program = freshProgram();
|
|
113
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
114
|
+
const replayCmd = rulecodeCmd.commands.find((c) => c.name() === 'replay');
|
|
115
|
+
const gtOpt = replayCmd.options.find((o) => o.long === '--golden-trace');
|
|
116
|
+
expect(gtOpt?.required).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
// ── No accidental negations ───────────────────────────────────────────────
|
|
119
|
+
it('does NOT register --no-json on any subcommand', () => {
|
|
120
|
+
const program = freshProgram();
|
|
121
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
122
|
+
for (const sub of rulecodeCmd.commands) {
|
|
123
|
+
const noJson = sub.options.find((o) => o.long === '--no-json');
|
|
124
|
+
expect(noJson).toBeUndefined();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// ── Parser-level dispatch ─────────────────────────────────────────────────
|
|
128
|
+
it('parseAsync dispatches spec subcommand with json=true', async () => {
|
|
129
|
+
const program = freshProgram();
|
|
130
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
131
|
+
const specCmd = rulecodeCmd.commands.find((c) => c.name() === 'spec');
|
|
132
|
+
const captured = { opts: null };
|
|
133
|
+
attachCapture(specCmd, captured);
|
|
134
|
+
await program.parseAsync(['node', 'pd', 'rulecode', 'spec', '--json']);
|
|
135
|
+
expect(captured.opts).not.toBeNull();
|
|
136
|
+
expect(captured.opts.json).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
it('parseAsync dispatches validate with --code', async () => {
|
|
139
|
+
const program = freshProgram();
|
|
140
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
141
|
+
const validateCmd = rulecodeCmd.commands.find((c) => c.name() === 'validate');
|
|
142
|
+
const captured = { opts: null };
|
|
143
|
+
attachCapture(validateCmd, captured);
|
|
144
|
+
await program.parseAsync([
|
|
145
|
+
'node', 'pd', 'rulecode', 'validate',
|
|
146
|
+
'--code', 'function evaluate(input, helpers) { return { decision: "allow", matched: false, reason: "x" }; }',
|
|
147
|
+
'--json',
|
|
148
|
+
]);
|
|
149
|
+
expect(captured.opts).not.toBeNull();
|
|
150
|
+
expect(captured.opts.code).toContain('function evaluate');
|
|
151
|
+
expect(captured.opts.json).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
it('parseAsync dispatches replay with --code and --golden-trace', async () => {
|
|
154
|
+
const program = freshProgram();
|
|
155
|
+
const rulecodeCmd = registerRulecodeCommand(program);
|
|
156
|
+
const replayCmd = rulecodeCmd.commands.find((c) => c.name() === 'replay');
|
|
157
|
+
const captured = { opts: null };
|
|
158
|
+
attachCapture(replayCmd, captured);
|
|
159
|
+
await program.parseAsync([
|
|
160
|
+
'node', 'pd', 'rulecode', 'replay',
|
|
161
|
+
'--code', 'function evaluate(input, helpers) { return { decision: "allow", matched: false, reason: "x" }; }',
|
|
162
|
+
'--golden-trace', '/tmp/trace.json',
|
|
163
|
+
'--json',
|
|
164
|
+
]);
|
|
165
|
+
expect(captured.opts).not.toBeNull();
|
|
166
|
+
expect(captured.opts.code).toContain('function evaluate');
|
|
167
|
+
expect(captured.opts.goldenTrace).toBe('/tmp/trace.json');
|
|
168
|
+
expect(captured.opts.json).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
it('parseAsync rejects replay without --golden-trace (requiredOption)', async () => {
|
|
171
|
+
const program = freshProgram();
|
|
172
|
+
registerRulecodeCommand(program);
|
|
173
|
+
await expect(program.parseAsync([
|
|
174
|
+
'node', 'pd', 'rulecode', 'replay',
|
|
175
|
+
'--code', 'function evaluate() {}',
|
|
176
|
+
])).rejects.toThrow(/golden-trace/);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=rulecode-flag-wiring.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rulecode-flag-wiring.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/rulecode-flag-wiring.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler tests for `pd rulecode spec|validate|replay` (PRI-439 Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Tests the actual handler logic (not Commander parser wiring — that's in
|
|
5
|
+
* rulecode-flag-wiring.test.ts). Verifies:
|
|
6
|
+
* - spec returns the canonical RuleCode dialect spec text
|
|
7
|
+
* - validate detects forbidden patterns, missing return fields, matched=false
|
|
8
|
+
* - validate passes clean code
|
|
9
|
+
* - replay runs sandbox replay against a golden trace file
|
|
10
|
+
* - failure paths include structured reason + nextAction (CLI gate rule 6)
|
|
11
|
+
* - --json outputs exactly one parseable JSON object (CLI gate rule 1)
|
|
12
|
+
* - missing --code/--code-file fails loud with reason (ERR-009)
|
|
13
|
+
* - missing/malformed --golden-trace fails loud with reason (ERR-009)
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=rulecode-handler.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rulecode-handler.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/rulecode-handler.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler tests for `pd rulecode spec|validate|replay` (PRI-439 Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Tests the actual handler logic (not Commander parser wiring — that's in
|
|
5
|
+
* rulecode-flag-wiring.test.ts). Verifies:
|
|
6
|
+
* - spec returns the canonical RuleCode dialect spec text
|
|
7
|
+
* - validate detects forbidden patterns, missing return fields, matched=false
|
|
8
|
+
* - validate passes clean code
|
|
9
|
+
* - replay runs sandbox replay against a golden trace file
|
|
10
|
+
* - failure paths include structured reason + nextAction (CLI gate rule 6)
|
|
11
|
+
* - --json outputs exactly one parseable JSON object (CLI gate rule 1)
|
|
12
|
+
* - missing --code/--code-file fails loud with reason (ERR-009)
|
|
13
|
+
* - missing/malformed --golden-trace fails loud with reason (ERR-009)
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import * as os from 'node:os';
|
|
19
|
+
import { handleRulecodeSpec, handleRulecodeValidate, handleRulecodeReplay, } from '../rulecode.js';
|
|
20
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
21
|
+
async function runHandler(fn) {
|
|
22
|
+
const stdoutChunks = [];
|
|
23
|
+
const stderrChunks = [];
|
|
24
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation((...args) => {
|
|
25
|
+
stdoutChunks.push(args.map(String).join(' '));
|
|
26
|
+
});
|
|
27
|
+
const errorSpy = vi.spyOn(console, 'error').mockImplementation((...args) => {
|
|
28
|
+
stderrChunks.push(args.map(String).join(' '));
|
|
29
|
+
});
|
|
30
|
+
process.exitCode = undefined;
|
|
31
|
+
try {
|
|
32
|
+
await fn();
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
logSpy.mockRestore();
|
|
36
|
+
errorSpy.mockRestore();
|
|
37
|
+
}
|
|
38
|
+
const exitCode = process.exitCode;
|
|
39
|
+
process.exitCode = undefined;
|
|
40
|
+
return {
|
|
41
|
+
stdout: stdoutChunks.join(''),
|
|
42
|
+
stderr: stderrChunks.join(''),
|
|
43
|
+
exitCode,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function parseJson(stdout) {
|
|
47
|
+
return JSON.parse(stdout);
|
|
48
|
+
}
|
|
49
|
+
const CLEAN_CODE = `function evaluate(input, helpers) {
|
|
50
|
+
if (helpers.getToolName() === 'Bash') {
|
|
51
|
+
return { decision: "block", matched: true, reason: "bash commands blocked" };
|
|
52
|
+
}
|
|
53
|
+
return { decision: "allow", matched: false, reason: "non-bash allowed" };
|
|
54
|
+
}`;
|
|
55
|
+
const FORBIDDEN_CODE = `function evaluate(input, helpers) {
|
|
56
|
+
require('fs');
|
|
57
|
+
return { decision: "allow", matched: false, reason: "x" };
|
|
58
|
+
}`;
|
|
59
|
+
const MISSING_FIELDS_CODE = `function evaluate(input, helpers) {
|
|
60
|
+
if (helpers.isRiskPath()) {
|
|
61
|
+
return { matched: true };
|
|
62
|
+
}
|
|
63
|
+
return { decision: "allow", matched: false, reason: "safe" };
|
|
64
|
+
}`;
|
|
65
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
66
|
+
describe('pd rulecode spec', () => {
|
|
67
|
+
it('returns the canonical spec text as JSON', async () => {
|
|
68
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeSpec({ json: true }));
|
|
69
|
+
const output = parseJson(stdout);
|
|
70
|
+
expect(output.status).toBe('ok');
|
|
71
|
+
expect(output.spec).toContain('RuleCode Dialect Spec');
|
|
72
|
+
expect(output.spec).toContain('CANONICAL FORM');
|
|
73
|
+
expect(output.spec).toContain('FORBIDDEN PATTERNS');
|
|
74
|
+
expect(exitCode).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
it('outputs text when --json is false', async () => {
|
|
77
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeSpec({ json: false }));
|
|
78
|
+
expect(stdout).toContain('RuleCode Dialect Spec');
|
|
79
|
+
expect(stdout.startsWith('{')).toBe(false);
|
|
80
|
+
expect(exitCode).toBeUndefined();
|
|
81
|
+
});
|
|
82
|
+
it('does not set exit code on success', async () => {
|
|
83
|
+
const { exitCode } = await runHandler(() => handleRulecodeSpec({ json: true }));
|
|
84
|
+
expect(exitCode).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('pd rulecode validate', () => {
|
|
88
|
+
it('passes clean code with valid=true', async () => {
|
|
89
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeValidate({ code: CLEAN_CODE, json: true }));
|
|
90
|
+
const output = parseJson(stdout);
|
|
91
|
+
expect(output.status).toBe('ok');
|
|
92
|
+
expect(output.valid).toBe(true);
|
|
93
|
+
expect(output.violationCount).toBe(0);
|
|
94
|
+
expect(output.violations).toEqual([]);
|
|
95
|
+
expect(exitCode).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
it('detects forbidden patterns', async () => {
|
|
98
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeValidate({ code: FORBIDDEN_CODE, json: true }));
|
|
99
|
+
const output = parseJson(stdout);
|
|
100
|
+
expect(output.status).toBe('failed');
|
|
101
|
+
expect(output.valid).toBe(false);
|
|
102
|
+
expect(output.violationCount).toBeGreaterThan(0);
|
|
103
|
+
expect(output.violations.some((v) => v.includes('forbidden pattern'))).toBe(true);
|
|
104
|
+
expect(output.reason).toBeDefined();
|
|
105
|
+
expect(output.nextAction).toBeDefined();
|
|
106
|
+
expect(exitCode).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
it('detects missing return fields', async () => {
|
|
109
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeValidate({ code: MISSING_FIELDS_CODE, json: true }));
|
|
110
|
+
const output = parseJson(stdout);
|
|
111
|
+
expect(output.valid).toBe(false);
|
|
112
|
+
expect(output.violations.length).toBeGreaterThan(0);
|
|
113
|
+
expect(exitCode).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
it('fails loud when no --code or --code-file provided', async () => {
|
|
116
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeValidate({ json: true }));
|
|
117
|
+
const output = parseJson(stdout);
|
|
118
|
+
expect(output.status).toBe('failed');
|
|
119
|
+
expect(output.valid).toBe(false);
|
|
120
|
+
expect(output.reason).toContain('no code provided');
|
|
121
|
+
expect(output.nextAction).toContain('--code');
|
|
122
|
+
expect(exitCode).toBe(1);
|
|
123
|
+
});
|
|
124
|
+
it('reads code from --code-file', async () => {
|
|
125
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rulecode-test-'));
|
|
126
|
+
const codeFile = path.join(tmpDir, 'rule.js');
|
|
127
|
+
fs.writeFileSync(codeFile, CLEAN_CODE, 'utf8');
|
|
128
|
+
try {
|
|
129
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeValidate({ codeFile, json: true }));
|
|
130
|
+
const output = parseJson(stdout);
|
|
131
|
+
expect(output.valid).toBe(true);
|
|
132
|
+
expect(exitCode).toBeUndefined();
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
it('fails loud when --code-file does not exist', async () => {
|
|
139
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeValidate({ codeFile: '/nonexistent/path/rule.js', json: true }));
|
|
140
|
+
const output = parseJson(stdout);
|
|
141
|
+
expect(output.status).toBe('failed');
|
|
142
|
+
expect(output.reason).toContain('cannot read');
|
|
143
|
+
expect(output.nextAction).toBeDefined();
|
|
144
|
+
expect(exitCode).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('pd rulecode replay', () => {
|
|
148
|
+
const GOLDEN_TRACE_CASES = [
|
|
149
|
+
{
|
|
150
|
+
caseId: 'pos-1',
|
|
151
|
+
kind: 'positive',
|
|
152
|
+
toolName: 'Write',
|
|
153
|
+
params: { normalizedPath: 'src/safe.ts' },
|
|
154
|
+
expectedDecision: 'allow',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
caseId: 'neg-1',
|
|
158
|
+
kind: 'negative',
|
|
159
|
+
toolName: 'Bash',
|
|
160
|
+
params: { command: 'rm -rf /' },
|
|
161
|
+
expectedDecision: 'block',
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
function writeGoldenTraceFile(cases) {
|
|
165
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rulecode-replay-'));
|
|
166
|
+
const filePath = path.join(tmpDir, 'trace.json');
|
|
167
|
+
fs.writeFileSync(filePath, JSON.stringify(cases, null, 2), 'utf8');
|
|
168
|
+
return filePath;
|
|
169
|
+
}
|
|
170
|
+
it('passes replay with clean code and valid golden trace', async () => {
|
|
171
|
+
const traceFile = writeGoldenTraceFile(GOLDEN_TRACE_CASES);
|
|
172
|
+
try {
|
|
173
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }));
|
|
174
|
+
const output = parseJson(stdout);
|
|
175
|
+
expect(output.status).toBe('ok');
|
|
176
|
+
expect(output.decision).toBe('accepted_shadow');
|
|
177
|
+
expect(exitCode).toBeUndefined();
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
it('fails loud when --golden-trace file does not exist', async () => {
|
|
184
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({
|
|
185
|
+
code: CLEAN_CODE,
|
|
186
|
+
goldenTrace: '/nonexistent/trace.json',
|
|
187
|
+
json: true,
|
|
188
|
+
}));
|
|
189
|
+
const output = parseJson(stdout);
|
|
190
|
+
expect(output.status).toBe('failed');
|
|
191
|
+
expect(output.reason).toContain('cannot read');
|
|
192
|
+
expect(exitCode).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
it('fails loud when golden trace is not valid JSON', async () => {
|
|
195
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-rulecode-replay-'));
|
|
196
|
+
const traceFile = path.join(tmpDir, 'trace.json');
|
|
197
|
+
fs.writeFileSync(traceFile, '{ not valid json', 'utf8');
|
|
198
|
+
try {
|
|
199
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }));
|
|
200
|
+
const output = parseJson(stdout);
|
|
201
|
+
expect(output.status).toBe('failed');
|
|
202
|
+
expect(output.reason).toContain('not valid JSON');
|
|
203
|
+
expect(exitCode).toBe(1);
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
it('fails loud when golden trace is not an array', async () => {
|
|
210
|
+
const traceFile = writeGoldenTraceFile({ not: 'an array' });
|
|
211
|
+
try {
|
|
212
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }));
|
|
213
|
+
const output = parseJson(stdout);
|
|
214
|
+
expect(output.status).toBe('failed');
|
|
215
|
+
expect(output.reason).toContain('must contain a JSON array');
|
|
216
|
+
expect(exitCode).toBe(1);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
it('fails loud when golden trace has fewer than 2 cases', async () => {
|
|
223
|
+
const traceFile = writeGoldenTraceFile([GOLDEN_TRACE_CASES[0]]);
|
|
224
|
+
try {
|
|
225
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }));
|
|
226
|
+
const output = parseJson(stdout);
|
|
227
|
+
expect(output.status).toBe('failed');
|
|
228
|
+
expect(output.reason).toContain('at least 2 cases');
|
|
229
|
+
expect(exitCode).toBe(1);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
it('fails loud when a golden trace case is malformed', async () => {
|
|
236
|
+
const traceFile = writeGoldenTraceFile([
|
|
237
|
+
{ caseId: 'x' }, // missing required fields
|
|
238
|
+
GOLDEN_TRACE_CASES[1],
|
|
239
|
+
]);
|
|
240
|
+
try {
|
|
241
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ code: CLEAN_CODE, goldenTrace: traceFile, json: true }));
|
|
242
|
+
const output = parseJson(stdout);
|
|
243
|
+
expect(output.status).toBe('failed');
|
|
244
|
+
expect(output.reason).toContain('malformed');
|
|
245
|
+
expect(exitCode).toBe(1);
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
it('fails loud when no --code or --code-file provided', async () => {
|
|
252
|
+
const traceFile = writeGoldenTraceFile(GOLDEN_TRACE_CASES);
|
|
253
|
+
try {
|
|
254
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ goldenTrace: traceFile, json: true }));
|
|
255
|
+
const output = parseJson(stdout);
|
|
256
|
+
expect(output.status).toBe('failed');
|
|
257
|
+
expect(output.reason).toContain('no code provided');
|
|
258
|
+
expect(exitCode).toBe(1);
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
it('reports sandbox failures with structured reason + nextAction', async () => {
|
|
265
|
+
const traceFile = writeGoldenTraceFile(GOLDEN_TRACE_CASES);
|
|
266
|
+
// Code with forbidden pattern — sandbox will reject
|
|
267
|
+
const badCode = `function evaluate(input, helpers) {
|
|
268
|
+
eval('1');
|
|
269
|
+
return { decision: "allow", matched: false, reason: "x" };
|
|
270
|
+
}`;
|
|
271
|
+
try {
|
|
272
|
+
const { stdout, exitCode } = await runHandler(() => handleRulecodeReplay({ code: badCode, goldenTrace: traceFile, json: true }));
|
|
273
|
+
const output = parseJson(stdout);
|
|
274
|
+
expect(output.status).toBe('failed');
|
|
275
|
+
expect(output.decision).not.toBe('accepted_shadow');
|
|
276
|
+
expect(output.reason).toBeDefined();
|
|
277
|
+
expect(output.nextAction).toBeDefined();
|
|
278
|
+
expect(exitCode).toBe(1);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
fs.rmSync(path.dirname(traceFile), { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
//# sourceMappingURL=rulecode-handler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rulecode-handler.test.js","sourceRoot":"","sources":["../../../src/commands/__tests__/rulecode-handler.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -45,6 +45,7 @@ interface CandidateRepairOptions {
|
|
|
45
45
|
*/
|
|
46
46
|
export declare function resolveSourcePainIdFromDiagnostician(stateManager: RuntimeStateManager, candidate: {
|
|
47
47
|
taskId?: string;
|
|
48
|
+
sourceRunId?: string;
|
|
48
49
|
}): Promise<string | null>;
|
|
49
50
|
export declare function handleCandidateList(opts: CandidateListOptions): Promise<void>;
|
|
50
51
|
interface CandidateInternalizeOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"candidate.d.ts","sourceRoot":"","sources":["../../src/commands/candidate.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,mBAAmB,EAYpB,MAAM,6BAA6B,CAAC;AAOrC,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,oBAAoB;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,qBAAqB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAgED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,oCAAoC,CACxD,YAAY,EAAE,mBAAmB,EACjC,SAAS,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"candidate.d.ts","sourceRoot":"","sources":["../../src/commands/candidate.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,mBAAmB,EAYpB,MAAM,6BAA6B,CAAC;AAOrC,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,oBAAoB;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,qBAAqB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,sBAAsB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAgED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,oCAAoC,CACxD,YAAY,EAAE,mBAAmB,EACjC,SAAS,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgDxB;AAgDD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCnF;AAID,UAAU,2BAA2B;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAaD,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyJjG;AAID,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyCnF;AAID;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0GvF;AAID;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkErF;AAID;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEvF;AAID,UAAU,wBAAwB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAiFD,wBAAsB,sCAAsC,CAAC,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwP1G;AAID,UAAU,qBAAqB;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+CrF"}
|
|
@@ -66,13 +66,39 @@ async function ensureConsumedAt(stateManager, candidateId) {
|
|
|
66
66
|
* Callers at the production boundary must fail loud on `null`.
|
|
67
67
|
*/
|
|
68
68
|
export async function resolveSourcePainIdFromDiagnostician(stateManager, candidate) {
|
|
69
|
-
const
|
|
70
|
-
if (!
|
|
69
|
+
const candidateTaskId = candidate.taskId?.trim();
|
|
70
|
+
if (!candidateTaskId)
|
|
71
71
|
return null;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
72
|
+
let diagTask = await stateManager.getTask(candidateTaskId);
|
|
73
|
+
if (!diagTask)
|
|
74
|
+
return null;
|
|
75
|
+
// Production candidates are emitted by diag_router. Resolve its validated
|
|
76
|
+
// diagnosisId from the exact source run, then verify the referenced task is
|
|
77
|
+
// a diagnostician task before trusting its lineage.
|
|
78
|
+
if (diagTask.taskKind === 'diag_router') {
|
|
79
|
+
const sourceRunId = candidate.sourceRunId?.trim();
|
|
80
|
+
if (!sourceRunId)
|
|
81
|
+
return null;
|
|
82
|
+
const sourceRun = await stateManager.getRun(sourceRunId);
|
|
83
|
+
if (!sourceRun || sourceRun.taskId !== candidateTaskId || typeof sourceRun.outputPayload !== 'string')
|
|
84
|
+
return null;
|
|
85
|
+
let routerOutput;
|
|
86
|
+
try {
|
|
87
|
+
routerOutput = JSON.parse(sourceRun.outputPayload);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (routerOutput === null || typeof routerOutput !== 'object' || Array.isArray(routerOutput) ||
|
|
93
|
+
!Object.hasOwn(routerOutput, 'diagnosisId'))
|
|
94
|
+
return null;
|
|
95
|
+
const diagnosisId = Reflect.get(routerOutput, 'diagnosisId');
|
|
96
|
+
if (typeof diagnosisId !== 'string' || diagnosisId.trim() === '')
|
|
97
|
+
return null;
|
|
98
|
+
diagTask = await stateManager.getTask(diagnosisId.trim());
|
|
99
|
+
}
|
|
100
|
+
// Reject every non-canonical task kind to prevent cross-chain lineage
|
|
101
|
+
// contamination from arbitrary candidate.taskId values.
|
|
76
102
|
if (!diagTask || diagTask.taskKind !== 'diagnostician')
|
|
77
103
|
return null;
|
|
78
104
|
if (typeof diagTask.diagnosticJson !== 'string')
|