Files
hiveops-guide/backend/src/main/resources/content/agent/win-app-events/architect.md
T
johannes f21c783d8c fix(guide): gate Architect + Claude tabs to BCOS_ADMIN only (#16)
The 162 Architect/Claude docs were tagged 'audience: dev'. The 'dev' tier
resolves to isInternal() = ROLE_MSP_ADMIN || ROLE_BCOS_ADMIN, so every
MSP_ADMIN could read full internal architecture: source paths, DB tables,
service ports, NGINX routes. Verified live on bcos.dev as msp_a@msp1.bcos.dev.

Retag them to 'audience: bcos' (isBcos, BCOS_ADMIN only). Content-only change;
the 'dev' tier mapping is deliberately left alone so the 15 Internal, 35
Overview and 66 Testing docs stay MSP-visible.

Also add platform.guide-access documenting the audience tiers and the
per-role visibility matrix, so the access model is queryable from the Guide
instead of re-derived from source each time.

Verified: MSP module count unchanged (116 across 17 apps); no module loses
all its tabs. Guide is bcos.dev only, not deployed to production.

Closes #16

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:55:07 -04:00

6.5 KiB

module, title, tab, order, audience
module title tab order audience
agent.win-app-events Windows App Event Log Monitor — forwards Windows Application-log errors as HiveIQ incidents Architect 30 bcos

Internal · how it's built. Confirmed against hiveops-module-win-app-events source (com.hiveops.winevents) 2026-07-01. See [technical.agent] for agent-wide module/event context.

Maven module

hiveops-module-win-app-events (artifact hiveops-module-win-app-events, own version 1.0.1, parented by hiveops-agent-parent 4.4.3). Packaged as a standalone jar (<finalName>${project.artifactId}</finalName>hiveops-module-win-app-events.jar). Dependencies are all provided (supplied by the fat JAR at runtime):

  • hiveops-core — the AgentModule SPI, ModuleContext, HttpClientHelper/HttpClientSettings, PatternReloadRegistry.
  • hiveops-agent-journal (1.0.0) — IncidentEventClient, EventType, CreateJournalEventRequest (the same event path journal-events uses).
  • log4j-api, commons-lang3.

Registered via ServiceLoader SPI: src/main/resources/META-INF/services/com.hiveops.core.module.AgentModulecom.hiveops.winevents.WinAppEventModule.

Classes and flow

Four classes, no sub-packages:

Class Role
WinAppEventModule AgentModule impl — OS/enable gating, config parse, scheduler, hot-reload registration
WinAppEventPoller Runnable — the anchor/poll loop; runs wevtutil, applies filters, emits events
WinAppEventParser Turns raw wevtutil XML into WinAppEvent records
WinAppEvent Immutable holder: recordId, provider, eventId, level, timeCreated, message; builds matchText() and toDetails()

Poll-based cycle (not log-tail or ETW hook — it repeatedly shells wevtutil and diffs by RecordID):

  1. initialize(ModuleContext) — bails (leaves ready=false) if not Windows, if winevt.events.enabled != true, or if no agent.endpoint/incident.endpoint. Otherwise builds an IncidentEventClient from HttpClientHelper.loadSettingsFromProperties(props, prefix) (prefix "agent" if agent.endpoint set, else "incident"), reads levels/batch.size/sources/pattern, constructs the WinAppEventPoller, resolves the hiveops.properties path for later reloads, and sets ready=true.
  2. isEnabled() returns ready.
  3. start() — creates a single daemon thread (newSingleThreadScheduledExecutor, thread name win-app-events) and scheduleAtFixedRate(poller, 0, intervalSec, SECONDS). Then registers this::reloadPatterns with PatternReloadRegistry.
  4. WinAppEventPoller.run() — if suspended, skip. If lastEventRecordId < 0, call anchor() and return (the very first tick). Otherwise poll().
    • anchor() runs wevtutil qe Application /rd:true /f:XML /c:1 /q:*[System[Level>=1 and Level<=<maxLevel>]], sets lastEventRecordId to that newest RecordID (or 0 if the log is empty). This is why no history is replayed.
    • poll() runs wevtutil qe Application /rd:false /f:XML /LNIFO:Message /c:<batchSize> /q:*[System[(Level>=1 and Level<=<maxLevel>) and (EventRecordID > <lastId>)]]. /rd:false = oldest-first (in-order processing); /LNIFO:Message renders human-readable message text from the provider DLL.
  5. Filtering (in order, per event): the level filter is baked into the wevtutil XPath; then source filter (sourceFilter non-empty → Provider name must case-insensitively equal one entry); then pattern filter (eventPattern != null → regex .find() on matchText() = "<provider> <eventId> <message>").
  6. Bookmark advancelastEventRecordId is set to the max RecordID seen this poll even if every event was filtered out, so filtered RecordIDs are never re-queried.
  7. Emit — survivors become CreateJournalEventRequests (agentAtmId=atmName, eventType=WIN_APP_EVENT_ERROR, eventDetails=WinAppEvent.toDetails(), eventSource=HIVEOPS_AGENT_WINEVT) and are sent via client.sendEvents(list).

WinAppEventParser wraps the wevtutil output in a synthetic <Events>…</Events> root (handles both single- and multi-event output), DOM-parses each <Event>, and reads EventRecordID, Provider@Name, Level (mapped 1→Critical, 2→Error, 3→Warning, 4→Information), EventID, TimeCreated@SystemTime, and the message. Message extraction prefers RenderingInfo/Message (present because of /LNIFO:Message) and falls back to joining EventData/Data values with " | ". Unparseable individual events are skipped at DEBUG; a totally unparseable payload yields an empty list.

Hot reload

WinAppEventModule.reloadPatterns() (registered with PatternReloadRegistry) re-reads hiveops.properties from disk (startedFromDir/hiveops.properties, cwd fallback) and calls poller.setPattern(...) and poller.setSuspended(...). Both poller fields are volatile. ProcessConfigUpdate (the CONFIG_UPDATE fleet-task handler in hiveops-file-collection) writes the new properties then calls PatternReloadRegistry.trigger() — that's the entire hot-reload path. Level/sources/interval/batch are not re-read here (captured at initialize() time only).

How events reach the platform

This module rides the same journal-event path as the journal-events module — there is no separate protocol:

WinAppEventPoller
   │  IncidentEventClient.sendEvents(List<CreateJournalEventRequest>)
   │  (endpoint from agent.endpoint | incident.endpoint; auth via HttpClientHelper)
   ▼
POST {endpoint}/api/journal-events      ← one HTTP POST per event (sendEvents loops sendEvent)
   ▼
agent-proxy  (agent-facing gateway, X-Institution-Key / token auth)
   ▼
hiveops-incident  → journal-event ingest → incident pipeline / device_summary

Note: sendEvents is not a batch POST — it iterates and POSTs each event individually to /api/journal-events; winevt.events.batch.size only bounds how many events one wevtutil call pulls, not the HTTP shape.

Platform abstraction

The module does not use com.hiveops.platform.PlatformServiceFactory / PlatformService. OS detection is done locally (System.getProperty("os.name")…contains("windows")) and it shells wevtutil directly via ProcessBuilder. The runner (runWevtutil) decodes stdout with the charset from the file.encoding system property (a comment notes wevtutil emits UTF-16LE and relies on the JVM's active code-page resolution), reads stdout fully, then waitFor()s; a non-zero exit is logged at DEBUG and yields an empty string (→ empty parse → no events that tick).