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:
co-authored by
Claude Sonnet 4.6
parent
f74a5b6e42
commit
a8edbea534
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user