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
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
"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": "Live trust data: export AWF_DATABASE_URL=postgres://localhost/awf (or pass --db postgres://... to the CLI). Without it the scripts below render hard-coded example profiles.",
"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",
"authorize:authorized:live": "node tools/authorize-task/authorize-task.js --task-class ui_refactor --risk-lane low --runtime cursor --workspace awf-demo --db $AWF_DATABASE_URL",
"test": "npm run test --workspaces --if-present"
},
"dependencies": {
Expand Down
80 changes: 77 additions & 3 deletions tools/authorize-task/authorize-task.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
// --risk-lane high \
// --runtime codex \
// --workspace awf-demo
//
// Live data:
// Pass --db postgres://... (or set AWF_DATABASE_URL) to read the
// most-recently-scored row from trust_capability_profiles for the
// (runtime, task_class) pair. On miss or DB error the CLI falls back
// to the hard-coded example profiles silently (error path warns to
// stderr).

import pg from 'pg';

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

Expand Down Expand Up @@ -202,16 +211,78 @@ const row = (label, value) => ` ${label.padEnd(26)}${value}`;
const ev = (label, value) => ` ${label.padEnd(24)}${value}`;
const DIV = '─────────────────────────────────────────────';

function main() {
// SQL is intentionally narrow: pick the most-recently-scored profile
// for the (runtime_provider, task_class) pair across all workspaces.
// A demo-grade lookup — production callers use the writer's
// getTrustCapabilityProfile() keyed by (workspace, runtime, task_class,
// subject_key).
const LIVE_PROFILE_SQL = `
SELECT trust_level, avg_score, avg_d1, avg_d2,
avg_d3, avg_d4, session_count,
evidence_strength, confidence_band
FROM trust_capability_profiles tcp
JOIN trust_subjects ts ON ts.id = tcp.trust_subject_id
WHERE ts.runtime_provider = $1
AND tcp.task_class = $2
ORDER BY tcp.last_score_at DESC
LIMIT 1
`;

// Format a NUMERIC dimension (pg returns NUMERIC as string). Returns
// undefined for null/missing so the existing `?? 'no history'` render
// path applies.
function fmtDim(v) {
if (v === null || v === undefined) return undefined;
return String(Math.round(Number(v)));
}

// Map a live trust_capability_profiles row into the shape the renderer
// expects (tier/sessions/evidence/d1..d4). Zero-session rows yield no
// d1..d4 so they render as "no history".
function liveRowToTrust(r) {
const hasHistory = r.session_count > 0;
const d1 = hasHistory ? `${fmtDim(r.avg_d1)} (n=${r.session_count})` : undefined;
return {
tier: r.trust_level,
sessions: r.session_count,
evidence: r.evidence_strength,
d1,
d2: hasHistory ? fmtDim(r.avg_d2) : undefined,
d3: hasHistory ? fmtDim(r.avg_d3) : undefined,
d4: hasHistory ? fmtDim(r.avg_d4) : undefined,
};
}

// Returns { trust, source } where source is 'live' | 'fallback'.
// dbUrl null/undefined means: skip DB entirely, use fallback silently.
async function getTrustProfile(runtime, taskClass, dbUrl) {
const fallback = TRUST_PROFILES[runtime]?.[taskClass];
if (!dbUrl) return { trust: fallback, source: 'fallback' };

const pool = new pg.Pool({ connectionString: dbUrl });
try {
const r = await pool.query(LIVE_PROFILE_SQL, [runtime, taskClass]);
if (r.rows.length === 0) return { trust: fallback, source: 'fallback' };
return { trust: liveRowToTrust(r.rows[0]), source: 'live' };
} catch (e) {
console.error(`warning: trust DB query failed (${e.message}); using example data`);
return { trust: fallback, source: 'fallback' };
} finally {
await pool.end().catch(() => {});
}
}

async 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 dbUrl = args.db ?? process.env.AWF_DATABASE_URL ?? null;

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

const composite = risk.complexity + risk.blast + risk.security
Expand Down Expand Up @@ -242,6 +313,9 @@ function main() {
out.push(row('Current trust tier:', trust.tier));
out.push(row('Sessions:', trust.sessions));
out.push(row('Evidence strength:', trust.evidence));
out.push(row('Source:', source === 'live'
? 'live trust database'
: 'example data (set AWF_DATABASE_URL for live data)'));
out.push('');
out.push('Authorization Decision:');
out.push(row('Decision:', decision));
Expand Down Expand Up @@ -294,4 +368,4 @@ function main() {
console.log(out.join('\n'));
}

main();
main().catch(e => { console.error(e); process.exit(1); });
Loading