@shield-agent/kya 0.32.9 → 0.33.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.cursor-plugin/plugin.json +1 -1
  2. package/.mcp.json +1 -1
  3. package/LIMITATIONS.md +2 -2
  4. package/README.md +178 -12
  5. package/amp/settings.example.json +1 -1
  6. package/claude/claude_desktop_config.example.json +1 -1
  7. package/cline/cline_mcp_settings.example.json +1 -1
  8. package/copilot/mcp-config.example.json +1 -1
  9. package/data/iocs.json +17 -0
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +149 -16
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands/connect.d.ts +1 -1
  15. package/dist/commands/connect.d.ts.map +1 -1
  16. package/dist/commands/connect.js +11 -3
  17. package/dist/commands/connect.js.map +1 -1
  18. package/dist/commands/dash.d.ts.map +1 -1
  19. package/dist/commands/dash.js +25 -1
  20. package/dist/commands/dash.js.map +1 -1
  21. package/dist/commands/doctor.d.ts +8 -0
  22. package/dist/commands/doctor.d.ts.map +1 -0
  23. package/dist/commands/doctor.js +26 -0
  24. package/dist/commands/doctor.js.map +1 -0
  25. package/dist/commands/eval-tool.d.ts +3 -0
  26. package/dist/commands/eval-tool.d.ts.map +1 -1
  27. package/dist/commands/eval-tool.js +2 -1
  28. package/dist/commands/eval-tool.js.map +1 -1
  29. package/dist/commands/export.d.ts +33 -0
  30. package/dist/commands/export.d.ts.map +1 -0
  31. package/dist/commands/export.js +121 -0
  32. package/dist/commands/export.js.map +1 -0
  33. package/dist/commands/hook.d.ts.map +1 -1
  34. package/dist/commands/hook.js +120 -26
  35. package/dist/commands/hook.js.map +1 -1
  36. package/dist/commands/hosted-hook.d.ts +8 -1
  37. package/dist/commands/hosted-hook.d.ts.map +1 -1
  38. package/dist/commands/hosted-hook.js +15 -5
  39. package/dist/commands/hosted-hook.js.map +1 -1
  40. package/dist/commands/map.d.ts +21 -0
  41. package/dist/commands/map.d.ts.map +1 -0
  42. package/dist/commands/map.js +76 -0
  43. package/dist/commands/map.js.map +1 -0
  44. package/dist/commands/notify-flush.d.ts.map +1 -1
  45. package/dist/commands/notify-flush.js +7 -1
  46. package/dist/commands/notify-flush.js.map +1 -1
  47. package/dist/commands/posture.d.ts +21 -0
  48. package/dist/commands/posture.d.ts.map +1 -0
  49. package/dist/commands/posture.js +177 -0
  50. package/dist/commands/posture.js.map +1 -0
  51. package/dist/commands/receipt-serve.d.ts +7 -0
  52. package/dist/commands/receipt-serve.d.ts.map +1 -1
  53. package/dist/commands/receipt-serve.js +50 -0
  54. package/dist/commands/receipt-serve.js.map +1 -1
  55. package/dist/commands/start.d.ts.map +1 -1
  56. package/dist/commands/start.js +5 -1
  57. package/dist/commands/start.js.map +1 -1
  58. package/dist/commands/team.d.ts.map +1 -1
  59. package/dist/commands/team.js +30 -11
  60. package/dist/commands/team.js.map +1 -1
  61. package/dist/commands/wire-hooks.d.ts +5 -1
  62. package/dist/commands/wire-hooks.d.ts.map +1 -1
  63. package/dist/commands/wire-hooks.js +23 -8
  64. package/dist/commands/wire-hooks.js.map +1 -1
  65. package/dist/commands/wrap.d.ts +38 -1
  66. package/dist/commands/wrap.d.ts.map +1 -1
  67. package/dist/commands/wrap.js +82 -6
  68. package/dist/commands/wrap.js.map +1 -1
  69. package/dist/daily-slot.d.ts +2 -0
  70. package/dist/daily-slot.d.ts.map +1 -0
  71. package/dist/daily-slot.js +29 -0
  72. package/dist/daily-slot.js.map +1 -0
  73. package/dist/dash/dash.d.ts.map +1 -1
  74. package/dist/dash/dash.js +10 -0
  75. package/dist/dash/dash.js.map +1 -1
  76. package/dist/dash/entitlement.d.ts +1 -1
  77. package/dist/dash/entitlement.d.ts.map +1 -1
  78. package/dist/dash/entitlement.js +1 -0
  79. package/dist/dash/entitlement.js.map +1 -1
  80. package/dist/dash/input.d.ts +1 -1
  81. package/dist/dash/input.d.ts.map +1 -1
  82. package/dist/dash/input.js +3 -0
  83. package/dist/dash/input.js.map +1 -1
  84. package/dist/dash/render.js +1 -1
  85. package/dist/dash/render.js.map +1 -1
  86. package/dist/dash/screens/free.js +1 -1
  87. package/dist/dash/screens/free.js.map +1 -1
  88. package/dist/device-id.d.ts +5 -0
  89. package/dist/device-id.d.ts.map +1 -0
  90. package/dist/device-id.js +78 -0
  91. package/dist/device-id.js.map +1 -0
  92. package/dist/fs-atomic.d.ts +9 -3
  93. package/dist/fs-atomic.d.ts.map +1 -1
  94. package/dist/fs-atomic.js +17 -11
  95. package/dist/fs-atomic.js.map +1 -1
  96. package/dist/gate/config-gen.d.ts +26 -2
  97. package/dist/gate/config-gen.d.ts.map +1 -1
  98. package/dist/gate/config-gen.js +53 -4
  99. package/dist/gate/config-gen.js.map +1 -1
  100. package/dist/gate/otlp-receiver.d.ts.map +1 -1
  101. package/dist/gate/otlp-receiver.js +3 -1
  102. package/dist/gate/otlp-receiver.js.map +1 -1
  103. package/dist/guard-pack.d.ts +58 -0
  104. package/dist/guard-pack.d.ts.map +1 -0
  105. package/dist/guard-pack.js +753 -0
  106. package/dist/guard-pack.js.map +1 -0
  107. package/dist/ioc/exec.d.ts +19 -0
  108. package/dist/ioc/exec.d.ts.map +1 -0
  109. package/dist/ioc/exec.js +49 -0
  110. package/dist/ioc/exec.js.map +1 -0
  111. package/dist/ioc/format.d.ts +4 -0
  112. package/dist/ioc/format.d.ts.map +1 -0
  113. package/dist/ioc/format.js +23 -0
  114. package/dist/ioc/format.js.map +1 -0
  115. package/dist/ioc/list.d.ts +29 -0
  116. package/dist/ioc/list.d.ts.map +1 -0
  117. package/dist/ioc/list.js +25 -0
  118. package/dist/ioc/list.js.map +1 -0
  119. package/dist/ioc/result.d.ts +18 -0
  120. package/dist/ioc/result.d.ts.map +1 -0
  121. package/dist/ioc/result.js +26 -0
  122. package/dist/ioc/result.js.map +1 -0
  123. package/dist/ioc/scan.d.ts +21 -0
  124. package/dist/ioc/scan.d.ts.map +1 -0
  125. package/dist/ioc/scan.js +247 -0
  126. package/dist/ioc/scan.js.map +1 -0
  127. package/dist/ioc/spawn.d.ts +26 -0
  128. package/dist/ioc/spawn.d.ts.map +1 -0
  129. package/dist/ioc/spawn.js +52 -0
  130. package/dist/ioc/spawn.js.map +1 -0
  131. package/dist/map/build.d.ts +58 -0
  132. package/dist/map/build.d.ts.map +1 -0
  133. package/dist/map/build.js +439 -0
  134. package/dist/map/build.js.map +1 -0
  135. package/dist/map/heat.d.ts +44 -0
  136. package/dist/map/heat.d.ts.map +1 -0
  137. package/dist/map/heat.js +96 -0
  138. package/dist/map/heat.js.map +1 -0
  139. package/dist/map/load.d.ts +38 -0
  140. package/dist/map/load.d.ts.map +1 -0
  141. package/dist/map/load.js +38 -0
  142. package/dist/map/load.js.map +1 -0
  143. package/dist/map/page.d.ts +90 -0
  144. package/dist/map/page.d.ts.map +1 -0
  145. package/dist/map/page.js +374 -0
  146. package/dist/map/page.js.map +1 -0
  147. package/dist/map/target-class.d.ts +22 -0
  148. package/dist/map/target-class.d.ts.map +1 -0
  149. package/dist/map/target-class.js +151 -0
  150. package/dist/map/target-class.js.map +1 -0
  151. package/dist/map/types.d.ts +102 -0
  152. package/dist/map/types.d.ts.map +1 -0
  153. package/dist/map/types.js +3 -0
  154. package/dist/map/types.js.map +1 -0
  155. package/dist/mcp/protocol.d.ts.map +1 -1
  156. package/dist/mcp/protocol.js +16 -5
  157. package/dist/mcp/protocol.js.map +1 -1
  158. package/dist/never-code.d.ts +7 -0
  159. package/dist/never-code.d.ts.map +1 -0
  160. package/dist/never-code.js +9 -0
  161. package/dist/never-code.js.map +1 -0
  162. package/dist/never-list-input.d.ts +48 -12
  163. package/dist/never-list-input.d.ts.map +1 -1
  164. package/dist/never-list-input.js +696 -29
  165. package/dist/never-list-input.js.map +1 -1
  166. package/dist/notify/dispatch.d.ts +4 -0
  167. package/dist/notify/dispatch.d.ts.map +1 -1
  168. package/dist/notify/dispatch.js +3 -1
  169. package/dist/notify/dispatch.js.map +1 -1
  170. package/dist/offline-evaluate.d.ts +26 -1
  171. package/dist/offline-evaluate.d.ts.map +1 -1
  172. package/dist/offline-evaluate.js +33 -4
  173. package/dist/offline-evaluate.js.map +1 -1
  174. package/dist/otel/exporter.d.ts +15 -2
  175. package/dist/otel/exporter.d.ts.map +1 -1
  176. package/dist/otel/exporter.js +52 -6
  177. package/dist/otel/exporter.js.map +1 -1
  178. package/dist/otel/map-attrs-optin.d.ts +39 -0
  179. package/dist/otel/map-attrs-optin.d.ts.map +1 -0
  180. package/dist/otel/map-attrs-optin.js +109 -0
  181. package/dist/otel/map-attrs-optin.js.map +1 -0
  182. package/dist/parse-args.d.ts +2 -0
  183. package/dist/parse-args.d.ts.map +1 -1
  184. package/dist/parse-args.js +4 -2
  185. package/dist/parse-args.js.map +1 -1
  186. package/dist/posture/card.d.ts +26 -0
  187. package/dist/posture/card.d.ts.map +1 -0
  188. package/dist/posture/card.js +46 -0
  189. package/dist/posture/card.js.map +1 -0
  190. package/dist/posture/collect.d.ts +19 -0
  191. package/dist/posture/collect.d.ts.map +1 -0
  192. package/dist/posture/collect.js +182 -0
  193. package/dist/posture/collect.js.map +1 -0
  194. package/dist/posture/send.d.ts +24 -0
  195. package/dist/posture/send.d.ts.map +1 -0
  196. package/dist/posture/send.js +162 -0
  197. package/dist/posture/send.js.map +1 -0
  198. package/dist/posture/spawn.d.ts +20 -0
  199. package/dist/posture/spawn.d.ts.map +1 -0
  200. package/dist/posture/spawn.js +33 -0
  201. package/dist/posture/spawn.js.map +1 -0
  202. package/dist/posture/store.d.ts +34 -0
  203. package/dist/posture/store.d.ts.map +1 -0
  204. package/dist/posture/store.js +107 -0
  205. package/dist/posture/store.js.map +1 -0
  206. package/dist/posture/validate.d.ts +33 -0
  207. package/dist/posture/validate.d.ts.map +1 -0
  208. package/dist/posture/validate.js +232 -0
  209. package/dist/posture/validate.js.map +1 -0
  210. package/dist/project-policy.d.ts +23 -0
  211. package/dist/project-policy.d.ts.map +1 -0
  212. package/dist/project-policy.js +95 -0
  213. package/dist/project-policy.js.map +1 -0
  214. package/dist/receipt/daemon.d.ts +9 -9
  215. package/dist/receipt/daemon.d.ts.map +1 -1
  216. package/dist/receipt/daemon.js +26 -11
  217. package/dist/receipt/daemon.js.map +1 -1
  218. package/dist/receipt/embeddings.d.ts +28 -10
  219. package/dist/receipt/embeddings.d.ts.map +1 -1
  220. package/dist/receipt/embeddings.js +221 -35
  221. package/dist/receipt/embeddings.js.map +1 -1
  222. package/dist/receipt/gate-page.d.ts.map +1 -1
  223. package/dist/receipt/gate-page.js +5 -22
  224. package/dist/receipt/gate-page.js.map +1 -1
  225. package/dist/receipt/live-server.d.ts +8 -0
  226. package/dist/receipt/live-server.d.ts.map +1 -1
  227. package/dist/receipt/live-server.js +136 -4
  228. package/dist/receipt/live-server.js.map +1 -1
  229. package/dist/receipt/render-receipt.d.ts +7 -1
  230. package/dist/receipt/render-receipt.d.ts.map +1 -1
  231. package/dist/receipt/render-receipt.js +118 -13
  232. package/dist/receipt/render-receipt.js.map +1 -1
  233. package/dist/receipt/search.d.ts +5 -0
  234. package/dist/receipt/search.d.ts.map +1 -1
  235. package/dist/receipt/search.js +23 -2
  236. package/dist/receipt/search.js.map +1 -1
  237. package/dist/receipt/semantic-setup.d.ts +21 -0
  238. package/dist/receipt/semantic-setup.d.ts.map +1 -0
  239. package/dist/receipt/semantic-setup.js +111 -0
  240. package/dist/receipt/semantic-setup.js.map +1 -0
  241. package/dist/receipt/semantic-status.d.ts +7 -0
  242. package/dist/receipt/semantic-status.d.ts.map +1 -0
  243. package/dist/receipt/semantic-status.js +26 -0
  244. package/dist/receipt/semantic-status.js.map +1 -0
  245. package/dist/sdk/govern.d.ts.map +1 -1
  246. package/dist/sdk/govern.js +7 -3
  247. package/dist/sdk/govern.js.map +1 -1
  248. package/dist/telemetry.d.ts.map +1 -1
  249. package/dist/telemetry.js +3 -23
  250. package/dist/telemetry.js.map +1 -1
  251. package/dist/trail-map-fields.d.ts +10 -0
  252. package/dist/trail-map-fields.d.ts.map +1 -0
  253. package/dist/trail-map-fields.js +53 -0
  254. package/dist/trail-map-fields.js.map +1 -0
  255. package/dist/trail-summary.d.ts.map +1 -1
  256. package/dist/trail-summary.js +11 -3
  257. package/dist/trail-summary.js.map +1 -1
  258. package/dist/trail.d.ts +9 -1
  259. package/dist/trail.d.ts.map +1 -1
  260. package/dist/trail.js +24 -1
  261. package/dist/trail.js.map +1 -1
  262. package/droid/README.md +1 -1
  263. package/droid/mcp.example.json +1 -1
  264. package/gemini/settings.example.json +1 -1
  265. package/grok/README.md +1 -1
  266. package/kilo/kilo.example.json +1 -1
  267. package/kimi/mcp.example.json +1 -1
  268. package/kiro/mcp.example.json +1 -1
  269. package/manifest.json +1 -1
  270. package/mastracode/mcp.example.json +1 -1
  271. package/mcp.json +1 -1
  272. package/openai/codex.config.example.toml +1 -1
  273. package/opencode/opencode.example.json +1 -1
  274. package/package.json +8 -4
  275. package/qwen/settings.example.json +1 -1
  276. package/server.json +2 -2
  277. package/vendor/cytoscape/LICENSE +19 -0
  278. package/vendor/cytoscape/README.md +7 -0
  279. package/vendor/cytoscape/cytoscape.min.js +31 -0
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Activity feed HTML/MD for local trail events.
3
3
  */
