Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"demo:setup": "node scripts/demo-setup.js",
"demo": "node examples/awf-demo/src/run-demo.js",
"demo:cross-runtime": "cat examples/cross-runtime/terminal-demo.md",
"authorize:blocked": "node tools/authorize-task/authorize-task.js --task-class db_migration --risk-lane high --runtime codex --workspace awf-demo",
"authorize:authorized": "node tools/authorize-task/authorize-task.js --task-class ui_refactor --risk-lane low --runtime cursor --workspace awf-demo",
"authorize:supervised": "node tools/authorize-task/authorize-task.js --task-class api_change --risk-lane high --runtime claude_code --workspace awf-demo",
"test": "npm run test --workspaces --if-present"
},
"dependencies": {
Expand Down
297 changes: 297 additions & 0 deletions tools/authorize-task/authorize-task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
#!/usr/bin/env node
// AWF Runtime Authorization — Sprint 3 demo CLI.
//
// Simulates the Trust-Based Runtime Authorization algorithm from
// AWF Sprint Plan v4.4.2. Hard-codes a small library of task risk
// profiles and per-runtime trust capability profiles, then prints
// the AUTHORIZED | SUPERVISED | BLOCKED decision with the same
// fields the production decision record carries:
//
// - task risk profile (5 dimensions, composite, risk lane)
// - trust capability profile (tier, sessions, evidence strength)
// - decision + required vs current tier
// - reason text and D1-D4 evidence summary
// - required controls
// - recommended alternative runtime (when blocked)
// - audit event type the auth service would emit
//
// Usage:
// node tools/authorize-task/authorize-task.js \
// --task-class db_migration \
// --risk-lane high \
// --runtime codex \
// --workspace awf-demo

const TIER_ORDER = ['PROVISIONAL', 'RESTRICTED', 'STANDARD', 'HIGH'];

// Per-task-class risk dimensions (each 1-5). Composite = sum.
const TASK_RISK_PROFILES = {
ui_refactor: { complexity: 1, blast: 2, security: 1, concurrency: 1, business: 2 },
api_change: { complexity: 4, blast: 4, security: 3, concurrency: 3, business: 3 },
db_migration: { complexity: 4, blast: 5, security: 3, concurrency: 3, business: 5 },
security_fix: { complexity: 3, blast: 3, security: 5, concurrency: 2, business: 4 }
};

// Per-runtime trust capability, keyed by task_class.
// Profiles with no D1-D4 fields render as "no history" (E0, n=0).
const TRUST_PROFILES = {
cursor: {
ui_refactor: {
tier: 'HIGH', sessions: 47, evidence: 'E3',
d1: '92 (n=47)', d2: '88', d3: '100 (zero violations)', d4: '100 (no recurrence)'
},
api_change: {
tier: 'STANDARD', sessions: 12, evidence: 'E2',
d1: '78 (n=12)', d2: '70', d3: '95', d4: '90'
},
db_migration: { tier: 'PROVISIONAL', sessions: 0, evidence: 'E0' },
security_fix: { tier: 'PROVISIONAL', sessions: 0, evidence: 'E0' }
},
codex: {
ui_refactor: {
tier: 'STANDARD', sessions: 18, evidence: 'E2',
d1: '81 (n=18)', d2: '72', d3: '98', d4: '94'
},
api_change: {
tier: 'RESTRICTED', sessions: 4, evidence: 'E1',
d1: '74 (n=4)', d2: '60', d3: '95', d4: 'insufficient (n<5)'
},
db_migration: { tier: 'PROVISIONAL', sessions: 0, evidence: 'E0' },
security_fix: { tier: 'PROVISIONAL', sessions: 0, evidence: 'E0' }
},
claude_code: {
ui_refactor: {
tier: 'HIGH', sessions: 62, evidence: 'E3',
d1: '94 (n=62)', d2: '90', d3: '100', d4: '100'
},
api_change: {
tier: 'STANDARD', sessions: 22, evidence: 'E3',
d1: '86 (n=22)', d2: '81', d3: '98', d4: '95',
recent_failure: 'breaking_change_without_version_bump'
},
db_migration: {
tier: 'STANDARD', sessions: 14, evidence: 'E3',
d1: '82 (n=14)', d2: '78', d3: '100', d4: '95'
},
security_fix: {
tier: 'STANDARD', sessions: 15, evidence: 'E3',
d1: '85 (n=15)', d2: '80', d3: '100', d4: '95'
}
}
};

// (task_class, lane) → controls that must accompany a SUPERVISED
// decision, or that would have been required for a BLOCKED one.
const CONTROLS = {
db_migration: {
critical: [
'Human pre-approval required',
'Migration plan review',
'Schema validation',
'Rollback plan attached'
]
},
api_change: {
high: [
'pre-tool-call hook on schema files',
'tech-lead review before PR',
'contract tests must pass'
]
}
};

// Display-only names for runtime (sentence-start in reason text)
// and task-class plural (for the BLOCKED critical reason sentence).
const RUNTIME_DISPLAY = {
cursor: 'Cursor',
codex: 'Codex',
claude_code: 'Claude Code'
};
const TASK_DISPLAY_PLURAL = {
ui_refactor: 'UI refactors',
api_change: 'API changes',
db_migration: 'database migrations',
security_fix: 'security fixes'
};

function parseArgs(argv) {
const args = {};
for (let i = 0; i < argv.length; i++) {
if (argv[i].startsWith('--')) {
args[argv[i].slice(2)] = argv[i + 1];
i++;
}
}
return args;
}

function laneFor(composite) {
if (composite <= 9) return 'low';
if (composite <= 14) return 'medium';
if (composite <= 19) return 'high';
return 'critical';
}

function laneLabel(lane) {
return { low: 'Low', medium: 'Medium', high: 'High', critical: 'Critical' }[lane];
}

function requiredTierFor(lane) {
return { low: 'RESTRICTED', medium: 'STANDARD', high: 'STANDARD', critical: 'HIGH' }[lane];
}

const tierIdx = t => TIER_ORDER.indexOf(t);

function decide(lane, currentTier, requiredTier) {
// Critical lane requires human approval, which runtime auth alone cannot grant.
if (lane === 'critical') return 'BLOCKED';
if (tierIdx(currentTier) < tierIdx(requiredTier)) return 'BLOCKED';
if (lane === 'high' && tierIdx(currentTier) < tierIdx('HIGH')) return 'SUPERVISED';
return 'AUTHORIZED';
}

function recommendRuntime(taskClass, excluding) {
let best = null, bestIdx = -1;
for (const [runtime, profiles] of Object.entries(TRUST_PROFILES)) {
if (runtime === excluding) continue;
const p = profiles[taskClass];
if (!p) continue;
const idx = tierIdx(p.tier);
if (idx > bestIdx) { bestIdx = idx; best = { runtime, tier: p.tier }; }
}
return best;
}

function auditEvent(decision) {
return {
AUTHORIZED: 'RUNTIME_AUTHORIZATION_GRANTED',
SUPERVISED: 'RUNTIME_AUTHORIZATION_GRANTED_WITH_CONTROLS',
BLOCKED: 'RUNTIME_AUTHORIZATION_BLOCKED'
}[decision];
}

function reasonLines({ decision, runtime, taskClass, lane, currentTier, requiredTier }) {
const name = RUNTIME_DISPLAY[runtime] ?? runtime;
if (decision === 'BLOCKED') {
if (lane === 'critical') {
return [
`${name} has not earned sufficient trust for ${laneLabel(lane)} lane`,
`${TASK_DISPLAY_PLURAL[taskClass] ?? taskClass} in this workspace.`
];
}
return [
`${name} holds ${currentTier} tier for ${taskClass},`,
`below the ${requiredTier} requirement for ${laneLabel(lane)} lane execution.`
];
}
if (decision === 'SUPERVISED') {
return [
`${name} holds ${currentTier} tier for ${taskClass}, sufficient`,
`for ${laneLabel(lane)} lane execution under control wrappers.`
];
}
return [
`${name} holds ${currentTier} tier for ${taskClass}, exceeding`,
`the ${requiredTier} requirement for ${laneLabel(lane)} lane execution.`
];
}

// Column alignment helpers. Values land at column 28 in all sections.
const head = (label, value) => `${label.padEnd(16)}${value}`;
const row = (label, value) => ` ${label.padEnd(26)}${value}`;
const ev = (label, value) => ` ${label.padEnd(24)}${value}`;
const DIV = '─────────────────────────────────────────────';

function main() {
const args = parseArgs(process.argv.slice(2));
const taskClass = args['task-class'];
const riskLaneArg = args['risk-lane'];
const runtime = args.runtime;
const workspace = args.workspace;

const risk = TASK_RISK_PROFILES[taskClass];
if (!risk) { console.error(`unknown task class: ${taskClass}`); process.exit(1); }
const trust = TRUST_PROFILES[runtime]?.[taskClass];
if (!trust) { console.error(`unknown runtime/task pair: ${runtime}/${taskClass}`); process.exit(1); }

const composite = risk.complexity + risk.blast + risk.security
+ risk.concurrency + risk.business;
const lane = laneFor(composite);
const requiredTier = requiredTierFor(lane);
const decision = decide(lane, trust.tier, requiredTier);

const out = [];
out.push('AWF Runtime Authorization');
out.push(DIV);
out.push(head('Task class:', taskClass));
out.push(head('Risk lane:', riskLaneArg));
out.push(head('Candidate:', runtime));
out.push(head('Workspace:', workspace));
out.push('');
out.push('Task Risk Profile:');
out.push(row('Code complexity:', risk.complexity));
out.push(row('Blast radius:', risk.blast));
out.push(row('Security sensitivity:', risk.security));
out.push(row('Concurrency risk:', risk.concurrency));
out.push(row('Business criticality:', risk.business));
out.push(row('Composite score:', `${composite} → ${laneLabel(lane)} lane`));
out.push('');
out.push('Trust Capability Profile:');
out.push(row('Runtime:', runtime));
out.push(row('Task class:', taskClass));
out.push(row('Current trust tier:', trust.tier));
out.push(row('Sessions:', trust.sessions));
out.push(row('Evidence strength:', trust.evidence));
out.push('');
out.push('Authorization Decision:');
out.push(row('Decision:', decision));
out.push(row('Required trust tier:',
decision === 'SUPERVISED' ? `${requiredTier} + controls` : requiredTier));
out.push(row('Current trust tier:', trust.tier));
out.push(' ');
out.push(' Reason:');
reasonLines({ decision, runtime, taskClass, lane, currentTier: trust.tier, requiredTier })
.forEach(l => out.push(` ${l}`));
out.push(' ');
out.push(' Evidence:');
out.push(ev('D1 Correctness:', trust.d1 ?? 'no history'));
out.push(ev('D2 Observability:', trust.d2 ?? 'no history'));
out.push(ev('D3 Policy:', trust.d3 ?? 'no history'));
out.push(ev('D4 Recurrence:', trust.d4 ?? 'no history'));
if (trust.recent_failure) {
out.push('');
out.push(row('Recent failure pattern:', trust.recent_failure));
}
out.push('');

const controls = CONTROLS[taskClass]?.[lane];
if (decision === 'BLOCKED') {
if (controls) {
out.push(' Required controls:');
controls.forEach(c => out.push(` - ${c}`));
out.push('');
}
const rec = recommendRuntime(taskClass, runtime);
if (rec) {
out.push(row('Recommended runtime:', rec.runtime));
out.push(` Note: ${rec.runtime} holds ${rec.tier} tier for ${taskClass}`);
out.push(` but ${laneLabel(lane)} lane requires HIGH. No runtime is authorized`);
out.push(` for ${laneLabel(lane)} lane without human approval regardless of tier.`);
out.push('');
}
} else if (decision === 'SUPERVISED' && controls) {
out.push(' Required controls:');
controls.forEach(c => out.push(` - ${c}`));
out.push('');
} else if (decision === 'AUTHORIZED') {
out.push(' Required controls:');
out.push(' None beyond standard merge approval');
out.push('');
}

out.push(row('Audit event:', auditEvent(decision)));
out.push(DIV);
console.log(out.join('\n'));
}

main();
Loading