rlhf-feedback-loop 0.5.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/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/adapters/README.md +8 -0
- package/adapters/amp/skills/rlhf-feedback/SKILL.md +20 -0
- package/adapters/chatgpt/INSTALL.md +80 -0
- package/adapters/chatgpt/openapi.yaml +292 -0
- package/adapters/claude/.mcp.json +8 -0
- package/adapters/codex/config.toml +4 -0
- package/adapters/gemini/function-declarations.json +95 -0
- package/adapters/mcp/server-stdio.js +444 -0
- package/bin/cli.js +167 -0
- package/config/mcp-allowlists.json +29 -0
- package/config/policy-bundles/constrained-v1.json +53 -0
- package/config/policy-bundles/default-v1.json +80 -0
- package/config/rubrics/default-v1.json +52 -0
- package/config/subagent-profiles.json +32 -0
- package/openapi/openapi.yaml +292 -0
- package/package.json +91 -0
- package/plugins/amp-skill/INSTALL.md +52 -0
- package/plugins/amp-skill/SKILL.md +31 -0
- package/plugins/claude-skill/INSTALL.md +55 -0
- package/plugins/claude-skill/SKILL.md +46 -0
- package/plugins/codex-profile/AGENTS.md +20 -0
- package/plugins/codex-profile/INSTALL.md +57 -0
- package/plugins/gemini-extension/INSTALL.md +74 -0
- package/plugins/gemini-extension/gemini_prompt.txt +10 -0
- package/plugins/gemini-extension/tool_contract.json +28 -0
- package/scripts/billing.js +471 -0
- package/scripts/budget-guard.js +173 -0
- package/scripts/code-reasoning.js +307 -0
- package/scripts/context-engine.js +547 -0
- package/scripts/contextfs.js +513 -0
- package/scripts/contract-audit.js +198 -0
- package/scripts/dpo-optimizer.js +208 -0
- package/scripts/export-dpo-pairs.js +316 -0
- package/scripts/export-training.js +448 -0
- package/scripts/feedback-attribution.js +313 -0
- package/scripts/feedback-inbox-read.js +162 -0
- package/scripts/feedback-loop.js +838 -0
- package/scripts/feedback-schema.js +300 -0
- package/scripts/feedback-to-memory.js +165 -0
- package/scripts/feedback-to-rules.js +109 -0
- package/scripts/generate-paperbanana-diagrams.sh +99 -0
- package/scripts/hybrid-feedback-context.js +676 -0
- package/scripts/intent-router.js +164 -0
- package/scripts/mcp-policy.js +92 -0
- package/scripts/meta-policy.js +194 -0
- package/scripts/plan-gate.js +154 -0
- package/scripts/prove-adapters.js +364 -0
- package/scripts/prove-attribution.js +364 -0
- package/scripts/prove-automation.js +393 -0
- package/scripts/prove-data-quality.js +219 -0
- package/scripts/prove-intelligence.js +256 -0
- package/scripts/prove-lancedb.js +370 -0
- package/scripts/prove-loop-closure.js +255 -0
- package/scripts/prove-rlaif.js +404 -0
- package/scripts/prove-subway-upgrades.js +250 -0
- package/scripts/prove-training-export.js +324 -0
- package/scripts/prove-v2-milestone.js +273 -0
- package/scripts/prove-v3-milestone.js +381 -0
- package/scripts/rlaif-self-audit.js +123 -0
- package/scripts/rubric-engine.js +230 -0
- package/scripts/self-heal.js +127 -0
- package/scripts/self-healing-check.js +111 -0
- package/scripts/skill-quality-tracker.js +284 -0
- package/scripts/subagent-profiles.js +79 -0
- package/scripts/sync-gh-secrets-from-env.sh +29 -0
- package/scripts/thompson-sampling.js +331 -0
- package/scripts/train_from_feedback.py +914 -0
- package/scripts/validate-feedback.js +580 -0
- package/scripts/vector-store.js +100 -0
- package/src/api/server.js +497 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
6
|
+
const FEEDBACK_DIR = process.env.RLHF_FEEDBACK_DIR || path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
|
|
7
|
+
const LEDGER_PATH = path.join(FEEDBACK_DIR, 'budget-ledger.json');
|
|
8
|
+
const LOCK_PATH = `${LEDGER_PATH}.lock`;
|
|
9
|
+
|
|
10
|
+
function parseMonthlyBudget(rawValue) {
|
|
11
|
+
const parsed = Number(rawValue);
|
|
12
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
13
|
+
throw new Error(`Invalid RLHF_MONTHLY_BUDGET_USD value: '${rawValue}'`);
|
|
14
|
+
}
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getMonthlyBudget() {
|
|
19
|
+
const rawValue = process.env.RLHF_MONTHLY_BUDGET_USD || '10';
|
|
20
|
+
return parseMonthlyBudget(rawValue);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function currentMonthKey() {
|
|
24
|
+
const now = new Date();
|
|
25
|
+
return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadLedger() {
|
|
29
|
+
if (!fs.existsSync(LEDGER_PATH)) return { months: {} };
|
|
30
|
+
return JSON.parse(fs.readFileSync(LEDGER_PATH, 'utf-8'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function saveLedger(ledger) {
|
|
34
|
+
fs.mkdirSync(path.dirname(LEDGER_PATH), { recursive: true });
|
|
35
|
+
fs.writeFileSync(LEDGER_PATH, `${JSON.stringify(ledger, null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function blockMs(ms) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
while (Date.now() - start < ms) {
|
|
41
|
+
// Intentional synchronous short wait while lock clears.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function acquireLock({ timeoutMs = 5000, staleMs = 15000 } = {}) {
|
|
46
|
+
const startedAt = Date.now();
|
|
47
|
+
fs.mkdirSync(path.dirname(LOCK_PATH), { recursive: true });
|
|
48
|
+
|
|
49
|
+
while (true) {
|
|
50
|
+
try {
|
|
51
|
+
return fs.openSync(LOCK_PATH, 'wx');
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err.code !== 'EEXIST') throw err;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const stat = fs.statSync(LOCK_PATH);
|
|
57
|
+
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
58
|
+
fs.rmSync(LOCK_PATH, { force: true });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// lock disappeared between retries
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
66
|
+
throw new Error('Could not acquire budget ledger lock');
|
|
67
|
+
}
|
|
68
|
+
blockMs(20);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function releaseLock(lockFd) {
|
|
74
|
+
try {
|
|
75
|
+
fs.closeSync(lockFd);
|
|
76
|
+
} finally {
|
|
77
|
+
fs.rmSync(LOCK_PATH, { force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function addSpend({ amountUsd, source, note }) {
|
|
82
|
+
if (!Number.isFinite(amountUsd) || amountUsd < 0) {
|
|
83
|
+
throw new Error('amountUsd must be a non-negative number');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const budgetUsd = getMonthlyBudget();
|
|
87
|
+
const lockFd = acquireLock();
|
|
88
|
+
try {
|
|
89
|
+
const ledger = loadLedger();
|
|
90
|
+
const month = currentMonthKey();
|
|
91
|
+
if (!ledger.months[month]) {
|
|
92
|
+
ledger.months[month] = {
|
|
93
|
+
totalUsd: 0,
|
|
94
|
+
entries: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const nextTotal = ledger.months[month].totalUsd + amountUsd;
|
|
99
|
+
if (nextTotal > budgetUsd) {
|
|
100
|
+
throw new Error(`Budget exceeded: ${nextTotal.toFixed(2)} > ${budgetUsd.toFixed(2)} USD/month`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ledger.months[month].totalUsd = nextTotal;
|
|
104
|
+
ledger.months[month].entries.push({
|
|
105
|
+
ts: new Date().toISOString(),
|
|
106
|
+
source: source || 'unknown',
|
|
107
|
+
note: note || '',
|
|
108
|
+
amountUsd,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
saveLedger(ledger);
|
|
112
|
+
return {
|
|
113
|
+
month,
|
|
114
|
+
totalUsd: ledger.months[month].totalUsd,
|
|
115
|
+
budgetUsd,
|
|
116
|
+
};
|
|
117
|
+
} finally {
|
|
118
|
+
releaseLock(lockFd);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getBudgetStatus() {
|
|
123
|
+
const budgetUsd = getMonthlyBudget();
|
|
124
|
+
const ledger = loadLedger();
|
|
125
|
+
const month = currentMonthKey();
|
|
126
|
+
const total = ledger.months[month] ? ledger.months[month].totalUsd : 0;
|
|
127
|
+
return {
|
|
128
|
+
month,
|
|
129
|
+
totalUsd: total,
|
|
130
|
+
budgetUsd,
|
|
131
|
+
remainingUsd: Math.max(0, budgetUsd - total),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function runCli() {
|
|
136
|
+
const args = process.argv.slice(2);
|
|
137
|
+
if (args.includes('--status')) {
|
|
138
|
+
console.log(JSON.stringify(getBudgetStatus(), null, 2));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const addArg = args.find((a) => a.startsWith('--add='));
|
|
143
|
+
if (!addArg) {
|
|
144
|
+
console.log('Usage: node scripts/budget-guard.js --status');
|
|
145
|
+
console.log('Usage: node scripts/budget-guard.js --add=0.15 --source=paperbanana --note="diagram generation"');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const amountUsd = Number(addArg.replace('--add=', ''));
|
|
150
|
+
const sourceArg = args.find((a) => a.startsWith('--source='));
|
|
151
|
+
const noteArg = args.find((a) => a.startsWith('--note='));
|
|
152
|
+
|
|
153
|
+
const result = addSpend({
|
|
154
|
+
amountUsd,
|
|
155
|
+
source: sourceArg ? sourceArg.replace('--source=', '') : 'unknown',
|
|
156
|
+
note: noteArg ? noteArg.replace('--note=', '') : '',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
console.log(JSON.stringify(result, null, 2));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
addSpend,
|
|
164
|
+
getBudgetStatus,
|
|
165
|
+
getMonthlyBudget,
|
|
166
|
+
parseMonthlyBudget,
|
|
167
|
+
LEDGER_PATH,
|
|
168
|
+
LOCK_PATH,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (require.main === module) {
|
|
172
|
+
runCli();
|
|
173
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agentic Code Reasoning — Structured Trace Engine
|
|
4
|
+
*
|
|
5
|
+
* Based on Meta's "Agentic Code Reasoning" paper (arxiv 2603.01896).
|
|
6
|
+
* Forces structured line-level reasoning instead of pattern-matching guesses.
|
|
7
|
+
*
|
|
8
|
+
* Produces a verification trace for every code change claim, self-heal fix,
|
|
9
|
+
* or DPO pair, requiring explicit evidence for each assertion.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const crypto = require('node:crypto');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} TraceStep
|
|
16
|
+
* @property {string} location - File path and line range (e.g. "scripts/self-heal.js:49-69")
|
|
17
|
+
* @property {string} claim - What this step asserts about correctness
|
|
18
|
+
* @property {string} evidence - Concrete evidence supporting the claim
|
|
19
|
+
* @property {'verified'|'unverified'|'refuted'} verdict - Assessment of the claim
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} ReasoningTrace
|
|
24
|
+
* @property {string} traceId - Unique identifier for this trace
|
|
25
|
+
* @property {string} timestamp - ISO 8601 timestamp
|
|
26
|
+
* @property {string} type - Trace type: 'self-heal' | 'dpo-pair' | 'proof-gate' | 'verification'
|
|
27
|
+
* @property {string} subject - What is being verified (script name, pair ID, etc.)
|
|
28
|
+
* @property {TraceStep[]} steps - Ordered reasoning steps
|
|
29
|
+
* @property {string[]} edgeCases - Edge cases explicitly addressed or ruled out
|
|
30
|
+
* @property {Object} summary - Aggregated verdict
|
|
31
|
+
* @property {number} summary.totalSteps - Total reasoning steps
|
|
32
|
+
* @property {number} summary.verified - Steps with verified verdict
|
|
33
|
+
* @property {number} summary.unverified - Steps with unverified verdict
|
|
34
|
+
* @property {number} summary.refuted - Steps with refuted verdict
|
|
35
|
+
* @property {number} summary.confidence - Ratio of verified to total (0-1)
|
|
36
|
+
* @property {boolean} summary.passed - True if confidence >= threshold and refuted === 0
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const DEFAULT_CONFIDENCE_THRESHOLD = 0.7;
|
|
40
|
+
|
|
41
|
+
function generateTraceId() {
|
|
42
|
+
return `trace-${crypto.randomBytes(6).toString('hex')}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createTrace(type, subject) {
|
|
46
|
+
return {
|
|
47
|
+
traceId: generateTraceId(),
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
type,
|
|
50
|
+
subject,
|
|
51
|
+
steps: [],
|
|
52
|
+
edgeCases: [],
|
|
53
|
+
summary: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function addStep(trace, { location, claim, evidence, verdict = 'unverified' }) {
|
|
58
|
+
if (!location || !claim) {
|
|
59
|
+
throw new Error('TraceStep requires location and claim');
|
|
60
|
+
}
|
|
61
|
+
const validVerdicts = ['verified', 'unverified', 'refuted'];
|
|
62
|
+
if (!validVerdicts.includes(verdict)) {
|
|
63
|
+
throw new Error(`Invalid verdict: ${verdict}. Must be one of: ${validVerdicts.join(', ')}`);
|
|
64
|
+
}
|
|
65
|
+
trace.steps.push({ location, claim, evidence: evidence || '', verdict });
|
|
66
|
+
return trace;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function addEdgeCase(trace, description) {
|
|
70
|
+
if (description) trace.edgeCases.push(description);
|
|
71
|
+
return trace;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function finalizeTrace(trace, { confidenceThreshold = DEFAULT_CONFIDENCE_THRESHOLD } = {}) {
|
|
75
|
+
const totalSteps = trace.steps.length;
|
|
76
|
+
const verified = trace.steps.filter((s) => s.verdict === 'verified').length;
|
|
77
|
+
const unverified = trace.steps.filter((s) => s.verdict === 'unverified').length;
|
|
78
|
+
const refuted = trace.steps.filter((s) => s.verdict === 'refuted').length;
|
|
79
|
+
const confidence = totalSteps > 0 ? Math.round((verified / totalSteps) * 1000) / 1000 : 0;
|
|
80
|
+
|
|
81
|
+
trace.summary = {
|
|
82
|
+
totalSteps,
|
|
83
|
+
verified,
|
|
84
|
+
unverified,
|
|
85
|
+
refuted,
|
|
86
|
+
confidence,
|
|
87
|
+
passed: confidence >= confidenceThreshold && refuted === 0,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return trace;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build a reasoning trace for a self-heal fix execution.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} fixResult - Result from runFixPlan for a single script
|
|
97
|
+
* @param {string} fixResult.script - Script name
|
|
98
|
+
* @param {string} fixResult.status - 'success' | 'failed'
|
|
99
|
+
* @param {number} fixResult.exitCode
|
|
100
|
+
* @param {string} fixResult.outputTail - Last 2000 chars of output
|
|
101
|
+
* @param {string[]} changedFiles - Files changed by this fix
|
|
102
|
+
* @returns {ReasoningTrace}
|
|
103
|
+
*/
|
|
104
|
+
function traceForSelfHealFix(fixResult, changedFiles = []) {
|
|
105
|
+
const trace = createTrace('self-heal', fixResult.script);
|
|
106
|
+
|
|
107
|
+
addStep(trace, {
|
|
108
|
+
location: `npm run ${fixResult.script}`,
|
|
109
|
+
claim: `Fix script "${fixResult.script}" executes without error`,
|
|
110
|
+
evidence: fixResult.status === 'success'
|
|
111
|
+
? `Exit code ${fixResult.exitCode}, completed in ${fixResult.durationMs}ms`
|
|
112
|
+
: `Exit code ${fixResult.exitCode}, error: ${fixResult.error || 'non-zero exit'}`,
|
|
113
|
+
verdict: fixResult.status === 'success' ? 'verified' : 'refuted',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (changedFiles.length > 0) {
|
|
117
|
+
addStep(trace, {
|
|
118
|
+
location: changedFiles.join(', '),
|
|
119
|
+
claim: `Fix modified ${changedFiles.length} file(s) — changes are intentional`,
|
|
120
|
+
evidence: `Changed: ${changedFiles.join(', ')}`,
|
|
121
|
+
verdict: 'verified',
|
|
122
|
+
});
|
|
123
|
+
} else {
|
|
124
|
+
addStep(trace, {
|
|
125
|
+
location: `npm run ${fixResult.script}`,
|
|
126
|
+
claim: 'Fix produced no file changes (idempotent or no-op)',
|
|
127
|
+
evidence: 'git diff --name-only returned empty after execution',
|
|
128
|
+
verdict: 'verified',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const outputTail = (fixResult.outputTail || '').toLowerCase();
|
|
133
|
+
const hasErrors = /error|fail|exception|fatal/i.test(outputTail);
|
|
134
|
+
addStep(trace, {
|
|
135
|
+
location: `npm run ${fixResult.script} (output)`,
|
|
136
|
+
claim: 'Script output contains no error indicators',
|
|
137
|
+
evidence: hasErrors
|
|
138
|
+
? `Output contains error keywords: ${outputTail.slice(-200)}`
|
|
139
|
+
: 'No error keywords in output tail',
|
|
140
|
+
verdict: hasErrors && fixResult.status === 'success' ? 'unverified' : (hasErrors ? 'refuted' : 'verified'),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
addEdgeCase(trace, 'Script timeout not triggered (completed within deadline)');
|
|
144
|
+
if (changedFiles.length === 0) {
|
|
145
|
+
addEdgeCase(trace, 'No files changed — fix may already be applied or script is no-op');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return finalizeTrace(trace);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build a reasoning trace for a DPO preference pair.
|
|
153
|
+
*
|
|
154
|
+
* @param {Object} pair - The DPO pair with prompt, chosen, rejected, metadata
|
|
155
|
+
* @returns {ReasoningTrace}
|
|
156
|
+
*/
|
|
157
|
+
function traceForDpoPair(pair) {
|
|
158
|
+
const trace = createTrace('dpo-pair', `${pair.metadata.errorId}->${pair.metadata.learningId}`);
|
|
159
|
+
|
|
160
|
+
addStep(trace, {
|
|
161
|
+
location: `error:${pair.metadata.errorId}`,
|
|
162
|
+
claim: 'Rejected response represents a genuine mistake',
|
|
163
|
+
evidence: `Error title: "${pair.metadata.errorTitle}"`,
|
|
164
|
+
verdict: pair.metadata.errorTitle ? 'verified' : 'unverified',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
addStep(trace, {
|
|
168
|
+
location: `learning:${pair.metadata.learningId}`,
|
|
169
|
+
claim: 'Chosen response represents a correct approach',
|
|
170
|
+
evidence: `Learning title: "${pair.metadata.learningTitle}"`,
|
|
171
|
+
verdict: pair.metadata.learningTitle ? 'verified' : 'unverified',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const matchedKeys = pair.metadata.matchedKeys || [];
|
|
175
|
+
addStep(trace, {
|
|
176
|
+
location: 'domain-matching',
|
|
177
|
+
claim: `Error and learning share domain context (${matchedKeys.length} key(s))`,
|
|
178
|
+
evidence: `Matched keys: [${matchedKeys.join(', ')}], overlap score: ${pair.metadata.overlapScore}`,
|
|
179
|
+
verdict: matchedKeys.length > 0 ? 'verified' : 'refuted',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const rubric = pair.metadata.rubric;
|
|
183
|
+
if (rubric) {
|
|
184
|
+
const hasDelta = rubric.weightedDelta != null && rubric.weightedDelta > 0;
|
|
185
|
+
addStep(trace, {
|
|
186
|
+
location: 'rubric-delta',
|
|
187
|
+
claim: 'Learning scores higher than error on rubric (positive delta)',
|
|
188
|
+
evidence: `Learning: ${rubric.learningWeightedScore}, Error: ${rubric.errorWeightedScore}, Delta: ${rubric.weightedDelta}`,
|
|
189
|
+
verdict: hasDelta ? 'verified' : (rubric.weightedDelta === 0 ? 'unverified' : 'refuted'),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const failingCriteria = rubric.errorFailingCriteria || rubric.failingCriteria || [];
|
|
193
|
+
if (failingCriteria.length > 0) {
|
|
194
|
+
addStep(trace, {
|
|
195
|
+
location: 'rubric-failures',
|
|
196
|
+
claim: `Error had ${failingCriteria.length} failing rubric criteria`,
|
|
197
|
+
evidence: `Failing: [${failingCriteria.join(', ')}]`,
|
|
198
|
+
verdict: 'verified',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
addStep(trace, {
|
|
203
|
+
location: 'rubric-delta',
|
|
204
|
+
claim: 'Rubric scores provide quantitative quality signal',
|
|
205
|
+
evidence: 'No rubric data available for this pair',
|
|
206
|
+
verdict: 'unverified',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
addStep(trace, {
|
|
211
|
+
location: 'prompt-inference',
|
|
212
|
+
claim: 'Inferred prompt captures the shared scenario correctly',
|
|
213
|
+
evidence: `Prompt: "${pair.prompt}"`,
|
|
214
|
+
verdict: pair.prompt && pair.prompt.length > 10 ? 'verified' : 'unverified',
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
addEdgeCase(trace, 'Pair may lack temporal proximity — error and learning from different sessions');
|
|
218
|
+
addEdgeCase(trace, 'Domain overlap is keyword-based — semantic similarity not verified');
|
|
219
|
+
|
|
220
|
+
return finalizeTrace(trace);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Build a reasoning trace for a proof harness check.
|
|
225
|
+
*
|
|
226
|
+
* @param {Object} checkResult - A check from the proof report
|
|
227
|
+
* @param {string} checkResult.name - Check name
|
|
228
|
+
* @param {boolean} checkResult.passed - Whether the check passed
|
|
229
|
+
* @param {Object} checkResult.details - Check-specific details
|
|
230
|
+
* @returns {ReasoningTrace}
|
|
231
|
+
*/
|
|
232
|
+
function traceForProofCheck(checkResult) {
|
|
233
|
+
const trace = createTrace('proof-gate', checkResult.name);
|
|
234
|
+
|
|
235
|
+
addStep(trace, {
|
|
236
|
+
location: `check:${checkResult.name}`,
|
|
237
|
+
claim: `Proof check "${checkResult.name}" passes`,
|
|
238
|
+
evidence: checkResult.passed
|
|
239
|
+
? `Passed with details: ${JSON.stringify(checkResult.details)}`
|
|
240
|
+
: `Failed: ${JSON.stringify(checkResult.details)}`,
|
|
241
|
+
verdict: checkResult.passed ? 'verified' : 'refuted',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (checkResult.details) {
|
|
245
|
+
const details = checkResult.details;
|
|
246
|
+
if (details.status !== undefined) {
|
|
247
|
+
addStep(trace, {
|
|
248
|
+
location: `check:${checkResult.name}/status`,
|
|
249
|
+
claim: 'HTTP/response status is expected value',
|
|
250
|
+
evidence: `Status: ${details.status}`,
|
|
251
|
+
verdict: checkResult.passed ? 'verified' : 'refuted',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (details.accepted !== undefined) {
|
|
255
|
+
addStep(trace, {
|
|
256
|
+
location: `check:${checkResult.name}/accepted`,
|
|
257
|
+
claim: `Acceptance state is ${details.accepted}`,
|
|
258
|
+
evidence: `accepted=${details.accepted}`,
|
|
259
|
+
verdict: 'verified',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return finalizeTrace(trace);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Aggregate multiple traces into a verification summary.
|
|
269
|
+
*
|
|
270
|
+
* @param {ReasoningTrace[]} traces
|
|
271
|
+
* @returns {Object} Aggregated summary
|
|
272
|
+
*/
|
|
273
|
+
function aggregateTraces(traces) {
|
|
274
|
+
const totalTraces = traces.length;
|
|
275
|
+
const passedTraces = traces.filter((t) => t.summary && t.summary.passed).length;
|
|
276
|
+
const allSteps = traces.flatMap((t) => t.steps);
|
|
277
|
+
const totalSteps = allSteps.length;
|
|
278
|
+
const verified = allSteps.filter((s) => s.verdict === 'verified').length;
|
|
279
|
+
const refuted = allSteps.filter((s) => s.verdict === 'refuted').length;
|
|
280
|
+
const avgConfidence = totalTraces > 0
|
|
281
|
+
? Math.round(traces.reduce((sum, t) => sum + (t.summary ? t.summary.confidence : 0), 0) / totalTraces * 1000) / 1000
|
|
282
|
+
: 0;
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
totalTraces,
|
|
286
|
+
passedTraces,
|
|
287
|
+
failedTraces: totalTraces - passedTraces,
|
|
288
|
+
totalSteps,
|
|
289
|
+
verified,
|
|
290
|
+
unverified: totalSteps - verified - refuted,
|
|
291
|
+
refuted,
|
|
292
|
+
averageConfidence: avgConfidence,
|
|
293
|
+
allPassed: passedTraces === totalTraces,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
module.exports = {
|
|
298
|
+
createTrace,
|
|
299
|
+
addStep,
|
|
300
|
+
addEdgeCase,
|
|
301
|
+
finalizeTrace,
|
|
302
|
+
traceForSelfHealFix,
|
|
303
|
+
traceForDpoPair,
|
|
304
|
+
traceForProofCheck,
|
|
305
|
+
aggregateTraces,
|
|
306
|
+
DEFAULT_CONFIDENCE_THRESHOLD,
|
|
307
|
+
};
|