a1-ai 2.8.0

A1 — The cryptographic identity and authorization layer that turns anonymous AI agents into accountable, verifiable entities. One Identity. Full Provenance.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
// ─────────────────────────────────────────────────────────────────────────────
// TAB CONTENTS
// ─────────────────────────────────────────────────────────────────────────────
function Overview({health,logs}){
  const{settings}=useContext(Ctx);
  const gwUrl=settings.gwUrl||'http://localhost:8080';
  const recent=logs.slice(-6).reverse();
  const errC=logs.filter(l=>!l.ok).length;
  const avgMs=logs.length?Math.round(logs.reduce((a,l)=>a+l.ms,0)/logs.length):0;
  return h('div',null,
    h(ProtectionStatusBanner, { gwUrl }),
    h('div',{className:'g4 mb14'},
      h('div',{className:'card'},h('div',{className:'ctitle'},'Gateway'),h('div',{className:'bignum'},h('span',{className:'dot dot-'+(health?'green':'red'),style:{marginRight:8}}),health?'Online':'Offline'),h('div',{className:'bigsub'},health?'v'+(health.version||'2.8.0'):settings.gwUrl)),
      h('div',{className:'card'},h('div',{className:'ctitle'},'Requests'),h('div',{className:'bignum'},logs.length),h('div',{className:'bigsub'},'this session')),
      h('div',{className:'card'},h('div',{className:'ctitle'},'Errors'),h('div',{className:'bignum',style:{color:errC>0?'var(--red)':'var(--text)'}},errC),h('div',{className:'bigsub'},errC>0?'check live log':'clean')),
      h('div',{className:'card'},h('div',{className:'ctitle'},'Avg Latency'),h('div',{className:'bignum'},avgMs>0?avgMs+'ms':''),h('div',{className:'bigsub'},'round-trip'))
    ),
    h('div',{className:'g2 mb14'},
      h('div',{className:'card'},
        h('div',{className:'ctitle'},'A1 — One Identity. Full Provenance.'),
        h('p',{style:{fontSize:'var(--fsm)',color:'var(--t2)',marginBottom:14,lineHeight:'1.7'}},'The cryptographic identity layer that closes the Recursive Delegation Gap. Every agent action carries an irrefutable chain of custody from the executing agent back to the authorizing human.'),
        h(SocialLinks,null)
      ),
      health?h('div',{className:'card','data-help':'gateway-id'},
        h('div',{className:'ctitle'},'Gateway Identity'),
        h('div',{className:'field'},h('div',{className:'lbl'},'DID'),h(TruncId,{val:health.signing_pk_hex?'did:a1:'+health.signing_pk_hex:null,showFull:settings.showFullIds})),
        h('div',{className:'field'},h('div',{className:'lbl'},'Signing Key'),h(TruncId,{val:health.signing_pk_hex,showFull:settings.showFullIds})),
        h('div',{className:'field'},h('div',{className:'lbl'},'Protocol'),h('span',{className:'badge badge-dim'},'dyolo v2.8.0')),
        h('div',{className:'field'},h('div',{className:'lbl'},'Storage'),h('span',{className:'badge badge-dim'},health.storage_backend||'memory')),
        h('div',{style:{display:'flex',gap:6,marginTop:4,flexWrap:'wrap'}},
          h('span',{className:'badge '+(health.webhook_enabled?'badge-ok':'badge-dim')},(health.webhook_enabled?'':'')+' Webhook'),
          h('span',{className:'badge '+(health.jwt_exchange_enabled?'badge-ok':'badge-dim')},(health.jwt_exchange_enabled?'':'')+' JWT Exchange'),
          h('span',{className:'badge '+(health.multi_tenant_enabled?'badge-ok':'badge-dim')},(health.multi_tenant_enabled?'':'')+' Multi-Tenant')
        )
      ):h('div',{className:'card'},h('div',{className:'empty'},'Gateway offline — check Settings → Gateway URL'))
    ),
    h('div',{className:'card','data-help':'overview'},
      h('div',{className:'sec-head'},
        h('div',{className:'sec-title'},'Activity'),
        h('div',{style:{display:'flex',alignItems:'center',gap:5}},h('div',{className:'dot dot-green dot-pulse'}),h('span',{style:{fontSize:'var(--fxs)',color:'var(--t2)',fontFamily:'var(--mono)'}},'live'))
      ),
      recent.length===0?h('div',{className:'empty'},'No requests yet. Use the tabs to interact with the gateway.'):
      h('div',{className:'log-stream',style:{height:200}},recent.map(e=>h(LogEntry,{key:e.id,e})))
    )
  );
}