4
+ import { liveMapScripts, MAP_CSS, MAP_LIVE_ZONE_CSS } from "../map/page.js";
5
+ import { semanticInstallCommand, semanticRuntimeInstalled } from "./semantic-status.js";
4
6
  import { assertNoSecrets, clip, stripEscapes } from "../dash/render.js";
5
7
  import { clipMultiline, DIFF_MAX_TOTAL_CHARS } from "../diff-preview.js";
6
8
  import { DOMAIN_LABELS, domainOrder } from "../certify/render.js";
7
- import { productLabel, readTrail, readTrailSince } from "../trail.js";
9
+ import { productLabel, eventsSince, readTrail } from "../trail.js";
8
10
  import { mcpServerLabel, parseMcpToolId } from "../mcp-servers.js";
9
11
  import { loadInvestigateLastRun, } from "../investigate/last-run.js";
10
12
  import { readNotifyLog, summarizeNotifyLog, } from "../notify/log.js";
@@ -18,6 +20,8 @@ import { GATE_NOT_SETUP_QUICKSTART, loadGatePage, toolNameFromToolId, toolServer
18
20
  import { receiptCss } from "./receipt-css.js";
19
21
  import { applyChipFilters, rankEvents } from "./search.js";
20
22
  import { emptyOpenshellCard, loadOpenshellCard } from "./openshell-card.js";
23
+ import { readIocResult } from "../ioc/result.js";
24
+ import { postureCard } from "../posture/card.js";
21
25
  export const PAGE_SIZE = 50;
22
26
  export function clampPage(page, total) {
23
27
  if (total === 0)
@@ -282,6 +286,8 @@ export function buildReceiptModel(sessionId, events, extras) {
282
286
  investigate: extras?.investigate,
283
287
  alerts: extras?.alerts,
284
288
  otel: extras?.otel,
289
+ ioc: extras?.ioc,
290
+ posture: extras?.posture,
285
291
  };
286
292
  }
287
293
  export function buildWindowReceiptModel(events, days, extras) {
@@ -310,6 +316,8 @@ export function buildWindowReceiptModel(events, days, extras) {
310
316
  investigate: extras?.investigate,
311
317
  alerts: extras?.alerts,
312
318
  otel: extras?.otel,
319
+ ioc: extras?.ioc,
320
+ posture: extras?.posture,
313
321
  };
314
322
  }
315
323
  /** Shared loader for static artifacts and the live loopback server. */
@@ -317,13 +325,13 @@ export function loadReceiptModel(input) {
317
325
  const days = input.days > 0 ? Math.floor(input.days) : 3;
318
326
  const env = input.env ?? process.env;
319
327
  const showback = loadShowbackCard(input.cwd);
320
- const events = input.sessionId
321
- ? readTrail(input.cwd, env)
322
- : readTrailSince(input.cwd, new Date(Date.now() - days * 24 * 60 * 60 * 1000), env);
323
- // The Import card aggregates the full trail, not just the window: imported
324
- // spans keep their foreign timestamps and would fall out of a short window.
328
+ // One trail read per model. The Import card aggregates the full trail, not just the window:
329
+ // imported spans keep their foreign timestamps and would fall out of a short window.
325
330
  // The OpenShell card rolls up the same full-trail view for the same reason.
326
- const importEvents = input.sessionId ? events : readTrail(input.cwd, env);
331
+ const importEvents = readTrail(input.cwd, env);
332
+ const events = input.sessionId
333
+ ? importEvents
334
+ : eventsSince(importEvents, new Date(Date.now() - days * 24 * 60 * 60 * 1000));
327
335
  const certify = loadCertifyCard(input.cwd);
328
336
  const extras = {
329
337
  page: input.page ?? 1,
@@ -343,6 +351,8 @@ export function loadReceiptModel(input) {
343
351
  investigate: loadInvestigateLastRun(env),
344
352
  alerts: summarizeNotifyLog(readNotifyLog(env)),
345
353
  otel: readOtelStats(env),
354
+ ioc: readIocResult(env),
355
+ posture: postureCard(env),
346
356
  spend: showback
347
357
  ? {
348
358
  tokens: showback.totalTokensIn + showback.totalTokensOut,
@@ -389,8 +399,21 @@ function projectMeta(project) {
389
399
  <span class="dot">-</span>
390
400
  <span class="project">${esc(clip(p, 40))}</span>`;
391
401
  }
392
- function renderFeed(events, nowMs, live, preserveOrder) {
402
+ /** Empty list while events exist: the search or the chip filters matched nothing. */
403
+ function noMatchHint(query, env = process.env) {
404
+ const q = query?.trim();
405
+ if (!q)
406
+ return "No events match these filters. Clear a filter to see more.";
407
+ const base = `No events match &ldquo;${esc(clip(q, 60))}&rdquo;.`;
408
+ if (semanticRuntimeInstalled(env))
409
+ return base;
410
+ return (`${base} Plain-language search is off, so only exact words match. ` +
411
+ `Turn it on once with <code>${esc(semanticInstallCommand(env))}</code>, then search again.`);
412
+ }
413
+ function renderFeed(events, nowMs, live, preserveOrder, noMatch) {
393
414
  if (events.length === 0) {
415
+ if (noMatch)
416
+ return `<p class="empty">${noMatch}</p>`;
394
417
  const hint = live
395
418
  ? "No events yet - evaluate a tool call with <code>kya wrap</code> and this page will refresh."
396
419
  : "No events yet - evaluate a tool call with <code>kya wrap</code>, then regenerate this receipt.";
@@ -1837,7 +1860,7 @@ function normalizeTrailEvents(events) {
1837
1860
  targetPath: e.targetPath ? clip(e.targetPath, 80) : undefined,
1838
1861
  }));
1839
1862
  }
1840
- export function renderFeedPage(events, filters, page, nowMs, live, query, embeddings, queryEmbedding) {
1863
+ export function renderFeedPage(events, filters, page, nowMs, live, query, embeddings, queryEmbedding, indexVersion) {
1841
1864
  const q = query?.trim();
1842
1865
  let ordered;
1843
1866
  let total;
@@ -1848,6 +1871,7 @@ export function renderFeedPage(events, filters, page, nowMs, live, query, embedd
1848
1871
  embeddings,
1849
1872
  queryEmbedding,
1850
1873
  facetBoost: 0.05,
1874
+ indexVersion,
1851
1875
  });
1852
1876
  ordered = ranked.map((r) => r.event);
1853
1877
  total = ranked.length;
@@ -1862,7 +1886,7 @@ export function renderFeedPage(events, filters, page, nowMs, live, query, embedd
1862
1886
  const start = (p - 1) * PAGE_SIZE;
1863
1887
  const pageEvents = ordered.slice(start, start + PAGE_SIZE);
1864
1888
  return {
1865
- feedHtml: renderFeed(pageEvents, nowMs, live, !!q),
1889
+ feedHtml: renderFeed(pageEvents, nowMs, live, !!q, pageEvents.length === 0 && events.length > 0 ? noMatchHint(q) : undefined),
1866
1890
  paginationHtml: paginationBar(total, p, pages),
1867
1891
  total,
1868
1892
  page: p,
@@ -1948,6 +1972,70 @@ export function buildLiveFragments(model, livePaged) {
1948
1972
  },
1949
1973
  };
1950
1974
  }
1975
+ /**
1976
+ * IoC banner: only when a scan found something. It sits above the zones, so it shows on every
1977
+ * page as a safety signal. The detail is in the System zone.
1978
+ */
1979
+ function iocBannerHtml(ioc) {
1980
+ const n = ioc?.findings.length ?? 0;
1981
+ if (n === 0)
1982
+ return "";
1983
+ return `<aside class="alert" aria-label="IoC findings">
1984
+ <strong>IoC check: ${n} finding${n === 1 ? "" : "s"} (advisory)</strong>
1985
+ <a href="#system">See the details on the System page</a>
1986
+ </aside>`;
1987
+ }
1988
+ /** IoC card for the System zone: the findings list, or a quiet line when clean. Nothing before the first scan. */
1989
+ function iocSectionHtml(ioc) {
1990
+ if (!ioc)
1991
+ return "";
1992
+ const n = ioc.findings.length;
1993
+ if (n > 0) {
1994
+ const items = ioc.findings
1995
+ .map((f) => `<li><code>${esc(f.ioc)}</code> ${esc(f.check)} <code>${esc(f.evidence)}</code><br><span class="mute">Fix: ${esc(f.hint)}</span></li>`)
1996
+ .join("");
1997
+ return `<section class="card" aria-label="IoC check details">
1998
+ <h3>IoC check: ${n} finding${n === 1 ? "" : "s"} (advisory)</h3>
1999
+ <ul>${items}</ul>
2000
+ <p class="mute">List ${esc(ioc.listVersion)} · checked ${esc(ioc.checkedAt)}. A clean check does not prove the machine is clean.</p>
2001
+ </section>`;
2002
+ }
2003
+ return `<section class="card" aria-label="IoC check">
2004
+ <h3>IoC check</h3>
2005
+ <p class="mute">No findings. List ${esc(ioc.listVersion)} · checked ${esc(ioc.checkedAt)} · unreadable files skipped: ${ioc.skipped}. Advisory: a clean check does not prove the machine is clean.</p>
2006
+ </section>`;
2007
+ }
2008
+ /** Developer setup card (advisory). Shows what a send would contain and the local on/off switch. */
2009
+ function postureSectionHtml(card, live) {
2010
+ if (!card)
2011
+ return "";
2012
+ const list = (items) => items.length === 0 ? "none" : items.map((i) => `<code>${esc(i)}</code>`).join(", ");
2013
+ const toggle = live
2014
+ ? card.share
2015
+ ? `<button type="button" class="stat" data-report-action="posture" data-posture-share="off">Stop sharing</button>`
2016
+ : `<button type="button" class="stat" data-report-action="posture" data-posture-share="on">Turn sharing on</button>`
2017
+ : `<p class="mute small">To change this, run <code>kya posture on</code> or <code>kya posture off</code>.</p>`;
2018
+ const blocked = card.violations.length > 0
2019
+ ? `<p class="mute">Not sendable as it is: ${esc(card.violations.map((v) => `${v.path || "report"} (${v.reason})`).join("; "))}.</p>`
2020
+ : "";
2021
+ return `<section class="card" aria-label="Developer setup">
2022
+ <h3>Developer setup</h3>
2023
+ <p class="mute">Sharing is <strong>${card.share ? "on" : "off"}</strong>. Hosted login: ${card.hosted ? "yes" : "no"}. Workspace policy (cached): ${esc(card.policy)}${card.lastSentAt ? `. Last sent: ${esc(card.lastSentAt)}` : ""}.</p>
2024
+ <p class="mute small">Nothing is sent unless you have a hosted login and your workspace admin turned it on. Turning this on only changes a setting on this machine.${card.hosted ? "" : " Hosted login: no, so nothing is sent from this machine."}</p>
2025
+ <p class="mute small">This list is what the report says about this machine. A send also adds a random device id and a label. The default label is 8 hex characters of a hash. Change it with <code>kya posture label NAME</code>. A send contains no paths, no environment values and no user name:</p>
2026
+ <ul>
2027
+ <li>Agent hosts: ${list(card.hosts)}</li>
2028
+ <li>Agent extensions: ${list(card.extensions)}</li>
2029
+ <li>MCP servers (name, transport, command name): ${list(card.mcpServers)}</li>
2030
+ <li>IoC check: ${esc(card.iocLine)}</li>
2031
+ <li>Never-list: mode ${esc(card.neverMode)}</li>
2032
+ </ul>
2033
+ ${blocked}
2034
+ ${toggle}
2035
+ <div id="report-action-result-posture"></div>
2036
+ <p class="mute small">Advisory. This card reports what is shared. It does not prevent anything.</p>
2037
+ </section>`;
2038
+ }
1951
2039
  export function renderReceiptHtml(model) {
1952
2040
  const nowMs = Date.now();
1953
2041
  const events = normalizeTrailEvents(model.events);
@@ -1977,6 +2065,8 @@ export function renderReceiptHtml(model) {
1977
2065
  const tools = model.mcpSeen && model.mcpSeen.length > 0
1978
2066
  ? model.mcpSeen.map((m) => m.toolId)
1979
2067
  : [...new Set(events.map((e) => e.toolId))];
2068
+ // Only the findings banner repeats on every page; the quiet cards live in the System zone.
2069
+ const iocBanner = iocBannerHtml(model.ioc);
1980
2070
  const blockedBanner = blocked.length === 0
1981
2071
  ? ""
1982
2072
  : `<aside class="alert" aria-label="Blocked never-events">
@@ -2332,6 +2422,7 @@ export function renderReceiptHtml(model) {
2332
2422
  dirty = false;
2333
2423
  feedUpdate();
2334
2424
  scheduleFragments();
2425
+ if (window.kyaMapRefresh && location.hash === '#map') window.kyaMapRefresh();
2335
2426
  }
2336
2427
  // EventSource is absent in DOM shims and very old engines; the page still
2337
2428
  // works, it just does not stream updates.
@@ -2623,7 +2714,9 @@ export function renderReceiptHtml(model) {
2623
2714
  var resultEl = document.getElementById('report-action-result-' + action);
2624
2715
  btn.disabled = true;
2625
2716
  setResult(resultEl, 'Working...', false);
2626
- post('/action/' + action, {})
2717
+ var shareAttr = btn.getAttribute('data-posture-share');
2718
+ var body = shareAttr ? { share: shareAttr === 'on' } : {};
2719
+ post('/action/' + action, body)
2627
2720
  .then(parseJson)
2628
2721
  .then(function(data){
2629
2722
  if (data && data.ok) {
@@ -2633,7 +2726,9 @@ export function renderReceiptHtml(model) {
2633
2726
  ? 'Done: ' + data.total + ' item(s) discovered'
2634
2727
  : action === 'notify-test'
2635
2728
  ? (data.detail ? String(data.detail) : 'Test alert sent to ' + data.targets + ' target(s)')
2636
- : String(data.detail || 'ok');
2729
+ : action === 'posture'
2730
+ ? 'Developer setup sharing is ' + (data.share ? 'on' : 'off') + '.'
2731
+ : String(data.detail || 'ok');
2637
2732
  setResult(resultEl, '<p>' + esc(msg) + '</p>', false);
2638
2733
  if (action === 'otel-test') {
2639
2734
  btn.disabled = false;
@@ -2714,6 +2809,8 @@ export function renderReceiptHtml(model) {
2714
2809
  // (zoneBody lives at module level; buildLiveFragments above produces the
2715
2810
  // overview and certify bodies through the same helper.)
2716
2811
  const systemBody = zoneBody([
2812
+ iocSectionHtml(model.ioc),
2813
+ postureSectionHtml(model.posture, !!model.liveToken),
2717
2814
  capabilitiesPanel(model),
2718
2815
  gatePanel(model.gate, nowMs),
2719
2816
  wiredHostsPanel(model.wiredHosts),
@@ -2729,7 +2826,7 @@ export function renderReceiptHtml(model) {
2729
2826
  <meta charset="utf-8"/>
2730
2827
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
2731
2828
  <title>${esc(model.title)} - ${esc(model.rangeLabel)}</title>
2732
- <style>${receiptCss()}</style>
2829
+ <style>${receiptCss()}${model.liveToken ? MAP_CSS + MAP_LIVE_ZONE_CSS : ""}</style>
2733
2830
  </head>
2734
2831
  <body>
2735
2832
  <aside class="sidebar">
@@ -2745,6 +2842,7 @@ export function renderReceiptHtml(model) {
2745
2842
  ${navItem("#changes", "Changes", changes.fileCount)}
2746
2843
  ${navItem("#certify", "Certify", model.certify?.gap ?? 0)}
2747
2844
  ${navItem("#orr", "ORR", model.orr ? model.orr.scorecards.fail : undefined)}
2845
+ ${model.liveToken ? navItem("#map", "Map", undefined) : ""}
2748
2846
  ${navGroup("Gateway", model.gate?.state === "running")}
2749
2847
  ${navSubItem("#gateway-home", "Home", model.gate?.servers.length ?? 0)}
2750
2848
  ${navSubItem("#gateway-listeners", "Listeners", model.gate?.listeners?.length ?? 0)}
@@ -2770,6 +2868,7 @@ ${model.liveToken ? '<main data-live-token="1">' : '<main>'}
2770
2868
  ${identityLine(model.identity)}
2771
2869
  </header>
2772
2870
  ${blockedBanner}
2871
+ ${iocBanner}
2773
2872
  <section class="zone" id="overview" aria-labelledby="tab-overview">
2774
2873
  <div class="hero">
2775
2874
  ${fr.fragments.heroCertify}
@@ -2809,12 +2908,14 @@ ${fr.fragments.changesList}
2809
2908
  <section class="zone" id="orr" aria-labelledby="tab-orr">
2810
2909
  <div id="frag-orr">${fr.fragments.orrPanel}</div>
2811
2910
  </section>
2911
+ ${model.liveToken ? '<section class="zone" id="map" aria-labelledby="tab-map"><div id="frag-map"><p class="mute">Loading the map.</p></div></section>' : ""}
2812
2912
  ${gatePagePanel(model.gate, nowMs)}
2813
2913
  <section class="zone" id="system" aria-labelledby="tab-system">
2814
2914
  ${systemBody}
2815
2915
  </section>
2816
2916
  </main>
2817
2917
  ${filterScript}
2918
+ ${model.liveToken ? liveMapScripts() : ""}
2818
2919
  ${staticSearchScript}
2819
2920
  ${themeScript}
2820
2921
  ${gateScript}
@@ -2836,6 +2937,10 @@ export function renderReceiptMarkdown(model) {
2836
2937
  `Allow ${c.allow} | Deny ${c.deny} | Review ${c.require} | Never ${c.never}`,
2837
2938
  "",
2838
2939
  ];
2940
+ if (model.ioc) {
2941
+ const k = model.ioc.findings.length;
2942
+ lines.push(mdText(`IoC check (advisory): ${k === 0 ? "no findings" : `${k} finding${k === 1 ? "" : "s"}`}, list ${model.ioc.listVersion}, checked ${model.ioc.checkedAt}.`), "");
2943
+ }
2839
2944
  if (model.identity) {
2840
2945
  const id = model.identity;
2841
2946
  const nameId = id.agentName && id.agentId