Repository navigation
Event Log Viewer Preview: validation and productization ledger #539
Copy link
Copy link
Open
0 / 10 of 1 issue completedOpen
0 / 10 of 1 issue completed
Copy link
Labels
enhancementNew feature or requestNew feature or requestepicTracking issue for a larger body of workTracking issue for a larger body of workrustPull requests that update rust codePull requests that update rust codewindowsWindows platform relatedWindows platform relatedworkspaceWorkspace UI areaWorkspace UI area
Description
Event Log Viewer Preview - validation and productization ledger
Status: Preview landed on main - PR #583, squash-merged as c77c1f10 (2026-09-18). The cross-platform viewer foundation is no longer branch-only: portable .evtx analysis, live Windows channels, provider enrichment, filtering/grouping, operational diagnosis, unified event/text timelines, bounded acquisition and export, explicit coverage gaps, and offline evidence handling.
Ledger
- Real-world Preview validation (Windows + cross-platform field feedback)
- Provider-database coverage and fidelity
- Backend-owned paging for very large explicit loads - resolved as deferred by ADR-005, which the ADR's own consequences section states. The changes it did require are in
main(stop control, per-request cancel flag, cancelled-load coverage gap), and the deferred path keeps its revisit criteria: a channel a client-side load cannot hold, or an operator reporting the load unusable after the stop control is in place, each with numbers. - Remote / VSS / archive acceptance
- Installer, signing, and cross-platform packaging
- Privacy and publication gaps
- Criteria for removing the Preview label
Superseded construction work
- feat(event-log): mark Event Log Viewer as preview #609 - integrated through
67cf11d9(merged into the candidate; shipped in feat(event-log): complete Event Viewer epic foundation #583) - fix(event-log): load streamed live-channel batches in every path #610 - superseded by the canonical streamed-record implementation (feat(event-log): complete Event Viewer epic foundation #583, merged as superseded
481e5fe5) - fix(event-log): bound the automatic load and fill the view as channels land #611 - superseded by
be6150e3(feat(event-log): complete Event Viewer epic foundation #583) - fix(event-log): bound the coverage gap list so it cannot displace the view #612 - superseded by the canonical bounded coverage UI (feat(event-log): complete Event Viewer epic foundation #583, merged as superseded
e0789ee4) - fix(event-log): stop treating disabled channels as coverage gaps #613 - superseded by
edd7e063(PR fix(event-log): channel eligibility — disabled channels are not coverage gaps (replaces #613) #634)
Branch history for those five is preserved in a verified local git bundle (cmtraceopen-event-viewer-superseded.bundle, SHA-256 c959a26964b89738dcb772d6acc223d3a8efeb19ce5686c50b20c3ee3acf7b0d) before remote branch deletion.
Historical context (construction phase)
Event Viewer: become best in class ## Context On 2026-08-05, @awakecoding asked for "a Windows event viewer that isn't hot garbage like the built-in one", naming NirSoft's FullEventLogView as the current best available, with one specific complaint: it loads a whole week of logs by default on startup. This epic covers a full static reverse engineering of FullEventLogView v1.81, a survey of the wider competitive field, and the resulting strategy and phased plan. --- # Part 1: FullEventLogView v1.81 teardown Static analysis only, no execution. | File | Size | SHA-256 | |---|---|---| | FullEventLogView.exe (x86) | 128,512 | 6387cda75c085afd62fdeaa251da02d4bc0150b3c58d849e1d59981a4e16529a | | FullEventLogView.exe (x64) | 182,784 | 6563533519d8d536410796977c1581150dfe40fd048fc3bb80ce4882caf50d7d | Both built 2026-02-16. Techniques: PE header and section analysis (pefile), import table diffing, UTF-16 string extraction (custom extractor, macOS strings has no -e l), resource directory walk, and targeted x86-64 disassembly (objdump) of the query builder and scan loop. ## Build shape Classic NirSoft: linker 8.0 (VC++ 2005), GUI subsystem, hand-rolled Win32 against a dynamically linked msvcrt.dll. Manifest declares dpiAware=true and no requestedExecutionLevel, so it runs asInvoker and self-elevates via ShellExecuteExW with the RunAs verb (Ctrl+F11 / /RunAsAdmin). Security posture, relevant because we ship signed builds with provenance and SBOMs: | Mitigation | x86 | x64 | |---|---|---| | ASLR (DYNAMIC_BASE) | no | no | | DEP (NX_COMPAT) | no | no | | Control Flow Guard | no | no | | Authenticode signature | none | none | ## The engine is 23 dynamically bound wevtapi functions wevtapi.dll is not in the import table. It is resolved at runtime via LoadLibraryW + GetProcAddress into a binding struct (x64: 0x140026980, slots +0x08 through +0xB8). That is how one binary claims Vista through 11 support: on a system without the modern API the pointers stay null. EvtOpenLog EvtGetLogInfo EvtOpenSession EvtClose EvtFormatMessage EvtNext EvtQuery EvtGetObjectArrayProperty EvtGetObjectArraySize EvtCreateRenderContext EvtGetQueryInfo EvtRender EvtOpenChannelEnum EvtNextChannelPath EvtOpenChannelConfig EvtGetChannelConfigProperty EvtOpenPublisherMetadata EvtSetChannelConfigProperty EvtSaveChannelConfig EvtClearLog EvtNextPublisherId EvtOpenPublisherEnum EvtGetPublisherMetadataProperty Slot mapping confirmed against three independent disassembly sites (+0x30 = EvtNext, +0x38 = EvtQuery, +0x60 = EvtRender). Absent: EvtSubscribe, EvtSeek, EvtExportLog, EvtArchiveExportedLog. That explains several of its limits. ## It is 100% single threaded Neither binary imports CreateThread, _beginthreadex, CreateEventW, any critical section function, WaitForMultipleObjects, CreateFileMappingW/MapViewOfFile, VirtualAlloc, HeapAlloc, QueueUserWorkItem, or CoInitialize. The scan runs on the UI thread with a cooperative message pump and a per-event abort callback. That is the root cause of a recurring bug class in its own changelog: "remained in memory if you closed the main window during events scanning" (1.50), "failed to close properly if you exit while Auto Refresh is active" (1.81), and the need for an explicit Esc-to-stop (1.74). No registry use at all. State lives in FullEventLogView.cfg via GetPrivateProfileString/WritePrivateProfileString. ## The query pipeline Reconstructed from 0x14001ca84 (clause builder) and 0x14001cb60 (scan driver). Only one predicate is ever pushed server side: EventID. The complete XPath template set in the binary is: xml <QueryList> <Query><Select>*[System[( EventID = %d or (EventID >= %d and EventID <= %d) ... )]] </Select></Query> ... </QueryList> Maximum 10 EventIDs per <Select>, multiple <Select> nodes batched into one <QueryList>. That batching is the fix for changelog 1.72 ("filter more than 23 event IDs"), a workaround for the Event Log service expression limit. Ranges are supported. If the EventID filter is not in "show only these" mode, the query is literally *. There is no TimeCreated[@SystemTime...], no timediff(), no Level=, no Provider[@Name=], and no Keywords predicate anywhere in either binary. Level, provider, channel, time range, and description filtering are all client side, after the event has been pulled and rendered. EvtQuery flags: channel 0x201 (EvtQueryChannelPath | EvtQueryReverseDirection), file 0x202 (EvtQueryFilePath | EvtQueryReverseDirection). On ERROR_NOT_SUPPORTED (50) it retries forward (0x1 / 0x2). That is the changelog 1.31 remote fix: remote sessions cannot iterate in reverse, so remote queries read each channel from the oldest event forward. EvtQueryTolerateQueryErrors (0x1000) is never set. Reverse direction plus a per-event callback that returns 0 to stop is what makes the 7-day default survivable. The time filter is not a query, it is an early-exit condition. ## The per-event loop From 0x14001ccb3..0x14001cde8: 1. EvtNext(hResults, EventsSize=1, &hEvent, Timeout=5000, Flags=0, &returned) 2. abort-check callback 3. EvtRender(NULL, hEvent, EvtRenderEventXml, 0, NULL, &used, &props) (size probe) 4. allocate, then EvtRender(...) again for the actual XML 5. process callback, which string-parses the rendered XML for TimeCreated/@SystemTime, Execution/@ProcessID, Execution/@ThreadID, Provider/@Name, Security/@UserID, EventID, EventRecordID, Level, Task, Opcode, Computer, Channel, Keywords, Data, EventPayload 6. EvtClose(hEvent) EventsSize is hardcoded to 1. Roughly four wevtapi round trips per event. EvtNext supports up to 1024 handles per call, and EvtCreateRenderContext(EvtRenderContextSystem) + EvtRender(EvtRenderEventValues) returns a packed EVT_VARIANT array with no XML serialization on either side. FullEventLogView imports EvtCreateRenderContext but the scan path passes Context=NULL and asks for XML text. Descriptions come from EvtFormatMessage with an EvtOpenPublisherMetadata handle cache (array at +0x53c0, count +0x53d0, stride 0x420 bytes). ## Feature inventory Sources (F7): this computer, remote computer with optional credentials, folder of log files with wildcard, single .evtx or .etl. Plus archived logs (Archive-*, requires elevation), Volume Shadow Copy paths, drag and drop. Columns (16 plus N): Event Time, Record ID, Event ID, Level, Opcode, Task, Process ID, Thread ID, Channel, Provider, Keywords, Description, Computer, User, Log File, and String 1..N (default 10) exposing insertion strings as individually sortable columns. Advanced Options filters (F9), all client side except EventID: levels; Event ID (all/only/exclude, ranges); Provider (all/only/exclude, wildcards); Channel (all/only/exclude, wildcards); Time (all / last N sec-min-hour-days / absolute local / absolute GMT); Description strings (all/only/exclude, wildcards, with a fast "parameters only" vs slow "full description" mode). Quick Filter (Ctrl+Q): 6 modes (one string, multiple words, multiple strings, all words, all strings, Event ID list with ranges), all vs visible columns, show vs hide, case sensitivity. Refresh: F5 full, F8 smooth (appends only newer events), auto-refresh 1 to 10 seconds. No EvtSubscribe, so live mode is polling. Optional tray icon, balloon per new event, start hidden. Export: text, tab, tabular, CSV, HTML horizontal, HTML vertical, XML, JSON, raw event XML. Encoding selectable. /SaveDirect streams to disk without building the in-memory list (and cannot sort). Command line: every one of the ~55 .cfg keys is settable as a switch, which is how it does headless export, plus the /s* save switches, repeatable /sort with ~ for descending, /SaveDirect, /cfg, /RunAsAdmin, /ClearChannelEvents. Empty filename means stdout. Other: EvtClearLog channel clearing, channel config read/write, column chooser with reorder and pixel widths, 3 lower-pane modes, black background, full screen, font selection, ~40 language .ini localizations, custom exception dialog. --- # Part 2: Competitive landscape Four distinct classes, each defining different expectations. ## Class A: interactive event viewers (our direct competition) ### Microsoft EventLogExpert (the real benchmark) MIT licensed, hosted under the Microsoft org, maintained by a group inside Microsoft. Not an official supported product, but it is the most credible modern competitor. .NET/Blazor, shipped as MSIX, Windows 11 / Server 2022 / Server 2025 only, x64 and ARM64. What it does that matters: - Provider Databases: .db files capturing provider metadata from one machine so .evtx files opened on a different machine "resolve descriptions and task categories correctly". This is how you read Exchange, SQL Server, or AD logs on a workstation without those roles installed. Its dependency set includes Microsoft.Data.Sqlite, so these are SQLite-backed. - Combined view interleaving any mix of file and live logs by time across machines. - Filter Library: saved single filters, named filter sets, favorites, tag-based organization, automatic recents, plus import and export. - Basic filters (property x operator) joined with AND/OR, plus Date filters, Advanced expressions, and Exclusion filters. - Per-row highlight colors driven by your filters. - Inline description previews in the table, on-demand XML in the details pane. - Live channels with auto-discovery, admin-only channels disabled when not elevated. - Configurable column visibility, ordering, sort. Folder loading and command-line folder arguments. Gaps we can exploit: Windows 11 and Server 2022 or newer only, no remote computer support visible, no export formats documented, no grouping or pivot, no detection or diagnosis layer, events only. ### Event Log Explorer (FSPro Labs, commercial) The feature-depth benchmark. Three editions (Standard, Enterprise, Forensic). - Filtering in stages: XML/XPath filter, log loading filter (excludes at load time), general filter, quick filter, linked filter, special marking filter via scripts, plus general search and "go to date". This layered before-load / on-load / after-load model is the right architecture and is worth copying outright. - Reads EVT/EVTX directly without the Windows Event Log API to recover damaged files. - Sources: live local and remote, EVT and EVTX files, SQL Server database, disk images, deep scan of raw images, snapshots. - Consolidation across machines, import computers from Active Directory / XML / text. - Export to Excel, HTML, Text, PDF; printing with templates; analytical reports with summary tables and pivot charts. - SQL Server backed storage for volume, custom columns (30 in Enterprise/Forensic) including formula-based via PascalScript. - Credential manager, workspaces, color coding, timezone correction, bookmarking. - LLM integration that analyzes event details and suggests troubleshooting steps. ### Others in class A - Windows Event Viewer: the baseline everyone is escaping. The documented complaint pattern is consistent: basic filtering is inadequate, so users are pushed into hand-written XML custom views that are poorly documented and error prone, and it does not scale to a DC or Exchange server. - Venture (mttaggart/venture): cross-platform EVTX viewer built with Tauri, the same stack as us. Files only, filters, sorting, flagging. Small scope, but it means the cross-platform GUI niche is not empty. - VS Code evtx-viewer extension: cross-platform, claims >10MB/s and <512MB with LRU caching. - LogViewPlus / LogFusion: commercial Windows log viewers that also read local and remote Windows Event Logs alongside text logs. LogViewPlus is the closest anyone comes to our "events plus text logs" pitch, but it is a generic parsing tool with no Windows device-management domain knowledge. ## Class B: DFIR tooling (where the good analysis ideas are) - EvtxECmd + Timeline Explorer (Eric Zimmerman). EvtxECmd bypasses the Windows API, parses single files or recursive directories, handles locked files, parses Volume Shadow Copies with --vss, dedupes by SHA-1, and outputs CSV/XML/JSON. Its key idea is Maps: community-maintained files that normalize each event type's wildly different EventData into six standard PayloadData1..6 columns, so heterogeneous events become one tabular dataset. Timeline Explorer is the GUI: drag-to-group by column (group by EventID, UserName, LogonType and instantly see counts), per-column filter dropdowns and text filters, toggleable filters, and row tagging for triage. - Hayabusa (Yamato Security). Rust, multi-threaded, the only open-source tool with full Sigma support including v2 correlation rules, 4,000+ curated rules, CSV/JSON/JSONL timeline output, MITRE ATT&CK mapping via Takajo. Reports data reduction ratio, top computers, top alerts. Notes that EventID filters give 10-15% speedup and channel-based filtering 20% to 10x. AGPLv3, so we cannot vendor its code; Sigma rules themselves are DRL 1.1. - Chainsaw (WithSecure). Rust, Sigma plus custom rules, also covers MFT, registry hives, Shimcache/Amcache, SRUM. - forensic-timeliner: consolidates EZ Tools, KAPE, Hayabusa, Chainsaw and NirSoft CSV output into one unified timeline. The existence of this tool is evidence that cross-source timeline merging is a real, unmet need. ## Class C: enterprise and SIEM SolarWinds, ManageEngine, Graylog, Datadog, Sentinel. Different product category (agents, servers, retention, compliance). They set the expectation that log tooling can alert and report, but competing here is out of scope. ## Class D: parsing engines | Parser | Language | 30MB / ~62K records | |---|---|---| | omerbenamram/evtx (8 threads) | Rust | ~20.7 ms | | omerbenamram/evtx (1 thread) | Rust | ~71.6 ms | | libevtx | C | ~2.44 s | | Velocidex evtx | Go | seconds | | python-evtx | Python | ~2m41s | We already use the fastest one. Cargo.lock pins evtx = 0.12.2, which is current (crates.io last updated 2026-06-13, matching the compiled-template rewrite). It supports multithreading and basic recovery of missing records and chunks. Known unimplemented node types: CDATA and EVTHandle. Important distinction: for file sources our parsing is already best in class. For live sources the bottleneck is wevtapi RPC, not parsing, which is what Part 1 and Phase 1 address. ## Feature matrix | | Event Viewer | FullEventLogView | EventLogExpert | Event Log Explorer | EvtxECmd + TE | Hayabusa | CMTrace Open today | |---|---|---|---|---|---|---|---| | License | proprietary | freeware | MIT | commercial | free/closed GUI | AGPLv3 | MIT | | Platform | Win | Win | Win 11+ | Win | Win | cross | Win + macOS + Linux | | Live logs | yes | yes | yes | yes | no | live mode | yes | | .evtx files | yes | yes | yes | yes | yes | yes | yes | | Damaged files | no | no | no | Forensic ed. | yes | yes | partial (crate recovery) | | Remote machine | yes | yes | no | yes | no | via Velociraptor | no | | Offline description resolution | no | no | yes (.db) | description server | via Maps | n/a | no | | Saved filter library | custom views | .cfg only | yes | yes | no | n/a | no | | Load-time filtering | XPath | EventID only | yes | 3 stages | n/a | yes | no | | Grouping / pivot | no | no | no | pivot reports | yes | no | no | | Row tagging / bookmarks | no | no | no | yes | yes | no | markers exist elsewhere | | Detection rules | no | no | no | LLM assist | Sigma 4000+ | Sigma | no | | Export formats | evtx/csv/xml | 9 | no | Excel/HTML/PDF | CSV/JSON/XML | CSV/JSON | none | | Text log correlation | no | no | no | no | no | no | timeline exists | | Device-management diagnosis | no | no | no | no | no | no | exists elsewhere | --- # Part 3: Where we stand Verified against main. Backend: src-tauri/src/event_log/live.rs (533 LOC), commands.rs (120), parser.rs (296), models.rs (72). We already use EvtQuery(EvtQueryChannelPath | EvtQueryReverseDirection) with query "*", EvtNext with a batch of 16, EvtRender XML, and EvtFormatMessage with a publisher-metadata HashMap cache. Per-event RPC cost is already meaningfully below FullEventLogView's, and we read .evtx on macOS and Linux, which nothing in class A except Venture does. Frontend: evtx-store.ts has filterLevels, filterEventIds, filterSearch, and sort by time/eventId/level/provider/channel. That is the whole filter model. Concrete gaps: no time filter, no provider filter, 7 columns, no export, no remote, hard max_events default 1000, sequential channel loop at src-tauri/src/event_log/commands.rs:53, no offline description resolution, no saved filters, no grouping. --- # Part 4: Strategy Matching FullEventLogView feature for feature is not the goal and would not make us best in class. The research points at three plays. 1. Win on the axis nobody contests: events next to the logs that explain them. Every tool surveyed is events-only. Nobody merges Windows events with text logs. We already have 17 parsers and a unified timeline. Putting IntuneManagementExtension.log, setupact.log, and DeviceManagement-Enterprise-Diagnostics-Provider/Admin on one timeline is something no competitor can do without rebuilding their product. The existence of forensic-timeliner proves the demand. This is the differentiator, and it should be built early rather than saved for last. 2. Offline description resolution is the highest-leverage single feature. EventLogExpert built Provider Databases because you cannot resolve descriptions without the provider registered. For us it is existential: on macOS and Linux there is no provider registry at all, so today our cross-platform .evtx support shows raw EventData with no message text. Fix this and we become the only GUI anywhere that renders full event descriptions for Intune, ConfigMgr, Exchange, or SQL Server events on a Mac. Leave it unfixed and the cross-platform story is hollow. 3. Normalize heterogeneous EventData, then diagnose it. EvtxECmd Maps solve the "every event type has different fields" problem by projecting into standard columns. Combined with our error_db, this is how events become searchable, groupable, and diagnosable. The detection layer should then be operational, not security: Hayabusa and Chainsaw own the security-Sigma space, and we should not fight them there. Nobody has rules for Autopilot, ESP, MDM enrollment, or ConfigMgr client health over event data, and we already have the diagnostic rule pattern from dsregcmd and esp. Explicitly not doing: SIEM, agents, alerting, retention, compliance reporting (class C), and security threat hunting (class B owns it, and Hayabusa's AGPLv3 rules out code reuse anyway). --- # Part 4.5: The steal list Every item below was license-checked. We take formats, schemas, and interaction models, not code. Attribution goes in NOTICE. Nothing is copied from AGPLv3 (Hayabusa) or closed-source (Timeline Explorer, Event Log Explorer) sources. ## 1. Event maps: adopt the EvtxECmd schema verbatim Source: EricZimmerman/evtx, MIT. The format is fully specified and open: - Filename convention Channel-Name_Provider-Name_EventID.Map. Channel plus Event ID plus Provider is the uniqueness key, not the filename. - Header: Author, Description, EventId, Channel, Provider. - Maps: a list of { Property, PropertyValue, Values[] }. Property is the destination column (UserName, RemoteHost, ExecutableInfo, PayloadData1..6). PropertyValue is a template with %variable% placeholders. Each Values entry binds a variable name to an XPath query against the EventData XML. - Lookups: translation tables with Name, Default, and raw-value-to-text pairs, so numeric codes render as readable text. Reference the same XPath under two variable names to show raw and decoded side by side. - Maps load alphabetically, so a 1_ filename prefix lets a local override win without editing the original. Decision: implement this schema as-is in cmtraceopen-parser so the existing community corpus loads unmodified. Extend only additively. Then author our own domain maps for MDM, Autopilot, ESP, and ConfigMgr channels, which nobody has written. Corpus reality check (measured 2026-08-09 against EricZimmerman/evtx@master): 468 map files exist, but they are overwhelmingly security and DFIR oriented. Top channels are Security (98), Application (52), System (36), Sysmon (29), Defender (19), Symantec Endpoint Protection (18), then Varonis, Cisco AnyConnect, SentinelOne, TerminalServices. Coverage of the channels our users actually open is effectively zero: no maps at all for DeviceManagement-Enterprise-Diagnostics-Provider, ModernDeployment-Diagnostics-Provider, Provisioning-Diagnostics-Provider, AAD/Operational, AppXDeployment-Server, WindowsUpdateClient, or anything ConfigMgr. The only relevant hits are 13 maps for Microsoft-Windows-Shell-Core/Operational (9701-9712 and 28115), which are genuinely useful because they cover RunOnceEx and OOBE shell activity during Autopilot. Why this matters, corrected: adopting the schema is not a shortcut to free domain coverage, because that coverage does not exist. The real reasons are (a) interoperability, since maps we write also work in EvtxECmd and Timeline Explorer and maps others write work in ours, (b) a proven design for the heterogeneous-EventData problem that we would otherwise have to invent, and (c) the device-management map corpus is unclaimed. Writing it is a genuine contribution and a durable moat rather than a copy of someone else's work. ## 2. Provider databases: read theirs, write ours Source: microsoft/EventLogExpert, MIT. Verified design: SQLite, deliberately denormalized. The entire set of events and messages for a provider is serialized to (compressed) JSON and stored as a single row per provider. Their PR discussion states this is far faster than resolving through the event reader and roughly matches local provider resolution. Databases live in %LOCALAPPDATA%\EventLogExpert\Databases and all *.db files there are used together. Their EventDbTool CLI does create / diff / merge / show / upgrade, and recent releases surface this as a Database Tools UI. The operational pattern worth stealing is the diff: run capture on a machine that has the product installed, diff the result against a base OS database, and you are left with a small database containing only that product's providers. Precedent for reuse exists: Manny-MSFT/EvtxExporterCLI re-derived the reading code (EF Core models, compressed JSON converters, schema) from EventLogExpert.Eventing. Decision: implement a Rust reader for their format so anyone already carrying EventLogExpert databases can use them in CMTrace Open, plus our own Windows-side capture writer. Adopt the base-OS diff workflow to ship small curated databases for MDM, Autopilot, AAD, ConfigMgr client, AppX, and Windows Update. Why this matters: this is the single feature that makes event descriptions render on macOS and Linux. ## 3. Layered filtering (concept only) Source: Event Log Explorer, commercial, closed source. Architecture only, nothing copied. Three-stage model: before-load (XPath pushed to the service), on-load (drop events as they stream in), after-load (the interactive grid filter). FullEventLogView only has after-load. So do we, today. This staging is what makes large scans feel instant. ## 4. Grouping and tagging (interaction model only) Source: Timeline Explorer, free but closed source. Interaction only. Drag a column header into a grouping pane, nest multiple groupings, see counts per group, right-click to ungroup or clear. Per-column filter dropdowns plus a quick text filter, filters toggleable without deleting them, and a checkbox Tag column for triage. No event viewer in the interactive class has any of this. Adding it to a virtualized list beats both classes at once. ## 5. Filter library and highlight rules Source: microsoft/EventLogExpert, MIT. Saved single filters, named filter sets, favorites, tag-based organization, automatic recents, plus import and export. Per-row highlight colors driven by the filter rules themselves. ## 6. Query shape (concept only) Source: FullEventLogView, freeware. Behavior observed by disassembly, no code taken. <QueryList> containing multiple <Select> nodes of at most 10 EventIDs each, to stay under the Event Log service expression limit. Reverse direction by default with a forward fallback on ERROR_NOT_SUPPORTED. --- # Part 5: Phased plan Each phase ships something usable on its own, per the layered-growth rule. To be split into child issues. ### Phase 0: hands-on bake-off The feature matrix in Part 2 is built from documentation and source reading, not from running the tools. Verify it before building against it. Windows dev box required. - [x] Install EventLogExpert, FullEventLogView, EvtxECmd and Timeline Explorer on the Windows dev box. Venture's release had no matching x64 asset; deferred, not needed for the numbers. - [ ] Verify the contested matrix cells: does EventLogExpert have remote computer support, any export, or grouping? Does Event Log Explorer's three-stage filtering behave as documented? - [ ] Build a reference corpus: one machine's full channel set plus an MDMDiagReport.zip, frozen as the standard benchmark input. - [ ] Measure each tool on that corpus: cold start to first visible row, all-channels-last-7-days wall clock, peak RSS, time to render 100k rows, and time to resolve descriptions on an Intune channel. - [x] Dump EventLogExpert's provider database schema and confirm the compressed-JSON-per-provider layout. Full spec posted; BLOBs are gzip JSON. - [x] Ran eventdbtool create: 15.8 MB, 1,179 providers, 151 s. Also found --offline-image, which builds a database from a Windows ISO with no host registry. Diff against a base-OS database still to run. - [x] Pull the EvtxECmd Maps corpus, count the maps, and measure how many cover channels our users actually open. Done 2026-08-09 without the box: 468 maps, 13 relevant (all Shell-Core/Operational), zero for MDM, Autopilot, AAD, AppX, Windows Update, or ConfigMgr. See Part 4.5. - [ ] Capture the interactions being copied (grouping, filter library, highlight rules) as notes and screenshots on this issue. Deliverable: a corrected feature matrix plus a baseline measurement table. Phase 1's benchmark gate compares against these numbers, not against estimates. ### Phase 1: performance and correctness of the live path - [x] Push filters into the XPath query. cmtraceopen_parser::event_query builds time, level, provider, Event ID and keyword predicates; the live view's time window is wired end to end. Level and provider remain client-side pending a deliberate behaviour change. - [x] <QueryList> / 10-per-<Select> batching, with each node repeating the other predicates because the service unions nodes. Exclusion lists are never split, since union would inseparably invert them. - [x] EvtNext batch raised from 16 to 256. Measured, and the measurement found a bug. A seven-day scan of 1,204 channels on real Windows hit EvtNext failing with RPC_S_INVALID_BOUND (0x800706c6) on one channel: the service refusing the size of the request, not its contents. The loop's response was to stop reading and return what it already had, and the caller received Ok, counted the events and showed the channel as fully loaded. A silently truncated channel presented as a complete one, reachable only because the batch was raised without the measurement this line already asked for. Fixed (fd6fdbf, 3c9cca1): the batch halves to a floor of 8 and retries rather than abandoning the channel; if a read still fails the records gathered are returned with the reason attached, since query_channel_inner and its wrappers now return ChannelScan { records, gaps } and a caller cannot take the records without being handed what is missing. The decision moved to event_log::fetch as a pure function so CI tests it on every platform instead of only on Windows. - [x] Move the hot path from EvtRender(EvtRenderEventXml) plus string parsing to EvtCreateRenderContext(EvtRenderContextSystem) plus EvtRender(EvtRenderEventValues), keeping XML render lazy for the detail pane. Resolved by measurement, and deliberately not implemented. Per-event cost on Security (280,393 events, the channel that is 71% of a full scan), from a temporarily instrumented build: | phase | us/event | share | |---|---|---| | EvtFormatMessage | 511.4 | 74% | | XML parse | 75.6 | 11% | | EvtRender (XML) | 55.8 | 8% | | record build | 49.9 | 7% | This item targets the bottom two rows, 19% at the absolute best. It cannot actually collect that: the parse is only removable by skipping the XML entirely, and EventData, the decoded .etl payload and every registered map are read from that parsed tree, so skipping it empties the detail pane. The item's stated justification, "removes two RPCs", does not hold either: every event needs an EvtRender and an EvtFormatMessage, and a System-context values render replaces the first rather than removing it. Net round trips saved: zero. Building it would cost the detail pane's fields to chase a rounding error. The real target is the row this phase never looked at. EvtFormatMessage is 74% of per-event time, one round trip per event asking the service to render a template. Phase 2 already has the replacement: cmtraceopen_parser::provider::render_description is built and tested, and the file path already uses it via describe_event. The live path does not - evtx_query_channels never clones provider_store - so it asks the operating system for every message. Wiring the provider store into the live path and falling back to EvtFormatMessage only when the database has no answer is the change worth making, and it is gated on Phase 2 shipping curated databases, since with no database loaded the fallback is the current behaviour. - [x] Channels queried concurrently with rayon. Results collected per channel and ordered afterwards, so concurrency cannot affect output. - [x] EvtQueryTolerateQueryErrors set, and a failed channel still reports 0 events so the gap stays visible. - [x] Replace the hard max_events cap with streamed incremental delivery to the virtualized list. The cap half was already stale: every call site passes maxEvents: null and the backend turns None into usize::MAX, so no hard cap was in force. The doc comment claiming a default of 1000 described behaviour the function never had, and is fixed (8964b2d). The multi-channel defect deferred out of #541 is fixed at the call site (38f66be): queryChannels sent every selected channel in one request, so the backend held every event of every channel before anything reached the screen. It now queries one channel at a time, like the store's two other load paths already did, which also isolates failure - a single request fails as a whole, so one unreadable channel used to discard every other channel's results. Delivery within a channel is now incremental (41b8ec6). query_channel_inner hands each fetched batch to a callback as it is built; a caller that takes the records never holds more than one batch, and a caller that ignores the argument still gets the channel whole, so the collecting wrappers are unchanged. ChannelScan carries delivered apart from records, because a streaming caller empties that vector and its length would otherwise report a fully read channel as holding nothing. The receiving half is the part that matters: an event channel promises no delivery, so each batch carries a per-channel sequence number and the store checks what it assembled against both the reply's stated count and the sequence run, reporting either shortfall as a coverage gap. A dropped batch is otherwise indistinguishable from events that never happened. Both checks are mutation-tested. Verified on Windows: 719 tests, clippy clean. Benchmark gate: satisfied for this tool. Harness is src-tauri/examples/evtx_scan.rs, run with cargo run --release --example evtx_scan --features event-log -- --days 7; peak working set is sampled by the caller, because a process cannot read its own peak after it exits. Recorded on RING0IVY24-01 (Windows 11 26200, Xeon E5-2697 v4, 4 cores, 8 GB), commit fd6fdbf, all channels, last 7 days. Three runs of the first configuration agreed within 1.5%. | | | |---|---| | channels enumerated | 1,204 | | channels not readable at all | 714 | | channels read only in part | 0 | | events | 404,769 | | wall clock | 267 s | | per event | 659 us | | peak working set | 1,066 MB | What the numbers say, as distinct from what was assumed: - One channel is the workload. Security is 286,401 of 404,769 events (71%) and 191.8 s of 267 s (72%). Querying channels concurrently does not help it, and neither does delivering results per channel: an operator waits three minutes on a single blocking call with nothing on screen. This is the argument for streaming within a channel, and it is now evidence rather than a guess. - raw_xml is the largest single cost but not a majority of memory. 429 MB of raw_xml against 296 MB of message and field text, so 59% of record bytes. Dropping it would be a large saving and would not on its own solve a 1 GB scan. An earlier claim in this thread that raw_xml "dominates" was overstated. - "Removes two RPCs" does not survive contact. Each event costs an EvtRender and an EvtFormatMessage. A System-context values render replaces the first rather than removing it, and the second is what produces the readable message, so it stays. The real gains available there are the XML parse and the memory, not the round trips. 659 us per event against two round trips is consistent with RPC being the floor. Still owed: the same scenario measured against a competitor. That belongs to the Phase 0 bake-off, which has not been run, so no comparative claim is made here. ### Phase 2: offline description resolution (the unlock) - [ ] Provider metadata capture on Windows: walk EvtOpenPublisherEnum / EvtOpenPublisherMetadata / EvtGetPublisherMetadataProperty / EvtGetObjectArray* and persist message tables, level/task/opcode/keyword name tables, and channel names. - [x] Portable provider database format. Reads EventLogExpert's .db directly (event_log/provider_db.rs), so no new format was needed and their corpus is usable as-is. Verified against a real 15.8 MB / 1,179-provider database. Found and fixed a real defect doing so: .NET serializes keyword masks as signed, and rejecting the negatives failed the entire MDM provider payload. - [x] Message formatting is cmtraceopen_parser::provider::render_description, pure and wasm32-clean. A partially rendered description is refused rather than shown, since a sentence with %4 left in it is less honest than the field summary it would replace. - [ ] Ship curated databases for the providers our users actually care about (MDM, Autopilot, ESP, AAD, ConfigMgr client, AppX, Windows Update). - [ ] Export and import databases so a customer can capture on their machine and send us the file. ### Phase 3: analysis UX - [x] Event map engine implemented against the real EvtxECmd schema, plus a .map loader and an XML-to-node adapter. Two behaviours verified against the tool itself: repeated elements are joined with ", ", and first-loaded wins. Domain maps for MDM and ConfigMgr still to author. - [x] Grouping by level, provider, channel, event ID or day, with counts per group and a group-by control (evtx-grouping). Drag-to-group and pivot still to do. - [x] Filter library: saved filters with validated persistence, plus export and import (evtx-saved-filters, evtx-filter-store). Favorites, tags and recents still to do. - [ ] Layered filtering: before-load (XPath), on-load, after-load, matching Event Log Explorer's model. - [ ] Per-row highlight colors driven by filter rules. - [x] Columns for Record ID, Task, Opcode, Process ID, Thread ID, User SID, Keywords and Computer, with a chooser covering visibility, order and width (evtx-columns). SID-to-name resolution and insertion-string columns still to do. These were dead on the file-open path until adfeb115, which is covered below. - [ ] Row tagging and bookmarks, reusing the existing marker store. - [ ] Quick-filter modes: multiple words, multiple strings, all-words, all-strings, EventID list with ranges, case sensitivity, show vs hide. - [x] Timezone correction and an explicit UTC toggle (evtx-time.ts, toolbar toggle labelled with the real offset). Fixed a live defect doing it: the list column printed the raw UTC string Windows wrote while the timeline and day-grouping used local time, so the same event showed a different clock depending on where you looked, with nothing on screen saying which. - [ ] Font sizing must go through logListFontSize from ui-store and getLogListMetrics, consistent with every other workspace. Partly done: the filter bar and the coverage banner derive from it; ChannelPicker, SourcePicker and EvtxDetailPane still hardcode about ten sizes between them, so raising the list font leaves those panes small. ### Phase 4: the differentiator, unified timeline - [ ] Interleave events from multiple .evtx files, multiple live channels, and multiple machines into one time-ordered view. - [x] Events and parsed text logs interleaved on one timeline (cmtraceopen_parser::unified_timeline, UnifiedTimelineView). Provenance is tracked per row, and an item with no usable timestamp is reported as unplaced rather than dropped or guessed at. - [ ] Ingest evidence bundles directly: MDMDiagReport.zip and mdmdiagnosticstool -area "DeviceEnrollment;DeviceProvisioning;Autopilot" output contain .evtx, text logs, and registry exports in one archive. Open the zip, get the whole picture. - [ ] Correlate by process, session, correlation/activity ID, and device, not by timestamp proximity alone. ### Phase 5: sources - [ ] Remote computer via EvtOpenSession, credentials through the OS credential path, never a config file. - [ ] Folder-of-logs with wildcard and multi-file merge. - [x] EventPayload hex-to-text conversion (cmtraceopen_parser::event_payload), wired into both the live and file paths. Encoding is decided by scoring UTF-16LE against UTF-8 rather than assumed, and refused when neither reading is convincing, because mojibake looks like data where raw hex is visibly unreadable. - [ ] Archived channel logs (Archive-*) and Volume Shadow Copy paths. - [ ] Damaged and dirty file handling: verify what evtx 0.12.2 recovery actually covers and surface unrecoverable chunks as a coverage gap rather than silence. ### Phase 6: output and live - [x] Export to CSV, TSV, JSON and raw event XML, with spreadsheet formula injection neutralized since event content is attacker-influenceable. HTML and streaming still to do. The XML export emitted JSON on the file path until adfeb115. - [ ] Headless CLI export, matching what FullEventLogView's /scomma style switches enable in scripts. - [ ] Live tail preferring EvtSubscribe (push) over polling, falling back to poll where unavailable. - [ ] Clear channel behind explicit confirmation and an elevation check. ### Phase 7: diagnosis - [ ] Operational rule engine over normalized events: Autopilot, ESP, MDM enrollment, ConfigMgr client health. Reuse the dsregcmd and esp rule pattern. Explicitly not security detections. - [ ] Error code enrichment from error_db on event data fields. - [ ] "What went wrong" summary spanning events and text logs. - [ ] Redaction on export via the shared redaction module. --- ## Constraints - cmtraceopen-parser stays pure Rust and wasm32-compatible. All wevtapi work belongs in src-tauri/src/event_log/, never in the parser crate. Message formatting and map application belong in the parser crate so they work cross-platform. - Windows-only paths need #[cfg(target_os = "windows")] gating plus non-Windows stubs, and cannot be verified on the macOS dev host. Live-API changes must be verified on real Windows or by the Windows CI job before any acceptance claim. - Licensing: we are MIT. EventLogExpert and EvtxECmd are both MIT, so their database format and map schema are legitimate to implement against, with attribution in NOTICE. Hayabusa is AGPLv3, so no code reuse. Sigma rules are DRL 1.1. Timeline Explorer and Event Log Explorer are closed source; interaction models and architecture only, never code. See Part 4.5 for the per-item clearance. - Fixtures stay synthetic or sanitized. No real tenant data, user names, SIDs, or serials. - Missing, denied, capped, or skipped channels are coverage states and must be surfaced as such, never rolled into an implied "healthy". ## Sources - FullEventLogView - microsoft/EventLogExpert - Event Log Explorer features - Yamato-Security/hayabusa - WithSecureLabs/chainsaw - EricZimmerman/evtx (EvtxECmd) and Timeline Explorer - omerbenamram/evtx - mttaggart/venture - acquiredsecurity/forensic-timeliner - Advanced XML filtering in the Windows Event Viewer - Original request thread: @awakecoding, 2026-08-05
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestepicTracking issue for a larger body of workTracking issue for a larger body of workrustPull requests that update rust codePull requests that update rust codewindowsWindows platform relatedWindows platform relatedworkspaceWorkspace UI areaWorkspace UI area
Activity
[-]Event Viewer: reach and beat NirSoft FullEventLogView (full RE of v1.81 x86/x64)[/-][+]Event Viewer: become best in class (RE of FullEventLogView + competitive landscape)[/+]adamgell commentedon Aug 9, 2026
Updated with Part 2 (competitive landscape: Microsoft EventLogExpert, Event Log Explorer, EvtxECmd + Timeline Explorer, Hayabusa, Chainsaw, Venture, parser benchmarks), Part 4 (strategy), and a re-sequenced 7-phase plan.
Two changes of substance versus the first version:
adamgell commentedon Aug 9, 2026
Added Part 4.5: The steal list and Phase 0: hands-on bake-off.
The material change is licensing clearance. Both formats we most want are MIT:
Channel-Name_Provider-Name_EventID.Map, YAML, with XPath-bound variables substituted intoPayloadData1..6templates andLookupstranslation tables for decoding numeric codes. Implementing the schema as-is means the existing community map corpus loads unmodified, and we only author the domain maps nobody has written (MDM, Autopilot, ESP, ConfigMgr).EventDbTooldoes create/diff/merge, and the diff-against-base-OS step is the operational pattern worth stealing, since it yields a small database containing only a given product's providers. Manny-MSFT/EvtxExporterCLI is precedent for re-deriving the reader.Plan is now: implement a Rust reader for their database format (so existing databases just work) plus our own capture writer.
Phase 0 exists because the Part 2 matrix was built from documentation and source reading, not from running the tools. It produces a corrected matrix and a baseline measurement table, which Phase 1's benchmark gate then compares against.
To be explicit about the boundary: we are taking formats, schemas, and interaction models. No code from AGPLv3 (Hayabusa) or closed-source (Timeline Explorer, Event Log Explorer) sources. Attribution goes in
NOTICE.adamgell commentedon Aug 9, 2026
Correction to the map corpus claim, measured against
EricZimmerman/evtx@masterrather than assumed.I said we would "inherit hundreds of community maps instead of writing them." That is wrong for our domain.
468 map files exist, but the corpus is security and DFIR oriented: Security (98), Application (52), System (36), Sysmon (29), Defender (19), Symantec (18), then Varonis, Cisco AnyConnect, SentinelOne, TerminalServices.
Coverage of the channels our users actually open is effectively zero. No maps for
DeviceManagement-Enterprise-Diagnostics-Provider,ModernDeployment-Diagnostics-Provider,Provisioning-Diagnostics-Provider,AAD/Operational,AppXDeployment-Server,WindowsUpdateClient, or ConfigMgr. The only relevant hits are 13 maps forMicrosoft-Windows-Shell-Core/Operational(9701-9712, 28115), which do matter because they cover RunOnceEx and OOBE shell activity during Autopilot.Schema is confirmed correct against real files. A minimal map:
Richer maps (Security 4624) bind several XPath variables per property and substitute them into templates like
"%domain%\\%user%".The decision does not change, but the reasoning does. Adopt the schema for interoperability (our maps work in EvtxECmd and Timeline Explorer, theirs work in ours), because it is a proven design for the heterogeneous-EventData problem, and because the device-management map corpus is unclaimed. Writing it is a contribution and a moat, not a shortcut.
Part 4.5 and the Phase 0 checklist are updated. That Phase 0 item is now closed without needing the dev box.
adamgell commentedon Aug 9, 2026
Phase 0 progress: bake-off host provisioned, provider DB format reverse engineered
Host
RING0IVY24-01is up and I have SSH. Recording what landed and, more importantly, the complete provider database format, which unblocks Phase 2.Bake-off host baseline
Corpus already present on this box, so it is usable for map authoring immediately:
DeviceManagement-Enterprise-Diagnostics-Provider/AdminShell-Core/OperationalAAD/OperationalProvisioning-Diagnostics-Provider/AdminModernDeployment-Diagnostics-Provider/AutopilotCaveat for the benchmark gate: 4 logical cores. Our parallel channel scan (Phase 1) will show a much smaller advantage here than on a typical 8 to 16 core workstation, so this host will understate the parallelism win. Record core count alongside every number.
Installed
Event Log Explorer remains a manual step (interactive trial licence).
EventLogExpert provider database format (complete)
Obtained by building a real database on this host:
eventdbtool create ivy01.dbproduced 15.8 MB covering 1,179 providers in 151 seconds.Schema
One table. That is the whole thing.
VersionKeyis a content hash, formatvk1:<base32>, so the same provider at different OS builds coexists as separate rows.The BLOBs are gzip-compressed JSON
Every BLOB begins
1f 8b 08. Decompressed shapes, taken from the real MDM provider row:Events(554 KB decompressed for the MDM provider):[{"Description":"MDM Enroll: Certificate policy create message failed. Result: (%1).", "Id":2,"Keywords":[576460752303423488],"Level":2, "LogName":"Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Enrollment", "Opcode":0,"Task":0,"Version":0, "Template":"<template xmlns=\"http://schemas.microsoft.com/win/2004/08/events\"> <data name=\"HRESULT\" inType=\"win:HexInt32\" outType=\"win:Win32Error\"/> </template>"}]Messages(223 KB):[{"ProviderName":"...","RawId":1342177282,"ShortId":2, "Text":"Error\r\n","Template":null,"Tag":null,"LogLink":null}]Tasks,Keywords,Opcodesare flat maps:{"1":"None"} {"1":"Error","2":"Debug","4":"Trace"}Why this is better than expected
Descriptioncarries the%1insertion placeholders, andTemplatecarries the ETW manifest template with typed fields includinginTypeandoutType. So the database gives us not just message text but the declared type of every field, including formatting hints likewin:Win32Error. That means offline rendering can format an HRESULT as an error code rather than a raw integer, and it feedserror_dbenrichment directly.Architecture consequence
rusqlitelinks C SQLite, so it cannot go incmtraceopen-parserunder the wasm32 purity rule. The split:src-tauri/src/event_log/reads the.db(rusqlite), gunzips (flate2), deserialises (serde_json), and hands typed structs across.cmtraceopen-parserowns message formatting: placeholder substitution, template-typed field rendering, keyword and task and opcode resolution. Pure, wasm-safe, and testable without a database.Two findings that change the plan
1. Databases can be built from a Windows image, fully offline
eventdbtool create --offline-imageaccepts a mounted volume, an extracted folder, a.wim/.esd, an.iso(with--wim-index), or a.vhdx/.vhd(auto-mounted read-only), and reads no host registry or host files.We do not need users to capture anything for base OS providers. We can generate databases per Windows build in CI from ISOs and ship them. Capture on a real machine is then only needed for third-party providers such as the ConfigMgr client.
Combined with
--skip-providers-in-file, the shipping pipeline is: build a base OS database from the ISO, then diff a managed machine against it to get a small ConfigMgr-only database.2. Exported
.evtxcan carry its own message stringseventdbtool showresolves an exported.evtx"via its siblingLocaleMetaData/*.MTAfiles". Windows produces these when a log is archived rather than plain-exported.Our
scripts/collection/Invoke-CmtraceEvidenceCollection.ps1:924currently useswevtutil.exe epl, which does not emit locale metadata. Switching to the archive form would make every collected bundle self-describing, so descriptions render on macOS with no provider database at all. That is a small change to the collection script with a large payoff, and it needs verifying on this box next.Phase 0 checklist status
eventdbtool createend to end (15.8 MB, 1,179 providers, 151 s)wevtutilarchive versus export for LocaleMetaDataadamgell commentedon Aug 9, 2026
wevtutilarchive verified, and the MTAFile container format is decodedAnswering the open question from the previous comment. Short version: it works, it is cheap, and the format is tractable in pure Rust.
It works
eventdbtool showagainst the archived file then reports:So an archived
.evtxcarries the message metadata needed to render descriptions with no provider database and no registry.It is cheap
Measured across seven real target channels on IVY01:
Raw cost looks high, but the number that matters is the shipped bundle:
241 KB per evidence bundle for full offline description resolution. Note MTA content is scoped to the events actually present in each log, not the whole provider, which is why the two DeviceManagement channels produce different files with different hashes.
MTAFile container format (verified byte-exact on two samples)
Sections are contiguous and the last one ends exactly at EOF on both samples. Always three:
EVT,MSG,PUB.Each section body opens with
FF FF FF FFfollowed by a repeatedu32, which reads as an index or offset table with a null sentinel, then a string heap.MSGyields the level names (Log Always,Critical,Warning);PUByields the provider name. SoEVTis event definitions,MSGis message strings,PUBis publisher metadata, as the names suggest.The container is pure byte parsing with no OS dependency, so an MTA reader belongs in
cmtraceopen-parserand stays wasm32-safe. The inner section layouts still need reverse engineering, but they are ordinary index-plus-heap structures, not a compiled resource format.Recommendation: change the collection script now, before the parser exists
scripts/collection/Invoke-CmtraceEvidenceCollection.ps1:924currently runs onlywevtutil.exe epl. It should also archive.The argument for doing this immediately, ahead of any parsing work: metadata cannot be recovered after the fact. A bundle collected today without LocaleMetaData is permanently unresolvable once that machine is gone or rebuilt. Every bundle collected between now and whenever the reader ships is lossy for no reason. The change is one command and 241 KB.
Two things to get right when implementing:
wevtutil alwritesLocaleMetaData\relative to the exported file, so the collection layout and the bundle packer both need to carry that subdirectory./l:en-USproduced_1033.MTA. Locale handling needs deciding for non-English hosts rather than hardcoding.How this changes Phase 2
Two complementary paths, not competing ones:
Provider databases stay the primary path since the format is fully known and
--offline-imagemeans we can generate them without any capture. MTA is the fallback that makes an arbitrary customer bundle self-describing even when it contains providers we never shipped.wevtutilarchive versus export for LocaleMetaDatafix(parser): join repeated elements, matching verified EvtxECmd behav…
feat(event-log): load EvtxECmd .map files from disk
fix(parser): render map templates in a single pass
feat(parser): build XPath queries so the service does the filtering
43 remaining items
adamgell commentedon Sep 19, 2026
Status update: both follow-ups are now on
main.a06cecda(test: explicitly requested EVTX fixtures fail instead of skipping (Refs #636) #637): explicitly requested EVTX/DNS/JAMF fixtures fail closed; vacuous passes are announced with an unmistakableSKIP:banner.a7a21428(feat(event-log): stop control and honest coverage for large explicit loads (Refs #635) #639): frontend owns explicit loads (ADR-005, Proposed→landed), with the Stop control andcancelledcoverage gaps; backend paging deferred per the ADR's revisit criteria.The epic stays open for the remaining ledger items (real-world validation, provider-database coverage, remote/VSS/archive acceptance, packaging, privacy, Preview-label criteria).
fix(event-log): keep an arranged insertion-string column offerable (#539
fix(event-log): accept only the canonical insertion-string column id (#…
fix(dsregcmd): make the stage seam test-only, scoped, and bounded (#539…
adamgell commentedon Sep 24, 2026
Taking the "Privacy and publication gaps" checkbox from a bare line to something decidable, since it is the one ledger item that does not need a Windows lab.
What I traced.
EvtxFilterBar.tsxexportVisible()selects the visible records and streams them viastreamEventLogExport({ records, format, destination, sourcePaths, … })to a destination the operator picks. There is no redaction call on that path.What the exported record carries (
src/types/event-log.ts, the record this serialises):computermessagesourceFilecorrelationActivityId,provider,channelWhy this is a gap and not just "raw evidence". Three other egress families in this repository project at a boundary — DsRegCmd (
dsregcmd/mod.rs:58), Autopilot (redacted_export_projection), ESP — and their projections exist precisely because the hand-off workflow is the leak. The event-log export is the one family with no projection, and it is the one whose data is most consistently rich in identity, because events are captured verbatim from the machine.The part that is a decision, not a defect — and the reason this checkbox is still open rather than obviously fixed. For
dsregcmd /statusand the Autopilot snapshot, identity is incidental to the diagnosis, so masking costs little. For an event record it is frequently the diagnosis: a failed logon is identified by the account and SID in the message. A default-on projection here would remove the value the export exists to carry.So the item reduces to a choice with three shapes, and each is small:
ExportandExport (redacted), with the projection reusing the shared masking grammar rather than a fourth private one (the lesson recorded in fix(intune): stop the Compliance lane exporting unmasked SIDs and eight other identity shapes #546).My read: (2) is the one that matches what the repository has already decided elsewhere, and (1) is the cheapest honest step if the answer is "raw is correct here". Either way this no longer needs anyone to re-derive it.
adamgell commentedon Sep 24, 2026
Ledger correction, since this line is now stale in a way that matters for anyone reading the epic for open work.
The ledger says: "Backend-owned paging for very large explicit loads (tracked in #635)", unchecked. #635 is closed — resolved by
a7a21428via #639, with ADR-005 recording the ruling: the frontend owns explicit loads, a Stop control plus cancelled coverage gaps close the control gap, and backend paging is deferred with written revisit criteria.So this is not an open engineering item; it is a settled decision with a revisit trigger. The checkbox can be ticked, or — better, I think — reworded to say "backend paging, revisited when the ADR-005 criteria are met", because ticking it would hide the trigger that says when the question comes back.
Two stale items in two days, and both were mine to find by reading rather than assuming. #635 here, and
'/Users/Adam.Gell/.omp/agent/memories/--Users-Adam.Gell-repo-cmtraceopen--/memory_summary.md'still saying #583 is pending merge six days after it merged. Neither was wrong when written; both stopped being right when their subject moved, and neither announces that.Worth saying plainly because the epic is the document anyone reads to decide what to work on next: an unchecked box labelled "tracked in #635" now points at a closed issue, which reads as a live dependency and is not one. If the ledger's other lines have similar tracked references, they are worth the same check.
adamgell commentedon Sep 24, 2026
Closing my own suggestion rather than leaving it as a task for someone else.
I extracted every issue/PR reference in this epic and checked each one's state:
So
#635was the only reference pointing at something that had moved, and the ledger's "superseded construction work" section is accurate as written. The five superseded entries are worth a word of credit, actually: whoever wrote them recorded why each was superseded and the commit that did it, which is why this check took one command instead of a reading exercise.That leaves the checkbox itself as the only decision here: tick it, or reword it to name the ADR-005 revisit criteria so the question comes back when it should.
adamgell commentedon Sep 24, 2026
Checked the two ledger items that touch packaging and publication - "Installer, signing, and cross-platform packaging" and "Privacy and publication gaps" - and both resolve to external steps rather than code. Recording the evidence so the boxes can be read accurately.
Scoop: automated, current, and correct.
bucket/cmtrace.jsonandbucket/cmtrace-lite.jsonare both on1.6.0, and their recorded hashes match the release registry exactly:The mechanism is real and worth naming, because it is why this channel stays current:
scoop-publish.ymlruns onrelease: publishedand callsUpdate-ScoopManifest.ps1, which takes eachsha256from the release asset digest - the same field I verified against - and then cannot push it, because the workflow comment records that the Protect ruleset rejectsGITHUB_TOKENon the default branch. So it opens a pull request instead, which is also why it has to kick its own checks. A bump that needed a human to notice is exactly the failure the cask had, and this one does not.Homebrew: not automated, deliberately, and the reasoning is already written down.
Casks/README.mdsays the cask "needs no release automation" because autobump inHomebrew/homebrew-caskis opt-out, Homebrew's own action re-checks every few hours, and "abrew bump-cask-prworkflow in this repository was considered and rejected" - it would duplicate work Homebrew does for free. I had assumed the opposite before reading it, which is the reason to read the README before proposing the workflow.So the remaining work in both items is submission, not code:
ScoopInstaller/Extrasbucket/README.mdHomebrew/homebrew-caskNeither item is codeable from here, and neither is blocked on anything in this repository. They are two submissions to other projects, one of which (Scoop) removes a
scoop bucket addstep from the install instructions and the other (Homebrew) is what makesbrew install --cask cmtrace-openwork at all. Worth knowing that the packaging code is complete and the ledger's remaining gap is publication.One correction to the ledger while I am here: the paging item is ticked now, resolved as deferred by ADR-005, and the provider-database item is under test in #749.
adamgell commentedon Sep 24, 2026
Checked the ledger's "Privacy and publication gaps" item against the contract that governs it, and the notable result is that its own audit table has gone stale in the direction of understating progress.
ADR-004 Revision 1 contains the audit - a table of "the surfaces that carry data out today", with a
Projected?column, and the sharpest sentence in any ADR here: "The operator therefore reads a masked screen and, one button away, writes cleartext to a file of their choosing. The product currently makes a privacy promise on screen that its export contradicts."Two of those rows cite tracking issues, and both issues are closed:
crates/cmtraceopen-parser/src/esp/export.rs:90now builds the envelope withredacted_export_projection(snapshot), and that module's header states the projection happens first - so the frontend can no longer write an unredacted session, which is the failure the row describes.DsregcmdWorkspace.tsxasks for the projection rather than assuming it, and the changelog records the JSON summary, the rendered summary and the copied status text as no longer carrying tenant id, domain, device id, thumbprint, UPN or user SID.So the contradiction the ADR named - a masked screen beside a cleartext export - is resolved for both tracked cases, and the contract says it is not.
The rows it marks "No" that have no issue behind them are, on inspection, the in-process surfaces: the IPC command return and the
emitstream carry the analysis to this application's own frontend, which masks it for display, and every boundary out of the application now projects. That is defensible rather than outstanding, and the table's column heading ("carry data out") is what makes it read as a defect.One surface is absent from the table entirely: the event viewer's export. It writes CSV, TSV, JSON, XML, HTML and raw XML to a file the operator chooses, and events carry account names and user SIDs. I do not think it is a gap, and the reason is the ADR's own criterion rather than a judgement: the event viewer masks nothing on screen, so there is no screen-versus-export contradiction to resolve - the product's promise there is "this is what the log says", and redacting it would defeat the workspace. Worth recording as a decision though, because the same reasoning would not hold if a future view started masking.
What I would flag as the actionable part: the table lives in an accepted ADR, and it currently tells a reader that two closed gaps are open. The ADR's own pattern for this is a revision - Revision 2 exists for exactly that reason - so a short Revision 3 recording the two closures would keep the contract honest. That is your call, since the revisions are recorded as owner decisions, and I have not touched the file.
Net: the privacy item is materially further along than its ledger line suggests, and the remaining surface question is a decision to write down rather than work to do.
adamgell commentedon Sep 24, 2026
Proposing the last ledger row: "Criteria for removing the Preview label"
That row is the only item in the ledger with no issue, no pull request, and no mention anywhere in the repository — I checked all three before writing this. Every other row maps to something in flight:
mainas the row saysWhat I would propose
The label comes off when each row above is closed, and the closure is evidenced rather than asserted. Two rules make that testable instead of aspirational:
The second rule is the one worth arguing about. Everything else on the ledger is a gate this project can meet by working; that one is a gate only someone outside can open, and writing it down is what stops the label coming off because the checklist looked complete.
Why this is a comment and not a pull request
It is a product decision, and the ledger is where it belongs. I have not edited the ledger — the checkbox and the wording are yours.
test(event-log): check the packaged provider databases against their …
test(dsregcmd): bound slow-storage responsiveness checks (#669)
docs(adr): list ADR-005 in the decisions index (#759)
docs(event-log,dsregcmd): add workbench and verdict-first UI handoff …