function LiveLogs({logs,onClear}){
  const{settings}=useContext(Ctx);
  const[filter,setFilter]=useState('');
  const[autoS,setAutoS]=useState(settings.autoScroll);
  const ref=useRef(null);
  const filtered=useMemo(()=>{
    if(!filter)return logs;
    const f=filter.toLowerCase();
    return logs.filter(l=>l.path.includes(f)||l.method.toLowerCase().includes(f)||String(l.status).includes(f));
  },[logs,filter]);
  useEffect(()=>{if(autoS&&ref.current)ref.current.scrollTop=ref.current.scrollHeight;},[filtered,autoS]);
  const okC=logs.filter(l=>l.ok).length;
  const errC=logs.filter(l=>!l.ok).length;
  return h('div',{'data-help':'live-log',style:{width:'100%'}},
    h('div',{className:'log-wrap'},
      h('div',{className:'log-bar'},
        h('span',{className:'log-bar-title'},'Live Request Log'),
        okC>0&&h('span',{className:'badge badge-ok'},okC+' ok'),
        errC>0&&h('span',{className:'badge badge-err'},errC+' err'),
        h('span',{style:{fontSize:'var(--fxs)',color:'var(--t2)',fontFamily:'var(--mono)',marginLeft:4}},logs.length+' total')
      ),
      h('div',{style:{padding:'6px 10px',borderBottom:'1px solid var(--b1)',display:'flex',alignItems:'center',gap:6,background:'var(--s2)'},'data-help':'log-filter'},
        h('input',{className:'inp inp-mono',style:{flex:1,padding:'4px 8px'},placeholder:'filter by path / method / status…',value:filter,onChange:e=>setFilter(e.target.value)}),
        h('button',{className:'btn btn-sm btn-s',style:{fontFamily:'var(--mono)'},onClick:()=>setAutoS(a=>!a)},autoS?'':''),
        h('button',{className:'btn btn-sm btn-d',onClick:onClear},'clear')
      ),
      h('div',{ref,className:'log-stream',style:{height:460}},
        filtered.length===0?h('div',{className:'empty'},'No requests. Make API calls from other tabs.'):
        filtered.slice().reverse().map(e=>h(LogEntry,{key:e.id,e}))
      )
    ),
    h('p',{style:{fontSize:'var(--fxs)',color:'var(--t2)',marginTop:9,fontFamily:'var(--mono)'}},'All requests made by this Studio session. Click an entry to expand. For gateway-level logs, tail the a1-gateway process stdout.')
  );
}

function Passports(){
  const{api,settings}=useContext(Ctx);
  const[name,setName]=useState('');
  const[caps,setCaps]=useState('trade.equity,portfolio.read');
  const[ttl,setTtl]=useState('30');
  const[sk,setSk]=useState('');
  const[msg,setMsg]=useState(null);
  const[msgT,setMsgT]=useState('ok');
  const[result,setResult]=useState(null);
  const PRESETS=['trade.equity','portfolio.read','portfolio.write','audit.read','email.send','data.export','web.search','api.call'];
  async function issue(){
    if(!name){setMsg('Agent name required.');setMsgT('error');return;}
    setMsg(null);
    const key=sk||Array.from(crypto.getRandomValues(new Uint8Array(32))).map(b=>b.toString(16).padStart(2,'0')).join('');
    const r=await api('POST','/v1/cert/issue',{passport_namespace:name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean),ttl_seconds:parseInt(ttl)*86400,signing_key_hex:key});
    if(r.ok){setMsg('Passport issued.');setMsgT('ok');setResult({...r.data,_sk:key});}
    else{setMsg(r.data.error||'Failed.');setMsgT('error');}
  }
  return h('div',{'data-help':'passports',style:{width:'100%'}},
    h('div',{className:'sec-head'},h('div',{className:'sec-title'},'Issue Passport')),
    h(Alert,{msg,type:msgT}),
    h('div',{className:'card mb14'},
      h('div',{className:'g2'},
        h('div',{className:'field'},h('label',{className:'lbl'},'Agent Name'),h('input',{className:'inp',placeholder:'acme-trading-bot',value:name,onChange:e=>setName(e.target.value)})),
        h('div',{className:'field'},h('label',{className:'lbl'},'TTL (days)'),h('input',{className:'inp inp-mono',type:'number',min:1,max:365,value:ttl,onChange:e=>setTtl(e.target.value)}))
      ),
      h('div',{className:'field'},h('label',{className:'lbl'},'Capabilities'),h('input',{className:'inp inp-mono',value:caps,onChange:e=>setCaps(e.target.value)})),
      h('div',{style:{display:'flex',flexWrap:'wrap',gap:4,marginBottom:11}},
        PRESETS.map(c=>h('button',{key:c,className:'btn btn-xs btn-s',onClick:()=>setCaps(p=>p?(p.includes(c)?p:p+','+c):c)},'+ '+c))
      ),
      h('div',{className:'field'},h('label',{className:'lbl'},'Signing Key (optional — auto-generated if blank)'),h('input',{className:'inp inp-mono',placeholder:'64-char hex Ed25519 seed',value:sk,onChange:e=>setSk(e.target.value)})),
      h('button',{className:'btn btn-p',onClick:issue},'Issue Passport')
    ),
    result&&h('div',{className:'card'},
      h('div',{className:'ctitle',style:{marginBottom:10}},'Result — copy the signing key now'),
      h('div',{className:'field'},h('label',{className:'lbl'},'Signing Key (save this)'),h(TruncId,{val:result._sk,showFull:true})),
      h('pre',{style:{background:'var(--s2)',border:'1px solid var(--b1)',borderRadius:'var(--r)',padding:11,fontSize:9,overflow:'auto',maxHeight:200,color:'var(--t2)',fontFamily:'var(--mono)'}},
        JSON.stringify(Object.fromEntries(Object.entries(result).filter(([k])=>k!=='_sk')),null,2)
      )
    )
  );
}

