fix T1+T4: wire rollback into pipeline; real Ollama telemetry in metrics

T1 (H7): casan-step.mjs now calls rollback-manager.sh checkpoint before
overwriting plan.md at attempt-2, writes tx-id to plan.checkpoint.txid
sidecar, and executes rollback on REJECTED verdict. rollback-transactions.jsonl
records a real cp restore command. Adversarial test: checkpoint exists,
real cp command recorded, plan hash matches pre-overwrite content.

T4 (H6): casan-harness.sh exports CASAN_STEP_NAME=$ACTION_NAME before
agent-metrics.sh so nested model calls (model-call.py) and the provider-
cost-lookup.py query share the same step label. metrics.jsonl now writes
cost_source=provider_telemetry instead of word_count_estimate when a real
Ollama call is made within the same step. Adversarial test: verified with
CASAN_STEP_NAME=t4-telemetry-test end-to-end.

adversarial-harness-tests.sh: 40 → 44 PASS / 0 FAIL (+3 T1, +1 T4)
security-gate.sh: PASS=10 FAIL=0 SKIP=0 (verified, local ornith:9b)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
thanhnv
2026-07-01 11:25:50 +09:00
co-authored by Claude Sonnet 4.6
parent f74a5b6e42
commit a8edbea534
18 changed files with 284 additions and 159 deletions
+40 -1
View File
@@ -35,6 +35,23 @@ function judgeArtifact(filePath, criteria) {
} catch { return { verdict: 'SKIP', note: 'parse_error' }; }
}
// T1: Real rollback wiring — checkpoint before overwrite, restore on REJECTED verdict
function checkpointArtifact(filePath) {
try { readFileSync(filePath, 'utf8'); } catch { return null; } // file absent → nothing to checkpoint
const r = spawnSync('bash', [join(SCRIPTS_DIR, 'rollback-manager.sh'), 'checkpoint', filePath],
{ encoding: 'utf8', timeout: 10000 });
if (r.status !== 0) return null;
const m = (r.stdout || '').match(/transaction_id=(\S+)/);
return m ? m[1] : null;
}
function executeRollback(txId) {
if (!txId) return false;
const r = spawnSync('bash', [join(SCRIPTS_DIR, 'rollback-manager.sh'), 'execute', txId],
{ encoding: 'utf8', timeout: 10000 });
return r.status === 0;
}
const step = process.argv[2];
const attempt = process.argv[3] ?? '1';
const outputPath = process.env.CASAN_OUTPUT;
@@ -95,6 +112,9 @@ let finalVerdict = 'APPROVED';
let finalArtifacts = [];
let finalIssues = [];
// Sidecar stores the checkpoint tx-id between step 05-plan and step 06-reviewplan
const CHECKPOINT_SIDECAR = `docs/output/specs/${featureId}/plan.checkpoint.txid`;
const backendFiles = [
'backend/src/auth/auth.service.ts',
'backend/src/objectives/objectives.service.ts',
@@ -143,6 +163,13 @@ switch (step) {
}
case '05-plan': {
const incomplete = attempt === '1';
// T1: checkpoint existing plan before overwriting so rollback-manager.sh execute can restore it
if (attempt !== '1') {
const txId = checkpointArtifact(planPath);
if (txId) {
try { writeFileSync(CHECKPOINT_SIDECAR, txId, 'utf8'); } catch {}
}
}
write(planPath, `# Implementation Plan: OKR Web App\n\n## Stack\nNestJS, Prisma Client, SQLite, React, Vite, Tailwind, Zod, TanStack Query.\n\n## Backend Modules\n- auth: JWT login and cookie issuance.\n- users: admin/manager user list.\n- objectives: role-filtered list, detail, create.\n- key-results: detail, create, progress update.\n\n## Tests\n- Backend service tests.\n- Backend HTTP e2e tests.\n${incomplete ? '- TODO: define golden regression and rollback strategy.\n' : '- Golden regression test compares seeded manager objectives with backend/test/golden/objectives.manager.json.\n- Rollback strategy restores changed artifacts from backups through rollback-manager.sh.\n'}\n## Build\nRun npm test and npm run build for backend and frontend.\n`);
if (!incomplete) {
write(dataModelPath, '# Data Model\n\nUser 1:N Objective. Objective 1:N KeyResult. KeyResult 1:N ProgressUpdate. Role/status are SQLite strings constrained in service/types.\n');
@@ -173,10 +200,22 @@ switch (step) {
: { verdict: 'SKIP', note: 'rule_already_rejected' };
if (judgeResult.verdict === 'REJECTED') verdict = 'REJECTED';
const judgeNote = `model-judge: ${judgeResult.verdict} (${judgeResult.note})`;
// T1: on REJECTED, execute rollback to restore the previously checkpointed plan
let rollbackNote = '';
if (verdict === 'REJECTED') {
let txId = null;
try { txId = readFileSync(CHECKPOINT_SIDECAR, 'utf8').trim(); } catch {}
if (txId) {
const restored = executeRollback(txId);
rollbackNote = restored
? ` Rollback executed: plan restored to pre-overwrite state (tx=${txId}).`
: ` Rollback attempted but failed (tx=${txId}).`;
}
}
report(
`docs/output/output_logs/${featureId}/reports/06-review-plan-report-attempt-${attempt}.md`,
`## Plan Conformance Review Report — Attempt ${attempt}`,
`Criteria checked against plan.md and required companion artifacts. ${judgeNote}. Verdict is ${verdict}.`,
`Criteria checked against plan.md and required companion artifacts. ${judgeNote}. Verdict is ${verdict}.${rollbackNote}`,
verdict,
[planPath, dataModelPath, researchPath, quickstartPath, contractPath],
issues,