Skip to content

Commit b370d7a

Browse files
sagitta414claude
andcommitted
Contest compliance: wire real Foundry IQ grounding + correct overclaims + runbook · v0.31.0
Summon -> /api/ground (Azure AI Search grounded retrieval) shown as council cited evidence with a grounded badge; footer no longer overclaims (built with Copilot / Foundry IQ + Fabric IQ) and now states the real stack; README required-criteria checklist + Copilot/Foundry-IQ diagram; SETUP-IQ.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2e89fb4 commit b370d7a

5 files changed

Lines changed: 93 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## [0.31.0] - 2026-06-13 — Contest-compliance: real Foundry IQ grounding + precise claims
4+
5+
- **Real Foundry IQ integration, wired in.** Summon now calls /api/ground (permission-aware, cited
6+
grounded retrieval over Azure AI Search) and surfaces the citations in the council’s Foundry IQ
7+
evidence with a ● Grounded via Foundry IQ badge. Falls back to baked evidence until provisioned —
8+
see SETUP-IQ.md to activate (~10 min).
9+
- **Corrected overclaims** to be precise/defensible: the in-game footer no longer says built with
10+
Copilot / Foundry IQ + Fabric IQ; it now states the actual stack (Foundry IQ grounded retrieval ·
11+
MCP · Azure AI Foundry · synthetic data). README gains a Required-criteria checklist (Foundry IQ
12+
real; Fabric IQ noted as thematic; a truthful GitHub Copilot placeholder) and a Copilot/Foundry-IQ
13+
architecture diagram. Added SETUP-IQ.md provisioning runbook.
14+
315
## [0.30.0] - 2026-06-13 — Interactive architecture map + game-screen rail fix
416

517
- **Architecture map is now interactive.** Tap any node to open a detail popover explaining what it

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,37 @@ each call function tools over an identity store, investigate independently, *deb
2424

2525
```mermaid
2626
flowchart LR
27+
Dev[GitHub Copilot<br/>in VS Code] -. builds / drives .-> G
2728
G[Game UI<br/>encounter + combat] -->|account| API[/api/reason<br/>Azure Function/]
2829
subgraph Council [Multi-agent council]
2930
W[🛡 Warden agent] -->|debate| S[🔍 Skeptic agent] --> C[✓ Council agent]
3031
end
3132
API --> Council
3233
Council -->|tool calls| T[(Identity tools<br/>signin · deps · oauth · groups)]
3334
T -->|cited results| Council
35+
API -->|grounded query| FIQ[Foundry IQ<br/>Azure AI Search<br/>grounded retrieval]
36+
FIQ -->|cited evidence| C
3437
Council -->|warden · skeptic · verdict<br/>+ citations + trace| G
35-
T -. also exposed over .-> MCP[[MCP server<br/>Copilot / VS Code / Foundry]]
38+
T -. also exposed over .-> MCP[[MCP server<br/>GitHub Copilot / VS Code / Foundry]]
3639
API -. optional .-> F[Azure AI Foundry<br/>connected agents]
3740
```
3841

42+
### ✅ Required-criteria checklist (Microsoft Agents League — Creative Apps)
43+
- **Microsoft IQ integration → Foundry IQ.** `/api/ground` performs real permission-aware, **cited
44+
grounded retrieval over Azure AI Search** (Foundry IQ); the game surfaces those citations as the
45+
council's *Foundry IQ · cited evidence* with a "● Grounded via Foundry IQ" badge. Activate by
46+
provisioning a Search index + setting `FOUNDRY_SEARCH_*` (see [`SETUP-IQ.md`](SETUP-IQ.md)); falls
47+
back to baked evidence when unconfigured. *(The in-game "Fabric IQ" label is a thematic nod to data
48+
lineage, not a Fabric integration — the real IQ layer here is Foundry IQ.)*
49+
- **GitHub Copilot** *(required — fill this in truthfully before submitting):* document your **actual**
50+
GitHub Copilot usage — which code Copilot helped write, Copilot Chat sessions for debugging/
51+
explanation, and ideally a short clip of Copilot in VS Code. A concrete, verifiable hook: the same
52+
identity tools are exposed over **MCP**, so you can connect this MCP server to **GitHub Copilot in
53+
VS Code / Copilot CLI** and drive the agents' tools from a Copilot chat — record that. *(Do not
54+
claim Copilot usage you didn't do.)*
55+
- **Creative application.** A playable, cinematic security-training game — see the live URL + demo.
56+
- **Architecture diagram.** Above.
57+
3958
**Three tiers, each falling back safely:**
4059