function Swarms(){
  const{api,settings}=useContext(Ctx);
  const[name,setName]=useState('');
  const[caps,setCaps]=useState('trade.equity,portfolio.read');
  const[ttl,setTtl]=useState('30');
  const[key,setKey]=useState('');
  const[swarmId,setSwarmId]=useState('');
  const[agentPk,setAgentPk]=useState('');
  const[role,setRole]=useState('worker');
  const[roleCaps,setRoleCaps]=useState('trade.equity');
  const[mTtl,setMTtl]=useState('3600');
  const[members,setMembers]=useState([]);
  const[msg,setMsg]=useState(null);
  const[msgT,setMsgT]=useState('ok');
  async function create(){
    if(!name||!key){setMsg('Name + signing key required.');setMsgT('error');return;}
    const r=await api('POST','/v1/swarm/create',{swarm_name:name,capabilities:caps.split(',').map(s=>s.trim()).filter(Boolean),ttl_days:parseInt(ttl),signing_key_hex:key});
    if(r.ok){setSwarmId(r.data.swarm_id);setMsg('Swarm created.');setMsgT('ok');}
    else{setMsg(r.data.error||'Failed');setMsgT('error');}
  }
  async function addMember(){
    if(!swarmId||!agentPk){setMsg('Create swarm + enter agent key first.');setMsgT('error');return;}
    const r=await api('POST','/v1/swarm/member/add',{swarm_id:swarmId,agent_pk_hex:agentPk,role,capabilities:roleCaps.split(',').map(s=>s.trim()).filter(Boolean),ttl_seconds:parseInt(mTtl),signing_key_hex:key});
    if(r.ok){setMsg('Member added.');setMsgT('ok');}
    else{setMsg(r.data.error||'Failed');setMsgT('error');}
  }
  async function listM(){
    if(!swarmId)return;
    const r=await api('GET',`/v1/swarm/${swarmId}/members`);
    if(r.ok)setMembers(r.data.members||[]);
  }
  return h('div',{'data-help':'swarms',style:{width:'100%'}},
    h('div',{className:'sec-head'},h('div',{className:'sec-title'},'Swarm Passports')),
    h(Alert,{msg,type:msgT}),
    h('div',{className:'card mb14'},
      h('div',{className:'ctitle'},'Create Swarm'),
      h('div',{className:'g2'},
        h('div',{className:'field'},h('label',{className:'lbl'},'Swarm Name'),h('input',{className:'inp',placeholder:'acme-swarm',value:name,onChange:e=>setName(e.target.value)})),
        h('div',{className:'field'},h('label',{className:'lbl'},'TTL (days)'),h('input',{className:'inp inp-mono',type:'number',value:ttl,onChange:e=>setTtl(e.target.value)}))
      ),
      h('div',{className:'field'},h('label',{className:'lbl'},'Root Capabilities'),h('input',{className:'inp inp-mono',value:caps,onChange:e=>setCaps(e.target.value)})),
      h('div',{className:'field'},h('label',{className:'lbl'},'Orchestrator Signing Key (hex)'),h('input',{className:'inp inp-mono',placeholder:'64-char hex',value:key,onChange:e=>setKey(e.target.value)})),
      h('div',{style:{display:'flex',gap:7,flexWrap:'wrap'}},
        h('button',{className:'btn btn-p',onClick:create},'Create Swarm'),
        swarmId&&h('button',{className:'btn btn-s',onClick:listM},'Refresh Members')
      ),
      swarmId&&h('div',{style:{marginTop:10}},h('div',{className:'lbl'},'Swarm ID'),h(TruncId,{val:swarmId,showFull:settings.showFullIds}))
    ),
    swarmId&&h('div',{className:'card mb14'},
      h('div',{className:'ctitle'},'Add Member'),
      h('div',{className:'g2'},
        h('div',{className:'field'},h('label',{className:'lbl'},'Agent Public Key (hex)'),h('input',{className:'inp inp-mono',placeholder:'64-char hex',value:agentPk,onChange:e=>setAgentPk(e.target.value)})),
        h('div',{className:'field'},h('label',{className:'lbl'},'Role'),h('select',{className:'inp',value:role,onChange:e=>setRole(e.target.value)},['orchestrator','worker','supervisor','auditor'].map(r=>h('option',{key:r,value:r},r))))
      ),
      h('div',{className:'g2'},
        h('div',{className:'field'},h('label',{className:'lbl'},'Role Capabilities'),h('input',{className:'inp inp-mono',value:roleCaps,onChange:e=>setRoleCaps(e.target.value)})),
        h('div',{className:'field'},h('label',{className:'lbl'},'TTL (sec)'),h('input',{className:'inp inp-mono',type:'number',value:mTtl,onChange:e=>setMTtl(e.target.value)}))
      ),
      h('button',{className:'btn btn-p',onClick:addMember},'Add Member')
    ),
    members.length>0&&h('div',{className:'card'},
      h('div',{className:'ctitle'},'Members ('+members.length+')'),
      h('table',{className:'tbl'},
        h('thead',null,h('tr',null,h('th',null,'DID'),h('th',null,'Role'),h('th',null,'Expires'))),
        h('tbody',null,members.map((m,i)=>h('tr',{key:i},
          h('td',null,h(TruncId,{val:m.agent_did,showFull:settings.showFullIds})),
          h('td',null,h('span',{className:'badge badge-dim'},m.role)),
          h('td',{style:{fontFamily:'var(--mono)',fontSize:10,color:'var(--t2)'}},new Date(m.expires_at_unix*1000).toLocaleString())
        )))
      )
    )
  );
}

