import test from 'node:test'; import assert from 'node:assert/strict'; import {mkdtempSync,rmSync} from 'node:fs'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; import {createRequire} from 'node:module'; import {openStore,digest} from '../apps/server/store.mjs'; import {createApp} from '../apps/server/app.mjs'; import {verifyPolicy} from '../packages/core/policy.mjs'; const {reconcileGenerationFloor}=createRequire(import.meta.url)('../apps/browser/agent-grants.cjs'); async function fixture(t){ const dir=mkdtempSync(join(tmpdir(),'safetynet-agent-publication-')),store=openStore(dir),origin='http://127.0.0.1:4310'; const server=createApp(store,{publicUrl:origin,secure:false,requireIndependentPolicyApproval:true}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r)); t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});}); async function api(path,method='GET',body,subject='author',role='admin'){ const credential=`synthetic-agent-publication-${subject}-12345678901234567890`; store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:subject,role})); const response=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method,headers:{cookie:`sn_session=${credential}`,origin,'content-type':'application/json'},body:body===undefined?undefined:JSON.stringify(body)}); return {status:response.status,body:await response.json()}; } const approve=(review,subject='reviewer',role='admin')=>api(`/api/admin/policy-reviews/${review.reviewId}/approve`,'POST',{payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true},subject,role); return {store,api,approve,signed:()=>verifyPolicy(store.envelope(),store.publicKey())}; } const grant=generation=>({id:'research-agent',name:'Research Agent',enabled:true,expiresAt:'2099-12-31T23:59:59Z',scopes:['sessions','read'],allowedHosts:['*.company.example'],generation}); test('individual grants require independent review and survive signed history restoration without credential resurrection',async t=>{ const f=await fixture(t),candidate=f.store.get('policy');candidate.version++;candidate.agents={...candidate.agents,enabled:true,identityMode:'individual',scopes:['sessions','read'],allowedHosts:['*.company.example'],grants:[grant(1)]}; const queued=await f.api('/api/admin/policy','PUT',candidate);assert.equal(queued.status,202);assert.equal(f.signed().agents.enabled,false); const detail=await f.api(`/api/admin/policy-reviews/${queued.body.reviewId}`);assert.equal(detail.status,200);assert.ok(detail.body.diff.changes.some(c=>c.path==='/agents/grants'&&c.after[0].id==='research-agent')); assert.equal((await f.approve(queued.body,'author')).status,403);assert.equal((await f.approve(queued.body,'auditor','auditor')).status,403);assert.equal((await f.approve(queued.body)).status,200);assert.deepEqual(f.signed().agents.grants,[grant(1)]); const floor1=reconcileGenerationFloor(f.signed().agents.grants).floor; const rotated=f.store.get('policy');rotated.version++;rotated.agents.grants[0].generation=2;rotated.agents.grants[0].scopes=['sessions'];const rotation=await f.api('/api/admin/policy','PUT',rotated);assert.equal(rotation.status,202);assert.equal((await f.approve(rotation.body)).status,200); const floor2=reconcileGenerationFloor(f.signed().agents.grants,floor1).floor;assert.equal(floor2['research-agent'],2); const restore=await f.api('/api/admin/policy/restore','POST',{sourceVersion:2,expectedVersion:3,approve:true});assert.equal(restore.status,202);assert.equal(f.signed().version,3); const restoreReview=await f.api(`/api/admin/policy-reviews/${restore.body.reviewId}`);assert.ok(restoreReview.body.diff.changes.some(c=>c.path==='/agents/grants'&&c.after[0].generation===1));assert.equal((await f.approve(restore.body,'author')).status,403);assert.equal((await f.approve(restore.body)).status,200); const restored=f.signed();assert.equal(restored.version,4);assert.deepEqual(restored.agents.grants,[grant(1)]);const effective=reconcileGenerationFloor(restored.agents.grants,floor2);assert.equal(effective.grants[0].enabled,false);assert.equal(effective.floor['research-agent'],2); const history=await f.api('/api/admin/policy/history/2');assert.deepEqual(history.body.policy.agents.grants,[grant(1)]); assert.equal((await f.api('/api/admin/policy','PUT',{...restored,version:5},'auditor','auditor')).status,403);assert.equal((await f.api('/api/admin/policy/restore','POST',{sourceVersion:2,expectedVersion:4,approve:true},'auditor','auditor')).status,403);assert.equal(f.signed().version,4); });