4160
| Tier | What runs | Activate |

SETUP-IQ.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Activate Foundry IQ (real grounded retrieval) — ~10 min
2+
3+
The game already ships a real **Foundry IQ** integration: [`api/ground/index.js`](api/ground/index.js)
4+
does permission-aware, **cited grounded retrieval over Azure AI Search**. Until you provision a
5+
search index it returns `{grounded:false}` and the game falls back to baked evidence. These steps
6+
make it live — and the council's *Foundry IQ · cited evidence* then shows a **"● Grounded via
7+
Foundry IQ"** badge with real citations.
8+
9+
## 1. Create an Azure AI Search service (free tier is fine)
10+
```powershell
11+
az search service create -n afterlogin-search -g rg-afterlogin --sku free -l eastus
12+
```
13+
14+
## 2. Create the knowledge index + load a few control docs
15+
- Portal → your Search service → **Indexes → Add index** named `afterlogin-knowledge` with fields:
16+
`id` (key, string), `title` (string, searchable), `content` (string, searchable),
17+
`source` (string, retrievable).
18+
- Add a handful of documents (JSON) describing the real controls the game teaches — e.g.:
19+
```json
20+
[
21+
{"id":"1","title":"Revoke sessions vs. password reset","content":"A stolen session/primary refresh token survives a password reset. Use Revoke Sessions / Continuous Access Evaluation to kill the live session.","source":"Entra ID — sign-in & session management"},
22+
{"id":"2","title":"Illicit OAuth consent","content":"Revoking sessions does not remove an app's standing OAuth grant. Remove the enterprise-app consent to cut the app's access.","source":"Entra ID — enterprise apps / OAuth consent"},
23+
{"id":"3","title":"Standing privilege","content":"MFA does not strip standing Global Admin. Remove the role / use PIM eligible assignments and rotate secrets.","source":"Entra ID Governance — PIM"},
24+
{"id":"4","title":"Load-bearing service accounts","content":"Don't delete a dormant-looking service account with live dependencies — bind & watch (Conditional Access + monitoring) instead.","source":"Identity Governance — lifecycle"}
25+
]
26+
```
27+
*(Optional: add a semantic configuration named e.g. `afterlogin-sem` for extractive answers.)*
28+
29+
## 3. Point the game's API at it (SWA app settings)
30+
```powershell
31+
az staticwebapp appsettings set -n afterlogin -g rg-afterlogin --subscription 3226d5fa-52e1-40c0-9c74-67e984356692 --setting-names FOUNDRY_SEARCH_ENDPOINT=https://afterlogin-search.search.windows.net FOUNDRY_SEARCH_KEY=YOURQUERYKEY FOUNDRY_SEARCH_INDEX=afterlogin-knowledge
32+
```
33+
*(Get the query key: Portal → Search service → Keys. Use a **query** key, not admin. Optionally add `FOUNDRY_SEMANTIC_CONFIG=afterlogin-sem`.)*
34+
35+
## 4. Verify
36+
```bash
37+
curl "https://victorious-plant-0c1e7790f.7.azurestaticapps.net/api/ground?q=stolen%20session%20token%20remediation"
38+
# expect: {"grounded":true,"citations":[...]}
39+
```
40+
Then in-game: **Summon** any spirit → the *Foundry IQ · cited evidence* block shows the green
41+
**● Grounded via Foundry IQ · Azure AI Search** badge with live citations. That's a real,
42+
demonstrable Microsoft IQ integration for the submission.
43+
44+
> Same pattern, in tenant: this is the Foundry IQ grounding layer (Azure AI Search) — swap the index
45+
> for your own knowledge sources and the council grounds its evidence in them.