function DidVc(){
  const{api,settings}=useContext(Ctx);
  const[pk,setPk]=useState('');
  const[didRes,setDidRes]=useState(null);
  const[gwDoc,setGwDoc]=useState(null);
  const[vPk,setVPk]=useState('');
  const[vNs,setVNs]=useState('');
  const[vCaps,setVCaps]=useState('');
  const[vTtl,setVTtl]=useState('86400');
  const[vcRes,setVcRes]=useState(null);
  const[vcStr,setVcStr]=useState('');
  const[vcVer,setVcVer]=useState(null);
  const[msg,setMsg]=useState(null);
  const[msgT,setMsgT]=useState('ok');
  const PRE={background:'var(--s2)',border:'1px solid var(--b1)',borderRadius:'var(--r)',padding:9,fontSize:9,overflow:'auto',maxHeight:160,color:'var(--t2)',fontFamily:'var(--mono)',marginTop:9};
  async function resolveGw(){const r=await api('GET','/v1/did/gateway');if(r.ok)setGwDoc(r.data);else{setMsg(r.data.error||'Failed');setMsgT('error');}}
  async function resolve(){if(!pk){setMsg('Enter key hex.');setMsgT('error');return;}const r=await api('GET','/v1/did/'+pk);if(r.ok){setDidRes(r.data);setMsg(null);}else{setMsg(r.data.error||'Failed');setMsgT('error');}}
  async function issueVc(){if(!vPk||!vNs){setMsg('Subject key + namespace required.');setMsgT('error');return;}const r=await api('POST','/v1/vc/issue',{subject_pk_hex:vPk,passport_namespace:vNs,capabilities:vCaps.split(',').map(s=>s.trim()).filter(Boolean),ttl_seconds:parseInt(vTtl)});if(r.ok){setVcRes(r.data);setMsg(null);}else{setMsg(r.data.error||'Failed');setMsgT('error');}}
  async function verifyVc(){let c;try{c=JSON.parse(vcStr);}catch{setMsg('Invalid JSON.');setMsgT('error');return;}const r=await api('POST','/v1/vc/verify',{credential:c});if(r.ok){setVcVer(r.data);setMsg(null);}else{setMsg(r.data.error||'Failed');setMsgT('error');}}
  return h('div',{'data-help':'did',style:{width:'100%'}},
    h(Alert,{msg,type:msgT}),
    h('div',{className:'g2 mb14'},
      h('div',{className:'card'},h('div',{className:'ctitle'},'Gateway DID'),h('p',{style:{fontSize:'var(--fsm)',color:'var(--t2)',marginBottom:10}},"Resolve the gateway's W3C DID Document."),h('button',{className:'btn btn-p',onClick:resolveGw},'Resolve'),gwDoc&&h('pre',{style:PRE},JSON.stringify(gwDoc,null,2))),
      h('div',{className:'card'},h('div',{className:'ctitle'},'Resolve Any DID'),h('div',{className:'field'},h('label',{className:'lbl'},'Public Key (hex)'),h('input',{className:'inp inp-mono',placeholder:'64-char hex',value:pk,onChange:e=>setPk(e.target.value)})),h('button',{className:'btn btn-p',onClick:resolve},'Resolve'),didRes&&h('pre',{style:PRE},JSON.stringify(didRes,null,2)))
    ),
    h('div',{className:'g2 mb14'},
      h('div',{className:'card'},
        h('div',{className:'ctitle'},'Issue VC'),
        h('div',{className:'field'},h('label',{className:'lbl'},'Subject Public Key'),h('input',{className:'inp inp-mono',placeholder:'64-char hex',value:vPk,onChange:e=>setVPk(e.target.value)})),
        h('div',{className:'field'},h('label',{className:'lbl'},'Namespace'),h('input',{className:'inp',placeholder:'acme-bot',value:vNs,onChange:e=>setVNs(e.target.value)})),
        h('div',{className:'g2'},h('div',{className:'field'},h('label',{className:'lbl'},'Capabilities'),h('input',{className:'inp inp-mono',value:vCaps,onChange:e=>setVCaps(e.target.value)})),h('div',{className:'field'},h('label',{className:'lbl'},'TTL (sec)'),h('input',{className:'inp inp-mono',type:'number',value:vTtl,onChange:e=>setVTtl(e.target.value)}))),
        h('button',{className:'btn btn-p',onClick:issueVc},'Issue VC'),
        vcRes&&h('pre',{style:PRE},JSON.stringify(vcRes,null,2))
      ),
      h('div',{className:'card'},
        h('div',{className:'ctitle'},'Verify VC'),
        h('div',{className:'field'},h('label',{className:'lbl'},'Credential JSON'),h('textarea',{className:'inp inp-mono',rows:7,placeholder:'Paste W3C VC JSON…',value:vcStr,onChange:e=>setVcStr(e.target.value)})),
        h('button',{className:'btn btn-p',onClick:verifyVc},'Verify'),
        vcVer&&h('div',{style:{marginTop:9}},h('span',{className:vcVer.valid?'badge badge-ok':'badge badge-err'},vcVer.valid?'✓ Valid':'✗ Invalid'),vcVer.error&&h('p',{style:{fontSize:'var(--fxs)',color:'var(--red)',marginTop:5,fontFamily:'var(--mono)'}},vcVer.error))
      )
    )
  );
}

