store.db.exec(`CREATE TABLE IF NOT EXISTS policy_reviews(id TEXT PRIMARY KEY,time TEXT NOT NULL,author TEXT NOT NULL,expected_version INTEGER NOT NULL,payload_hash TEXT NOT NULL,policy TEXT NOT NULL,kind TEXT NOT NULL,operation TEXT NOT NULL,status TEXT NOT NULL,reviewer TEXT,reviewed_at TEXT,published_version INTEGER)`);
if(p.evidence.enabled&&!config.evidenceKeyFile)throwObject.assign(Error('Protected evidence storage must be configured before collection is enabled'),{status:400});
if(store.getRaw('policy').version!==expected||p.version!==expected+1)throwObject.assign(Error('Policy version conflict'),{status:409});
if(!globalBookmarksValid(store,p))throwObject.assign(Error('Company bookmark IDs conflict with personal bookmarks'),{status:409});
if(Date.parse(p.expiresAt)<=Date.now()||Date.parse(p.expiresAt)>Date.now()+30*86400000)throwObject.assign(Error('Policy expiry must be within the next 30 days'),{status:400});
if(!store.db.prepare('UPDATE devices SET document_analysis_enabled=? WHERE id=? AND revoked=0').run(operation.enabled?1:0,operation.deviceId).changes)throwObject.assign(Error('Active device not found'),{status:409});
if(!store.db.prepare("UPDATE teaching SET status='published',reviewer=?,policy_version=? WHERE id=? AND status='draft'").run(actor,p.version,operation.id).changes)throwObject.assign(Error('Teaching draft no longer available'),{status:409});
store.put('policy',p);store.db.prepare('INSERT INTO policies VALUES(?,?,?,?)').run(p.version,newDate().toISOString(),actor,JSON.stringify(p));
store.db.prepare('INSERT INTO policy_group_history(version,groups) VALUES(?,?)').run(p.version,JSON.stringify(store.getRaw('policyGroups')||[]));
store.db.prepare('INSERT INTO user_profile_history(version,configuration) VALUES(?,?)').run(p.version,JSON.stringify(store.getRaw('userProfiles')||{profiles:[],categories:[]}));
if(store.db.prepare("SELECT COUNT(*) AS count FROM policy_reviews WHERE status='pending' AND expected_version=?").get(expectedVersion).count>=1000)throwObject.assign(Error('Pending policy review limit reached'),{status:409});
store.db.exec('BEGIN IMMEDIATE');try{
store.db.prepare('INSERT INTO policy_reviews(id,time,author,expected_version,payload_hash,policy,kind,operation,status) VALUES(?,?,?,?,?,?,?,?,?)').run(id,newDate().toISOString(),author,expectedVersion,payloadHash,JSON.stringify(p),kind,JSON.stringify(operation),'pending');
app.get('/api/admin/policy-reviews',(_req,res)=>res.json({requireIndependentPolicyApproval:!!config.requireIndependentPolicyApproval,reviews:store.db.prepare('SELECT * FROM policy_reviews ORDER BY time DESC LIMIT 100').all().map(view)}));
app.get('/api/admin/policy-reviews/:id',(req,res)=>{constrow=store.db.prepare('SELECT * FROM policy_reviews WHERE id=?').get(req.params.id);if(!row)returnres.status(404).json({error:'Policy review not found'});constpolicy=PolicySchema.parse(JSON.parse(row.policy));res.json({...view(row),policy,operation:JSON.parse(row.operation),diff:policyDiff(store.get('policy'),policy)});});
if(action==='approve'){if(b.expectedVersion!==row.expected_version)throwObject.assign(Error('Policy version conflict'),{status:409});validate(policy,row.expected_version,operation);apply(policy,req.principal.sub,row.kind,operation);}
store.db.prepare('UPDATE policy_reviews SET status=?,reviewer=?,reviewed_at=?,published_version=? WHERE id=?').run(status,req.principal.sub,newDate().toISOString(),action==='approve'?policy.version:null,row.id);