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>
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-eventssource (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— theAgentModuleSPI,ModuleContext,HttpClientHelper/HttpClientSettings,PatternReloadRegistry.hiveops-agent-journal(1.0.0) —IncidentEventClient,EventType,CreateJournalEventRequest(the same event pathjournal-eventsuses).log4j-api,commons-lang3.
Registered via ServiceLoader SPI: src/main/resources/META-INF/services/com.hiveops.core.module.AgentModule → com.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):
initialize(ModuleContext)— bails (leavesready=false) if not Windows, ifwinevt.events.enabled != true, or if noagent.endpoint/incident.endpoint. Otherwise builds anIncidentEventClientfromHttpClientHelper.loadSettingsFromProperties(props, prefix)(prefix"agent"ifagent.endpointset, else"incident"), readslevels/batch.size/sources/pattern, constructs theWinAppEventPoller, resolves thehiveops.propertiespath for later reloads, and setsready=true.isEnabled()returnsready.start()— creates a single daemon thread (newSingleThreadScheduledExecutor, thread namewin-app-events) andscheduleAtFixedRate(poller, 0, intervalSec, SECONDS). Then registersthis::reloadPatternswithPatternReloadRegistry.WinAppEventPoller.run()— ifsuspended, skip. IflastEventRecordId < 0, callanchor()and return (the very first tick). Otherwisepoll().anchor()runswevtutil qe Application /rd:true /f:XML /c:1 /q:*[System[Level>=1 and Level<=<maxLevel>]], setslastEventRecordIdto that newest RecordID (or0if the log is empty). This is why no history is replayed.poll()runswevtutil 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:Messagerenders human-readable message text from the provider DLL.
- Filtering (in order, per event): the level filter is baked into the
wevtutilXPath; then source filter (sourceFilternon-empty → Provider name must case-insensitively equal one entry); then pattern filter (eventPattern != null→ regex.find()onmatchText()="<provider> <eventId> <message>"). - Bookmark advance —
lastEventRecordIdis set to the max RecordID seen this poll even if every event was filtered out, so filtered RecordIDs are never re-queried. - Emit — survivors become
CreateJournalEventRequests (agentAtmId=atmName,eventType=WIN_APP_EVENT_ERROR,eventDetails=WinAppEvent.toDetails(),eventSource=HIVEOPS_AGENT_WINEVT) and are sent viaclient.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).