function Authorize(){
  const{api}=useContext(Ctx);
  const[chain,setChain]=useState('');
  const[intent,setIntent]=useState('trade.equity');
  const[epk,setEpk]=useState('');
  const[result,setResult]=useState(null);
  const[msg,setMsg]=useState(null);
  const[msgT,setMsgT]=useState('ok');
  async function run(){
    let c;try{c=JSON.parse(chain);}catch{setMsg('Invalid chain JSON.');setMsgT('error');return;}
    setMsg(null);
    const r=await api('POST','/v1/authorize',{chain:c,intent_name:intent,executor_pk_hex:epk});
    if(r.ok){setResult(r.data);setMsg('Authorized.');setMsgT('ok');}
    else{setResult(r.data);setMsg(r.data.error||'Failed.');setMsgT('error');}
  }
  return h('div',{'data-help':'authorize',style:{width:'100%'}},
    h('div',{className:'sec-head'},h('div',{className:'sec-title'},'Test Authorization')),
    h(Alert,{msg,type:msgT}),
    h('div',{className:'card'},
      h('div',{className:'g2'},
        h('div',{className:'field'},h('label',{className:'lbl'},'Intent Name'),h('input',{className:'inp inp-mono',value:intent,onChange:e=>setIntent(e.target.value)})),
        h('div',{className:'field'},h('label',{className:'lbl'},'Executor Public Key (hex)'),h('input',{className:'inp inp-mono',placeholder:'64-char hex',value:epk,onChange:e=>setEpk(e.target.value)}))
      ),
      h('div',{className:'field','data-help':'auth-chain'},h('label',{className:'lbl'},'Delegation Chain (JSON)'),h('textarea',{className:'inp inp-mono',rows:7,placeholder:'Paste SignedChain JSON…',value:chain,onChange:e=>setChain(e.target.value)})),
      h('button',{className:'btn btn-p',onClick:run},'Authorize'),
      result&&h('pre',{style:{marginTop:13,background:'var(--s2)',border:'1px solid var(--b1)',borderRadius:'var(--r)',padding:10,fontSize:9,overflow:'auto',maxHeight:240,color:'var(--t2)',fontFamily:'var(--mono)'}},JSON.stringify(result,null,2))
    )
  );
}

