projscan 4.13.0 → 4.14.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/README.md +29 -13
- package/dist/cli/commands/prove.js +87 -6
- package/dist/cli/commands/prove.js.map +1 -1
- package/dist/core/intentRouterCatalog.js +34 -0
- package/dist/core/intentRouterCatalog.js.map +1 -1
- package/dist/core/intentRouterKeywordToolGuards.js +10 -0
- package/dist/core/intentRouterKeywordToolGuards.js.map +1 -1
- package/dist/core/intentRouterWorkflowKeywordWeights.js +29 -0
- package/dist/core/intentRouterWorkflowKeywordWeights.js.map +1 -1
- package/dist/core/proofLedger.d.ts +1 -0
- package/dist/core/proofLedger.js +14 -4
- package/dist/core/proofLedger.js.map +1 -1
- package/dist/core/prove.d.ts +2 -0
- package/dist/core/prove.js +306 -6
- package/dist/core/prove.js.map +1 -1
- package/dist/core/startFixedRouteCriteria.js +4 -0
- package/dist/core/startFixedRouteCriteria.js.map +1 -1
- package/dist/core/startRouteActions.js +5 -0
- package/dist/core/startRouteActions.js.map +1 -1
- package/dist/mcp/tools/prove.js +1 -1
- package/dist/mcp/tools/prove.js.map +1 -1
- package/dist/projscan-sbom.cdx.json +6 -6
- package/dist/tool-manifest.json +3 -3
- package/dist/types/proofLedger.d.ts +1 -1
- package/dist/types/prove.d.ts +17 -1
- package/docs/GUIDE.md +34 -15
- package/package.json +1 -1
|
@@ -93,6 +93,26 @@ const BUG_HUNT_WEIGHT_TWO_KEYWORDS = new Set([
|
|
|
93
93
|
'win',
|
|
94
94
|
'wins',
|
|
95
95
|
]);
|
|
96
|
+
const PROVE_WEIGHT_TWO_KEYWORDS = new Set([
|
|
97
|
+
'agent',
|
|
98
|
+
'allowed',
|
|
99
|
+
'allow',
|
|
100
|
+
'permission',
|
|
101
|
+
'permissions',
|
|
102
|
+
'proof',
|
|
103
|
+
'contract',
|
|
104
|
+
'contracts',
|
|
105
|
+
'scope',
|
|
106
|
+
'scoped',
|
|
107
|
+
'forbidden',
|
|
108
|
+
'receipt',
|
|
109
|
+
'receipts',
|
|
110
|
+
'replay',
|
|
111
|
+
'ledger',
|
|
112
|
+
'stale',
|
|
113
|
+
'fresh',
|
|
114
|
+
'bounded',
|
|
115
|
+
]);
|
|
96
116
|
export function workflowKeywordWeight(tool, keyword) {
|
|
97
117
|
if (tool === 'projscan_evidence_pack')
|
|
98
118
|
return evidencePackKeywordWeight(keyword);
|
|
@@ -100,6 +120,8 @@ export function workflowKeywordWeight(tool, keyword) {
|
|
|
100
120
|
return releaseTrainKeywordWeight(keyword);
|
|
101
121
|
if (tool === 'projscan_bug_hunt')
|
|
102
122
|
return bugHuntKeywordWeight(keyword);
|
|
123
|
+
if (tool === 'projscan_prove')
|
|
124
|
+
return proveKeywordWeight(keyword);
|
|
103
125
|
return undefined;
|
|
104
126
|
}
|
|
105
127
|
function evidencePackKeywordWeight(keyword) {
|
|
@@ -121,4 +143,11 @@ function bugHuntKeywordWeight(keyword) {
|
|
|
121
143
|
return 0.25;
|
|
122
144
|
return undefined;
|
|
123
145
|
}
|
|
146
|
+
function proveKeywordWeight(keyword) {
|
|
147
|
+
if (PROVE_WEIGHT_TWO_KEYWORDS.has(keyword))
|
|
148
|
+
return 2;
|
|
149
|
+
if (['change', 'changed', 'edit', 'edits', 'prove'].includes(keyword))
|
|
150
|
+
return 0.5;
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
124
153
|
//# sourceMappingURL=intentRouterWorkflowKeywordWeights.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intentRouterWorkflowKeywordWeights.js","sourceRoot":"","sources":["../../src/core/intentRouterWorkflowKeywordWeights.ts"],"names":[],"mappings":"AAAA,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEhF,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC;IAChD,UAAU;IACV,OAAO;IACP,UAAU;IACV,SAAS;IACT,SAAS;IACT,WAAW;IACX,SAAS;IACT,aAAa;IACb,OAAO;IACP,KAAK;IACL,WAAW;IACX,MAAM;IACN,MAAM;IACN,QAAQ;IACR,OAAO;IACP,UAAU;IACV,WAAW;IACX,SAAS;IACT,QAAQ;IACR,OAAO;IACP,KAAK;IACL,QAAQ;IACR,OAAO;IACP,SAAS;IACT,WAAW;IACX,OAAO;IACP,SAAS;IACT,YAAY;IACZ,MAAM;IACN,SAAS;IACT,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,MAAM;IACN,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,iCAAiC,GAAG,IAAI,GAAG,CAAC;IAChD,WAAW;IACX,QAAQ;IACR,WAAW;IACX,UAAU;IACV,YAAY;IACZ,OAAO;IACP,MAAM;IACN,SAAS;IACT,MAAM;IACN,SAAS;IACT,UAAU;IACV,SAAS;IACT,UAAU;IACV,YAAY;IACZ,aAAa;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,WAAW;IACX,MAAM;IACN,OAAO;IACP,OAAO;IACP,WAAW;IACX,SAAS;CACV,CAAC,CAAC;AAEH,MAAM,4BAA4B,GAAG,IAAI,GAAG,CAAC;IAC3C,KAAK;IACL,MAAM;IACN,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,SAAS;IACT,UAAU;IACV,OAAO;IACP,UAAU;IACV,OAAO;IACP,KAAK;IACL,QAAQ;IACR,SAAS;IACT,aAAa;IACb,QAAQ;IACR,MAAM;IACN,UAAU;IACV,SAAS;IACT,QAAQ;IACR,SAAS;IACT,MAAM;IACN,OAAO;IACP,MAAM;IACN,SAAS;IACT,OAAO;IACP,KAAK;IACL,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,OAAe;IACjE,IAAI,IAAI,KAAK,wBAAwB;QAAE,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACjF,IAAI,IAAI,KAAK,wBAAwB;QAAE,OAAO,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACjF,IAAI,IAAI,KAAK,mBAAmB;QAAE,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;IACvE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,yBAAyB,CAAC,OAAe;IAChD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,iCAAiC,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IAC7D,IAAI,iCAAiC,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IAC7D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,yBAAyB,CAAC,OAAe;IAChD,OAAO,iCAAiC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAe;IAC3C,IAAI,4BAA4B,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
1
|
+
{"version":3,"file":"intentRouterWorkflowKeywordWeights.js","sourceRoot":"","sources":["../../src/core/intentRouterWorkflowKeywordWeights.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ export declare const DEFAULT_PROOF_LEDGER_PATH = ".projscan/proof-ledger.jsonl";
|
|
|
3
3
|
export declare function normalizeProofCommand(command: string): string;
|
|
4
4
|
export declare function changedFileFingerprint(files: string[]): string;
|
|
5
5
|
export declare function redactProofSummary(value: string | undefined): string;
|
|
6
|
+
export declare function redactProofOutput(value: string): string;
|
|
6
7
|
export declare function appendProofLedgerRecord(rootPath: string, ledgerPath: string | undefined, input: ProofLedgerWriteInput): Promise<ProofLedgerRecord>;
|
|
7
8
|
export declare function readProofLedger(rootPath: string, ledgerPath: string | undefined): Promise<ProofLedgerRecord[]>;
|
|
8
9
|
export declare function latestProofRecordFor(records: ProofLedgerRecord[], command: string): ProofLedgerRecord | undefined;
|
package/dist/core/proofLedger.js
CHANGED
|
@@ -7,6 +7,7 @@ const REDACTION_PATTERNS = [
|
|
|
7
7
|
/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
8
8
|
/\b(?:sk|pk|whsec|ghp|gho|github_pat)_[A-Za-z0-9_=-]{8,}/gi,
|
|
9
9
|
/\b(password|passwd|pwd|token|secret|api[_-]?key)\s*[:=]\s*["']?[^"'\s,;]+/gi,
|
|
10
|
+
/\b(--?(?:password|passwd|pwd|token|secret|api[_-]?key))\s+[^"'\s,;]+/gi,
|
|
10
11
|
/\b[A-Za-z_][A-Za-z0-9_]*\.env\s*[:=]\s*[^"'\s,;]+/gi,
|
|
11
12
|
];
|
|
12
13
|
export function normalizeProofCommand(command) {
|
|
@@ -17,17 +18,21 @@ export function changedFileFingerprint(files) {
|
|
|
17
18
|
return crypto.createHash('sha256').update(normalized.join('\n')).digest('hex');
|
|
18
19
|
}
|
|
19
20
|
export function redactProofSummary(value) {
|
|
20
|
-
let summary = (value ?? '').replace(/\s+/g, ' ').trim();
|
|
21
|
+
let summary = redactProofOutput(value ?? '').replace(/\s+/g, ' ').trim();
|
|
21
22
|
if (summary.length === 0)
|
|
22
23
|
summary = 'No proof output summary supplied.';
|
|
23
|
-
for (const pattern of REDACTION_PATTERNS) {
|
|
24
|
-
summary = summary.replace(pattern, (match, label) => label ? `${label}=[redacted]` : '[redacted]');
|
|
25
|
-
}
|
|
26
24
|
if (summary.length > MAX_SUMMARY_LENGTH) {
|
|
27
25
|
return `${summary.slice(0, MAX_SUMMARY_LENGTH - 1)}...`;
|
|
28
26
|
}
|
|
29
27
|
return summary;
|
|
30
28
|
}
|
|
29
|
+
export function redactProofOutput(value) {
|
|
30
|
+
let output = value;
|
|
31
|
+
for (const pattern of REDACTION_PATTERNS) {
|
|
32
|
+
output = output.replace(pattern, redactionReplacement);
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
31
36
|
export async function appendProofLedgerRecord(rootPath, ledgerPath, input) {
|
|
32
37
|
const completedAt = input.completedAt ?? new Date().toISOString();
|
|
33
38
|
const durationMs = Math.max(0, Math.round(input.durationMs));
|
|
@@ -125,6 +130,11 @@ function isProofLedgerRecord(value) {
|
|
|
125
130
|
function normalizePath(value) {
|
|
126
131
|
return value.split(path.sep).join('/').replace(/^\.\//, '');
|
|
127
132
|
}
|
|
133
|
+
function redactionReplacement(_match, ...args) {
|
|
134
|
+
const captures = args.slice(0, -2);
|
|
135
|
+
const label = captures.find((value) => typeof value === 'string' && value.length > 0);
|
|
136
|
+
return label ? `${label}=[redacted]` : '[redacted]';
|
|
137
|
+
}
|
|
128
138
|
function isNodeErrorCode(error, code) {
|
|
129
139
|
return (typeof error === 'object' &&
|
|
130
140
|
error !== null &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proofLedger.js","sourceRoot":"","sources":["../../src/core/proofLedger.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,CAAC,MAAM,yBAAyB,GAAG,8BAA8B,CAAC;AAExE,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAC/B,MAAM,kBAAkB,GAAG;IACzB,kCAAkC;IAClC,2DAA2D;IAC3D,6EAA6E;IAC7E,qDAAqD;CACtD,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAe;IACpD,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAyB;IAC1D,IAAI,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"proofLedger.js","sourceRoot":"","sources":["../../src/core/proofLedger.ts"],"names":[],"mappings":"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"}
|
package/dist/core/prove.d.ts
CHANGED
|
@@ -14,5 +14,7 @@ export interface ComputeProveOptions {
|
|
|
14
14
|
durationMs?: number;
|
|
15
15
|
summary?: string;
|
|
16
16
|
logPath?: string;
|
|
17
|
+
runCommand?: string[];
|
|
18
|
+
runTimeoutMs?: number;
|
|
17
19
|
}
|
|
18
20
|
export declare function computeProve(rootPath: string, options?: ComputeProveOptions): Promise<ProveReport>;
|
package/dist/core/prove.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
1
3
|
import fs from 'node:fs/promises';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { readFeedbackFile } from './feedback.js';
|
|
4
|
-
import { appendProofLedgerRecord, changedFileFingerprint, latestProofRecordFor, readProofLedger, } from './proofLedger.js';
|
|
6
|
+
import { appendProofLedgerRecord, changedFileFingerprint, latestProofRecordFor, readProofLedger, redactProofOutput, } from './proofLedger.js';
|
|
5
7
|
import { quoteShellArg } from './startShellArgs.js';
|
|
6
8
|
import { computeSimulation } from './simulate.js';
|
|
7
9
|
import { getChangedFiles } from '../utils/changedFiles.js';
|
|
8
10
|
const DEFAULT_CONTRACT_PATH = '.projscan/proof-contract.json';
|
|
11
|
+
const DEFAULT_RUN_TIMEOUT_MS = 10 * 60 * 1000;
|
|
12
|
+
const PROOF_RUN_TIMEOUT_EXIT_CODE = 124;
|
|
13
|
+
const COMMAND_NOT_FOUND_EXIT_CODE = 127;
|
|
14
|
+
const MAX_PROOF_RUN_OUTPUT_CHARS = 256 * 1024;
|
|
15
|
+
const MAX_PROOF_RUN_LOG_CHARS = 512 * 1024;
|
|
9
16
|
const GENERATED_FORBIDDEN_PATTERNS = [
|
|
10
17
|
'.agentflight/**',
|
|
11
18
|
'.agentloop/**',
|
|
@@ -85,16 +92,48 @@ const CONFIG_BASENAMES = new Set([
|
|
|
85
92
|
]);
|
|
86
93
|
const CONFIG_SUFFIXES = ['.config.js', '.config.cjs', '.config.mjs', '.config.ts'];
|
|
87
94
|
export async function computeProve(rootPath, options = {}) {
|
|
88
|
-
const modeCount = [
|
|
95
|
+
const modeCount = [
|
|
96
|
+
Boolean(options.intent?.trim()),
|
|
97
|
+
Boolean(options.changed),
|
|
98
|
+
Boolean(options.recordCommand?.trim()),
|
|
99
|
+
options.runCommand !== undefined,
|
|
100
|
+
].filter(Boolean).length;
|
|
89
101
|
if (modeCount > 1) {
|
|
90
|
-
throw new Error('prove accepts only one of --intent, --changed,
|
|
102
|
+
throw new Error('prove accepts only one of --intent, --changed, --record-command, or --run');
|
|
91
103
|
}
|
|
104
|
+
if (options.runCommand !== undefined)
|
|
105
|
+
return computeRunProof(rootPath, options);
|
|
92
106
|
if (options.recordCommand?.trim())
|
|
93
107
|
return computeRecordProof(rootPath, options);
|
|
94
108
|
if (options.changed)
|
|
95
109
|
return computeChangedProof(rootPath, options);
|
|
96
110
|
return computeIntentProof(rootPath, options);
|
|
97
111
|
}
|
|
112
|
+
async function computeRunProof(rootPath, options) {
|
|
113
|
+
const run = await executeProofCommand(rootPath, options.runCommand ?? [], options.runTimeoutMs);
|
|
114
|
+
const changedFiles = await getChangedFiles(rootPath, options.baseRef);
|
|
115
|
+
const record = await appendProofLedgerRecord(rootPath, options.ledgerPath, {
|
|
116
|
+
command: run.command,
|
|
117
|
+
exitCode: run.exitCode,
|
|
118
|
+
durationMs: run.durationMs,
|
|
119
|
+
changedFiles: proofRelevantChangedFiles(changedFiles.files),
|
|
120
|
+
outputSummary: run.outputSummary,
|
|
121
|
+
logPath: run.logPath,
|
|
122
|
+
source: 'prove-run',
|
|
123
|
+
});
|
|
124
|
+
const verdict = record.status === 'passed' ? 'ready' : 'blocked';
|
|
125
|
+
const verifiedWorkflow = verifiedWorkflowForRecord(verdict, record.status);
|
|
126
|
+
return {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
mode: 'run',
|
|
129
|
+
verdict,
|
|
130
|
+
summary: `${verdict}: executed ${record.status} proof for ${record.command}`,
|
|
131
|
+
commands: [record.command],
|
|
132
|
+
warnings: changedFiles.available ? [] : [changedFiles.reason ?? 'Changed-file evidence is unavailable.'],
|
|
133
|
+
verifiedWorkflow,
|
|
134
|
+
ledgerRecord: record,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
98
137
|
async function computeRecordProof(rootPath, options) {
|
|
99
138
|
const proof = recordProofInput(options);
|
|
100
139
|
const changedFiles = await getChangedFiles(rootPath, options.baseRef);
|
|
@@ -108,6 +147,7 @@ async function computeRecordProof(rootPath, options) {
|
|
|
108
147
|
source: 'prove-record',
|
|
109
148
|
});
|
|
110
149
|
const verdict = record.status === 'passed' ? 'ready' : 'blocked';
|
|
150
|
+
const verifiedWorkflow = verifiedWorkflowForRecord(verdict, record.status);
|
|
111
151
|
return {
|
|
112
152
|
schemaVersion: 1,
|
|
113
153
|
mode: 'record',
|
|
@@ -115,6 +155,7 @@ async function computeRecordProof(rootPath, options) {
|
|
|
115
155
|
summary: `${verdict}: recorded ${record.status} proof for ${record.command}`,
|
|
116
156
|
commands: [record.command],
|
|
117
157
|
warnings: changedFiles.available ? [] : [changedFiles.reason ?? 'Changed-file evidence is unavailable.'],
|
|
158
|
+
verifiedWorkflow,
|
|
118
159
|
ledgerRecord: record,
|
|
119
160
|
};
|
|
120
161
|
}
|
|
@@ -136,6 +177,167 @@ function recordProofInput(options) {
|
|
|
136
177
|
logPath: options.logPath,
|
|
137
178
|
};
|
|
138
179
|
}
|
|
180
|
+
async function executeProofCommand(rootPath, command, timeoutMs) {
|
|
181
|
+
const commandVector = normalizeRunCommand(command);
|
|
182
|
+
const displayCommand = redactProofOutput(commandVector.map(quoteShellArg).join(' '));
|
|
183
|
+
const startedAtMs = Date.now();
|
|
184
|
+
const effectiveTimeoutMs = resolveRunTimeoutMs(timeoutMs);
|
|
185
|
+
const result = await spawnProofCommand(rootPath, commandVector, effectiveTimeoutMs);
|
|
186
|
+
const durationMs = Date.now() - startedAtMs;
|
|
187
|
+
const outputSummary = proofRunOutputSummary(result, effectiveTimeoutMs);
|
|
188
|
+
const logPath = await writeProofRunLog(rootPath, {
|
|
189
|
+
command: displayCommand,
|
|
190
|
+
exitCode: result.exitCode,
|
|
191
|
+
durationMs,
|
|
192
|
+
stdout: result.stdout,
|
|
193
|
+
stderr: result.stderr,
|
|
194
|
+
errorMessage: result.errorMessage,
|
|
195
|
+
timedOut: result.timedOut,
|
|
196
|
+
truncated: result.truncated,
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
command: displayCommand,
|
|
200
|
+
exitCode: result.exitCode,
|
|
201
|
+
durationMs,
|
|
202
|
+
outputSummary,
|
|
203
|
+
logPath,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function normalizeRunCommand(command) {
|
|
207
|
+
const normalized = command.map((part) => String(part));
|
|
208
|
+
if (normalized.length === 0 || normalized[0]?.trim().length === 0) {
|
|
209
|
+
throw new Error('prove --run requires a command after --, for example: projscan prove --run -- npm test');
|
|
210
|
+
}
|
|
211
|
+
return normalized;
|
|
212
|
+
}
|
|
213
|
+
function resolveRunTimeoutMs(value) {
|
|
214
|
+
if (value === undefined)
|
|
215
|
+
return DEFAULT_RUN_TIMEOUT_MS;
|
|
216
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
217
|
+
throw new Error('prove --run-timeout-ms requires a positive number');
|
|
218
|
+
}
|
|
219
|
+
return Math.round(value);
|
|
220
|
+
}
|
|
221
|
+
function spawnProofCommand(rootPath, command, timeoutMs) {
|
|
222
|
+
return new Promise((resolve) => {
|
|
223
|
+
const [executable, ...args] = command;
|
|
224
|
+
let stdout = '';
|
|
225
|
+
let stderr = '';
|
|
226
|
+
let truncated = false;
|
|
227
|
+
let timedOut = false;
|
|
228
|
+
let finished = false;
|
|
229
|
+
let killTimer;
|
|
230
|
+
const timeout = setTimeout(() => {
|
|
231
|
+
timedOut = true;
|
|
232
|
+
child.kill('SIGTERM');
|
|
233
|
+
killTimer = setTimeout(() => child.kill('SIGKILL'), 1_000);
|
|
234
|
+
killTimer.unref();
|
|
235
|
+
}, timeoutMs);
|
|
236
|
+
timeout.unref();
|
|
237
|
+
const child = spawn(executable, args, {
|
|
238
|
+
cwd: rootPath,
|
|
239
|
+
env: process.env,
|
|
240
|
+
shell: false,
|
|
241
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
242
|
+
});
|
|
243
|
+
const finish = (exitCode, errorMessage) => {
|
|
244
|
+
if (finished)
|
|
245
|
+
return;
|
|
246
|
+
finished = true;
|
|
247
|
+
clearTimeout(timeout);
|
|
248
|
+
if (killTimer)
|
|
249
|
+
clearTimeout(killTimer);
|
|
250
|
+
resolve({
|
|
251
|
+
exitCode: timedOut ? PROOF_RUN_TIMEOUT_EXIT_CODE : exitCode,
|
|
252
|
+
stdout,
|
|
253
|
+
stderr,
|
|
254
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
255
|
+
timedOut,
|
|
256
|
+
truncated,
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
child.stdout?.on('data', (chunk) => {
|
|
260
|
+
const next = appendBoundedOutput(stdout, chunk);
|
|
261
|
+
stdout = next.value;
|
|
262
|
+
truncated ||= next.truncated;
|
|
263
|
+
});
|
|
264
|
+
child.stderr?.on('data', (chunk) => {
|
|
265
|
+
const next = appendBoundedOutput(stderr, chunk);
|
|
266
|
+
stderr = next.value;
|
|
267
|
+
truncated ||= next.truncated;
|
|
268
|
+
});
|
|
269
|
+
child.on('error', (error) => {
|
|
270
|
+
finish(COMMAND_NOT_FOUND_EXIT_CODE, error instanceof Error ? error.message : String(error));
|
|
271
|
+
});
|
|
272
|
+
child.on('close', (code, signal) => {
|
|
273
|
+
if (code === null) {
|
|
274
|
+
finish(signal ? 1 : 0);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
finish(code);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function appendBoundedOutput(current, chunk) {
|
|
282
|
+
const text = chunk.toString('utf-8');
|
|
283
|
+
const remaining = MAX_PROOF_RUN_OUTPUT_CHARS - current.length;
|
|
284
|
+
if (remaining <= 0)
|
|
285
|
+
return { value: current, truncated: text.length > 0 };
|
|
286
|
+
if (text.length > remaining) {
|
|
287
|
+
return { value: current + text.slice(0, remaining), truncated: true };
|
|
288
|
+
}
|
|
289
|
+
return { value: current + text, truncated: false };
|
|
290
|
+
}
|
|
291
|
+
function proofRunOutputSummary(result, timeoutMs) {
|
|
292
|
+
const parts = [
|
|
293
|
+
result.timedOut ? `timed out after ${timeoutMs}ms` : undefined,
|
|
294
|
+
result.errorMessage ? `start error: ${result.errorMessage}` : undefined,
|
|
295
|
+
result.stdout.trim() ? `stdout: ${result.stdout.trim()}` : undefined,
|
|
296
|
+
result.stderr.trim() ? `stderr: ${result.stderr.trim()}` : undefined,
|
|
297
|
+
result.truncated ? 'output truncated' : undefined,
|
|
298
|
+
].filter((part) => Boolean(part));
|
|
299
|
+
return parts.join(' | ');
|
|
300
|
+
}
|
|
301
|
+
async function writeProofRunLog(rootPath, input) {
|
|
302
|
+
const relativePath = proofRunLogPath(input.command);
|
|
303
|
+
const fullPath = path.resolve(rootPath, relativePath);
|
|
304
|
+
const root = path.resolve(rootPath);
|
|
305
|
+
const relativeToRoot = path.relative(root, fullPath);
|
|
306
|
+
if (!relativeToRoot || relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
|
|
307
|
+
throw new Error('Proof log path must stay inside the project root.');
|
|
308
|
+
}
|
|
309
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
310
|
+
await fs.writeFile(fullPath, redactedProofRunLog(input), 'utf-8');
|
|
311
|
+
return relativePath;
|
|
312
|
+
}
|
|
313
|
+
function proofRunLogPath(command) {
|
|
314
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
315
|
+
const digest = crypto.createHash('sha256').update(command).digest('hex').slice(0, 10);
|
|
316
|
+
return `.projscan/proof-logs/prove-run-${stamp}-${digest}.log`;
|
|
317
|
+
}
|
|
318
|
+
function redactedProofRunLog(input) {
|
|
319
|
+
const raw = [
|
|
320
|
+
`command: ${input.command}`,
|
|
321
|
+
`exitCode: ${input.exitCode}`,
|
|
322
|
+
`durationMs: ${input.durationMs}`,
|
|
323
|
+
`timedOut: ${input.timedOut ? 'yes' : 'no'}`,
|
|
324
|
+
`truncated: ${input.truncated ? 'yes' : 'no'}`,
|
|
325
|
+
input.errorMessage ? `error: ${input.errorMessage}` : undefined,
|
|
326
|
+
'--- stdout ---',
|
|
327
|
+
input.stdout || '(empty)',
|
|
328
|
+
'--- stderr ---',
|
|
329
|
+
input.stderr || '(empty)',
|
|
330
|
+
]
|
|
331
|
+
.filter((line) => typeof line === 'string')
|
|
332
|
+
.join('\n');
|
|
333
|
+
const redacted = redactProofOutput(raw);
|
|
334
|
+
return `${truncateText(redacted, MAX_PROOF_RUN_LOG_CHARS)}\n`;
|
|
335
|
+
}
|
|
336
|
+
function truncateText(value, maxLength) {
|
|
337
|
+
if (value.length <= maxLength)
|
|
338
|
+
return value;
|
|
339
|
+
return `${value.slice(0, maxLength - 24)}\n[projscan log truncated]\n`;
|
|
340
|
+
}
|
|
139
341
|
function isNonNegativeFiniteNumber(value) {
|
|
140
342
|
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
141
343
|
}
|
|
@@ -163,6 +365,7 @@ async function computeIntentProof(rootPath, options) {
|
|
|
163
365
|
contract,
|
|
164
366
|
commands: contract.proofCommands,
|
|
165
367
|
warnings: simulation.warnings,
|
|
368
|
+
verifiedWorkflow: contract.verifiedWorkflow,
|
|
166
369
|
...(savedContractPath ? { savedContractPath } : {}),
|
|
167
370
|
};
|
|
168
371
|
}
|
|
@@ -193,6 +396,7 @@ async function computeChangedProof(rootPath, options) {
|
|
|
193
396
|
receipt,
|
|
194
397
|
commands: receipt.proofStatus.commandsRequired,
|
|
195
398
|
warnings: receipt.evidenceGaps,
|
|
399
|
+
verifiedWorkflow: receipt.verifiedWorkflow,
|
|
196
400
|
};
|
|
197
401
|
}
|
|
198
402
|
function quickProofPreflight(changedFiles) {
|
|
@@ -225,7 +429,7 @@ function buildContract(input) {
|
|
|
225
429
|
...(input.trustMemory?.gaps ?? []),
|
|
226
430
|
]);
|
|
227
431
|
const confidence = confidenceForTrustMemory(input.simulation.confidence, input.trustMemory);
|
|
228
|
-
|
|
432
|
+
const contract = {
|
|
229
433
|
schemaVersion: 1,
|
|
230
434
|
id: `proof-contract-${slug(input.intent)}`,
|
|
231
435
|
intent: input.intent,
|
|
@@ -255,6 +459,10 @@ function buildContract(input) {
|
|
|
255
459
|
receiptCommand: `projscan prove --changed --contract ${quoteShellArg(DEFAULT_CONTRACT_PATH)} --format markdown`,
|
|
256
460
|
riskDelta: input.simulation.riskDelta,
|
|
257
461
|
};
|
|
462
|
+
return {
|
|
463
|
+
...contract,
|
|
464
|
+
verifiedWorkflow: verifiedWorkflowForContract(contract),
|
|
465
|
+
};
|
|
258
466
|
}
|
|
259
467
|
function contractProofCommands(simulationCommands) {
|
|
260
468
|
return unique([
|
|
@@ -286,7 +494,7 @@ function buildReceipt(input) {
|
|
|
286
494
|
scope,
|
|
287
495
|
preflightVerdict: input.preflightVerdict,
|
|
288
496
|
});
|
|
289
|
-
|
|
497
|
+
const receipt = {
|
|
290
498
|
summary: summaryForReceipt(commitReadiness, scope),
|
|
291
499
|
commitReadiness,
|
|
292
500
|
scope,
|
|
@@ -298,6 +506,95 @@ function buildReceipt(input) {
|
|
|
298
506
|
evidenceGaps,
|
|
299
507
|
reviewerGuidance: reviewerGuidanceFor(commitReadiness, scope, reviewerDecision, proofStatus.status),
|
|
300
508
|
};
|
|
509
|
+
return {
|
|
510
|
+
...receipt,
|
|
511
|
+
verifiedWorkflow: verifiedWorkflowForReceipt(receipt),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function verifiedWorkflowForContract(contract) {
|
|
515
|
+
return {
|
|
516
|
+
phase: 'contract',
|
|
517
|
+
status: intentVerdict(contract),
|
|
518
|
+
nextAction: 'save the Proof Contract, make the bounded edit, then record proof commands',
|
|
519
|
+
nextCommand: contract.receiptCommand,
|
|
520
|
+
staleProof: false,
|
|
521
|
+
missingProof: contract.proofCommands.length > 0,
|
|
522
|
+
failedProof: false,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function verifiedWorkflowForRecord(verdict, recordStatus) {
|
|
526
|
+
const failedProof = recordStatus === 'failed';
|
|
527
|
+
return {
|
|
528
|
+
phase: 'record',
|
|
529
|
+
status: verdict,
|
|
530
|
+
nextAction: failedProof
|
|
531
|
+
? 'fix the failed proof command, record it again, then replay changed proof'
|
|
532
|
+
: 'run projscan prove --changed to replay the ledger against the current diff',
|
|
533
|
+
nextCommand: 'projscan prove --changed --format markdown',
|
|
534
|
+
staleProof: false,
|
|
535
|
+
missingProof: false,
|
|
536
|
+
failedProof,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function verifiedWorkflowForReceipt(receipt) {
|
|
540
|
+
const proofStatus = receipt.proofStatus.status;
|
|
541
|
+
const staleProof = proofStatus === 'stale' || receipt.proofStatus.staleCommands.length > 0;
|
|
542
|
+
const missingProof = proofStatus === 'missing' ||
|
|
543
|
+
proofStatus === 'partial' ||
|
|
544
|
+
receipt.proofStatus.missingCommands.length > 0;
|
|
545
|
+
const failedProof = proofStatus === 'failed' || receipt.proofStatus.failedCommands.length > 0;
|
|
546
|
+
return {
|
|
547
|
+
phase: 'receipt',
|
|
548
|
+
status: receipt.commitReadiness,
|
|
549
|
+
nextAction: nextActionForReceipt({
|
|
550
|
+
receipt,
|
|
551
|
+
staleProof,
|
|
552
|
+
missingProof,
|
|
553
|
+
failedProof,
|
|
554
|
+
}),
|
|
555
|
+
nextCommand: nextCommandForReceipt({
|
|
556
|
+
receipt,
|
|
557
|
+
staleProof,
|
|
558
|
+
missingProof,
|
|
559
|
+
failedProof,
|
|
560
|
+
}),
|
|
561
|
+
reviewerDecision: receipt.reviewerDecision,
|
|
562
|
+
scopeStatus: receipt.scope.status,
|
|
563
|
+
proofStatus,
|
|
564
|
+
riskDeltaDirection: receipt.riskDeltaDirection,
|
|
565
|
+
staleProof,
|
|
566
|
+
missingProof,
|
|
567
|
+
failedProof,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function nextActionForReceipt(input) {
|
|
571
|
+
if (input.failedProof)
|
|
572
|
+
return 'fix failed proof commands before review';
|
|
573
|
+
if (input.staleProof)
|
|
574
|
+
return 'rerun stale proof commands before review';
|
|
575
|
+
if (input.missingProof)
|
|
576
|
+
return 'record missing proof commands before review';
|
|
577
|
+
if (input.receipt.scope.status === 'drifted') {
|
|
578
|
+
return 'resolve scope drift or update the Proof Contract before review';
|
|
579
|
+
}
|
|
580
|
+
if (input.receipt.reviewerDecision === 'safe-to-review') {
|
|
581
|
+
return 'share the Proof Receipt with the reviewer';
|
|
582
|
+
}
|
|
583
|
+
return 'review focused scope and proof gaps before approval';
|
|
584
|
+
}
|
|
585
|
+
function nextCommandForReceipt(input) {
|
|
586
|
+
if (input.failedProof) {
|
|
587
|
+
return `projscan prove --record-command ${quoteShellArg(input.receipt.proofStatus.failedCommands[0] ?? '<command>')} --exit-code 0 --duration-ms <ms>`;
|
|
588
|
+
}
|
|
589
|
+
if (input.staleProof) {
|
|
590
|
+
return `projscan prove --record-command ${quoteShellArg(input.receipt.proofStatus.staleCommands[0] ?? '<command>')} --exit-code 0 --duration-ms <ms>`;
|
|
591
|
+
}
|
|
592
|
+
if (input.missingProof) {
|
|
593
|
+
return 'projscan prove --record-command "<command>" --exit-code 0 --duration-ms <ms>';
|
|
594
|
+
}
|
|
595
|
+
if (input.receipt.scope.status === 'drifted')
|
|
596
|
+
return 'projscan prove --changed --format markdown';
|
|
597
|
+
return 'projscan evidence-pack --pr-comment';
|
|
301
598
|
}
|
|
302
599
|
function proofStatusFor(proofCommands, ledger, changedFiles) {
|
|
303
600
|
const relevantChangedFiles = proofRelevantChangedFiles(changedFiles);
|
|
@@ -401,7 +698,7 @@ function scopeFor(contract, contractPath, changedFiles) {
|
|
|
401
698
|
]);
|
|
402
699
|
const forbiddenTouched = changedFiles.filter((file) => contract.forbiddenFiles.some((pattern) => pathMatches(file, pattern)));
|
|
403
700
|
const allowedTouched = changedFiles.filter((file) => allowed.has(file));
|
|
404
|
-
const outsideAllowed = changedFiles.filter((file) => !allowed.has(file));
|
|
701
|
+
const outsideAllowed = changedFiles.filter((file) => !allowed.has(file) && !isLocalProofArtifactPath(file));
|
|
405
702
|
const classifications = changedFiles.map((file) => classifyChangedFile({
|
|
406
703
|
file,
|
|
407
704
|
forbidden: forbiddenTouched.includes(file),
|
|
@@ -772,6 +1069,9 @@ function isGeneratedPath(file) {
|
|
|
772
1069
|
file.startsWith('coverage/') ||
|
|
773
1070
|
file.startsWith('dist/'));
|
|
774
1071
|
}
|
|
1072
|
+
function isLocalProofArtifactPath(file) {
|
|
1073
|
+
return file.startsWith('.projscan/');
|
|
1074
|
+
}
|
|
775
1075
|
function isSecuritySensitivePath(file) {
|
|
776
1076
|
return (file === '.env' ||
|
|
777
1077
|
file.startsWith('.env.') ||
|