encounter.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@
121121
.fg svg{width:100%;height:auto;overflow:visible}.fg .flowdash{animation:fdash 1s linear infinite}@keyframes fdash{to{stroke-dashoffset:-22}}
122122
.ev{font-size:11.5px;color:var(--ink);line-height:1.45;padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)}
123123
.ev b{color:#fff}
124+
.grbadge{font-size:8px;font-weight:700;letter-spacing:1px;text-transform:uppercase;color:#7dffb0;background:rgba(45,210,150,.12);border:1px solid rgba(45,210,150,.34);border-radius:6px;padding:3px 8px;display:inline-block;margin:2px 0 8px}
125+
.ev.gr{border-left:2px solid rgba(45,210,150,.55);padding-left:9px;margin-bottom:4px}
126+
.ev.gr .grsrc{display:block;font-family:ui-monospace,Menlo,monospace;font-size:9px;color:var(--mut);margin-top:3px}
124127
.actbar{display:flex;gap:4px}
125128
.ab{cursor:pointer;font-size:10.5px;letter-spacing:1px;text-transform:uppercase;color:var(--mut);padding:6px 12px;border-radius:16px;border:1px solid transparent;transition:.18s}
126129
.ab.on{color:var(--ink);border-color:var(--stroke);background:var(--glass)}
@@ -1325,7 +1328,7 @@
13251328
<div class="career" id="career"></div>
13261329
<div class="dockrow"><span class="dlink" onclick="openCodex()">📖 Behind the Game</span><span class="dsep">·</span><span class="dlink" onclick="openArch()">🗺 Live architecture</span><span class="dsep">·</span><span class="dlink" onclick="startShowcase()">▶ 90-second showcase</span></div>
13271330
</div>
1328-
<div class="ifoot2">Synthetic data · Foundry IQ + Fabric IQ · built with Copilot</div>
1331+
<div class="ifoot2">Synthetic data · no PII · Foundry IQ grounded retrieval · MCP · Azure AI Foundry</div>
13291332
</div>
13301333

13311334
<script>
@@ -1481,7 +1484,7 @@
14811484
h+='</svg>'+(!sm?'<div style="color:#b7a9e8;font-size:11px;margin-top:4px">'+N+' binding'+(N>1?'s':'')+' mapped — status <b>unverified</b>. <b>Summon</b> to reveal which are live.</div>':(liveN?'<div style="color:#ff3b5c;font-size:11px;font-weight:600;margin-top:4px">⚠ '+liveN+' live, load-bearing binding'+(liveN>1?'s':'')+' — do not delete</div>':'<div style="color:'+GC[s.grade]+';font-size:11px;margin-top:4px">verified: no live bindings — safe to retire</div>'));}
14821485
else h+='<div class="locknote">⊘ Divine to map what depends on it.</div>';
14831486
h+='</div><div class="rev"><div class="rh">🔎 <b>Foundry IQ</b> · cited evidence</div>';
1484-
if(st.summoned)h+=(s.ev||[]).map(e=>'<div class="ev">'+e+'</div>').join('');
1487+
if(st.summoned){if(st.ground&&st.ground.grounded&&st.ground.citations&&st.ground.citations.length){h+='<div class="grbadge">● Grounded via Foundry IQ · Azure AI Search</div>'+st.ground.citations.map(c=>'<div class="ev gr"><b>'+(c.title||'source')+'</b> '+((c.snippet||'').replace(/[<>]/g,''))+'<span class="grsrc">'+(c.source||'')+(c.score!=null?' · score '+(Math.round(c.score*100)/100):'')+'</span></div>').join('');}h+=(s.ev||[]).map(e=>'<div class="ev">'+e+'</div>').join('');}
14851488
else h+='<div class="locknote">⊘ <b>Summon</b> (costs <b>'+summonCost()+'</b> 🔮) for the decisive evidence — it verifies the live bindings, unlocks the Skeptic’s full read, and reveals the council’s confidence.</div>';
14861489
h+='</div>';
14871490
if(st.divined)h+='<div class="council"><div class="ch">◈ Agent Council deliberates</div><div class="ag'+(st.councilDone?' on':'')+'" id="ag0"><span class="agav">🛡</span><span class="agtx"><b>Warden</b> <span class="sp"></span></span></div><div class="ag'+(st.councilDone?' on':'')+'" id="ag1"><span class="agav">🔍</span><span class="agtx"><b>Skeptic</b> <span class="sp"></span></span></div><div class="ag'+(st.councilDone?' on':'')+'" id="ag2"><span class="agav">✓</span><span class="agtx"><b>Council</b> <span class="sp"></span></span></div></div><div class="vyours">⚖ The verdict is <b>yours</b> — the agents advise and flag their own doubts; weigh the evidence and decide.</div>';
@@ -1505,7 +1508,8 @@
15051508
updateProg();}
15061509