function Compliance(){
  const{api}=useContext(Ctx);
  const[scope,setScope]=useState('');
  const[report,setReport]=useState(null);
  const[msg,setMsg]=useState(null);
  const[msgT,setMsgT]=useState('ok');
  async function generate(){
    if(!scope){setMsg('Enter scope.');setMsgT('error');return;}
    const now=Math.floor(Date.now()/1000);
    const r=await api('POST','/v1/governance/audit-report',{scope,period_start_unix:now-86400*30,period_end_unix:now});
    if(r.ok){setReport(r.data);setMsg(null);}else{setMsg(r.data.error||'Failed');setMsgT('error');}
  }
  function dl(){
    if(!report)return;
    const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([JSON.stringify(report,null,2)],{type:'application/json'}));
    a.download='a1-audit-'+report.scope+'-'+new Date().toISOString().slice(0,10)+'.json';a.click();
  }
  return h('div',{'data-help':'compliance',style:{width:'100%'}},
    h('div',{className:'sec-head'},h('div',{className:'sec-title'},'Compliance & Audit')),
    h(Alert,{msg,type:msgT}),
    h('div',{className:'card mb14'},
      h('p',{style:{fontSize:'var(--fsm)',color:'var(--t2)',marginBottom:13,lineHeight:'1.7'}},'Generate a compliance report covering EU AI Act Art. 13/14, NIST AI RMF Govern 1.7, SOC 2 CC6.1/CC7.2, and ISO 27001 A.9.'),
      h('div',{className:'field'},h('label',{className:'lbl'},'Organization / Scope'),h('input',{className:'inp',placeholder:'acme-corp',value:scope,onChange:e=>setScope(e.target.value)})),
      h('div',{style:{display:'flex',gap:7}},h('button',{className:'btn btn-p',onClick:generate},'Generate'),report&&h('button',{className:'btn btn-s',onClick:dl},'↓ JSON'))
    ),
    report&&h('div',{className:'card'},
      h('div',{style:{fontFamily:'var(--mono)',fontSize:'var(--fsm)',fontWeight:700,marginBottom:9}},report.title),
      h('div',{style:{fontSize:'var(--fxs)',color:'var(--t2)',fontFamily:'var(--mono)',marginBottom:12}},'Generated: '+report.generated_at),
      h('div',{className:'g2',style:{marginBottom:12}},
        h('div',null,h('div',{className:'lbl'},'Authorizations'),h('div',{style:{fontFamily:'var(--mono)',fontSize:18,fontWeight:700}},report.total_authorizations)),
        h('div',null,h('div',{className:'lbl'},'Policy Hash'),h(TruncId,{val:report.policy_commitment_hex,showFull:false}))
      ),
      h('div',{className:'lbl',style:{marginBottom:7}},'Standards'),
      h('div',{style:{display:'flex',flexWrap:'wrap',gap:5}},(report.compliance_standards||[]).map((s,i)=>h('span',{key:i,className:'badge badge-ok'},s)))
    )
  );
}

function Settings({settings,onUpdate,health,onShowOnboard}){
  const[loc,setLoc]=useState({...settings});
  const[saved,setSaved]=useState(false);
  function upd(k,v){setLoc(s=>({...s,[k]:v}));}
  function save(){onUpdate(loc);setSaved(true);setTimeout(()=>setSaved(false),1400);}
  const ENV=[
    ['A1_SIGNING_KEY_HEX','32-byte hex Ed25519 seed','Required'],
    ['A1_ADMIN_SECRET','Bearer token for admin endpoints','Production'],
    ['A1_REDIS_URL','Redis nonce + revocation store','Recommended'],
    ['A1_PG_URL','Postgres persistent audit log','Enterprise'],
    ['A1_GOVERNANCE_POLICY_FILE','Path to governance.json','Enterprise'],
    ['A1_NEGOTIATE_CAPABILITIES','Allowed caps for /negotiate (CSV)','AIP'],
    ['A1_RATE_LIMIT_RPS','Per-IP rate limit (default 500)','Optional'],
    ['GATEWAY_ADDR','Bind address (default 0.0.0.0:8080)','Optional'],
    ['RUST_LOG','Log filter (e.g. a1_gateway=info)','Optional'],
  ];

  return h('div',{'data-help':'settings',style:{width:'100%'}},
    h('div',{className:'sec-head'},h('div',{className:'sec-title'},'Settings')),

    h('div',{className:'sg'},
      h('div',{className:'sg-head'},'Mode'),
      h('div',{className:'sg-body'},
        h(ToggleRow,{
          label:'Developer Mode',
          sub:'Show advanced sections: Monitor logs, raw Passports, Swarms, DID & VC, Authorize, Compliance, and Direct Connect. Turn off to keep the sidebar clean.',
          checked:loc.developerMode,
          onChange:v=>{ upd('developerMode',v); onUpdate({...loc,developerMode:v}); }
        }),
        !loc.developerMode&&h('div',{style:{marginTop:8,padding:'8px 10px',background:'var(--s2)',border:'1px solid var(--b1)',borderRadius:'var(--r)',fontSize:'var(--fxs)',color:'var(--t2)',lineHeight:'1.6'}},
          '\u2705 Simple mode — Redis, Postgres, KMS, Swarms, and Compliance are hidden. They\'re only needed for production team deployments. Most people never need them.'
        )
      )
    ),

    h('div',{className:'sg'},
      h('div',{className:'sg-head'},'Gateway Connection'),
      h('div',{className:'sg-body'},
        h('div',{className:'field'},h('label',{className:'lbl'},'Gateway URL'),h('input',{className:'inp inp-mono',value:loc.gwUrl,onChange:e=>upd('gwUrl',e.target.value)})),
        h('div',{className:'field'},
          h('label',{className:'lbl'},'Admin Secret'),
          h('div',{style:{display:'flex',gap:6}},
            h('input',{className:'inp inp-mono',type:loc.showSecret?'text':'password',placeholder:'A1_ADMIN_SECRET',value:loc.adminSecret,onChange:e=>upd('adminSecret',e.target.value),style:{flex:1}}),
            h('button',{className:'btn btn-s btn-sm',onClick:()=>upd('showSecret',!loc.showSecret)},loc.showSecret?'Hide':'Show')
          ),
          h('p',{style:{fontSize:'var(--fxs)',color:'var(--t2)',marginTop:4,fontFamily:'var(--mono)'}},'Stored locally in this browser only.')
        ),
        health&&h('div',{style:{fontSize:'var(--fxs)',fontFamily:'var(--mono)',color:'var(--t2)',padding:'5px 0'}},h('span',{className:'dot dot-green',style:{marginRight:6}}),'Connected \u00b7 '+loc.gwUrl)
      )
    ),

    h('div',{className:'sg'},
      h('div',{className:'sg-head'},'UI Scale & Display'),
      h('div',{className:'sg-body'},
        h('div',{className:'field','data-help':'density-ctrl'},
          h('label',{className:'lbl'},'Density'),
          h('div',{style:{display:'flex',alignItems:'center',gap:6,marginBottom:6}},
            h('div',{className:'density-pills'},
              ['auto','compact','normal','comfortable'].map(d=>h('button',{key:d,className:'density-pill'+(loc.density===d?' on':''),onClick:()=>upd('density',d)},d))
            )
          ),
          h('p',{style:{fontSize:'var(--fxs)',color:'var(--t2)',fontFamily:'var(--mono)'}},'Auto adapts to your screen width automatically.')
        ),
        h('div',{className:'field','data-help':'font-ctrl'},
          h('label',{className:'lbl'},'Font Size \u2014 '+loc.fontSize+'px'),
          h('div',{className:'slider-row'},
            h('input',{type:'range',min:11,max:18,step:1,value:loc.fontSize,onChange:e=>{upd('fontSize',parseInt(e.target.value));applyScaling({...loc,fontSize:parseInt(e.target.value)});}}),
            h('span',{className:'slider-val'},loc.fontSize+'px')
          )
        ),
        h(ToggleRow,{label:'Show Full IDs',sub:'Display complete hex keys and DIDs without truncation',checked:loc.showFullIds,onChange:v=>upd('showFullIds',v)}),
        h(ToggleRow,{label:'Auto-scroll Live Log',sub:'Jump to newest log entries as they arrive',checked:loc.autoScroll,onChange:v=>upd('autoScroll',v)}),
        h('div',{className:'g2',style:{marginTop:10}},
          h('div',{className:'field'},h('label',{className:'lbl'},'Health Poll (ms)'),h('input',{className:'inp inp-mono',type:'number',min:1000,max:60000,step:1000,value:loc.pollMs,onChange:e=>upd('pollMs',parseInt(e.target.value)||4000)})),
          h('div',{className:'field'},h('label',{className:'lbl'},'Max Log Entries'),h('input',{className:'inp inp-mono',type:'number',min:50,max:2000,step:50,value:loc.logMax,onChange:e=>upd('logMax',parseInt(e.target.value)||200)}))
        )
      )
    ),

    h('div',{className:'sg'},
      h('div',{className:'sg-head'},'Help & Onboarding'),
      h('div',{className:'sg-body',style:{padding:'12px 14px'}},
        h('p',{style:{fontSize:'var(--fsm)',color:'var(--t2)',marginBottom:12,lineHeight:'1.6'}},'Use the draggable ? button (bottom-right) to toggle help mode. Hover any dashed element to see what it does.'),
        h('div',{style:{display:'flex',gap:7,flexWrap:'wrap'}},
          h('button',{className:'btn btn-s',onClick:onShowOnboard},'\u229f Re-open Getting Started Guide'),
          h('button',{className:'btn btn-s',onClick:()=>{localStorage.removeItem(LS_OB);}},'-Reset onboarding flag')
        )
      )
    ),

    h('div',{className:'sg'},
      h('div',{className:'sg-head'},'Environment Variables Reference'),
      h('div',null,
        h('table',{className:'tbl'},
          h('thead',null,h('tr',null,h('th',null,'Variable'),h('th',null,'Purpose'),h('th',null,'When'))),
          h('tbody',null,ENV.map(([k,v,w],i)=>h('tr',{key:i},
            h('td',{style:{fontFamily:'var(--mono)',fontSize:'var(--fxs)'}},k),
            h('td',{style:{color:'var(--t2)',fontSize:'var(--fsm)'}},v),
            h('td',null,h('span',{className:'badge badge-dim',style:{fontSize:9}},w))
          )))
        )
      )
    ),

    h('div',{style:{display:'flex',gap:7,marginTop:14}},
      h('button',{className:'btn btn-p',onClick:save},saved?'\u2713 Saved':'Save'),
      h('button',{className:'btn btn-d btn-sm',onClick:()=>{localStorage.removeItem(LS);setLoc({...DEFAULTS});}},'\u21ba Reset All')
    )
  );
}