15071510
function divine(){if(!cur)return;const st=ST[cur];if(st.divined)return;st.divined=true;document.getElementById('invD').classList.add('on');renderDossier(cur);if(!st.councilDone)runCouncil(cur);tutAdvance('divine');}
1508-
function summon(){if(!cur)return;const st=ST[cur];if(st.summoned)return;const c=tutActive?0:summonCost();if(essence<c){sfx('tick');toast('Not enough essence to Summon (need '+c+' 🔮) — judge on what you have, or earn more by laying spirits to rest.');return;}if(c){essence-=c;essSpent+=c;updEss();}st.summoned=true;renderDossier(cur);const iS=document.getElementById('invS');iS.classList.add('on');iS.innerHTML='◑ Summoned ✓ <span class="iqb">Foundry IQ</span>';runCouncil(cur);toast('🔎 <b>Foundry IQ</b> — evidence in. The live bindings are verified and the council reaches its full read.');tutAdvance('summon');}
1511+
function fetchGround(a){try{const s=SOULS[a];if(!s)return;const q=encodeURIComponent((s.label||a)+' '+(s.kind||'account')+' '+(s.lb?'load-bearing live dependencies':'stale unused access')+' remediation control');fetch('/api/ground?q='+q).then(r=>r.ok?r.json():null).then(d=>{if(d&&d.grounded&&d.citations&&d.citations.length){ST[a].ground=d;if(cur===a&&ST[a].summoned)renderDossier(a);}}).catch(()=>{});}catch(e){}}
1512+
function summon(){if(!cur)return;const st=ST[cur];if(st.summoned)return;const c=tutActive?0:summonCost();if(essence<c){sfx('tick');toast('Not enough essence to Summon (need '+c+' 🔮) — judge on what you have, or earn more by laying spirits to rest.');return;}if(c){essence-=c;essSpent+=c;updEss();}st.summoned=true;fetchGround(cur);renderDossier(cur);const iS=document.getElementById('invS');iS.classList.add('on');iS.innerHTML='◑ Summoned ✓ <span class="iqb">Foundry IQ</span>';runCouncil(cur);toast('🔎 <b>Foundry IQ</b> — evidence in. The live bindings are verified and the council reaches its full read.');tutAdvance('summon');}
15091513
async function runCouncil(a){if(!document.getElementById('ag0'))return;const c=ai(a),sm=ST[a].summoned,showC=DIFF[diff].conf&&!modis('fog');
15101514
let w=c.w,sk=sm?c.s:'I can read the surface, but I cannot confirm it without the cited evidence — Summon to be certain.',liveAI=false,agData=null;
15111515
if(sm){const ch0=document.querySelector('#dossier .council .ch');if(ch0)ch0.innerHTML='◈ Agent Council · <span style="color:rgb(var(--acc))">🔧 agents investigating…</span>';const got=await fetchCouncil(a).catch(()=>null);if(cur===a&&got){w=got.w;sk=got.s;liveAI=true;agData=got;}}
@@ -2023,7 +2027,7 @@
20232027
+rowsSvg
20242028
+'<rect x="150" y="650" width="300" height="60" rx="11" fill="rgba('+acc+',.14)" stroke="rgb('+acc+')"/>'
20252029
+'<text x="300" y="688" text-anchor="middle" fill="rgb('+acc+')" font-size="24" font-weight="bold" font-family="Georgia,serif">'+verdict+'</text>'
2026-
+'<text x="300" y="754" text-anchor="middle" fill="#8a83a8" font-size="12" letter-spacing="1.5" font-family="Georgia,serif">Synthetic data · Foundry IQ + Fabric IQ + Copilot</text>'
2030+
+'<text x="300" y="754" text-anchor="middle" fill="#8a83a8" font-size="12" letter-spacing="1.5" font-family="Georgia,serif">Synthetic data · no PII · Foundry IQ grounded retrieval · MCP</text>'
20272031
+'</svg>';}
20282032
function saveReport(){try{const svg=reportSVG();const blob=new Blob([svg],{type:'image/svg+xml;charset=utf-8'});const url=URL.createObjectURL(blob);const img=new Image();
20292033
img.onload=function(){try{const c=document.createElement('canvas');c.width=600;c.height=800;c.getContext('2d').drawImage(img,0,0);URL.revokeObjectURL(url);c.toBlob(function(b){const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='afterlogin-audit-report.png';document.body.appendChild(a);a.click();a.remove();},'image/png');}catch(e){const a=document.createElement('a');a.href=url;a.download='afterlogin-audit-report.svg';a.click();}};

0 commit comments

Comments
 (0)