function DevToolsHub({ health, logs, onClear }) {
  const DEV_TABS = [
    { id: 'overview',    label: 'Overview',    icon: '\uD83D\uDCCA' },
    { id: 'log',         label: 'Live Log',    icon: '\uD83D\uDCE1' },
    { id: 'passports',   label: 'Passports',   icon: '\uD83D\uDEC2' },
    { id: 'swarms',      label: 'Swarms',      icon: '\uD83D\uDC1D' },
    { id: 'did',         label: 'DID & VC',    icon: '\uD83E\uDEAA' },
    { id: 'authorize',   label: 'Authorize',   icon: '\uD83D\uDD12' },
    { id: 'compliance',  label: 'Compliance',  icon: '\uD83D\uDCCB' },
    { id: 'enterprise',  label: 'Enterprise',  icon: '\uD83C\uDFE2' },
  ];

  const [sub, setSub] = React.useState('overview');

  const CONTENT = {
    overview:   h(Overview,        { health, logs }),
    log:        h(LiveLogs,        { logs, onClear }),
    passports:  h(Passports,       null),
    swarms:     h(Swarms,          null),
    did:        h(DidVc,           null),
    authorize:  h(Authorize,       null),
    compliance: h(Compliance,      null),
    enterprise: h(EnterprisePanel, null),
  };

  return h('div', null,
    h('div', {
      style: {
        display: 'flex', gap: 4, marginBottom: 16, flexWrap: 'wrap',
        borderBottom: '1px solid var(--b2)', paddingBottom: 10,
      }
    },
      DEV_TABS.map(t =>
        h('button', {
          key: t.id,
          className: 'btn btn-sm ' + (sub === t.id ? 'btn-p' : 'btn-s'),
          onClick: () => setSub(t.id),
        }, t.icon + '\u2009' + t.label)
      )
    ),
    CONTENT[sub]
  );
}